File size: 15,897 Bytes
8c1b9fe
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
// Structural UI guards for the premium redesign. The project's test harness is
// dependency-free (no DOM runtime), so these assert that each redesigned surface
// composes the required sections, copy and states at the source level — catching
// accidental regressions of the acceptance criteria without a browser.
import assert from "node:assert/strict";
import { readFileSync } from "node:fs";
import { fileURLToPath } from "node:url";
import { dirname, join } from "node:path";

const root = join(dirname(fileURLToPath(import.meta.url)), "..");
const read = (p) => readFileSync(join(root, p), "utf8");

let passed = 0;
const t = (name, fn) => {
  fn();
  passed++;
  console.log("  ok", name);
};
const has = (src, needle, msg) =>
  assert.ok(src.includes(needle), msg || `expected to find: ${needle}`);

// ---- landing composes all main sections ---------------------------------
t("landing page renders all main sections", () => {
  const page = read("app/page.tsx");
  for (const s of ["Hero", "SystemStatus", "Features", "HowItWorks", "Differentiators", "Stack", "CTA", "Footer"]) {
    has(page, `<${s} `, `landing missing <${s}>`);
  }
});

t("hero has value prop, dual CTAs and trust badges", () => {
  const hero = read("components/landing/Hero.tsx");
  has(hero, "Voice-native RAG for private documents");
  has(hero, "/chat"); // launch CTA
  has(hero, "github.com/MHHamdan/Auralynq"); // github CTA
  for (const b of ["Local-first", "$0 default", "Open source", "Citations on every answer", "Voice in / out", "Provider-agnostic"]) {
    has(hero, b, `hero missing trust badge: ${b}`);
  }
});

t("system status surfaces the seven subsystems with health states", () => {
  const s = read("components/landing/SystemStatus.tsx");
  for (const g of ["API", "Corpus", "Vector DB", "Embeddings", "LLM", "ASR / TTS", "Tracing"]) {
    has(s, g, `status missing group: ${g}`);
  }
  for (const h of ["healthy", "degraded", "offline"]) has(s, h);
});

t("capabilities render six benefit cards", () => {
  const f = read("components/landing/Features.tsx");
  for (const c of [
    "Voice-native document chat",
    "Grounded answers with citations",
    "PathRAG graph reasoning",
    "Hybrid retrieval & reranking",
    "Agent trace & observability",
    "Local-first provider routing",
  ]) {
    has(f, c, `features missing card: ${c}`);
  }
});

t("how-it-works renders the nine-step pipeline", () => {
  const h = read("components/landing/HowItWorks.tsx");
  for (const step of ["Upload", "Parse", "Chunk", "Index", "Retrieve", "Rerank", "Reason", "Cite", "Observe"]) {
    has(h, step, `pipeline missing step: ${step}`);
  }
});

t("differentiators proof section exists", () => {
  const d = read("components/landing/Differentiators.tsx");
  has(d, "What makes Auralynq different?");
  has(d, "Evidence-gated abstention");
  has(d, "Local-first by default");
});

// ---- chat shell ----------------------------------------------------------
t("chat uses app bar, composer and never-empty inspector overview", () => {
  const page = read("app/chat/page.tsx");
  has(page, "<AppBar");
  has(page, "<Composer");
  has(page, "<InspectorOverview");
  has(page, '"overview"'); // default inspector tab
});

t("composer has mode selector, voice, upload and key hints", () => {
  const c = read("components/chat/Composer.tsx");
  for (const m of ["Ask corpus", "Summarize", "Inventory", "Web"]) has(c, m);
  has(c, "VoiceRecorder");
  has(c, "Upload a document");
  has(c, "Shift");
});

t("voice recorder exposes explicit states", () => {
  const v = read("components/VoiceRecorder.tsx");
  for (const st of ["idle", "listening", "transcribing", "speaking", "failed"]) has(v, st);
});

t("inspector overview is useful before any query", () => {
  const o = read("components/chat/InspectorOverview.tsx");
  for (const s of ["Your corpus", "System status", "Try asking"]) has(o, s);
});

