@donotdev/cli 0.0.20 → 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 (103) hide show
  1. package/README.md +31 -0
  2. package/dependencies-matrix.json +86 -19
  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 +491 -69
  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 +23 -15
  9. package/dist/bin/commands/create-project.js +101 -16
  10. package/dist/bin/commands/db.js +142136 -0
  11. package/dist/bin/commands/deploy.js +336 -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 +256 -212
  22. package/dist/bin/commands/staging.js +343 -127
  23. package/dist/bin/commands/sync-secrets.js +55 -33
  24. package/dist/bin/commands/type-check.js +6 -6
  25. package/dist/bin/commands/wai.js +6 -6
  26. package/dist/bin/dndev.js +76 -11
  27. package/dist/bin/donotdev.js +21 -12
  28. package/dist/index.js +437 -142
  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-dndev/index.html.example +164 -0
  34. package/templates/app-dndev/public/logo.svg.example +1 -0
  35. package/templates/app-dndev/public/manifest.json.example +10 -0
  36. package/templates/app-dndev/src/App.tsx.example +35 -0
  37. package/templates/app-dndev/src/components/CockpitLayout.css.example +181 -0
  38. package/templates/app-dndev/src/components/CockpitLayout.tsx.example +209 -0
  39. package/templates/app-dndev/src/components/Kanban.css.example +385 -0
  40. package/templates/app-dndev/src/components/ModeToggle.tsx.example +32 -0
  41. package/templates/app-dndev/src/components/OverlaySlot.tsx.example +68 -0
  42. package/templates/app-dndev/src/components/TerminalPanel.css.example +228 -0
  43. package/templates/app-dndev/src/components/TerminalPanel.tsx.example +714 -0
  44. package/templates/app-dndev/src/components/markdown-prose.css.example +49 -0
  45. package/templates/app-dndev/src/components/phases/CaptainLog.tsx.example +107 -0
  46. package/templates/app-dndev/src/components/phases/ContextTabs.tsx.example +352 -0
  47. package/templates/app-dndev/src/components/phases/PhaseCard.tsx.example +126 -0
  48. package/templates/app-dndev/src/components/phases/PhaseDetail.tsx.example +147 -0
  49. package/templates/app-dndev/src/components/phases/ReviewPanel.tsx.example +115 -0
  50. package/templates/app-dndev/src/components/phases/phaseData.ts.example +366 -0
  51. package/templates/app-dndev/src/config/app.ts.example +103 -0
  52. package/templates/app-dndev/src/config/commands.ts.example +171 -0
  53. package/templates/app-dndev/src/config/legal.ts.example +170 -0
  54. package/templates/app-dndev/src/config/providers.ts.example +7 -0
  55. package/templates/app-dndev/src/globals.css.example +10 -0
  56. package/templates/app-dndev/src/hooks/useDndevFile.ts.example +144 -0
  57. package/templates/app-dndev/src/main.tsx.example +21 -0
  58. package/templates/app-dndev/src/pages/BoardPage.tsx.example +640 -0
  59. package/templates/app-dndev/src/pages/GrillPage.tsx.example +658 -0
  60. package/templates/app-dndev/src/pages/HomePage.tsx.example +347 -0
  61. package/templates/app-dndev/src/pages/NotFoundPage.tsx.example +33 -0
  62. package/templates/app-dndev/src/pages/PhasesPage.tsx.example +137 -0
  63. package/templates/app-dndev/src/pages/SettingsPage.tsx.example +64 -0
  64. package/templates/app-dndev/src/pages/legal/LegalNoticePage.tsx.example +75 -0
  65. package/templates/app-dndev/src/pages/legal/PrivacyPage.tsx.example +69 -0
  66. package/templates/app-dndev/src/pages/legal/TermsPage.tsx.example +71 -0
  67. package/templates/app-dndev/src/stores/dndevStore.ts.example +386 -0
  68. package/templates/app-dndev/src/themes.css.example +161 -0
  69. package/templates/app-dndev/terminal-sidecar.cjs.example +341 -0
  70. package/templates/app-dndev/tsconfig.json.example +9 -0
  71. package/templates/app-dndev/vite.config.ts.example +24 -0
  72. package/templates/app-next/src/locales/home_en.json.example +6 -6
  73. package/templates/app-vite/index.html.example +1 -1
  74. package/templates/app-vite/src/locales/home_en.json.example +6 -6
  75. package/templates/functions-supabase/supabase/functions/.env.example +0 -2
  76. package/templates/root-consumer/.claude/commands/grill.md.example +86 -8
  77. package/templates/root-consumer/.dndev.secrets.example +32 -0
  78. package/templates/root-consumer/.gitignore.example +3 -0
  79. package/templates/root-consumer/AI.md.example +4 -0
  80. package/templates/root-consumer/entities/index.ts.example +2 -5
  81. package/templates/root-consumer/guides/dndev/COMPONENTS_ATOMIC.md.example +4 -0
  82. package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +23 -20
  83. package/templates/root-consumer/guides/dndev/INDEX.md.example +1 -0
  84. package/templates/root-consumer/guides/dndev/SETUP_BILLING.md.example +3 -7
  85. package/templates/root-consumer/guides/dndev/SETUP_CICD.md.example +115 -0
  86. package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +41 -0
  87. package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +13 -18
  88. package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +17 -12
  89. package/templates/root-consumer/guides/dndev/advanced/COOKIE_REFERENCE.md.example +252 -252
  90. package/templates/root-consumer/guides/dndev/advanced/VERSION_CONTROL.md.example +174 -174
  91. package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +185 -251
  92. package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +26 -8
  93. package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +66 -49
  94. package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +6 -5
  95. package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +9 -9
  96. package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +1 -1
  97. package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +7 -6
  98. package/templates/root-consumer/guides/wai-way/context_map.json.example +51 -20
  99. package/templates/root-consumer/guides/wai-way/hld_template.md.example +138 -0
  100. package/templates/root-consumer/guides/wai-way/lld_template.md.example +103 -0
  101. package/templates/root-consumer/guides/wai-way/prd_template.md.example +140 -0
  102. /package/templates/{root-consumer → app-demo}/entities/Contact.ts.example +0 -0
  103. /package/templates/{root-consumer → app-demo}/entities/demo.ts.example +0 -0
