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:
@@ -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("; ")}`);
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user