@assistkick/create 1.0.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 (178) hide show
  1. package/dist/bin/create.d.ts +2 -0
  2. package/dist/bin/create.js +25 -0
  3. package/dist/bin/create.js.map +1 -0
  4. package/dist/src/scaffolder.d.ts +22 -0
  5. package/dist/src/scaffolder.js +120 -0
  6. package/dist/src/scaffolder.js.map +1 -0
  7. package/package.json +24 -0
  8. package/templates/product-system/.env.example +8 -0
  9. package/templates/product-system/CLAUDE.md +45 -0
  10. package/templates/product-system/package.json +32 -0
  11. package/templates/product-system/packages/backend/package.json +37 -0
  12. package/templates/product-system/packages/backend/src/middleware/auth_middleware.test.ts +86 -0
  13. package/templates/product-system/packages/backend/src/middleware/auth_middleware.ts +35 -0
  14. package/templates/product-system/packages/backend/src/routes/auth.ts +463 -0
  15. package/templates/product-system/packages/backend/src/routes/coherence.ts +187 -0
  16. package/templates/product-system/packages/backend/src/routes/graph.ts +67 -0
  17. package/templates/product-system/packages/backend/src/routes/kanban.ts +201 -0
  18. package/templates/product-system/packages/backend/src/routes/pipeline.ts +41 -0
  19. package/templates/product-system/packages/backend/src/routes/projects.ts +122 -0
  20. package/templates/product-system/packages/backend/src/routes/users.ts +97 -0
  21. package/templates/product-system/packages/backend/src/server.ts +159 -0
  22. package/templates/product-system/packages/backend/src/services/auth_service.test.ts +115 -0
  23. package/templates/product-system/packages/backend/src/services/auth_service.ts +82 -0
  24. package/templates/product-system/packages/backend/src/services/coherence-review.ts +339 -0
  25. package/templates/product-system/packages/backend/src/services/email_service.ts +75 -0
  26. package/templates/product-system/packages/backend/src/services/init.ts +80 -0
  27. package/templates/product-system/packages/backend/src/services/invitation_service.test.ts +235 -0
  28. package/templates/product-system/packages/backend/src/services/invitation_service.ts +193 -0
  29. package/templates/product-system/packages/backend/src/services/password_reset_service.test.ts +151 -0
  30. package/templates/product-system/packages/backend/src/services/password_reset_service.ts +135 -0
  31. package/templates/product-system/packages/backend/src/services/project_service.test.ts +215 -0
  32. package/templates/product-system/packages/backend/src/services/project_service.ts +171 -0
  33. package/templates/product-system/packages/backend/src/services/pty_session_manager.test.ts +88 -0
  34. package/templates/product-system/packages/backend/src/services/pty_session_manager.ts +279 -0
  35. package/templates/product-system/packages/backend/src/services/terminal_ws_handler.ts +133 -0
  36. package/templates/product-system/packages/backend/src/services/user_management_service.test.ts +158 -0
  37. package/templates/product-system/packages/backend/src/services/user_management_service.ts +128 -0
  38. package/templates/product-system/packages/backend/tsconfig.json +22 -0
  39. package/templates/product-system/packages/frontend/index.html +13 -0
  40. package/templates/product-system/packages/frontend/package-lock.json +2666 -0
  41. package/templates/product-system/packages/frontend/package.json +30 -0
  42. package/templates/product-system/packages/frontend/public/favicon.svg +16 -0
  43. package/templates/product-system/packages/frontend/src/App.tsx +29 -0
  44. package/templates/product-system/packages/frontend/src/api/client.ts +386 -0
  45. package/templates/product-system/packages/frontend/src/api/client_projects.test.ts +104 -0
  46. package/templates/product-system/packages/frontend/src/api/client_refresh.test.ts +145 -0
  47. package/templates/product-system/packages/frontend/src/components/CoherenceView.tsx +414 -0
  48. package/templates/product-system/packages/frontend/src/components/GraphLegend.tsx +124 -0
  49. package/templates/product-system/packages/frontend/src/components/GraphSettings.tsx +112 -0
  50. package/templates/product-system/packages/frontend/src/components/GraphView.tsx +370 -0
  51. package/templates/product-system/packages/frontend/src/components/InviteUserDialog.tsx +85 -0
  52. package/templates/product-system/packages/frontend/src/components/KanbanView.tsx +470 -0
  53. package/templates/product-system/packages/frontend/src/components/LoginPage.tsx +116 -0
  54. package/templates/product-system/packages/frontend/src/components/ProjectSelector.tsx +187 -0
  55. package/templates/product-system/packages/frontend/src/components/QaIssueSheet.tsx +192 -0
  56. package/templates/product-system/packages/frontend/src/components/SidePanel.tsx +231 -0
  57. package/templates/product-system/packages/frontend/src/components/TerminalView.tsx +200 -0
  58. package/templates/product-system/packages/frontend/src/components/Toolbar.tsx +84 -0
  59. package/templates/product-system/packages/frontend/src/components/UsersView.tsx +249 -0
  60. package/templates/product-system/packages/frontend/src/constants/graph.ts +191 -0
  61. package/templates/product-system/packages/frontend/src/hooks/useAuth.tsx +54 -0
  62. package/templates/product-system/packages/frontend/src/hooks/useGraph.ts +27 -0
  63. package/templates/product-system/packages/frontend/src/hooks/useKanban.ts +21 -0
  64. package/templates/product-system/packages/frontend/src/hooks/useProjects.ts +86 -0
  65. package/templates/product-system/packages/frontend/src/hooks/useTheme.ts +26 -0
  66. package/templates/product-system/packages/frontend/src/hooks/useToast.tsx +62 -0
  67. package/templates/product-system/packages/frontend/src/hooks/use_projects_logic.test.ts +61 -0
  68. package/templates/product-system/packages/frontend/src/main.tsx +12 -0
  69. package/templates/product-system/packages/frontend/src/pages/accept_invitation_page.tsx +167 -0
  70. package/templates/product-system/packages/frontend/src/pages/forgot_password_page.tsx +100 -0
  71. package/templates/product-system/packages/frontend/src/pages/register_page.tsx +137 -0
  72. package/templates/product-system/packages/frontend/src/pages/reset_password_page.tsx +146 -0
  73. package/templates/product-system/packages/frontend/src/routes/ProtectedRoute.tsx +12 -0
  74. package/templates/product-system/packages/frontend/src/routes/accept_invitation.tsx +14 -0
  75. package/templates/product-system/packages/frontend/src/routes/dashboard.tsx +221 -0
  76. package/templates/product-system/packages/frontend/src/routes/forgot_password.tsx +13 -0
  77. package/templates/product-system/packages/frontend/src/routes/login.tsx +14 -0
  78. package/templates/product-system/packages/frontend/src/routes/register.tsx +14 -0
  79. package/templates/product-system/packages/frontend/src/routes/reset_password.tsx +13 -0
  80. package/templates/product-system/packages/frontend/src/styles/index.css +3358 -0
  81. package/templates/product-system/packages/frontend/src/utils/auth_validation.test.ts +51 -0
  82. package/templates/product-system/packages/frontend/src/utils/auth_validation.ts +19 -0
  83. package/templates/product-system/packages/frontend/src/utils/login_validation.test.ts +61 -0
  84. package/templates/product-system/packages/frontend/src/utils/login_validation.ts +24 -0
  85. package/templates/product-system/packages/frontend/src/utils/logout.test.ts +63 -0
  86. package/templates/product-system/packages/frontend/src/utils/node_sizing.test.ts +62 -0
  87. package/templates/product-system/packages/frontend/src/utils/node_sizing.ts +24 -0
  88. package/templates/product-system/packages/frontend/src/utils/task_status.test.ts +53 -0
  89. package/templates/product-system/packages/frontend/src/utils/task_status.ts +14 -0
  90. package/templates/product-system/packages/frontend/tsconfig.json +21 -0
  91. package/templates/product-system/packages/frontend/vite.config.ts +20 -0
  92. package/templates/product-system/packages/shared/.env.example +3 -0
  93. package/templates/product-system/packages/shared/README.md +1 -0
  94. package/templates/product-system/packages/shared/db/migrate.ts +32 -0
  95. package/templates/product-system/packages/shared/db/migrations/0000_dashing_gorgon.sql +128 -0
  96. package/templates/product-system/packages/shared/db/migrations/meta/0000_snapshot.json +819 -0
  97. package/templates/product-system/packages/shared/db/migrations/meta/_journal.json +13 -0
  98. package/templates/product-system/packages/shared/db/schema.ts +137 -0
  99. package/templates/product-system/packages/shared/drizzle.config.js +14 -0
  100. package/templates/product-system/packages/shared/lib/claude-service.ts +215 -0
  101. package/templates/product-system/packages/shared/lib/coherence.ts +278 -0
  102. package/templates/product-system/packages/shared/lib/completeness.ts +30 -0
  103. package/templates/product-system/packages/shared/lib/constants.ts +327 -0
  104. package/templates/product-system/packages/shared/lib/db.ts +81 -0
  105. package/templates/product-system/packages/shared/lib/git_workflow.ts +110 -0
  106. package/templates/product-system/packages/shared/lib/graph.ts +186 -0
  107. package/templates/product-system/packages/shared/lib/kanban.ts +161 -0
  108. package/templates/product-system/packages/shared/lib/markdown.ts +205 -0
  109. package/templates/product-system/packages/shared/lib/pipeline-state-store.ts +124 -0
  110. package/templates/product-system/packages/shared/lib/pipeline.ts +489 -0
  111. package/templates/product-system/packages/shared/lib/prompt_builder.ts +170 -0
  112. package/templates/product-system/packages/shared/lib/relevance_search.ts +159 -0
  113. package/templates/product-system/packages/shared/lib/session.ts +152 -0
  114. package/templates/product-system/packages/shared/lib/validator.ts +117 -0
  115. package/templates/product-system/packages/shared/lib/work_summary_parser.ts +130 -0
  116. package/templates/product-system/packages/shared/package.json +30 -0
  117. package/templates/product-system/packages/shared/scripts/assign-project.ts +52 -0
  118. package/templates/product-system/packages/shared/tools/add_edge.ts +61 -0
  119. package/templates/product-system/packages/shared/tools/add_node.ts +101 -0
  120. package/templates/product-system/packages/shared/tools/end_session.ts +87 -0
  121. package/templates/product-system/packages/shared/tools/get_gaps.ts +87 -0
  122. package/templates/product-system/packages/shared/tools/get_kanban.ts +125 -0
  123. package/templates/product-system/packages/shared/tools/get_node.ts +78 -0
  124. package/templates/product-system/packages/shared/tools/get_status.ts +98 -0
  125. package/templates/product-system/packages/shared/tools/migrate_to_turso.ts +385 -0
  126. package/templates/product-system/packages/shared/tools/move_card.ts +143 -0
  127. package/templates/product-system/packages/shared/tools/rebuild_index.ts +77 -0
  128. package/templates/product-system/packages/shared/tools/remove_edge.ts +59 -0
  129. package/templates/product-system/packages/shared/tools/remove_node.ts +96 -0
  130. package/templates/product-system/packages/shared/tools/resolve_question.ts +75 -0
  131. package/templates/product-system/packages/shared/tools/search_nodes.ts +106 -0
  132. package/templates/product-system/packages/shared/tools/start_session.ts +144 -0
  133. package/templates/product-system/packages/shared/tools/update_node.ts +133 -0
  134. package/templates/product-system/packages/shared/tsconfig.json +24 -0
  135. package/templates/product-system/pnpm-workspace.yaml +2 -0
  136. package/templates/product-system/smoke_test.ts +219 -0
  137. package/templates/product-system/tests/coherence_review.test.ts +562 -0
  138. package/templates/product-system/tests/db_sqlite_fallback.test.ts +75 -0
  139. package/templates/product-system/tests/edge_type_color_coding.test.ts +147 -0
  140. package/templates/product-system/tests/emit-tool-use-events.test.ts +85 -0
  141. package/templates/product-system/tests/feature_kind.test.ts +139 -0
  142. package/templates/product-system/tests/gap_indicators.test.ts +199 -0
  143. package/templates/product-system/tests/graceful_init.test.ts +142 -0
  144. package/templates/product-system/tests/graph_legend.test.ts +314 -0
  145. package/templates/product-system/tests/graph_settings_sheet.test.ts +804 -0
  146. package/templates/product-system/tests/hide_defined_filter.test.ts +205 -0
  147. package/templates/product-system/tests/kanban.test.ts +529 -0
  148. package/templates/product-system/tests/neighborhood_focus.test.ts +132 -0
  149. package/templates/product-system/tests/node_search.test.ts +340 -0
  150. package/templates/product-system/tests/node_sizing.test.ts +170 -0
  151. package/templates/product-system/tests/node_type_toggle_filters.test.ts +285 -0
  152. package/templates/product-system/tests/node_type_visual_encoding.test.ts +103 -0
  153. package/templates/product-system/tests/pipeline-state-store.test.ts +268 -0
  154. package/templates/product-system/tests/pipeline-unit.test.ts +593 -0
  155. package/templates/product-system/tests/pipeline.test.ts +195 -0
  156. package/templates/product-system/tests/pipeline_stats_all_cards.test.ts +193 -0
  157. package/templates/product-system/tests/play_all.test.ts +296 -0
  158. package/templates/product-system/tests/qa_issue_sheet.test.ts +464 -0
  159. package/templates/product-system/tests/relevance_search.test.ts +186 -0
  160. package/templates/product-system/tests/search_reorder.test.ts +88 -0
  161. package/templates/product-system/tests/serve_ui.test.ts +281 -0
  162. package/templates/product-system/tests/serve_ui_drizzle.test.ts +114 -0
  163. package/templates/product-system/tests/session_context_recall.test.ts +135 -0
  164. package/templates/product-system/tests/side_panel.test.ts +345 -0
  165. package/templates/product-system/tests/spec_completeness_label.test.ts +69 -0
  166. package/templates/product-system/tests/url_routing_test.ts +122 -0
  167. package/templates/product-system/tests/user_login.test.ts +150 -0
  168. package/templates/product-system/tests/user_registration.test.ts +205 -0
  169. package/templates/product-system/tests/web_terminal.test.ts +572 -0
  170. package/templates/product-system/tests/work_summary.test.ts +211 -0
  171. package/templates/product-system/tests/zoom_pan.test.ts +43 -0
  172. package/templates/product-system/tsconfig.json +24 -0
  173. package/templates/skills/product-bootstrap/SKILL.md +312 -0
  174. package/templates/skills/product-code-reviewer/SKILL.md +147 -0
  175. package/templates/skills/product-debugger/SKILL.md +206 -0
  176. package/templates/skills/product-debugger/references/agent-browser.md +1156 -0
  177. package/templates/skills/product-developer/SKILL.md +182 -0
  178. package/templates/skills/product-interview/SKILL.md +220 -0