t("inspector overview shows a first-run checklist for an empty corpus", () => {
  const o = read("components/chat/InspectorOverview.tsx");
  has(o, "First run");
  has(o, "!summary.indexed"); // only shown before any document is indexed
  for (const step of [
    "Upload a document",
    "Ask a question",
    "Click a citation",
    "Open the Source Workspace",
    "Try ModelFit",
    "Run a benchmark",
  ]) {
    has(o, step, `first-run checklist missing step: ${step}`);
  }
});

// ---- inspector panels ----------------------------------------------------
t("evidence panel has coverage, breakdown, debug disclosure and empty state", () => {
  const e = read("components/EvidencePaths.tsx");
  has(e, "Evidence coverage");
  has(e, "displaySource");
  has(e, "Debug · raw source");
  has(e, "Ask a question to see the evidence trail");
  has(e, "whySelected");
});

t("trace panel has summary dashboard and Phoenix card", () => {
  const tr = read("components/TracePanel.tsx");
  has(tr, "Hallu. risk");
  has(tr, "Open in Phoenix");
  has(tr, "not connected"); // degraded hint
  has(tr, "Run a query to see the trace");
});

t("eval panel has feedback widget and planned evals", () => {
  const ev = read("components/EvalPanel.tsx");
  has(ev, "Rate the last answer");
  has(ev, "Groundedness");
  has(ev, "Abstention correctness");
});

t("insufficient-evidence card uses soft warning copy", () => {
  const ie = read("components/InsufficientEvidence.tsx");
  has(ie, "Not enough evidence in your indexed documents");
  has(ie, "held back instead of guessing");
  has(ie, "border-warn"); // soft warning, not error red
});

t("corpus inventory renders the inventory fields", () => {
  const ci = read("components/CorpusInventory.tsx");
  for (const field of ["Documents", "Languages", "File types", "Last indexed", "Top topics"]) {
    has(ci, field, `inventory missing field: ${field}`);
  }
});

t("citations clean internal paths via displaySource", () => {
  has(read("components/Citations.tsx"), "displaySource");
  has(read("components/Message.tsx"), "CorpusInventory");
});

// ---- design system -------------------------------------------------------
t("design tokens define all three themes with status + text scale", () => {
  const css = read("app/globals.css");
  for (const th of ['[data-theme="dark"]', '[data-theme="light"]', '[data-theme="comfort"]']) has(css, th);
  for (const tok of ["--c-text-2", "--c-text-3", "--c-ok", "--c-warn", "--c-bad", "--ring"]) has(css, tok);
  has(css, ".focusable");
  has(css, ".evidence-strong");
});

// ---- new: trace rail, algorithm selector, settings -----------------------
t("agent activity rail is always visible in chat inspector", () => {
  const page = read("app/chat/page.tsx");
  has(page, "AgentActivityRail");
  has(page, "traceAlwaysVisible");
  has(page, "agentActivity");
});

t("agent activity rail has all required states", () => {
  const rail = read("components/chat/AgentActivityRail.tsx");
  has(rail, "idle");
  has(rail, "generating");
  has(rail, "abstained");
  has(rail, "corpus_empty");
  has(rail, "system_route");
  has(rail, "TraceMiniTimeline");
  has(rail, "AlgorithmBadge");
  has(rail, "RiskBadge");
  has(rail, "Ready — ask a question to see agent activity");
});

t("algorithm selector renders strategy dropdown near composer", () => {
  const sel = read("components/chat/AlgorithmSelector.tsx");
  has(sel, "RAG Algorithm");
  has(sel, "Available");
  has(sel, "Experimental");
  has(sel, "Planned");
  has(sel, "auralynq_rag");
  has(sel, "fetchRAGStrategies");
  has(sel, "auralynq.rag_strategy.v1");
});

t("composer includes algorithm selector", () => {
  const c = read("components/chat/Composer.tsx");
  has(c, "AlgorithmSelector");
  has(c, "ragStrategy");
  has(c, "onRagStrategyChange");
});

t("settings panel has all configurable options", () => {
  const sp = read("components/chat/SettingsPanel.tsx");
  has(sp, "Font Size");
  has(sp, "Density");
  has(sp, "Inspector Width");
  has(sp, "Trace Visibility");
  has(sp, "New Chat Behavior");
  has(sp, "chat_only");
  has(sp, "clear_corpus");
  has(sp, "--font-scale");
  has(sp, "--inspector-width");
});

