← Liana Grigory
Insights

Copy the reason, not the constant

When a working feature moves from one app to another, the structure and the logic transfer. The constants do not, because every number is a measurement of the place it came from.

By Liana Grigory · 25 September 2026 · 8 min read

Across the apps I build there is a standing rule that a new app starts by copying the screens that already work in an earlier one. Messaging, the bottom tab bar, the calendar, the search filters, the native shell: each of those was paid for once, in time and in bugs, and there is no reason to pay for it again. Different niche, different palette, same layout and same logic. The class names come across unchanged so the two apps can be read side by side.

That rule has a second half, added after the first half went wrong: copy the logic and the structure; never copy a constant. This piece is about why the numbers are the one part of working code that does not travel.

The number that was right

The messaging thread in Tegula Stone sizes itself with calc(100dvh - 190px). The value is correct. In Tegula the document itself scrolls, and the thread has to fit in exactly the space left once the header, the composer and the tab bar have taken theirs. Someone measured those, added them up, and wrote down the total.

When the thread view was copied into Care Royal, the rule and the value came across together. Care Royal's shell is built differently: the document is held still and an inner container scrolls. Dropped into that layout, a thread sized against the viewport created a third nested scroll region inside the other two and pushed the menu bar out of place. Nothing about the CSS was wrong. It was correct about a page that was not the page it was now on.

What makes this worth writing about is that the logic copied perfectly. The inbox and thread as two separate screens, the back control, the message list, the composer pinned to the bottom — all of it transferred without change and worked. The only thing that broke was a single number, and it broke because it was the only part of the feature that was a measurement rather than a decision.

A constant is a conclusion with the premises deleted

Logic generalises because it describes relationships: the composer sits below the messages, the list scrolls and the header does not. A constant describes a particular instance of those relationships. 190px is the sum of three heights in one layout, and the expression that produced it — header plus composer plus bar, in a document that scrolls — was thrown away the moment someone typed the answer.

That is fine inside the app where it was measured. The premises are still true there, and if they change, someone will probably notice the thread misbehaving. Copied elsewhere, the number arrives without its premises, and looks exactly as authoritative as it did at home.

Pixel offsets are the visible case. The same thing is true of nearly every constant that looks like configuration:

  • Timeouts and retry counts, which encode how slow a particular dependency was on a particular network.
  • Rate limits, which encode one product's expected traffic and one abuse pattern. A limit sized for a public contact form is wrong for an authenticated messaging screen, in either direction.
  • Page sizes and query limits, which encode how large a record is and how much of it a screen displays.
  • Cache lifetimes, which encode how often the underlying data changes and how much staleness that product can tolerate.
  • Thresholds of every kind: when to paginate, when to warn, when to split a document.
  • Stacking orders, which only mean anything relative to every other layer on the same page.

Every one of these will usually still “work” after copying, in the sense of producing no error. That is what makes them dangerous. A wrong function tends to fail visibly. A wrong number fails as a slightly off experience, on a particular device, at a particular size, with the keyboard open, for the users you are least likely to be.

How I copy now

The method is short, and every step exists because skipping it caused a real problem.

1. Copy structure and logic verbatim

Class names, element hierarchy, the state machine, the order of operations. Do not improve them on the way across. The value of a copy comes from being recognisably the same thing: when the original gets fixed, the fix can be applied to the copy by reading one against the other. A copy that was tidied in transit can no longer be compared, and every future fix has to be rediscovered.

2. Re-derive every constant

For each number in the copied block, write down what it means, then work out what that meaning comes to in the new app. Where possible, replace the number with the expression that produces it, or better, with a layout that does not need it at all. A thread that is a flex child with min-height:0 inside a container that already has the right height needs no arithmetic, and so has no arithmetic to be wrong about. The best outcome of re-deriving a constant is often deleting it.

3. Copy the comments too

Comments that explain why are the premises the constant lost. They are the only record of what the number was measured against, and they are the part that stops the next person from “cleaning up” something that was deliberate. A copied block with its comments stripped is a block that looks arbitrary, and arbitrary-looking code gets changed.

If the original has a magic number with no comment, that is a defect in the original. Write the comment there first, then copy it.

4. If the original is wrong, fix it in both

Copying is a second review of the source. Reading a feature closely enough to transplant it is the most careful reading it usually gets, and it regularly turns up bugs in the original. A bug found while copying is a bug in the source, and it gets fixed there as well as in the copy. Otherwise the two apps diverge in exactly the places where one of them is known to be wrong.

5. Record the lineage

The commit that brings a feature across says which app it came from. The copied CSS lives in one contiguous block headed with its source, rather than being scattered through the target stylesheet. Both exist so that anyone can later put the two side by side and see what is shared, what was deliberately changed, and what drifted.

Why not a shared library

The obvious objection is that all of this is what a shared component package is for. Write the thread once, import it twice, and there is nothing to copy.

For a small portfolio I think copying with lineage is the better trade, and it is a trade rather than a free win. A shared package couples the release cycles of the apps that depend on it. Two products at different stages, with different shells and different scroll models, would need the component to be parameterised for both, and the parameters are precisely the constants discussed above, now hidden behind an interface. A change made for one app becomes a regression risk for the other, which makes every change slower.

The cost of copying is drift, and drift is real. I wrote about it in two footers, one site: copies diverge without anybody editing anything wrongly. What keeps it manageable is the discipline above. Identical class names make the copies diffable. Contiguous blocks with a named source make them findable. Fixing bugs in both places keeps the shared parts shared. If the number of copies grows to the point where that stops being tractable, that is the point at which extracting a library pays for itself, and not before.

What travels and what does not

The general version is simple enough to hold in your head while copying anything. Decisions travel: what goes where, what scrolls, what happens on tap, what order things are written in. Measurements do not: how tall, how long, how many, how often. The decisions are why the original works. The measurements are why it works there.

So when you lift working code into a new home, the questions to ask of each line are different. Of the logic: is this the same problem? Of the numbers: what was this measuring, and is that thing the same size here? The second question takes longer, and it is the one that would have kept a menu bar where it belonged.

Written by Liana Grigory, Entrepreneur and Software Engineer, from work on The Care Royal, Tegula Stone and Unified Savers. Everything above describes decisions actually made on those systems, including the ones that turned out to be wrong.

Home · Terms of Use · Privacy Notice