Skip to content

MVP-1 client-reachable surface inventory — 2026-08-12 (OGUR-70)

Governing rule: mvp-scope.md §2.3 / §5 — every surface a client can reach must be real; anything not real must be gated (unreachable) or deleted. This document records the per-surface decision QA reads.

Gate mechanism. frontend/src/lib/demoMode.tsdemoSurfacesEnabled(), reading VITE_DEMO_SURFACES at build time. Default (unset) = gated — client builds, CI, and the deployed container never register the demo routes and hide every affordance pointing at them. Founders opt back in locally with VITE_DEMO_SURFACES=true. The gate is deliberately not identity-based: User has no role field, and an anonymous visitor at the deployed URL is a pre-login client, so the flag must hold regardless of session. No mock was wired to a real endpoint (§5 forbids it — conversational Ask is OUT).

Evidence pass. 2026-08-12, exercised as the client identity genfit.provisional@ogur.local (seeded by scripts/seed/seed_users.py --provisional, logged in via POST /api/auth/login) against the built frontend served same-origin by the API (OGUR-67 shape). Before gating, that session could reach the full Sanofi mock franchise, mock Ask, mock conversation history, the Dupixent asset workspace with a simulated briefing, and blank pages at /settings and unknown URLs. After gating, every row below was re-verified.

Surface Entry points Observed as client (pre-gate) Disposition Mechanism Evidence Verified by
/ ModeSelector root URL, LeftRail logo Explore + Monitor cards real (Monitor card gated) conditional card render ModeSelector.tsx clientGating: "ModeSelector offers Explore but not Monitor"
/explore Workspaces tab rail, ModeSelector, ⌘K, catch-all target real built-landscape roster plus mock workspaces + templates real (mock cards gated) WORKSPACES grid demo-only WorkspacesTab.tsx manual (gated browse)
/explore Asset catalog tab tab bar, ?tab=Asset catalog mock ALL_PORTFOLIO_ASSETS table gated tab removed; ?tab= validated against visible tabs Explore.tsx clientGating: "crafted ?tab= URL"
/explore/research/new ResearchWizard Workspaces hero real decompose/classes/match; mock adjacency panel real (adjacency panel gated) panel demo-only ResearchWizard.tsx manual
/explore/landscape/:id LandscapeReport built-landscapes cards real frozen packs + feedback real — untouched LandscapeReport.tsx manual (GNS561 report renders)
/signals (+ /deals redirect) rail, ⌘K real GET /api/signals real — kept GlobalSignals.tsx manual
/ask GlobalAsk ⌘K, direct URL scripted mock answers (MOCK_FALLBACK_ENABLED) gated route unregistered → catch-all App.tsx clientGating route table
Ask pill + ⌘J + hint bubble + AskDrawer every shell page opens mock Ask drawer gated all four behind demo in AppShell App.tsx clientGating: "no Ask AI pill, ⌘J opens nothing"
/franchise, /franchise/:slug rail, ModeSelector Monitor full Sanofi mock franchise (hand-written LOE, phase mix, cliffs) gated routes unregistered App.tsx clientGating route table
/history rail MOCK_CONVERSATIONS (12 fabricated threads) gated route unregistered App.tsx clientGating route table
/asset/:drugName ⌘K assets, asset tables, Inspector mock briefings, SIMULATED docs, Dupixent-only timeline on every asset (CompetitiveTimeline default DUPIXENT_TIMELINE) gated route unregistered — covers §5's timeline + LandscapeTab hardcode; every entry point also gated App.tsx clientGating route table
/foundations URL only design-system gallery gated route unregistered App.tsx clientGating route table
/settings rail link rail blank page — route never existed deleted (both modes) nav item removed LeftRail.tsx clientGating + demo file
Unknown URLs blank page (no 404 route) fixed path="*"/explore App.tsx clientGating: /no-such-route
LeftRail "My Franchise", "Conversation history", ⌘J shortcut row rail links into gated surfaces gated nav/shortcut arrays filtered LeftRail.tsx clientGating: "LeftRail shows Explorer only"
⌘K palette: Ask item, Assets, Targets, Companies groups ⌘K navigate to gated routes / open mock Inspector panels gated conditional groups CommandPalette.tsx clientGating: "command palette has no…"
Inspector "Summarize with AI" signal selection mockSummarize() behind fake 700ms delay, rendered as "AI summary" gated button demo-only; real signal.summary source text kept Inspector.tsx clientGating: Inspector signal panel
Inspector asset / target / company panels ⌘K, entity chips SANOFI mock hydration; static placeholders; "Open full view" → gated /asset/* gated panel renders nothing for non-signal selections Inspector.tsx clientGating: "mock panel renders nothing"

Accepted debt (recorded, not gated)

  • GlobalSignals.tsx hardcodes landscape_id: 'nsclc-001' — real data, wrong scope parameterization. Real per §5; fix is post-release.
  • src/mocks/* files, MOCK_FALLBACK_ENABLED, DUPIXENT_TIMELINE, and the LandscapeTab dupilumab hardcode remain on disk — unreachable from a client build. §5 names their deletion as post-release debt, not an Aug 13 requirement.
  • /deals redirects into /signals; if /signals is ever gated, /deals must follow.

Replay

  • Gated default: npx vitest run src/__tests__/clientGating.test.tsx
  • Demo mode: npx vitest run src/__tests__/clientGating.demo.test.tsx
  • Live: build without VITE_DEMO_SURFACES, serve via the API (FRONTEND_DIST_PATH=frontend/dist), sign in as a provisional client, and walk the table.