feat(nav): honor the shard's public nav order, labels and hiding (M12 phase 5) #38
Reference in New Issue
Block a user
No description provided.
Delete Branch "feat/m12-phase-5-public-nav"
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 5 of M12 (
docs/android/THEMING_AND_NAV.md§6) — the first time anything in the app's navigation comes from the shard, after four phases of appearance. Targetsedge; the milestone reachesmainas oneedge→mainmerge.The drawer has been the coded
APP_MENUin coded order since M1. This lets an admin'snav_publicrow relabel, reorder and hide its public rows.The mapping table is the coupling, so it lives in one file
The public nav is keyed by website paths, so honoring it needs a translation. That is the one new piece of cross-repo coupling the milestone introduces, and
NavPaths.ktholds all of it with the website's ownNAVarray quoted directly above the table — visible and reviewable in one place instead of spread across the drawer's call sites.The
featurevalues are deliberately not mirrored.APP_MENUstays the app's own source of truth for gating; a second copy of a security-relevant value that drifts silently is worth more than it costs.Nine of the sixteen website rows have a drawer row. The other seven map to a screen the app reaches another way — three news categories are tabs on one News screen, and champs / guilds / governors / houses sit behind the Shard hub because that is the better shape on a phone — and an override for one of them is ignored. That is §6.1's rule that a nav override may never introduce navigation: the hub and the tab strip are design choices, not accidents to correct. The mapping still exists for all sixteen because phase 6's added links resolve an admin-authored path against the same table, and there a category tab or a hub board is a perfectly good destination — the admin asked for it by path.
The merge is presentation, and the gates still are the boundary
A port of the website's
applyNavOverrides, narrowed to what a drawer can express:label,order,hidden, nothing else. It runs beforevisibleEntries, so the two gates from M10/M11 still decide what this caller may see. An override that relabels the Market row, moves it to the front and sayshidden: falseshows nothing to a caller whose shard does not publish the market — hiding is subtractive, never additive.groupandsectionare read and dropped: the app renders no sections until phase 6 and never renders the sidebar's groups at all, and a value that cannot be honored is better dropped than half-applied.One thing §6.2 did not settle, and the sort turns on it
An untouched row's implicit sort key has to be its index in the WEBSITE's nav, not the app's. A stored
orderis a position in the site's sixteen-row list, so a key taken from the app's nine-row block puts explicit and implicit keys on two incomparable number lines — an unmoved About (app index 8) would sort ahead of an unmoved Market (web order 14) the moment any row carried an explicit order. The docs PR renumbers §6.2's table and calls its order load-bearing for this reason; it also corrects that order, which had the three news categories in the wrong sequence. Both tie-breaks are the web's: an explicit order beats a coincidental index, and two explicit orders keep code order because the sort is stable.Two questions settled by the org lead before code
1. The three news-category paths get a mapped route but no drawer row, on the same rule as the hub four. The alternative considered — surfacing such a row only when the admin overrides it — keeps AC-1 but lets an override introduce navigation after all.
2. An admin may hide Home, mirroring the website, where
/is hideable from the public header. Home stays theNavHost's start destination and stays reachable by back-press. The contrast that makes it safe is with the website's/admin/navigation, which has three guards because hiding it would strip the only way to undo an override — nothing about a hidden Home row is unrecoverable.Routes.news(category)ships here; phase 6 is its first callerDeclared beside
Routes.NEWSrather than replacing it, because the two are used for different things: the pattern is whatcomposable()anddestination.routespeak, the bare route is what callers navigate to. Navigating to plainRoutes.NEWSmatches the pattern with no argument, so the drawer row and the push deep-link (Routes.forStream) are untouched. The consequence worth knowing:destination.routeis now a pattern carrying a query, soRunicAppcompares onsubstringBefore('?')for both the top-level check and the selected-row check.It takes the
PostCategoryenum rather than a slug string (unlike the existingRoutes.post()) so an unmapped category cannot reach the NavHost — the tab strip is the enum's entries.NewsViewModelfalls back to the default feed for an unknown slug anyway, since a hand-edited settings row can carry one.Tests
NavOverridesTest(19) +NavPathsTest(11) + 2 newNewsViewModelcases, with three existing ones rewritten onto aSavedStateHandle. 442 unit tests green (410 + 32),lintDebugandassembleDebugclean.The strongest is AC-1's: with no stored row — or an empty one, or one with nothing usable in it — the merge returns
APP_MENUitself, so the assertion isassertSameand there is no path where an unedited nav is rebuilt and could come out different. AC-3 is covered three ways: the relabelled-and-movedhidden: falserow above, role-gated rows staying hidden from an anonymous caller whatever the row says, and hiding composing with the gates rather than competing with them.What the JVM still cannot reach is phase 4's limit again — no Robolectric, no
androidTestsource set. That the reordered drawer draws in the new order, and that navigating to plainnewsreally matches the optional-argument pattern at runtime, are AC-5's.Docs: RunicGateway/docs#117.