Skip to main content
Back to writing

A Long-Lived Repository Ends Up Holding Several Products at Once

A practical account of governing websites, web prototypes, native clients, OpenSpec, Figma, and design contracts so developers and coding agents can find the current source of truth.

I recently cleaned up a local project that had been in development for a long time. Its root directory contained a website, an interactive web prototype, a native client, image mockups, Figma files, and several generations of specs. None of them was an empty shell. The website still started. The prototype still worked. Native development was active. Some old product documents were more complete than the current ones.

That was the problem. When the next request arrived, which source should a developer or Codex trust? An agent that started with the largest directory or the most complete page could do careful work in code that no longer shipped.

The confusion did not come from one bad decision. It grew out of choices that were useful at the time.

At the start, speed mattered most. Research, copy, pages, images, and scripts lived in one workspace. One person could move from user feedback to an interface change without crossing repository boundaries. A web implementation made it cheap to test a flow. Keeping everything together was efficient, and there was no reason to predict which parts might need separate repositories later.

Then the target platform became clear. Before building the native client, we used the web to test a client-like information structure and navigation flow. We did not delete that prototype when native development began. Its flows, page states, and visual experiments were still useful references.

The same code changed jobs several times. It first carried the product. It later tested flows. Eventually, it explained historical design choices. The files did not need to change for their authority to change. We kept the old pages because they could explain why a flow existed and help us compare later revisions. Keeping them did not mean maintaining them as a product.

Git records when a line changed. It does not declare that a directory has become reference-only. Research, rejected plans, and image drafts have the same problem. They can remain useful without retaining the authority to define the next feature.

People can hold that distinction in memory. I may remember which directory is retired, which image is only a candidate, and which document was later overturned. Codex does not know any of that unless the workspace says so. A request such as “change this page” can find matching pages in the website, the web prototype, and the native client. All three implementations may look reasonable. All three may still run.

This failure often does not look like a bug. Syntax checks and tests can pass while the change lands on a product surface that no longer ships. Ordinary engineering checks cannot detect that the code is valid but the selected source of truth is wrong.

The cleanest answer would be to split the website, client, and historical prototypes into separate repositories. That was not the first answer we chose. During active product change, a split also changes scripts, paths, references, and build assumptions. We still needed the old material to review earlier design decisions. Instead of moving everything immediately, we first gave each product surface an explicit identity.

The native client that currently ships became active. Material still used by design, testing, or research became supporting. Old websites and prototypes that could only be consulted became archived. The registry did not contain product requirements. It told tools where to enter by default.

The registry also had to be enforceable. Registered paths had to exist. The native client could not regain a dependency on the web runtime. Default commands could inspect only the current product surface. Archived directories could not enter release acceptance. With those checks, the boundary no longer depended on someone remembering it. A violation made the task fail.

Codex task instructions became shorter as a result. We had previously included a long project history to prevent missing context. Now a task needed four inputs: the target product surface, the allowed sources of truth, the archived areas to exclude, and the required level of verification. The agent received less material and made a better decision.

We also separated documents by the question they answer.

To learn what the product should do, read the latest confirmed decision and OpenSpec. To learn what it does now, read the native runtime, its automated tests, and the evidence from the current verification run. To inspect work that is still open, read the research, image prototypes, and current Figma candidates. To see how far a version has progressed, read the build, acceptance, and release records.

These facts cannot stand in for one another. An entry in Figma shows that a design direction exists. A clickable Flow shows that a demonstration path works. Neither proves that the native client handles the same data, failures, and platform behavior.

The web prototype eventually kept one job: validating flows. It was fast to change and made page-state combinations easy to test. But browsers and native clients do not share the same scrolling, fonts, inputs, keyboards, safe areas, or system controls. On the target platform, the keyboard can cover a page that looked correct in the browser. A sheet can behave differently. A touch target can be too small.

The web prototype therefore continued to test flow and information structure. It stopped being the visual master for the native client, and it could not prove that a feature had been implemented.

We later used image prototypes to discuss visual direction. We fixed the task and page state first, generated or drew a screen close to the target platform, confirmed the direction, broke down the layout, compared it with native screenshots, and then returned to code. Images were good for composition. They did not guarantee correct text, production data, or an approved entry point. Familiar colors and controls did not make the image a runtime contract.

Once image prototypes entered the process, observable behavior moved into OpenSpec. The spec described trigger conditions, page states, error feedback, save timing, and capabilities that were explicitly out of scope. Images and Figma could change many times. A behavior could not enter the runtime until we confirmed it and accepted the change.

