@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.
- package/README.md +31 -0
- package/dependencies-matrix.json +86 -19
- package/dist/bin/commands/agent-setup.js +2 -2
- package/dist/bin/commands/build.js +6 -6
- package/dist/bin/commands/bump.js +491 -69
- package/dist/bin/commands/cacheout.js +6 -6
- package/dist/bin/commands/coach.js +6 -6
- package/dist/bin/commands/create-app.js +23 -15
- package/dist/bin/commands/create-project.js +101 -16
- package/dist/bin/commands/db.js +142136 -0
- package/dist/bin/commands/deploy.js +336 -126
- package/dist/bin/commands/dev.js +6 -6
- package/dist/bin/commands/doctor.js +140 -33
- package/dist/bin/commands/emu.js +6 -6
- package/dist/bin/commands/format.js +6 -6
- package/dist/bin/commands/get-demo.js +11 -6
- package/dist/bin/commands/make-admin.js +14210 -13770
- package/dist/bin/commands/preview.js +6 -6
- package/dist/bin/commands/seed.js +142426 -0
- package/dist/bin/commands/setup-cicd.js +8904 -0
- package/dist/bin/commands/setup.js +256 -212
- package/dist/bin/commands/staging.js +343 -127
- package/dist/bin/commands/sync-secrets.js +55 -33
- package/dist/bin/commands/type-check.js +6 -6
- package/dist/bin/commands/wai.js +6 -6
- package/dist/bin/dndev.js +76 -11
- package/dist/bin/donotdev.js +21 -12
- package/dist/index.js +437 -142
- package/package.json +1 -1
- package/templates/app-demo/.env.example +1 -0
- package/templates/{root-consumer → app-demo}/entities/ExampleEntity.ts.example +15 -9
- package/templates/app-demo/index.html.example +1 -1
- package/templates/app-dndev/index.html.example +164 -0
- package/templates/app-dndev/public/logo.svg.example +1 -0
- package/templates/app-dndev/public/manifest.json.example +10 -0
- package/templates/app-dndev/src/App.tsx.example +35 -0
- package/templates/app-dndev/src/components/CockpitLayout.css.example +181 -0
- package/templates/app-dndev/src/components/CockpitLayout.tsx.example +209 -0
- package/templates/app-dndev/src/components/Kanban.css.example +385 -0
- package/templates/app-dndev/src/components/ModeToggle.tsx.example +32 -0
- package/templates/app-dndev/src/components/OverlaySlot.tsx.example +68 -0
- package/templates/app-dndev/src/components/TerminalPanel.css.example +228 -0
- package/templates/app-dndev/src/components/TerminalPanel.tsx.example +714 -0
- package/templates/app-dndev/src/components/markdown-prose.css.example +49 -0
- package/templates/app-dndev/src/components/phases/CaptainLog.tsx.example +107 -0
- package/templates/app-dndev/src/components/phases/ContextTabs.tsx.example +352 -0
- package/templates/app-dndev/src/components/phases/PhaseCard.tsx.example +126 -0
- package/templates/app-dndev/src/components/phases/PhaseDetail.tsx.example +147 -0
- package/templates/app-dndev/src/components/phases/ReviewPanel.tsx.example +115 -0
- package/templates/app-dndev/src/components/phases/phaseData.ts.example +366 -0
- package/templates/app-dndev/src/config/app.ts.example +103 -0
- package/templates/app-dndev/src/config/commands.ts.example +171 -0
- package/templates/app-dndev/src/config/legal.ts.example +170 -0
- package/templates/app-dndev/src/config/providers.ts.example +7 -0
- package/templates/app-dndev/src/globals.css.example +10 -0
- package/templates/app-dndev/src/hooks/useDndevFile.ts.example +144 -0
- package/templates/app-dndev/src/main.tsx.example +21 -0
- package/templates/app-dndev/src/pages/BoardPage.tsx.example +640 -0
- package/templates/app-dndev/src/pages/GrillPage.tsx.example +658 -0
- package/templates/app-dndev/src/pages/HomePage.tsx.example +347 -0
- package/templates/app-dndev/src/pages/NotFoundPage.tsx.example +33 -0
- package/templates/app-dndev/src/pages/PhasesPage.tsx.example +137 -0
- package/templates/app-dndev/src/pages/SettingsPage.tsx.example +64 -0
- package/templates/app-dndev/src/pages/legal/LegalNoticePage.tsx.example +75 -0
- package/templates/app-dndev/src/pages/legal/PrivacyPage.tsx.example +69 -0
- package/templates/app-dndev/src/pages/legal/TermsPage.tsx.example +71 -0
- package/templates/app-dndev/src/stores/dndevStore.ts.example +386 -0
- package/templates/app-dndev/src/themes.css.example +161 -0
- package/templates/app-dndev/terminal-sidecar.cjs.example +341 -0
- package/templates/app-dndev/tsconfig.json.example +9 -0
- package/templates/app-dndev/vite.config.ts.example +24 -0
- package/templates/app-next/src/locales/home_en.json.example +6 -6
- package/templates/app-vite/index.html.example +1 -1
- package/templates/app-vite/src/locales/home_en.json.example +6 -6
- package/templates/functions-supabase/supabase/functions/.env.example +0 -2
- package/templates/root-consumer/.claude/commands/grill.md.example +86 -8
- package/templates/root-consumer/.dndev.secrets.example +32 -0
- package/templates/root-consumer/.gitignore.example +3 -0
- package/templates/root-consumer/AI.md.example +4 -0
- package/templates/root-consumer/entities/index.ts.example +2 -5
- package/templates/root-consumer/guides/dndev/COMPONENTS_ATOMIC.md.example +4 -0
- package/templates/root-consumer/guides/dndev/ENV_SETUP.md.example +23 -20
- package/templates/root-consumer/guides/dndev/INDEX.md.example +1 -0
- package/templates/root-consumer/guides/dndev/SETUP_BILLING.md.example +3 -7
- package/templates/root-consumer/guides/dndev/SETUP_CICD.md.example +115 -0
- package/templates/root-consumer/guides/dndev/SETUP_CRUD.md.example +41 -0
- package/templates/root-consumer/guides/dndev/SETUP_SUPABASE.md.example +13 -18
- package/templates/root-consumer/guides/dndev/SETUP_VERCEL.md.example +17 -12
- package/templates/root-consumer/guides/dndev/advanced/COOKIE_REFERENCE.md.example +252 -252
- package/templates/root-consumer/guides/dndev/advanced/VERSION_CONTROL.md.example +174 -174
- package/templates/root-consumer/guides/wai-way/WAI_WAY_CLI.md.example +185 -251
- package/templates/root-consumer/guides/wai-way/agents/extractor.md.example +26 -8
- package/templates/root-consumer/guides/wai-way/blueprints/0_brainstorm.md.example +66 -49
- package/templates/root-consumer/guides/wai-way/blueprints/1_scaffold.md.example +6 -5
- package/templates/root-consumer/guides/wai-way/blueprints/2_entities.md.example +9 -9
- package/templates/root-consumer/guides/wai-way/blueprints/3_compose.md.example +1 -1
- package/templates/root-consumer/guides/wai-way/blueprints/4_configure.md.example +7 -6
- package/templates/root-consumer/guides/wai-way/context_map.json.example +51 -20
- package/templates/root-consumer/guides/wai-way/hld_template.md.example +138 -0
- package/templates/root-consumer/guides/wai-way/lld_template.md.example +103 -0
- package/templates/root-consumer/guides/wai-way/prd_template.md.example +140 -0
- /package/templates/{root-consumer → app-demo}/entities/Contact.ts.example +0 -0
- /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
|
+
=========================== */
|