feat(nav): group the drawer into the shard's sections and honor its added links (M12 phase 6) #39
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/m12-phase-6-nav-sections-links"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Phase 6 of M12 (
docs/android/THEMING_AND_NAV.md§6.3) — the last of the public nav the website publishes. Phase 5 let an admin relabel, reorder and hide the drawer's rows; this lets them group rows into sections and add links of their own. Targetsedge.The spec's link table was wrong about the website
§6.3 listed the paths an added link could resolve natively, and three of them are paths the site does not serve. Checked against
website/client/src/App.jsx: there is no/site/news/<idOrSlug>(a news item renders on its category page; the site's one post-detail route is the newsletter's/site/newsletter/:id), no/page/<slug>(CMS pages come from a top-level/<slug>), and no/contactat all — the app's contact form is app-only, which §6.2 already said. Two real detail routes were missing,/site/atlas/:slugand/site/market/vendors/:serial.Phase 5 hit the same class of error in §6.2's row order, and the fix is the same one:
NavPaths.ktnow quotesApp.jsx's route list next to theSiteHeader.jsxNAVit already carried. A resolver that does not read the site's own route table cannot be right by accident, and the next person to change it should be looking at both.Where the
/<slug>catch-all earns its keep, and what it must not swallowThe likeliest added link is a page the admin wrote, so
/<slug>→PAGE(slug)is the rule that decides whether this feature mostly opens the app or mostly opens a browser. It comes with a reserved list —admin,account,player,site,wiki,invite,preview,api,uploads— because React Router ranks its static routes above/:slugand the app has to do the same: without it a link to the admin panel would open an in-app 404 CMS page instead of the real thing in a browser.A path carrying a query or a fragment hands off, whatever its route part says. No app route takes either, so a native match would quietly drop what the admin typed.
Phase 6 does not re-implement phase 5, which is why AC-1 is untouched
buildNavTreehands straight toapplyNavOverrideswhen the stored row carries no sections and no links, so an untouched instance still getsAPP_MENUback by identity — the tree build only runs when the admin actually created structure. The test for it is that an items-only row through the tree equals the same row through the flat merge, plus the originalassertSamestill standing inNavOverridesTest.One place the two consumers disagree, deliberately:
sectionis read but is not part ofNavOverride.isEmpty. A section-only override says nothing to a flat list, so an instance that only ever grouped rows still gets its coded list back by identity fromapplyNavOverrides; the tree adds its own check.The gates, inside a section as well as outside it
visibleEntriesis split intoisEntryVisiblesopruneNavcan apply the same predicate to a sectioned row — both callers have to ask exactly one question, or a row could end up gated by a rule its top-level twin is not. The merge still runs first and the gate still decides: a Market row relabelled, moved to the front, markedhidden: falseand tucked into its own section is still not shown on a shard that does not publish the market, and the section it left empty is dropped rather than drawn as a header over nothing.What is not gated is as load-bearing as what is. An added link carries no role or feature check — the screen behind it enforces its own, exactly as typing the URL on the web does — so a section holding one is never emptied by the caller's role. Both directions are tested.
Degradation is forgiving in one specific way worth calling out: a dangling reference degrades to "no grouping", never to "no row". A member of a section that does not exist — deleted, or dropped for a blank label — is an ordinary top-level row, and a link naming an unknown section keeps its destination and loses only its grouping.
Sort keys extend phase 5's number line
A coded row still keys on its index in the website's sixteen-row nav. An admin-created section or link with no stored order keys on
16, 17, …in creation order, so it appends after the coded rows instead of jumping to the front on a0default. Both tie-breaks are still the web's.Four drawer decisions, settled by the org lead before code
Static section header with indented rows (always open — a collapsible group can hide the row the admin meant to surface); a trailing
ExitToAppicon on hand-off links only, so leaving the app is never a surprise while a natively-resolved link stays indistinguishable from a coded row;LocalAssetResolverfor the Custom Tab's absolute URL, since it already resolves any site-relative path against the base URL at the app root; and a resolved link opening like any other drawer row (navigateTopLevel, detail screen or not) so back-press lands on Home as it does from every row.The icon is
material-icons-core's —OpenInNewlives in the extended artifact, which is not a dependency and would not be worth adding after phase 3 tripled the APK.Tests
NavTreeTest(27) + 7 newNavPathsTestcases. 476 unit tests green (442 + 34),lintDebugandassembleDebugclean with no new lint findings.Phase 4's limit still holds — no Robolectric, no
androidTestsource set — so the section header's indent, the hand-off icon, and that a Custom Tab really opens are AC-5's, not the JVM's.Docs: RunicGateway/docs#118.