@donotdev/cli 0.0.19 → 0.0.21

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 (128) hide show
  1. package/README.md +31 -0
  2. package/dependencies-matrix.json +205 -50
  3. package/dist/bin/commands/agent-setup.js +2 -2
  4. package/dist/bin/commands/build.js +6 -6
  5. package/dist/bin/commands/bump.js +495 -70
  6. package/dist/bin/commands/cacheout.js +6 -6
  7. package/dist/bin/commands/coach.js +6 -6
  8. package/dist/bin/commands/create-app.js +24 -16
  9. package/dist/bin/commands/create-project.js +114 -18
  10. package/dist/bin/commands/db.js +142136 -0
  11. package/dist/bin/commands/deploy.js +354 -126
  12. package/dist/bin/commands/dev.js +6 -6
  13. package/dist/bin/commands/doctor.js +140 -33
  14. package/dist/bin/commands/emu.js +6 -6
  15. package/dist/bin/commands/format.js +6 -6
  16. package/dist/bin/commands/get-demo.js +11 -6
  17. package/dist/bin/commands/make-admin.js +14210 -13770
  18. package/dist/bin/commands/preview.js +6 -6
  19. package/dist/bin/commands/seed.js +142426 -0
  20. package/dist/bin/commands/setup-cicd.js +8904 -0
  21. package/dist/bin/commands/setup.js +259 -212
  22. package/dist/bin/commands/staging.js +361 -127
  23. package/dist/bin/commands/sync-secrets.js +55 -33
  24. package/dist/bin/commands/type-check.js +16 -10
  25. package/dist/bin/commands/wai.js +6 -6
  26. package/dist/bin/dndev.js +194 -188
  27. package/dist/bin/donotdev.js +139 -189
  28. package/dist/index.js +468 -144
  29. package/package.json +1 -1
  30. package/templates/app-demo/.env.example +1 -0
  31. package/templates/{root-consumer → app-demo}/entities/ExampleEntity.ts.example +15 -9
  32. package/templates/app-demo/index.html.example +1 -1
  33. package/templates/app-demo/public/apple-touch-icon.png.example +0 -0
  34. package/templates/app-demo/public/favicon.svg.example +1 -0
  35. package/templates/app-demo/public/icon-192x192.png.example +0 -0
  36. package/templates/app-demo/public/icon-512x512.png.example +0 -0
  37. package/templates/app-demo/src/App.tsx.example +3 -1
  38. package/templates/app-demo/src/config/app.ts.example +1 -0
  39. package/templates/app-demo/src/entities/booking.ts.example +75 -0
  40. package/templates/app-demo/src/entities/onboarding.ts.example +160 -0
  41. package/templates/app-demo/src/entities/product.ts.example +12 -0
  42. package/templates/app-demo/src/entities/quote.ts.example +70 -0
  43. package/templates/app-demo/src/pages/ChangelogPage.tsx.example +28 -1
  44. package/templates/app-demo/src/pages/ConditionalFormPage.tsx.example +88 -0
  45. package/templates/app-demo/src/pages/DashboardPage.tsx.example +2 -0
  46. package/templates/app-demo/src/pages/HomePage.tsx.example +355 -2
  47. package/templates/app-demo/src/pages/OnboardingPage.tsx.example +47 -0
  48. package/templates/app-demo/src/pages/PricingPage.tsx.example +28 -1
  49. package/templates/app-demo/src/pages/ProductsPage.tsx.example +2 -0
  50. package/templates/app-demo/src/pages/ProfilePage.tsx.example +2 -0
  51. package/templates/app-demo/src/pages/SettingsPage.tsx.example +2 -0
  52. package/templates/app-demo/src/pages/ShowcaseDetailPage.tsx.example +22 -16
  53. package/templates/app-demo/src/pages/ShowcasePage.tsx.example +3 -1
  54. package/templates/app-demo/src/pages/components/ComponentRenderer.tsx.example +147 -51
  55. package/templates/app-demo/src/pages/components/ComponentsData.tsx.example +103 -21
  56. package/templates/app-demo/src/pages/components/componentConfig.ts.example +139 -59
  57. package/templates/app-demo/src/pages/legal/LegalPage.tsx.example +12 -1
  58. package/templates/app-demo/src/pages/legal/PrivacyPage.tsx.example +10 -1
  59. package/templates/app-demo/src/pages/legal/TermsPage.tsx.example +10 -1
  60. package/templates/app-demo/src/themes.css.example +289 -77
  61. package/templates/app-demo/stats.html.example +4949 -0
  62. package/templates/app-dndev/index.html.example +164 -0
  63. package/templates/app-dndev/public/logo.svg.example +1 -0
  64. package/templates/app-dndev/public/manifest.json.example +10 -0
  65. package/templates/app-dndev/src/App.tsx.example +35 -0
  66. package/templates/app-dndev/src/components/CockpitLayout.css.example +181 -0
  67. package/templates/app-dndev/src/components/CockpitLayout.tsx.example +209 -0
  68. package/templates/app-dndev/src/components/Kanban.css.example +385 -0
  69. package/templates/app-dndev/src/components/ModeToggle.tsx.example +32 -0
  70. package/templates/app-dndev/src/components/OverlaySlot.tsx.example +68 -0
  71. package/templates/app-dndev/src/components/TerminalPanel.css.example +228 -0
  72. package/templates/app-dndev/src/components/TerminalPanel.tsx.example +714 -0
  73. package/templates/app-dndev/src/components/markdown-prose.css.example +49 -0
  74. package/templates/app-dndev/src/components/phases/CaptainLog.tsx.example +107 -0
  75. package/templates/app-dndev/src/components/phases/ContextTabs.tsx.example +352 -0
  76. package/templates/app-dndev/src/components/phases/PhaseCard.tsx.example +126 -0
  77. package/templates/app-dndev/src/components/phases/PhaseDetail.tsx.example +147 -0
  78. package/templates/app-dndev/src/components/phases/ReviewPanel.tsx.example +115 -0
  79. package/templates/app-dndev/src/components/phases/phaseData.ts.example +366 -0
  80. package/templates/app-dndev/src/config/app.ts.example +103 -0
  81. package/templates/app-dndev/src/config/commands.ts.example +171 -0
  82. package/templates/app-dndev/src/config/legal.ts.example +170 -0
  83. package/templates/app-dndev/src/config/providers.ts.example +7 -0
  84. package/templates/app-dndev/src/globals.css.example +10 -0
  85. package/templates/app-dndev/src/hooks/useDndevFile.ts.example +144 -0
  86. package/templates/app-dndev/src/main.tsx.example +21 -0
  87. package/templates/app-dndev/src/pages/BoardPage.tsx.example +640 -0
  88. package/templates/app-dndev/src/pages/GrillPage.tsx.example +658 -0
  89. package/templates/app-dndev/src/pages/HomePage.tsx.example +347 -0
  90. package/templates/app-dndev/src/pages/NotFoundPage.tsx.example +33 -0
  91. package/templates/app-dndev/src/pages/PhasesPage.tsx.example +137 -0
  92. package/templates/app-dndev/src/pages/SettingsPage.tsx.example +64 -0
  93. package/templates/app-dndev/src/pages/legal/LegalNoticePage.tsx.example +75 -0
  94. package/templates/app-dndev/src/pages/legal/PrivacyPage.tsx.example +69 -0
  95. package/templates/app-dndev/src/pages/legal/TermsPage.tsx.example +71 -0
  96. package/templates/app-dndev/src/stores/dndevStore.ts.example +386 -0
  97. package/templates/app-dndev/src/themes.css.example +161 -0
  98. package/templates/app-dndev/terminal-sidecar.cjs.example +341 -0
  99. package/templates/app-dndev/tsconfig.json.example +9 -0
  100. package/templates/app-dndev/vite.config.ts.example +24 -0
  101. package/templates/app-vite/index.html.example +1 -1
  102. package/templates/functions-supabase/supabase/functions/.env.example +0 -2
  103. package/templates/root-consumer/.claude/commands/grill.md.example +86 -8
  104. package/templates/root-consumer/.dndev.secrets.example +32 -0
  105. package/templates/root-consumer/.gitignore.example +3 -0
  106. package/templates/root-consumer/AI.md.example +4 -0
  107. package/templates/root-consumer/entities/index.ts.example +2 -5
  108. package/templates/root-consumer/guides/dndev/COMPONENTS_ATOMIC.md.example +4 -0
  109. package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +23 -20
  110. package/templates/root-consumer/guides/dndev/INDEX.md.example +1 -0
  111. package/templates/root-consumer/guides/dndev/SETUP_BILLING.md.example +3 -7
  112. package/templates/root-consumer/guides/dndev/SETUP_CICD.md.example +115 -0
  113. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +41 -0
  114. package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +13 -18
  115. package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +17 -12
  116. package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +185 -251
  117. package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +26 -8
  118. package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +66 -49
  119. package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +6 -5
  120. package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +9 -9
  121. package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +1 -1
  122. package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +7 -6
  123. package/templates/root-consumer/guides/wai-way/context_map.json.example +51 -20
  124. package/templates/root-consumer/guides/wai-way/hld_template.md.example +138 -0
  125. package/templates/root-consumer/guides/wai-way/lld_template.md.example +103 -0
  126. package/templates/root-consumer/guides/wai-way/prd_template.md.example +140 -0
  127. /package/templates/{root-consumer → app-demo}/entities/Contact.ts.example +0 -0
  128. /package/templates/{root-consumer → app-demo}/entities/demo.ts.example +0 -0