@@ -0,0 +1,69 @@
1
+ /**
2
+ * @fileoverview Privacy Policy Page
3
+ * @description Privacy policy page using the reusable template
4
+ *
5
+ * @version 0.0.1
6
+ * @since 0.0.1
7
+ * @author AMBROISE PARK Consulting
8
+ */
9
+
10
+ import { Shield } from 'lucide-react';
11
+
12
+ import { type PageMeta } from '@donotdev/core';
13
+ import { PrivacyPolicyTemplate } from '@donotdev/templates';
14
+ import { PageContainer } from '@donotdev/ui';
15
+
16
+ import { legalConfig } from '../../config/legal';
17
+
18
+ /**
19
+ * Privacy page translation namespace
20
+ *
21
+ * @version 0.0.1
22
+ * @since 0.0.1
23
+ * @author AMBROISE PARK Consulting
24
+ */
25
+ export const NAMESPACE = 'privacy';
26
+
27
+ /**
28
+ * Privacy page metadata configuration
29
+ *
30
+ * @version 0.0.1
31
+ * @since 0.0.1
32
+ * @author AMBROISE PARK Consulting
33
+ */
34
+ export const meta: PageMeta = {
35
+ namespace: NAMESPACE,
36
+ icon: <Shield />,
37
+ hideFromMenu: true,
38
+ };
39
+
40
+ /**
41
+ * Privacy Policy Page Component
42
+ *
43
+ * @version 0.0.1
44
+ * @since 0.0.1
45
+ * @author AMBROISE PARK Consulting
46
+ *
47
+ * Uses the reusable PrivacyPolicyTemplate with centralized legal config
48
+ */
49
+ function PrivacyPage() {
50
+ return (
51
+ <PageContainer>
52
+ <PrivacyPolicyTemplate
53
+ companyName={legalConfig.company.name}
54
+ websiteUrl={legalConfig.website.url}
55
+ privacyEmail={legalConfig.contact.privacyEmail}
56
+ companyAddress={legalConfig.contact.address}
57
+ dpoEmail={legalConfig.contact.dpoEmail}
58
+ sections={legalConfig.sections.privacy}
59
+ contactInfo={{
60
+ supportEmail: legalConfig.contact.supportEmail,
61
+ }}
62
+ lastUpdated={legalConfig.lastUpdated?.privacy}
63
+ />
64
+ </PageContainer>
65
+ );
66
+ }
67
+
68
+ export default PrivacyPage;
69
+
@@ -0,0 +1,71 @@
1
+ /**
2
+ * @fileoverview Terms of Service Page
3
+ * @description Terms of service page using the reusable template
4
+ *
5
+ * @version 0.0.1
6
+ * @since 0.0.1
7
+ * @author AMBROISE PARK Consulting
8
+ */
9
+
10
+ import { FileText } from 'lucide-react';
11
+
12
+ import { type PageMeta } from '@donotdev/core';
13
+ import { TermsOfServiceTemplate } from '@donotdev/templates';
14
+ import { PageContainer } from '@donotdev/ui';
15
+
16
+ import { legalConfig } from '../../config/legal';
17
+
18
+ /**
19
+ * Terms page translation namespace
20
+ *
21
+ * @version 0.0.1
22
+ * @since 0.0.1
23
+ * @author AMBROISE PARK Consulting
24
+ */
25
+ export const NAMESPACE = 'terms';
26
+
27
+ /**
28
+ * Terms page metadata configuration
29
+ *
30
+ * @version 0.0.1
31
+ * @since 0.0.1
32
+ * @author AMBROISE PARK Consulting
33
+ */
34
+ export const meta: PageMeta = {
35
+ namespace: NAMESPACE,
36
+ icon: <FileText />,
37
+ hideFromMenu: true,
38
+ };
39
+
40
+ /**
41
+ * Terms of Service Page Component
42
+ *
43
+ * @version 0.0.1
44
+ * @since 0.0.1
45
+ * @author AMBROISE PARK Consulting
46
+ *
47
+ * Uses the reusable TermsOfServiceTemplate with centralized legal config
48
+ */
49
+ function TermsPage() {
50
+ return (
51
+ <PageContainer>
52
+ <TermsOfServiceTemplate
53
+ companyName={legalConfig.company.name}
54
+ websiteUrl={legalConfig.website.url}
55
+ legalEmail={legalConfig.contact.email}
56
+ companyAddress={legalConfig.contact.address}
57
+ jurisdiction={legalConfig.jurisdiction.country}
58
+ arbitrationOrg={legalConfig.jurisdiction.arbitrationOrg}
59
+ arbitrationLocation={legalConfig.jurisdiction.arbitrationLocation}
60
+ sections={legalConfig.sections.terms}
61
+ contactInfo={{
62
+ supportEmail: legalConfig.contact.supportEmail,
63
+ }}
64
+ lastUpdated={legalConfig.lastUpdated?.terms}
65
+ />
66
+ </PageContainer>
67
+ );
68
+ }
69
+
70
+ export default TermsPage;
71
+
@@ -0,0 +1,386 @@
1
+ /**
2
+ * @fileoverview Unified dndev app store — dashboard mode, kanban cards, terminal state
3
+ *
4
+ * Single store, three domains. Use fine-grained selectors:
5
+ * useDoNotDashStore(s => s.mode)
6
+ * useDoNotDashStore(s => s.cards)
7
+ * useDoNotDashStore(s => s.activeTab)
8
+ *
9
+ * Imperative actions via getState():
10
+ * useDoNotDashStore.getState().addCard(...)
11
+ * useDoNotDashStore.getState().injectPrompt(...)
12
+ */
13
+
14
+ import { createDoNotDevStore } from '@donotdev/core';
15
+
16
+ import { writeDndevFile } from '../hooks/useDndevFile';
17
+
18
+ // ============================================================================
19
+ // TYPES — Dashboard
20
+ // ============================================================================
21
+
22
+ export type DashboardMode = 'build' | 'run';
23
+
24
+ // ============================================================================
25
+ // TYPES — Kanban
26
+ // ============================================================================
27
+
28
+ export type CardColumn = 'backlog' | 'in_progress' | 'review' | 'done';
29
+ export type CardPriority = 'low' | 'medium' | 'high' | 'critical';
30
+ export type TicketType = 'task' | 'bug' | 'idea' | 'improvement';
31
+
32
+ export interface KanbanCard {
33
+ id: string;
34
+ title: string;
35
+ description?: string;
36
+ column: CardColumn;
37
+ priority: CardPriority;
38
+ type?: TicketType;
39
+ filePath?: string;
40
+ lineNumber?: number;
41
+ route?: string;
42
+ screenshot?: string;
43
+ createdAt: string;
44
+ updatedAt: string;
45
+ }
46
+
47
+ export const COLUMNS: { id: CardColumn; label: string }[] = [
48
+ { id: 'backlog', label: 'Backlog' },
49
+ { id: 'in_progress', label: 'In Progress' },
50
+ { id: 'review', label: 'Review' },
51
+ { id: 'done', label: 'Done' },
52
+ ];
53
+
54
+ // ============================================================================
55
+ // TYPES — Terminal
56
+ // ============================================================================
57
+
58
+ export type TabMode = 'ai-agent' | 'app' | 'shell';
59
+
60
+ /** @deprecated Use TabMode instead */
61
+ export type TerminalTab = string;
62
+
63
+ export interface TabConfig {
64
+ id: string;
65
+ label: string;
66
+ icon: 'cpu' | 'terminal' | 'square-terminal';
67
+ mode: TabMode;
68
+ appName?: string; // mode: 'app' only
69
+ }
70
+
71
+ export const DEFAULT_TABS: TabConfig[] = [
72
+ { id: 'ai-cli', label: 'AI Agent', icon: 'terminal', mode: 'ai-agent' },
73
+ ];
74
+
75
+ interface TerminalSession {
76
+ isConnected: boolean;
77
+ }
78
+
79
+ // ============================================================================
80
+ // COMBINED STATE + ACTIONS
81
+ // ============================================================================
82
+
83
+ interface DndevState {
84
+ // Dashboard
85
+ mode: DashboardMode;
86
+ lastView: string;
87
+ panelSizes: Record<string, number[]>;
88
+ // Docs
89
+ docsSelectedPath: string | null;
90
+ // Kanban
91
+ cards: KanbanCard[];
92
+ // Terminal
93
+ tabs: TabConfig[];
94
+ activeTab: string;
95
+ sessions: Record<string, TerminalSession>;
96
+ isExpanded: boolean;
97
+ isFullscreen: boolean;
98
+ pendingPrompt: string | null;
99
+ pendingTab: string;
100
+ }
101
+
102
+ interface DndevActions {
103
+ // Dashboard
104
+ setMode: (mode: DashboardMode) => void;
105
+ setLastView: (path: string) => void;
106
+ toggleMode: () => void;
107
+ setPanelSizes: (route: string, sizes: number[]) => void;
108
+ // Docs
109
+ setDocsSelectedPath: (path: string | null) => void;
110
+ // Kanban
111
+ addCard: (card: Partial<KanbanCard> & { title: string }) => void;
112
+ removeCard: (id: string) => void;
113
+ moveCard: (id: string, column: CardColumn, persist?: boolean) => void;
114
+ updateCard: (id: string, updates: Partial<KanbanCard>) => void;
115
+ reorderCards: (cards: KanbanCard[]) => void;
116
+ loadCards: (cards: KanbanCard[]) => void;
117
+ // Terminal
118
+ setActiveTab: (tab: string) => void;
119
+ setExpanded: (expanded: boolean) => void;
120
+ toggleExpanded: () => void;
121
+ setSessionConnected: (tab: string, connected: boolean) => void;
122
+ injectPrompt: (prompt: string, target?: string | { mode: TabMode }) => void;
123
+ consumePrompt: () => string | null;
124
+ addShellTab: () => string;
125
+ addAppTab: (appName: string) => void;
126
+ removeTab: (id: string) => void;
127
+ findTabByMode: (mode: TabMode) => TabConfig | undefined;
128
+ toggleFullscreen: () => void;
129
+ }
130
+
131
+ // ============================================================================
132
+ // HELPERS
133
+ // ============================================================================
134
+
135
+ function generateId(): string {
136
+ return `${Date.now()}-${Math.random().toString(36).slice(2, 8)}`;
137
+ }
138
+
139
+ function now(): string {
140
+ return new Date().toISOString();
141
+ }
142
+
143
+ async function saveCards(cards: KanbanCard[]): Promise<void> {
144
+ await writeDndevFile('.dndev/kanban.json', cards);
145
+ }
146
+
147
+ function saveDashboard(mode: DashboardMode, lastView: string, panelSizes?: Record<string, number[]>): void {
148
+ const state = useDoNotDashStore.getState();
149
+ writeDndevFile('.dndev/dashboard.json', {
150
+ mode,
151
+ lastView,
152
+ panelSizes: panelSizes ?? state?.panelSizes ?? {},
153
+ }).catch((err) => console.error('[dndev] Failed to persist dashboard:', err));
154
+ }
155
+
156
+ // ============================================================================
157
+ // STORE
158
+ // ============================================================================
159
+
160
+ export const useDoNotDashStore = createDoNotDevStore<DndevState & DndevActions>({
161
+ name: 'donotdash-store',
162
+
163
+ createStore: (set, get) => ({
164
+ // --- Dashboard state ---
165
+ mode: 'build',
166
+ lastView: '/',
167
+ panelSizes: {},
168
+
169
+ setMode: (mode) => {
170
+ set({ mode });
171
+ saveDashboard(mode, get().lastView);
172
+ },
173
+ setLastView: (lastView) => {
174
+ set({ lastView });
175
+ saveDashboard(get().mode, lastView);
176
+ },
177
+ toggleMode: () => {
178
+ const next = get().mode === 'build' ? 'run' : 'build';
179
+ set({ mode: next });
180
+ saveDashboard(next, get().lastView);
181
+ },
182
+ setPanelSizes: (route, sizes) => {
183
+ set((s) => {
184
+ const panelSizes = { ...s.panelSizes, [route]: sizes };
185
+ saveDashboard(s.mode, s.lastView, panelSizes);
186
+ return { panelSizes };
187
+ });
188
+ },
189
+
190
+ // --- Docs state ---
191
+ docsSelectedPath: null,
192
+ setDocsSelectedPath: (path) => set({ docsSelectedPath: path }),
193
+
194
+ // --- Kanban state ---
195
+ cards: [],
196
+
197
+ addCard: (card) => {
198
+ const newCard: KanbanCard = {
199
+ id: generateId(),
200
+ title: card.title,
201
+ description: card.description,
202
+ column: card.column ?? 'backlog',
203
+ priority: card.priority ?? 'medium',
204
+ type: card.type ?? 'task',
205
+ filePath: card.filePath,
206
+ route: card.route,
207
+ screenshot: card.screenshot,
208
+ lineNumber: card.lineNumber,
209
+ createdAt: now(),
210
+ updatedAt: now(),
211
+ };
212
+ set((state) => {
213
+ const cards = [...state.cards, newCard];
214
+ saveCards(cards);
215
+ return { cards };
216
+ });
217
+ },
218
+ removeCard: (id) => {
219
+ set((state) => {
220
+ const cards = state.cards.filter((c) => c.id !== id);
221
+ saveCards(cards);
222
+ return { cards };
223
+ });
224
+ },
225
+ moveCard: (id, column, persist = true) => {
226
+ set((state) => {
227
+ const cards = state.cards.map((c) =>
228
+ c.id === id ? { ...c, column, updatedAt: now() } : c
229
+ );
230
+ if (persist) saveCards(cards);
231
+ return { cards };
232
+ });
233
+ },
234
+ updateCard: (id, updates) => {
235
+ set((state) => {
236
+ const cards = state.cards.map((c) =>
237
+ c.id === id ? { ...c, ...updates, updatedAt: now() } : c
238
+ );
239
+ saveCards(cards);
240
+ return { cards };
241
+ });
242
+ },
243
+ reorderCards: (cards) => {
244
+ set({ cards });
245
+ saveCards(cards);
246
+ },
247
+ loadCards: (cards) => {
248
+ set({ cards });
249
+ },
250
+
251
+ // --- Terminal state ---
252
+ tabs: DEFAULT_TABS,
253
+ activeTab: 'ai-cli',
254
+ sessions: {},
255
+ isExpanded: true,
256
+ isFullscreen: false,
257
+ pendingPrompt: null,
258
+ pendingTab: 'ai-cli',
259
+
260
+ setActiveTab: (tab) => set({ activeTab: tab }),
261
+ setExpanded: (expanded) => set({ isExpanded: expanded }),
262
+ toggleExpanded: () => set((s) => ({ isExpanded: !s.isExpanded })),
263
+ setSessionConnected: (tab, connected) => {
264
+ set((s) => ({
265
+ sessions: {
266
+ ...s.sessions,
267
+ [tab]: { isConnected: connected },
268
+ },
269
+ }));
270
+ },
271
+ findTabByMode: (mode) => {
272
+ return get().tabs.find((t) => t.mode === mode);
273
+ },
274
+ injectPrompt: (prompt, target) => {
275
+ const state = get();
276
+ let tabId: string;
277
+
278
+ if (typeof target === 'string') {
279
+ // Backward compat: find tab by id
280
+ tabId = target;
281
+ } else {
282
+ const mode = target?.mode ?? 'ai-agent';
283
+ const found = state.tabs.find((t) => t.mode === mode);
284
+ if (found) {
285
+ tabId = found.id;
286
+ } else if (mode === 'shell') {
287
+ // Auto-spawn a shell tab
288
+ tabId = state.addShellTab();
289
+ // Re-read after addShellTab mutated state
290
+ set({ pendingPrompt: prompt, pendingTab: tabId, activeTab: tabId, isExpanded: true });
291
+ return;
292
+ } else {
293
+ tabId = state.tabs[0]?.id ?? 'ai-cli';
294
+ }
295
+ }
296
+
297
+ set({ pendingPrompt: prompt, pendingTab: tabId, activeTab: tabId, isExpanded: true });
298
+ },
299
+ consumePrompt: () => {
300
+ const prompt = get().pendingPrompt;
301
+ if (prompt) set({ pendingPrompt: null });
302
+ return prompt;
303
+ },
304
+ addShellTab: () => {
305
+ const { tabs } = get();
306
+ let n = 1;
307
+ while (tabs.some((t) => t.id === `shell-${n}`)) n++;
308
+ const id = `shell-${n}`;
309
+ set({
310
+ tabs: [...tabs, { id, label: `Shell ${n}`, icon: 'square-terminal' as const, mode: 'shell' }],
311
+ activeTab: id,
312
+ isExpanded: true,
313
+ });
314
+ return id;
315
+ },
316
+ addAppTab: (appName) => {
317
+ const { tabs } = get();
318
+ const id = `app-${appName}`;
319
+ // Don't duplicate
320
+ if (tabs.some((t) => t.id === id)) {
321
+ set({ activeTab: id, isExpanded: true });
322
+ return;
323
+ }
324
+ // Set tab + inject prompt synchronously — no setTimeout race
325
+ set({
326
+ tabs: [...tabs, { id, label: appName, icon: 'cpu' as const, mode: 'app', appName }],
327
+ activeTab: id,
328
+ isExpanded: true,
329
+ pendingPrompt: `dndev dev ${appName}`,
330
+ pendingTab: id,
331
+ });
332
+ },
333
+ removeTab: (id) => {
334
+ const { tabs, activeTab } = get();
335
+ const tab = tabs.find((t) => t.id === id);
336
+ // Protect ai-agent tabs
337
+ if (tab?.mode === 'ai-agent') return;
338
+ if (tabs.length <= 1) return;
339
+ const idx = tabs.findIndex((t) => t.id === id);
340
+ const newTabs = tabs.filter((t) => t.id !== id);
341
+ // Select adjacent tab (prefer left neighbor, fall back to right)
342
+ let nextActive = activeTab;
343
+ if (activeTab === id) {
344
+ const adjacentIdx = idx > 0 ? idx - 1 : 0;
345
+ nextActive = newTabs[adjacentIdx]!.id;
346
+ }
347
+ set({ tabs: newTabs, activeTab: nextActive });
348
+ },
349
+ toggleFullscreen: () => set((s) => ({ isFullscreen: !s.isFullscreen })),
350
+ }),
351
+
352
+ initialize: async (): Promise<boolean> => {
353
+ async function fetchSilent(filePath: string): Promise<unknown | null> {
354
+ try {
355
+ const res = await fetch(`/api/dndev/file?path=${encodeURIComponent(filePath)}`);
356
+ if (!res.ok) return null;
357
+ const json = await res.json();
358
+ return json.content ?? null;
359
+ } catch {
360
+ return null;
361
+ }
362
+ }
363
+
364
+ // Load kanban cards
365
+ const kanbanData = await fetchSilent('.dndev/kanban.json');
366
+ if (Array.isArray(kanbanData)) {
367
+ useDoNotDashStore.getState().loadCards(kanbanData);
368
+ }
369
+
370
+ // Load dashboard prefs
371
+ const dashData = await fetchSilent('.dndev/dashboard.json') as { mode?: DashboardMode; lastView?: string; panelSizes?: Record<string, number[]> } | null;
372
+ if (dashData?.mode) {
373
+ useDoNotDashStore.setState({
374
+ mode: dashData.mode,
375
+ lastView: dashData.lastView || '/',
376
+ panelSizes: dashData.panelSizes ?? {},
377
+ });
378
+ } else {
379
+ // Auto-detect: protocol.json exists → BUILD
380
+ const protocol = await fetchSilent('.dndev/protocol.json');
381
+ useDoNotDashStore.setState({ mode: protocol ? 'build' : 'run' });
382
+ }
383
+
384
+ return true;
385
+ },
386
+ });
@@ -0,0 +1,161 @@
1
+ /**
2
+ * src/themes.css — SSOT for visual essence and theme variables.
3
+ * Import this in globals.css; do not set font/color overrides in globals (themes.css only).
4
+ *
5
+ * Default essence = SaaS (Inter, neutral). No class on <html> = SaaS.
6
+ * Optional essences below (.brutalist, .luxury) do NOT apply by default.
7
+ * To use one: set the class on the document root (e.g. document.documentElement.className = 'brutalist')
8
+ * or use the framework theme switcher if your app exposes it.
9
+ */
10
+
11
+ /* ===========================
12
+ LIGHT THEME (SaaS default)
13
+ =========================== */
14
+ :root.light {
15
+ --theme-icon: 'Sun';
16
+ --theme-label: 'Light';
17
+ --theme-is-dark: 0;
18
+
19
+ --background: #ffffff;
20
+ --foreground: #09090b;
21
+ --primary: #0284c7;
22
+ --primary-foreground: #ffffff;
23
+ --secondary: #f4f4f5;
24
+ --secondary-foreground: #18181b;
25
+ --accent: #ea580c;
26
+ --accent-foreground: #ffffff;
27
+ --muted: #f4f4f5;
28
+ --muted-foreground: #71717a;
29
+ --border: #e4e4e7;
30
+ --input: #e4e4e7;
31
+ --ring: var(--primary);
32
+ --card: #ffffff;
33
+ --card-foreground: #09090b;
34
+ --popover: #ffffff;
35
+ --popover-foreground: #09090b;
36
+ --destructive: #dc2626;
37
+ --destructive-foreground: #ffffff;
38
+ --success: #16a34a;
39
+ --success-foreground: #ffffff;
40
+ --warning: #f59e0b;
41
+ --warning-foreground: #09090b;
42
+ }
43
+
44
+ /* ===========================
45
+ DARK THEME (SaaS default)
46
+ =========================== */
47
+ :root.dark {
48
+ --theme-icon: 'Moon';
49
+ --theme-label: 'Dark';
50
+ --theme-is-dark: 1;
51
+
52
+ --background: #09090b;
53
+ --foreground: #fafafa;
54
+ --primary: #0ea5e9;
55
+ --primary-foreground: #ffffff;
56
+ --secondary: #27272a;
57
+ --secondary-foreground: #fafafa;
58
+ --accent: #f97316;
59
+ --accent-foreground: #ffffff;
60
+ --muted: #27272a;
61
+ --muted-foreground: #a1a1aa;
62
+ --border: #27272a;
63
+ --input: #27272a;
64
+ --ring: var(--primary);
65
+ --card: #09090b;
66
+ --card-foreground: #fafafa;
67
+ --popover: #18181b;
68
+ --popover-foreground: #fafafa;
69
+ --destructive: #dc2626;
70
+ --destructive-foreground: #ffffff;
71
+ --success: #16a34a;
72
+ --success-foreground: #ffffff;
73
+ --warning: #f59e0b;
74
+ --warning-foreground: #09090b;
75
+ }
76
+
77
+ /* ===========================
78
+ OPTIONAL ESSENCES
79
+ Do not apply by default. Apply by setting class on <html> (e.g. class="brutalist").
80
+ Fonts: Space Grotesk (Brutalist), Playfair Display (Luxury) are in @donotdev/ui.
81
+ =========================== */
82
+
83
+ /** Brutalist — Industrial, grid, Space Grotesk, orange/black. Does not apply by default. */
84
+ .brutalist {
85
+ --theme-icon: 'Construction';
86
+ --theme-label: 'Brutalist';
87
+ --theme-is-dark: 1;
88
+ --background: #000000;
89
+ --foreground: #ffffff;
90
+ --primary: #f97316;
91
+ --secondary: #1a1a1a;
92
+ --accent: #f97316;
93
+ --muted: #111111;
94
+ --muted-foreground: #888888;
95
+ --border: #ffffff;
96
+ --input: #111111;
97
+ --ring: #f97316;
98
+ --card: #000000;
99
+ --card-foreground: #ffffff;
100
+ --popover: #000000;
101
+ --popover-foreground: #ffffff;
102
+ --primary-foreground: #ffffff;
103
+ --secondary-foreground: #ffffff;
104
+ --accent-foreground: #ffffff;
105
+ --success: #22c55e;
106
+ --warning: #f97316;
107
+ --destructive: #dc2626;
108
+ --success-foreground: #ffffff;
109
+ --warning-foreground: #ffffff;
110
+ --destructive-foreground: #ffffff;
111
+ --radius-interactive: 0;
112
+ --radius-surface: 0;
113
+ --radius-floating: 0;
114
+ --font-family: 'Space Grotesk', var(--font-sans);
115
+ --font-headline: 'Space Grotesk', var(--font-sans);
116
+ --border-width: 2px;
117
+ --border-huge: 4px;
118
+ --shadow-sm: 4px 4px 0px 0px var(--primary);
119
+ --shadow-md: 8px 8px 0px 0px var(--primary);
120
+ --shadow-xl: 12px 12px 0px 0px var(--primary);
121
+ }
122
+
123
+ /** Luxury — Serif headlines (Playfair), gold/cream/dark. Does not apply by default. */
124
+ .luxury {
125
+ --theme-icon: 'Gem';
126
+ --theme-label: 'Luxury';
127
+ --theme-is-dark: 0;
128
+ --background: #faf8f5;
129
+ --foreground: #1c1917;
130
+ --primary: #b45309;
131
+ --secondary: #fef3c7;
132
+ --accent: #92400e;
133
+ --muted: #fef9c3;
134
+ --muted-foreground: #78716c;
135
+ --border: #e7e5e4;
136
+ --input: #ffffff;
137
+ --ring: #b45309;
138
+ --card: #ffffff;
139
+ --card-foreground: #1c1917;
140
+ --popover: #ffffff;
141
+ --popover-foreground: #1c1917;
142
+ --primary-foreground: #ffffff;
143
+ --secondary-foreground: #1c1917;
144
+ --accent-foreground: #ffffff;
145
+ --success: #15803d;
146
+ --warning: #b45309;
147
+ --destructive: #b91c1c;
148
+ --success-foreground: #ffffff;
149
+ --warning-foreground: #1c1917;
150
+ --destructive-foreground: #ffffff;
151
+ --font-family: var(--font-sans);
152
+ --font-headline: 'Playfair Display', var(--font-serif);
153
+ --radius-interactive: 0.375rem;
154
+ --radius-surface: 0.5rem;
155
+ --radius-floating: 0.5rem;
156
+ }
157
+
158
+ /* ===========================
159
+ CUSTOM THEMES
160
+ Add your brand themes below (same pattern: class + --theme-label, --theme-icon, --theme-is-dark).
161
+ =========================== */