test: decouple the feature-switch gates from the template's demo screens
The template's demonstration screens exist to explain the template. A product replaces them with its domain, so deleting them is the expected end state — but two gates were coupled to them, and the merge accommodated that coupling instead of fixing it. product-features.test.ts derives its own scope now: a registry must gate on the manifest when it imports a module belonging to a manifest-declared feature. The previous fix put installed-feature-runtimes.tsx in the exempt list, which silenced the guard for that file permanently. Verified by removing the manifest reference from installed-feature-adapters.ts and watching the guard fail; a counter asserts the sweep still watches at least one file. product-feature-switch.test.tsx exercises the kill switch end to end again. The mechanism is the ownership lookup plus isFeatureActive, which has nothing to do with which screens ship, so the ownership map is the fixture: one real registered route attributed to a real installed feature, with the product's own router, components and codecs. That restoration exposed a real gap. The navigation-withdrawal half is not implemented here: it lives in the template's PrimaryNavigation and this product does not render the template's AppShell at all — the public header is a hand-written list of paths. A disabled feature's route is refused by the router but its link would still be advertised. Harmless only while no feature-owned route is navigable, which is now asserted so the gap cannot ship silently. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 5
parent
cd1ef5cda2
commit
93ce86eef4
@@ -211,14 +211,67 @@ parents.
|
||||
To compare against a pre-merge state, use a separate checkout rather than
|
||||
stashing an in-progress merge.
|
||||
|
||||
## Correction after review
|
||||
|
||||
Two of the resolutions above were wrong, and were fixed in a follow-up commit.
|
||||
|
||||
The template's demonstration screens — `platform-overview-page`, the UI and
|
||||
state galleries, the auth example and the reference feature's screens — exist to
|
||||
explain the template. A product replaces them with its domain, and deleting them
|
||||
is the expected end state, not a regression. Two gates were nevertheless coupled
|
||||
to them, and the first resolution accommodated that coupling instead of fixing
|
||||
it.
|
||||
|
||||
### `tests/unit/product-features.test.ts` — a hard-coded exemption became a rule
|
||||
|
||||
The guard requires every installed registry to compose from the manifest.
|
||||
`installed-feature-runtimes.tsx` was added to its exempt list once the reference
|
||||
feature's presentation layer was gone. That silenced the guard for that file
|
||||
permanently.
|
||||
|
||||
It now derives its own scope: a registry must gate on the manifest **when it
|
||||
imports a module belonging to a manifest-declared feature**. A product whose
|
||||
registries compose only its own domain drops out of the rule honestly, and the
|
||||
guard fires again the moment a declared feature is imported without gating —
|
||||
verified by removing the manifest reference from
|
||||
`installed-feature-adapters.ts` and watching the guard fail. A counter asserts
|
||||
the sweep is still watching at least one file, so an empty scope cannot pass
|
||||
silently.
|
||||
|
||||
### `tests/component/product-feature-switch.test.tsx` — coverage restored
|
||||
|
||||
The end-to-end kill-switch assertions were replaced with composition checks
|
||||
because the screens they rendered were gone. The mechanism under test is the
|
||||
ownership lookup plus `isFeatureActive`, which has nothing to do with which
|
||||
screens ship, so **the ownership map is now the fixture**: one real registered
|
||||
route is attributed to a real installed feature, and the router, shell,
|
||||
components and codecs are all the product's own. The deep-link half is asserted
|
||||
end to end again.
|
||||
|
||||
### What that restoration exposed
|
||||
|
||||
The navigation-withdrawal half **is not implemented in this product**. It lives
|
||||
in the template's `PrimaryNavigation`, and this product does not render the
|
||||
template's `AppShell` at all — the public site header is a hand-written list of
|
||||
paths in `src/features/tech-log/presentation/public/components/site-header.tsx`,
|
||||
and the studio has its own shell.
|
||||
|
||||
So a disabled feature's route is refused by the router but its link would still
|
||||
be advertised. That is harmless only while no feature-owned route is navigable,
|
||||
which is true today and is now asserted. If that assertion fails, the header has
|
||||
to consult `ROUTE_FEATURE_OWNER` — or navigation has to move back onto
|
||||
`NAVIGATION_ROUTES` — before the route ships.
|
||||
|
||||
## 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.
|
||||
govern it. That is defensible — a product's own domain is not an optional
|
||||
feature — but it means the switch governs nothing user-visible today.
|
||||
2. **The reference feature declares routes it cannot serve.** Its screens were
|
||||
deleted with the rest of the demonstration UI, but its contract still
|
||||
declares `REFERENCE_RESOURCE_*` routes. Either the declarations go, or the
|
||||
feature does. `TechLog` does not import it (`git grep reference-feature --
|
||||
src/features/tech-log` is empty), so removing it is a live option and
|
||||
FE-GATE-020 exists to prove it can be removed.
|
||||
3. **The public site header is not feature-aware.** See above.
|
||||
|
||||
Reference in New Issue
Block a user