feat(console): real-mode nav/route gating to backend-backed screens#69
Conversation
Real mode (VITE_DEMO_MODE unset) now exposes only the screens wired to the eERC backend — Payroll and Treasury — and lands on /treasury. The no-backend screens (Overview, Contractors, Invoices, one-off Pay, Approvals, Auditor grants, Audit log, Settings) stay demo-only: hidden from the sidebar + command bar and their routes redirect to the treasury landing; in a real build they tree-shake out entirely. Demo mode (VITE_DEMO_MODE=1) still shows the full product. A shared nav model (app/nav.ts) is the single source of truth for both the sidebar and the command palette. Epic #58 step B (final). Closes #68.
📝 WalkthroughWalkthroughChangesNavigation and route gating
Estimated code review effort: 3 (Moderate) | ~20 minutes Sequence Diagram(s)sequenceDiagram
participant User
participant Shell
participant NavModel
participant Router
User->>Shell: Open console
Shell->>NavModel: Get visibleNavGroups and visibleNavItems
NavModel-->>Shell: Return mode-filtered navigation
Shell->>Router: Register mode-specific routes
Router-->>User: Render screen or redirect to REAL_HOME
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@apps/console/src/app/CommandBar.tsx`:
- Around line 10-25: Replace the duplicated DESTINATIONS definitions and
DEMO_MODE filtering in CommandBar with the shared visibleNavItems() navigation
model. Map the returned NavItemDef entries to the command palette shape, and add
any command-specific labels or metadata to NavItemDef so both sidebar and
palette use the same routes and real-mode visibility contract.
In `@apps/console/src/app/nav.ts`:
- Around line 5-6: Keep the supported organization and team settings accessible
in real mode: update the Settings navigation metadata and real-mode route
registration in Shell.tsx so /settings is not treated as demo-only, then modify
SettingsScreen to gate only unsupported settings cards or tabs rather than the
entire screen.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: defaults
Review profile: CHILL
Plan: Pro Plus
Run ID: 8e35afec-cdf5-4360-ae4e-fcafc9c8866b
📒 Files selected for processing (4)
apps/console/src/app/CommandBar.tsxapps/console/src/app/Shell.tsxapps/console/src/app/nav.test.tsapps/console/src/app/nav.ts
| import { DEMO_MODE } from "../demo/flag"; | ||
|
|
||
| // Jump-to targets mirror the sidebar: in real mode only the backend-backed | ||
| // screens are reachable, so the palette hides the demo-only destinations too. | ||
| const DESTINATIONS = [ | ||
| { label: "Dashboard", to: "/" }, | ||
| { label: "Contractors", to: "/contractors" }, | ||
| { label: "Payroll", to: "/payroll" }, | ||
| { label: "Invoices to pay", to: "/invoices" }, | ||
| { label: "Send & vendor pay", to: "/pay" }, | ||
| { label: "Approvals", to: "/approvals" }, | ||
| { label: "Treasury", to: "/treasury" }, | ||
| { label: "Auditor grants", to: "/grants" }, | ||
| { label: "Audit log", to: "/audit" }, | ||
| { label: "Settings & team", to: "/settings" }, | ||
| ]; | ||
| { label: "Dashboard", to: "/", real: false }, | ||
| { label: "Contractors", to: "/contractors", real: false }, | ||
| { label: "Payroll", to: "/payroll", real: true }, | ||
| { label: "Invoices to pay", to: "/invoices", real: false }, | ||
| { label: "Send & vendor pay", to: "/pay", real: false }, | ||
| { label: "Approvals", to: "/approvals", real: false }, | ||
| { label: "Treasury", to: "/treasury", real: true }, | ||
| { label: "Auditor grants", to: "/grants", real: false }, | ||
| { label: "Audit log", to: "/audit", real: false }, | ||
| { label: "Settings & team", to: "/settings", real: false }, | ||
| ].filter((d) => DEMO_MODE || d.real); |
There was a problem hiding this comment.
📐 Maintainability & Code Quality | 🟠 Major | ⚡ Quick win
Derive palette destinations from the shared navigation model.
This duplicates both route definitions and real-mode visibility metadata instead of using visibleNavItems(). It can drift from the sidebar and route-gating contract; keep any command-specific labels as metadata in NavItemDef if needed.
Suggested direction
-import { DEMO_MODE } from "../demo/flag";
+import { visibleNavItems } from "./nav";
-const DESTINATIONS = [
- // duplicated destinations and `real` flags
-].filter((d) => DEMO_MODE || d.real);
+const DESTINATIONS = visibleNavItems().map((item) => ({
+ label: item.label,
+ to: item.to,
+}));📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| import { DEMO_MODE } from "../demo/flag"; | |
| // Jump-to targets mirror the sidebar: in real mode only the backend-backed | |
| // screens are reachable, so the palette hides the demo-only destinations too. | |
| const DESTINATIONS = [ | |
| { label: "Dashboard", to: "/" }, | |
| { label: "Contractors", to: "/contractors" }, | |
| { label: "Payroll", to: "/payroll" }, | |
| { label: "Invoices to pay", to: "/invoices" }, | |
| { label: "Send & vendor pay", to: "/pay" }, | |
| { label: "Approvals", to: "/approvals" }, | |
| { label: "Treasury", to: "/treasury" }, | |
| { label: "Auditor grants", to: "/grants" }, | |
| { label: "Audit log", to: "/audit" }, | |
| { label: "Settings & team", to: "/settings" }, | |
| ]; | |
| { label: "Dashboard", to: "/", real: false }, | |
| { label: "Contractors", to: "/contractors", real: false }, | |
| { label: "Payroll", to: "/payroll", real: true }, | |
| { label: "Invoices to pay", to: "/invoices", real: false }, | |
| { label: "Send & vendor pay", to: "/pay", real: false }, | |
| { label: "Approvals", to: "/approvals", real: false }, | |
| { label: "Treasury", to: "/treasury", real: true }, | |
| { label: "Auditor grants", to: "/grants", real: false }, | |
| { label: "Audit log", to: "/audit", real: false }, | |
| { label: "Settings & team", to: "/settings", real: false }, | |
| ].filter((d) => DEMO_MODE || d.real); | |
| import { visibleNavItems } from "./nav"; | |
| // Jump-to targets mirror the sidebar: in real mode only the backend-backed | |
| // screens are reachable, so the palette hides the demo-only destinations too. | |
| const DESTINATIONS = visibleNavItems().map((item) => ({ | |
| label: item.label, | |
| to: item.to, | |
| })); |
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/app/CommandBar.tsx` around lines 10 - 25, Replace the
duplicated DESTINATIONS definitions and DEMO_MODE filtering in CommandBar with
the shared visibleNavItems() navigation model. Map the returned NavItemDef
entries to the command palette shape, and add any command-specific labels or
metadata to NavItemDef so both sidebar and palette use the same routes and
real-mode visibility contract.
| * the rest (Overview, Contractors, Invoices, one-off Pay, Approvals, Grants, Audit, | ||
| * Settings) have no backend on this platform and stay **demo-only**. Demo mode |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | 🏗️ Heavy lift
Keep backed organization and team settings reachable in real mode.
/settings is marked demo-only, so it disappears from navigation and the real-mode wildcard in Shell.tsx redirects it to Treasury. This conflicts with the requirement to retain backed organization/team settings. Mark the supported Settings entry real-backed, register it in the real-mode routes, and gate only unsupported Settings cards/tabs within SettingsScreen.
Also applies to: 36-36
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
In `@apps/console/src/app/nav.ts` around lines 5 - 6, Keep the supported
organization and team settings accessible in real mode: update the Settings
navigation metadata and real-mode route registration in Shell.tsx so /settings
is not treated as demo-only, then modify SettingsScreen to gate only unsupported
settings cards or tabs rather than the entire screen.
| <Route path="/payroll" element={<Payroll />} /> | ||
| <Route path="/treasury" element={<Treasury />} /> | ||
| <Route path="*" element={<Navigate to={REAL_HOME} replace />} /> | ||
| </> |
There was a problem hiding this comment.
When a real-mode user with an active org opens /claim, this wildcard catches the URL and sends them to /treasury. The previous route table registered InviteClaim, so invite links can no longer reach the claim screen in the real build.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/console/src/app/Shell.tsx
Line: 257
Comment:
**Invite Claim Route Redirected**
When a real-mode user with an active org opens `/claim`, this wildcard catches the URL and sends them to `/treasury`. The previous route table registered `InviteClaim`, so invite links can no longer reach the claim screen in the real build.
How can I resolve this? If you propose a fix, please make it concise.| {DEMO_MODE ? ( | ||
| <> | ||
| <Route path="/" element={<Dashboard />} /> | ||
| <Route path="/approvals" element={<Approvals />} /> | ||
| <Route path="/contractors" element={<Contractors />} /> | ||
| <Route path="/payroll" element={<Payroll />} /> | ||
| <Route path="/invoices" element={<Invoices />} /> | ||
| <Route path="/pay" element={<Pay />} /> | ||
| <Route path="/treasury" element={<Treasury />} /> | ||
| <Route path="/grants" element={<Grants />} /> | ||
| <Route path="/audit" element={<AuditLog />} /> | ||
| <Route path="/claim" element={<InviteClaim />} /> | ||
| <Route path="/settings" element={<SettingsScreen />} /> | ||
| <Route path="*" element={<Dashboard />} /> | ||
| </> | ||
| ) : ( |
There was a problem hiding this comment.
This branch makes the demo-only routes unreachable in real mode, but the demo screens are still statically imported at module load. A real build can still include and evaluate those screen modules and their transitive dependencies, so the intended real-mode bundle split does not happen from this route gate alone.
Prompt To Fix With AI
This is a comment left during a code review.
Path: apps/console/src/app/Shell.tsx
Line: 235-250
Comment:
**Demo Screens Still Imported**
This branch makes the demo-only routes unreachable in real mode, but the demo screens are still statically imported at module load. A real build can still include and evaluate those screen modules and their transitive dependencies, so the intended real-mode bundle split does not happen from this route gate alone.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
Epic: #58 · Step B (real-mode nav/route gating) — the final step · Closes #68
Hand-coded (not via codex, per request). With the backend-backed features wired (auth #60, onboarding #63, treasury #65, payroll #67), this makes real mode honest: it exposes only what actually talks to the eERC backend, and keeps the full console in demo mode as the product vision.
What changed
app/nav.ts(new) — a single source of truth for the nav: each item carries arealBackedflag.visibleNavItems(demoMode)/visibleNavGroups(demoMode)filter to the wired screens in real mode;REAL_HOME = "/treasury".app/Shell.tsx— the sidebar renders fromvisibleNavItems()(grouped), and the route table is gated onDEMO_MODE: demo keeps every route; real mode registers only/payroll+/treasuryand redirects everything else to/treasury. Because the demo-only screens are referenced solely under the eliminatedDEMO_MODEbranch, a real build tree-shakes them out.app/CommandBar.tsx— the ⌘K palette is filtered the same way.app/nav.test.ts(new) — asserts the real-mode nav set (only Payroll + Treasury), that every no-backend screen is hidden, that groups collapse in order, and that the landing (REAL_HOME) is itself real-backed. Demo mode still shows the full set.Why Settings/Overview/etc. are hidden, not ported
Those domains have no backend on this platform (no
/payments,/invoices,/grants,/policies,/integrations,/dashboard; even the Settings team table read targets a non-existent/members). Per the epic's option-A decision they stay demo-only. Workspace switching remains available in the top bar. Real team management (/orgs/:id/members) is a genuine future feature, not a gate.Verification (all green)
typecheck·test(76, incl. 6 new nav tests) ·build(real) ·VITE_DEMO_MODE=1 build·biome lint .This completes the console→backend reconciliation (epic #58). Remaining: redeploy
console.benzo.space(real mode) + the BenzoNet e2e (gated on test-USDC for a treasury deposit).Summary by CodeRabbit
New Features
Tests
Greptile Summary
This PR gates the console navigation and routes by demo mode. The main changes are:
Confidence Score: 4/5
The real-mode route gate needs a fix before merging.
/claimnow redirects to treasury for active-org users in real mode.apps/console/src/app/Shell.tsx
Important Files Changed
/claimand still statically imports demo-only screens.Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "feat(console): real-mode nav/route gatin..." | Re-trigger Greptile