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>
9.6 KiB
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
- 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. - 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.
- The reference feature declares routes it cannot serve. Either its screens return, or its route declarations should be withdrawn from its contract.