spec-gen-cli 1.1.0 → 1.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (205) hide show
  1. package/README.md +163 -77
  2. package/dist/api/analyze.d.ts.map +1 -1
  3. package/dist/api/analyze.js +56 -27
  4. package/dist/api/analyze.js.map +1 -1
  5. package/dist/api/drift.d.ts.map +1 -1
  6. package/dist/api/drift.js +26 -20
  7. package/dist/api/drift.js.map +1 -1
  8. package/dist/api/generate.d.ts.map +1 -1
  9. package/dist/api/generate.js +19 -43
  10. package/dist/api/generate.js.map +1 -1
  11. package/dist/api/init.d.ts.map +1 -1
  12. package/dist/api/init.js +6 -5
  13. package/dist/api/init.js.map +1 -1
  14. package/dist/api/run.d.ts.map +1 -1
  15. package/dist/api/run.js +67 -51
  16. package/dist/api/run.js.map +1 -1
  17. package/dist/api/specs.d.ts.map +1 -1
  18. package/dist/api/specs.js +5 -4
  19. package/dist/api/specs.js.map +1 -1
  20. package/dist/api/types.d.ts +7 -1
  21. package/dist/api/types.d.ts.map +1 -1
  22. package/dist/api/verify.d.ts.map +1 -1
  23. package/dist/api/verify.js +31 -32
  24. package/dist/api/verify.js.map +1 -1
  25. package/dist/cli/commands/analyze.d.ts.map +1 -1
  26. package/dist/cli/commands/analyze.js +41 -62
  27. package/dist/cli/commands/analyze.js.map +1 -1
  28. package/dist/cli/commands/doctor.d.ts +9 -0
  29. package/dist/cli/commands/doctor.d.ts.map +1 -0
  30. package/dist/cli/commands/doctor.js +273 -0
  31. package/dist/cli/commands/doctor.js.map +1 -0
  32. package/dist/cli/commands/drift.d.ts.map +1 -1
  33. package/dist/cli/commands/drift.js +18 -32
  34. package/dist/cli/commands/drift.js.map +1 -1
  35. package/dist/cli/commands/generate.d.ts.map +1 -1
  36. package/dist/cli/commands/generate.js +40 -101
  37. package/dist/cli/commands/generate.js.map +1 -1
  38. package/dist/cli/commands/init.d.ts.map +1 -1
  39. package/dist/cli/commands/init.js +17 -15
  40. package/dist/cli/commands/init.js.map +1 -1
  41. package/dist/cli/commands/mcp.d.ts.map +1 -1
  42. package/dist/cli/commands/mcp.js +2 -1
  43. package/dist/cli/commands/mcp.js.map +1 -1
  44. package/dist/cli/commands/run.d.ts +0 -15
  45. package/dist/cli/commands/run.d.ts.map +1 -1
  46. package/dist/cli/commands/run.js +61 -111
  47. package/dist/cli/commands/run.js.map +1 -1
  48. package/dist/cli/commands/verify.d.ts.map +1 -1
  49. package/dist/cli/commands/verify.js +18 -52
  50. package/dist/cli/commands/verify.js.map +1 -1
  51. package/dist/cli/commands/view.d.ts +4 -0
  52. package/dist/cli/commands/view.d.ts.map +1 -1
  53. package/dist/cli/commands/view.js +29 -24
  54. package/dist/cli/commands/view.js.map +1 -1
  55. package/dist/cli/index.js +22 -4
  56. package/dist/cli/index.js.map +1 -1
  57. package/dist/constants.d.ts +254 -0
  58. package/dist/constants.d.ts.map +1 -0
  59. package/dist/constants.js +320 -0
  60. package/dist/constants.js.map +1 -0
  61. package/dist/core/analyzer/artifact-generator.d.ts +8 -0
  62. package/dist/core/analyzer/artifact-generator.d.ts.map +1 -1
  63. package/dist/core/analyzer/artifact-generator.js +59 -11
  64. package/dist/core/analyzer/artifact-generator.js.map +1 -1
  65. package/dist/core/analyzer/call-graph.d.ts.map +1 -1
  66. package/dist/core/analyzer/call-graph.js +135 -1
  67. package/dist/core/analyzer/call-graph.js.map +1 -1
  68. package/dist/core/analyzer/dependency-graph.d.ts +32 -0
  69. package/dist/core/analyzer/dependency-graph.d.ts.map +1 -1
  70. package/dist/core/analyzer/dependency-graph.js +104 -10
  71. package/dist/core/analyzer/dependency-graph.js.map +1 -1
  72. package/dist/core/analyzer/duplicate-detector.d.ts.map +1 -1
  73. package/dist/core/analyzer/duplicate-detector.js +4 -0
  74. package/dist/core/analyzer/duplicate-detector.js.map +1 -1
  75. package/dist/core/analyzer/embedding-service.d.ts +6 -0
  76. package/dist/core/analyzer/embedding-service.d.ts.map +1 -1
  77. package/dist/core/analyzer/embedding-service.js +15 -1
  78. package/dist/core/analyzer/embedding-service.js.map +1 -1
  79. package/dist/core/analyzer/file-walker.d.ts.map +1 -1
  80. package/dist/core/analyzer/file-walker.js +4 -3
  81. package/dist/core/analyzer/file-walker.js.map +1 -1
  82. package/dist/core/analyzer/http-route-parser.d.ts +111 -0
  83. package/dist/core/analyzer/http-route-parser.d.ts.map +1 -0
  84. package/dist/core/analyzer/http-route-parser.js +466 -0
  85. package/dist/core/analyzer/http-route-parser.js.map +1 -0
  86. package/dist/core/analyzer/import-parser.d.ts.map +1 -1
  87. package/dist/core/analyzer/import-parser.js +19 -5
  88. package/dist/core/analyzer/import-parser.js.map +1 -1
  89. package/dist/core/analyzer/refactor-analyzer.d.ts.map +1 -1
  90. package/dist/core/analyzer/refactor-analyzer.js +8 -7
  91. package/dist/core/analyzer/refactor-analyzer.js.map +1 -1
  92. package/dist/core/analyzer/repository-mapper.d.ts.map +1 -1
  93. package/dist/core/analyzer/repository-mapper.js +12 -13
  94. package/dist/core/analyzer/repository-mapper.js.map +1 -1
  95. package/dist/core/analyzer/signature-extractor.d.ts +1 -1
  96. package/dist/core/analyzer/signature-extractor.d.ts.map +1 -1
  97. package/dist/core/analyzer/signature-extractor.js +69 -1
  98. package/dist/core/analyzer/signature-extractor.js.map +1 -1
  99. package/dist/core/analyzer/spec-vector-index.d.ts.map +1 -1
  100. package/dist/core/analyzer/spec-vector-index.js +4 -3
  101. package/dist/core/analyzer/spec-vector-index.js.map +1 -1
  102. package/dist/core/analyzer/vector-index.d.ts.map +1 -1
  103. package/dist/core/analyzer/vector-index.js +29 -1
  104. package/dist/core/analyzer/vector-index.js.map +1 -1
  105. package/dist/core/drift/drift-detector.d.ts.map +1 -1
  106. package/dist/core/drift/drift-detector.js +7 -6
  107. package/dist/core/drift/drift-detector.js.map +1 -1
  108. package/dist/core/drift/git-diff.d.ts.map +1 -1
  109. package/dist/core/drift/git-diff.js +28 -16
  110. package/dist/core/drift/git-diff.js.map +1 -1
  111. package/dist/core/generator/mapping-generator.d.ts.map +1 -1
  112. package/dist/core/generator/mapping-generator.js +11 -10
  113. package/dist/core/generator/mapping-generator.js.map +1 -1
  114. package/dist/core/generator/openspec-compat.d.ts.map +1 -1
  115. package/dist/core/generator/openspec-compat.js +3 -2
  116. package/dist/core/generator/openspec-compat.js.map +1 -1
  117. package/dist/core/generator/openspec-format-generator.js.map +1 -1
  118. package/dist/core/generator/openspec-writer.d.ts +0 -4
  119. package/dist/core/generator/openspec-writer.d.ts.map +1 -1
  120. package/dist/core/generator/openspec-writer.js +30 -41
  121. package/dist/core/generator/openspec-writer.js.map +1 -1
  122. package/dist/core/generator/spec-pipeline.d.ts.map +1 -1
  123. package/dist/core/generator/spec-pipeline.js +4 -4
  124. package/dist/core/generator/spec-pipeline.js.map +1 -1
  125. package/dist/core/generator/stages/stage1-survey.d.ts.map +1 -1
  126. package/dist/core/generator/stages/stage1-survey.js +5 -3
  127. package/dist/core/generator/stages/stage1-survey.js.map +1 -1
  128. package/dist/core/generator/stages/stage2-entities.d.ts.map +1 -1
  129. package/dist/core/generator/stages/stage2-entities.js +10 -9
  130. package/dist/core/generator/stages/stage2-entities.js.map +1 -1
  131. package/dist/core/generator/stages/stage3-services.d.ts.map +1 -1
  132. package/dist/core/generator/stages/stage3-services.js +9 -8
  133. package/dist/core/generator/stages/stage3-services.js.map +1 -1
  134. package/dist/core/generator/stages/stage4-api.d.ts.map +1 -1
  135. package/dist/core/generator/stages/stage4-api.js +10 -9
  136. package/dist/core/generator/stages/stage4-api.js.map +1 -1
  137. package/dist/core/generator/stages/stage5-architecture.d.ts.map +1 -1
  138. package/dist/core/generator/stages/stage5-architecture.js +5 -4
  139. package/dist/core/generator/stages/stage5-architecture.js.map +1 -1
  140. package/dist/core/generator/stages/stage6-adr.d.ts.map +1 -1
  141. package/dist/core/generator/stages/stage6-adr.js +7 -2
  142. package/dist/core/generator/stages/stage6-adr.js.map +1 -1
  143. package/dist/core/services/chat-agent.d.ts.map +1 -1
  144. package/dist/core/services/chat-agent.js +46 -15
  145. package/dist/core/services/chat-agent.js.map +1 -1
  146. package/dist/core/services/config-manager.d.ts.map +1 -1
  147. package/dist/core/services/config-manager.js +32 -26
  148. package/dist/core/services/config-manager.js.map +1 -1
  149. package/dist/core/services/gitignore-manager.d.ts.map +1 -1
  150. package/dist/core/services/gitignore-manager.js +2 -13
  151. package/dist/core/services/gitignore-manager.js.map +1 -1
  152. package/dist/core/services/llm-service.d.ts.map +1 -1
  153. package/dist/core/services/llm-service.js +33 -35
  154. package/dist/core/services/llm-service.js.map +1 -1
  155. package/dist/core/services/mcp-handlers/analysis.d.ts.map +1 -1
  156. package/dist/core/services/mcp-handlers/analysis.js +23 -14
  157. package/dist/core/services/mcp-handlers/analysis.js.map +1 -1
  158. package/dist/core/services/mcp-handlers/graph.d.ts.map +1 -1
  159. package/dist/core/services/mcp-handlers/graph.js +24 -23
  160. package/dist/core/services/mcp-handlers/graph.js.map +1 -1
  161. package/dist/core/services/mcp-handlers/semantic.d.ts +1 -1
  162. package/dist/core/services/mcp-handlers/semantic.d.ts.map +1 -1
  163. package/dist/core/services/mcp-handlers/semantic.js +17 -16
  164. package/dist/core/services/mcp-handlers/semantic.js.map +1 -1
  165. package/dist/core/services/mcp-handlers/utils.d.ts.map +1 -1
  166. package/dist/core/services/mcp-handlers/utils.js +4 -3
  167. package/dist/core/services/mcp-handlers/utils.js.map +1 -1
  168. package/dist/core/services/project-detector.d.ts.map +1 -1
  169. package/dist/core/services/project-detector.js +2 -13
  170. package/dist/core/services/project-detector.js.map +1 -1
  171. package/dist/core/verifier/verification-engine.d.ts +9 -3
  172. package/dist/core/verifier/verification-engine.d.ts.map +1 -1
  173. package/dist/core/verifier/verification-engine.js +25 -13
  174. package/dist/core/verifier/verification-engine.js.map +1 -1
  175. package/dist/types/index.d.ts +2 -0
  176. package/dist/types/index.d.ts.map +1 -1
  177. package/dist/utils/command-helpers.d.ts +38 -0
  178. package/dist/utils/command-helpers.d.ts.map +1 -0
  179. package/dist/utils/command-helpers.js +82 -0
  180. package/dist/utils/command-helpers.js.map +1 -0
  181. package/dist/utils/errors.d.ts.map +1 -1
  182. package/dist/utils/errors.js +4 -3
  183. package/dist/utils/errors.js.map +1 -1
  184. package/dist/utils/logger.d.ts.map +1 -1
  185. package/dist/utils/logger.js +14 -3
  186. package/dist/utils/logger.js.map +1 -1
  187. package/dist/utils/progress.d.ts +1 -1
  188. package/dist/utils/progress.d.ts.map +1 -1
  189. package/dist/utils/progress.js +15 -12
  190. package/dist/utils/progress.js.map +1 -1
  191. package/dist/utils/shutdown.d.ts.map +1 -1
  192. package/dist/utils/shutdown.js +4 -3
  193. package/dist/utils/shutdown.js.map +1 -1
  194. package/package.json +9 -5
  195. package/src/viewer/InteractiveGraphViewer.jsx +182 -139
  196. package/src/viewer/components/ArchitectureView.jsx +19 -19
  197. package/src/viewer/components/ChatPanel.jsx +40 -40
  198. package/src/viewer/components/ClusterGraph.jsx +34 -22
  199. package/src/viewer/components/FilterBar.jsx +26 -26
  200. package/src/viewer/components/FlatGraph.jsx +22 -15
  201. package/src/viewer/components/MicroComponents.jsx +14 -12
  202. package/src/viewer/utils/constants.js +17 -0
  203. package/src/viewer/utils/graph-helpers.js +7 -3
  204. package/src/viewer/utils/graph-helpers.test.ts +39 -0
  205. package/src/viewer/utils/themes.js +170 -0