The visual and interaction specification answered a different set of questions. It recorded stable rules for the target platform, including layout hierarchy, component meaning, touch areas, sheets, loading states, and empty states. OpenSpec still owned page fields and state consequences. Code and verification evidence still determined whether something was implemented. The visual specification did not need to restate every page.

Figma handled design review, not product behavior. Foundations held base rules. Components held reusable parts. Screens held pages. Prototype contained only the current candidate. Flow contained clickable paths. Archive kept replaced work that still had audit value.

Candidates also needed status. Exploration meant the direction was still open. Current candidate identified the version under review. Approved meant the design had been accepted. Implemented required code and tests. Device verified required evidence from the device. A clickable canvas and a usable runtime were not the same state.

Figma could expose a gap in the spec. After confirming the missing behavior, we created or amended an OpenSpec change. The direction mattered: the canvas could inform the spec, but it could not silently override accepted behavior. Otherwise we would merely replace “the old website defines the product” with “the newest canvas defines the product.”

A smaller kind of drift appeared across implementation and design. State names, short labels, semantic colors, and icon meanings could exist in native code, image-generation scripts, and Figma at the same time. Changing one copy left two old versions behind.

We introduced a versioned design contract for those shared facts. It did not repeat the product requirements or describe a complete screen. It stored facts that had multiple real consumers and suited machine-readable form: state enums, short labels, semantic values, prohibited visual patterns, and the related Figma anchors.

An update began with the contract and its version. Scripts then generated native data and deterministic prototype snapshots. We updated the Figma review anchors and ran automated checks, development-tool verification, and device verification. A direct edit to a runtime constant produced a generation diff. A design state changed without a contract update remained a candidate.

We did not build automatic two-way synchronization. If an exploratory Figma file could write directly into the contract, an unconfirmed drag or text edit could become a product rule. We kept one explicit approval point and a fixed direction from contract to generated files and runtime.

The contract also needed a narrow scope. A one-off layout experiment belonged in the design file. So did a detail with no second consumer. Structure paid for itself only when a value crossed runtime, prototype, and acceptance boundaries and had a real drift risk. Otherwise, governance would turn from scattered material into a form that had to be filled out for every pixel.

The work eventually separated into two paths.

The exploration path began with a problem or feedback. It moved through research, a web or image prototype, and then the current Figma candidate and Flow. Rejection and rollback were expected. Every attempt did not need to change the main spec.

The implementation path began after confirmation. Observable behavior entered OpenSpec. Design facts at risk of drift entered the versioned contract. Scripts generated runtime data and prototype snapshots. Figma kept the matching review anchors. The native client implemented the contract, followed by automated checks, development-tool verification, and device verification.

Release status was split by evidence as well. Implemented, Automated verified, DevTools verified, Device verified, Uploaded, In review, and Released described different stages. Written code, passing tests, device checks, upload, and public release could no longer collapse into one claim that the work was “done.”

A candidate also had to point to an exact runtime version, build manifest, and code position. That link let us confirm that the locally accepted build was the build later uploaded. If the working tree contained unrelated changes, the record said so. A vague “tests passed” could not speak for the entire tree.

We did not design this governance system in advance. We needed active and archived only after the web and native surfaces began to coexist. Observable behavior moved into OpenSpec after image drafts started carrying unconfirmed states. Repeated drift across several consumers created the need for a versioned contract. As Figma grew, design candidate, approval, implementation, and device verification also needed separate labels.

If I took over a similar workspace now, I would first identify the one current product surface and reduce the authority of historical material. I would then separate four questions: what should happen, what happens now, what remains under exploration, and what has actually been released. Once those boundaries were stable, I would choose one change-prone feature and run it all the way through the spec, design contract, prototype, Figma, and runtime.

Splitting repositories can still be the right later move. It becomes easier when different clients have stable owners, independent release rhythms, and clear interfaces. While those boundaries are still changing, marking the sources of truth and default entrypoints is usually more useful than moving files immediately.

A long-lived project looks less like a ship and more like a workbench that keeps collecting material. Old tools, drawings, and test pieces do not all need to be thrown away. The team does need to mark which tools are used for production today. When a new developer or Codex enters the workspace, they must know what may still change, what is reference-only, and which records prove how far the current version has progressed.

The old code still exists. It no longer gets the final word.

If you use Codex or another coding agent, start by marking the current product surface, historical material, and each level of verification before adding more rules. If the task itself also needs an independent, inspectable authority layer, see the Stop That Shit source and English product guide. It governs authority for the current task; it does not replace workspace truth governance.