@builderos/create-agent-os 0.0.2

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 (194) hide show
  1. package/README.md +39 -0
  2. package/bin/cli.js +133 -0
  3. package/package.json +40 -0
  4. package/src/template/App.tsx +68 -0
  5. package/src/template/agent-os/commands/create-tasks/1-get-spec-requirements.md +19 -0
  6. package/src/template/agent-os/commands/create-tasks/2-create-tasks-list.md +234 -0
  7. package/src/template/agent-os/commands/create-tasks/create-tasks.md +254 -0
  8. package/src/template/agent-os/commands/design-screen/design-screen.md +32 -0
  9. package/src/template/agent-os/commands/design-shell/design-shell.md +34 -0
  10. package/src/template/agent-os/commands/design-tokens/design-tokens.md +36 -0
  11. package/src/template/agent-os/commands/export-product/export-product.md +44 -0
  12. package/src/template/agent-os/commands/implement-tasks/1-determine-tasks.md +13 -0
  13. package/src/template/agent-os/commands/implement-tasks/2-implement-tasks.md +63 -0
  14. package/src/template/agent-os/commands/implement-tasks/3-verify-implementation.md +113 -0
  15. package/src/template/agent-os/commands/implement-tasks/implement-tasks.md +207 -0
  16. package/src/template/agent-os/commands/initialize-design/initialize-design.md +54 -0
  17. package/src/template/agent-os/commands/orchestrate-tasks/orchestrate-tasks.md +180 -0
  18. package/src/template/agent-os/commands/plan-product/1-product-concept.md +53 -0
  19. package/src/template/agent-os/commands/plan-product/2-create-mission.md +78 -0
  20. package/src/template/agent-os/commands/plan-product/3-create-roadmap.md +73 -0
  21. package/src/template/agent-os/commands/plan-product/4-create-tech-stack.md +46 -0
  22. package/src/template/agent-os/commands/plan-product/plan-product.md +241 -0
  23. package/src/template/agent-os/commands/sample-data/sample-data.md +51 -0
  24. package/src/template/agent-os/commands/scaffold-implementation/scaffold-implementation.md +36 -0
  25. package/src/template/agent-os/commands/screenshot-design/screenshot-design.md +21 -0
  26. package/src/template/agent-os/commands/shape-spec/1-initialize-spec.md +95 -0
  27. package/src/template/agent-os/commands/shape-spec/2-shape-spec.md +300 -0
  28. package/src/template/agent-os/commands/shape-spec/shape-spec.md +40 -0
  29. package/src/template/agent-os/commands/write-spec/write-spec.md +134 -0
  30. package/src/template/agent-os/config.yml +13 -0
  31. package/src/template/agent-os/product/mission.md +29 -0
  32. package/src/template/agent-os/product/roadmap.md +9 -0
  33. package/src/template/agent-os/product/tech-stack.md +14 -0
  34. package/src/template/agent-os/specs/README.md +1 -0
  35. package/src/template/agent-os/standards/backend/api.md +10 -0
  36. package/src/template/agent-os/standards/backend/migrations.md +9 -0
  37. package/src/template/agent-os/standards/backend/models.md +10 -0
  38. package/src/template/agent-os/standards/backend/queries.md +9 -0
  39. package/src/template/agent-os/standards/frontend/accessibility.md +10 -0
  40. package/src/template/agent-os/standards/frontend/components.md +11 -0
  41. package/src/template/agent-os/standards/frontend/css.md +7 -0
  42. package/src/template/agent-os/standards/frontend/responsive.md +11 -0
  43. package/src/template/agent-os/standards/global/coding-style.md +10 -0
  44. package/src/template/agent-os/standards/global/commenting.md +5 -0
  45. package/src/template/agent-os/standards/global/conventions.md +11 -0
  46. package/src/template/agent-os/standards/global/error-handling.md +9 -0
  47. package/src/template/agent-os/standards/global/tech-stack.md +31 -0
  48. package/src/template/agent-os/standards/global/validation.md +11 -0
  49. package/src/template/agent-os/standards/testing/test-writing.md +9 -0
  50. package/src/template/agent-os-ui/README.md +73 -0
  51. package/src/template/agent-os-ui/package-lock.json +5028 -0
  52. package/src/template/agent-os-ui/package.json +52 -0
  53. package/src/template/agent-os-ui/postcss.config.js +6 -0
  54. package/src/template/agent-os-ui/src/components/AgentShell.tsx +31 -0
  55. package/src/template/agent-os-ui/src/components/AgentSidebar.tsx +65 -0
  56. package/src/template/agent-os-ui/src/components/GuidanceCard.tsx +75 -0
  57. package/src/template/agent-os-ui/src/components/MarkdownViewer.tsx +25 -0
  58. package/src/template/agent-os-ui/src/components/PromptButton.tsx +28 -0
  59. package/src/template/agent-os-ui/src/components/StatusItem.tsx +45 -0
  60. package/src/template/agent-os-ui/src/components/ThemeToggle.tsx +72 -0
  61. package/src/template/agent-os-ui/src/index.ts +11 -0
  62. package/src/template/agent-os-ui/src/style.css +3 -0
  63. package/src/template/agent-os-ui/tailwind.config.js +50 -0
  64. package/src/template/agent-os-ui/tsconfig.json +33 -0
  65. package/src/template/agent-os-ui/vite.config.ts +32 -0
  66. package/src/template/control-center/backend/backend.log +2 -0
  67. package/src/template/control-center/backend/index.js +228 -0
  68. package/src/template/control-center/backend/package-lock.json +951 -0
  69. package/src/template/control-center/backend/package.json +19 -0
  70. package/src/template/control-center/frontend/README.md +73 -0
  71. package/src/template/control-center/frontend/eslint.config.js +23 -0
  72. package/src/template/control-center/frontend/index.html +21 -0
  73. package/src/template/control-center/frontend/package-lock.json +5752 -0
  74. package/src/template/control-center/frontend/package.json +42 -0
  75. package/src/template/control-center/frontend/public/runtime-config.json +11 -0
  76. package/src/template/control-center/frontend/public/vite.svg +1 -0
  77. package/src/template/control-center/frontend/src/App.css +42 -0
  78. package/src/template/control-center/frontend/src/App.tsx +738 -0
  79. package/src/template/control-center/frontend/src/assets/react.svg +1 -0
  80. package/src/template/control-center/frontend/src/components/ThemeToggle.tsx +64 -0
  81. package/src/template/control-center/frontend/src/components/ui/ToastContext.tsx +81 -0
  82. package/src/template/control-center/frontend/src/index.css +194 -0
  83. package/src/template/control-center/frontend/src/main.tsx +14 -0
  84. package/src/template/control-center/frontend/src/vite-env.d.ts +1 -0
  85. package/src/template/control-center/frontend/tsconfig.app.json +28 -0
  86. package/src/template/control-center/frontend/tsconfig.json +7 -0
  87. package/src/template/control-center/frontend/tsconfig.node.json +26 -0
  88. package/src/template/control-center/frontend/vite.config.ts +22 -0
  89. package/src/template/design/.claude/commands/design-os/data-model.md +122 -0
  90. package/src/template/design/.claude/commands/design-os/design-screen.md +309 -0
  91. package/src/template/design/.claude/commands/design-os/design-shell.md +238 -0
  92. package/src/template/design/.claude/commands/design-os/design-tokens.md +166 -0
  93. package/src/template/design/.claude/commands/design-os/export-product.md +1105 -0
  94. package/src/template/design/.claude/commands/design-os/product-roadmap.md +121 -0
  95. package/src/template/design/.claude/commands/design-os/product-vision.md +99 -0
  96. package/src/template/design/.claude/commands/design-os/sample-data.md +263 -0
  97. package/src/template/design/.claude/commands/design-os/screenshot-design.md +112 -0
  98. package/src/template/design/.claude/commands/design-os/shape-section.md +138 -0
  99. package/src/template/design/.claude/skills/frontend-design/SKILL.md +42 -0
  100. package/src/template/design/.github/CODE_OF_CONDUCT.md +5 -0
  101. package/src/template/design/.github/CONTRIBUTING.md +51 -0
  102. package/src/template/design/.github/ISSUE_TEMPLATE/config.yml +22 -0
  103. package/src/template/design/.github/PULL_REQUEST_TEMPLATE.md +20 -0
  104. package/src/template/design/.github/SECURITY.yml +5 -0
  105. package/src/template/design/.github/SUPPORT.md +19 -0
  106. package/src/template/design/.github/workflows/pr-decline.yml +135 -0
  107. package/src/template/design/.github/workflows/stale.yml +25 -0
  108. package/src/template/design/CHANGELOG.md +13 -0
  109. package/src/template/design/LICENSE +21 -0
  110. package/src/template/design/README.md +54 -0
  111. package/src/template/design/agents.md +218 -0
  112. package/src/template/design/claude.md +1 -0
  113. package/src/template/design/components.json +22 -0
  114. package/src/template/design/docs/codebase-implementation.md +153 -0
  115. package/src/template/design/docs/design-section.md +135 -0
  116. package/src/template/design/docs/export.md +149 -0
  117. package/src/template/design/docs/getting-started.md +59 -0
  118. package/src/template/design/docs/index.md +56 -0
  119. package/src/template/design/docs/product-planning.md +113 -0
  120. package/src/template/design/docs/requirements.md +22 -0
  121. package/src/template/design/docs/usage.md +62 -0
  122. package/src/template/design/eslint.config.js +23 -0
  123. package/src/template/design/index.html +21 -0
  124. package/src/template/design/package-lock.json +5473 -0
  125. package/src/template/design/package.json +47 -0
  126. package/src/template/design/product-plan.zip +0 -0
  127. package/src/template/design/public/vite.svg +1 -0
  128. package/src/template/design/src/assets/react.svg +1 -0
  129. package/src/template/design/src/components/AppLayout.tsx +95 -0
  130. package/src/template/design/src/components/DataCard.tsx +139 -0
  131. package/src/template/design/src/components/DataModelPage.tsx +120 -0
  132. package/src/template/design/src/components/DesignPage.tsx +284 -0
  133. package/src/template/design/src/components/EmptyState.tsx +155 -0
  134. package/src/template/design/src/components/ExportPage.tsx +344 -0
  135. package/src/template/design/src/components/NextPhaseButton.tsx +33 -0
  136. package/src/template/design/src/components/PhaseNav.tsx +152 -0
  137. package/src/template/design/src/components/PhaseWarningBanner.tsx +81 -0
  138. package/src/template/design/src/components/ProductOverviewCard.tsx +102 -0
  139. package/src/template/design/src/components/ProductPage.tsx +97 -0
  140. package/src/template/design/src/components/ScreenDesignPage.tsx +370 -0
  141. package/src/template/design/src/components/ScreenDesignsCard.tsx +49 -0
  142. package/src/template/design/src/components/SectionPage.tsx +256 -0
  143. package/src/template/design/src/components/SectionsCard.tsx +47 -0
  144. package/src/template/design/src/components/SectionsPage.tsx +181 -0
  145. package/src/template/design/src/components/ShellCard.tsx +85 -0
  146. package/src/template/design/src/components/ShellDesignPage.tsx +242 -0
  147. package/src/template/design/src/components/SpecCard.tsx +121 -0
  148. package/src/template/design/src/components/StepIndicator.tsx +75 -0
  149. package/src/template/design/src/components/ThemeToggle.tsx +86 -0
  150. package/src/template/design/src/components/ui/ToastContext.tsx +81 -0
  151. package/src/template/design/src/components/ui/avatar.tsx +53 -0
  152. package/src/template/design/src/components/ui/badge.tsx +46 -0
  153. package/src/template/design/src/components/ui/button.tsx +60 -0
  154. package/src/template/design/src/components/ui/card.tsx +92 -0
  155. package/src/template/design/src/components/ui/collapsible.tsx +48 -0
  156. package/src/template/design/src/components/ui/dialog.tsx +143 -0
  157. package/src/template/design/src/components/ui/dropdown-menu.tsx +255 -0
  158. package/src/template/design/src/components/ui/input.tsx +21 -0
  159. package/src/template/design/src/components/ui/label.tsx +22 -0
  160. package/src/template/design/src/components/ui/progress.tsx +24 -0
  161. package/src/template/design/src/components/ui/scroll-area.tsx +18 -0
  162. package/src/template/design/src/components/ui/select.tsx +67 -0
  163. package/src/template/design/src/components/ui/separator.tsx +28 -0
  164. package/src/template/design/src/components/ui/sheet.tsx +137 -0
  165. package/src/template/design/src/components/ui/skeleton.tsx +13 -0
  166. package/src/template/design/src/components/ui/switch.tsx +46 -0
  167. package/src/template/design/src/components/ui/table.tsx +116 -0
  168. package/src/template/design/src/components/ui/tabs.tsx +64 -0
  169. package/src/template/design/src/index.css +284 -0
  170. package/src/template/design/src/lib/data-model-loader.ts +91 -0
  171. package/src/template/design/src/lib/design-system-loader.ts +101 -0
  172. package/src/template/design/src/lib/product-loader.ts +221 -0
  173. package/src/template/design/src/lib/router.tsx +52 -0
  174. package/src/template/design/src/lib/section-loader.ts +272 -0
  175. package/src/template/design/src/lib/shell-loader.ts +175 -0
  176. package/src/template/design/src/lib/utils.ts +6 -0
  177. package/src/template/design/src/main.tsx +15 -0
  178. package/src/template/design/src/sections/.gitkeep +0 -0
  179. package/src/template/design/src/sections/ai-orchestration-engine-oai/OrchestrationEngine.tsx +348 -0
  180. package/src/template/design/src/sections/core-platform-shell/AppShell.tsx +403 -0
  181. package/src/template/design/src/sections/gemini-live-integration/GeminiIntegration.tsx +332 -0
  182. package/src/template/design/src/sections/interactive-2d-canvas/WhiteboardCanvas.tsx +334 -0
  183. package/src/template/design/src/sections/participation-equity-tracker/EquityTracker.tsx +383 -0
  184. package/src/template/design/src/sections/persistent-memory-system/PersistentMemory.tsx +308 -0
  185. package/src/template/design/src/sections/real-time-communication-layer/VideoSession.tsx +342 -0
  186. package/src/template/design/src/sections/visual-intelligence-agents/VisualAgents.tsx +311 -0
  187. package/src/template/design/src/types/product.ts +97 -0
  188. package/src/template/design/src/types/section.ts +33 -0
  189. package/src/template/design/tsconfig.app.json +34 -0
  190. package/src/template/design/tsconfig.json +13 -0
  191. package/src/template/design/tsconfig.node.json +26 -0
  192. package/src/template/design/vite.config.ts +18 -0
  193. package/src/template/package.json +27 -0
  194. package/src/template/vite.config.ts +16 -0
