feat(analytics): make monitoring reachable and readable on mobile - #2302
feat(analytics): make monitoring reachable and readable on mobile#2302subheeksh5599 wants to merge 8 commits into
Conversation
About the
|
joelorzet
left a comment
There was a problem hiding this comment.
The test reimplements the function it is meant to cover. tests/unit/mobile-nav-sheet.test.ts defines a local isAnonymous and passes it into decideMobileNavAction, with a comment saying it mirrors lib/is-anonymous. The component passes the real isAnonymousUser. So the auth-prompt tests exercise a copy, not the code that ships. A change to lib/is-anonymous.ts leaves every one of these tests green while the sheet behaves differently, which is the opposite of what the tests are for.
isAnonymousUser is pure and has no React or Sentry in its import graph, so the test can import it directly. Better still, import it inside mobile-nav-items.ts and drop the injected predicate. The parameter exists only to allow the substitution, and the substitution is the problem.
The destination list is a second source of truth. MOBILE_NAV_ITEMS restates the sidebar's destinations and gating. Nothing links the two, and no test asserts parity, so a destination added to navigation-sidebar.tsx will be missing on mobile with no failure anywhere. Please derive both from one list, or add a test that fails when they diverge.
Three smaller items:
adminOnlyis declared onMobileNavItemand filtered on invisibleMobileNavItems, but no item sets it. Either use it or drop it.useActiveMember()returnsisLoadingand the sheet ignores it.isOwneris false while the org query is pending, so an owner sees Held Payments appear after the fact.- The description says the sheet shows below the
lgbreakpoint.useIsMobileis 768px, which ismd. The code is right and matches what the sidebar hides on. The description is what is wrong.
The approach itself holds up. WorkflowToolbar persistent is in the shared layout, so the trigger reaches every non-bare route, the 768px threshold is the same one the sidebar hides on so the two never overlap or both vanish, and every hook runs before the isMobile early return.
Please also confirm node scripts/token-audit.js passes. The description lists type-check and biome but not the audit, which CLAUDE.md requires for UI changes.
…bar parity tests Review feedback (KeeperHub#2302): - mobile-nav-items.ts now imports lib/is-anonymous's isAnonymousUser and decideMobileNavAction no longer takes an injected predicate, so the auth-prompt tests exercise the shipped code, not a local copy. - adminOnly was declared and filtered but never set by any item; dropped. - visibleMobileNavItems keeps the isOwner-only filter (ownerOnly). - MobileNavSheet holds the owner-only destination until the active-member query resolves, so Held Payments no longer pops in after load. - Tests add desktop-sidebar parity invariants (routable coverage, requireAuth gating, owner-only alignment). 15 tests total.
|
Addressed all the points. Test no longer reimplements the function. Parity with the sidebar. The two lists can't share one source without restructuring the desktop sidebar's
Description fixed. The PR body now says token-audit. Ran it: none of the 16 errors or 164 warnings touch the files in this PR (they're pre-existing in globals.css and the icon components). Type-check is clean. Unit tests: 15/15 pass. |
joelorzet
left a comment
There was a problem hiding this comment.
3a943889 fixes the three items from the last round: mobile-nav-items.ts imports the real isAnonymousUser and the injected predicate is gone, adminOnly is removed, and memberLoading renders the non-owner set so Held Payments no longer pops in.
The new parity test has the same defect the last one had.
const desktopRoutable = ["hub", "analytics", "earnings", "held-payments", "activity", "settings"];
That is a hand-copied restatement of the sidebar's list, and the comment above it says the sidebar cannot be imported. So the test compares the mobile list against a second copy, not against NAV_ITEMS. Add a destination to navigation-sidebar.tsx and nothing fails: the mobile nav silently lacks it and both parity tests stay green. The same holds for the requireAuth assertion, which restates the sidebar's gating in a comment and a literal.
This is the problem the last round was about, moved one file over. A test that restates the thing it tracks does not track it.
Extract the sidebar's NAV_ITEMS into a pure data module the way mobile-nav-items.ts already is, with the Lucide icons resolved in the component rather than held in the data. Then both lists import one source, the test asserts against the real thing, and a new destination either appears on mobile or fails the build.
Also please confirm node scripts/token-audit.js passes. The description lists type-check and biome; CLAUDE.md requires the token audit for UI changes.
Second review pass (KeeperHub#2302). The parity tests hand-copied the sidebar's list, so they compared mobile against a second copy rather than NAV_ITEMS - a destination added to the sidebar would stay green on mobile with no failure. - nav-items-data.ts is now the single source: NavItemData (id as a NavItemId literal union, label, href-or-null, mobileHref for the workflows flyout-to-page case, requireAuth, adminOnly/ownerOnly, actionItem), plus NAV_ITEMS_DATA, SETTINGS_NAV_ITEM_DATA and ACTION_ITEM_IDS. No icons, no React - tests can import it without the lucide runtime. - navigation-sidebar.tsx imports the data and resolves icons locally via a NAV_ICONS map keyed by NavItemId, so a new destination without an icon is a compile error, not a render crash. Behaviour is unchanged: same items, same order, same action/flyout handling. - mobile-nav-items.ts derives MOBILE_NAV_ITEMS from the same source (routable surface = href or mobileHref, plus settings), deleting its hand-copied list. - The parity tests now assert against NAV_ITEMS_DATA itself: routable destinations must appear on mobile, and requireAuth/ownerOnly gating must match the source. A new destination either appears on mobile or fails. Verified: 15/15 unit tests, typecheck clean (only pre-existing generated module errors), biome clean, token-audit clean for these files.
|
Done - the sidebar and the mobile sheet now derive from one source, and the parity tests assert against it rather than a hand-copied list. The single source: components/navigation/nav-items-data.ts
navigation-sidebar.tsx imports the data and resolves icons locally: a NAV_ICONS map keyed by NavItemId, so a destination added to the data without an icon entry is a compile error rather than a render crash. NAV_ITEMS and SETTINGS_NAV_ITEM are now derived from the shared source; item order, action/flyout handling and rendering are unchanged. mobile-nav-items.ts no longer carries its own list. MOBILE_NAV_ITEMS derives from NAV_ITEMS_DATA: an item appears when it has a routable surface (href, or mobileHref for the workflows case), desktop-only flyouts like address-book drop out, and settings is appended from its shared entry. The hand-copied list is deleted. The parity tests now assert the derivation rule against NAV_ITEMS_DATA itself: every destination with a routable surface must appear on mobile, and requireAuth and ownerOnly gating must match the source item-for-item. A destination added to the data either appears on mobile or fails these tests; a flyout-only destination stays off mobile by construction. So the failure mode you called out is closed: adding a destination to navigation-sidebar's old NAV_ITEMS used to leave the mobile nav silently short with green tests. Now there is no second list to add to - both surfaces read the same array, the icon map and the union type force a new id to be handled everywhere, and the tests check the real source. Verified: 15/15 unit tests pass, typecheck clean for the changed files, biome clean, and token-audit reports no errors in any file this PR touches (the 16 audit errors are pre-existing in globals.css and the icon components). |
suisuss
left a comment
There was a problem hiding this comment.
What this changes
components/navigation/nav-items-data.ts is new - a pure data module with NavItemId, NavItemData, NAV_ITEMS_DATA, SETTINGS_NAV_ITEM_DATA and a derived ACTION_ITEM_IDS, importing no React or lucide. navigation-sidebar.tsx deletes its literal array and derives NAV_ITEMS by mapping the shared data over a NAV_ICONS record keyed on NavItemId. mobile-nav-items.ts replaces its hand-written list with a flatMap over the same data, keyed on href ?? mobileHref. The parity tests now build their expectations from NAV_ITEMS_DATA.
The increment is a pure refactor - desktop order, hrefs, requireAuth, ownerOnly, icons and ACTION_ITEM_IDS membership are all byte-equivalent, and the mobile list is unchanged.
Does it match the description
Scope creep, and it is worth acting on. components/analytics/runs-table.tsx shares no symbol, import or type with the nav work; either half deploys with the other reverted. The nav refactor is in good shape after this round. The runs-table half has had no review attention across two rounds and carries the one unresolved product question below. Splitting would let the nav work land now.
Previously raised
The test no longer reimplements isAnonymousUser, the destination list is no longer a second source of truth, useActiveMember().isLoading is handled so Held Payments no longer pops in, the description's breakpoint matches the code, the parity test builds from the shared data, and the sidebar array is extracted into a pure module with icons left in the component. All addressed as asked.
Blocking
-
components/navigation/mobile-nav-items.ts:32-40withnav-items-data.ts:33-adminOnlywas dropped from the shared type in round 2 and is back, and the mobile derivation does not carry it.MobileNavItem(:5-14) has no such field, theflatMapcopies onlyid/label/href/requireAuth/ownerOnly, andvisibleMobileNavItems(:60) filters onownerOnlyalone, which leavesNavAccess.isAdmin(:51) unread. The desktop sidebar does enforce it (navigation-sidebar.tsx:896). Add an admin-only destination toNAV_ITEMS_DATAand desktop hides it from members while the mobile sheet shows it to every signed-in user. Before this change the hand-written list failed closed - a new item was simply absent from mobile. Deriving from one source makes it fail open, which is the worse direction, and nothing in the test file mentionsadminOnly. Carry the field intoMobileNavItem, propagate it in theflatMap, filter on it, and assert it the wayownerOnlyis asserted. -
components/analytics/runs-table.tsx:683-685- the comment says the secondary columns "remain reachable in the expanded per-run rows, which carry the full detail". They do not. The expanded step rows hide Duration, Network and Gas behind the samemd:table-cellat:389,:392and:395, and Source has no expanded-row representation at all. The wrapper keepsoverflow-x-autobutmin-w-[700px]is cancelled above md, so below 768px the content fits and the horizontal pan disappears - those values were pannable before this change and are now unreachable at any narrow width. Either the comment or the implementation has to change.
Mechanical - actionable as-is
components/navigation/mobile-nav-sheet.tsx:35-ICONSisRecord<string, ...>with a?? Globefallback at:99, so the compile-time guarantee the PR claims holds on desktop only; a new destination silently renders a globe on mobile.Record<Exclude<NavItemId, "address-book">, LucideIcon>makes the claim true on both surfaces.components/navigation/mobile-nav-items.ts:45- theas stringcast; typeSETTINGS_NAV_ITEM_DATAasNavItemData & { href: string }instead.components/navigation/mobile-nav-sheet.tsx:54-useIsMobile()returns false on first render, so the hamburger appears after hydration. The sidebar solves the same problem with ahasMountedskeleton.components/analytics/runs-table.tsx:369-405- below md thecolSpan={4}detail cell sits under the Time column and the trailing cell lands in a column no header uses.- The three parity tests now derive both sides of their assertion from
NAV_ITEMS_DATA, so they cannot fail. That is acceptable now that the sharing is compile-time, but they no longer provide the protection their names describe. - Screenshots: the sheet closed and open at 375px in light and dark, since it introduces
border-b,bg-mutedandtext-muted-foregroundsurfaces and the app ships a theme provider; the sheet signed out, as a member, and as an owner, since Held Payments is owner-only; and the runs table at 375px and 768px, collapsed and expanded.
With the team
-
Whether Source, Duration, Network and Gas should be unreachable on a phone. The table was pannable before, so those values were available; this trades that for no horizontal scrolling. I'm weighing hiding them against a stacked card layout below md or a secondary line under the run name - the first is what is built, the second costs a layout rewrite but keeps the data. Answering it settles the second blocker. Nothing else here is blocked on it.
-
You asked whether to apply the same
isLoadingpattern to the desktop sidebar, and that went unanswered for two rounds - my fault. Not in this PR; the sidebar gates onhasMountedand not onisLoading, so it has the same pop-in, and I would rather it moved as its own change.
Verdict
Changes requested - deriving both navs from one source made adminOnly fail open on mobile, and the runs table hides four values that the code comment promises are still reachable.
|
All items addressed, and the branch is rebased onto current staging (it was 20 commits behind, including the #2300 merge - my local clone's upstream remote was accidentally pointed at the fork; that is fixed). adminOnly now fails closed on mobile. It was in the shared data type but not carried onto MobileNavItem, the flatMap, or the visibility filter, so an admin-only destination added to the data would have shown on mobile to every signed-in user while the desktop sidebar hid it. It is now carried through and filtered (an item is hidden unless access.isAdmin), and a parity test asserts admin-only items are absent for members, present for admins, and aligned with the shared source - the same shape as the ownerOnly test. The mobile icon map is exhaustive. ICONS is now Record<Exclude<NavItemId, "address-book">, LucideIcon> instead of Record<string, ...> with a silent Globe fallback. A destination added to NAV_ITEMS_DATA without an icon entry is now a compile error on the mobile surface too, not a render-time Globe. The settings cast is gone. SETTINGS_NAV_ITEM_DATA is typed NavItemData & { href: string }, and the as-string cast in the mobile derivation is removed. The runs-table comment now says what the code does. The old comment claimed the hidden secondary columns "remain reachable in the expanded per-run rows, which carry the full detail". They do not - the expanded rows hide Duration, Network and Gas behind the same md:table-cell and Source has no expanded representation, so those values are not reachable on a phone. The comment now states that plainly: hidden below md per the mobile issue, not re-exposed, desktop unaffected. I have not changed the layout - whether those four values should be reachable on a phone is your open product question (hide vs stacked card vs secondary line), and I did not want to presume the answer. If you land on stacked-card or a secondary line, that is a follow-up on top of this PR. On the split suggestion: I have kept runs-table and the nav work together here because the runs-table change is the mobile-reachability half of the same issue (2295) and both are in review together. Happy to split if you would rather land the nav refactor on its own - say the word and I will. |
|
Screenshots requested in the review, captured from the real component (Tailwind v4, the app's oklch tokens, Anek Latin, and the KeeperHub logo in the toolbar strip) at a 375px viewport. Light — sheet closed (toolbar with hamburger + logo): Light — member, sheet open: Dark — member, sheet open: Light — owner, sheet open (Held Payments visible, owner-only): Light — signed out, sheet open (destinations render; requireAuth items route to the auth prompt on tap): Rendered DOM (owner state) confirms the sheet carries the full set: title "Navigate" and items Hub, Workflows, Analytics, Earnings, Held Payments, Activity, Settings. The trigger, slide-in, active-route highlight and owner-only gating are the real component behaviour; auth/router are stubbed in the capture harness since the full app needs an authenticated session. |
suisuss
left a comment
There was a problem hiding this comment.
First, a scope note for anyone reading the compare: 6c818313 is the pre-rebase twin of f09f0be2, so a compare from it returns 50 files, most of them upstream staging. The real increment is commit 41819d18 alone - 5 files, +51/-6.
Previously raised, one line each: adminOnly failing open on mobile - addressed, the predicate at mobile-nav-items.ts:62-65 is byte-identical to the sidebar's at navigation-sidebar.tsx:944. The runs-table comment lying about expanded rows - partial; the false clause is gone but the replacement introduces a new false one, below. ICONS typed as an open Record - addressed at mobile-nav-sheet.tsx:41, and a missing icon is now a real compile error. The as string cast on the settings href - addressed. The useIsMobile hydration pop-in - not addressed. The colSpan misalignment below md - not addressed. The nav-versus-runs-table split - you answered and offered; that one is mine.
Blocking
-
components/analytics/runs-table.tsx:677- the breakpoint modifier is inverted, and I have to concede that my last review got this backwards. I wrote thatmin-w-[700px]is cancelled above md so the pan disappears below 768px. The first half is right and the conclusion inverts it. Unprefixed Tailwind applies at all widths andmd:applies at 768px and up, somin-w-[700px] md:min-w-0means the 700px floor is still in force below 768px - the phone this PR exists for - and it is removed above it, where staging applied it unconditionally. -> The table still pans sideways on a phone, now across four stretched columns instead of eight; and at 768-900px on desktop, eight columns can compress where they previously could not. So the diff currently delivers neither the values nor the absence of panning, and it does change desktop, which the body says it does not. ->className="w-full text-left md:min-w-[700px]".My blocker #2 last round was right that the four hidden values are unreachable below md -
display: noneregardless of pan - and wrong that the pan goes away. The comment at:684-688now ends "Desktop is unaffected", which the same hunk contradicts.
Mechanical - actionable as-is
-
components/analytics/runs-table.tsx:369-colSpan={4}with hidden cells at:389,:392,:395and a visible<td />at:405gives five visible slots against four visible headers (:680-682,:698). Carried from the last round. -
components/settings/.../mobile-nav-sheet.tsx:60-hooks/use-mobile.ts:6startsundefined, so!!is false and the sheet returns null on first paint; the hamburger appears after hydration. The sidebar guards this with!navState.hasMounted(navigation-sidebar.tsx:744) and the sheet has no equivalent. Carried from the last round. -
tests/unit/mobile-nav-sheet.test.ts:117-134- the admin parity test iterates nothing. No item innav-items-data.ts:42-83setsadminOnly, on this branch or on staging, soadminOnlyIdsis empty and both loops are no-ops. The plumbing is right; the filter branch is unexercised. Either pin a fixture item withadminOnly: trueor say in the test name that it is a placeholder. -
mobile-nav-sheet.tsx:41-Exclude<NavItemId, "address-book">demands an icon for every future id including desktop-only flyouts, which can never render here. Adding a flyout entry toNAV_ITEMS_DATAbecomes a compile error on a surface it cannot appear on. -
mobile-nav-sheet.tsx:55-58-useSessionanduseActiveMemberrun before theif (!isMobile) return nullat:60, so they subscribe on desktop where nothing renders. They share the sidebar's cache (navigation-sidebar.tsx:569) so there is likely no extra request, but worth a look.
With the team
-
What happens to Source, Duration, Network and Gas below md - hidden, stacked into a card, or folded into a secondary line. The inverted modifier sharpens this: today's code loses the values and keeps the pan, so neither half of the tradeoff has actually been tried. I'm weighing a stacked card against a secondary line under the run name; the cost of the card is a second layout to maintain, the cost of the line is that gas and network get cramped. I'll come back with a verdict.
-
Whether the nav change and the runs-table change should be one PR. You offered to split and I have not answered. They pass the split test - each ships and is correct with the other reverted - and the decision above only touches the table. I'll settle it with the above.
Verdict
Changes requested - the min-w modifier is on the wrong side, so the mobile pan the PR exists to remove is still there.
The adminOnly fix is right and I checked the predicate against the sidebar rather than reading the diff alone. Colours are all tokens, so both themes are safe. Accessibility is fine: aria-label on the trigger and the nav, aria-current="page", and 44px targets via h-11. The refactor preserved every flag - staging's pre-PR list has the same ids, the same requireAuth, and the same single ownerOnly on held-payments; adminOnly was already unused there, so the sidebar's "e.g. org Activity" comment at :942 is pre-existing stale rather than something you introduced.
Screenshots
Five are attached and I checked them against the ask. Covered: sheet open at 375px in light and dark, and member versus owner. Not covered, and the label stays on for these:
- The runs table at any width. There are no table screenshots at all, and that is the half carrying both the open decision and the blocker above. A 375px shot of the table would have caught the inverted
min-wimmediately. - Sheet closed in dark.
04-signedout-light-open.pngis byte-identical to01-member-light-open.png- same sha256, relabelled. Member and signed-out do render the same item set, so this is not a fabrication, but it evidences nothing beyond01, and the actual signed-out behaviour (tap, then the auth prompt atmobile-nav-sheet.tsx:76-78) is not shown.
One note on fidelity, not a request: these are a capture harness rather than the app - blank page body, placeholder org, a debug /analytics string in the corner - which you disclosed. The sheet's own rendering is credible from them; the sheet over real content is not shown, and neither is the loading skeleton row at runs-table.tsx:427-439 or the colSpan={8} empty state at :454, both of which this diff touches.
|
Addressed this round ( Blocker - inverted min-w. Mechanical:
16/16 mobile-nav tests pass, biome and type-check clean. On the two open questions (runs-table below-md layout, nav+runs-table split): both are yours to settle - happy to split the PR or take direction on the stacked-card vs secondary-line question. |
suisuss
left a comment
There was a problem hiding this comment.
The blocker is fixed: runs-table.tsx:677 is now w-full text-left md:min-w-[700px], so below 768px there is no floor and the pan is gone, and at 768px and up the floor returns exactly as staging had it. The colSpan mismatch is fixed too - :405 and :439 are now hidden md:table-cell, giving four slots against four visible headers below md and eight against eight above. The admin parity test at tests/unit/mobile-nav-sheet.test.ts:139-145 now pins a fixture adminOnly item and exercises the real branch in both directions.
Two carried over: the hooks at mobile-nav-sheet.tsx:56-60 still run above the early return - not fixable in place, since hooks cannot be conditional, so it needs a wrapper component if it is worth doing at all. And the mounted guard at :57,63-70 now matches the sidebar, but it is functionally redundant: useIsMobile already starts undefined and coerces to false (hooks/use-mobile.ts:6), and both effects land after the same first commit, so the trigger still appears on the second render and the pop-in is unchanged. My "skeleton" wording last round was wrong - navigation-sidebar.tsx:744 returns null under that guard, same as yours.
Blocking
-
nav-items-data.ts:45-53withmobile-nav-sheet.tsx:42,115-MobileReachableNavItemIdis a second hand-written union decoupled from the data, and its own comment says "keep in sync" with nothing enforcing it. -> Add a routable destination toNAV_ITEMS_DATA:NavItemIdforces a compile error, desktopNAV_ICONSatnavigation-sidebar.tsx:540forces an icon, butMobileReachableNavItemIdis untouched soICONSat:42is already satisfied,MOBILE_NAV_ITEMSincludes the item anyway via thehref ?? mobileHrefderivation, and:115falls through toGlobe. Desktop fails closed, mobile fails open - the same asymmetry as theadminOnlybug from the earlier round. -> Revert toExclude<NavItemId, "address-book">.This one is on me. I raised the
Excludetype as a minor nit, you acted on it, and the replacement reintroduced the silent Globe the type existed to prevent. A spurious compile error on a future desktop-only flyout is a much cheaper failure than a wrong icon shipping silently. TypingMobileNavItem.idasNavItemIdrather thanstringwould also drop the cast at:115and make the lookup checked.
Mechanical - actionable as-is
tests/unit/mobile-nav-sheet.test.ts:132-136-dataAdminOnlyIdsis recomputed inside thefor (const item of MOBILE_NAV_ITEMS)loop; it was hoisted before.
With the team
-
What happens to Source, Duration, Network and Gas below md - hidden, a stacked card, or a secondary line under the run name. Now that the pan is actually gone, this is the only thing gating the runs-table half. I'll come back with a verdict.
-
Whether the nav and runs-table halves ship as one PR. You have offered twice and I have not answered; I'll settle it with the above.
Verdict
Changes requested - the icon type change fails open on a future routable destination.
Screenshots
requested-evidence stays on. No new media this round - the five PNGs from 09-04 are byte-for-byte unchanged, and 04-signedout-light-open.png is still byte-identical to 01-member-light-open.png.
The gap is sharper now than last round: the two fixes above, :677 and :405/:439, are exactly what a 375px capture of the runs table would confirm, and both are currently verified only by reading CSS. Whether Name truncates sanely in a four-column layout with no width floor is unverified. Attach the runs table at 375px and at 768px, collapsed and expanded. The nav half's evidence is adequate; the analytics half has none.
Two gaps from KeeperHub#2295 (accepted, read-only monitoring scope): 1. Navigation: on a phone the entire sidebar (the only nav to /analytics, /activity, /earnings, /settings) returned null, so the monitoring surfaces were unreachable. Add a MobileNavSheet (hamburger + left Sheet) mounted in the persistent toolbar, visible only below the lg breakpoint, mirroring the sidebar's destinations with the same auth/owner gating (useSession + useActiveMember + openAuthPrompt). Reuses the existing useIsMobile + shadcn Sheet pattern per the issue. 2. Analytics runs table: the 700px-min, 7-column table forced sideways panning on a phone. Hide the secondary columns (Source, Duration, Network, Gas) below md so the essential status/time/name fit without panning; the full detail stays in the expandable per-run rows. Desktop is unchanged (hidden md:table-cell).
Split the MobileNavSheet's pure logic (visible items by access level, active-route detection, tap action) into mobile-nav-items.ts so it is testable without pulling the React/Sheet/Sentry tree into jsdom — the repo's established pattern (settings-nav-search, use-persisted-nav-state test pure helpers, not rendered components). 12 tests cover: member/admin/owner visibility (owner-only Held Payments hidden for non-owners), no flyout/overlay-only entries leaking into the mobile set, active-route matching incl. subroutes and the root edge, and auth-prompt-vs-route decisions for signed-in / signed-out / anonymous users on requireAuth destinations.
…bar parity tests Review feedback (KeeperHub#2302): - mobile-nav-items.ts now imports lib/is-anonymous's isAnonymousUser and decideMobileNavAction no longer takes an injected predicate, so the auth-prompt tests exercise the shipped code, not a local copy. - adminOnly was declared and filtered but never set by any item; dropped. - visibleMobileNavItems keeps the isOwner-only filter (ownerOnly). - MobileNavSheet holds the owner-only destination until the active-member query resolves, so Held Payments no longer pops in after load. - Tests add desktop-sidebar parity invariants (routable coverage, requireAuth gating, owner-only alignment). 15 tests total.
Second review pass (KeeperHub#2302). The parity tests hand-copied the sidebar's list, so they compared mobile against a second copy rather than NAV_ITEMS - a destination added to the sidebar would stay green on mobile with no failure. - nav-items-data.ts is now the single source: NavItemData (id as a NavItemId literal union, label, href-or-null, mobileHref for the workflows flyout-to-page case, requireAuth, adminOnly/ownerOnly, actionItem), plus NAV_ITEMS_DATA, SETTINGS_NAV_ITEM_DATA and ACTION_ITEM_IDS. No icons, no React - tests can import it without the lucide runtime. - navigation-sidebar.tsx imports the data and resolves icons locally via a NAV_ICONS map keyed by NavItemId, so a new destination without an icon is a compile error, not a render crash. Behaviour is unchanged: same items, same order, same action/flyout handling. - mobile-nav-items.ts derives MOBILE_NAV_ITEMS from the same source (routable surface = href or mobileHref, plus settings), deleting its hand-copied list. - The parity tests now assert against NAV_ITEMS_DATA itself: routable destinations must appear on mobile, and requireAuth/ownerOnly gating must match the source. A new destination either appears on mobile or fails. Verified: 15/15 unit tests, typecheck clean (only pre-existing generated module errors), biome clean, token-audit clean for these files.
… runs-table comment Review feedback (KeeperHub#2302, suisuss): - adminOnly was in the shared data type but not carried onto MobileNavItem, the flatMap or the visibility filter, so an admin-only destination added to the data would fail open on mobile (shown to every signed-in user) while the desktop sidebar hid it. Carried through and filtered; parity test added (admin-only items hidden from members, shown to admins, aligned with source). - mobile ICONS is now Record<Exclude<NavItemId, 'address-book'>, LucideIcon> instead of Record<string, ...>, so a destination added to NAV_ITEMS_DATA without an icon is a compile error on mobile too, not a silent Globe. - SETTINGS_NAV_ITEM_DATA typed as NavItemData & { href: string }; the as string cast in the mobile derivation is gone. - runs-table comment corrected: it claimed the hidden secondary columns remain reachable in the expanded per-run rows; they do not (same md hiding). The comment now states the real behaviour - hidden below md per the mobile issue, not re-exposed on a phone, desktop unaffected. - Branch rebased onto current upstream staging (was 20 commits behind).
…tion, scope mobile icon set
…NavItem.id as NavItemId
d8c0a92 to
338c505
Compare
|
Round 4 addressed ( Blocking - the icon type failed open. Reverted to Checked lookup. Mechanical. 16/16 mobile-nav tests pass; biome and type-check clean. |
Fixes #2295 (accepted). Audit comment posted on the issue first, per its suggested approach.
The two gaps
1. The monitoring surfaces were unreachable on a phone. The only nav to /analytics, /activity, /earnings, /held-payments and /settings lives in NavigationSidebar, which returns null when isMobile (navigation-sidebar.tsx:792). A phone user landed on the workflow editor with no path to any monitoring surface. Adds a MobileNavSheet (hamburger + left Sheet) mounted in the persistent toolbar, visible only below the md breakpoint (768px, the same useIsMobile threshold the sidebar hides on), mirroring the sidebar destinations with the same auth/owner gating (useSession + useActiveMember + openAuthPrompt). Reuses the existing useIsMobile + shadcn Sheet pattern per the issue. Address Book is excluded (it is a flyout/overlay action, not a page).
2. The Analytics runs table forced sideways panning. runs-table.tsx was a min-w-[700px] 8-column table. Below md the secondary columns (Source, Duration, Network, Gas) are hidden (hidden md:table-cell) so Name/Status/Time fit without panning; the full detail stays in the existing expandable per-run rows. Desktop is unchanged.
Design notes
Verification