forked from Mike/freno-dev
better changelog capture
This commit is contained in:
@@ -86,7 +86,7 @@
|
|||||||
|
|
||||||
This project serves four product subdomains (`nessa.freno.me`, `lineage.freno.me`, `gaze.freno.me`, `inputhalo.freno.me`) plus the personal site on `freno.me`. See `docs/subdomain-setup.md` for DNS/Vercel configuration.
|
This project serves four product subdomains (`nessa.freno.me`, `lineage.freno.me`, `gaze.freno.me`, `inputhalo.freno.me`) plus the personal site on `freno.me`. See `docs/subdomain-setup.md` for DNS/Vercel configuration.
|
||||||
|
|
||||||
- **Route placement:** Subdomain pages live under `src/routes/<prefix>/*` (e.g. `src/routes/nessa/...`) as the source-of-truth content components. The public browser path on each subdomain (e.g. `lineage.freno.me/privacy` → `/privacy`) is served by **host-aware root route dispatch**: the root route file (`src/routes/privacy.tsx`, `deletion.tsx`, `contact.tsx`, `downloads.tsx`, `index.tsx`) resolves on the public path on BOTH server and client, then selects the matching subdomain component via `useSite()`. Do **not** rewrite the request path server-side — SolidStart's client `Router` matches on `window.location.pathname`, so a server-only rewrite diverges SSR from hydration and causes hydration mismatches. (The `vercel.json` host `rewrites` are declared but **not applied** by Vercel — the Nitro `vercel` preset emits a Build Output API `config.json` whose `routes` array fully replaces `vercel.json` rewrites/redirects/headers — so the host-aware root dispatch is what actually serves subdomain pages.)
|
- **Route placement:** Subdomain pages live under `src/routes/<prefix>/*` (e.g. `src/routes/nessa/...`) as the source-of-truth content components. The public browser path on each subdomain (e.g. `lineage.freno.me/privacy` → `/privacy`) is served by **host-aware root route dispatch**: the root route file (`src/routes/privacy.tsx`, `deletion.tsx`, `contact.tsx`, `downloads.tsx`, `index.tsx`) resolves on the public path on BOTH server and client, then selects the matching subdomain component via `useSite()`. Do **not** rewrite the request path server-side — SolidStart's client `Router` matches on `window.location.pathname`, so a server-only rewrite diverges SSR from hydration and causes hydration mismatches. (The `vercel.json` host `rewrites` are declared but **not applied** by Vercel — the Nitro `vercel` preset emits a Build Output API `config.json` whose `routes` array fully replaces `vercel.json` rewrites/redirects/headers — so the host-aware root dispatch is what actually serves subdomain pages.) **Every subdomain surface requires both files:** the `src/routes/<prefix>/<name>.tsx` content component AND a matching top-level dispatch file (`src/routes/<name>.tsx` or `src/routes/<name>/index.tsx`) that imports and dispatches to it — without the dispatch file, the page is unreachable on the subdomain. The `src/routes/subdomain-dispatch.test.ts` suite enforces this automatically on every `bun test` run.
|
||||||
- **Site context:** Use `useSite()` (SolidJS) or `getSiteFromEvent`/`getSiteFromRequest` (server) from `src/lib/site-context.ts` to detect the current site. Never host-snoop in route files — SolidStart's router can't match on host.
|
- **Site context:** Use `useSite()` (SolidJS) or `getSiteFromEvent`/`getSiteFromRequest` (server) from `src/lib/site-context.ts` to detect the current site. Never host-snoop in route files — SolidStart's router can't match on host.
|
||||||
- **API routes:** `/api/*` is a shared pool — subdomain API requests pass through to existing routes via vercel.json pass-through rewrites (ordering matters).
|
- **API routes:** `/api/*` is a shared pool — subdomain API requests pass through to existing routes via vercel.json pass-through rewrites (ordering matters).
|
||||||
- **Auth:** Host-scoped only — no cookie domain broadening.
|
- **Auth:** Host-scoped only — no cookie domain broadening.
|
||||||
|
|||||||
@@ -29,12 +29,23 @@ const AGENTS: AgentEntry[] = [
|
|||||||
name: "Claude Code",
|
name: "Claude Code",
|
||||||
wiring: "Hook entries in settings.json pointing at the shared hook binary",
|
wiring: "Hook entries in settings.json pointing at the shared hook binary",
|
||||||
events: [
|
events: [
|
||||||
"UserPromptSubmit", "SessionStart", "SessionEnd", "Stop", "StopFailure",
|
"UserPromptSubmit",
|
||||||
"SubagentStart", "SubagentStop", "Notification", "PreToolUse",
|
"SessionStart",
|
||||||
"PermissionRequest", "PostToolUse", "PostToolUseFailure",
|
"SessionEnd",
|
||||||
"PermissionDenied", "PreCompact"
|
"Stop",
|
||||||
|
"StopFailure",
|
||||||
|
"SubagentStart",
|
||||||
|
"SubagentStop",
|
||||||
|
"Notification",
|
||||||
|
"PreToolUse",
|
||||||
|
"PermissionRequest",
|
||||||
|
"PostToolUse",
|
||||||
|
"PostToolUseFailure",
|
||||||
|
"PermissionDenied",
|
||||||
|
"PreCompact"
|
||||||
],
|
],
|
||||||
gating: "Full — tool calls and permission prompts can be allowed or denied from a card"
|
gating:
|
||||||
|
"Full — tool calls and permission prompts can be allowed or denied from a card"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Codex",
|
name: "Codex",
|
||||||
@@ -44,89 +55,167 @@ const AGENTS: AgentEntry[] = [
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OpenCode",
|
name: "OpenCode",
|
||||||
wiring: "In-process plugin (~/.config/opencode/plugins/nook.js) on the server bus",
|
wiring:
|
||||||
|
"In-process plugin (~/.config/opencode/plugins/nook.js) on the server bus",
|
||||||
events: [
|
events: [
|
||||||
"SessionStart", "SessionEnd", "UserPromptSubmit", "PreToolUse",
|
"SessionStart",
|
||||||
"PostToolUse", "Stop", "MessageUpdate", "PermissionRequest", "QuestionAsked"
|
"SessionEnd",
|
||||||
|
"UserPromptSubmit",
|
||||||
|
"PreToolUse",
|
||||||
|
"PostToolUse",
|
||||||
|
"Stop",
|
||||||
|
"MessageUpdate",
|
||||||
|
"PermissionRequest",
|
||||||
|
"QuestionAsked"
|
||||||
],
|
],
|
||||||
gating: "Full — the plugin answers OpenCode's own permission API when you tap Allow or Deny"
|
gating:
|
||||||
|
"Full — the plugin answers OpenCode's own permission API when you tap Allow or Deny"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Pi",
|
name: "Pi",
|
||||||
wiring: "In-process extension (~/.pi/agent/extensions/nook.ts)",
|
wiring: "In-process extension (~/.pi/agent/extensions/nook.ts)",
|
||||||
events: [
|
events: [
|
||||||
"SessionStart", "SessionEnd", "UserPromptSubmit", "PreToolUse",
|
"SessionStart",
|
||||||
"PostToolUse", "Stop", "MessageUpdate", "QuestionAsked", "QuestionResolved"
|
"SessionEnd",
|
||||||
|
"UserPromptSubmit",
|
||||||
|
"PreToolUse",
|
||||||
|
"PostToolUse",
|
||||||
|
"Stop",
|
||||||
|
"MessageUpdate",
|
||||||
|
"QuestionAsked",
|
||||||
|
"QuestionResolved"
|
||||||
],
|
],
|
||||||
gating: "Full when you force verification — Pi alone never asks natively, so the gate engages on the approval tool"
|
gating:
|
||||||
|
"Full when you force verification — Pi alone never asks natively, so the gate engages on the approval tool"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Oh My Pi",
|
name: "Oh My Pi",
|
||||||
wiring: "In-process extension package (~/.omp/agent/extensions/nook/)",
|
wiring: "In-process extension package (~/.omp/agent/extensions/nook/)",
|
||||||
events: [
|
events: [
|
||||||
"SessionStart", "SessionEnd", "UserPromptSubmit", "PreToolUse",
|
"SessionStart",
|
||||||
"PostToolUse", "Stop", "MessageUpdate", "QuestionAsked", "QuestionResolved"
|
"SessionEnd",
|
||||||
|
"UserPromptSubmit",
|
||||||
|
"PreToolUse",
|
||||||
|
"PostToolUse",
|
||||||
|
"Stop",
|
||||||
|
"MessageUpdate",
|
||||||
|
"QuestionAsked",
|
||||||
|
"QuestionResolved"
|
||||||
],
|
],
|
||||||
gating: "Full — honors the agent's own approval tiers, per-tool policies included"
|
gating:
|
||||||
|
"Full — honors the agent's own approval tiers, per-tool policies included"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Hermes",
|
name: "Hermes",
|
||||||
wiring: "Shell-hook entries in the agent's config, consent-gated by allowlist",
|
wiring:
|
||||||
|
"Shell-hook entries in the agent's config, consent-gated by allowlist",
|
||||||
events: [
|
events: [
|
||||||
"SessionStart", "SessionEnd", "UserPromptSubmit", "Stop", "PreToolUse",
|
"SessionStart",
|
||||||
"PostToolUse", "SubagentStart", "SubagentStop"
|
"SessionEnd",
|
||||||
|
"UserPromptSubmit",
|
||||||
|
"Stop",
|
||||||
|
"PreToolUse",
|
||||||
|
"PostToolUse",
|
||||||
|
"SubagentStart",
|
||||||
|
"SubagentStop"
|
||||||
],
|
],
|
||||||
gating: "Observation — every event appears as a card; the agent keeps its own prompting"
|
gating:
|
||||||
|
"Observation — every event appears as a card; the agent keeps its own prompting"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "OpenClaw",
|
name: "OpenClaw",
|
||||||
wiring: "Hook handler package (~/.openclaw/hooks/nook/)",
|
wiring: "Hook handler package (~/.openclaw/hooks/nook/)",
|
||||||
events: ["SessionStart", "UserPromptSubmit", "AgentResponse", "PreCompact", "SessionEnd"],
|
events: [
|
||||||
gating: "Observation — strict fire-and-forget; the agent surface has no tool or permission events"
|
"SessionStart",
|
||||||
|
"UserPromptSubmit",
|
||||||
|
"AgentResponse",
|
||||||
|
"PreCompact",
|
||||||
|
"SessionEnd"
|
||||||
|
],
|
||||||
|
gating:
|
||||||
|
"Observation — strict fire-and-forget; the agent surface has no tool or permission events"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Z.ai (ZCode)",
|
name: "Z.ai (ZCode)",
|
||||||
wiring: "Hook entries in ~/.zcode/cli/config.json",
|
wiring: "Hook entries in ~/.zcode/cli/config.json",
|
||||||
events: [
|
events: [
|
||||||
"SessionStart", "UserPromptSubmit", "PreToolUse", "PermissionRequest",
|
"SessionStart",
|
||||||
"PostToolUse", "PostToolUseFailure", "Stop"
|
"UserPromptSubmit",
|
||||||
|
"PreToolUse",
|
||||||
|
"PermissionRequest",
|
||||||
|
"PostToolUse",
|
||||||
|
"PostToolUseFailure",
|
||||||
|
"Stop"
|
||||||
],
|
],
|
||||||
gating: "Full — tool calls and permission prompts can be allowed or denied from a card"
|
gating:
|
||||||
|
"Full — tool calls and permission prompts can be allowed or denied from a card"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Gemini CLI",
|
name: "Gemini CLI",
|
||||||
wiring: "Hook entries in settings.json",
|
wiring: "Hook entries in settings.json",
|
||||||
events: ["SessionStart", "SessionEnd", "AgentStart", "Stop", "Notification"],
|
events: [
|
||||||
gating: "Observation — every event appears as a card; the agent keeps its own prompting"
|
"SessionStart",
|
||||||
|
"SessionEnd",
|
||||||
|
"AgentStart",
|
||||||
|
"Stop",
|
||||||
|
"Notification"
|
||||||
|
],
|
||||||
|
gating:
|
||||||
|
"Observation — every event appears as a card; the agent keeps its own prompting"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Cline",
|
name: "Cline",
|
||||||
wiring: "One hook file per event in ~/Documents/Cline/Hooks",
|
wiring: "One hook file per event in ~/Documents/Cline/Hooks",
|
||||||
events: [
|
events: [
|
||||||
"SessionStart", "SessionEnd", "UserPromptSubmit", "PreToolUse",
|
"SessionStart",
|
||||||
"PostToolUse", "Stop", "PreCompact", "Notification"
|
"SessionEnd",
|
||||||
|
"UserPromptSubmit",
|
||||||
|
"PreToolUse",
|
||||||
|
"PostToolUse",
|
||||||
|
"Stop",
|
||||||
|
"PreCompact",
|
||||||
|
"Notification"
|
||||||
],
|
],
|
||||||
gating: "Observation — every event appears as a card; the agent keeps its own prompting"
|
gating:
|
||||||
|
"Observation — every event appears as a card; the agent keeps its own prompting"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "Qwen Code",
|
name: "Qwen Code",
|
||||||
wiring: "Hook entries in settings.json (same surface as the Claude-style schema)",
|
wiring:
|
||||||
|
"Hook entries in settings.json (same surface as the Claude-style schema)",
|
||||||
events: [
|
events: [
|
||||||
"UserPromptSubmit", "SessionStart", "SessionEnd", "Stop", "StopFailure",
|
"UserPromptSubmit",
|
||||||
"SubagentStart", "SubagentStop", "Notification", "PreToolUse",
|
"SessionStart",
|
||||||
"PermissionRequest", "PostToolUse", "PostToolUseFailure",
|
"SessionEnd",
|
||||||
"PermissionDenied", "PreCompact"
|
"Stop",
|
||||||
|
"StopFailure",
|
||||||
|
"SubagentStart",
|
||||||
|
"SubagentStop",
|
||||||
|
"Notification",
|
||||||
|
"PreToolUse",
|
||||||
|
"PermissionRequest",
|
||||||
|
"PostToolUse",
|
||||||
|
"PostToolUseFailure",
|
||||||
|
"PermissionDenied",
|
||||||
|
"PreCompact"
|
||||||
],
|
],
|
||||||
gating: "Full — tool calls and permission prompts can be allowed or denied from a card"
|
gating:
|
||||||
|
"Full — tool calls and permission prompts can be allowed or denied from a card"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
name: "DeepSeek",
|
name: "DeepSeek",
|
||||||
wiring: "Standalone hooks.json at ~/.dsh",
|
wiring: "Standalone hooks.json at ~/.dsh",
|
||||||
events: [
|
events: [
|
||||||
"SessionStart", "UserPromptSubmit", "PreToolUse", "PostToolUse", "Stop",
|
"SessionStart",
|
||||||
"SubagentStart", "SubagentStop"
|
"UserPromptSubmit",
|
||||||
|
"PreToolUse",
|
||||||
|
"PostToolUse",
|
||||||
|
"Stop",
|
||||||
|
"SubagentStart",
|
||||||
|
"SubagentStop"
|
||||||
],
|
],
|
||||||
gating: "Tool calls can be allowed or denied from a card; permission prompts stay with the agent"
|
gating:
|
||||||
|
"Tool calls can be allowed or denied from a card; permission prompts stay with the agent"
|
||||||
}
|
}
|
||||||
];
|
];
|
||||||
|
|
||||||
@@ -175,9 +264,7 @@ export default function NookAgentsPage() {
|
|||||||
<div class="grid gap-4 lg:grid-cols-2">
|
<div class="grid gap-4 lg:grid-cols-2">
|
||||||
<For each={AGENTS}>
|
<For each={AGENTS}>
|
||||||
{(agent) => (
|
{(agent) => (
|
||||||
<article
|
<article class="border-overlay0 bg-surface0/40 rounded-2xl border p-5">
|
||||||
class="border-overlay0 bg-surface0/40 rounded-2xl border p-5"
|
|
||||||
>
|
|
||||||
<div class="mb-3 flex items-center justify-between gap-3">
|
<div class="mb-3 flex items-center justify-between gap-3">
|
||||||
<h2 class="text-text text-lg font-semibold">{agent.name}</h2>
|
<h2 class="text-text text-lg font-semibold">{agent.name}</h2>
|
||||||
<span
|
<span
|
||||||
@@ -207,8 +294,7 @@ export default function NookAgentsPage() {
|
|||||||
|
|
||||||
<p class="text-subtext1 mt-10 max-w-2xl text-sm">
|
<p class="text-subtext1 mt-10 max-w-2xl text-sm">
|
||||||
Running something else? A custom agent can wire itself in with the
|
Running something else? A custom agent can wire itself in with the
|
||||||
same one-line contract the built-in integrations use — see the
|
same one-line contract the built-in integrations use.
|
||||||
plugin spec in the app's docs.
|
|
||||||
</p>
|
</p>
|
||||||
</main>
|
</main>
|
||||||
</>
|
</>
|
||||||
|
|||||||
@@ -29,7 +29,7 @@ const FAQ: FaqEntry[] = [
|
|||||||
{
|
{
|
||||||
question: "How much battery does it cost?",
|
question: "How much battery does it cost?",
|
||||||
answer:
|
answer:
|
||||||
"The Nook is a small native UI, not a background agent — it watches the agents you're already running; it doesn't run anything itself. The heaviest thing it does is read and write your Mac's fan and charging controllers, which is a lightweight sensor read. If you're running enough coding agents to need it, those agents dwarf the app's own draw — which is rather the point: the fan control exists because the agents are what heat the machine."
|
"The Nook is a small native UI, not a background agent — it watches the agents you're already running; it doesn't run anything itself. The heaviest thing it does is read and write your Mac's fan and charging controllers, which is a lightweight sensor read. If you're running enough coding agents to need it, those agents dwarf the app's own draw."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: "What permissions does it need?",
|
question: "What permissions does it need?",
|
||||||
@@ -39,7 +39,7 @@ const FAQ: FaqEntry[] = [
|
|||||||
{
|
{
|
||||||
question: "Why macOS 14 or later?",
|
question: "Why macOS 14 or later?",
|
||||||
answer:
|
answer:
|
||||||
"The interface is built on Apple's current observation framework, which ships with macOS 14 (Sonoma). Supporting older versions would mean rewriting the whole UI layer on a deprecated foundation — time that goes into the app instead. macOS 14 runs on hardware from 2018 onward, which covers the machines that run coding agents comfortably."
|
"The interface is built on Apple's current observation framework, which ships with macOS 14 (Sonoma)."
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
question: "Does it work offline?",
|
question: "Does it work offline?",
|
||||||
@@ -60,7 +60,7 @@ export default function NookFaqPage() {
|
|||||||
ogTitle="The Nook — FAQ"
|
ogTitle="The Nook — FAQ"
|
||||||
/>
|
/>
|
||||||
<SubdomainHeader />
|
<SubdomainHeader />
|
||||||
<main class="min-h-screen px-[8vw] py-[8vh]">
|
<main class="flex min-h-screen flex-col items-center px-[8vw] py-[8vh]">
|
||||||
<h1 class="text-text mb-3 text-3xl font-bold tracking-tight sm:text-4xl">
|
<h1 class="text-text mb-3 text-3xl font-bold tracking-tight sm:text-4xl">
|
||||||
Questions people actually ask
|
Questions people actually ask
|
||||||
</h1>
|
</h1>
|
||||||
@@ -76,7 +76,7 @@ export default function NookFaqPage() {
|
|||||||
.
|
.
|
||||||
</p>
|
</p>
|
||||||
|
|
||||||
<div class="max-w-3xl space-y-3">
|
<div class="w-full max-w-3xl space-y-3">
|
||||||
<For each={FAQ}>
|
<For each={FAQ}>
|
||||||
{(entry, i) => (
|
{(entry, i) => (
|
||||||
<div class="border-overlay0 bg-surface0/40 rounded-2xl border">
|
<div class="border-overlay0 bg-surface0/40 rounded-2xl border">
|
||||||
|
|||||||
99
src/routes/subdomain-dispatch.test.ts
Normal file
99
src/routes/subdomain-dispatch.test.ts
Normal file
@@ -0,0 +1,99 @@
|
|||||||
|
/**
|
||||||
|
* Subdomain routing invariant test.
|
||||||
|
*
|
||||||
|
* Verifies that every subdomain route (src/routes/<prefix>/<name>) has a
|
||||||
|
* corresponding top-level dispatch route (src/routes/<name>.tsx or
|
||||||
|
* src/routes/<name>/index.tsx). This is required because production Vercel
|
||||||
|
* deployments do not apply vercel.json host rewrites — the Nitro Build Output
|
||||||
|
* API config.json replaces them entirely, so host-aware root dispatch is the
|
||||||
|
* only mechanism that serves subdomain pages.
|
||||||
|
*
|
||||||
|
* See docs/subdomain-setup.md and AGENTS.md "Subdomain Routing" for context.
|
||||||
|
*/
|
||||||
|
import { describe, it, expect } from "bun:test";
|
||||||
|
import { readdirSync, existsSync, statSync } from "fs";
|
||||||
|
|
||||||
|
function isRouteFile(name: string): boolean {
|
||||||
|
return name.endsWith(".tsx") || name.endsWith(".ts");
|
||||||
|
}
|
||||||
|
|
||||||
|
function listRouteFiles(dir: string): string[] {
|
||||||
|
try {
|
||||||
|
return readdirSync(dir).filter(isRouteFile);
|
||||||
|
} catch {
|
||||||
|
return [];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
function hasDispatchRoute(routeName: string, routesDir: string): boolean {
|
||||||
|
// Check for src/routes/<name>.tsx
|
||||||
|
if (existsSync(`${routesDir}/${routeName}.tsx`)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
// Check for src/routes/<name>/index.tsx
|
||||||
|
if (existsSync(`${routesDir}/${routeName}/index.tsx`)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
describe("subdomain routing dispatch invariant", () => {
|
||||||
|
const routesDir = "./src/routes";
|
||||||
|
|
||||||
|
it("every subdomain route has a top-level dispatch route", () => {
|
||||||
|
const prefixDirs = listRouteFiles(routesDir)
|
||||||
|
.filter((name) => {
|
||||||
|
try {
|
||||||
|
return statSync(`${routesDir}/${name}`).isDirectory();
|
||||||
|
} catch {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
})
|
||||||
|
// Exclude non-product subdomains
|
||||||
|
.filter((name) =>
|
||||||
|
["nessa", "lineage", "gaze", "inputhalo", "nook"].includes(name)
|
||||||
|
);
|
||||||
|
|
||||||
|
const missing = [];
|
||||||
|
|
||||||
|
for (const prefix of prefixDirs) {
|
||||||
|
const prefixFiles = listRouteFiles(`${routesDir}/${prefix}`);
|
||||||
|
for (const file of prefixFiles) {
|
||||||
|
// Skip test files and non-route files
|
||||||
|
if (file.includes("test") || file.includes("spec")) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
if (!isRouteFile(file)) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Determine route name (without extension)
|
||||||
|
const routeName = file.replace(/\.(tsx|ts)$/, "");
|
||||||
|
|
||||||
|
// index.tsx maps to the parent directory name
|
||||||
|
const effectiveRoute = routeName === "index" ? prefix : routeName;
|
||||||
|
|
||||||
|
// Skip routes that are intentionally not dispatched to root
|
||||||
|
// (these are subdomain-specific, not shared surfaces)
|
||||||
|
if (
|
||||||
|
effectiveRoute === "content" ||
|
||||||
|
effectiveRoute === "meta" ||
|
||||||
|
effectiveRoute.endsWith("-content") ||
|
||||||
|
effectiveRoute === "password-reset" ||
|
||||||
|
effectiveRoute === "request-password-reset" ||
|
||||||
|
effectiveRoute === "checkout" ||
|
||||||
|
effectiveRoute === "success" ||
|
||||||
|
effectiveRoute === "notifications"
|
||||||
|
) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!hasDispatchRoute(effectiveRoute, routesDir)) {
|
||||||
|
missing.push(`${prefix}/${file} → missing dispatch for /${effectiveRoute}`);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(missing).toEqual([]);
|
||||||
|
});
|
||||||
|
});
|
||||||
Reference in New Issue
Block a user