@@ -1,5 +1,5 @@
1
1
  import { useState, useCallback, useRef, useEffect, useMemo } from 'react';
2
- import { extColor } from './utils/constants.js';
2
+ import { extColor, CLUSTER_PALETTE, CLUSTER_PALETTE_LIGHT } from './utils/constants.js';
3
3
  import {
4
4
  parseSpecRequirements,
5
5
  buildMappingIndex,
@@ -14,9 +14,10 @@ import { FilterBar } from './components/FilterBar.jsx';
14
14
  import { ArchitectureView } from './components/ArchitectureView.jsx';
15
15
  import { Hint, SL, Row, Chip, KindBadge } from './components/MicroComponents.jsx';
16
16
  import { ChatPanel } from './components/ChatPanel.jsx';
17
+ import { THEMES, THEME_KEYS, DEFAULT_THEME } from './utils/themes.js';
17
18
 
18
19
  export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '/api/spec' }) {
19
- const [graph, setGraph] = useState(null);
20
+ const [rawGraph, setRawGraph] = useState(null);
20
21
  const [llmCtx, setLlmCtx] = useState(null);
21
22
  const [refReport, setRefReport] = useState(null);
22
23
  const [mapping, setMapping] = useState(null);
@@ -42,6 +43,24 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
42
43
  });