t("css defines font-scale and density CSS variables", () => {
  const css = read("app/globals.css");
  has(css, "--font-scale");
  has(css, "--density-scale");
  has(css, "--inspector-width");
  has(css, "--chat-max-width");
});

t("new chat supports clear-corpus mode with confirmation", () => {
  const page = read("app/chat/page.tsx");
  has(page, "clear_corpus");
  has(page, "clearConfirmOpen");
  has(page, "executeClearCorpus");
  has(page, "This will remove all indexed documents");
});

t("app bar shows entities count and settings button", () => {
  const bar = read("components/chat/AppBar.tsx");
  has(bar, "entities");
  has(bar, "onToggleSettings");
  has(bar, "Display settings");
});

t("eval panel shows last query metrics from backend", () => {
  const ev = read("components/EvalPanel.tsx");
  has(ev, "evalLast");
  has(ev, "Last query metrics");
  has(ev, "exportEvalRun");
  has(ev, "Strategy comparison");
});

// ---- visual grounding & source view ----------------------------------------

t("source view tab added to chat inspector", () => {
  const page = read("app/chat/page.tsx");
  has(page, '"source"');
  has(page, "SourceViewPanel");
  has(page, "visualGrounding");
  has(page, "activeCitation");
});

t("source view panel has page viewer, overlay and legend", () => {
  const sv = read("components/SourceViewPanel.tsx");
  has(sv, "PageViewer");
  has(sv, "HighlightBox");
  has(sv, "ClaimGroundingView");
  has(sv, "normalized_bbox");
  has(sv, "reindex_required");
  has(sv, "visual_grounding_available");
  has(sv, "Source View");
  has(sv, "Reindex");
  has(sv, "grounding_stage");
});

t("source view panel handles unavailable grounding gracefully", () => {
  const sv = read("components/SourceViewPanel.tsx");
  has(sv, "Visual grounding not available");
  has(sv, "Ask a question to see source grounding");
});

t("source view panel has zoom controls", () => {
  const sv = read("components/SourceViewPanel.tsx");
  has(sv, "Zoom in");
  has(sv, "Zoom out");
  has(sv, "fit");
});

t("evidence panel has source view open button on citations", () => {
  const ev = read("components/EvidencePaths.tsx");
  has(ev, "onOpenSource");
  has(ev, "onOpenSourceTab");
  has(ev, "Open Source View");
  has(ev, "hasVisualGrounding");
  has(ev, "Visual grounding");
});

t("api has visual grounding types and fetchers", () => {
  const api = read("lib/api.ts");
  has(api, "VisualGrounding");
  has(api, "VisualHighlight");
  has(api, "ClaimGrounding");
  has(api, "fetchDocumentPages");
  has(api, "documentPageImageUrl");
  has(api, "visual_grounding_available");
  has(api, "grounding_stage");
  has(api, "normalized_bbox");
});

t("answer result type includes visual grounding field", () => {
  const api = read("lib/api.ts");
  has(api, "visual_grounding?: VisualGrounding | null");
});

t("source view panel renders highlight boxes with citation colors", () => {
  const sv = read("components/SourceViewPanel.tsx");
  has(sv, "CITATION_COLORS");
  has(sv, "colorFor");
  has(sv, "borderFor");
  has(sv, "color_index");
});

t("source view panel shows claim grounding with support status", () => {
  const sv = read("components/SourceViewPanel.tsx");
  has(sv, "supported");
  has(sv, "partial");
  has(sv, "unsupported");
  has(sv, "SUPPORT_CLS");
  has(sv, "SUPPORT_LABEL");
});

t("chat page clears visual grounding on new query", () => {
  const page = read("app/chat/page.tsx");
  has(page, "setVisualGrounding(null)");
  has(page, "setActiveCitation(null)");
});

// ---- Source Workspace Modal structural tests ----------------------------

t("SourceWorkspaceModal component file exists and exports correctly", () => {
  const sw = read("components/SourceWorkspaceModal.tsx");
  has(sw, "export function SourceWorkspaceModal");
  has(sw, "export interface SourceWorkspaceModalProps");
  has(sw, "grounding: VisualGrounding | null | undefined");
  has(sw, "citations: Citation[]");
  has(sw, "activeCitation: string | null");
  has(sw, "onClose: () => void");
});

