@hanzlaa/rcode 3.5.0 → 3.6.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 (33) hide show
  1. package/package.json +7 -1
  2. package/server/dashboard.js +105 -3
  3. package/server/lib/html/client/agents-data.js +27 -0
  4. package/server/lib/html/client/app.js +15 -0
  5. package/server/lib/html/client/components/App.js +211 -0
  6. package/server/lib/html/client/components/OrchPanel.js +293 -0
  7. package/server/lib/html/client/components/Sidebar.js +73 -0
  8. package/server/lib/html/client/components/Topbar.js +53 -0
  9. package/server/lib/html/client/components/XtermPanel.js +220 -0
  10. package/server/lib/html/client/components/shared.js +330 -0
  11. package/server/lib/html/client/icons-client.js +85 -0
  12. package/server/lib/html/client/orchestrator.js +279 -0
  13. package/server/lib/html/client/preact.js +34 -0
  14. package/server/lib/html/client/store.js +91 -0
  15. package/server/lib/html/client/util.js +186 -0
  16. package/server/lib/html/client/views/AgentsView.js +83 -0
  17. package/server/lib/html/client/views/DecisionsView.js +102 -0
  18. package/server/lib/html/client/views/FilesView.js +223 -0
  19. package/server/lib/html/client/views/KanbanView.js +236 -0
  20. package/server/lib/html/client/views/MemoryView.js +157 -0
  21. package/server/lib/html/client/views/MilestonesView.js +136 -0
  22. package/server/lib/html/client/views/OrchestrationView.js +167 -0
  23. package/server/lib/html/client/views/OverviewView.js +221 -0
  24. package/server/lib/html/client/views/PhasesView.js +184 -0
  25. package/server/lib/html/client/views/RoadmapView.js +238 -0
  26. package/server/lib/html/client/views/SprintsView.js +178 -0
  27. package/server/lib/html/client/views/TasksView.js +148 -0
  28. package/server/lib/html/client.js +41 -1775
  29. package/server/lib/html/css.js +264 -44
  30. package/server/lib/html/icons.js +68 -0
  31. package/server/lib/html/shell.js +9 -296
  32. package/server/lib/scanner.js +76 -0
  33. package/server/orchestrator.js +237 -313
@@ -1,3 +1,4 @@
1
+ /* CLAUDE.md exemption: pure CSS data file, no logic — 1000-line limit does not apply */
1
2
  /**
2
3
  * Dashboard CSS — Linear design system.
3
4
  * Dark-first (Linear-style). Rihal accent: #5e6ad2 (Aether Blue).
@@ -62,6 +63,11 @@ function renderCss() {
62
63
  --text-sm: 14px;
63
64
  --text-xs: 13px;
64
65
  --text-2xs: 11px;
66
+ --text-stat: 28px; /* stat card value — large metric numeral */
67
+
68
+ /* Component dimensions */
69
+ --h-header-btn: 26px; /* topbar button height */
70
+ --size-icon-btn: 32px; /* square icon button (hamburger, etc.) */
65
71
 
66
72
  /* Spacing (4px base) */
67
73
  --space-1: 2px;