43
44
  const [loaded, setLoaded] = useState(false);
44
45
  const [chatOpen, setChatOpen] = useState(false);
46
+ const [themeName, setThemeName] = useState(
47
+ () => localStorage.getItem('spec-gen-theme') || DEFAULT_THEME
48
+ );
49
+ const theme = THEMES[themeName] ?? THEMES[DEFAULT_THEME];
50
+ const clusterPalette = themeName === 'light' ? CLUSTER_PALETTE_LIGHT : CLUSTER_PALETTE;
51
+
52
+ // Derive graph from raw data — recomputes automatically when theme, refReport or raw data changes.
53
+ const graph = useMemo(() => {
54
+ if (!rawGraph) return null;
55
+ const g = parseGraph(rawGraph, clusterPalette);
56
+ return refReport ? enrichGraphWithRefactors(g, refReport) : g;
57
+ }, [rawGraph, clusterPalette, refReport]);
58
+ const cycleTheme = () => setThemeName((prev) => {
59
+ const idx = THEME_KEYS.indexOf(prev);
60
+ const next = THEME_KEYS[(idx + 1) % THEME_KEYS.length];
61
+ localStorage.setItem('spec-gen-theme', next);
62
+ return next;
63
+ });
45
64
  const fileRef = useRef();
46
65
  const hasAutoLoadedRef = useRef(false);
47
66
 
@@ -52,8 +71,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
52
71
  const loadGraph = useCallback(
53
72
  (jsonStr) => {
54
73
  try {
55
- const g = parseGraph(JSON.parse(jsonStr));
56
- setGraph(refReport ? enrichGraphWithRefactors(g, refReport) : g);
74
+ setRawGraph(JSON.parse(jsonStr));
57
75
  setSelectedId(null);
58
76
  setAffectedIds([]);
59
77
  setFocusedIds([]);
@@ -70,7 +88,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
70
88
  alert('Invalid JSON: ' + e.message);
71
89
  }
72
90
  },
73
- [refReport]
91
+ []
74
92
  );
75
93
 
76
94
  const loadMapping = useCallback((jsonStr) => {
@@ -109,7 +127,6 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
109
127
  if (refRes.ok) {
110
128
  const report = await refRes.json();
111
129
  setRefReport(report);
112
- setGraph((g) => (g ? enrichGraphWithRefactors(g, report) : g));
113
130
  }
114
131
  } catch { /* ignore */ }
115
132
 
@@ -283,14 +300,14 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
283
300
  });
284
301
  }
285
302
 
286
- // Select the first highlighted node to show details and prominent highlight
287
- if (validNodeIds.length > 0) {
288
- const id = validNodeIds[0];
289
- setSelectedId(id);
290
- setAffectedIds(computeBlast(visibleEdges, id));
303
+ // If exactly one node matched, auto-select it for details but skip blast radius
304
+ // to avoid lighting up unrelated edges through the full reachability set.
305
+ if (validNodeIds.length === 1) {
306
+ setSelectedId(validNodeIds[0]);
307
+ setAffectedIds([]);
291
308
  setTab(mapping ? 'spec' : 'node');
292
309
  }
293
- }, [focusedIds, graph, visibleEdges, mapping, computeBlast]);
310
+ }, [focusedIds, graph, mapping]);
294
311
 
295
312
  const selectedNode = graph?.nodes.find((n) => n.id === selectedId);
296
313
 
@@ -320,32 +337,38 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
320
337
  }, [selectedId, affectedIds, visibleEdges]);
321
338
 
322
339
  const stats = graph?.statistics || {};
323
- const clusterNames = graph?.clusters.map((c) => c.name) || [];
340
+ // Use structuralClusters (clusters with real internal edges) for all UI.
341
+ // Compute from clusters if not present in the JSON (for backward compatibility).
342
+ const structuralClusters = graph?.structuralClusters ??
343
+ (graph?.clusters?.filter(c => c.internalEdges > 0) ?? []);
344
+ const displayClusters = structuralClusters;
345
+ const clusterNames = displayClusters.map((c) => c.name);
324
346
 
325
347
  // ── Upload screen ─────────────────────────────────────────────────────────
326
348
  if (!graph)
327
349
  return (
328
350
  <div
329
351
  style={{
352
+ ...theme.vars,
330
353
  width: '100%',
331
354
  height: '100vh',
332
- background: '#07091a',
355
+ background: 'var(--bg-base)',
333
356
  display: 'flex',
334
357
  flexDirection: 'column',
335
358
  alignItems: 'center',
336
359
  justifyContent: 'center',
337
360
  fontFamily: "'JetBrains Mono',monospace",
338
- color: '#c8cde8',
361
+ color: 'var(--tx-primary)',
339
362
  opacity: loaded ? 1 : 0,
340
363
  transition: 'opacity 0.3s',
341
364
  }}
342
365
  >
343
- <div style={{ fontSize: 10, letterSpacing: '0.18em', color: '#2a2f4a', marginBottom: 28 }}>
366
+ <div style={{ fontSize: 10, letterSpacing: '0.18em', color: 'var(--tx-faint)', marginBottom: 28 }}>
344
367
  INTERACTIVE GRAPH VIEWER
345
368
  </div>
346
369
  <div
347
370
  style={{
348
- border: '1px dashed #252a45',
371
+ border: '1px dashed var(--ac-edge-type)',
349
372
  borderRadius: 12,
350
373
  padding: '44px 64px',
351
374
  textAlign: 'center',
@@ -363,11 +386,11 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
363
386
  }
364
387
  }}
365
388
  >
366
- <div style={{ fontSize: 32, marginBottom: 14, color: '#7c6af7' }}>⬡</div>
367
- <div style={{ fontSize: 12, color: '#8890b0', marginBottom: 6 }}>
368
- Drop a <code style={{ color: '#7c6af7' }}>dependency-graph.json</code>
389
+ <div style={{ fontSize: 32, marginBottom: 14, color: 'var(--ac-primary)' }}>⬡</div>
390
+ <div style={{ fontSize: 12, color: 'var(--tx-secondary)', marginBottom: 6 }}>
391
+ Drop a <code style={{ color: 'var(--ac-primary)' }}>dependency-graph.json</code>
369
392
  </div>
370
- <div style={{ fontSize: 10, color: '#3a3f5c' }}>or click to browse</div>
393
+ <div style={{ fontSize: 10, color: 'var(--tx-ghost)' }}>or click to browse</div>
371
394
  </div>
372
395
  <input
373
396
  ref={fileRef}
@@ -411,11 +434,12 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
411
434
  return (
412
435
  <div
413
436
  style={{
437
+ ...theme.vars,
414
438
  width: '100%',
415
439
  height: '100vh',
416
- background: '#07091a',
440
+ background: 'var(--bg-base)',
417
441
  fontFamily: "'JetBrains Mono',monospace",
418
- color: '#c8cde8',
442
+ color: 'var(--tx-primary)',
419
443
  display: 'flex',
420
444
  flexDirection: 'column',
421
445
  opacity: loaded ? 1 : 0,
@@ -429,8 +453,8 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
429
453
  alignItems: 'center',
430
454
  gap: 10,
431
455
  padding: '8px 18px',
432
- borderBottom: '1px solid #0f1224',
433
- background: '#080a1c',
456
+ borderBottom: '1px solid var(--bd-faint)',
457
+ background: 'var(--bg-panel)',
434
458
  flexShrink: 0,
435
459
  }}
436
460
  >
@@ -440,12 +464,12 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
440
464
  width: 6,
441
465
  height: 6,
442
466
  borderRadius: '50%',
443
- background: '#7c6af7',
444
- boxShadow: '0 0 8px #7c6af7',
467
+ background: 'var(--ac-primary)',
468
+ boxShadow: '0 0 8px var(--ac-primary)',
445
469
  }}
