'use strict'; // VERB LAW (Truth-Everywhere Part 2, item 1): the user-facing verb is READ, // never SCAN. This lint fails if user-VISIBLE scan copy reappears anywhere in // the web app or the backend strings that reach the UI (ticker text, upgrade // pitch). It targets RENDERED patterns, not internal identifiers — route paths // (/api/scan), state vars (scanning), DB columns (scan_count), component names // (DemoScan/ScanIcon), the CSS 'scanlines' class, and the transitional ticker // color-map key are all legitimately internal and NOT matched. const fs = require('fs'); const path = require('path'); const ROOTS = [ path.join(__dirname, '..', '..', 'web', 'src'), path.join(__dirname, '..', '..', 'src', 'services'), ]; // Each pattern is a user-visible scan phrase that must never ship. const FORBIDDEN = [ { re: /label:\s*['"]Scan['"]/, why: "nav/tab label 'Scan' → 'Read'" }, { re: /slate scanned/i, why: "ticker 'slate scanned' → 'slate read'" }, { re: /\b\d+\s+scans\b/i, why: "tier copy 'N scans' → 'N reads'" }, { re: /unlimited scans/i, why: "'unlimited scans' → 'unlimited reads'" }, { re: /you'?ve scanned/i, why: "'You've scanned …' → 'You've read …'" }, { re: /Awaiting next scan/i, why: "'Awaiting next scan' → 'Awaiting next read'" }, { re: /Scanning\.\.\./, why: "loading 'Scanning…' → 'Reading…'" }, { re: />\s*Scan\b(?!Icon)/, why: "JSX text 'Scan' → 'Read'" }, ]; function walk(dir, out) { for (const name of fs.readdirSync(dir)) { const p = path.join(dir, name); const st = fs.statSync(p); if (st.isDirectory()) { if (name !== 'node_modules') walk(p, out); } else if (/\.(tsx?|jsx?)$/.test(name)) out.push(p); } } describe('VERB LAW — no user-visible "scan" copy', () => { const files = []; for (const r of ROOTS) if (fs.existsSync(r)) walk(r, files); test('every rendered surface uses READ, never SCAN', () => { const hits = []; for (const f of files) { const lines = fs.readFileSync(f, 'utf8').split('\n'); lines.forEach((line, i) => { const t = line.trim(); // Comments aren't user-visible — skip comment-only lines (rate-limit // docs, history notes). A rendered string never starts with a comment. if (t.startsWith('//') || t.startsWith('*') || t.startsWith('/*')) return; for (const { re, why } of FORBIDDEN) { if (re.test(line)) hits.push(`${path.relative(process.cwd(), f)}:${i + 1} — ${why}\n ${line.trim()}`); } }); } if (hits.length) throw new Error(`User-visible "scan" copy found (verb is READ):\n${hits.join('\n')}`); expect(hits).toEqual([]); }); });