@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
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@donotdev/cli",
3
- "version": "0.0.20",
3
+ "version": "0.0.21",
4
4
  "description": "Command-line interface for DoNotDev Framework",
5
5
  "type": "module",
6
6
  "private": false,
@@ -0,0 +1 @@
1
+ VITE_AUTH_PARTNERS=password, google, github, apple, discord, facebook, linkedin, reddit, twitch, twitter, microsoft, spotify, yahoo
@@ -32,13 +32,16 @@ export const productEntity = defineEntity({
32
32
  listCardFields: ['images', 'name', 'price', 'category'],
33
33
 
34
34
  // ==========================================================================
35
- // ACCESS RULES - Who can do what
35
+ // ACCESS RULES - Who can do what (maps to Supabase RLS policies)
36
36
  // ==========================================================================
37
37
  //
38
38
  // 'guest' → Anyone (including not logged in)
39
39
  // 'user' → Any logged-in user
40
- // 'owner' → Only the document owner (createdById matches)
41
40
  // 'admin' → Only admin role
41
+ // 'super' → Only super admin
42
+ //
43
+ // NOTE: These are DB-level roles. For owner-based logic, use field-level
44
+ // 'editable' / 'visibility' settings (see FIELD DEFINITIONS below).
42
45
  //
43
46
  access: {
44
47
  create: 'admin', // Only admins can create products
@@ -67,16 +70,19 @@ export const productEntity = defineEntity({
67
70
  // 'images' → Image upload (multiple)
68
71
  // 'reference' → Link to another entity
69
72
  //
70
- // VISIBILITY:
71
- // 'guest' → Everyone can see this field
72
- // 'user' → Only logged-in users
73
- // 'owner' → Only document owner
74
- // 'admin' → Only admins
73
+ // VISIBILITY (field-level, UI only):
74
+ // 'guest' → Everyone can see this field
75
+ // 'user' → Only logged-in users
76
+ // 'admin' → Only admins
77
+ // 'super' → Only super admins
78
+ // 'hidden' → Never sent to client (server-only)
75
79
  //
76
- // EDITABLE:
80
+ // EDITABLE (field-level, UI only):
77
81
  // 'admin' → Only admins can edit
78
- // 'owner' → Only owner can edit
82
+ // 'owner' → Only document creator can edit
83
+ // 'user' → Any authenticated user can edit
79
84
  // 'create-only' → Set on creation, read-only after
85
+ // false → Never editable (read-only)
80
86
  //
81
87
  fields: {
82
88
  // ==========================================================================
@@ -20,7 +20,7 @@
20
20
  box-sizing: border-box;
21
21
  }
22
22
  body {
23
- font-family: 'Inter', var(--font-family, ui-sans-serif, system-ui, -apple-system, sans-serif);
23
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
24
24
  line-height: 1.5;
25
25
  -webkit-font-smoothing: antialiased;
26
26
  -moz-osx-font-smoothing: grayscale;
@@ -0,0 +1,164 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8" />
5
+
6
+ <!-- ✅ CORE: Performance-critical meta tags first -->
7
+ <meta name="viewport" content="width=device-width, initial-scale=1.0" />
8
+ <meta name="color-scheme" content="light dark" />
9
+
10
+ <!-- ✅ DnDev: Framework will inject FaviconHead here -->
11
+ <!-- All favicon links will be added by FaviconHead component -->
12
+
13
+ <!-- ✅ DnDev: Asset manifest injection point -->
14
+ <!-- window.__DNDEV_ASSETS__ and __DNDEV_ASSET_MANIFEST__ injected here -->
15
+
16
+ <!-- ✅ PWA: Manifest link (if exists) -->
17
+ <link rel="manifest" href="/manifest.json" />
18
+
19
+ <!-- Fonts: Inter, Space Grotesk, Playfair, Roboto loaded via @donotdev/ui/dndev.css (bundled). -->
20
+ <!-- DNDEV_FONT_PRELOADS -->
21
+
22
+ <!-- ✅ PERFORMANCE: Preconnect to external domains (OAuth providers) -->
23
+ <!-- GitHub OAuth -->
24
+ <link rel="preconnect" href="https://github.com">
25
+ <link rel="preconnect" href="https://api.github.com">
26
+ <!-- Google OAuth (remove if not using) -->
27
+ <link rel="preconnect" href="https://accounts.google.com">
28
+ <link rel="preconnect" href="https://oauth2.googleapis.com">
29
+ <link rel="preconnect" href="https://apis.google.com">
30
+ <link rel="preconnect" href="https://www.googleapis.com">
31
+
32
+ <!-- ✅ SEO: Basic meta tags (AutoMetaTags will override these) -->
33
+ <title>Loading...</title>
34
+ <meta name="description" content="Modern web application powered by DoNotDev" />
35
+
36
+
37
+ <!-- ✅ PERFORMANCE: Critical CSS inlined here by build -->
38
+ <style>
39
+ /* Critical above-the-fold styles */
40
+ html, body {
41
+ margin: 0;
42
+ padding: 0;
43
+ box-sizing: border-box;
44
+ }
45
+ body {
46
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
47
+ line-height: 1.5;
48
+ -webkit-font-smoothing: antialiased;
49
+ -moz-osx-font-smoothing: grayscale;
50
+ background: #ffffff;
51
+ color: #000000;
52
+ }
53
+ @media (prefers-color-scheme: dark) {
54
+ body {
55
+ background: #000000;
56
+ color: #ffffff;
57
+ }
58
+ }
59
+ /* Critical shell loader styles - pure CSS, instant render */
60
+ #shell-loader {
61
+ position: fixed;
62
+ inset: 0;
63
+ background: #000000;
64
+ display: flex;
65
+ align-items: center;
66
+ justify-content: center;
67
+ z-index: 9999;
68
+ opacity: 1;
69
+ transition: opacity 0.3s ease-out;
70
+ will-change: opacity;
71
+ /* Isolate from framework CSS */
72
+ margin: 0;
73
+ padding: 0;
74
+ box-sizing: border-box;
75
+ }
76
+ #shell-loader.shell-loader--fading {
77
+ opacity: 0;
78
+ pointer-events: none;
79
+ }
80
+ .shell-loader__content {
81
+ text-align: center;
82
+ color: white;
83
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Helvetica Neue', Arial, sans-serif;
84
+ width: 100%;
85
+ max-width: 500px;
86
+ /* Prevent layout shifts from framework CSS */
87
+ margin: 0;
88
+ padding: 0;
89
+ box-sizing: border-box;
90
+ line-height: 1.2;
91
+ }
92
+ .shell-loader__brand {
93
+ display: block;
94
+ font-size: clamp(3rem, 10vw, 5rem);
95
+ font-weight: 800;
96
+ letter-spacing: -0.05em;
97
+ margin: 0 0 1rem 0;
98
+ padding: 0;
99
+ box-sizing: border-box;
100
+ }
101
+ /* ECG Waveform Animation - stroke-dasharray (performant, cross-browser, standard SVG technique) */
102
+ .shell-loader__dots {
103
+ display: block;
104
+ width: 300px;
105
+ height: 60px;
106
+ margin: 0 auto;
107
+ padding: 0;
108
+ box-sizing: border-box;
109
+ }
110
+ .shell-loader__dots svg {
111
+ width: 100%;
112
+ height: 100%;
113
+ display: block;
114
+ }
115
+ .shell-loader__dots path {
116
+ stroke: #667eea;
117
+ stroke-width: 3;
118
+ fill: none;
119
+ stroke-linecap: round;
120
+ stroke-linejoin: round;
121
+ stroke-dasharray: 400;
122
+ stroke-dashoffset: 400;
123
+ animation: ecg-draw 2s linear infinite;
124
+ }
125
+ @keyframes ecg-draw {
126
+ 0% {
127
+ stroke-dashoffset: 400;
128
+ opacity: 0;
129
+ }
130
+ 10% {
131
+ opacity: 1;
132
+ }
133
+ 70% {
134
+ stroke-dashoffset: 0;
135
+ opacity: 1;
136
+ }
137
+ 90%, 100% {
138
+ stroke-dashoffset: 0;
139
+ opacity: 0;
140
+ }
141
+ }
142
+ </style>
143
+ </head>
144
+ <body>
145
+
146
+ <!-- ✅ SHELL LOADER: Branded loading experience before React loads -->
147
+ <div id="shell-loader">
148
+ <div class="shell-loader__content">
149
+ <span class="shell-loader__brand">DoNotDev</span>
150
+ <span class="shell-loader__dots">
151
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 300 60" preserveAspectRatio="xMidYMid meet">
152
+ <path d="M0,30 L100,30 L110,20 L120,30 L130,30 L140,5 L150,55 L160,30 L175,30 L190,20 L205,30 L300,30" />
153
+ </svg>
154
+ </span>
155
+ </div>
156
+ </div>
157
+
158
+ <!-- ✅ APP: React app container -->
159
+ <div id="root"></div>
160
+
161
+ <!-- ✅ PERFORMANCE: Module script with proper attributes -->
162
+ <script type="module" src="/src/main.tsx"></script>
163
+ </body>
164
+ </html>
@@ -0,0 +1 @@
1
+ <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 500 500" xmlns:v="https://vecta.io/nano"><path d="M11.995 68.133c-.747.14-2.114 1.276-2.986 2.409l-1.557 2.199-.062 104.273.684 251.717c.935 2.835 3.423 4.749 6.347 4.89 2.366.142 3.61-.566 10.767-5.599l42.311-30.054L112.3 366.14l16.366-11.768c3.234-2.268 6.098-4.749 6.346-5.599 1.369-3.828 9.271-32.679 9.023-32.891-.124-.213-1.743.78-3.547 2.198-1.803 1.347-8.773 6.309-15.432 10.916L87.1 355.862l-24.891 17.65-23.831 16.729c-5.413 3.898-10.143 7.017-10.454 7.017-.685 0-.809-291.837-.063-292.334.249-.141 1.619.497 3.049 1.559l51.521 37.711 18.668 13.61 20.222 14.674 13.69 9.996 9.333 6.734 12.756 9.215c10.455 7.798 15.992 11.767 16.553 11.908.374.073 1.99-4.889 3.609-11.058l3.048-11.202-5.537-4.038-10.641-7.799c-2.863-1.984-10.391-7.584-16.8-12.405l-23.21-17.154-20.721-15.24L89.4 121.157c-2.613-1.986-7.653-5.671-11.2-8.223L23.506 73.096c-6.471-4.679-8.774-5.671-11.512-4.962l.001-.001zm235.829 4.466c-1.057.708-2.302 2.055-2.737 2.977-.622 1.276-.809 14.673-.746 62.733l.062 61.104 4.915 8.01 10.206 16.871 5.225 8.861.187-62.308 1.057-61.812c.56.212 6.098 4.111 12.321 8.648l43.806 31.97c1.307.991 1.431.708 5.973-9.641l4.667-10.705-1.618-1.346-14.747-10.845-20.844-15.313-36.65-26.864c-5.788-4.182-7.841-4.608-11.077-2.34zm-34.784 196.85l-7.279 15.807-11.511 25.166c.123.141 5.351-3.474 11.697-7.939l18.356-13.042 8.027-5.813c1.057-.922.685-1.701-6.347-13.469l-7.466-12.546-5.477 11.838v-.002z" fill="var(--primary,#dc143c)"/><path d="M376.752 57.145l-4.977 11.2c-1.432 2.907-4.792 10.066-7.468 15.95l-9.955 21.619-25.388 54.583-11.2 24.101-20.658 43.949-11.7 24.81-7.84 16.304c-.809 1.347-.621 1.631-15.679-23.747l-26.072-43.949-22.213-37.71c-8.587-14.815-10.267-16.73-12.322-14.319-.435.566-2.676 8.009-4.978 16.586l-22.711 82.228-13.378 47.494-8.153 29.064-18.232 64.86-12.133 43.382-4.355 16.162c.186 0 3.671-7.443 14.871-31.898l10.578-22.684 8.088-17.013 8.774-18.43 9.771-20.556 7.901-16.659 9.333-19.848 9.957-21.267 10.765-23.038c6.844-14.957 9.397-20.061 10.08-20.273.374-.142 3.485 4.606 6.968 10.562l24.828 41.255 31.981 52.809c5.289 8.932 6.846 11.06 8.153 11.2.87.142 1.929-.212 2.302-.779.685-.851 19.6-65.499 44.054-150.421l25.823-88.889 17.798-62.168c-.125-.141-1.308 2.34-2.615 5.53h.002z" fill="var(--accent,#ffd700)"/><path d="M359.081 153.904l-3.111 11.555c-1.369 4.89-2.302 9.074-2.053 9.287s5.041 3.757 10.703 7.869l33.289 24.314 37.645 27.505 19.601 14.318 6.409 4.536c2.489 1.49 5.538-.991-32.542 26.369l-38.269 27.432-31.236 22.402-33.913 24.456-28.187 20.273-12.134 8.648c-10.019 7.16-19.413 13.681-19.848 13.681-.188 0-.374-14.248-.374-31.757v-31.686l-4.419-7.088-10.019-16.518c-3.111-5.102-5.849-9.356-6.098-9.356s-.375 11.909-.249 26.37l.249 64.223v37.711l2.054 2.623c1.866 2.268 2.489 2.553 4.914 2.553 3.237 0 4.169-.568 18.855-11.202l76.535-55.291 21.78-15.807 120.403-86.411c3.67-2.552 5.102-4.748 5.475-8.434.188-2.409 0-3.971-.746-5.599-1.244-2.623-2.677-3.686-48.908-37.711l-51.023-37.642-14.624-10.774-12.88-9.427c-2.986-2.199-5.724-4.041-6.035-4.041-.374 0-.934 1.206-1.245 2.622l.001-.003z" fill="var(--primary,#dc143c)"/></svg>
@@ -0,0 +1,10 @@
1
+ {
2
+ "name": "DoNotDev App",
3
+ "short_name": "DoNotDev",
4
+ "description": "Built with DoNotDev Framework ©",
5
+ "start_url": "/",
6
+ "display": "standalone",
7
+ "background_color": "#ffffff",
8
+ "theme_color": "#000000",
9
+ "icons": []
10
+ }
@@ -0,0 +1,35 @@
1
+ /**
2
+ * @fileoverview Main application component
3
+ *
4
+ * Admin preset with cockpit layout wrapper.
5
+ * Single unified store (DoNotDashStore) for all app state.
6
+ * Terminal embedded inside CockpitLayout (route-aware split panes).
7
+ * Ctrl+K navigation uses framework GoToWrapper (rendered by DnDevLayout).
8
+ */
9
+
10
+ import { ViteAppProviders } from '@donotdev/ui/vite';
11
+
12
+ import { CockpitLayout } from './components/CockpitLayout';
13
+ import './components/TerminalPanel.css';
14
+ import './components/CockpitLayout.css';
15
+ import './components/Kanban.css';
16
+ import './components/markdown-prose.css';
17
+ import './config/providers';
18
+ import { appConfig } from './config/app';
19
+ import { useDoNotDashStore } from './stores/dndevStore';
20
+
21
+ export function App() {
22
+ return (
23
+ <ViteAppProviders
24
+ config={appConfig}
25
+ layout={{
26
+ breadcrumbs: 'smart',
27
+ footer: () => null,
28
+ wrapper: ({ children }) => <CockpitLayout>{children}</CockpitLayout>,
29
+ }}
30
+ customStores={[
31
+ { name: 'donotdash', type: 'regular', store: useDoNotDashStore },
32
+ ]}
33
+ />
34
+ );
35
+ }
@@ -0,0 +1,181 @@
1
+ /* ===========================
2
+ COCKPIT LAYOUT
3
+ Route-aware split panes via react-resizable-panels
4
+ =========================== */
5
+
6
+ .dndev-cockpit {
7
+ display: flex;
8
+ flex-direction: column;
9
+ height: calc(100vh - var(--header-height, 56px));
10
+ min-height: 0;
11
+ overflow: hidden;
12
+ }
13
+
14
+ /* Panel groups fill cockpit */
15
+ .dndev-cockpit > [data-panel-group] {
16
+ flex: 1;
17
+ min-height: 0;
18
+ }
19
+
20
+ /* Panels must shrink below content intrinsic width */
21
+ .dndev-cockpit [data-panel] {
22
+ min-width: 0;
23
+ min-height: 0;
24
+ }
25
+
26
+ /* Kill breakthrough CSS inside cockpit panels.
27
+ Sections use width: calc(100dvw - var(--sidebar-width)) to break out of PageContainer,
28
+ but inside react-resizable-panels the panel IS the boundary, not the viewport.
29
+ Without this, Sections render at 100dvw and bleed under the terminal panel. */
30
+ .dndev-cockpit .dndev-container > .dndev-section-full-width {
31
+ width: 100%;
32
+ max-width: 100%;
33
+ margin-inline: 0;
34
+ }
35
+
36
+ /* ===========================
37
+ PANES
38
+ =========================== */
39
+
40
+ .dndev-cockpit-pane {
41
+ height: 100%;
42
+ min-height: 0;
43
+ min-width: 0;
44
+ overflow: hidden;
45
+ display: flex;
46
+ flex-direction: column;
47
+ }
48
+
49
+ .dndev-cockpit-pane--content {
50
+ overflow: hidden;
51
+ }
52
+
53
+ /* Scrollable area for page content — below the toolbar */
54
+ .dndev-cockpit-scroll {
55
+ flex: 1;
56
+ min-height: 0;
57
+ min-width: 0;
58
+ overflow-x: hidden;
59
+ overflow-y: auto;
60
+ }
61
+
62
+ /* ===========================
63
+ COCKPIT TOOLBAR — real header row above content
64
+ =========================== */
65
+
66
+ .dndev-cockpit-toolbar {
67
+ flex-shrink: 0;
68
+ display: flex;
69
+ justify-content: space-between;
70
+ align-items: center;
71
+ padding: 4px 8px;
72
+ gap: 8px;
73
+ border-bottom: 1px solid var(--border);
74
+ background: var(--card);
75
+ }
76
+
77
+ .dndev-cockpit-toolbar-start,
78
+ .dndev-cockpit-toolbar-end {
79
+ display: flex;
80
+ align-items: center;
81
+ gap: 4px;
82
+ }
83
+
84
+ /* OverlaySlotTarget center area — pages inject controls here */
85
+ .dndev-cockpit-overlays-center {
86
+ display: flex;
87
+ align-items: center;
88
+ gap: 4px;
89
+ }
90
+
91
+ /* GoTo search — constrain width so it doesn't stretch to fill flex space */
92
+ .dndev-cockpit-goto {
93
+ max-width: 320px;
94
+ }
95
+
96
+ .dndev-cockpit-pane--terminal {
97
+ background: #0a0a0a;
98
+ }
99
+
100
+ .dndev-cockpit-pane--left-stack {
101
+ border-inline-end: none;
102
+ }
103
+
104
+ .dndev-cockpit-pane--explorer {
105
+ overflow-y: auto;
106
+ background: var(--card);
107
+ }
108
+
109
+ /* ===========================
110
+ RESIZE HANDLES
111
+ =========================== */
112
+
113
+ .dndev-cockpit-handle {
114
+ flex-shrink: 0;
115
+ transition: background var(--dur-fast, 100ms);
116
+ }
117
+
118
+ .dndev-cockpit-handle:hover,
119
+ .dndev-cockpit-handle:focus-visible,
120
+ .dndev-cockpit-handle[data-resize-handle-active] {
121
+ background: var(--primary);
122
+ outline: none;
123
+ }
124
+
125
+ /* Vertical handle (between left/right panes) */
126
+ .dndev-cockpit-handle--vertical {
127
+ width: 4px;
128
+ cursor: col-resize;
129
+ background: var(--border);
130
+ }
131
+
132
+ /* Horizontal handle (between top/bottom panes) */
133
+ .dndev-cockpit-handle--horizontal {
134
+ height: 4px;
135
+ cursor: row-resize;
136
+ background: var(--border);
137
+ }
138
+
139
+ @media (hover: none) {
140
+ .dndev-cockpit-handle--vertical { width: 8px; }
141
+ .dndev-cockpit-handle--horizontal { height: 8px; }
142
+ }
143
+
144
+ /* ===========================
145
+ FULLSCREEN — CSS-only, no React tree change
146
+ Terminal breaks out via position:fixed, content hidden
147
+ =========================== */
148
+
149
+ .dndev-cockpit[data-fullscreen] .dndev-cockpit-pane--content,
150
+ .dndev-cockpit[data-fullscreen] .dndev-cockpit-pane--explorer,
151
+ .dndev-cockpit[data-fullscreen] .dndev-cockpit-handle {
152
+ display: none !important;
153
+ }
154
+
155
+ /* Terminal in Panel (horizontal/left-stack) */
156
+ .dndev-cockpit[data-fullscreen] .dndev-cockpit-pane--terminal {
157
+ position: fixed;
158
+ inset: 0;
159
+ z-index: 200;
160
+ height: 100vh !important;
161
+ max-height: none !important;
162
+ }
163
+
164
+ /* Terminal direct child (bottom-bar) */
165
+ .dndev-cockpit[data-fullscreen] > .dndev-terminal-panel {
166
+ position: fixed;
167
+ inset: 0;
168
+ z-index: 200;
169
+ height: 100vh !important;
170
+ max-height: none !important;
171
+ min-height: 0 !important;
172
+ }
173
+
174
+ /* ===========================
175
+ REMOVE FRAMEWORK COMPENSATION
176
+ The terminal is no longer fixed — it's inside the layout.
177
+ =========================== */
178
+
179
+ .dndev-layout {
180
+ padding-bottom: 0 !important;
181
+ }
@@ -0,0 +1,209 @@
1
+ /**
2
+ * @fileoverview Cockpit layout wrapper — route-aware split panes via react-resizable-panels v4
3
+ *
4
+ * Reads current route, maps to a LayoutPreset, and renders the appropriate
5
+ * split arrangement with terminal as a contextual slot.
6
+ *
7
+ * Two presets:
8
+ * - bottom-bar: content on top, terminal at bottom (observer pages)
9
+ * - horizontal: content left, terminal right (operator pages)
10
+ *
11
+ * Terminal is rendered ONCE per layout — never conditionally swapped.
12
+ * Fullscreen is CSS-only (data-fullscreen attribute), no React tree change.
13
+ */
14
+
15
+ import { useCallback, useEffect, useRef } from 'react';
16
+ import { Group, Panel, Separator, usePanelRef } from 'react-resizable-panels';
17
+
18
+ import { LanguageSelector } from '@donotdev/core';
19
+ import { GoTo, ThemeToggle, useLocation } from '@donotdev/ui';
20
+
21
+ import { useDoNotDashStore } from '../stores/dndevStore';
22
+ import { ModeToggle } from './ModeToggle';
23
+ import { OverlaySlotProvider, OverlaySlotTarget } from './OverlaySlot';
24
+ import { TerminalPanel } from './TerminalPanel';
25
+
26
+ import type { ReactNode } from 'react';
27
+ import type { Layout } from 'react-resizable-panels';
28
+
29
+ // ============================================================================
30
+ // CONTENT PANE — scrollable children + fixed overlays (GoTo, ModeToggle)
31
+ // ============================================================================
32
+
33
+ function ContentPane({ children }: { children: ReactNode }): ReactNode {
34
+ const { pathname } = useLocation();
35
+ const scrollRef = useRef<HTMLDivElement>(null);
36
+
37
+ // Reset scroll position on route change
38
+ useEffect(() => {
39
+ if (scrollRef.current) scrollRef.current.scrollTop = 0;
40
+ }, [pathname]);
41
+
42
+ return (
43
+ <div className="dndev-cockpit-pane dndev-cockpit-pane--content">
44
+ <div className="dndev-cockpit-toolbar">
45
+ <div className="dndev-cockpit-toolbar-start">
46
+ <ThemeToggle display="compact" />
47
+ <LanguageSelector display="compact" />
48
+ <GoTo className="dndev-cockpit-goto" />
49
+ </div>
50
+ <OverlaySlotTarget />
51
+ <div className="dndev-cockpit-toolbar-end">
52
+ <ModeToggle />
53
+ </div>
54
+ </div>
55
+ <div ref={scrollRef} className="dndev-cockpit-scroll">
56
+ {children}
57
+ </div>
58
+ </div>
59
+ );
60
+ }
61
+
62
+ // ============================================================================
63
+ // LAYOUT PRESETS
64
+ // ============================================================================
65
+
66
+ type LayoutPreset = 'bottom-bar' | 'horizontal';
67
+
68
+ const LAYOUT_MAP: Record<string, LayoutPreset> = {
69
+ '/': 'bottom-bar',
70
+ '/phases': 'horizontal',
71
+ '/board': 'bottom-bar',
72
+ '/grill': 'horizontal',
73
+ '/settings': 'bottom-bar',
74
+ };
75
+
76
+ function getPreset(pathname: string): LayoutPreset {
77
+ if (LAYOUT_MAP[pathname]) return LAYOUT_MAP[pathname];
78
+ const base = '/' + pathname.split('/').filter(Boolean)[0];
79
+ return LAYOUT_MAP[base] ?? 'bottom-bar';
80
+ }
81
+
82
+ // ============================================================================
83
+ // RESIZE HANDLE
84
+ // ============================================================================
85
+
86
+ function ResizeHandle({ direction }: { direction: 'horizontal' | 'vertical' }) {
87
+ return (
88
+ <Separator
89
+ className={`dndev-cockpit-handle dndev-cockpit-handle--${direction}`}
90
+ />
91
+ );
92
+ }
93
+
94
+ // ============================================================================
95
+ // COMPONENT
96
+ // ============================================================================
97
+
98
+ interface CockpitLayoutProps {
99
+ children: ReactNode;
100
+ }
101
+
102
+ export function CockpitLayout({ children }: CockpitLayoutProps): ReactNode {
103
+ const { pathname } = useLocation();
104
+ const preset = getPreset(pathname);
105
+ const isFullscreen = useDoNotDashStore((s) => s.isFullscreen);
106
+ const isExpanded = useDoNotDashStore((s) => s.isExpanded);
107
+ const terminalPanelRef = usePanelRef();
108
+
109
+ // Sync store isExpanded ↔ panel collapse/expand (bidirectional, loop-safe)
110
+ useEffect(() => {
111
+ const panel = terminalPanelRef.current;
112
+ if (!panel) return;
113
+ const panelCollapsed = panel.isCollapsed();
114
+ if (isExpanded && panelCollapsed) panel.expand();
115
+ else if (!isExpanded && !panelCollapsed) panel.collapse();
116
+ }, [isExpanded, terminalPanelRef]);
117
+
118
+ // Sync panel collapse/expand → store (handles manual drag past minSize)
119
+ const syncCollapseToStore = useCallback(() => {
120
+ const panel = terminalPanelRef.current;
121
+ if (!panel) return;
122
+ const collapsed = panel.isCollapsed();
123
+ const storeExpanded = useDoNotDashStore.getState().isExpanded;
124
+ if (collapsed && storeExpanded) useDoNotDashStore.getState().setExpanded(false);
125
+ else if (!collapsed && !storeExpanded) useDoNotDashStore.getState().setExpanded(true);
126
+ }, [terminalPanelRef]);
127
+
128
+ // GOTCHA: onLayoutChanged fires on initial render. Without debounce:
129
+ // saveSizes → writes .dndev/dashboard.json → file watcher HMR event → re-render → loop.
130
+ // 500ms debounce breaks the cycle — initial render fires are swallowed.
131
+ const saveTimer = useRef<ReturnType<typeof setTimeout>>(null);
132
+ const saveSizes = useCallback((layout: Layout) => {
133
+ if (saveTimer.current) clearTimeout(saveTimer.current);
134
+ saveTimer.current = setTimeout(() => {
135
+ const sizes = Object.values(layout);
136
+ useDoNotDashStore.getState().setPanelSizes(pathname, sizes);
137
+ }, 500);
138
+ }, [pathname]);
139
+
140
+ // Clean up debounce timer on unmount or pathname change
141
+ useEffect(() => {
142
+ return () => {
143
+ if (saveTimer.current) clearTimeout(saveTimer.current);
144
+ };
145
+ }, [pathname]);
146
+
147
+ const direction = preset === 'horizontal' ? 'horizontal' : 'vertical';
148
+
149
+ if (preset === 'horizontal') {
150
+ return (
151
+ <OverlaySlotProvider>
152
+ <div className="dndev-cockpit" data-preset="horizontal" data-fullscreen={isFullscreen || undefined}>
153
+ <Group
154
+ orientation="horizontal"
155
+ onLayoutChanged={saveSizes}
156
+ id={`cockpit-${pathname}`}
157
+ >
158
+ <Panel defaultSize="65%" minSize="30%">
159
+ <ContentPane>{children}</ContentPane>
160
+ </Panel>
161
+ <ResizeHandle direction="vertical" />
162
+ <Panel
163
+ defaultSize="35%"
164
+ minSize="20%"
165
+ collapsible
166
+ collapsedSize="34px"
167
+ panelRef={terminalPanelRef}
168
+ onResize={syncCollapseToStore}
169
+ >
170
+ <div className="dndev-cockpit-pane dndev-cockpit-pane--terminal">
171
+ <TerminalPanel embedded direction={direction} />
172
+ </div>
173
+ </Panel>
174
+ </Group>
175
+ </div>
176
+ </OverlaySlotProvider>
177
+ );
178
+ }
179
+
180
+ // bottom-bar (default) — content on top, terminal at bottom, resizable
181
+ return (
182
+ <OverlaySlotProvider>
183
+ <div className="dndev-cockpit" data-preset="bottom-bar" data-fullscreen={isFullscreen || undefined}>
184
+ <Group
185
+ orientation="vertical"
186
+ onLayoutChanged={saveSizes}
187
+ id={`cockpit-${pathname}`}
188
+ >
189
+ <Panel defaultSize="75%" minSize="20%">
190
+ <ContentPane>{children}</ContentPane>
191
+ </Panel>
192
+ <ResizeHandle direction="horizontal" />
193
+ <Panel
194
+ defaultSize="25%"
195
+ minSize="80px"
196
+ collapsible
197
+ collapsedSize="34px"
198
+ panelRef={terminalPanelRef}
199
+ onResize={syncCollapseToStore}
200
+ >
201
+ <div className="dndev-cockpit-pane dndev-cockpit-pane--terminal">
202
+ <TerminalPanel embedded direction={direction} />
203
+ </div>
204
+ </Panel>
205
+ </Group>
206
+ </div>
207
+ </OverlaySlotProvider>
208
+ );
209
+ }