@try-works/dsh-recursive-mode 0.1.11 → 0.1.13

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.
@@ -0,0 +1,566 @@
1
+ /**
2
+ * One-shot board/inspector stylesheet injection (run 15 + run 16 / Paper).
3
+ * The client bundle is a plain CJS closure with NO CSS pipeline (tsdown), so we
4
+ * inject a single <style data-dsh-recursive> element into document.head.
5
+ *
6
+ * Run 16 (Paper): the design is ported 1:1 — DEFAULT LIGHT theme, dark via a
7
+ * data-theme attribute on the .rec-board / .rec-inspector root. Raw --rm3-*
8
+ * tokens live on the scoped roots; semantic --board-* aliases remap under
9
+ * [data-theme='dark']. EVERY component rule consumes ONLY --board-* aliases
10
+ * (never --rm3-* directly). Idempotent injection; disposer removes the element.
11
+ */
12
+
13
+ const BOARD_CSS = `
14
+ /* ===== dsh-recursive Paper theme (run 16) ===== */
15
+
16
+ /* Raw tokens: light (default) + dark + shared, scoped to the board/inspector. */
17
+ .rec-board, .rec-inspector {
18
+ /* light */
19
+ --rm3-light-background: #FFFFFF;
20
+ --rm3-light-foreground: #111111;
21
+ --rm3-light-card: #FFFFFF;
22
+ --rm3-light-muted: #FAFAFA;
23
+ --rm3-light-muted-foreground: #666666;
24
+ --rm3-light-accent: #F5F5F5;
25
+ --rm3-light-border: #EAEAEA;
26
+ --rm3-light-success: #27A644;
27
+ --rm3-light-warning: #B67A11;
28
+ --rm3-light-error: #D84F6A;
29
+ --rm3-light-advisory: #9664E8;
30
+ --rm3-light-info: #3F87F5;
31
+ --rm3-light-destructive: #B4261A;
32
+ /* dark */
33
+ --rm3-background: #0A0A0A;
34
+ --rm3-foreground: #EDEDED;
35
+ --rm3-card: #0F0F0F;
36
+ --rm3-muted: #141414;
37
+ --rm3-muted-foreground: #9A9A9A;
38
+ --rm3-accent: #1A1A1A;
39
+ --rm3-border: #1F1F1F;
40
+ --rm3-success: #27A644;
41
+ --rm3-warning: #D9A441;
42
+ --rm3-error: #E06C89;
43
+ --rm3-advisory: #B479FF;
44
+ --rm3-info: #6EA8FF;
45
+ /* shared */
46
+ --rm3-font-sans: 'Geist', ui-sans-serif, system-ui, sans-serif;
47
+ --rm3-font-mono: 'Geist Mono', ui-monospace, Menlo, monospace;
48
+ --rm3-text-xs: 12px;
49
+ --rm3-text-sm: 14px;
50
+ --rm3-text-md: 16px;
51
+ --rm3-text-lg: 20px;
52
+ --rm3-text-xl: 28px;
53
+ --rm3-font-weight-regular: 400;
54
+ --rm3-font-weight-medium: 500;
55
+ --rm3-font-weight-semibold: 600;
56
+ --rm3-radius-sm: 5px;
57
+ --rm3-radius-md: 6px;
58
+ --rm3-radius-lg: 8px;
59
+ --rm3-radius-xl: 11px;
60
+ --rm3-space-8: 8px;
61
+ --rm3-space-12: 12px;
62
+ --rm3-space-16: 16px;
63
+ --rm3-space-24: 24px;
64
+ --rm3-space-32: 32px;
65
+ --rm3-tracking-tight: -0.02em;
66
+ --rm3-tracking-mono: 0.02em;
67
+
68
+ /* Semantic aliases (LIGHT default) */
69
+ --board-bg: var(--rm3-light-background);
70
+ --board-fg: var(--rm3-light-foreground);
71
+ --board-card: var(--rm3-light-card);
72
+ --board-muted: var(--rm3-light-muted);
73
+ --board-muted-fg: var(--rm3-light-muted-foreground);
74
+ --board-accent: var(--rm3-light-accent);
75
+ --board-border: var(--rm3-light-border);
76
+ --board-success: var(--rm3-light-success);
77
+ --board-warning: var(--rm3-light-warning);
78
+ --board-error: var(--rm3-light-error);
79
+ --board-advisory: var(--rm3-light-advisory);
80
+ --board-info: var(--rm3-light-info);
81
+ --board-destructive: var(--rm3-light-destructive);
82
+ --board-font-sans: var(--rm3-font-sans);
83
+ --board-font-mono: var(--rm3-font-mono);
84
+ --board-text-xs: var(--rm3-text-xs);
85
+ --board-text-sm: var(--rm3-text-sm);
86
+ --board-text-md: var(--rm3-text-md);
87
+ --board-text-lg: var(--rm3-text-lg);
88
+ --board-text-xl: var(--rm3-text-xl);
89
+ --board-fw-regular: var(--rm3-font-weight-regular);
90
+ --board-fw-medium: var(--rm3-font-weight-medium);
91
+ --board-fw-semibold: var(--rm3-font-weight-semibold);
92
+ --board-radius-sm: var(--rm3-radius-sm);
93
+ --board-radius-md: var(--rm3-radius-md);
94
+ --board-radius-lg: var(--rm3-radius-lg);
95
+ --board-radius-xl: var(--rm3-radius-xl);
96
+ --board-space-8: var(--rm3-space-8);
97
+ --board-space-12: var(--rm3-space-12);
98
+ --board-space-16: var(--rm3-space-16);
99
+ --board-space-24: var(--rm3-space-24);
100
+ --board-space-32: var(--rm3-space-32);
101
+ --board-tracking-tight: var(--rm3-tracking-tight);
102
+ --board-tracking-mono: var(--rm3-tracking-mono);
103
+ }
104
+
105
+ /* Semantic aliases remapped for DARK. */
106
+ .rec-board[data-theme='dark'], .rec-inspector[data-theme='dark'] {
107
+ --board-bg: var(--rm3-background);
108
+ --board-fg: var(--rm3-foreground);
109
+ --board-card: var(--rm3-card);
110
+ --board-muted: var(--rm3-muted);
111
+ --board-muted-fg: var(--rm3-muted-foreground);
112
+ --board-accent: var(--rm3-accent);
113
+ --board-border: var(--rm3-border);
114
+ --board-success: var(--rm3-success);
115
+ --board-warning: var(--rm3-warning);
116
+ --board-error: var(--rm3-error);
117
+ --board-advisory: var(--rm3-advisory);
118
+ --board-info: var(--rm3-info);
119
+ --board-destructive: var(--rm3-error);
120
+ }
121
+
122
+ /* ===== Board ===== */
123
+ .rec-board {
124
+ position: fixed;
125
+ inset: 0;
126
+ z-index: 9999;
127
+ display: flex;
128
+ flex-direction: column;
129
+ box-sizing: border-box;
130
+ padding: var(--board-space-16);
131
+ gap: var(--board-space-12);
132
+ background: var(--board-bg);
133
+ color: var(--board-fg);
134
+ font-family: var(--board-font-sans);
135
+ pointer-events: auto;
136
+ overflow: hidden;
137
+ }
138
+
139
+ .rec-board-header {
140
+ display: flex;
141
+ align-items: center;
142
+ gap: var(--board-space-12);
143
+ flex: none;
144
+ }
145
+
146
+ .rec-board-title {
147
+ margin: 0;
148
+ font-size: 22px;
149
+ font-weight: var(--board-fw-semibold);
150
+ letter-spacing: var(--board-tracking-tight);
151
+ color: var(--board-fg);
152
+ white-space: nowrap;
153
+ }
154
+
155
+ .rec-board-path {
156
+ flex: none;
157
+ font-family: var(--board-font-mono);
158
+ font-size: var(--board-text-xs);
159
+ letter-spacing: var(--board-tracking-mono);
160
+ color: var(--board-muted-fg);
161
+ overflow: hidden;
162
+ text-overflow: ellipsis;
163
+ white-space: nowrap;
164
+ max-width: 40%;
165
+ }
166
+
167
+ .rec-board-count {
168
+ flex: none;
169
+ font-size: var(--board-text-xs);
170
+ font-weight: var(--board-fw-medium);
171
+ color: var(--board-muted-fg);
172
+ background: var(--board-muted);
173
+ border-radius: 9999px;
174
+ padding: 4px 10px;
175
+ }
176
+
177
+ .rec-board-empty {
178
+ margin: 0;
179
+ padding: var(--board-space-32);
180
+ text-align: center;
181
+ color: var(--board-muted-fg);
182
+ }
183
+
184
+ .rec-close {
185
+ display: inline-flex;
186
+ align-items: center;
187
+ justify-content: center;
188
+ width: 28px;
189
+ height: 28px;
190
+ padding: 0;
191
+ margin-left: auto;
192
+ flex: none;
193
+ background: transparent;
194
+ border: 1px solid var(--board-border);
195
+ border-radius: var(--board-radius-md);
196
+ color: var(--board-muted-fg);
197
+ cursor: pointer;
198
+ font-size: var(--board-text-md);
199
+ line-height: 1;
200
+ }
201
+
202
+ .rec-close:hover {
203
+ background: var(--board-accent);
204
+ color: var(--board-fg);
205
+ }
206
+
207
+ .rec-theme-toggle {
208
+ display: inline-flex;
209
+ align-items: center;
210
+ justify-content: center;
211
+ width: 28px;
212
+ height: 28px;
213
+ padding: 0;
214
+ flex: none;
215
+ background: transparent;
216
+ border: 1px solid var(--board-border);
217
+ border-radius: var(--board-radius-md);
218
+ color: var(--board-muted-fg);
219
+ cursor: pointer;
220
+ font-size: var(--board-text-md);
221
+ line-height: 1;
222
+ }
223
+
224
+ .rec-theme-toggle:hover {
225
+ background: var(--board-accent);
226
+ color: var(--board-fg);
227
+ }
228
+
229
+ .rec-columns {
230
+ display: grid;
231
+ grid-auto-flow: column;
232
+ grid-auto-columns: minmax(220px, 1fr);
233
+ gap: var(--board-space-12);
234
+ flex: 1;
235
+ min-height: 0;
236
+ overflow-x: auto;
237
+ overflow-y: hidden;
238
+ overscroll-behavior-inline: contain;
239
+ padding-bottom: 6px;
240
+ scrollbar-color: var(--board-border) var(--board-accent);
241
+ scrollbar-width: thin;
242
+ }
243
+
244
+ .rec-columns::-webkit-scrollbar { height: 10px; }
245
+ .rec-columns::-webkit-scrollbar-track { background: var(--board-accent); border-radius: 999px; }
246
+ .rec-columns::-webkit-scrollbar-thumb { background: var(--board-border); border-radius: 999px; }
247
+
248
+ .rec-column {
249
+ display: flex;
250
+ flex-direction: column;
251
+ min-height: 0;
252
+ background: var(--board-muted);
253
+ border: 1px solid var(--board-border);
254
+ border-radius: var(--board-radius-xl);
255
+ overflow: hidden;
256
+ }
257
+
258
+ .rec-column-header {
259
+ display: flex;
260
+ align-items: center;
261
+ gap: 6px;
262
+ padding: var(--board-space-12);
263
+ flex: none;
264
+ }
265
+
266
+ .rec-column-title {
267
+ margin: 0;
268
+ flex: 1;
269
+ font-size: 13px;
270
+ font-weight: var(--board-fw-semibold);
271
+ color: var(--board-fg);
272
+ overflow: hidden;
273
+ text-overflow: ellipsis;
274
+ white-space: nowrap;
275
+ }
276
+
277
+ .rec-column-count {
278
+ flex: none;
279
+ font-size: var(--board-text-xs);
280
+ font-weight: var(--board-fw-medium);
281
+ color: var(--board-muted-fg);
282
+ background: var(--board-accent);
283
+ border-radius: 9999px;
284
+ padding: 1px 8px;
285
+ }
286
+
287
+ .rec-status-dot {
288
+ width: 6px;
289
+ height: 6px;
290
+ border-radius: 50%;
291
+ flex: none;
292
+ }
293
+
294
+ .rec-status-dot[data-status='new'] { background: var(--board-muted-fg); }
295
+ .rec-status-dot[data-status='active'] { background: var(--board-info); }
296
+ .rec-status-dot[data-status='paused'] { background: var(--board-warning); }
297
+ .rec-status-dot[data-status='blocked'] { background: var(--board-error); }
298
+ .rec-status-dot[data-status='complete'] { background: var(--board-success); }
299
+
300
+ .rec-cards {
301
+ display: flex;
302
+ flex-direction: column;
303
+ gap: var(--board-space-8);
304
+ padding: 10px;
305
+ overflow-y: auto;
306
+ flex: 1;
307
+ min-height: 0;
308
+ }
309
+
310
+ .rec-column-empty {
311
+ padding: var(--board-space-24) var(--board-space-8);
312
+ text-align: center;
313
+ font-size: var(--board-text-xs);
314
+ color: var(--board-muted-fg);
315
+ }
316
+
317
+ .rec-card {
318
+ display: flex;
319
+ flex-direction: column;
320
+ gap: var(--board-space-8);
321
+ padding: var(--board-space-12) 14px;
322
+ text-align: left;
323
+ background: var(--board-card);
324
+ border: 1px solid var(--board-border);
325
+ border-radius: var(--board-radius-lg);
326
+ cursor: pointer;
327
+ color: var(--board-fg);
328
+ font-family: inherit;
329
+ transition: box-shadow 120ms ease, border-color 120ms ease, transform 120ms ease;
330
+ }
331
+
332
+ .rec-card:hover {
333
+ border-color: var(--board-info);
334
+ transform: translateY(-1px);
335
+ }
336
+
337
+ .rec-card:active {
338
+ transform: translateY(0);
339
+ }
340
+
341
+ .rec-card-title {
342
+ font-size: 13px;
343
+ font-weight: var(--board-fw-semibold);
344
+ line-height: 1.35;
345
+ overflow: hidden;
346
+ display: -webkit-box;
347
+ -webkit-line-clamp: 2;
348
+ -webkit-box-orient: vertical;
349
+ }
350
+
351
+ .rec-card-progress {
352
+ height: 6px;
353
+ background: var(--board-accent);
354
+ border-radius: 3px;
355
+ overflow: hidden;
356
+ }
357
+
358
+ .rec-card-progress-fill {
359
+ height: 100%;
360
+ background: var(--board-info);
361
+ }
362
+
363
+ .rec-card-meta {
364
+ display: flex;
365
+ align-items: center;
366
+ gap: var(--board-space-8);
367
+ }
368
+
369
+ .rec-card-phase {
370
+ flex: 1;
371
+ font-family: var(--board-font-mono);
372
+ font-size: var(--board-text-xs);
373
+ letter-spacing: var(--board-tracking-mono);
374
+ color: var(--board-muted-fg);
375
+ overflow: hidden;
376
+ text-overflow: ellipsis;
377
+ white-space: nowrap;
378
+ }
379
+
380
+ /* ===== Solid status pill (Paper StatusPill) ===== */
381
+ .rec-pill {
382
+ flex: none;
383
+ border-radius: 9999px;
384
+ padding: 4px 10px;
385
+ font-size: var(--board-text-xs);
386
+ font-weight: var(--board-fw-medium);
387
+ line-height: 1;
388
+ border: none;
389
+ background: var(--board-muted-fg);
390
+ color: var(--board-bg);
391
+ }
392
+
393
+ .rec-pill[data-pill='locked'] { background: var(--board-success); color: var(--board-bg); }
394
+ .rec-pill[data-pill='in-progress'] { background: var(--board-info); color: var(--board-bg); }
395
+ .rec-pill[data-pill='paused'] { background: var(--board-warning); color: var(--board-bg); }
396
+ .rec-pill[data-pill='blocked'] { background: var(--board-error); color: var(--board-bg); }
397
+ .rec-pill[data-pill='tampered'] { background: var(--board-error); color: var(--board-bg); }
398
+ .rec-pill[data-pill='advisory'] { background: var(--board-advisory); color: var(--board-bg); }
399
+ .rec-pill[data-pill='neutral'] { background: var(--board-muted-fg); color: var(--board-bg); }
400
+
401
+ /* ===== Inspector: centered modal detail ===== */
402
+ .rec-inspector {
403
+ position: fixed;
404
+ inset: 0;
405
+ z-index: 9999;
406
+ display: flex;
407
+ align-items: center;
408
+ justify-content: center;
409
+ background: rgba(0, 0, 0, 0.4);
410
+ pointer-events: auto;
411
+ }
412
+
413
+ .rec-detail {
414
+ display: flex;
415
+ flex-direction: column;
416
+ width: min(640px, calc(100vw - 48px));
417
+ max-height: calc(100vh - 80px);
418
+ background: var(--board-bg);
419
+ border: 1px solid var(--board-border);
420
+ border-radius: 14px;
421
+ box-shadow: 0 24px 64px rgba(0, 0, 0, 0.24);
422
+ color: var(--board-fg);
423
+ font-family: var(--board-font-sans);
424
+ overflow: hidden;
425
+ }
426
+
427
+ .rec-detail-header {
428
+ display: flex;
429
+ align-items: center;
430
+ gap: var(--board-space-12);
431
+ padding: var(--board-space-16) var(--board-space-16);
432
+ border-bottom: 1px solid var(--board-border);
433
+ flex: none;
434
+ }
435
+
436
+ .rec-detail-title {
437
+ margin: 0;
438
+ flex: 1;
439
+ font-size: 15px;
440
+ font-weight: var(--board-fw-semibold);
441
+ letter-spacing: var(--board-tracking-tight);
442
+ overflow-wrap: anywhere;
443
+ }
444
+
445
+ .rec-back {
446
+ display: inline-flex;
447
+ align-items: center;
448
+ gap: 4px;
449
+ padding: 5px var(--board-space-12);
450
+ font-size: var(--board-text-xs);
451
+ color: var(--board-fg);
452
+ background: transparent;
453
+ border: 1px solid var(--board-border);
454
+ border-radius: var(--board-radius-lg);
455
+ cursor: pointer;
456
+ white-space: nowrap;
457
+ }
458
+
459
+ .rec-back:hover {
460
+ background: var(--board-accent);
461
+ }
462
+
463
+ .rec-detail-body {
464
+ padding: var(--board-space-16);
465
+ overflow-y: auto;
466
+ display: flex;
467
+ flex-direction: column;
468
+ gap: var(--board-space-16);
469
+ flex: 1;
470
+ }
471
+
472
+ .rec-detail-section {
473
+ display: flex;
474
+ flex-direction: column;
475
+ gap: 6px;
476
+ }
477
+
478
+ .rec-detail-section h3 {
479
+ margin: 0;
480
+ font-size: var(--board-text-xs);
481
+ font-weight: var(--board-fw-semibold);
482
+ color: var(--board-muted-fg);
483
+ }
484
+
485
+ .rec-detail-text {
486
+ margin: 0;
487
+ font-size: var(--board-text-sm);
488
+ color: var(--board-muted-fg);
489
+ }
490
+
491
+ .rec-phase-row {
492
+ display: flex;
493
+ align-items: center;
494
+ gap: var(--board-space-12);
495
+ padding: var(--board-space-8) var(--board-space-12);
496
+ border: 1px solid var(--board-border);
497
+ border-radius: var(--board-radius-lg);
498
+ font-size: 13px;
499
+ }
500
+
501
+ .rec-phase-id {
502
+ min-width: 48px;
503
+ font-family: var(--board-font-mono);
504
+ font-size: var(--board-text-xs);
505
+ letter-spacing: var(--board-tracking-mono);
506
+ color: var(--board-info);
507
+ }
508
+
509
+ .rec-phase-name {
510
+ flex: 1;
511
+ color: var(--board-fg);
512
+ overflow: hidden;
513
+ text-overflow: ellipsis;
514
+ white-space: nowrap;
515
+ }
516
+
517
+ .rec-lockhash {
518
+ font-family: var(--board-font-mono);
519
+ font-size: 11px;
520
+ letter-spacing: var(--board-tracking-mono);
521
+ color: var(--board-muted-fg);
522
+ }
523
+
524
+ /* ===== Strip (session dock) — keeps the shell --dsw-* theme ===== */
525
+ .rec-badge {
526
+ flex: none;
527
+ padding: 2px 10px;
528
+ font-size: 12px;
529
+ border-radius: 999px;
530
+ border: 1px solid var(--dsw-alias-border-l2);
531
+ color: var(--dsw-alias-label-secondary);
532
+ }
533
+ .rec-badge-tamper {
534
+ color: var(--dsw-alias-state-error-primary);
535
+ border-color: var(--dsw-alias-state-error-primary);
536
+ }
537
+ .rec-badge-gate {
538
+ color: var(--dsw-alias-state-warn-primary);
539
+ border-color: var(--dsw-alias-state-warn-primary);
540
+ }
541
+
542
+ @media (prefers-reduced-motion: reduce) {
543
+ .rec-card, .rec-back, .rec-close, .rec-theme-toggle { transition: none; }
544
+ }
545
+ `
546
+
547
+ /**
548
+ * Inject the board/inspector stylesheet once. Idempotent: if a <style
549
+ * data-dsh-recursive> already exists, returns a disposer that does nothing
550
+ * (the first injector owns the element). Returns a disposer that removes it.
551
+ */
552
+ export function injectBoardStyles(): () => void {
553
+ if (typeof document === 'undefined') return () => {}
554
+ const existing = document.querySelector('style[data-dsh-recursive]')
555
+ if (existing !== null) return () => {}
556
+ const style = document.createElement('style')
557
+ style.setAttribute('data-dsh-recursive', '')
558
+ style.textContent = BOARD_CSS
559
+ document.head.appendChild(style)
560
+ let disposed = false
561
+ return () => {
562
+ if (disposed) return
563
+ disposed = true
564
+ style.remove()
565
+ }
566
+ }
@@ -0,0 +1,53 @@
1
+ /**
2
+ * Board/Inspector theme state (run 16 / Paper). Default LIGHT, dark via a
3
+ * header toggle that flips data-theme on the root .rec-board / .rec-inspector.
4
+ * Persisted to localStorage under 'dsh-recursive-theme' (first visit = light).
5
+ */
6
+ import { createElement, useCallback, useEffect, useState } from 'react'
7
+
8
+ export const BOARD_THEME_STORAGE_KEY = 'dsh-recursive-theme'
9
+
10
+ export type BoardTheme = 'light' | 'dark'
11
+
12
+ export interface BoardThemeState {
13
+ theme: BoardTheme
14
+ toggle: () => void
15
+ }
16
+
17
+ function readInitialTheme(): BoardTheme {
18
+ if (typeof localStorage === 'undefined') return 'light'
19
+ try {
20
+ return localStorage.getItem(BOARD_THEME_STORAGE_KEY) === 'dark' ? 'dark' : 'light'
21
+ } catch {
22
+ return 'light'
23
+ }
24
+ }
25
+
26
+ /**
27
+ * Hoisted at the TOP of Board and Inspector (never after an early return — run
28
+ * 0.1.10 invariant). Defaults light, reads a persisted dark on init, and writes
29
+ * the chosen theme back to localStorage.
30
+ */
31
+ export function useBoardTheme(): BoardThemeState {
32
+ const [theme, setTheme] = useState<BoardTheme>(readInitialTheme)
33
+ const toggle = useCallback(() => setTheme((t) => (t === 'light' ? 'dark' : 'light')), [])
34
+ useEffect(() => {
35
+ try {
36
+ localStorage.setItem(BOARD_THEME_STORAGE_KEY, theme)
37
+ } catch {
38
+ /* storage unavailable — theme still works for the session */
39
+ }
40
+ }, [theme])
41
+ return { theme, toggle }
42
+ }
43
+
44
+ /** The shared header toggle button (moon in light, sun in dark). */
45
+ export function ThemeToggle({ theme, toggle }: BoardThemeState) {
46
+ return createElement('button', {
47
+ type: 'button',
48
+ className: 'rec-theme-toggle',
49
+ onClick: toggle,
50
+ title: theme === 'light' ? 'Switch to dark theme' : 'Switch to light theme',
51
+ 'aria-label': 'Toggle theme',
52
+ }, theme === 'light' ? '☾' : '☀')
53
+ }