@@ -236,9 +242,9 @@ html, body {
236
242
  .hamburger-btn {
237
243
  display: none;
238
244
  flex-direction: column;
239
- gap: 4px;
240
- width: 32px;
241
- height: 32px;
245
+ gap: var(--space-2);
246
+ width: var(--size-icon-btn);
247
+ height: var(--size-icon-btn);
242
248
  align-items: center;
243
249
  justify-content: center;
244
250
  background: none;
@@ -261,7 +267,7 @@ html, body {
261
267
  display: none;
262
268
  position: fixed;
263
269
  inset: 0;
264
- background: rgba(0,0,0,0.5);
270
+ background: rgba(0,0,0,0.5); /* intentional: one-off overlay tint; translucency can't be expressed as a theme token */
265
271
  z-index: 15;
266
272
  }
267
273
 
@@ -276,7 +282,7 @@ html, body {
276
282
 
277
283
  /* ── Topbar / header ───────────────────────────────────────────── */
278
284
  header {
279
- background: rgba(8,9,10,0.8);
285
+ background: rgba(8,9,10,0.8); /* intentional: frosted glass tied to --bg-page exact value; alpha can't be expressed as a theme token */
280
286
  backdrop-filter: blur(12px);
281
287
  -webkit-backdrop-filter: blur(12px);
282
288
  border-bottom: 1px solid var(--border-subtle);
@@ -291,7 +297,7 @@ header {
291
297
  }
292
298
 
293
299
  [data-theme="light"] header {
294
- background: rgba(245,245,247,0.85);
300
+ background: rgba(245,245,247,0.85); /* intentional: light frosted glass; alpha channel can't be expressed as a theme token */
295
301
  }
296
302
 
297
303
  .brand {
@@ -328,6 +334,17 @@ header {
328
334
  gap: var(--space-2);
329
335
  }
330
336
 
337
+ .topbar-start-group {
338
+ display: flex;
339
+ align-items: center;
340
+ gap: var(--space-4); /* --space-4 = 12px */
341
+ }
342
+
343
+ .updated-ago {
344
+ font-size: var(--text-2xs); /* --text-2xs = 11px */
345
+ color: var(--text-muted);
346
+ }
347
+
331
348
  .live {
332
349
  width: 6px;
333
350
  height: 6px;
@@ -344,8 +361,8 @@ header {
344
361
  .header-btn {
345
362
  display: inline-flex;
346
363
  align-items: center;
347
- gap: 4px;
348
- height: 26px;
364
+ gap: var(--space-2);
365
+ height: var(--h-header-btn);
349
366
  padding: 0 var(--space-3);
350
367
  background: var(--bg-elev-2);
351
368
  border: 1px solid var(--border-default);
@@ -445,7 +462,7 @@ section .body {
445
462
  color: var(--text-muted);
446
463
  }
447
464
  .stat .value {
448
- font-size: 28px;
465
+ font-size: var(--text-stat);
449
466
  font-weight: 700;
450
467
  letter-spacing: -0.025em;
451
468
  color: var(--text-primary);
@@ -1566,6 +1583,21 @@ footer {
1566
1583
  text-align: center;
1567
1584
  }
1568
1585
 
1586
+ .orch-empty-tab {
1587
+ padding: var(--space-2) var(--space-3); /* 6px 8px via space tokens */
1588
+ font-size: var(--text-2xs); /* 11px */
1589
+ color: var(--text-muted);
1590
+ }
1591
+
1592
+ .orch-footer-spacer {
1593
+ flex: 1;
1594
+ }
1595
+
1596
+ .orch-footer-status {
1597
+ font-size: var(--text-2xs); /* 11px */
1598
+ color: var(--text-muted);
1599
+ }
1600
+
1569
1601
  /* Terminal log lines */
1570
1602
  .kt-line { color: #a0c4a0; word-break: break-word; }
1571
1603
  .kt-line.tool { color: #7cb8ff; }
@@ -1921,6 +1953,49 @@ footer {
1921
1953
  flex-direction: column;
1922
1954
  }
1923
1955
  .term-panel.open { display: flex; }
1956
+ .term-panel.fullscreen {
1957
+ inset: 0;
1958
+ left: 0;
1959
+ height: 100vh;
1960
+ }
1961
+
1962
+ /* Minimized terminal pill */
1963
+ .term-pill {
1964
+ display: none;
1965
+ position: fixed;
1966
+ bottom: var(--space-4);
1967
+ right: var(--space-4);
1968
+ z-index: 201;
1969
+ align-items: center;
1970
+ gap: var(--space-2);
1971
+ padding: var(--space-2) var(--space-4);
1972
+ background: var(--bg-elev-3);
1973
+ border: 1px solid var(--accent-primary);
1974
+ border-radius: var(--radius-4);
1975
+ color: var(--text-primary);
1976
+ font-size: var(--text-xs);
1977
+ cursor: pointer;
1978
+ box-shadow: 0 6px 20px rgba(0,0,0,0.45);
1979
+ }
1980
+ .term-pill.show { display: flex; }
1981
+ .term-pill:hover { background: var(--bg-hover); }
1982
+ .term-pill-icon { color: var(--text-muted); }
1983
+
1984
+ /* "running" badge for phase / sprint / task cards */
1985
+ .run-badge {
1986
+ display: inline-flex;
1987
+ align-items: center;
1988
+ gap: 4px;
1989
+ margin-left: var(--space-2);
1990
+ padding: 1px var(--space-2);
1991
+ background: rgba(63,185,80,0.15);
1992
+ border: 1px solid rgba(63,185,80,0.4);
1993
+ border-radius: var(--radius-2);
1994
+ color: var(--accent-green);
1995
+ font-size: var(--text-2xs);
1996
+ font-weight: 600;
1997
+ white-space: nowrap;
1998
+ }
1924
1999
  .term-header {
1925
2000
  display: flex;
1926
2001
  align-items: center;
@@ -1975,47 +2050,14 @@ footer {
1975
2050
  padding: 6px 8px;
1976
2051
  background: #0c0c0e;
1977
2052
  }
1978
- .term-input-row {
1979
- display: flex;
1980
- align-items: center;
1981
- gap: var(--space-2);
2053
+ .term-hint {
1982
2054
  padding: var(--space-2) var(--space-3);
1983
2055
  background: var(--bg-elev-2);
1984
2056
  border-top: 1px solid var(--border-subtle);
1985
- flex-shrink: 0;
1986
- }
1987
- .term-prompt {
1988
- color: var(--accent-primary);
1989
- font-family: var(--font-mono);
2057
+ color: var(--text-muted);
1990
2058
  font-size: var(--text-xs);
1991
2059
  flex-shrink: 0;
1992
2060
  }
1993
- .term-input-field {
1994
- flex: 1;
1995
- height: 26px;
1996
- background: transparent;
1997
- border: none;
1998
- color: var(--text-primary);
1999
- font-family: var(--font-mono);
2000
- font-size: var(--text-xs);
2001
- outline: none;
2002
- letter-spacing: 0;
2003
- }
2004
- .term-input-field::placeholder { color: var(--text-muted); }
2005
- .term-send-btn {
2006
- height: 24px;
2007
- padding: 0 var(--space-3);
2008
- background: var(--accent-primary);
2009
- border: none;
2010
- border-radius: var(--radius-2);
2011
- color: white;
2012
- font-size: 10px;
2013
- font-family: var(--font-mono);
2014
- cursor: pointer;
2015
- opacity: 0.85;
2016
- white-space: nowrap;
2017
- }
2018
- .term-send-btn:hover { opacity: 1; }
2019
2061
  /* Run / Terminal action buttons (used on sprint/phase detail) */
2020
2062
  .term-run-btn {
2021
2063
  display: inline-flex;
@@ -2040,12 +2082,133 @@ footer {
2040
2082
  color: var(--text-secondary);
2041
2083
  }
2042
2084
  .term-run-btn.outline:hover { background: var(--bg-hover); color: var(--text-primary); }
2085
+ .term-run-btn.danger {
2086
+ background: transparent;
2087
+ border-color: rgba(255,107,107,0.4);
2088
+ color: #ff6b6b;
2089
+ }
2090
+ .term-run-btn.danger:hover { background: rgba(255,107,107,0.12); opacity: 1; }
2043
2091
  .term-action-bar {
2044
2092
  display: flex;
2045
2093
  align-items: center;
2046
2094
  gap: var(--space-3);
2047
2095
  margin-bottom: var(--space-5);
2048
2096
  }
2097
+ .term-status-dot.exited { background: #ff4444; animation: none; }
2098
+ .term-status-dot.waiting { background: var(--accent-amber); animation: pulse 1.2s infinite; }
2099
+
2100
+ /* Compact ▶ Run button on phase / sprint / task list cards */
2101
+ .card-run-btn {
2102
+ float: right;
2103
+ margin-left: var(--space-3);
2104
+ height: 20px;
2105
+ padding: 0 var(--space-3);
2106
+ background: transparent;
2107
+ border: 1px solid var(--border-default);
2108
+ border-radius: var(--radius-2);
2109
+ color: var(--text-tertiary);
2110
+ font-size: 10px;
2111
+ font-weight: 500;
2112
+ cursor: pointer;
2113
+ transition: background var(--t-fast) var(--ease),
2114
+ color var(--t-fast) var(--ease),
2115
+ border-color var(--t-fast) var(--ease);
2116
+ }
2117
+ .card-run-btn:hover {
2118
+ background: var(--accent-green);
2119
+ border-color: var(--accent-green);
2120
+ color: #fff;
2121
+ }
2122
+ .ms-actions {
2123
+ display: inline-flex;
2124
+ align-items: center;
2125
+ gap: var(--space-2);
2126
+ margin-left: var(--space-3);
2127
+ }
2128
+ .ms-audit-btn:hover {
2129
+ background: var(--accent-primary);
2130
+ border-color: var(--accent-primary);
2131
+ }
2132
+
2133
+ /* ── Orchestration view ─────────────────────────────────────── */
2134
+ .orch-subtitle {
2135
+ color: var(--text-tertiary);
2136
+ font-size: var(--text-sm);
2137
+ margin-bottom: var(--space-5);
2138
+ }
2139
+ .orch-grid {
2140
+ display: grid;
2141
+ grid-template-columns: repeat(auto-fill, minmax(280px, 1fr));
2142
+ gap: var(--space-4);
2143
+ }
2144
+ .orch-card {
2145
+ background: var(--bg-elev-2);
2146
+ border: 1px solid var(--border-subtle);
2147
+ border-left: 3px solid var(--text-muted);
2148
+ border-radius: var(--radius-4);
2149
+ padding: var(--space-4) var(--space-5);
2150
+ display: flex;
2151
+ flex-direction: column;
2152
+ gap: var(--space-3);
2153
+ }
2154
+ .orch-card.orch-running { border-left-color: var(--accent-green); }
2155
+ .orch-card.orch-waiting {
2156
+ border-left-color: var(--accent-amber);
2157
+ background: rgba(245,158,11,0.05);
2158
+ }
2159
+ .orch-card.orch-waiting .orch-card-badge { color: var(--accent-amber); }
2160
+ .orch-card.orch-error,
2161
+ .orch-card.orch-exited { border-left-color: #ff4444; }
2162
+ .orch-card.orch-stopped { border-left-color: var(--accent-amber); }
2163
+ .orch-card.orch-done { border-left-color: var(--accent-blue); }
2164
+ .orch-card-head {
2165
+ display: flex;
2166
+ align-items: center;
2167
+ gap: var(--space-3);
2168
+ }
2169
+ .orch-card-id {
2170
+ font-weight: 600;
2171
+ font-size: var(--text-sm);
2172
+ color: var(--text-primary);
2173
+ }
2174
+ .orch-card-badge {
2175
+ margin-left: auto;
2176
+ font-size: var(--text-2xs);
2177
+ text-transform: uppercase;
2178
+ letter-spacing: 0.06em;
2179
+ color: var(--text-muted);
2180
+ }
2181
+ .orch-card-cmd {
2182
+ font-family: var(--font-mono);
2183
+ font-size: var(--text-xs);
2184
+ color: var(--text-secondary);
2185
+ background: var(--bg-elev-1);
2186
+ border-radius: var(--radius-2);
2187
+ padding: var(--space-2) var(--space-3);
2188
+ word-break: break-all;
2189
+ }
2190
+ .orch-card-meta {
2191
+ font-size: var(--text-2xs);
2192
+ color: var(--text-tertiary);
2193
+ }
2194
+ .orch-card-actions {
2195
+ display: flex;
2196
+ gap: var(--space-2);
2197
+ }
2198
+
2199
+ /* ── Icon alignment helpers (for sprint 32.2 SVG icon sweep) ── */
2200
+ .ic {
2201
+ display: inline-block;
2202
+ vertical-align: -0.15em; /* optical baseline alignment with surrounding text */
2203
+ flex-shrink: 0;
2204
+ }
2205
+ .btn-icon { display: inline-block; vertical-align: -0.1em; flex-shrink: 0; }
2206
+ .section-icon {
2207
+ display: inline-flex;
2208
+ align-items: center;
2209
+ gap: var(--space-2);
2210
+ }
2211
+ .tree-icon .ic { vertical-align: -0.15em; }
2049
2212
 
2050
2213
  /* ── Scrollbar global ───────────────────────────────────────── */
2051
2214
  ::-webkit-scrollbar { width: 6px; height: 6px; }
@@ -2053,6 +2216,63 @@ footer {
2053
2216
  ::-webkit-scrollbar-thumb { background: var(--border-strong); border-radius: 3px; }
2054
2217
  ::-webkit-scrollbar-thumb:hover { background: var(--text-muted); }
2055
2218
  * { scrollbar-width: thin; scrollbar-color: var(--border-strong) transparent; }
2219
+
2220
+ /* ── Command runner (Sprint 33.2) ───────────────────────────────────────────── */
2221
+ .cmd-runner {
2222
+ background: var(--bg-card);
2223
+ border: 1px solid var(--border);
2224
+ border-radius: var(--radius-md);
2225
+ padding: var(--space-4);
2226
+ margin-bottom: var(--space-5);
2227
+ }
2228
+ .cmd-runner-title {
2229
+ font-size: var(--text-sm);
2230
+ font-weight: 600;
2231
+ color: var(--text-muted);
2232
+ text-transform: uppercase;
2233
+ letter-spacing: 0.05em;
2234
+ margin-bottom: var(--space-3);
2235
+ display: flex;
2236
+ align-items: center;
2237
+ gap: var(--space-2);
2238
+ }
2239
+ .cmd-runner-row {
2240
+ display: flex;
2241
+ gap: var(--space-3);
2242
+ align-items: center;
2243
+ }
2244
+ .cmd-runner-select {
2245
+ flex: 1;
2246
+ background: var(--bg-input, var(--bg-elev-2));
2247
+ border: 1px solid var(--border);
2248
+ border-radius: var(--radius-sm);
2249
+ color: var(--text-primary);
2250
+ padding: var(--space-2) var(--space-3);
2251
+ font-size: var(--text-sm);
2252
+ cursor: pointer;
2253
+ }
2254
+ .cmd-runner-select:focus {
2255
+ outline: none;
2256
+ border-color: var(--accent-blue);
2257
+ }
2258
+ .cmd-runner-btn {
2259
+ background: var(--accent-blue);
2260
+ color: #fff;
2261
+ border: none;
2262
+ border-radius: var(--radius-sm);
2263
+ padding: var(--space-2) var(--space-4);
2264
+ font-size: var(--text-sm);
2265
+ font-weight: 600;
2266
+ cursor: pointer;
2267
+ display: flex;
2268
+ align-items: center;
2269
+ gap: var(--space-2);
2270
+ transition: opacity 0.15s;
2271
+ white-space: nowrap;
2272
+ }
2273
+ .cmd-runner-btn:hover:not(:disabled) { opacity: 0.85; }
2274
+ .cmd-runner-btn:disabled,
2275
+ .cmd-runner-btn--busy { opacity: 0.6; cursor: not-allowed; }
2056
2276
  </style>`;
2057
2277
  }
2058
2278
 
@@ -0,0 +1,68 @@
1
+ /**
2
+ * Inline SVG icon set (Lucide-style stroke icons).
3
+ *
4
+ * No CDN, no font — just path data rendered into a <svg>. Used server-side
5
+ * by shell.js for static chrome, and embedded as window.__ICONS__ so the
6
+ * client modules can render the same icons in dynamic markup.
7
+ *
8
+ * IMPORTANT: The ICONS map is duplicated in the ESM client counterpart:
9
+ * server/lib/html/client/icons-client.js (in sync with this file)
10
+ * Keep both files in sync when adding or changing icon paths. The duplication
11
+ * is the no-build-step cost — the browser cannot import a CJS module as ESM.
12
+ */
13
+
14
+ // name → inner SVG markup (viewBox 0 0 24 24, stroke = currentColor)
15
+ // Keep in sync with server/lib/html/client/icons-client.js
16
+ const ICONS = {
17
+ home: '<path d="m3 9 9-7 9 7v11a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2z"/><path d="M9 22V12h6v10"/>',
18
+ activity: '<path d="M22 12h-4l-3 9L9 3l-3 9H2"/>',
19
+ map: '<polygon points="3 6 9 3 15 6 21 3 21 18 15 21 9 18 3 21"/><line x1="9" y1="3" x2="9" y2="18"/><line x1="15" y1="6" x2="15" y2="21"/>',
20
+ target: '<circle cx="12" cy="12" r="10"/><circle cx="12" cy="12" r="6"/><circle cx="12" cy="12" r="2"/>',
21
+ layers: '<polygon points="12 2 2 7 12 12 22 7 12 2"/><polyline points="2 17 12 22 22 17"/><polyline points="2 12 12 17 22 12"/>',
22
+ zap: '<polygon points="13 2 3 14 12 14 11 22 21 10 12 10 13 2"/>',
23
+ checkSquare: '<polyline points="9 11 12 14 22 4"/><path d="M21 12v7a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h11"/>',
24
+ kanban: '<rect x="3" y="4" width="5" height="16" rx="1"/><rect x="10" y="4" width="5" height="11" rx="1"/><rect x="17" y="4" width="5" height="14" rx="1"/>',
25
+ file: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/>',
26
+ users: '<path d="M16 21v-2a4 4 0 0 0-4-4H6a4 4 0 0 0-4 4v2"/><circle cx="9" cy="7" r="4"/><path d="M22 21v-2a4 4 0 0 0-3-3.87"/><path d="M16 3.13a4 4 0 0 1 0 7.75"/>',
27
+ scale: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="8" y1="13" x2="16" y2="13"/><line x1="8" y1="17" x2="13" y2="17"/>',
28
+ database: '<ellipse cx="12" cy="5" rx="9" ry="3"/><path d="M3 5v14c0 1.66 4 3 9 3s9-1.34 9-3V5"/><path d="M3 12c0 1.66 4 3 9 3s9-1.34 9-3"/>',
29
+ play: '<polygon points="6 3 20 12 6 21 6 3"/>',
30
+ terminal: '<polyline points="4 17 10 11 4 5"/><line x1="12" y1="19" x2="20" y2="19"/>',
31
+ square: '<rect x="6" y="6" width="12" height="12" rx="1"/>',
32
+ x: '<line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/>',
33
+ minimize: '<line x1="5" y1="14" x2="19" y2="14"/>',
34
+ maximize: '<path d="M8 3H5a2 2 0 0 0-2 2v3"/><path d="M21 8V5a2 2 0 0 0-2-2h-3"/><path d="M3 16v3a2 2 0 0 0 2 2h3"/><path d="M16 21h3a2 2 0 0 0 2-2v-3"/>',
35
+ clock: '<circle cx="12" cy="12" r="10"/><polyline points="12 6 12 12 16 14"/>',
36
+ eye: '<path d="M2 12s3.5-7 10-7 10 7 10 7-3.5 7-10 7-10-7-10-7z"/><circle cx="12" cy="12" r="3"/>',
37
+ filePen: '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h7"/><polyline points="14 2 14 8 20 8"/><path d="M18.4 12.6a2 2 0 0 1 3 3L17 20l-4 1 1-4z"/>',
38
+ hourglass: '<path d="M5 22h14M5 2h14M17 22v-4.17a2 2 0 0 0-.59-1.42L12 12l-4.41 4.41A2 2 0 0 0 7 17.83V22M7 2v4.17a2 2 0 0 0 .59 1.42L12 12l4.41-4.41A2 2 0 0 0 17 6.17V2"/>',
39
+
40
+ // Added in sprint 32.2 — emoji-to-SVG sweep
41
+ building: '<rect x="4" y="2" width="16" height="20" rx="2"/><path d="M9 22V12h6v10"/><path d="M8 7h.01M12 7h.01M16 7h.01M8 11h.01M12 11h.01M16 11h.01"/>',
42
+ link: '<path d="M10 13a5 5 0 0 0 7.54.54l3-3a5 5 0 0 0-7.07-7.07l-1.72 1.71"/><path d="M14 11a5 5 0 0 0-7.54-.54l-3 3a5 5 0 0 0 7.07 7.07l1.71-1.71"/>',
43
+ 'alert-triangle':'<path d="m21.73 18-8-14a2 2 0 0 0-3.48 0l-8 14A2 2 0 0 0 4 21h16a2 2 0 0 0 1.73-3Z"/><line x1="12" y1="9" x2="12" y2="13"/><line x1="12" y1="17" x2="12.01" y2="17"/>',
44
+ brain: '<path d="M9.5 2A2.5 2.5 0 0 1 12 4.5v15a2.5 2.5 0 0 1-4.96-.46 2.5 2.5 0 0 1-1.28-4.56A3 3 0 0 1 5 12c0-.56.15-1.1.42-1.57a2.5 2.5 0 0 1-.42-4.93V5.5A2.5 2.5 0 0 1 9.5 2z"/><path d="M14.5 2A2.5 2.5 0 0 0 12 4.5v15a2.5 2.5 0 0 0 4.96-.46 2.5 2.5 0 0 0 1.28-4.56A3 3 0 0 0 19 12c0-.56-.15-1.1-.42-1.57a2.5 2.5 0 0 0 .42-4.93V5.5A2.5 2.5 0 0 0 14.5 2z"/>',
45
+ 'clipboard-list':'<rect x="8" y="2" width="8" height="4" rx="1" ry="1"/><path d="M16 4h2a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H6a2 2 0 0 1-2-2V6a2 2 0 0 1 2-2h2"/><line x1="12" y1="11" x2="12" y2="17"/><line x1="9" y1="14" x2="15" y2="14"/>',
46
+ flag: '<path d="M4 15s1-1 4-1 5 2 8 2 4-1 4-1V3s-1 1-4 1-5-2-8-2-4 1-4 1z"/><line x1="4" y1="22" x2="4" y2="15"/>',
47
+ monitor: '<rect x="2" y="3" width="20" height="14" rx="2" ry="2"/><line x1="8" y1="21" x2="16" y2="21"/><line x1="12" y1="17" x2="12" y2="21"/>',
48
+ 'file-text': '<path d="M14 2H6a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8z"/><polyline points="14 2 14 8 20 8"/><line x1="16" y1="13" x2="8" y2="13"/><line x1="16" y1="17" x2="8" y2="17"/><polyline points="10 9 9 9 8 9"/>',
49
+ copy: '<rect x="9" y="9" width="13" height="13" rx="2" ry="2"/><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"/>',
50
+ lightbulb: '<line x1="9" y1="18" x2="15" y2="18"/><line x1="10" y1="22" x2="14" y2="22"/><path d="M15.09 14c.18-.98.65-1.74 1.41-2.5A4.65 4.65 0 0 0 18 8 6 6 0 0 0 6 8c0 1 .23 2.23 1.5 3.5A4.61 4.61 0 0 1 8.91 14"/>',
51
+ 'edit-3': '<path d="M12 20h9"/><path d="M16.5 3.5a2.121 2.121 0 0 1 3 3L7 19l-4 1 1-4L16.5 3.5z"/>',
52
+
53
+ // Added in sprint 32.3 — App/Topbar theme toggle icons
54
+ moon: '<path d="M21 12.79A9 9 0 1 1 11.21 3 7 7 0 0 0 21 12.79z"/>',
55
+ sun: '<circle cx="12" cy="12" r="4"/><line x1="12" y1="2" x2="12" y2="6"/><line x1="12" y1="18" x2="12" y2="22"/><line x1="4.22" y1="4.22" x2="7.05" y2="7.05"/><line x1="16.95" y1="16.95" x2="19.78" y2="19.78"/><line x1="2" y1="12" x2="6" y2="12"/><line x1="18" y1="12" x2="22" y2="12"/><line x1="4.22" y1="19.78" x2="7.05" y2="16.95"/><line x1="16.95" y1="7.05" x2="19.78" y2="4.22"/>',
56
+ };
57
+
58
+ // Render an icon as an inline <svg>. size in px; cls adds extra classes.
59
+ function icon(name, size, cls) {
60
+ const p = ICONS[name];
61
+ if (!p) return '';
62
+ const s = size || 16;
63
+ return '<svg class="ic' + (cls ? ' ' + cls : '') + '" width="' + s + '" height="' + s +
64
+ '" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" ' +
65
+ 'stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' + p + '</svg>';
66
+ }
67
+
68
+ module.exports = { ICONS, icon };