slice 1g: Next.js M1 overview panel + Primary-Rule lint (ADR-0007)

Symbol command bar (Zustand active-symbol) -> OverviewPanel (price + day-change
shape/color/label per P7 G3 + Recharts sparkline + sector) + signup/login + ADR-0007
footer. next.config rewrites proxy /api -> backend:3001 (same-origin cookies). Vanilla
fetch tRPC client (decoupled from server Node types). Primary-Rule lint 2/2 green.
Slice 1 tracer bullet COMPLETE: 38 tests green, live-verified end-to-end.
This commit is contained in:
Investor Flow Build
2026-06-29 17:51:54 -04:00
parent 82079b3e66
commit a8b95e36e3
10 changed files with 567 additions and 0 deletions
@@ -0,0 +1,48 @@
import { test } from "node:test";
import { strict as assert } from "node:assert";
import { readFileSync } from "node:fs";
import { join, dirname } from "node:path";
import { fileURLToPath } from "node:url";
import { UI_STRING_LIST } from "../lib/strings.ts";
// ADR-0007 golden master (DESIGN.md §7.3): no imperative trade directive in any curated UI
// string or panel/page/client source. Stoplist of imperatives; "buy-zone estimate" (a noun) is allowed.
const __dirname = dirname(fileURLToPath(import.meta.url));
const STOPWORDS = ["buy", "sell", "you should", "add to your", "rotate into", "action needed"];
const ALLOWED_PHRASES = ["buy-zone"];
function imperativeHits(text: string): string[] {
const hits: string[] = [];
for (const w of STOPWORDS) {
const re = new RegExp("\\b" + w.replace(/\s+/g, "\\s+") + "\\b", "gi");
let m: RegExpExecArray | null;
while ((m = re.exec(text)) !== null) {
const ctx = text.slice(Math.max(0, m.index - 14), m.index + w.length + 14).replace(/\s+/g, " ").trim();
if (ALLOWED_PHRASES.some((a) => ctx.toLowerCase().includes(a))) continue;
hits.push(`"${w}" near: …${ctx}…`);
}
}
return hits;
}
test("curated UI strings pass the Primary-Rule lint", () => {
for (const s of UI_STRING_LIST) {
assert.deepEqual(imperativeHits(s), [], `Primary-Rule violation in string: "${s}"`);
}
});
test("panel/page/client source pass the Primary-Rule lint", () => {
const files = [
"../components/OverviewPanel.tsx",
"../app/page.tsx",
"../app/layout.tsx",
"../lib/strings.ts",
"../lib/trpc.ts",
"../stores/active-symbol-store.ts",
];
for (const f of files) {
const src = readFileSync(join(__dirname, f), "utf8");
const hits = imperativeHits(src);
assert.deepEqual(hits, [], `Primary-Rule violation in ${f}: ${hits.join("; ")}`);
}
});