@@ -0,0 +1,385 @@
1
+ /* ===========================
2
+ KANBAN — Board + Cards + Sheet
3
+ =========================== */
4
+
5
+ /* --- Board grid --- */
6
+
7
+ .dndev-kanban-board {
8
+ display: grid;
9
+ grid-template-columns: repeat(4, 1fr);
10
+ gap: 0;
11
+ border: 1px solid var(--border);
12
+ border-radius: var(--radius-surface, 8px);
13
+ overflow: hidden;
14
+ background: var(--card);
15
+ flex: 1;
16
+ min-height: 100dvh;
17
+ }
18
+
19
+ @media (width < 900px) {
20
+ .dndev-kanban-board { grid-template-columns: repeat(2, 1fr); }
21
+ }
22
+
23
+ @media (width < 540px) {
24
+ .dndev-kanban-board { grid-template-columns: 1fr; }
25
+ }
26
+
27
+ /* --- Column --- */
28
+
29
+ .dndev-kanban-column {
30
+ width: 100%;
31
+ min-width: 0;
32
+ display: flex;
33
+ flex-direction: column;
34
+ padding: 0;
35
+ }
36
+
37
+ .dndev-kanban-column + .dndev-kanban-column {
38
+ border-inline-start: 1px dashed var(--border);
39
+ }
40
+
41
+ @media (width < 900px) {
42
+ .dndev-kanban-column:nth-child(3),
43
+ .dndev-kanban-column:nth-child(4) { border-top: 1px dashed var(--border); }
44
+ .dndev-kanban-column:nth-child(3) { border-inline-start: none; }
45
+ }
46
+
47
+ @media (width < 540px) {
48
+ .dndev-kanban-column + .dndev-kanban-column {
49
+ border-inline-start: none;
50
+ border-top: 1px dashed var(--border);
51
+ }
52
+ }
53
+
54
+ .dndev-kanban-column[data-column='backlog'] { background: color-mix(in oklch, var(--muted) 40%, transparent); }
55
+ .dndev-kanban-column[data-column='in_progress'] { background: color-mix(in oklch, var(--warning, #f59e0b) 4%, transparent); }
56
+ .dndev-kanban-column[data-column='review'] { background: color-mix(in oklch, var(--primary) 4%, transparent); }
57
+ .dndev-kanban-column[data-column='done'] { background: color-mix(in oklch, var(--success, #22c55e) 4%, transparent); }
58
+
59
+ /* Column header */
60
+ .dndev-kanban-col-header {
61
+ display: flex;
62
+ align-items: center;
63
+ justify-content: space-between;
64
+ padding: 10px 12px;
65
+ border-bottom: 1px solid var(--border);
66
+ }
67
+
68
+ .dndev-kanban-col-title {
69
+ display: flex;
70
+ align-items: center;
71
+ gap: 8px;
72
+ }
73
+
74
+ .dndev-kanban-col-dot {
75
+ width: 8px;
76
+ height: 8px;
77
+ border-radius: 50%;
78
+ flex-shrink: 0;
79
+ }
80
+
81
+ .dndev-kanban-column[data-column='backlog'] .dndev-kanban-col-dot { background: var(--muted-foreground); }
82
+ .dndev-kanban-column[data-column='in_progress'] .dndev-kanban-col-dot { background: var(--warning, #f59e0b); }
83
+ .dndev-kanban-column[data-column='review'] .dndev-kanban-col-dot { background: var(--primary); }
84
+ .dndev-kanban-column[data-column='done'] .dndev-kanban-col-dot { background: var(--success, #22c55e); }
85
+
86
+ /* Cards area */
87
+ .dndev-kanban-cards {
88
+ flex: 1;
89
+ display: flex;
90
+ flex-direction: column;
91
+ gap: 4px;
92
+ padding: 6px;
93
+ min-height: 80px;
94
+ overflow-y: auto;
95
+ }
96
+
97
+ .dndev-kanban-empty {
98
+ display: flex;
99
+ align-items: start;
100
+ justify-content: center;
101
+ flex: 1;
102
+ min-height: 80px;
103
+ border: 1px dashed var(--border);
104
+ border-radius: 6px;
105
+ opacity: 0.5;
106
+ }
107
+
108
+ /* Show more button */
109
+ .dndev-kanban-show-more {
110
+ display: flex;
111
+ align-items: center;
112
+ justify-content: center;
113
+ gap: 6px;
114
+ padding: 8px;
115
+ border: none;
116
+ border-top: 1px dashed var(--border);
117
+ background: transparent;
118
+ color: var(--muted-foreground);
119
+ font-size: 12px;
120
+ font-weight: 500;
121
+ font-family: inherit;
122
+ cursor: pointer;
123
+ transition: color var(--dur-fast, 100ms);
124
+ }
125
+
126
+ .dndev-kanban-show-more:hover {
127
+ color: var(--foreground);
128
+ background: color-mix(in oklch, var(--muted) 30%, transparent);
129
+ }
130
+
131
+ /* ===========================
132
+ KANBAN CARD — compact, clickable
133
+ =========================== */
134
+
135
+ .dndev-kanban-card {
136
+ cursor: pointer;
137
+ }
138
+
139
+ .dndev-kanban-card-inner {
140
+ display: flex;
141
+ align-items: center;
142
+ gap: 6px;
143
+ padding: 6px 8px;
144
+ border-radius: 6px;
145
+ border-inline-start: 3px solid var(--muted-foreground);
146
+ background: var(--background);
147
+ border-top: 1px solid color-mix(in oklch, var(--border) 50%, transparent);
148
+ border-bottom: 1px solid color-mix(in oklch, var(--border) 50%, transparent);
149
+ border-inline-end: 1px solid color-mix(in oklch, var(--border) 50%, transparent);
150
+ transition: background var(--dur-fast, 100ms);
151
+ }
152
+
153
+ .dndev-kanban-card:hover .dndev-kanban-card-inner {
154
+ background: color-mix(in oklch, var(--muted) 40%, var(--background));
155
+ }
156
+
157
+ .dndev-kanban-card-body {
158
+ flex: 1;
159
+ min-width: 0;
160
+ }
161
+
162
+ .dndev-kanban-card-title {
163
+ font-size: 12px;
164
+ font-weight: 500;
165
+ line-height: 1.4;
166
+ color: var(--foreground);
167
+ display: -webkit-box;
168
+ -webkit-line-clamp: 2;
169
+ -webkit-box-orient: vertical;
170
+ overflow: hidden;
171
+ }
172
+
173
+ .dndev-kanban-card-meta {
174
+ font-size: 10px;
175
+ font-family: var(--font-mono, monospace);
176
+ color: var(--muted-foreground);
177
+ white-space: nowrap;
178
+ overflow: hidden;
179
+ text-overflow: ellipsis;
180
+ margin-top: 2px;
181
+ }
182
+
183
+ .dndev-kanban-card-overlay {
184
+ opacity: 0.9;
185
+ transform: rotate(2deg);
186
+ filter: drop-shadow(0 4px 12px rgba(0, 0, 0, 0.15));
187
+ }
188
+
189
+ /* Grip handle */
190
+ .dndev-kanban-grip {
191
+ display: flex;
192
+ align-items: center;
193
+ justify-content: center;
194
+ width: 16px;
195
+ min-height: 16px;
196
+ padding: 0;
197
+ border: none;
198
+ background: transparent;
199
+ color: var(--muted-foreground);
200
+ cursor: grab;
201
+ flex-shrink: 0;
202
+ border-radius: 3px;
203
+ opacity: 0;
204
+ transition: opacity var(--dur-fast, 100ms);
205
+ }
206
+
207
+ .dndev-kanban-card:hover .dndev-kanban-grip { opacity: 1; }
208
+ .dndev-kanban-grip:active { cursor: grabbing; }
209
+
210
+ /* Add button */
211
+ .dndev-kanban-add {
212
+ display: flex;
213
+ align-items: center;
214
+ justify-content: center;
215
+ width: 24px;
216
+ height: 24px;
217
+ padding: 0;
218
+ border: 1px dashed var(--border);
219
+ background: transparent;
220
+ color: var(--muted-foreground);
221
+ cursor: pointer;
222
+ border-radius: 4px;
223
+ transition: all var(--dur-fast, 100ms);
224
+ }
225
+
226
+ .dndev-kanban-add:hover {
227
+ color: var(--foreground);
228
+ border-color: var(--foreground);
229
+ border-style: solid;
230
+ background: var(--muted, rgba(128, 128, 128, 0.1));
231
+ }
232
+
233
+ /* ===========================
234
+ CARD DETAIL SHEET — slide from right
235
+ =========================== */
236
+
237
+ .dndev-sheet-overlay {
238
+ position: fixed;
239
+ inset: 0;
240
+ z-index: 150;
241
+ background: rgba(0, 0, 0, 0.4);
242
+ backdrop-filter: blur(2px);
243
+ display: flex;
244
+ justify-content: flex-end;
245
+ }
246
+
247
+ .dndev-sheet {
248
+ width: min(440px, 90vw);
249
+ height: 100%;
250
+ background: var(--card);
251
+ border-inline-start: 1px solid var(--border);
252
+ display: flex;
253
+ flex-direction: column;
254
+ animation: dndev-sheet-in 200ms ease;
255
+ }
256
+
257
+ @keyframes dndev-sheet-in {
258
+ from { transform: translateX(100%); }
259
+ to { transform: translateX(0); }
260
+ }
261
+
262
+ .dndev-sheet-header {
263
+ display: flex;
264
+ align-items: center;
265
+ justify-content: space-between;
266
+ padding: 16px 20px;
267
+ border-bottom: 1px solid var(--border);
268
+ }
269
+
270
+ .dndev-sheet-close {
271
+ display: flex;
272
+ align-items: center;
273
+ justify-content: center;
274
+ width: 28px;
275
+ height: 28px;
276
+ padding: 0;
277
+ border: 1px solid var(--border);
278
+ border-radius: 6px;
279
+ background: transparent;
280
+ color: var(--muted-foreground);
281
+ cursor: pointer;
282
+ transition: all var(--dur-fast, 100ms);
283
+ }
284
+
285
+ .dndev-sheet-close:hover {
286
+ background: var(--muted);
287
+ color: var(--foreground);
288
+ }
289
+
290
+ .dndev-sheet-body {
291
+ flex: 1;
292
+ overflow-y: auto;
293
+ padding: 20px;
294
+ display: flex;
295
+ flex-direction: column;
296
+ gap: 16px;
297
+ }
298
+
299
+ .dndev-sheet-field {
300
+ display: flex;
301
+ flex-direction: column;
302
+ gap: 6px;
303
+ }
304
+
305
+ .dndev-sheet-input {
306
+ padding: 8px 12px;
307
+ border: 1px solid var(--border);
308
+ border-radius: var(--radius-surface, 8px);
309
+ background: var(--background);
310
+ color: var(--foreground);
311
+ font-size: 14px;
312
+ font-family: inherit;
313
+ outline: none;
314
+ transition: border-color var(--dur-fast, 100ms);
315
+ }
316
+
317
+ .dndev-sheet-input:focus {
318
+ border-color: var(--primary);
319
+ }
320
+
321
+ .dndev-sheet-textarea {
322
+ padding: 8px 12px;
323
+ border: 1px solid var(--border);
324
+ border-radius: var(--radius-surface, 8px);
325
+ background: var(--background);
326
+ color: var(--foreground);
327
+ font-size: 14px;
328
+ font-family: inherit;
329
+ outline: none;
330
+ resize: vertical;
331
+ min-height: 80px;
332
+ transition: border-color var(--dur-fast, 100ms);
333
+ }
334
+
335
+ .dndev-sheet-textarea:focus {
336
+ border-color: var(--primary);
337
+ }
338
+
339
+ .dndev-sheet-option {
340
+ display: flex;
341
+ align-items: center;
342
+ gap: 6px;
343
+ padding: 4px 10px;
344
+ border: 1px solid var(--border);
345
+ border-radius: 999px;
346
+ background: transparent;
347
+ color: var(--muted-foreground);
348
+ font-size: 12px;
349
+ font-weight: 500;
350
+ font-family: inherit;
351
+ cursor: pointer;
352
+ transition: all var(--dur-fast, 100ms);
353
+ }
354
+
355
+ .dndev-sheet-option[data-active='true'] {
356
+ background: var(--foreground);
357
+ color: var(--background);
358
+ border-color: var(--foreground);
359
+ }
360
+
361
+ .dndev-sheet-option-dot {
362
+ width: 8px;
363
+ height: 8px;
364
+ border-radius: 50%;
365
+ flex-shrink: 0;
366
+ }
367
+
368
+ .dndev-sheet-footer {
369
+ display: flex;
370
+ align-items: center;
371
+ gap: 8px;
372
+ padding: 16px 20px;
373
+ border-top: 1px solid var(--border);
374
+ }
375
+
376
+ /* ===========================
377
+ color-mix FALLBACKS
378
+ =========================== */
379
+
380
+ @supports not (background: color-mix(in oklch, red 50%, blue)) {
381
+ .dndev-kanban-column[data-column='backlog'] { background: rgba(128, 128, 128, 0.06); }
382
+ .dndev-kanban-column[data-column='in_progress'] { background: rgba(245, 158, 11, 0.04); }
383
+ .dndev-kanban-column[data-column='review'] { background: rgba(59, 130, 246, 0.04); }
384
+ .dndev-kanban-column[data-column='done'] { background: rgba(34, 197, 94, 0.04); }
385
+ }
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @fileoverview BUILD / RUN mode toggle for the dashboard header
3
+ */
4
+
5
+ import { memo } from 'react';
6
+ import { Hammer, Zap } from 'lucide-react';
7
+
8
+ import { ToggleGroup } from '@donotdev/components';
9
+
10
+ import { useDoNotDashStore } from '../stores/dndevStore';
11
+
12
+ import type { ReactNode } from 'react';
13
+
14
+ function ModeToggleComponent(): ReactNode {
15
+ const mode = useDoNotDashStore((s) => s.mode);
16
+ const toggleMode = useDoNotDashStore((s) => s.toggleMode);
17
+
18
+ return (
19
+ <ToggleGroup
20
+ type="single"
21
+ size="sm"
22
+ value={mode}
23
+ onValueChange={(v) => { if (v) toggleMode(); }}
24
+ items={[
25
+ { value: 'build', label: <><Hammer size={12} /> BUILD</> },
26
+ { value: 'run', label: <><Zap size={12} /> RUN</> },
27
+ ]}
28
+ />
29
+ );
30
+ }
31
+
32
+ export const ModeToggle = memo(ModeToggleComponent);
@@ -0,0 +1,68 @@
1
+ /**
2
+ * @fileoverview Overlay center slot — lets pages inject content into CockpitLayout's overlay bar
3
+ *
4
+ * CockpitLayout renders OverlaySlotTarget (a DOM node). Pages render OverlaySlotPortal
5
+ * which portals its children into that DOM node. No state loops — pure React portals.
6
+ */
7
+
8
+ import { createContext, useContext, useRef, useState, useEffect } from 'react';
9
+ import { createPortal } from 'react-dom';
10
+
11
+ import type { ReactNode } from 'react';
12
+
13
+ // ============================================================================
14
+ // CONTEXT — shares the target DOM element
15
+ // ============================================================================
16
+
17
+ const OverlaySlotContext = createContext<HTMLDivElement | null>(null);
18
+
19
+ // ============================================================================
20
+ // PROVIDER (used by CockpitLayout)
21
+ // ============================================================================
22
+
23
+ export function OverlaySlotProvider({ children }: { children: ReactNode }) {
24
+ const [target, setTarget] = useState<HTMLDivElement | null>(null);
25
+
26
+ return (
27
+ <OverlaySlotContext.Provider value={target}>
28
+ {children}
29
+ {/* Hidden mount point — CockpitLayout moves this into the overlay bar */}
30
+ <div ref={setTarget} style={{ display: 'contents' }} />
31
+ </OverlaySlotContext.Provider>
32
+ );
33
+ }
34
+
35
+ // ============================================================================
36
+ // TARGET (rendered by CockpitLayout in the overlay bar)
37
+ // ============================================================================
38
+
39
+ /** Renders the overlay slot target where portaled content appears */
40
+ export function OverlaySlotTarget() {
41
+ const ref = useRef<HTMLDivElement>(null);
42
+ const target = useContext(OverlaySlotContext);
43
+
44
+ // Move the provider's mount point into this target element
45
+ useEffect(() => {
46
+ if (!ref.current || !target) return;
47
+ ref.current.appendChild(target);
48
+ return () => {
49
+ // Move back to avoid orphan
50
+ if (ref.current && target.parentNode === ref.current) {
51
+ ref.current.removeChild(target);
52
+ }
53
+ };
54
+ }, [target]);
55
+
56
+ return <div ref={ref} className="dndev-cockpit-overlays-center" />;
57
+ }
58
+
59
+ // ============================================================================
60
+ // PORTAL (used by pages to inject content)
61
+ // ============================================================================
62
+
63
+ /** Portal children into the overlay center slot */
64
+ export function OverlaySlotPortal({ children }: { children: ReactNode }) {
65
+ const target = useContext(OverlaySlotContext);
66
+ if (!target) return null;
67
+ return createPortal(children, target);
68
+ }
@@ -0,0 +1,228 @@
1
+ /* ===========================
2
+ TERMINAL PANEL
3
+ When embedded: fills its parent flex container (no fixed positioning)
4
+ When standalone (collapsed bar in bottom-bar layout): minimal tabbar only
5
+ =========================== */
6
+
7
+ .dndev-terminal-panel {
8
+ display: flex;
9
+ flex-direction: column;
10
+ background: var(--card);
11
+ contain: layout style;
12
+ min-height: 0;
13
+ }
14
+
15
+ /* Embedded mode — fills parent panel */
16
+ .dndev-terminal-panel[data-embedded] {
17
+ flex: 1;
18
+ height: 100%;
19
+ border: none;
20
+ }
21
+
22
+ /* ===========================
23
+ TAB BAR
24
+ =========================== */
25
+
26
+ .dndev-terminal-tabbar {
27
+ display: flex;
28
+ align-items: center;
29
+ justify-content: space-between;
30
+ height: 34px;
31
+ min-height: 34px;
32
+ padding-inline: 8px;
33
+ background: var(--muted);
34
+ border-bottom: 1px solid var(--border);
35
+ flex-shrink: 0;
36
+ gap: 4px;
37
+ }
38
+
39
+ .dndev-terminal-tabs {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 2px;
43
+ overflow: visible;
44
+ scrollbar-width: none;
45
+ }
46
+
47
+ .dndev-terminal-tabs::-webkit-scrollbar {
48
+ display: none;
49
+ }
50
+
51
+ .dndev-terminal-tab {
52
+ display: flex;
53
+ align-items: center;
54
+ gap: 6px;
55
+ padding: 4px 10px;
56
+ border: none;
57
+ background: transparent;
58
+ color: var(--muted-foreground);
59
+ font-size: 12px;
60
+ font-family: inherit;
61
+ cursor: pointer;
62
+ border-radius: 4px;
63
+ transition: color var(--dur-fast, 100ms), background var(--dur-fast, 100ms);
64
+ line-height: 1;
65
+ }
66
+
67
+ .dndev-terminal-tab:hover {
68
+ color: var(--foreground);
69
+ background: var(--background);
70
+ }
71
+
72
+ .dndev-terminal-tab[data-active='true'] {
73
+ color: var(--foreground);
74
+ background: var(--background);
75
+ font-weight: 500;
76
+ }
77
+
78
+ .dndev-terminal-toggle {
79
+ display: flex;
80
+ align-items: center;
81
+ justify-content: center;
82
+ width: 24px;
83
+ height: 24px;
84
+ border: none;
85
+ background: transparent;
86
+ color: var(--muted-foreground);
87
+ cursor: pointer;
88
+ border-radius: 4px;
89
+ transition: color var(--dur-fast, 100ms), background var(--dur-fast, 100ms);
90
+ }
91
+
92
+ .dndev-terminal-toggle:hover {
93
+ color: var(--foreground);
94
+ background: var(--background);
95
+ }
96
+
97
+ .dndev-terminal-actions {
98
+ display: flex;
99
+ align-items: center;
100
+ gap: 2px;
101
+ }
102
+
103
+ /* Add tab button */
104
+ .dndev-terminal-tab-add {
105
+ display: flex;
106
+ align-items: center;
107
+ justify-content: center;
108
+ width: 24px;
109
+ height: 24px;
110
+ border: none;
111
+ background: transparent;
112
+ color: var(--muted-foreground);
113
+ cursor: pointer;
114
+ border-radius: 4px;
115
+ margin-inline-start: 2px;
116
+ transition: color var(--dur-fast, 100ms), background var(--dur-fast, 100ms);
117
+ }
118
+
119
+ .dndev-terminal-tab-add:hover {
120
+ color: var(--foreground);
121
+ background: var(--background);
122
+ }
123
+
124
+ /* Tab close / kill button */
125
+ .dndev-terminal-tab-close {
126
+ display: inline-flex;
127
+ align-items: center;
128
+ justify-content: center;
129
+ width: 14px;
130
+ height: 14px;
131
+ margin-inline-start: 2px;
132
+ border-radius: 3px;
133
+ color: var(--muted-foreground);
134
+ cursor: pointer;
135
+ opacity: 0;
136
+ transition: opacity var(--dur-fast, 100ms), color var(--dur-fast, 100ms), background var(--dur-fast, 100ms);
137
+ }
138
+
139
+ .dndev-terminal-tab:hover .dndev-terminal-tab-close {
140
+ opacity: 1;
141
+ }
142
+
143
+ .dndev-terminal-tab-close:hover {
144
+ color: var(--destructive);
145
+ background: color-mix(in oklch, var(--destructive) 15%, transparent);
146
+ }
147
+
148
+ /* ===========================
149
+ TERMINAL CONTENT — xterm container
150
+ =========================== */
151
+
152
+ .dndev-terminal-content {
153
+ flex: 1;
154
+ min-height: 0;
155
+ overflow: hidden;
156
+ background: #0a0a0a;
157
+ position: relative;
158
+ }
159
+
160
+ /* xterm container — fills content area */
161
+ .dndev-terminal-xterm-host {
162
+ position: absolute;
163
+ inset: 0;
164
+ overflow: hidden;
165
+ }
166
+
167
+ /* Per-tab xterm wrapper — created dynamically in JS */
168
+ .dndev-terminal-tab-pane {
169
+ width: 100%;
170
+ height: 100%;
171
+ }
172
+
173
+ /* ===========================
174
+ XTERM OVERRIDES
175
+ =========================== */
176
+
177
+ .dndev-terminal-content .xterm {
178
+ height: 100%;
179
+ padding: 4px;
180
+ }
181
+
182
+ .dndev-terminal-content .xterm-viewport {
183
+ overflow-y: auto !important;
184
+ }
185
+
186
+ /* ===========================
187
+ APP PICKER DROPDOWN
188
+ =========================== */
189
+
190
+ .dndev-terminal-app-picker {
191
+ position: absolute;
192
+ top: 100%;
193
+ left: 0;
194
+ margin-top: 4px;
195
+ min-width: 160px;
196
+ background: var(--popover, var(--card));
197
+ border: 1px solid var(--border);
198
+ border-radius: 6px;
199
+ padding: 4px;
200
+ z-index: 50;
201
+ box-shadow: 0 4px 12px rgba(0, 0, 0, 0.3);
202
+ }
203
+
204
+ .dndev-terminal-app-picker-empty {
205
+ padding: 8px 12px;
206
+ font-size: 12px;
207
+ color: var(--muted-foreground);
208
+ }
209
+
210
+ .dndev-terminal-app-picker-item {
211
+ display: flex;
212
+ align-items: center;
213
+ gap: 8px;
214
+ width: 100%;
215
+ padding: 6px 10px;
216
+ border: none;
217
+ background: transparent;
218
+ color: var(--foreground);
219
+ font-size: 12px;
220
+ font-family: inherit;
221
+ cursor: pointer;
222
+ border-radius: 4px;
223
+ transition: background var(--dur-fast, 100ms);
224
+ }
225
+
226
+ .dndev-terminal-app-picker-item:hover {
227
+ background: var(--accent, var(--muted));
228
+ }