@@ -0,0 +1,102 @@
1
+ import { useState } from 'react'
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
3
+ import { Collapsible, CollapsibleContent, CollapsibleTrigger } from '@/components/ui/collapsible'
4
+ import { ArrowRight, ChevronDown } from 'lucide-react'
5
+ import type { ProductOverview } from '@/types/product'
6
+
7
+ interface ProductOverviewCardProps {
8
+ overview: ProductOverview
9
+ }
10
+
11
+ export function ProductOverviewCard({ overview }: ProductOverviewCardProps) {
12
+ const [problemsOpen, setProblemsOpen] = useState(false)
13
+ const [featuresOpen, setFeaturesOpen] = useState(false)
14
+
15
+ return (
16
+ <Card className="border-stone-200 dark:border-stone-700 shadow-sm">
17
+ <CardHeader className="pb-4">
18
+ <CardTitle className="text-lg font-semibold text-stone-900 dark:text-stone-100">
19
+ Product overview: {overview.name}
20
+ </CardTitle>
21
+ </CardHeader>
22
+ <CardContent className="space-y-4">
23
+ {/* Description */}
24
+ {overview.description && (
25
+ <p className="text-stone-600 dark:text-stone-400 leading-relaxed">
26
+ {overview.description}
27
+ </p>
28
+ )}
29
+
30
+ {/* Problems & Solutions - Expandable */}
31
+ {overview.problems.length > 0 && (
32
+ <Collapsible open={problemsOpen} onOpenChange={setProblemsOpen}>
33
+ <CollapsibleTrigger className="flex items-center justify-between w-full py-2 text-left group">
34
+ <span className="text-sm font-medium text-stone-500 dark:text-stone-400 uppercase tracking-wide">
35
+ Problems & Solutions
36
+ <span className="ml-2 text-stone-400 dark:text-stone-500 normal-case tracking-normal">
37
+ ({overview.problems.length})
38
+ </span>
39
+ </span>
40
+ <ChevronDown
41
+ className={`w-4 h-4 text-stone-400 dark:text-stone-500 transition-transform ${
42
+ problemsOpen ? 'rotate-180' : ''
43
+ }`}
44
+ strokeWidth={1.5}
45
+ />
46
+ </CollapsibleTrigger>
47
+ <CollapsibleContent>
48
+ <ul className="space-y-3 pt-2">
49
+ {overview.problems.map((problem, index) => (
50
+ <li key={index} className="flex items-start gap-3">
51
+ <ArrowRight className="w-4 h-4 text-stone-900 dark:text-stone-100 mt-1 shrink-0" strokeWidth={2} />
52
+ <div>
53
+ <span className="font-medium text-stone-800 dark:text-stone-200">
54
+ {problem.title}
55
+ </span>
56
+ <span className="text-stone-500 dark:text-stone-400 mx-2">—</span>
57
+ <span className="text-stone-600 dark:text-stone-400">
58
+ {problem.solution}
59
+ </span>
60
+ </div>
61
+ </li>
62
+ ))}
63
+ </ul>
64
+ </CollapsibleContent>
65
+ </Collapsible>
66
+ )}
67
+
68
+ {/* Key Features - Expandable */}
69
+ {overview.features.length > 0 && (
70
+ <Collapsible open={featuresOpen} onOpenChange={setFeaturesOpen}>
71
+ <CollapsibleTrigger className="flex items-center justify-between w-full py-2 text-left group">
72
+ <span className="text-sm font-medium text-stone-500 dark:text-stone-400 uppercase tracking-wide">
73
+ Key Features
74
+ <span className="ml-2 text-stone-400 dark:text-stone-500 normal-case tracking-normal">
75
+ ({overview.features.length})
76
+ </span>
77
+ </span>
78
+ <ChevronDown
79
+ className={`w-4 h-4 text-stone-400 dark:text-stone-500 transition-transform ${
80
+ featuresOpen ? 'rotate-180' : ''
81
+ }`}
82
+ strokeWidth={1.5}
83
+ />
84
+ </CollapsibleTrigger>
85
+ <CollapsibleContent>
86
+ <ul className="space-y-2 pt-2 ml-1">
87
+ {overview.features.map((feature, index) => (
88
+ <li key={index} className="flex items-start gap-4">
89
+ <span className="w-1.5 h-1.5 rounded-full bg-stone-900 dark:bg-stone-100 mt-2 shrink-0" />
90
+ <span className="text-stone-700 dark:text-stone-300">
91
+ {feature}
92
+ </span>
93
+ </li>
94
+ ))}
95
+ </ul>
96
+ </CollapsibleContent>
97
+ </Collapsible>
98
+ )}
99
+ </CardContent>
100
+ </Card>
101
+ )
102
+ }
@@ -0,0 +1,97 @@
1
+ import { useMemo } from 'react'
2
+ import { useNavigate } from 'react-router-dom'
3
+ import { loadProductData } from '@/lib/product-loader'
4
+ import { AppLayout } from '@/components/AppLayout'
5
+ import { EmptyState } from '@/components/EmptyState'
6
+ import { ProductOverviewCard } from '@/components/ProductOverviewCard'
7
+ import { SectionsCard } from '@/components/SectionsCard'
8
+ import { StepIndicator, type StepStatus } from '@/components/StepIndicator'
9
+ import { NextPhaseButton } from '@/components/NextPhaseButton'
10
+
11
+ /**
12
+ * Determine the status of each step on the Product page
13
+ * Steps: 1. Product Vision, 2. Roadmap
14
+ */
15
+ function getProductPageStepStatuses(
16
+ hasOverview: boolean,
17
+ hasRoadmap: boolean
18
+ ): StepStatus[] {
19
+ const statuses: StepStatus[] = []
20
+
21
+ // Step 1: Product Vision
22
+ if (hasOverview) {
23
+ statuses.push('completed')
24
+ } else {
25
+ statuses.push('current')
26
+ }
27
+
28
+ // Step 2: Roadmap
29
+ if (hasRoadmap) {
30
+ statuses.push('completed')
31
+ } else if (hasOverview) {
32
+ statuses.push('current')
33
+ } else {
34
+ statuses.push('upcoming')
35
+ }
36
+
37
+ return statuses
38
+ }
39
+
40
+ export function ProductPage() {
41
+ const navigate = useNavigate()
42
+ const productData = useMemo(() => loadProductData(), [])
43
+
44
+ const hasOverview = !!productData.overview
45
+ const hasRoadmap = !!productData.roadmap
46
+ const allStepsComplete = hasOverview && hasRoadmap
47
+
48
+ const stepStatuses = getProductPageStepStatuses(hasOverview, hasRoadmap)
49
+
50
+ return (
51
+ <AppLayout>
52
+ <div className="space-y-6">
53
+ {/* Page intro */}
54
+ <div className="mb-8">
55
+ <h1 className="text-2xl font-semibold text-stone-900 dark:text-stone-100 mb-2">
56
+ Product Definition
57
+ </h1>
58
+ <p className="text-stone-600 dark:text-stone-400">
59
+ Define your product vision and break it into development sections.
60
+ </p>
61
+ </div>
62
+
63
+ {/* Step 1: Product Vision */}
64
+ <div id="step-overview">
65
+ <StepIndicator step={1} status={stepStatuses[0]}>
66
+ {productData.overview ? (
67
+ <ProductOverviewCard overview={productData.overview} />
68
+ ) : (
69
+ <EmptyState type="overview" />
70
+ )}
71
+ </StepIndicator>
72
+ </div>
73
+
74
+ {/* Step 2: Roadmap / Sections Definition */}
75
+ <div id="step-roadmap">
76
+ <StepIndicator step={2} status={stepStatuses[1]} isLast={!allStepsComplete}>
77
+ {productData.roadmap ? (
78
+ <SectionsCard
79
+ roadmap={productData.roadmap}
80
+ onSectionClick={(sectionId) => navigate(`/sections/${sectionId}`)}
81
+ />
82
+ ) : (
83
+ <EmptyState type="roadmap" />
84
+ )}
85
+ </StepIndicator>
86
+ </div>
87
+
88
+ {/* Next Phase Button - shown when all steps complete */}
89
+ {allStepsComplete && (
90
+ <StepIndicator step={3} status="current" isLast>
91
+ <NextPhaseButton nextPhase="data-model" />
92
+ </StepIndicator>
93
+ )}
94
+ </div>
95
+ </AppLayout>
96
+ )
97
+ }
@@ -0,0 +1,370 @@
1
+ import { Suspense, useMemo, useState, useRef, useCallback, useEffect } from 'react'
2
+ import { useParams, useNavigate } from 'react-router-dom'
3
+ import { ArrowLeft, Maximize2, GripVertical, Layout, Smartphone, Tablet, Monitor } from 'lucide-react'
4
+ import { Button } from '@/components/ui/button'
5
+ import { ThemeToggle } from '@/components/ThemeToggle'
6
+ import { loadScreenDesignComponent, sectionUsesShell } from '@/lib/section-loader'
7
+ import { loadAppShell, hasShellComponents, loadShellInfo } from '@/lib/shell-loader'
8
+ import { loadProductData } from '@/lib/product-loader'
9
+ import React from 'react'
10
+
11
+ const MIN_WIDTH = 320
12
+ const DEFAULT_WIDTH_PERCENT = 100
13
+
14
+ export function ScreenDesignPage() {
15
+ const { sectionId, screenDesignName } = useParams<{ sectionId: string; screenDesignName: string }>()
16
+ const navigate = useNavigate()
17
+ const [widthPercent, setWidthPercent] = useState(DEFAULT_WIDTH_PERCENT)
18
+ const containerRef = useRef<HTMLDivElement>(null)
19
+ const isDragging = useRef(false)
20
+
21
+ // Load product data to get section title
22
+ const productData = useMemo(() => loadProductData(), [])
23
+ const section = productData.roadmap?.sections.find((s) => s.id === sectionId)
24
+
25
+ // Handle resize drag
26
+ const handleMouseDown = useCallback(() => {
27
+ isDragging.current = true
28
+
29
+ const handleMouseMove = (e: MouseEvent) => {
30
+ if (!isDragging.current || !containerRef.current) return
31
+
32
+ const containerRect = containerRef.current.getBoundingClientRect()
33
+ const containerWidth = containerRect.width
34
+ const containerCenter = containerRect.left + containerWidth / 2
35
+
36
+ // Calculate distance from center
37
+ const distanceFromCenter = Math.abs(e.clientX - containerCenter)
38
+ const maxDistance = containerWidth / 2
39
+
40
+ // Convert to percentage (distance from center * 2 = total width)
41
+ let newWidthPercent = (distanceFromCenter / maxDistance) * 100
42
+
43
+ // Clamp between min width and 100%
44
+ const minPercent = (MIN_WIDTH / containerWidth) * 100
45
+ newWidthPercent = Math.max(minPercent, Math.min(100, newWidthPercent))
46
+
47
+ setWidthPercent(newWidthPercent)
48
+ }
49
+
50
+ const handleMouseUp = () => {
51
+ isDragging.current = false
52
+ document.removeEventListener('mousemove', handleMouseMove)
53
+ document.removeEventListener('mouseup', handleMouseUp)
54
+ document.body.style.cursor = ''
55
+ document.body.style.userSelect = ''
56
+ }
57
+
58
+ document.addEventListener('mousemove', handleMouseMove)
59
+ document.addEventListener('mouseup', handleMouseUp)
60
+ document.body.style.cursor = 'ew-resize'
61
+ document.body.style.userSelect = 'none'
62
+ }, [])
63
+
64
+ const previewWidth = `${widthPercent}%`
65
+
66
+ return (
67
+ <div className="h-screen bg-stone-100 dark:bg-stone-900 animate-fade-in flex flex-col overflow-hidden">
68
+ {/* Header */}
69
+ <header className="border-b border-stone-200 dark:border-stone-800 bg-white dark:bg-stone-950 shrink-0 z-50">
70
+ <div className="px-4 py-2 flex items-center gap-4">
71
+ <Button
72
+ variant="ghost"
73
+ size="sm"
74
+ onClick={() => navigate(`/sections/${sectionId}`)}
75
+ className="text-stone-600 dark:text-stone-400 hover:text-stone-900 dark:hover:text-stone-100 -ml-2"
76
+ >
77
+ <ArrowLeft className="w-4 h-4 mr-2" strokeWidth={1.5} />
78
+ Back
79
+ </Button>
80
+ <div className="h-4 w-px bg-stone-200 dark:bg-stone-700" />
81
+ <div className="flex items-center gap-2 min-w-0">
82
+ <Layout className="w-4 h-4 text-stone-400 shrink-0" strokeWidth={1.5} />
83
+ {section && (
84
+ <span className="text-sm text-stone-500 dark:text-stone-400 truncate">
85
+ {section.title}
86
+ </span>
87
+ )}
88
+ <span className="text-stone-300 dark:text-stone-600">/</span>
89
+ <span className="text-sm font-medium text-stone-700 dark:text-stone-300 truncate">
90
+ {screenDesignName}
91
+ </span>
92
+ </div>
93
+
94
+ {/* Width indicator and device presets */}
95
+ <div className="ml-auto flex items-center gap-4">
96
+ {/* Device size presets */}
97
+ <div className="flex items-center gap-1 border-r border-stone-200 dark:border-stone-700 pr-4">
98
+ <button
99
+ onClick={() => setWidthPercent(30)}
100
+ className={`p-1.5 rounded transition-colors ${
101
+ widthPercent <= 40
102
+ ? 'bg-stone-200 dark:bg-stone-700 text-stone-900 dark:text-stone-100'
103
+ : 'text-stone-400 dark:text-stone-500 hover:text-stone-600 dark:hover:text-stone-300 hover:bg-stone-100 dark:hover:bg-stone-800'
104
+ }`}
105
+ title="Mobile (30%)"
106
+ >
107
+ <Smartphone className="w-4 h-4" strokeWidth={1.5} />
108
+ </button>
109
+ <button
110
+ onClick={() => setWidthPercent(60)}
111
+ className={`p-1.5 rounded transition-colors ${
112
+ widthPercent > 40 && widthPercent <= 60
113
+ ? 'bg-stone-200 dark:bg-stone-700 text-stone-900 dark:text-stone-100'
114
+ : 'text-stone-400 dark:text-stone-500 hover:text-stone-600 dark:hover:text-stone-300 hover:bg-stone-100 dark:hover:bg-stone-800'
115
+ }`}
116
+ title="Tablet (60%)"
117
+ >
118
+ <Tablet className="w-4 h-4" strokeWidth={1.5} />
119
+ </button>
120
+ <button
121
+ onClick={() => setWidthPercent(100)}
122
+ className={`p-1.5 rounded transition-colors ${
123
+ widthPercent > 60
124
+ ? 'bg-stone-200 dark:bg-stone-700 text-stone-900 dark:text-stone-100'
125
+ : 'text-stone-400 dark:text-stone-500 hover:text-stone-600 dark:hover:text-stone-300 hover:bg-stone-100 dark:hover:bg-stone-800'
126
+ }`}
127
+ title="Desktop (100%)"
128
+ >
129
+ <Monitor className="w-4 h-4" strokeWidth={1.5} />
130
+ </button>
131
+ </div>
132
+ <span className="text-xs text-stone-500 dark:text-stone-400 font-mono w-10 text-right">
133
+ {Math.round(widthPercent)}%
134
+ </span>
135
+ <ThemeToggle />
136
+ <a
137
+ href={`/sections/${sectionId}/screen-designs/${screenDesignName}/fullscreen`}
138
+ target="_blank"
139
+ rel="noopener noreferrer"
140
+ className="flex items-center gap-1.5 text-xs text-stone-500 dark:text-stone-400 hover:text-stone-700 dark:hover:text-stone-200 transition-colors"
141
+ >
142
+ <Maximize2 className="w-3.5 h-3.5" strokeWidth={1.5} />
143
+ Fullscreen
144
+ </a>
145
+ </div>
146
+ </div>
147
+ </header>
148
+
149
+ {/* Preview area with resizable container */}
150
+ <div
151
+ ref={containerRef}
152
+ className="flex-1 overflow-hidden flex items-stretch justify-center p-6"
153
+ >
154
+ {/* Left resize handle */}
155
+ <div
156
+ className="w-4 flex items-center justify-center cursor-ew-resize group shrink-0"
157
+ onMouseDown={handleMouseDown}
158
+ >
159
+ <div className="w-1 h-16 rounded-full bg-stone-300 dark:bg-stone-600 group-hover:bg-stone-400 dark:group-hover:bg-stone-500 transition-colors flex items-center justify-center">
160
+ <GripVertical className="w-3 h-3 text-stone-500 dark:text-stone-400 opacity-0 group-hover:opacity-100 transition-opacity" strokeWidth={2} />
161
+ </div>
162
+ </div>
163
+
164
+ {/* Preview container using iframe for true isolation */}
165
+ <div
166
+ className="bg-white dark:bg-stone-950 rounded-lg shadow-xl border border-stone-200 dark:border-stone-700 overflow-hidden"
167
+ style={{ width: previewWidth, minWidth: MIN_WIDTH, maxWidth: '100%' }}
168
+ >
169
+ <iframe
170
+ src={`/sections/${sectionId}/screen-designs/${screenDesignName}/fullscreen`}
171
+ className="w-full h-full border-0"
172
+ title="Screen Design Preview"
173
+ />
174
+ </div>
175
+
176
+ {/* Right resize handle */}
177
+ <div
178
+ className="w-4 flex items-center justify-center cursor-ew-resize group shrink-0"
179
+ onMouseDown={handleMouseDown}
180
+ >
181
+ <div className="w-1 h-16 rounded-full bg-stone-300 dark:bg-stone-600 group-hover:bg-stone-400 dark:group-hover:bg-stone-500 transition-colors flex items-center justify-center">
182
+ <GripVertical className="w-3 h-3 text-stone-500 dark:text-stone-400 opacity-0 group-hover:opacity-100 transition-opacity" strokeWidth={2} />
183
+ </div>
184
+ </div>
185
+ </div>
186
+ </div>
187
+ )
188
+ }
189
+
190
+ /**
191
+ * Fullscreen version of a screen design (for screenshots)
192
+ * Syncs theme with parent window via localStorage
193
+ * Wraps screen design in AppShell if shell components exist
194
+ */
195
+ export function ScreenDesignFullscreen() {
196
+ const { sectionId, screenDesignName } = useParams<{ sectionId: string; screenDesignName: string }>()
197
+
198
+ // Load screen design component
199
+ const ScreenDesignComponent = useMemo(() => {
200
+ if (!sectionId || !screenDesignName) return null
201
+ const loader = loadScreenDesignComponent(sectionId, screenDesignName)
202
+ if (!loader) return null
203
+ // Wrap the loader to handle potential export issues
204
+ return React.lazy(async () => {
205
+ try {
206
+ const module = await loader()
207
+ if (module && typeof module.default === 'function') {
208
+ return module
209
+ }
210
+ console.error('Screen design does not have a valid default export:', screenDesignName)
211
+ return { default: () => <div>Invalid screen design: {screenDesignName}</div> }
212
+ } catch (e) {
213
+ console.error('Failed to load screen design:', screenDesignName, e)
214
+ return { default: () => <div>Failed to load: {screenDesignName}</div> }
215
+ }
216
+ })
217
+ }, [sectionId, screenDesignName])
218
+
219
+ // Load AppShell component if it exists AND this section uses the shell
220
+ const AppShellComponent = useMemo(() => {
221
+ // Check if this section should use the shell (based on spec.md config)
222
+ if (sectionId && !sectionUsesShell(sectionId)) {
223
+ console.log('[ScreenDesignFullscreen] Section configured to not use shell')
224
+ return null
225
+ }
226
+
227
+ // Check if shell components exist
228
+ const shellExists = hasShellComponents()
229
+ console.log('[ScreenDesignFullscreen] Shell exists:', shellExists)
230
+ if (!shellExists) return null
231
+
232
+ const loader = loadAppShell()
233
+ console.log('[ScreenDesignFullscreen] AppShell loader:', loader)
234
+ if (!loader) {
235
+ console.warn('[ScreenDesignFullscreen] hasShellComponents() returned true but loadAppShell() returned null')
236
+ return null
237
+ }
238
+
239
+ // Wrap the loader to provide default props to the shell
240
+ return React.lazy(async () => {
241
+ try {
242
+ const module = await loader() as Record<string, unknown>
243
+ const ShellComponent = (module?.default || module?.AppShell) as React.ComponentType<Record<string, unknown>>
244
+
245
+ if (typeof ShellComponent !== 'function') {
246
+ console.warn('[ScreenDesignFullscreen] AppShell does not have a valid export')
247
+ return { default: ({ children }: { children?: React.ReactNode }) => <>{children}</> }
248
+ }
249
+
250
+ // Create a wrapper that provides default props to the shell
251
+ const ShellWrapper = ({ children }: { children?: React.ReactNode }) => {
252
+ // Try to get navigation items from shell spec
253
+ const shellInfo = loadShellInfo()
254
+ const specNavItems = shellInfo?.spec?.navigationItems || []
255
+
256
+ // Parse navigation items from spec (format: "**Label** → Description")
257
+ const navigationItems = specNavItems.length > 0
258
+ ? specNavItems.map((item, index) => {
259
+ // Extract label from **Label** format
260
+ const labelMatch = item.match(/\*\*([^*]+)\*\*/)
261
+ const label = labelMatch ? labelMatch[1] : item.split('→')[0]?.trim() || `Item ${index + 1}`
262
+ return {
263
+ label,
264
+ href: `/${label.toLowerCase().replace(/\s+/g, '-')}`,
265
+ isActive: index === 0,
266
+ }
267
+ })
268
+ : [
269
+ { label: 'Dashboard', href: '/', isActive: true },
270
+ { label: 'Items', href: '/items' },
271
+ { label: 'Settings', href: '/settings' },
272
+ ]
273
+
274
+ const defaultUser = {
275
+ name: 'Demo User',
276
+ }
277
+
278
+ // Pass props dynamically - the shell component decides what it needs
279
+ return (
280
+ <ShellComponent
281
+ navigationItems={navigationItems}
282
+ user={defaultUser}
283
+ onNavigate={() => {}}
284
+ onLogout={() => {}}
285
+ >
286
+ {children}
287
+ </ShellComponent>
288
+ )
289
+ }
290
+
291
+ return { default: ShellWrapper }
292
+ } catch (e) {
293
+ console.error('[ScreenDesignFullscreen] Failed to load AppShell:', e)
294
+ return { default: ({ children }: { children?: React.ReactNode }) => <>{children}</> }
295
+ }
296
+ })
297
+ }, [sectionId]) // Depends on sectionId to check section-specific shell config
298
+
299
+ // Sync theme with parent window
300
+ useEffect(() => {
301
+ const applyTheme = () => {
302
+ const theme = localStorage.getItem('theme') || 'system'
303
+ const root = document.documentElement
304
+
305
+ if (theme === 'system') {
306
+ const systemDark = window.matchMedia('(prefers-color-scheme: dark)').matches
307
+ root.classList.toggle('dark', systemDark)
308
+ } else {
309
+ root.classList.toggle('dark', theme === 'dark')
310
+ }
311
+ }
312
+
313
+ // Apply on mount
314
+ applyTheme()
315
+
316
+ // Listen for storage changes (from parent window)
317
+ const handleStorageChange = (e: StorageEvent) => {
318
+ if (e.key === 'theme') {
319
+ applyTheme()
320
+ }
321
+ }
322
+ window.addEventListener('storage', handleStorageChange)
323
+
324
+ // Also poll for changes since storage event doesn't fire in same window
325
+ const interval = setInterval(applyTheme, 100)
326
+
327
+ return () => {
328
+ window.removeEventListener('storage', handleStorageChange)
329
+ clearInterval(interval)
330
+ }
331
+ }, [])
332
+
333
+ if (!ScreenDesignComponent) {
334
+ return (
335
+ <div className="h-screen flex items-center justify-center bg-background">
336
+ <p className="text-stone-600 dark:text-stone-400">Screen design not found.</p>
337
+ </div>
338
+ )
339
+ }
340
+
341
+ // If shell exists, wrap screen design in AppShell
342
+ if (AppShellComponent) {
343
+ return (
344
+ <Suspense
345
+ fallback={
346
+ <div className="h-screen flex items-center justify-center bg-background">
347
+ <div className="text-stone-500 dark:text-stone-400">Loading...</div>
348
+ </div>
349
+ }
350
+ >
351
+ <AppShellComponent>
352
+ <ScreenDesignComponent />
353
+ </AppShellComponent>
354
+ </Suspense>
355
+ )
356
+ }
357
+
358
+ // No shell, render screen design directly
359
+ return (
360
+ <Suspense
361
+ fallback={
362
+ <div className="h-screen flex items-center justify-center bg-background">
363
+ <div className="text-stone-500 dark:text-stone-400">Loading...</div>
364
+ </div>
365
+ }
366
+ >
367
+ <ScreenDesignComponent />
368
+ </Suspense>
369
+ )
370
+ }
@@ -0,0 +1,49 @@
1
+ import { Link } from 'react-router-dom'
2
+ import { Card, CardContent, CardHeader, CardTitle } from '@/components/ui/card'
3
+ import { ChevronRight, Layout } from 'lucide-react'
4
+ import { EmptyState } from '@/components/EmptyState'
5
+ import type { ScreenDesignInfo } from '@/types/section'
6
+
7
+ interface ScreenDesignsCardProps {
8
+ screenDesigns: ScreenDesignInfo[]
9
+ sectionId: string
10
+ }
11
+
12
+ export function ScreenDesignsCard({ screenDesigns, sectionId }: ScreenDesignsCardProps) {
13
+ // Empty state
14
+ if (screenDesigns.length === 0) {
15
+ return <EmptyState type="screen-designs" />
16
+ }
17
+
18
+ return (
19
+ <Card className="border-stone-200 dark:border-stone-700 shadow-sm">
20
+ <CardHeader className="pb-4">
21
+ <CardTitle className="text-lg font-semibold text-stone-900 dark:text-stone-100">
22
+ Screen Designs
23
+ </CardTitle>
24
+ </CardHeader>
25
+ <CardContent className="p-0">
26
+ <ul className="divide-y divide-stone-200 dark:divide-stone-700">
27
+ {screenDesigns.map((screenDesign) => (
28
+ <li key={screenDesign.name}>
29
+ <Link
30
+ to={`/sections/${sectionId}/screen-designs/${screenDesign.name}`}
31
+ className="flex items-center justify-between gap-4 px-6 py-4 hover:bg-stone-50 dark:hover:bg-stone-800/50 transition-colors"
32
+ >
33
+ <div className="flex items-center gap-3 min-w-0">
34
+ <div className="w-8 h-8 rounded-md bg-stone-200 dark:bg-stone-700 flex items-center justify-center shrink-0">
35
+ <Layout className="w-4 h-4 text-stone-600 dark:text-stone-300" strokeWidth={1.5} />
36
+ </div>
37
+ <span className="font-medium text-stone-900 dark:text-stone-100 truncate">
38
+ {screenDesign.name}
39
+ </span>
40
+ </div>
41
+ <ChevronRight className="w-4 h-4 text-stone-400 dark:text-stone-500 shrink-0" strokeWidth={1.5} />
42
+ </Link>
43
+ </li>
44
+ ))}
45
+ </ul>
46
+ </CardContent>
47
+ </Card>
48
+ )
49
+ }