@@ -0,0 +1,142 @@
1
+ /**
2
+ * Tests for graceful initialization when graph.json/kanban.json are missing.
3
+ * Verifies that ensureDataFiles creates valid empty-state defaults
4
+ * without overwriting existing files.
5
+ * Uses node:test built-in runner.
6
+ */
7
+ import { describe, it, beforeEach, afterEach } from 'node:test';
8
+ import assert from 'node:assert/strict';
9
+ import { writeFile, readFile, access, mkdir, rm } from 'node:fs/promises';
10
+ import { join } from 'node:path';
11
+ import { tmpdir } from 'node:os';
12
+ import { randomUUID } from 'node:crypto';
13
+
14
+ // Replicate the same logic from serve_ui.js for testability
15
+ const EMPTY_GRAPH = {
16
+ version: '1.0',
17
+ project: {
18
+ name: '',
19
+ description: '',
20
+ created_at: '2026-01-01T00:00:00.000Z',
21
+ updated_at: '2026-01-01T00:00:00.000Z',
22
+ },
23
+ nodes: [],
24
+ edges: [],
25
+ };
26
+
27
+ const EMPTY_KANBAN = {};
28
+
29
+ const fileExists = async (filePath) => {
30
+ try {
31
+ await access(filePath);
32
+ return true;
33
+ } catch {
34
+ return false;
35
+ }
36
+ };
37
+
38
+ const ensureDataFiles = async (dataDir) => {
39
+ const nodesDir = join(dataDir, 'nodes');
40
+ const graphPath = join(dataDir, 'graph.json');
41
+ const kanbanPath = join(dataDir, 'kanban.json');
42
+
43
+ if (!await fileExists(nodesDir)) {
44
+ await mkdir(nodesDir, { recursive: true });
45
+ }
46
+ if (!await fileExists(graphPath)) {
47
+ await writeFile(graphPath, JSON.stringify(EMPTY_GRAPH, null, 2) + '\n', 'utf-8');
48
+ }
49
+ if (!await fileExists(kanbanPath)) {
50
+ await writeFile(kanbanPath, JSON.stringify(EMPTY_KANBAN, null, 2) + '\n', 'utf-8');
51
+ }
52
+ };
53
+
54
+ describe('ensureDataFiles — graceful initialization', () => {
55
+ let tempDir;
56
+
57
+ beforeEach(async () => {
58
+ tempDir = join(tmpdir(), `graceful-init-test-${randomUUID().slice(0, 8)}`);
59
+ await mkdir(tempDir, { recursive: true });
60
+ });
61
+
62
+ afterEach(async () => {
63
+ await rm(tempDir, { recursive: true, force: true });
64
+ });
65
+
66
+ it('creates graph.json with valid empty structure when missing', async () => {
67
+ await ensureDataFiles(tempDir);
68
+ const content = await readFile(join(tempDir, 'graph.json'), 'utf-8');
69
+ const data = JSON.parse(content);
70
+ assert.equal(data.version, '1.0');
71
+ assert.ok(data.project, 'should have project object');
72
+ assert.ok(Array.isArray(data.nodes), 'nodes should be an array');
73
+ assert.ok(Array.isArray(data.edges), 'edges should be an array');
74
+ assert.equal(data.nodes.length, 0);
75
+ assert.equal(data.edges.length, 0);
76
+ });
77
+
78
+ it('creates kanban.json with empty object when missing', async () => {
79
+ await ensureDataFiles(tempDir);
80
+ const content = await readFile(join(tempDir, 'kanban.json'), 'utf-8');
81
+ const data = JSON.parse(content);
82
+ assert.deepEqual(data, {});
83
+ });
84
+
85
+ it('creates nodes/ directory when missing', async () => {
86
+ await ensureDataFiles(tempDir);
87
+ assert.ok(await fileExists(join(tempDir, 'nodes')), 'nodes/ directory should exist');
88
+ });
89
+
90
+ it('does not overwrite existing graph.json', async () => {
91
+ const existingGraph = { version: '1.0', project: { name: 'My Project' }, nodes: [{ id: 'feat_001' }], edges: [] };
92
+ await writeFile(join(tempDir, 'graph.json'), JSON.stringify(existingGraph), 'utf-8');
93
+
94
+ await ensureDataFiles(tempDir);
95
+
96
+ const content = await readFile(join(tempDir, 'graph.json'), 'utf-8');
97
+ const data = JSON.parse(content);
98
+ assert.equal(data.project.name, 'My Project', 'should preserve existing project name');
99
+ assert.equal(data.nodes.length, 1, 'should preserve existing nodes');
100
+ assert.equal(data.nodes[0].id, 'feat_001');
101
+ });
102
+
103
+ it('does not overwrite existing kanban.json', async () => {
104
+ const existingKanban = { feat_001: { column: 'done', rejection_count: 0, notes: [] } };
105
+ await writeFile(join(tempDir, 'kanban.json'), JSON.stringify(existingKanban), 'utf-8');
106
+
107
+ await ensureDataFiles(tempDir);
108
+
109
+ const content = await readFile(join(tempDir, 'kanban.json'), 'utf-8');
110
+ const data = JSON.parse(content);
111
+ assert.ok(data.feat_001, 'should preserve existing kanban entries');
112
+ assert.equal(data.feat_001.column, 'done');
113
+ });
114
+
115
+ it('handles partial state — graph.json exists but kanban.json missing', async () => {
116
+ const existingGraph = { version: '1.0', project: {}, nodes: [], edges: [] };
117
+ await writeFile(join(tempDir, 'graph.json'), JSON.stringify(existingGraph), 'utf-8');
118
+
119
+ await ensureDataFiles(tempDir);
120
+
121
+ // graph.json should be untouched
122
+ const graphContent = await readFile(join(tempDir, 'graph.json'), 'utf-8');
123
+ assert.deepEqual(JSON.parse(graphContent), existingGraph);
124
+
125
+ // kanban.json should be created
126
+ const kanbanContent = await readFile(join(tempDir, 'kanban.json'), 'utf-8');
127
+ assert.deepEqual(JSON.parse(kanbanContent), {});
128
+ });
129
+
130
+ it('is idempotent — running twice produces same result', async () => {
131
+ await ensureDataFiles(tempDir);
132
+ const graphAfterFirst = await readFile(join(tempDir, 'graph.json'), 'utf-8');
133
+ const kanbanAfterFirst = await readFile(join(tempDir, 'kanban.json'), 'utf-8');
134
+
135
+ await ensureDataFiles(tempDir);
136
+ const graphAfterSecond = await readFile(join(tempDir, 'graph.json'), 'utf-8');
137
+ const kanbanAfterSecond = await readFile(join(tempDir, 'kanban.json'), 'utf-8');
138
+
139
+ assert.equal(graphAfterFirst, graphAfterSecond);
140
+ assert.equal(kanbanAfterFirst, kanbanAfterSecond);
141
+ });
142
+ });
@@ -0,0 +1,314 @@
1
+ /**
2
+ * Tests for GraphLegend component logic.
3
+ * Uses node:test built-in runner.
4
+ * Tests data transformations and state management, not DOM rendering.
5
+ */
6
+ import { describe, it, beforeEach } from 'node:test';
7
+ import assert from 'node:assert/strict';
8
+
9
+ // Mock DOM element that tracks innerHTML, className, classList, and child appends
10
+ const createMockElement = (tag = 'div') => {
11
+ const children = [];
12
+ const classSet = new Set();
13
+ const listeners = {};
14
+ const element = {
15
+ tagName: tag,
16
+ innerHTML: '',
17
+ className: '',
18
+ children,
19
+ classList: {
20
+ add: (c) => { classSet.add(c); element.className = [...classSet].join(' '); },
21
+ remove: (c) => { classSet.delete(c); element.className = [...classSet].join(' '); },
22
+ toggle: (c, force) => {
23
+ if (force === undefined) {
24
+ if (classSet.has(c)) classSet.delete(c); else classSet.add(c);
25
+ } else if (force) {
26
+ classSet.add(c);
27
+ } else {
28
+ classSet.delete(c);
29
+ }
30
+ element.className = [...classSet].join(' ');
31
+ },
32
+ contains: (c) => classSet.has(c),
33
+ },
34
+ appendChild: (child) => { children.push(child); },
35
+ removeChild: (child) => {
36
+ const idx = children.indexOf(child);
37
+ if (idx >= 0) children.splice(idx, 1);
38
+ },
39
+ parentNode: null,
40
+ querySelector: (sel) => {
41
+ // Simple mock for querySelector — returns a mock element
42
+ return createMockElement('button');
43
+ },
44
+ addEventListener: (event, handler) => {
45
+ listeners[event] = listeners[event] || [];
46
+ listeners[event].push(handler);
47
+ },
48
+ _listeners: listeners,
49
+ };
50
+ return element;
51
+ };
52
+
53
+ // Mock document
54
+ const createMockDocument = () => {
55
+ const elements = {};
56
+ return {
57
+ createElement: (tag) => {
58
+ const el = createMockElement(tag);
59
+ return el;
60
+ },
61
+ getElementById: (id) => {
62
+ if (!elements[id]) {
63
+ elements[id] = createMockElement('div');
64
+ }
65
+ return elements[id];
66
+ },
67
+ _elements: elements,
68
+ };
69
+ };
70
+
71
+ // Mock localStorage
72
+ const createMockLocalStorage = (initial = {}) => {
73
+ const store = { ...initial };
74
+ return {
75
+ getItem: (key) => store[key] ?? null,
76
+ setItem: (key, value) => { store[key] = value; },
77
+ removeItem: (key) => { delete store[key]; },
78
+ _store: store,
79
+ };
80
+ };
81
+
82
+ // We can't import the browser module directly due to ESM/DOM dependency,
83
+ // so we test the core logic patterns that GraphLegend uses.
84
+
85
+ describe('GraphLegend data logic', () => {
86
+
87
+ describe('NODE_COLORS completeness', () => {
88
+ it('should map all known node types to colors', async () => {
89
+ // Import the constants from graph_renderer
90
+ const { NODE_COLORS, EDGE_COLORS, TYPE_LABELS } = await import('../packages/frontend/src/constants/graph.js');
91
+
92
+ const expectedNodeTypes = [
93
+ 'feature', 'component', 'data_entity', 'decision', 'tech_choice',
94
+ 'non_functional_requirement', 'design_token', 'design_pattern',
95
+ 'user_role', 'flow', 'assumption', 'open_question',
96
+ ];
97
+
98
+ for (const type of expectedNodeTypes) {
99
+ assert.ok(NODE_COLORS[type], `NODE_COLORS should have color for ${type}`);
100
+ assert.ok(TYPE_LABELS[type], `TYPE_LABELS should have label for ${type}`);
101
+ }
102
+
103
+ assert.equal(Object.keys(NODE_COLORS).length, expectedNodeTypes.length,
104
+ 'NODE_COLORS should have exactly the expected node types');
105
+ });
106
+
107
+ it('should have valid hex color values for all node types', async () => {
108
+ const { NODE_COLORS } = await import('../packages/frontend/src/constants/graph.js');
109
+ const hexPattern = /^#[0-9a-f]{6}$/i;
110
+
111
+ for (const [type, color] of Object.entries(NODE_COLORS)) {
112
+ assert.match(color, hexPattern, `${type} color should be a valid hex color`);
113
+ }
114
+ });
115
+ });
116
+
117
+ describe('EDGE_COLORS completeness', () => {
118
+ it('should map all known relation types to colors', async () => {
119
+ const { EDGE_COLORS } = await import('../packages/frontend/src/constants/graph.js');
120
+
121
+ const expectedRelations = [
122
+ 'contains', 'depends_on', 'governed_by', 'constrained_by',
123
+ 'implemented_with', 'reads_writes', 'exposes', 'consumes',
124
+ 'performed_by', 'escalates_to', 'relates_to',
125
+ ];
126
+
127
+ for (const relation of expectedRelations) {
128
+ assert.ok(EDGE_COLORS[relation], `EDGE_COLORS should have color for ${relation}`);
129
+ }
130
+
131
+ assert.equal(Object.keys(EDGE_COLORS).length, expectedRelations.length,
132
+ 'EDGE_COLORS should have exactly the expected relation types');
133
+ });
134
+
135
+ it('should have valid hex color values for all edge types', async () => {
136
+ const { EDGE_COLORS } = await import('../packages/frontend/src/constants/graph.js');
137
+ const hexPattern = /^#[0-9a-f]{6}$/i;
138
+
139
+ for (const [relation, color] of Object.entries(EDGE_COLORS)) {
140
+ assert.match(color, hexPattern, `${relation} color should be a valid hex color`);
141
+ }
142
+ });
143
+ });
144
+
145
+ describe('TYPE_LABELS consistency', () => {
146
+ it('should have a label for every NODE_COLORS entry', async () => {
147
+ const { NODE_COLORS, TYPE_LABELS } = await import('../packages/frontend/src/constants/graph.js');
148
+
149
+ for (const type of Object.keys(NODE_COLORS)) {
150
+ assert.ok(TYPE_LABELS[type], `TYPE_LABELS should have label for ${type}`);
151
+ assert.ok(TYPE_LABELS[type].length <= 4,
152
+ `TYPE_LABEL for ${type} should be max 4 chars, got "${TYPE_LABELS[type]}"`);
153
+ }
154
+ });
155
+
156
+ it('should have uppercase labels', async () => {
157
+ const { TYPE_LABELS } = await import('../packages/frontend/src/constants/graph.js');
158
+
159
+ for (const [type, label] of Object.entries(TYPE_LABELS)) {
160
+ assert.equal(label, label.toUpperCase(),
161
+ `TYPE_LABEL for ${type} should be uppercase`);
162
+ }
163
+ });
164
+ });
165
+
166
+ describe('LEGEND_LABELS human-readable names', () => {
167
+ it('should have a human-readable label for every NODE_COLORS entry', async () => {
168
+ const { NODE_COLORS, LEGEND_LABELS } = await import('../packages/frontend/src/constants/graph.js');
169
+
170
+ for (const type of Object.keys(NODE_COLORS)) {
171
+ assert.ok(LEGEND_LABELS[type], `LEGEND_LABELS should have label for ${type}`);
172
+ }
173
+
174
+ assert.equal(Object.keys(LEGEND_LABELS).length, Object.keys(NODE_COLORS).length,
175
+ 'LEGEND_LABELS should have exactly the same number of entries as NODE_COLORS');
176
+ });
177
+
178
+ it('should use the correct human-readable names', async () => {
179
+ const { LEGEND_LABELS } = await import('../packages/frontend/src/constants/graph.js');
180
+
181
+ const expected = {
182
+ feature: 'Feature',
183
+ component: 'Component',
184
+ data_entity: 'Data Entity',
185
+ decision: 'Decision',
186
+ tech_choice: 'Tech Choice',
187
+ non_functional_requirement: 'Non-Functional Req',
188
+ design_token: 'Design Token',
189
+ design_pattern: 'Design Pattern',
190
+ user_role: 'User Role',
191
+ flow: 'Flow',
192
+ assumption: 'Assumption',
193
+ open_question: 'Open Question',
194
+ };
195
+
196
+ for (const [type, label] of Object.entries(expected)) {
197
+ assert.equal(LEGEND_LABELS[type], label,
198
+ `LEGEND_LABELS[${type}] should be "${label}", got "${LEGEND_LABELS[type]}"`);
199
+ }
200
+ });
201
+
202
+ it('should not use abbreviated codes', async () => {
203
+ const { LEGEND_LABELS } = await import('../packages/frontend/src/constants/graph.js');
204
+ const abbreviations = ['FEAT', 'COMP', 'DATA', 'DEC', 'TECH', 'NFR', 'DTOK', 'DPAT', 'ROLE', 'ASMP', 'OQ'];
205
+
206
+ for (const label of Object.values(LEGEND_LABELS)) {
207
+ assert.ok(!abbreviations.includes(label),
208
+ `LEGEND_LABELS should not contain abbreviated code "${label}"`);
209
+ }
210
+ });
211
+ });
212
+
213
+ describe('localStorage edge-relations collapse state', () => {
214
+ it('should default to collapsed when no stored preference', () => {
215
+ const storage = createMockLocalStorage();
216
+ const stored = storage.getItem('legend-edges-collapsed');
217
+ // Default: collapsed (true) when no preference is stored
218
+ const edgesCollapsed = stored === null ? true : stored === 'true';
219
+ assert.equal(edgesCollapsed, true, 'edge relations should default to collapsed');
220
+ });
221
+
222
+ it('should restore expanded state from localStorage', () => {
223
+ const storage = createMockLocalStorage({ 'legend-edges-collapsed': 'false' });
224
+ const stored = storage.getItem('legend-edges-collapsed');
225
+ const edgesCollapsed = stored === null ? true : stored === 'true';
226
+ assert.equal(edgesCollapsed, false, 'edge relations should be expanded');
227
+ });
228
+
229
+ it('should restore collapsed state from localStorage', () => {
230
+ const storage = createMockLocalStorage({ 'legend-edges-collapsed': 'true' });
231
+ const stored = storage.getItem('legend-edges-collapsed');
232
+ const edgesCollapsed = stored === null ? true : stored === 'true';
233
+ assert.equal(edgesCollapsed, true, 'edge relations should be collapsed');
234
+ });
235
+
236
+ it('should persist edge-relations collapse toggle to localStorage', () => {
237
+ const storage = createMockLocalStorage();
238
+
239
+ // Simulate toggle to expanded
240
+ storage.setItem('legend-edges-collapsed', 'false');
241
+ assert.equal(storage.getItem('legend-edges-collapsed'), 'false');
242
+
243
+ // Toggle back to collapsed
244
+ storage.setItem('legend-edges-collapsed', 'true');
245
+ assert.equal(storage.getItem('legend-edges-collapsed'), 'true');
246
+ });
247
+ });
248
+
249
+ describe('hiddenTypes toggle state management', () => {
250
+ it('should start with all types visible (empty hiddenTypes set)', () => {
251
+ const hiddenTypes = new Set();
252
+ assert.equal(hiddenTypes.size, 0, 'hiddenTypes should be empty by default');
253
+ });
254
+
255
+ it('should add a type to hiddenTypes when toggled off', () => {
256
+ const hiddenTypes = new Set();
257
+ hiddenTypes.add('feature');
258
+ assert.ok(hiddenTypes.has('feature'), 'feature should be hidden');
259
+ assert.equal(hiddenTypes.size, 1);
260
+ });
261
+
262
+ it('should remove a type from hiddenTypes when toggled back on', () => {
263
+ const hiddenTypes = new Set(['feature']);
264
+ hiddenTypes.delete('feature');
265
+ assert.ok(!hiddenTypes.has('feature'), 'feature should be visible again');
266
+ assert.equal(hiddenTypes.size, 0);
267
+ });
268
+
269
+ it('should support multiple hidden types simultaneously', () => {
270
+ const hiddenTypes = new Set();
271
+ hiddenTypes.add('feature');
272
+ hiddenTypes.add('component');
273
+ hiddenTypes.add('decision');
274
+ assert.equal(hiddenTypes.size, 3);
275
+ assert.ok(hiddenTypes.has('feature'));
276
+ assert.ok(hiddenTypes.has('component'));
277
+ assert.ok(hiddenTypes.has('decision'));
278
+ });
279
+
280
+ it('should return a copy from getHiddenTypes pattern', () => {
281
+ const hiddenTypes = new Set(['feature', 'component']);
282
+ const copy = new Set(hiddenTypes);
283
+ copy.add('decision');
284
+ assert.equal(hiddenTypes.size, 2, 'original should not be modified');
285
+ assert.equal(copy.size, 3, 'copy should have the added type');
286
+ });
287
+ });
288
+
289
+ describe('EDGE_LABELS mapping', () => {
290
+ it('should have human-readable labels for all edge types', async () => {
291
+ const { EDGE_COLORS } = await import('../packages/frontend/src/constants/graph.js');
292
+
293
+ // These are the labels defined in graph_legend.js
294
+ const EDGE_LABELS = {
295
+ contains: 'contains',
296
+ depends_on: 'depends on',
297
+ governed_by: 'governed by',
298
+ constrained_by: 'constrained by',
299
+ implemented_with: 'implemented with',
300
+ reads_writes: 'reads/writes',
301
+ exposes: 'exposes',
302
+ consumes: 'consumes',
303
+ performed_by: 'performed by',
304
+ escalates_to: 'escalates to',
305
+ relates_to: 'relates to',
306
+ };
307
+
308
+ for (const relation of Object.keys(EDGE_COLORS)) {
309
+ assert.ok(EDGE_LABELS[relation],
310
+ `EDGE_LABELS should have human-readable label for ${relation}`);
311
+ }
312
+ });
313
+ });
314
+ });