refactor(phase0): gate-only pages become shells - #198
Closed
adamXbot wants to merge 2 commits into
Closed
Conversation
…come shells
Batch 2 of the Rust-core Phase 0 page conversions (11/35 done). These
four pages shared two server concerns, and both now come from the API:
- Flag gates. `resolveFlagFromDb(key) !== "on" → notFound()` becomes
RequireFlagGate, which reads the resolved value from
GET /api/feature-flags (the `flags[].currentValue` field — the same
resolver output). It fails CLOSED: a failed flag read renders the 404
rather than the page, because these gates hide surfaces that
shouldn't exist for the install. notFound() is thrown during render
(not from the effect), which is the only way it works client-side.
- Initial data. Every one of these lists already had an exact API twin,
so no new endpoints were needed:
devices → /api/devices (already includes appCount, and
DevicesView already fetched it in refresh())
manual apps → /api/manual-apps (already returned the exact
{ apps, sources } pair the page assembled by hand)
focus editor → /api/focus (already returned all six fields; the
new FocusEditLoader maps them and holds the form
back until they land, since the form stages edits
from its initial props)
changelog → /api/apps for the filter dropdown
The `initialX` props stay optional on DevicesView / ManualAppsView /
UniversalChangelogView so their Storybook stories keep seeding
fixtures; in Storybook the mount fetch simply fails and the seeded
state stands.
Verified by hand where no e2e covers it: both gated pages render with
their data through the gate, the manual-app form still defaults to
Safari web app (sources[0], unchanged — both create paths reset from
live sources), and flipping flag.page.manual_apps off renders the real
404 page.
Gate: tsc clean; biome clean; unit 462 pass / 0 fail; full Playwright
46 passed; visual net 13/13 pixel-identical against pre-conversion
baselines captured via stash.
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Batch 3 of the Rust-core Phase 0 conversions (18/35 done). This batch is the pages whose only server work was a gate — no data reads at all: - Flag-gated content: /dashboard/about/ai-disclosure and /help/export-app-list swap resolveFlagFromDb + notFound() for RequireFlagGate (batch 2's component). - /dashboard/settings/focus-matrix built its 220-row seed from HARD_DEFAULTS in the server component. That list moves into FocusFlagMatrix, which already imported the same module — feature-flag-rules is the client-safe pure-data half of lib/. The prop stays optional so callers can still inject rows. - /onboard/goals was a server redirect() alias for /welcome. A static export can't serve one, so it forwards client-side like the root page. - diagnostics / about / help/focus only carried force-dynamic. Two findings recorded in the ledger's header comment rather than left to surprise the final batch: - Dropping force-dynamic does NOT make a route static today. The root layout calls headers() (CSP nonce) and cookies() (locale), which forces the whole tree dynamic — `next build` shows every route as ƒ, verified. The ledger, not the build output, is the per-page proof until the layout converts. - generateMetadata still calls getTranslations, and the locale comes per-request from the NEXT_LOCALE cookie (i18n.ts). A statically prerendered page would bake the English title while the body renders translated, so page titles move to the client with the layout batch. The ledger's redirect assertion was matching the call text, so a comment explaining what a shell replaced tripped it. It now matches the import specifier from next/navigation (redirect / permanentRedirect), the same discipline the lib/ check already used. Re-proved fail-able by injecting a real `redirect` import: fires; comment prose: clean. Gate: tsc clean; biome clean; unit 469 pass / 0 fail; full Playwright 46 passed; visual 13/13 pixel-identical vs pre-conversion baselines. Hand-checked (no e2e covers them): both gated pages render, and the focus matrix still lists 220 rows across 18 surface groups. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Collaborator
Author
|
Superseded by #199, which consolidates Phase 0 batches 2–6 into a single PR (32 of 35 pages). This branch's commits are all contained in that one — closing to avoid a partial merge landing ahead of it. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Batch 3 of Rust-core Phase 0 — 18 of 35 pages converted. Stacked on #197 (base is
main; merge #197 first). This batch is the clean boundary of "pages whose only server work was a gate" — no data reads at all:/dashboard/about/ai-disclosureand/help/export-app-listswapresolveFlagFromDb+notFound()for batch 2'sRequireFlagGate./dashboard/settings/focus-matrixbuilt its 220-row seed fromHARD_DEFAULTSin the server component. That moves intoFocusFlagMatrix, which already imported the same module —feature-flag-rulesis the client-safe pure-data half oflib/. The prop stays optional for injected rows./onboard/goalswas a serverredirect()alias for/welcome; a static export can't serve one, so it forwards client-side like the root page.force-dynamic. (help/focusturned out to have noforce-dynamicat all — my first grep matched the string inside a comment.)Two findings worth your attention
Both are recorded in the ledger's header comment rather than left to ambush the final batch:
force-dynamicdoes not make a route static today. The root layout callsheaders()(CSP nonce) andcookies()(locale), which forces the whole tree dynamic — I checked the build's route table and every route isƒ, including/about. So these removals are currently preparatory: the flip happens all at once when the layout converts. The ledger test, not the build output, is the per-page proof until then.generateMetadatastill callsgetTranslations, and the locale is per-request from theNEXT_LOCALEcookie (i18n.ts). A statically prerendered page would bake the English title while the body renders translated. Page titles therefore move to the client with the layout batch — flagged now so it's a planned step, not a late surprise.The ledger caught its own bug
The
redirect()assertion matched the call text, so my doc comment explaining what a shell replaced tripped it — the same prose-matching trap thelib/check already guarded against. It now matches the import specifier fromnext/navigation(redirect/permanentRedirect). Re-proved fail-able: injecting a realredirectimport fires it; comment prose is clean.Verification
tscclean · biome clean · unit 469 pass / 0 fail · full Playwright 46 passed · visual 13/13 pixel-identical vs stash-captured pre-conversion baselines. Hand-checked where no e2e reaches: both gated pages render through the gate, and the focus matrix still lists 220 rows across 18 surface groups (matching the flag registry).Next
Batch 4 is the pages needing new read endpoints (stats, privacy map, shortlist, review-recommendations, settings/layout, onboard/welcome family), then the big three (dashboard, app detail, apps grid), then the layout + CSP batch — which comes to you for explicit sign-off.
🤖 Generated with Claude Code