446
470
  />
447
471
  <span
448
- style={{ fontSize: 10, fontWeight: 700, color: '#e0e4f0', letterSpacing: '0.09em' }}
472
+ style={{ fontSize: 10, fontWeight: 700, color: 'var(--tx-bright)', letterSpacing: '0.09em' }}
449
473
  >
450
474
  GRAPH VIEWER
451
475
  </span>
@@ -453,20 +477,20 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
453
477
  {[
454
478
  ['nodes', stats.nodeCount],
455
479
  ['edges', stats.edgeCount],
456
- ['clusters', stats.clusterCount],
480
+ ['clusters', stats.structuralClusterCount ?? displayClusters.length],
457
481
  ].map(([l, v]) => (
458
482
  <div
459
483
  key={l}
460
484
  style={{
461
485
  fontSize: 9,
462
- color: '#3a4060',
463
- background: '#0e1028',
486
+ color: 'var(--tx-dim)',
487
+ background: 'var(--bg-raised)',
464
488
  borderRadius: 4,
465
489
  padding: '2px 7px',
466
- border: '1px solid #141830',
490
+ border: '1px solid var(--bd-muted)',
467
491
  }}
468
492
  >
469
- <span style={{ color: '#6a70a0' }}>{v}</span> {l}
493
+ <span style={{ color: 'var(--tx-muted)' }}>{v}</span> {l}
470
494
  </div>
471
495
  ))}
472
496
  <div style={{ display: 'flex', gap: 2, marginLeft: 8 }}>
@@ -485,10 +509,10 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
485
509
  style={{
486
510
  padding: '3px 10px',
487
511
  fontSize: 9,
488
- background: viewMode === v ? '#181b38' : 'transparent',
489
- border: `1px solid ${viewMode === v ? '#7c6af7' : '#141830'}`,
512
+ background: viewMode === v ? 'var(--bg-select)' : 'transparent',
513
+ border: `1px solid ${viewMode === v ? 'var(--ac-primary)' : 'var(--bd-muted)'}`,
490
514
  borderRadius: 4,
491
- color: viewMode === v ? '#c8cde8' : '#3a3f5c',
515
+ color: viewMode === v ? 'var(--tx-primary)' : 'var(--tx-ghost)',
492
516
  cursor: 'pointer',
493
517
  fontFamily: 'inherit',
494
518
  }}
@@ -503,9 +527,9 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
503
527
  onChange={(e) => handleSearch(e.target.value)}
504
528
  placeholder="search name, path, export, tag..."
505
529
  style={{
506
- background: '#0c0e22',
507
- border: '1px solid #141830',
508
- color: '#c8cde8',
530
+ background: 'var(--bg-input)',
531
+ border: '1px solid var(--bd-muted)',
532
+ color: 'var(--tx-primary)',
509
533
  padding: '5px 12px 5px 26px',
510
534
  borderRadius: 5,
511
535
  fontSize: 9,
@@ -521,7 +545,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
521
545
  top: '50%',
522
546
  transform: 'translateY(-50%)',
523
547
  fontSize: 11,
524
- color: '#3a3f5c',
548
+ color: 'var(--tx-ghost)',
525
549
  }}
526
550
  >
527
551
 
@@ -535,7 +559,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
535
559
  top: '50%',
536
560
  transform: 'translateY(-50%)',
537
561
  fontSize: 10,
538
- color: '#3a3f5c',
562
+ color: 'var(--tx-ghost)',
539
563
  cursor: 'pointer',
540
564
  lineHeight: 1,
541
565
  }}
@@ -551,7 +575,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
551
575
  top: '50%',
552
576
  transform: 'translateY(-50%)',
553
577
  fontSize: 9,
554
- color: '#7c6af7',
578
+ color: 'var(--ac-primary)',
555
579
  }}
556
580
  >
557
581
  {focusedIds.length}
@@ -565,16 +589,16 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
565
589
  right: 0,
566
590
  marginTop: 4,
567
591
  width: 280,
568
- background: '#0d0f22',
569
- border: '1px solid #1a1f38',
592
+ background: 'var(--bg-input)',
593
+ border: '1px solid var(--bd-muted)',
570
594
  borderRadius: 5,
571
595
  zIndex: 100,
572
596
  boxShadow: '0 4px 16px rgba(0,0,0,0.5)',
573
597
  overflow: 'hidden',
574
598
  }}
575
599
  >
576
- <div style={{ padding: '4px 8px', borderBottom: '1px solid #1a1f38', fontSize: 8, color: '#3a3f5c', fontFamily: 'inherit' }}>
577
- semantic matches
600
+ <div style={{ padding: '4px 8px', borderBottom: '1px solid var(--bd-muted)', fontSize: 8, color: 'var(--tx-ghost)', fontFamily: 'inherit' }}>
601
+ semantic matches
578
602
  </div>
