Files
vyndr/web/src/lib/routes.js
T
builtbykev 0e7871ac0e S4 (a1): the media engine — VOICE templates, /desk, Ghost drafts
4a VOICE v1.1 committed (board start); lint is EXECUTABLE — banned list +
   no-exclamation law enforced in the engine (throws in test, drops in
   prod) and locked by tests. Curly-apostrophe variants covered.
4b mediaEngine: deterministic templates (MORNING WIRE, SIGNAL, STREAK
   WATCH, THE SETTLE, RECEIPTS, ARCHETYPE WATCH, LINE DISPATCH) filled
   ONLY from snapshot/ledger/streaks JSON. Record percentages never render
   under n>=20 (counts + 'Record building' below). Stark layer = curated
   committed library (content/stark-lines.json), day-rotated selection —
   selected, never generated.
4c /desk (founder-only: requireAuth + DESK_OWNERS email allowlist,
   deny-by-default): all formats as text + <=280-char pre-segmented tweets
   with per-tweet copy buttons + char counts, wire/numbers-only variants,
   DATA BRIEF block (structured day numbers) with copy-for-claude.ai.
   ntfy ping after the day's first snapshot: 'Desk pack ready'.
4d ghostPublisher: DRAFTS ONLY (status:'draft' test-locked), env-gated
   no-op, HS256 JWT via node crypto (zero new deps). POST
   /api/internal/ghost/drafts saves slate preview + settle drafts.
   Nothing anywhere auto-posts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-11 14:31:35 -04:00

94 lines
2.9 KiB
JavaScript

/* ============================================================
VYNDR 2.0 — app-shell routing config (§6, §12).
Plain CommonJS so the client AuthGate/HashRedirect import it
(allowJs) AND the Jest suite requires it directly (no transform).
AUTH NOTE: our auth is client-side Supabase (session in
localStorage via @supabase/supabase-js), and the existing
middleware.ts is locale-only. A Next server middleware can't read
that session, so the gate is enforced CLIENT-side by <AuthGate>.
This file is the single source of truth for which routes gate.
============================================================ */
/* Gated — require an authenticated user. Deliberately narrower than the
prototype's GATED set: the prototype also gated dashboard + scan, but those
are OUR free-scan acquisition funnel (anon/free users get 5 reads), so
gating them would be a monetization regression. We gate only the genuinely
personal surfaces (a user's own ledger, bets, account, alerts). */
const GATED_ROUTES = [
'/desk', // Session 63 (A1-S4) — the founder's media surface (+ backend allowlist)
'/ledger',
'/tracker',
'/account',
'/profile',
'/settings',
'/notifications',
'/invite',
];
/* Open — reachable without auth (marketing + the free funnel + auth itself). */
/* Session 57 (Phase 0): '/terminal' removed — the route now server-redirects
to /dashboard (fabricated surface retired), so it needs no gate exemption. */
const OPEN_ROUTES = [
'/',
'/dashboard',
'/slate',
'/scan',
'/compare',
'/game',
'/pricing',
'/blog',
'/article',
'/responsible-gambling',
'/help',
'/about',
'/terms',
'/privacy',
'/login',
'/signup',
'/auth',
'/forgot-password',
'/verify',
'/welcome',
'/offline',
'/upgrade',
];
/* Hash deep-link aliases (§C.3.4). The prototype was a single HTML file using
#scan / #terminal; we keep Next file-based routing and treat these hashes as
redirects so old share links and PWA shortcuts still resolve. */
const HASH_ALIASES = {
'#slate': '/dashboard',
'#dashboard': '/dashboard',
'#scan': '/scan',
'#terminal': '/dashboard', // retired surface (Session 57) — old links land on the slate
'#compare': '/compare',
'#ledger': '/ledger',
'#tracker': '/tracker',
'#account': '/account',
'#pricing': '/pricing',
'#blog': '/blog',
'#invite': '/invite',
'#notifications': '/notifications',
};
/** True when `pathname` falls under a gated route (exact or nested). */
function isGatedRoute(pathname) {
if (!pathname) return false;
return GATED_ROUTES.some((r) => pathname === r || pathname.startsWith(r + '/'));
}
/** Resolve a window.location.hash (e.g. "#scan") to a real route, or null. */
function resolveHashAlias(hash) {
if (!hash) return null;
return HASH_ALIASES[hash] || null;
}
module.exports = {
GATED_ROUTES,
OPEN_ROUTES,
HASH_ALIASES,
isGatedRoute,
resolveHashAlias,
};