t("SourceWorkspaceModal renders three-panel layout with citation and evidence panels", () => {
  const sw = read("components/SourceWorkspaceModal.tsx");
  has(sw, "CitationPanel");
  has(sw, "EvidencePanel");
  has(sw, "WorkspacePageViewer");
  has(sw, "grid-cols-[260px_1fr_280px]");
  has(sw, "Grounded Source Workspace");
});

t("SourceWorkspaceModal has zoom controls with fit-width preset", () => {
  const sw = read("components/SourceWorkspaceModal.tsx");
  has(sw, "ZOOM_PRESETS");
  has(sw, "ZOOM_LABELS");
  has(sw, "fit");
  has(sw, "setZoom");
});

t("SourceWorkspaceModal has page navigation controls", () => {
  const sw = read("components/SourceWorkspaceModal.tsx");
  has(sw, "navigatePage");
  has(sw, "hasPrev");
  has(sw, "hasNext");
  has(sw, "currentPageKey");
  has(sw, "ArrowRight");
});

t("SourceWorkspaceModal supports full-screen mode toggle", () => {
  const sw = read("components/SourceWorkspaceModal.tsx");
  has(sw, "fullscreen");
  has(sw, "setFullscreen");
  has(sw, "Show side panels");
  has(sw, "Full-screen PDF view");
});

t("SourceWorkspaceModal handles unavailable grounding gracefully", () => {
  const sw = read("components/SourceWorkspaceModal.tsx");
  has(sw, "visual_grounding_available");
  has(sw, "Visual grounding not available");
  has(sw, "Reindex");
});

t("SourceWorkspaceModal navigates to active citation page on open", () => {
  const sw = read("components/SourceWorkspaceModal.tsx");
  has(sw, "activeCitation");
  has(sw, "initialPageKey");
  has(sw, "setCurrentPageKey");
});

t("SourceWorkspaceModal closes on Escape key", () => {
  const sw = read("components/SourceWorkspaceModal.tsx");
  has(sw, "Escape");
  has(sw, "onClose");
  has(sw, "window.addEventListener");
});

t("WorkspacePageViewer places overlays with normalized bbox percentages", () => {
  const sw = read("components/SourceWorkspaceModal.tsx");
  has(sw, "normalized_bbox");
  has(sw, "position: \"absolute\"");
  has(sw, ".toFixed(3)}%");
});

t("chat page opens workspace modal when View source is clicked", () => {
  const page = read("app/chat/page.tsx");
  has(page, "showWorkspace");
  has(page, "setShowWorkspace");
  has(page, "SourceWorkspaceModal");
  has(page, "onOpenWorkspace");
});

t("chat page resets workspace state on new query", () => {
  const page = read("app/chat/page.tsx");
  has(page, "setShowWorkspace(false)");
});

t("InlineSourceStrip has two buttons: preview and view source workspace", () => {
  const page = read("app/chat/page.tsx");
  has(page, "onOpenWorkspace");
  has(page, "View source ↗");
  has(page, "Preview");
});

t("SourceViewPanel has Expand button to open workspace", () => {
  const sv = read("components/SourceViewPanel.tsx");
  has(sv, "onOpenWorkspace");
  has(sv, "Expand");
  has(sv, "Open full Source Workspace");
});

t("AlgorithmSelector groups strategies by status", () => {
  const sel = read("components/chat/AlgorithmSelector.tsx");
  has(sel, "GROUP_CONFIG");
  has(sel, "Available now");
  has(sel, "Experimental");
  has(sel, "Planned / requires setup");
  has(sel, "StatusGroup");
});

t("AlgorithmSelector planned strategies are non-selectable", () => {
  const sel = read("components/chat/AlgorithmSelector.tsx");
  has(sel, "selectable: false");
  has(sel, "cursor-not-allowed");
  has(sel, "disabled={!selectable}");
});

t("api.ts exports PageLayoutBlock and PageLayoutResponse types", () => {
  const api = read("lib/api.ts");
  has(api, "export interface PageLayoutBlock");
  has(api, "export interface PageLayoutResponse");
  has(api, "export async function fetchPageLayout");
});

t("api.ts exports fetchPageLayout with correct URL pattern", () => {
  const api = read("lib/api.ts");
  has(api, "/pages/${page}/layout");
  has(api, "encodeURIComponent(docId)");
});

console.log(`\n${passed} ui structure tests passed`);