579
603
  {semanticResults.map((r) => {
580
604
  const node = graph?.nodes.find((n) => n.path === r.filePath || n.path.endsWith(r.filePath) || r.filePath.endsWith(n.path));
@@ -585,20 +609,20 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
585
609
  style={{
586
610
  padding: '5px 8px',
587
611
  cursor: node ? 'pointer' : 'default',
588
- borderBottom: '1px solid #111428',
612
+ borderBottom: '1px solid var(--bd-faint)',
589
613
  display: 'flex',
590
614
  flexDirection: 'column',
591
615
  gap: 2,
592
616
  opacity: node ? 1 : 0.4,
593
617
  }}
594
- onMouseEnter={(e) => { if (node) e.currentTarget.style.background = '#131630'; }}
618
+ onMouseEnter={(e) => { if (node) e.currentTarget.style.background = 'var(--bg-hover)'; }}
595
619
  onMouseLeave={(e) => { e.currentTarget.style.background = 'transparent'; }}
596
620
  >
597
621
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center' }}>
598
- <span style={{ fontSize: 9, color: '#c8cde8', fontFamily: "'JetBrains Mono',monospace" }}>{r.name}</span>
599
- <span style={{ fontSize: 8, color: '#4a3f7a', fontFamily: 'inherit' }}>{(1 - r.score).toFixed(2)}</span>
622
+ <span style={{ fontSize: 9, color: 'var(--tx-primary)', fontFamily: "'JetBrains Mono',monospace" }}>{r.name}</span>
623
+ <span style={{ fontSize: 8, color: 'var(--tx-dim)', fontFamily: 'inherit' }}>{(1 - r.score).toFixed(2)}</span>
600
624
  </div>
601
- <span style={{ fontSize: 8, color: '#3a3f5c', fontFamily: 'inherit', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
625
+ <span style={{ fontSize: 8, color: 'var(--tx-ghost)', fontFamily: 'inherit', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
602
626
  {r.filePath.split('/').slice(-2).join('/')}
603
627
  </span>
604
628
  </div>
@@ -609,14 +633,14 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
609
633
  </div>
610
634
  <button
611
635
  onClick={() => {
612
- setGraph(null);
636
+ setRawGraph(null);
613
637
  setSelectedId(null);
614
638
  }}
615
639
  style={{
616
640
  background: 'none',
617
- border: '1px solid #1a1f38',
641
+ border: '1px solid var(--bd-muted)',
618
642
  borderRadius: 4,
619
- color: '#3a3f5c',
643
+ color: 'var(--tx-ghost)',
620
644
  fontSize: 8,
621
645
  padding: '3px 8px',
622
646
  cursor: 'pointer',
@@ -629,10 +653,10 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
629
653
  <button
630
654
  onClick={() => mappingRef.current.click()}
631
655
  style={{
632
- background: mapping ? '#0a1a0a' : 'none',
633
- border: `1px solid ${mapping ? '#4ade80' : '#1a1f38'}`,
656
+ background: mapping ? 'var(--bg-select)' : 'none',
657
+ border: `1px solid ${mapping ? 'var(--ac-teal)' : 'var(--bd-muted)'}`,
634
658
  borderRadius: 4,
635
- color: mapping ? '#4ade80' : '#3a3f5c',
659
+ color: mapping ? 'var(--ac-teal)' : 'var(--tx-ghost)',
636
660
  fontSize: 8,
637
661
  padding: '3px 8px',
638
662
  cursor: 'pointer',
@@ -646,10 +670,10 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
646
670
  <button
647
671
  onClick={() => specRef.current.click()}
648
672
  style={{
649
- background: Object.keys(specReqs).length ? '#0a0a1a' : 'none',
650
- border: `1px solid ${Object.keys(specReqs).length ? '#7c6af7' : '#1a1f38'}`,
673
+ background: Object.keys(specReqs).length ? 'var(--bg-select)' : 'none',
674
+ border: `1px solid ${Object.keys(specReqs).length ? 'var(--ac-primary)' : 'var(--bd-muted)'}`,
651
675
  borderRadius: 4,
652
- color: Object.keys(specReqs).length ? '#7c6af7' : '#3a3f5c',
676
+ color: Object.keys(specReqs).length ? 'var(--ac-primary)' : 'var(--tx-ghost)',
653
677
  fontSize: 8,
654
678
  padding: '3px 8px',
655
679
  cursor: 'pointer',
@@ -663,10 +687,10 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
663
687
  <button
664
688
  onClick={() => setChatOpen((v) => !v)}
665
689
  style={{
666
- background: chatOpen ? '#1a1050' : 'none',
667
- border: `1px solid ${chatOpen ? '#7c6af7' : '#1a1f38'}`,
690
+ background: chatOpen ? 'var(--bg-select)' : 'none',
691
+ border: `1px solid ${chatOpen ? 'var(--ac-primary)' : 'var(--bd-muted)'}`,
668
692
  borderRadius: 4,
669
- color: chatOpen ? '#7c6af7' : '#3a3f5c',
693
+ color: chatOpen ? 'var(--ac-primary)' : 'var(--tx-ghost)',
670
694
  fontSize: 8,
671
695
  padding: '3px 8px',
672
696
  cursor: 'pointer',
@@ -677,6 +701,23 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
677
701
  >
678
702
  CHAT
679
703
  </button>
704
+ <button
705
+ onClick={cycleTheme}
706
+ title="Cycle theme"
707
+ style={{
708
+ background: 'none',
709
+ border: '1px solid var(--bd-muted)',
710
+ borderRadius: 4,
711
+ color: 'var(--ac-primary)',
712
+ fontSize: 8,
713
+ padding: '3px 8px',
714
+ cursor: 'pointer',
715
+ fontFamily: 'inherit',
716
+ letterSpacing: '0.06em',
717
+ }}
718
+ >
719
+ {theme.label}
720
+ </button>
680
721
  <input
681
722
  ref={mappingRef}
682
723
  type="file"
@@ -723,7 +764,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
723
764
  <ArchitectureView graph={graph} llmCtx={llmCtx} focusedIds={focusedIds} />
724
765
  ) : viewMode === 'clusters' ? (
725
766
  <ClusterGraph
726
- clusters={graph.clusters.filter(
767
+ clusters={displayClusters.filter(
727
768
  (cl) => !filters.cluster || cl.name === filters.cluster
728
769
  )}
729
770
  edges={visibleEdges}
@@ -740,6 +781,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
740
781
  affectedIds={affectedIds}
741
782
  linkedIds={linkedIds}
742
783
  focusedIds={focusedIds}
784
+ noGlow={themeName === 'light' || themeName === 'warm'}
743
785
  />
744
786
  ) : (
745
787
  <FlatGraph
@@ -751,6 +793,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
751
793
  onSelect={handleSelect}
752
794
  refactorOnly={filters.refactorOnly}
753
795
  linkedIds={linkedIds}
796
+ noGlow={themeName === 'light' || themeName === 'warm'}
754
797
  />
755
798
  )}
756
799
  {!selectedId && (
@@ -761,7 +804,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
761
804
  left: '50%',
762
805
  transform: 'translateX(-50%)',
763
806
  fontSize: 9,
764
- color: '#181c38',
807
+ color: 'var(--bd-edge)',
765
808
  letterSpacing: '0.1em',
766
809
  pointerEvents: 'none',
767
810
  whiteSpace: 'nowrap',
@@ -786,15 +829,15 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
786
829
  <div
787
830
  style={{
788
831
  width: 282,
789
- borderLeft: '1px solid #0f1224',
790
- background: '#080b1e',
832
+ borderLeft: '1px solid var(--bd-faint)',
833
+ background: 'var(--bg-deep)',
791
834
  display: viewMode === 'architecture' ? 'none' : 'flex',
792
835
  flexDirection: 'column',
793
836
  overflow: 'hidden',
794
837
  flexShrink: 0,
795
838
  }}
796
839
  >
797
- <div style={{ display: 'flex', borderBottom: '1px solid #0f1224', flexShrink: 0 }}>
840
+ <div style={{ display: 'flex', borderBottom: '1px solid var(--bd-faint)', flexShrink: 0 }}>
798
841
  {['node', 'links', 'blast', 'spec', 'skeleton', 'info'].map((t) => (
799
842
  <button
800
843
  key={t}
@@ -804,8 +847,8 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
804
847
  padding: '7px 0',
805
848
  background: 'none',
806
849
  border: 'none',
807
- borderBottom: tab === t ? '2px solid #7c6af7' : '2px solid transparent',
808
- color: tab === t ? '#c8cde8' : '#3a3f5c',
850
+ borderBottom: tab === t ? '2px solid var(--ac-primary)' : '2px solid transparent',
851
+ color: tab === t ? 'var(--tx-primary)' : 'var(--tx-ghost)',
809
852
  fontSize: 8,
810
853
  letterSpacing: '0.06em',
811
854
  fontWeight: 700,
@@ -824,13 +867,13 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
824
867
  {tab === 'node' && !selectedNode && <Hint>Select a node to inspect it.</Hint>}
825
868
  {tab === 'node' && selectedNode && (
826
869
  <div>
827
- <div style={{ fontSize: 12, fontWeight: 700, color: '#e0e4f0', marginBottom: 2 }}>
870
+ <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--tx-bright)', marginBottom: 2 }}>
828
871
  {selectedNode.label}
829
872
  </div>
830
873
  <div
831
874
  style={{
832
875
  fontSize: 8,
833
- color: '#3a3f5c',
876
+ color: 'var(--tx-ghost)',
834
877
  marginBottom: 9,
835
878
  wordBreak: 'break-all',
836
879
  lineHeight: 1.7,
@@ -847,7 +890,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
847
890
  <Row
848
891
  label="score"
849
892
  value={
850
- <span style={{ color: '#7c6af7', fontWeight: 700 }}>{selectedNode.score}</span>
893
+ <span style={{ color: 'var(--ac-primary)', fontWeight: 700 }}>{selectedNode.score}</span>
851
894
  }
852
895
  />
853
896
  <Row
@@ -877,12 +920,12 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
877
920
  gap: 5,
878
921
  alignItems: 'center',
879
922
  padding: '3px 0',
880
- borderBottom: '1px solid #0f1228',
923
+ borderBottom: '1px solid var(--bd-faint)',
881
924
  }}
882
925
  >
883
926
  <KindBadge kind={ex.kind} />
884
- <span style={{ fontSize: 9, color: '#8890b0' }}>{ex.name}</span>
885
- <span style={{ marginLeft: 'auto', fontSize: 8, color: '#2a2f4a' }}>
927
+ <span style={{ fontSize: 9, color: 'var(--tx-secondary)' }}>{ex.name}</span>
928
+ <span style={{ marginLeft: 'auto', fontSize: 8, color: 'var(--tx-faint)' }}>
886
929
  L{ex.line}
887
930
  </span>
888
931
  </div>
@@ -948,7 +991,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
948
991
  <>
949
992
  <SL>Imports ({outEdges.length})</SL>
950
993
  {outEdges.length === 0 && (
951
- <div style={{ color: '#2a2f4a', fontSize: 9 }}>No imports.</div>
994
+ <div style={{ color: 'var(--tx-faint)', fontSize: 9 }}>No imports.</div>
952
995
  )}
953
996
  {outEdges.map((e, i) => {
954
997
  const tn = graph.nodes.find((n) => n.id === e.target);
@@ -959,9 +1002,9 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
959
1002
  style={{
960
1003
  padding: '5px 7px',
961
1004
  marginBottom: 3,
962
- background: '#0c0e20',
1005
+ background: 'var(--bg-input)',
963
1006
  borderRadius: 4,
964
- border: `1px solid ${tn?.cluster.color || '#141830'}22`,
1007
+ border: `1px solid ${tn?.cluster.color || 'var(--bd-muted)'}22`,
965
1008
  cursor: 'pointer',
966
1009
  }}
967
1010
  >
@@ -974,17 +1017,17 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
974
1017
  }}
975
1018
  >
976
1019
  <span style={{ fontSize: 8, color: extColor(tn?.ext || '') }}>↗</span>
977
- <span style={{ fontSize: 9, color: '#c8cde8' }}>
1020
+ <span style={{ fontSize: 9, color: 'var(--tx-primary)' }}>
978
1021
  {tn?.label || e.target}
979
1022
  </span>
980
1023
  {e.isType && (
981
- <span style={{ fontSize: 7, color: '#3a3f6a', marginLeft: 'auto' }}>
1024
+ <span style={{ fontSize: 7, color: 'var(--tx-ghost)', marginLeft: 'auto' }}>
982
1025
  type
983
1026
  </span>
984
1027
  )}
985
1028
  </div>
986
1029
  {e.importedNames.length > 0 && (
987
- <div style={{ fontSize: 7.5, color: '#3a4060', paddingLeft: 12 }}>
1030
+ <div style={{ fontSize: 7.5, color: 'var(--tx-dim)', paddingLeft: 12 }}>
988
1031
  {e.importedNames.join(', ')}
989
1032
  </div>
990
1033
  )}
@@ -993,7 +1036,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
993
1036
  })}
994
1037
  <SL>Imported by ({inEdges.length})</SL>
995
1038
  {inEdges.length === 0 && (
996
- <div style={{ color: '#2a2f4a', fontSize: 9 }}>
1039
+ <div style={{ color: 'var(--tx-faint)', fontSize: 9 }}>
997
1040
  Not imported by any visible files.
998
1041
  </div>
999
1042
  )}
@@ -1006,9 +1049,9 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1006
1049
  style={{
1007
1050
  padding: '5px 7px',
1008
1051
  marginBottom: 3,
1009
- background: '#0c0e20',
1052
+ background: 'var(--bg-input)',
1010
1053
  borderRadius: 4,
1011
- border: `1px solid ${sn?.cluster.color || '#141830'}22`,
1054
+ border: `1px solid ${sn?.cluster.color || 'var(--bd-muted)'}22`,
1012
1055
  cursor: 'pointer',
1013
1056
  }}
1014
1057
  >
@@ -1020,18 +1063,18 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1020
1063
  marginBottom: e.importedNames.length ? 3 : 0,
1021
1064
  }}
1022
1065
  >
1023
- <span style={{ fontSize: 8, color: '#7c6af7' }}>↙</span>
1024
- <span style={{ fontSize: 9, color: '#c8cde8' }}>
1066
+ <span style={{ fontSize: 8, color: 'var(--ac-primary)' }}>↙</span>
1067
+ <span style={{ fontSize: 9, color: 'var(--tx-primary)' }}>
1025
1068
  {sn?.label || e.source}
1026
1069
  </span>
1027
1070
  {e.isType && (
1028
- <span style={{ fontSize: 7, color: '#3a3f6a', marginLeft: 'auto' }}>
1071
+ <span style={{ fontSize: 7, color: 'var(--tx-ghost)', marginLeft: 'auto' }}>
1029
1072
  type
1030
1073
  </span>
1031
1074
  )}
1032
1075
  </div>
1033
1076
  {e.importedNames.length > 0 && (
1034
- <div style={{ fontSize: 7.5, color: '#3a4060', paddingLeft: 12 }}>
1077
+ <div style={{ fontSize: 7.5, color: 'var(--tx-dim)', paddingLeft: 12 }}>
1035
1078
  {e.importedNames.join(', ')}
1036
1079
  </div>
1037
1080
  )}
@@ -1050,11 +1093,11 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1050
1093
  )}
1051
1094
  {tab === 'blast' && selectedId && (
1052
1095
  <div>
1053
- <div style={{ fontSize: 9, color: '#8890b0', marginBottom: 10 }}>
1054
- Modifying <span style={{ color: '#7c6af7' }}>{selectedNode?.label}</span> impacts:
1096
+ <div style={{ fontSize: 9, color: 'var(--tx-secondary)', marginBottom: 10 }}>
1097
+ Modifying <span style={{ color: 'var(--ac-primary)' }}>{selectedNode?.label}</span> impacts:
1055
1098
  </div>
1056
1099
  {affectedIds.length === 0 ? (
1057
- <div style={{ color: '#2a2f4a', fontSize: 9 }}>No visible downstream nodes.</div>
1100
+ <div style={{ color: 'var(--tx-faint)', fontSize: 9 }}>No visible downstream nodes.</div>
1058
1101
  ) : (
1059
1102
  affectedIds.map((id) => {
1060
1103
  const n = graph.nodes.find((x) => x.id === id);
@@ -1068,9 +1111,9 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1068
1111
  gap: 6,
1069
1112
  padding: '4px 7px',
1070
1113
  marginBottom: 3,
1071
- background: '#0c0e20',
1114
+ background: 'var(--bg-input)',
1072
1115
  borderRadius: 4,
1073
- border: '1px solid #141830',
1116
+ border: '1px solid var(--bd-muted)',
1074
1117
  cursor: 'pointer',
1075
1118
  }}
1076
1119
  >
@@ -1080,7 +1123,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1080
1123
  <span
1081
1124
  style={{
1082
1125
  fontSize: 9,
1083
- color: '#c8cde8',
1126
+ color: 'var(--tx-primary)',
1084
1127
  flex: 1,
1085
1128
  overflow: 'hidden',
1086
1129
  textOverflow: 'ellipsis',
@@ -1100,12 +1143,12 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1100
1143
  style={{
1101
1144
  marginTop: 10,
1102
1145
  padding: '8px 10px',
1103
- background: '#0c0e20',
1146
+ background: 'var(--bg-input)',
1104
1147
  borderRadius: 5,
1105
- border: '1px solid #1a1f38',
1148
+ border: '1px solid var(--bd-muted)',
1106
1149
  }}
1107
1150
  >
1108
- <div style={{ fontSize: 8, color: '#3a3f5c', marginBottom: 2 }}>BLAST RADIUS</div>
1151
+ <div style={{ fontSize: 8, color: 'var(--tx-ghost)', marginBottom: 2 }}>BLAST RADIUS</div>
1109
1152
  <div
1110
1153
  style={{
1111
1154
  fontSize: 22,
@@ -1115,11 +1158,11 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1115
1158
  ? '#f77c6a'
1116
1159
  : affectedIds.length > 3
1117
1160
  ? '#f7c76a'
1118
- : '#7c6af7',
1161
+ : 'var(--ac-primary)',
1119
1162
  }}
1120
1163
  >
1121
1164
  {affectedIds.length}{' '}
1122
- <span style={{ fontSize: 10, fontWeight: 400, color: '#3a3f5c' }}>nodes</span>
1165
+ <span style={{ fontSize: 10, fontWeight: 400, color: 'var(--tx-ghost)' }}>nodes</span>
1123
1166
  </div>
1124
1167
  </div>
1125
1168
  </div>
@@ -1128,8 +1171,8 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1128
1171
  {/* SPEC */}
1129
1172
  {tab === 'spec' && !mapping && (
1130
1173
  <Hint>
1131
- Load a <code style={{ color: '#7c6af7' }}>mapping.json</code> and{' '}
1132
- <code style={{ color: '#7c6af7' }}>spec.md</code> using the MAP / SPEC buttons in
1174
+ Load a <code style={{ color: 'var(--ac-primary)' }}>mapping.json</code> and{' '}
1175
+ <code style={{ color: 'var(--ac-primary)' }}>spec.md</code> using the MAP / SPEC buttons in
1133
1176
  the top bar.
1134
1177
  </Hint>
1135
1178
  )}
@@ -1158,11 +1201,11 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1158
1201
  if (unique.length === 0)
1159
1202
  return <Hint>No spec requirements mapped to this file.</Hint>;
1160
1203
 
1161
- const confidenceColor = (c) => (c === 'llm' ? '#4ade80' : '#3a3f5c');
1204
+ const confidenceColor = (c) => (c === 'llm' ? '#4ade80' : 'var(--tx-ghost)');
1162
1205
 
1163
1206
  return (
1164
1207
  <div>
1165
- <div style={{ fontSize: 8, color: '#3a3f5c', marginBottom: 8 }}>
1208
+ <div style={{ fontSize: 8, color: 'var(--tx-ghost)', marginBottom: 8 }}>
1166
1209
  {unique.length} requirement{unique.length > 1 ? 's' : ''} linked
1167
1210
  </div>
1168
1211
  {unique.map((entry, i) => {
@@ -1179,16 +1222,16 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1179
1222
  key={i}
1180
1223
  style={{
1181
1224
  marginBottom: 10,
1182
- background: '#0b0d1f',
1225
+ background: 'var(--bg-node)',
1183
1226
  borderRadius: 5,
1184
- border: '1px solid #141830',
1227
+ border: '1px solid var(--bd-muted)',
1185
1228
  overflow: 'hidden',
1186
1229
  }}
1187
1230
  >
1188
1231
  <div
1189
1232
  style={{
1190
1233
  padding: '6px 9px',
1191
- borderBottom: '1px solid #0f1224',
1234
+ borderBottom: '1px solid var(--bd-faint)',
1192
1235
  display: 'flex',
1193
1236
  alignItems: 'center',
1194
1237
  gap: 5,
@@ -1196,7 +1239,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1196
1239
  }}
1197
1240
  >
1198
1241
  <span
1199
- style={{ fontSize: 9, fontWeight: 700, color: '#c8cde8', flex: 1 }}
1242
+ style={{ fontSize: 9, fontWeight: 700, color: 'var(--tx-primary)', flex: 1 }}
1200
1243
  >
1201
1244
  {entry.requirement}
1202
1245
  </span>
@@ -1224,7 +1267,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1224
1267
  style={{
1225
1268
  padding: '7px 9px',
1226
1269
  fontSize: 8.5,
1227
- color: '#8890b0',
1270
+ color: 'var(--tx-secondary)',
1228
1271
  lineHeight: 1.7,
1229
1272
  maxHeight: 200,
1230
1273
  overflow: 'auto',
@@ -1236,7 +1279,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1236
1279
  <div
1237
1280
  key={li}
1238
1281
  style={{
1239
- color: '#5a6090',
1282
+ color: 'var(--tx-node)',
1240
1283
  fontWeight: 700,
1241
1284
  marginTop: 6,
1242
1285
  fontSize: 8,
@@ -1247,7 +1290,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1247
1290
  );
1248
1291
  if (line.startsWith('- **'))
1249
1292
  return (
1250
- <div key={li} style={{ paddingLeft: 6, color: '#6a709a' }}>
1293
+ <div key={li} style={{ paddingLeft: 6, color: 'var(--tx-secondary)' }}>
1251
1294
  {line.replace(/\*\*/g, '')}
1252
1295
  </div>
1253
1296
  );
@@ -1257,21 +1300,21 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1257
1300
  })}
1258
1301
  </div>
1259
1302
  ) : (
1260
- <div style={{ padding: '7px 9px', fontSize: 8, color: '#2a2f4a' }}>
1303
+ <div style={{ padding: '7px 9px', fontSize: 8, color: 'var(--tx-faint)' }}>
1261
1304
  {req
1262
- ? 'Requirement title mismatch -- spec section not found in the spec file.'
1263
- : <>Spec not loaded -- run <code style={{ color: '#7c6af7' }}>spec-gen view</code> or load <code style={{ color: '#7c6af7' }}>spec.md</code> manually.</>}
1305
+ ? 'Requirement title mismatch spec section not found in the spec file.'
1306
+ : <>Spec not loaded run <code style={{ color: 'var(--ac-primary)' }}>spec-gen view</code> or load <code style={{ color: 'var(--ac-primary)' }}>spec.md</code> manually.</>}
1264
1307
  </div>
1265
1308
  )}
1266
1309
  <div
1267
1310
  style={{
1268
1311
  padding: '4px 9px',
1269
- borderTop: '1px solid #0f1224',
1312
+ borderTop: '1px solid var(--bd-faint)',
1270
1313
  fontSize: 7.5,
1271
- color: '#2a3060',
1314
+ color: 'var(--ac-cluster-arr)',
1272
1315
  }}
1273
1316
  >
1274
- service: <span style={{ color: '#3a4080' }}>{entry.service}</span>
1317
+ service: <span style={{ color: 'var(--tx-dim)' }}>{entry.service}</span>
1275
1318
  </div>
1276
1319
  </div>
1277
1320
  );
@@ -1291,10 +1334,10 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1291
1334
  {!skeletonLoading && skeletonData && (
1292
1335
  <div>
1293
1336
  <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: 8 }}>
1294
- <span style={{ fontSize: 9, color: '#6a70a0', fontFamily: 'inherit' }}>
1337
+ <span style={{ fontSize: 9, color: 'var(--tx-muted)', fontFamily: 'inherit' }}>
1295
1338
  {skeletonData.language} · {skeletonData.skeletonLines}/{skeletonData.originalLines} lines
1296
1339
  </span>
1297
- <span style={{ fontSize: 9, color: skeletonData.reductionPct >= 20 ? '#7c6af7' : '#3a3f5c', fontFamily: 'inherit' }}>
1340
+ <span style={{ fontSize: 9, color: skeletonData.reductionPct >= 20 ? 'var(--ac-primary)' : 'var(--tx-ghost)', fontFamily: 'inherit' }}>
1298
1341
  -{skeletonData.reductionPct}%
1299
1342
  </span>
1300
1343
  </div>
@@ -1302,12 +1345,12 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1302
1345
  margin: 0,
1303
1346
  fontSize: 8,
1304
1347
  lineHeight: 1.6,
1305
- color: '#9aa0c8',
1348
+ color: 'var(--tx-secondary)',
1306
1349
  fontFamily: "'JetBrains Mono', monospace",
1307
1350
  whiteSpace: 'pre-wrap',
1308
1351
  wordBreak: 'break-word',
1309
- background: '#060819',
1310
- border: '1px solid #0f1224',
1352
+ background: 'var(--bg-deep)',
1353
+ border: '1px solid var(--bd-faint)',
1311
1354
  borderRadius: 4,
1312
1355
  padding: '8px 10px',
1313
1356
  }}>
@@ -1325,7 +1368,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1325
1368
  {[
1326
1369
  ['Nodes', stats.nodeCount],
1327
1370
  ['Edges', stats.edgeCount],
1328
- ['Clusters', stats.clusterCount],
1371
+ ['Clusters', stats.structuralClusterCount ?? displayClusters.length],
1329
1372
  ['Cycles', stats.cycleCount],
1330
1373
  ['Avg degree', stats.avgDegree?.toFixed(2)],
1331
1374
  ['Density', stats.density?.toFixed(4)],
@@ -1335,11 +1378,11 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1335
1378
  <SL>Active filters</SL>
1336
1379
  <Row
1337
1380
  label="Visible nodes"
1338
- value={<span style={{ color: '#7c6af7' }}>{filterStats.visible}</span>}
1381
+ value={<span style={{ color: 'var(--ac-primary)' }}>{filterStats.visible}</span>}
1339
1382
  />
1340
1383
  <Row
1341
1384
  label="Visible edges"
1342
- value={<span style={{ color: '#3ecfcf' }}>{filterStats.visibleEdges}</span>}
1385
+ value={<span style={{ color: 'var(--ac-teal)' }}>{filterStats.visibleEdges}</span>}
1343
1386
  />
1344
1387
  <Row label="Orphans" value={filterStats.orphanCount} />
1345
1388
  <SL>Top 10 by score</SL>
@@ -1358,12 +1401,12 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1358
1401
  cursor: 'pointer',
1359
1402
  }}
1360
1403
  >
1361
- <span style={{ fontSize: 8, color: '#2a2f4a', minWidth: 12 }}>{i + 1}</span>
1362
- <span style={{ fontSize: 8, color: extColor(n.ext) }}>{n.ext || '--'}</span>
1404
+ <span style={{ fontSize: 8, color: 'var(--tx-faint)', minWidth: 12 }}>{i + 1}</span>
1405
+ <span style={{ fontSize: 8, color: extColor(n.ext) }}>{n.ext || ''}</span>
1363
1406
  <span
1364
1407
  style={{
1365
1408
  fontSize: 9,
1366
- color: '#8890b0',
1409
+ color: 'var(--tx-secondary)',
1367
1410
  flex: 1,
1368
1411
  overflow: 'hidden',
1369
1412
  textOverflow: 'ellipsis',
@@ -1372,7 +1415,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1372
1415
  >
1373
1416
  {n.label}
1374
1417
  </span>
1375
- <span style={{ fontSize: 9, color: '#7c6af7' }}>{n.score}</span>
1418
+ <span style={{ fontSize: 9, color: 'var(--ac-primary)' }}>{n.score}</span>
1376
1419
  </div>
1377
1420
  );
1378
1421
  })}
@@ -1381,7 +1424,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1381
1424
  </div>
1382
1425
 
1383
1426
  {/* Cluster legend */}
1384
- <div style={{ padding: '9px 13px', borderTop: '1px solid #0f1224', flexShrink: 0 }}>
1427
+ <div style={{ padding: '9px 13px', borderTop: '1px solid var(--bd-faint)', flexShrink: 0 }}>
1385
1428
  <div style={{ display: 'flex', gap: 12, marginBottom: 8 }}>
1386
1429
  <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
1387
1430
  <svg width="24" height="8" style={{ overflow: 'visible' }}>
@@ -1390,7 +1433,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1390
1433
  y1="4"
1391
1434
  x2="18"
1392
1435
  y2="4"
1393
- stroke="#5a6090"
1436
+ stroke="var(--tx-node)"
1394
1437
  strokeWidth="1.5"
1395
1438
  markerEnd="url(#arr-legend)"
1396
1439
  />
@@ -1403,11 +1446,11 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1403
1446
  refY="2.5"
1404
1447
  orient="auto"
1405
1448
  >
1406
- <path d="M0,0 L0,5 L5,2.5z" fill="#5a6090" />
1449
+ <path d="M0,0 L0,5 L5,2.5z" style={{ fill: 'var(--tx-node)' }} />
1407
1450
  </marker>
1408
1451
  </defs>
1409
1452
  </svg>
1410
- <span style={{ fontSize: 7.5, color: '#3a3f5c' }}>runtime import</span>
1453
+ <span style={{ fontSize: 7.5, color: 'var(--tx-ghost)' }}>runtime import</span>
1411
1454
  </div>
1412
1455
  <div style={{ display: 'flex', alignItems: 'center', gap: 5 }}>
1413
1456
  <svg width="24" height="8" style={{ overflow: 'visible' }}>
@@ -1416,7 +1459,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1416
1459
  y1="4"
1417
1460
  x2="18"
1418
1461
  y2="4"
1419
- stroke="#3a3f5c"
1462
+ stroke="var(--tx-ghost)"
1420
1463
  strokeWidth="1.2"
1421
1464
  strokeDasharray="3 2"
1422
1465
  markerEnd="url(#arr-legend-type)"
@@ -1430,20 +1473,20 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1430
1473
  refY="2.5"
1431
1474
  orient="auto"
1432
1475
  >
1433
- <path d="M0,0 L0,5 L5,2.5z" fill="#3a3f5c" />
1476
+ <path d="M0,0 L0,5 L5,2.5z" style={{ fill: 'var(--tx-ghost)' }} />
1434
1477
  </marker>
1435
1478
  </defs>
1436
1479
  </svg>
1437
- <span style={{ fontSize: 7.5, color: '#3a3f5c' }}>type-only</span>
1480
+ <span style={{ fontSize: 7.5, color: 'var(--tx-ghost)' }}>type-only</span>
1438
1481
  </div>
1439
1482
  </div>
1440
1483
  <div
1441
- style={{ fontSize: 8, color: '#1e2240', letterSpacing: '0.08em', marginBottom: 5 }}
1484
+ style={{ fontSize: 8, color: 'var(--ac-arrow)', letterSpacing: '0.08em', marginBottom: 5 }}
1442
1485
  >
1443
1486
  CLUSTERS · click to filter
1444
1487
  </div>
1445
1488
  <div style={{ display: 'flex', flexWrap: 'wrap', gap: 5 }}>
1446
- {graph.clusters.map((cl) => (
1489
+ {displayClusters.map((cl) => (
1447
1490
  <div
1448
1491
  key={cl.id}
1449
1492
  onClick={() =>
@@ -1470,7 +1513,7 @@ export default function App({ graphUrl, mappingUrl = '/api/mapping', specUrl = '
1470
1513
  <span
1471
1514
  style={{
1472
1515
  fontSize: 7.5,
1473
- color: filters.cluster === cl.name ? cl.color : '#3a3f5c',
1516
+ color: filters.cluster === cl.name ? cl.color : 'var(--tx-ghost)',
1474
1517
  }}
1475
1518
  >
1476
1519
  {cl.name}