Integrates the frontend template sync (a0fbafb → 5434760) into the TechLog UI migration. Merged in this direction so every conflict is resolved and proved in the worktree; main is only fast-forwarded afterwards and never holds a state that was not verified here. 15 conflicts. The rule throughout: keep the template's mechanism, keep the product's content, and never invent a third state neither branch would accept. The template's product manifest and its runtime feature kill switch are adopted. The route registry is deliberately not composed from contract.routes: the reference feature still declares screens this product deleted, and reducing over them would register paths with no component behind them. ROUTE_FEATURE_OWNER is narrowed to registered routes for the same reason. The first resolution did compose from contract.routes and was rejected by product-features.test.ts. Three files pinned counts and a digest describing the gate contract. Neither side's numbers describe the merged config/ci/gates.json, so they were recomputed from it rather than chosen: 27 gates, 82 commands, 94 command references, 107 evidence references, 128 artifacts, shape sha256 5063586d. README.md and docs/accessibility/manual-checklist.md now enumerate this product's 27 routes, which the template's own verify:documentation requires. product-feature-switch.test.tsx was rewritten around the invariant that still applies here — no registered route without a component — rather than deleted with the screens it used to exercise. docs/operations/template-merge-2026-08-17.md records every decision, the gate results, and the three follow-ups this merge deliberately did not decide. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
225 lines
9.6 KiB
Markdown
225 lines
9.6 KiB
Markdown
# Template merge — `main` → `feature/techlog-ui-migration`
|
||
|
||
Record of how the frontend template sync on `main` was integrated into the
|
||
TechLog UI migration branch, and of every decision taken to resolve a conflict.
|
||
|
||
## Why this direction first
|
||
|
||
The template sync landed on `main` while the UI migration ran in a worktree.
|
||
Two orders were possible.
|
||
|
||
Merging the feature branch into `main` first would have resolved 15 conflicts
|
||
directly on the integration branch: a bad resolution would already be on `main`,
|
||
and it would have to be repaired forward, on the branch other work depends on.
|
||
|
||
Merging `main` into the worktree first keeps the resolution where the UI work
|
||
lives. Every gate runs against the resolved tree before anything reaches `main`,
|
||
and a resolution that turns out wrong is discarded by resetting one feature
|
||
branch. `main` is then only ever fast-forwarded, so it never holds a state that
|
||
was not already proved in the worktree.
|
||
|
||
That is the order used here.
|
||
|
||
## Starting state
|
||
|
||
| | |
|
||
| --- | --- |
|
||
| merge base | `325a2a0` |
|
||
| `main` | `bdee07a` — `chore: sync the frontend template from a0fbafb to 5434760`, 1 commit, 101 files, +3116/−448 |
|
||
| `feature/techlog-ui-migration` | `0355b64` — 29 commits |
|
||
| files changed by `main` | 101 |
|
||
| files changed by the feature branch | 423 |
|
||
| overlap | 23 |
|
||
|
||
Rollback refs were created before touching anything:
|
||
|
||
```
|
||
backup/ui-before-template-merge → 0355b64
|
||
backup/main-before-ff → bdee07a
|
||
```
|
||
|
||
### Pre-merge baseline on the feature branch
|
||
|
||
Captured so that a pre-existing failure could not be mistaken for a merge
|
||
regression.
|
||
|
||
| Gate | Result |
|
||
| --- | --- |
|
||
| `check:types` | pass |
|
||
| `lint` | pass |
|
||
| `verify:documentation` | `PASS_SCOPED` |
|
||
| `tests/unit` + `tests/component` | 1815 passed / 101 failed |
|
||
|
||
The 101 failures were confined to `tests/unit/ci-artifact-contract.test.ts` (19)
|
||
and `tests/unit/ci-workflow-generation.test.ts` (82) — sandbox subprocess gates
|
||
that cannot run in this environment.
|
||
|
||
## Conflicts and how each was decided
|
||
|
||
15 conflicts. The rule applied throughout: **keep the template's mechanism, keep
|
||
the product's content, and never invent a third state that neither branch
|
||
would accept.**
|
||
|
||
### Deletions the UI migration made deliberately (2)
|
||
|
||
| Path | Decision |
|
||
| --- | --- |
|
||
| `src/presentation/examples/platform-overview-page.tsx` | deletion kept |
|
||
| `tests/visual/.../platform-overview-light-chromium-visual-linux.png` | deletion kept |
|
||
|
||
`main` modified both; the feature branch deleted them in `c5c8b94`. Nothing on
|
||
the branch references either, so the deletion stands.
|
||
|
||
### `src/features/installed-feature-contracts.ts`
|
||
|
||
The template introduced a product manifest: registries are composed from
|
||
`INSTALLED_PRODUCT_FEATURES` so that narrowing the selection withdraws a
|
||
feature's routes, operations, schemas and messages.
|
||
|
||
The manifest composition is kept for operations, schemas, mappers and
|
||
invalidation. The **route registry is deliberately not composed from
|
||
`contract.routes`**, because the reference feature still declares
|
||
`REFERENCE_RESOURCE_*` routes whose screens this product deleted during the
|
||
migration. Reducing over them would register paths with no component behind
|
||
them — a typed deep link resolving to nothing.
|
||
|
||
The registry therefore lists the platform routes (empty on this branch) and
|
||
TechLog's. This was caught by running the gates: the first resolution did
|
||
compose from `contract.routes`, and `tests/unit/product-features.test.ts`
|
||
rejected it.
|
||
|
||
`ROUTE_FEATURE_OWNER` was narrowed to registered routes for the same reason.
|
||
Attributing an unregistered route to a feature claims the kill switch governs
|
||
something no router can mount.
|
||
|
||
### `src/features/installed-feature-runtimes.tsx`
|
||
|
||
The template gates the reference feature's route codecs and components on the
|
||
manifest. This product deleted that feature's presentation layer, so the import
|
||
does not resolve and there is nothing to gate. The gating was removed and the
|
||
reason recorded in the file; it belongs back the day those screens return.
|
||
|
||
Consequence: this file no longer references the manifest, so it was moved to the
|
||
exempt list in `tests/unit/product-features.test.ts` with the same note.
|
||
`tests/component/product-feature-switch.test.tsx` was rewritten to hold the
|
||
invariant that still applies here — no registered route without a component —
|
||
which is exactly the trap the first resolution fell into.
|
||
|
||
### `src/features/installed-feature-adapters.ts`
|
||
|
||
Both the manifest check and TechLog's input are kept. The reference feature's
|
||
input stays `Partial` because the manifest may narrow it out; TechLog's is total
|
||
because it is this product's own UI and is always installed. A build that
|
||
narrows the reference feature out still ships TechLog.
|
||
|
||
### `src/features/installed-feature-messages.ts`
|
||
|
||
The template's rationale — message keys stay total so `message()` cannot become
|
||
partial — is kept, and TechLog's catalog is merged in on the same terms.
|
||
|
||
### `src/presentation/routes/app-router.tsx`
|
||
|
||
The template looked the route definition and runtime component up by id inside
|
||
the route element; this branch passes both as props from the grouped route
|
||
contract. The prop-driven signature is kept.
|
||
|
||
The template's **feature kill switch is adopted**: a route whose owning feature
|
||
the runtime document disabled renders the disabled surface instead of mounting.
|
||
Withdrawing it from navigation alone would leave a working deep link. `getRoute`
|
||
was dropped from the imports because the definition arrives as a prop.
|
||
|
||
### `vite.config.ts`
|
||
|
||
Two independent additions — TechLog route chunking and `routerBasePath` — both
|
||
kept. A brace was lost in the first concatenation and caught by `check:types`.
|
||
|
||
### `scripts/build-frontend.ts`
|
||
|
||
Both new steps exist in the merged body, so the header comment was renumbered:
|
||
runtime config becomes step 4, the TechLog serving boundary step 7, the release
|
||
manifest step 8, and the inline step comments were corrected to match.
|
||
|
||
### `scripts/test-performance.ts`
|
||
|
||
The template clicked a navigation link before measuring; this product measures
|
||
its own landing route, which `goto` already reached, and has no `targetLabel`.
|
||
The click was dropped. The template's lesson was kept as a comment because it
|
||
applies to the next click that lands here: Playwright matches accessible names
|
||
by substring, so a nav entry can also match a call to action and resolve to two
|
||
links — a strict-mode violation that produces no performance evidence at all.
|
||
|
||
### Derived baselines — recomputed, not chosen (3)
|
||
|
||
`scripts/contracts/ci-gates.ts`, `tests/unit/task3-selective-integration.test.ts`
|
||
and `tests/unit/ci-workflow-generation.test.ts` each pin counts and a digest
|
||
describing the gate contract. **Neither side's numbers describe the merged
|
||
`config/ci/gates.json`**, so taking either would have been wrong. They were
|
||
recomputed from the merged file:
|
||
|
||
| Value | Result |
|
||
| --- | --- |
|
||
| canonical gate shape SHA-256 | `5063586d799f51de94c0f0ddaf9b75e180825bba5051bc309550425013ea81ef` |
|
||
| gates | 27 |
|
||
| commands | 82 |
|
||
| command references | 94 |
|
||
| evidence artifact references | 107 |
|
||
| artifacts | 128 (126 product + 2 from the template) |
|
||
|
||
### `README.md`, `docs/accessibility/manual-checklist.md`
|
||
|
||
The template enumerated its own example routes. Replacing them with generic
|
||
prose broke `verify:documentation`, which requires both documents to name every
|
||
installed route id — a rule the template sync itself introduced. Both documents
|
||
now enumerate this product's 27 routes.
|
||
|
||
## Result
|
||
|
||
Merge commit parents: `0355b64` (UI) and `bdee07a` (template).
|
||
|
||
### Gates on the merged tree
|
||
|
||
| Gate | Result |
|
||
| --- | --- |
|
||
| `check:types` | pass (6 projects) |
|
||
| `lint` | pass, `--max-warnings=0` |
|
||
| `check:architecture` | 399 modules, 1232 dependencies, 12 fixtures pass |
|
||
| `check:adapter-inventory` | 120 files, 5 importers of the shared abort primitive |
|
||
| `check:remediation-ledger` | 25 dispositions, 0 open |
|
||
| `check:diagnostics` | 8 diagnostics, 5 telemetry producers |
|
||
| `check:i18n` | 197 keys, 4 locales |
|
||
| `check:design-system` | 48 tokens |
|
||
| `verify:documentation` | `PASS_SCOPED` |
|
||
| `test:integration` | 81 passed |
|
||
| `test:recipes` | 17 passed |
|
||
| `test:runtime-schema` | 40 passed |
|
||
| `tests/unit` + `tests/component` | 1851 passed / 98 failed |
|
||
|
||
The 98 failures are the same two sandbox files as the baseline —
|
||
`ci-workflow-generation` (82) and `ci-artifact-contract` (16, down from 19). No
|
||
file fails that did not fail before the merge.
|
||
|
||
## Mistake made during this merge
|
||
|
||
`git stash` was run inside the worktree while the merge was still in progress,
|
||
to compare a gate against the pre-merge tree. That removed `MERGE_HEAD`: the
|
||
resolved content survived, but git no longer knew a merge was underway, and
|
||
committing then would have produced a single-parent commit — leaving `main` off
|
||
the ancestry and breaking the fast-forward that step 2 depends on. `MERGE_HEAD`
|
||
was restored to `bdee07a` before committing, and the resulting commit has both
|
||
parents.
|
||
|
||
To compare against a pre-merge state, use a separate checkout rather than
|
||
stashing an in-progress merge.
|
||
|
||
## Follow-ups this merge deliberately did not decide
|
||
|
||
1. **TechLog is outside the product manifest.** It is composed directly rather
|
||
than as a `SelectableProductFeature`, so the runtime kill switch does not
|
||
govern it. Promoting it is a product decision.
|
||
2. **The runtime kill switch currently governs no route.** The only installed
|
||
feature's screens were removed, so the switch still gates operations and
|
||
schemas but has no user-visible effect. It starts asserting again as soon as
|
||
a feature-owned route is registered.
|
||
3. **The reference feature declares routes it cannot serve.** Either its screens
|
||
return, or its route declarations should be withdrawn from its contract.
|