@theproductguy/create-mission-control 1.0.17 → 1.0.25

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 (51) hide show
  1. package/package.json +1 -1
  2. package/src/template/agent-os/WORKFLOW.md +139 -0
  3. package/src/template/agent-os/commands/adapt/adapt.md +189 -0
  4. package/src/template/agent-os/commands/animate/animate.md +184 -0
  5. package/src/template/agent-os/commands/audit/audit.md +123 -0
  6. package/src/template/agent-os/commands/bolder/bolder.md +126 -0
  7. package/src/template/agent-os/commands/clarify/clarify.md +173 -0
  8. package/src/template/agent-os/commands/colorize/colorize.md +152 -0
  9. package/src/template/agent-os/commands/critique/critique.md +112 -0
  10. package/src/template/agent-os/commands/delight/delight.md +311 -0
  11. package/src/template/agent-os/commands/design-screen/design-screen.md +5 -0
  12. package/src/template/agent-os/commands/design-shell/design-shell.md +5 -0
  13. package/src/template/agent-os/commands/design-tokens/design-tokens.md +5 -0
  14. package/src/template/agent-os/commands/extract/extract.md +88 -0
  15. package/src/template/agent-os/commands/harden/harden.md +351 -0
  16. package/src/template/agent-os/commands/impeccable/impeccable.md +163 -0
  17. package/src/template/agent-os/commands/normalize/normalize.md +61 -0
  18. package/src/template/agent-os/commands/onboard/onboard.md +236 -0
  19. package/src/template/agent-os/commands/optimize/optimize.md +262 -0
  20. package/src/template/agent-os/commands/plan-product/3-create-roadmap.md +7 -3
  21. package/src/template/agent-os/commands/polish/polish.md +196 -0
  22. package/src/template/agent-os/commands/quieter/quieter.md +112 -0
  23. package/src/template/agent-os/commands/simplify/simplify.md +131 -0
  24. package/src/template/agent-os/commands/teach-impeccable/teach-impeccable.md +67 -0
  25. package/src/template/control-center/backend/index.js +1 -1
  26. package/src/template/control-center/frontend/src/App.tsx +85 -839
  27. package/src/template/control-center/frontend/src/components/DesignOSOverlay.tsx +38 -0
  28. package/src/template/control-center/frontend/src/components/Guidance.tsx +56 -0
  29. package/src/template/control-center/frontend/src/components/NextStepCard.tsx +115 -0
  30. package/src/template/control-center/frontend/src/components/PromptButton.tsx +43 -0
  31. package/src/template/control-center/frontend/src/components/StatusItem.tsx +38 -0
  32. package/src/template/control-center/frontend/src/components/modals/CreateSpecModal.tsx +73 -0
  33. package/src/template/control-center/frontend/src/components/modals/DeleteSpecModal.tsx +42 -0
  34. package/src/template/control-center/frontend/src/components/modals/FileEditorModal.tsx +87 -0
  35. package/src/template/control-center/frontend/src/components/modals/SettingsModal.tsx +46 -0
  36. package/src/template/control-center/frontend/src/components/ui/ToastContext.tsx +1 -1
  37. package/src/template/control-center/frontend/src/contexts/IdeContext.tsx +6 -0
  38. package/src/template/control-center/frontend/src/hooks/useFileEditor.ts +42 -0
  39. package/src/template/control-center/frontend/src/hooks/useProjectState.ts +45 -0
  40. package/src/template/control-center/frontend/src/index.css +26 -0
  41. package/src/template/control-center/frontend/src/types.ts +65 -0
  42. package/src/template/control-center/frontend/tailwind.config.js +15 -3
  43. package/src/template/control-center/product/design-system/QA/audit-report.md +34 -0
  44. package/src/template/control-center/product/mission.md +38 -0
  45. package/src/template/control-center/product/roadmap.md +10 -0
  46. package/src/template/design-system/src/lib/product-loader.ts +6 -0
  47. package/src/template/package-lock.json +2756 -134
  48. package/src/template/package.json +2 -2
  49. package/src/template/agent-os-ui/_package.json +0 -54
  50. package/src/template/agent-os-ui/package.json +0 -54
  51. package/src/template/control-center/frontend/src/components/ThemeToggle.tsx +0 -64
@@ -0,0 +1,38 @@
1
+ import { Layers, X } from 'lucide-react';
2
+
3
+ interface DesignOSOverlayProps {
4
+ onClose: () => void;
5
+ }
6
+
7
+ export const DesignOSOverlay = ({ onClose }: DesignOSOverlayProps) => {
8
+ return (
9
+ <div className="fixed inset-0 z-[100] bg-background flex flex-col animate-fade-in w-screen h-screen">
10
+ <div className="h-14 border-b border-border flex items-center justify-between px-6 bg-card shrink-0 shadow-sm z-50">
11
+ <div className="flex items-center gap-3">
12
+ <div className="p-1.5 bg-sidebar-primary/10 text-sidebar-primary rounded-md">
13
+ <Layers size={20} />
14
+ </div>
15
+ <h2 className="font-semibold text-lg text-foreground">Design OS</h2>
16
+ <span className="text-muted-foreground text-sm border-l border-border pl-3">Design System & Handoff</span>
17
+ </div>
18
+ <div className="flex items-center gap-3">
19
+ <button
20
+ onClick={onClose}
21
+ className="p-2 text-muted-foreground hover:text-foreground hover:bg-secondary rounded-md transition-colors"
22
+ title="Close Design OS"
23
+ >
24
+ <X size={20} />
25
+ </button>
26
+ </div>
27
+ </div>
28
+ <div className="flex-1 bg-background relative">
29
+ <iframe
30
+ src={`http://localhost:5400?theme=${localStorage.getItem('theme') || 'system'}`}
31
+ className="absolute inset-0 w-full h-full border-none"
32
+ title="Design OS"
33
+ allow="clipboard-read; clipboard-write text/html" // updated per chrome warnings usually
34
+ />
35
+ </div>
36
+ </div >
37
+ );
38
+ };
@@ -0,0 +1,56 @@
1
+ import { ArrowRight, Copy } from 'lucide-react';
2
+ import { useToast } from '../components/ui/ToastContext';
3
+
4
+ interface GuidanceProps {
5
+ phase: string;
6
+ title: string;
7
+ description: string;
8
+ prompt?: string;
9
+ actionLabel?: string;
10
+ onAction?: () => void;
11
+ className?: string;
12
+ }
13
+
14
+ export const Guidance = ({ phase, title, description, prompt, actionLabel, onAction, className }: GuidanceProps) => {
15
+ const { toast } = useToast();
16
+
17
+ return (
18
+ <div className={`bg-card border border-border rounded-xl p-6 shadow-sm ${className}`}>
19
+ <div className="text-xs font-semibold tracking-wider text-muted-foreground uppercase mb-2">
20
+ {phase}
21
+ </div>
22
+ <div className="flex flex-col md:flex-row gap-6 items-start justify-between">
23
+ <div className="max-w-xl">
24
+ <h2 className="text-2xl font-bold text-foreground mb-2">{title}</h2>
25
+ <p className="text-muted-foreground leading-relaxed">
26
+ {description}
27
+ </p>
28
+ </div>
29
+ {(prompt || actionLabel) && (
30
+ <div className="flex items-center gap-3 shrink-0">
31
+ {prompt && (
32
+ <button
33
+ onClick={() => {
34
+ navigator.clipboard.writeText(prompt);
35
+ toast({ title: "Prompt Copied!", type: 'success' });
36
+ }}
37
+ className="flex items-center gap-2 px-4 py-2.5 bg-secondary hover:bg-secondary/80 text-secondary-foreground font-medium rounded-lg border border-border transition-all shadow-sm hover:shadow"
38
+ >
39
+ <Copy size={16} />
40
+ Copy Prompt
41
+ </button>
42
+ )}
43
+ {actionLabel && (
44
+ <button
45
+ onClick={onAction}
46
+ className="flex items-center gap-2 px-4 py-2.5 bg-primary hover:bg-primary/90 text-primary-foreground font-medium rounded-lg shadow-sm hover:shadow-md transition-all"
47
+ >
48
+ {actionLabel} <ArrowRight size={16} />
49
+ </button>
50
+ )}
51
+ </div>
52
+ )}
53
+ </div>
54
+ </div>
55
+ );
56
+ };
@@ -0,0 +1,115 @@
1
+ import type { ProjectState } from '../types';
2
+ import { Guidance } from './Guidance';
3
+
4
+ interface NextStepCardProps {
5
+ state: ProjectState;
6
+ onOpenDesign: () => void;
7
+ }
8
+
9
+ export const NextStepCard = ({ state, onOpenDesign }: NextStepCardProps) => {
10
+ let step = { phase: "", title: "", description: "", prompt: "", link: "", actionLabel: "", action: () => { } };
11
+
12
+ const isProductComplete =
13
+ state?.product?.mission?.exists && !state?.product?.mission?.isBoilerplate &&
14
+ state?.product?.techStack?.exists && !state?.product?.techStack?.isBoilerplate &&
15
+ state?.product?.roadmap?.exists && !state?.product?.roadmap?.isBoilerplate;
16
+
17
+ if (!isProductComplete) {
18
+ step = {
19
+ phase: "Phase 1: Product Strategy",
20
+ title: "Plan Your Product",
21
+ description: "Define your Mission, Roadmap, and Tech Stack to build a solid foundation.",
22
+ prompt: "Antigravity, let's start Phase 1: Product Planning. Please read 'agent-os/commands/plan-product/plan-product.md' and guide me.",
23
+ link: "",
24
+ actionLabel: "",
25
+ action: () => { }
26
+ }
27
+ } else if (!state?.design?.initialized) {
28
+ step = {
29
+ phase: "Phase 2: Design System",
30
+ title: "Sync Product to Design OS",
31
+ description: "Transfer your Product Mission and Roadmap to Design OS to automate the setup.",
32
+ prompt: "Antigravity, please sync my product plan to Design OS. Read 'agent-os/commands/initialize-design/initialize-design.md'.",
33
+ link: "",
34
+ actionLabel: "",
35
+ action: () => { }
36
+ }
37
+ } else if (!state?.design?.exported) {
38
+ step = {
39
+ phase: "Phase 2: Design System",
40
+ title: "Define Your Visuals",
41
+ description: "Defining the visuals now prevents generic UI later. Export your system when ready.",
42
+ prompt: "",
43
+ link: "",
44
+ actionLabel: "Open Design OS",
45
+ action: onOpenDesign
46
+ };
47
+ } else if (!state?.implementation?.scaffolded) {
48
+ step = {
49
+ phase: "Phase 3: Implementation",
50
+ title: "Scaffold Application",
51
+ description: "Your design is exported. Now, scaffold the production app with the design system.",
52
+ prompt: "Antigravity, scaffold the implementation. Read 'agent-os/commands/scaffold-implementation/scaffold-implementation.md'.",
53
+ link: "",
54
+ actionLabel: "",
55
+ action: () => { }
56
+ };
57
+ } else if (state?.specs?.length === 0) {
58
+ step = {
59
+ phase: "Phase 4: Feature Specs",
60
+ title: "Shape Your First Spec",
61
+ description: "Your app is ready! Create a spec for the first feature in your roadmap.",
62
+ prompt: "Antigravity, let's shape the spec for a new feature. Please read 'agent-os/commands/shape-spec/shape-spec.md'.",
63
+ link: "",
64
+ actionLabel: "",
65
+ action: () => { }
66
+ };
67
+ } else {
68
+ const incompleteSpec = state?.specs?.find(s => s.tasks.completed < s.tasks.total || !s.tasks.exists);
69
+ if (incompleteSpec) {
70
+ step = {
71
+ phase: "Phase 4: Feature Specs",
72
+ title: `Implement '${incompleteSpec.name}'`,
73
+ description: `Active spec detected. Write the code!`,
74
+ prompt: `Antigravity, implement the tasks for '${incompleteSpec.name}'. Read commands/implement-tasks/implement-tasks.md.`,
75
+ link: "",
76
+ actionLabel: "",
77
+ action: () => { }
78
+ };
79
+ } else if (state?.product?.roadmap?.nextItem && !state?.product?.roadmap?.isBoilerplate) {
80
+ const rawName = state?.product?.roadmap?.nextItem;
81
+ const cleanName = rawName.replace(/\*\*/g, '').replace(/^\d+\.\s*/, '').split('\n')[0].trim();
82
+ step = {
83
+ phase: "Phase 4: Feature Specs",
84
+ title: `Shape '${cleanName}'`,
85
+ description: `Ready to start the next feature? Shape the spec now.`,
86
+ prompt: `Antigravity, let's shape the spec for '${cleanName}'. Please read 'agent-os/commands/shape-spec/shape-spec.md'.`,
87
+ link: "",
88
+ actionLabel: "",
89
+ action: () => { }
90
+ };
91
+ } else {
92
+ step = {
93
+ phase: "Phase 4: Feature Specs",
94
+ title: "Verify or Plan Next",
95
+ description: "Verify implementation or start a new feature.",
96
+ prompt: "Antigravity, let's verify the implementation. Read 'agent-os/commands/implement-tasks/3-verify-implementation.md'.",
97
+ link: "",
98
+ actionLabel: "",
99
+ action: () => { }
100
+ }
101
+ }
102
+ }
103
+
104
+ return (
105
+ <Guidance
106
+ phase={step.phase}
107
+ title={step.title}
108
+ description={step.description}
109
+ prompt={step.prompt}
110
+ actionLabel={step.actionLabel}
111
+ onAction={step.action}
112
+ className="mb-8"
113
+ />
114
+ );
115
+ };
@@ -0,0 +1,43 @@
1
+ import { Copy, Play } from 'lucide-react';
2
+ import { useContext } from 'react';
3
+ import { IdeContext } from '../contexts/IdeContext';
4
+
5
+ interface PromptButtonProps {
6
+ label: string;
7
+ prompt: string;
8
+ onClick: (text: string) => void;
9
+ small?: boolean;
10
+ primary?: boolean;
11
+ }
12
+
13
+ export function PromptButton({ label, prompt, onClick, small, primary }: PromptButtonProps) {
14
+ const { scheme } = useContext(IdeContext);
15
+ const deepLink = `${scheme}://vscode.executeCommand/workbench.action.chat.open?query=${encodeURIComponent(prompt)}`;
16
+
17
+ return (
18
+ <div className={`flex gap-1 items-center ${!small ? 'w-full' : 'flex-1'}`}>
19
+ <button
20
+ onClick={() => onClick(prompt)}
21
+ className={`flex items-center justify-center gap-2 rounded-lg font-medium transition cursor-pointer flex-1
22
+ ${small ? 'px-3 py-1.5 text-xs' : 'px-4 py-2 text-sm'}
23
+ ${primary
24
+ ? 'bg-primary hover:bg-primary/90 text-primary-foreground shadow-sm'
25
+ : 'bg-background hover:bg-secondary border border-border text-foreground hover:text-foreground'}
26
+ `}
27
+ >
28
+ <Copy size={small ? 12 : 14} />
29
+ {label}
30
+ </button>
31
+
32
+ <a
33
+ href={deepLink}
34
+ className={`flex items-center justify-center rounded-lg border border-border bg-secondary hover:bg-secondary/80 text-foreground transition
35
+ ${small ? 'w-8 h-[30px]' : 'w-10 h-[38px]'}
36
+ `}
37
+ title={`Run in IDE (${scheme}://)`}
38
+ >
39
+ <Play size={small ? 12 : 14} fill="currentColor" className="opacity-70" />
40
+ </a>
41
+ </div>
42
+ )
43
+ }
@@ -0,0 +1,38 @@
1
+ import type { Status } from '../types';
2
+
3
+ interface StatusItemProps {
4
+ label: string;
5
+ status?: Status;
6
+ icon: React.ReactNode;
7
+ small?: boolean;
8
+ onClick?: () => void;
9
+ }
10
+
11
+ export function StatusItem({ label, status, icon, small, onClick }: StatusItemProps) {
12
+ if (!status) return null;
13
+ const isComplete = status.exists && (status.total > 0 ? status.completed === status.total : true);
14
+
15
+ return (
16
+ <div
17
+ onClick={onClick}
18
+ className={`flex items-center justify-between ${small ? 'text-xs' : 'text-sm'} ${onClick ? 'cursor-pointer hover:bg-secondary/50 p-1.5 -mx-1.5 rounded-md transition-colors group' : ''}`}
19
+ >
20
+ <div className="flex items-center gap-2 text-muted-foreground group-hover:text-foreground transition-colors">
21
+ {icon}
22
+ <span>{label}</span>
23
+ </div>
24
+ <div>
25
+ {status.exists && !status.isBoilerplate ? (
26
+ <span className={`px-2 py-0.5 rounded-full text-xs font-medium border ${isComplete
27
+ ? 'bg-emerald-50 dark:bg-emerald-900/20 text-emerald-600 border-emerald-200 dark:border-emerald-800'
28
+ : 'bg-secondary text-muted-foreground border-border'
29
+ }`}>
30
+ {isComplete ? 'Done' : 'Pending'}
31
+ </span>
32
+ ) : (
33
+ <span className="text-muted-foreground bg-secondary px-2 py-0.5 rounded text-xs font-medium border border-border">Pending</span>
34
+ )}
35
+ </div>
36
+ </div>
37
+ );
38
+ }
@@ -0,0 +1,73 @@
1
+ import axios from 'axios';
2
+ import { useState } from 'react';
3
+ import { useToast } from '../../components/ui/ToastContext';
4
+
5
+ interface CreateSpecModalProps {
6
+ runtimeConfig: any;
7
+ onClose: () => void;
8
+ onSuccess: () => void;
9
+ openFile: (path: string, title: string) => void;
10
+ }
11
+
12
+ export const CreateSpecModal = ({ runtimeConfig, onClose, onSuccess, openFile }: CreateSpecModalProps) => {
13
+ const [newSpecName, setNewSpecName] = useState("");
14
+ const { toast } = useToast();
15
+
16
+ const handleCreate = async () => {
17
+ if (newSpecName.trim()) {
18
+ const name = newSpecName.trim();
19
+ if (runtimeConfig?.api) {
20
+ await axios.post(`${runtimeConfig.api}/api/scaffold/spec`, { name });
21
+ const prompt = `Antigravity, let's shape the spec for '${name}'. Read commands/shape-spec/shape-spec.md.`;
22
+ navigator.clipboard.writeText(prompt);
23
+ setNewSpecName("");
24
+ onSuccess();
25
+ openFile(`specs/${name}/spec.md`, `${name} Spec`);
26
+ toast({ title: "Spec created & Prompt copied!", type: 'success' });
27
+ onClose();
28
+ }
29
+ }
30
+ };
31
+
32
+ return (
33
+ <div className="fixed inset-0 z-[200] bg-black/50 backdrop-blur-sm flex items-center justify-center p-6 animate-in fade-in">
34
+ <div className="bg-card border border-border w-full max-w-md rounded-xl shadow-2xl flex flex-col overflow-hidden animate-in zoom-in-95 duration-200">
35
+ <div className="px-6 py-4 border-b border-border bg-muted/30">
36
+ <h3 className="font-semibold text-lg">New Feature Spec</h3>
37
+ <p className="text-sm text-muted-foreground">Shape a new feature for your project.</p>
38
+ </div>
39
+ <div className="p-6">
40
+ <div className="space-y-4">
41
+ <div>
42
+ <label className="text-sm font-medium mb-1.5 block">Feature Name</label>
43
+ <input
44
+ autoFocus
45
+ type="text"
46
+ className="w-full px-3 py-2 rounded-md border border-input bg-secondary text-secondary-foreground text-sm ring-offset-background placeholder:text-muted-foreground focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-ring"
47
+ placeholder="e.g. user-profile"
48
+ value={newSpecName}
49
+ onChange={(e) => setNewSpecName(e.target.value)}
50
+ onKeyDown={(e) => {
51
+ if (e.key === 'Enter') handleCreate();
52
+ }}
53
+ />
54
+ <p className="text-xs text-muted-foreground mt-1.5">
55
+ This will create a new directory in <code>specs/</code> and copy the prompt to your clipboard.
56
+ </p>
57
+ </div>
58
+ <div className="flex justify-end gap-2 pt-2">
59
+ <button onClick={onClose} className="px-4 py-2 text-sm font-medium rounded-md hover:bg-secondary transition-colors">Cancel</button>
60
+ <button
61
+ disabled={!newSpecName.trim()}
62
+ onClick={handleCreate}
63
+ className="px-4 py-2 text-sm font-medium rounded-md bg-primary text-primary-foreground hover:bg-primary/90 transition-colors disabled:opacity-50 disabled:cursor-not-allowed"
64
+ >
65
+ Create Spec
66
+ </button>
67
+ </div>
68
+ </div>
69
+ </div>
70
+ </div>
71
+ </div>
72
+ );
73
+ };
@@ -0,0 +1,42 @@
1
+ import { Trash2 } from 'lucide-react';
2
+ import axios from 'axios';
3
+
4
+ interface DeleteSpecModalProps {
5
+ specName: string;
6
+ runtimeConfig: any;
7
+ onClose: () => void;
8
+ onSuccess: () => void;
9
+ }
10
+
11
+ export const DeleteSpecModal = ({ specName, runtimeConfig, onClose, onSuccess }: DeleteSpecModalProps) => {
12
+ return (
13
+ <div className="fixed inset-0 z-[200] bg-black/50 backdrop-blur-sm flex items-center justify-center p-6 animate-in fade-in">
14
+ <div className="bg-card border border-border w-full max-w-sm rounded-xl shadow-2xl flex flex-col overflow-hidden animate-in zoom-in-95 duration-200">
15
+ <div className="p-6">
16
+ <div className="flex items-center gap-3 mb-4 text-red-500">
17
+ <div className="p-2 bg-red-100 dark:bg-red-900/20 rounded-full">
18
+ <Trash2 size={24} />
19
+ </div>
20
+ <h3 className="font-semibold text-lg text-foreground">Delete Spec?</h3>
21
+ </div>
22
+ <p className="text-muted-foreground mb-6">
23
+ Are you sure you want to delete <strong>{specName}</strong>? This action cannot be undone and will permanently delete the spec files.
24
+ </p>
25
+ <div className="flex justify-end gap-2">
26
+ <button onClick={onClose} className="px-4 py-2 text-sm font-medium rounded-md hover:bg-secondary transition-colors">Cancel</button>
27
+ <button
28
+ onClick={async () => {
29
+ await axios.delete(`${runtimeConfig.api}/api/specs/${specName}`);
30
+ onSuccess();
31
+ onClose();
32
+ }}
33
+ className="px-4 py-2 text-sm font-medium rounded-md bg-red-500 hover:bg-red-600 text-white transition-colors"
34
+ >
35
+ Delete Spec
36
+ </button>
37
+ </div>
38
+ </div>
39
+ </div>
40
+ </div>
41
+ );
42
+ };
@@ -0,0 +1,87 @@
1
+ import { FileText, X } from 'lucide-react';
2
+ import ReactMarkdown from 'react-markdown';
3
+ import remarkGfm from 'remark-gfm';
4
+ import remarkBreaks from 'remark-breaks';
5
+ import { useState, useEffect } from 'react';
6
+
7
+ interface FileEditorModalProps {
8
+ viewingFile: { path: string, title: string };
9
+ content: string | null;
10
+ onClose: () => void;
11
+ onSave: (content: string) => Promise<void>;
12
+ }
13
+
14
+ export const FileEditorModal = ({ viewingFile, content, onClose, onSave }: FileEditorModalProps) => {
15
+ const [isEditing, setIsEditing] = useState(false);
16
+ const [editContent, setEditContent] = useState("");
17
+
18
+ useEffect(() => {
19
+ if (content) setEditContent(content);
20
+ }, [content]);
21
+
22
+ return (
23
+ <div className="fixed inset-0 z-[200] bg-black/50 backdrop-blur-sm flex items-center justify-center p-6 animate-in fade-in">
24
+ <div className="bg-card border border-border w-full max-w-4xl max-h-[90vh] rounded-xl shadow-2xl flex flex-col overflow-hidden animate-in zoom-in-95 duration-200">
25
+ <div className="flex items-center justify-between px-6 py-4 border-b border-border bg-muted/30">
26
+ <div className="flex items-center gap-3">
27
+ <div className="p-2 bg-primary/10 text-primary rounded-md">
28
+ <FileText size={18} />
29
+ </div>
30
+ <div>
31
+ <h3 className="font-semibold text-lg">{viewingFile.title}</h3>
32
+ <code className="text-xs text-muted-foreground font-mono">{viewingFile.path}</code>
33
+ </div>
34
+ </div>
35
+ <div className="flex items-center gap-2">
36
+ {!isEditing ? (
37
+ <button
38
+ onClick={() => setIsEditing(true)}
39
+ className="px-3 py-1.5 text-sm font-medium bg-secondary hover:bg-secondary/80 rounded-md transition-colors"
40
+ >
41
+ Edit
42
+ </button>
43
+ ) : (
44
+ <>
45
+ <button
46
+ onClick={() => setIsEditing(false)}
47
+ className="px-3 py-1.5 text-sm font-medium hover:bg-secondary rounded-md transition-colors"
48
+ >
49
+ Cancel
50
+ </button>
51
+ <button
52
+ onClick={async () => {
53
+ await onSave(editContent);
54
+ setIsEditing(false);
55
+ }}
56
+ className="px-3 py-1.5 text-sm font-medium bg-primary text-primary-foreground hover:bg-primary/90 rounded-md transition-colors"
57
+ >
58
+ Save Changes
59
+ </button>
60
+ </>
61
+ )}
62
+ <button onClick={onClose} className="p-2 hover:bg-secondary rounded-full transition-colors ml-2">
63
+ <X size={20} />
64
+ </button>
65
+ </div>
66
+ </div>
67
+ <div className="flex-1 overflow-y-auto p-6 bg-background">
68
+ {content === "Loading..." || content === "Error loading file." ? (
69
+ <div className="font-mono text-sm">{content}</div>
70
+ ) : isEditing ? (
71
+ <textarea
72
+ value={editContent}
73
+ onChange={(e) => setEditContent(e.target.value)}
74
+ className="w-full h-full min-h-[500px] p-4 font-mono text-sm bg-secondary text-secondary-foreground border border-border rounded-lg resize-none focus:outline-none focus:ring-2 focus:ring-primary/20"
75
+ />
76
+ ) : (
77
+ <article className="prose dark:prose-invert max-w-none prose-headings:font-serif prose-headings:font-semibold prose-a:text-primary prose-code:text-primary prose-pre:bg-secondary/50 prose-pre:border prose-pre:border-border prose-p:leading-relaxed prose-p:mb-4">
78
+ <ReactMarkdown remarkPlugins={[remarkGfm, remarkBreaks]}>
79
+ {content || ''}
80
+ </ReactMarkdown>
81
+ </article>
82
+ )}
83
+ </div>
84
+ </div>
85
+ </div>
86
+ );
87
+ };
@@ -0,0 +1,46 @@
1
+ import { X, Settings } from 'lucide-react';
2
+ import { useContext } from 'react';
3
+ import { IdeContext } from '../../contexts/IdeContext';
4
+
5
+ interface SettingsModalProps {
6
+ onClose: () => void;
7
+ }
8
+
9
+ export const SettingsModal = ({ onClose }: SettingsModalProps) => {
10
+ const { scheme, setScheme } = useContext(IdeContext);
11
+
12
+ return (
13
+ <div className="fixed inset-0 z-[200] bg-black/50 backdrop-blur-sm flex items-center justify-center p-6 animate-in fade-in">
14
+ <div className="bg-card border border-border w-full max-w-sm rounded-xl shadow-2xl flex flex-col overflow-hidden animate-in zoom-in-95 duration-200">
15
+ <div className="p-6 border-b border-border bg-muted/30 flex items-center justify-between">
16
+ <h3 className="font-semibold text-lg flex items-center gap-2"><Settings size={18} /> Settings</h3>
17
+ <button onClick={onClose} className="hover:bg-secondary p-1 rounded"><X size={18} /></button>
18
+ </div>
19
+ <div className="p-6 space-y-4">
20
+ <div>
21
+ <label className="text-sm font-medium mb-2 block">IDE / Editor</label>
22
+ <div className="space-y-2">
23
+ {['vscode', 'cursor', 'windsurf', 'antigravity'].map((s) => (
24
+ <button
25
+ key={s}
26
+ onClick={() => setScheme(s)}
27
+ className={`w-full flex items-center justify-between px-4 py-3 rounded-lg border transition-all ${scheme === s
28
+ ? 'bg-primary/10 border-primary text-primary'
29
+ : 'bg-secondary/20 border-border hover:border-foreground/20'
30
+ }`}
31
+ >
32
+ <span className="capitalize font-medium">{s}</span>
33
+ {scheme === s && <div className="w-2 h-2 rounded-full bg-primary" />}
34
+ </button>
35
+ ))}
36
+ </div>
37
+ <p className="text-xs text-muted-foreground mt-3">
38
+ Select the editor to open when clicking "Play".
39
+ <br />Currently using: <code>{scheme}://</code>
40
+ </p>
41
+ </div>
42
+ </div>
43
+ </div>
44
+ </div>
45
+ );
46
+ };
@@ -1,4 +1,4 @@
1
- import React, { createContext, useContext, useState, useCallback, ReactNode } from 'react';
1
+ import { createContext, useContext, useState, useCallback, type ReactNode } from 'react';
2
2
  import { X, CheckCircle, Info, AlertTriangle } from 'lucide-react';
3
3
 
4
4
  type ToastType = 'success' | 'error' | 'info' | 'warning';
@@ -0,0 +1,6 @@
1
+ import { createContext } from 'react';
2
+
3
+ export const IdeContext = createContext({
4
+ scheme: 'vscode',
5
+ setScheme: (_s: string) => { }
6
+ });
@@ -0,0 +1,42 @@
1
+ import { useState } from 'react';
2
+ import axios from 'axios';
3
+ import { useToast } from '../components/ui/ToastContext';
4
+
5
+ export const useFileEditor = (runtimeConfig: any, fetchStatus: () => void) => {
6
+ const [viewingFile, setViewingFile] = useState<{ path: string, title: string } | null>(null);
7
+ const [fileContent, setFileContent] = useState<string | null>(null);
8
+ const { toast } = useToast();
9
+
10
+ const openFile = async (path: string, title: string) => {
11
+ setViewingFile({ path, title });
12
+ setFileContent("Loading...");
13
+ try {
14
+ const res = await axios.get(`${runtimeConfig.api}/api/files?path=${encodeURIComponent(path)}`);
15
+ setFileContent(res.data.content);
16
+ } catch (error) {
17
+ setFileContent("Error loading file.");
18
+ }
19
+ };
20
+
21
+ const saveFile = async (content: string) => {
22
+ if (!viewingFile || !runtimeConfig) return;
23
+ try {
24
+ await axios.post(`${runtimeConfig.api}/api/files`, {
25
+ path: viewingFile.path,
26
+ content: content
27
+ });
28
+ setFileContent(content);
29
+ toast({ title: "File Saved!", type: "success" });
30
+ fetchStatus();
31
+ } catch (error) {
32
+ toast({ title: "Failed to save file", type: "error" });
33
+ }
34
+ };
35
+
36
+ const closeFile = () => {
37
+ setViewingFile(null);
38
+ setFileContent(null);
39
+ };
40
+
41
+ return { viewingFile, fileContent, openFile, saveFile, closeFile };
42
+ };
@@ -0,0 +1,45 @@
1
+ import { useState, useEffect } from 'react';
2
+ import axios from 'axios';
3
+ import type { ProjectState } from '../types';
4
+
5
+ export const useProjectState = () => {
6
+ const [state, setState] = useState<ProjectState | null>(null);
7
+ const [loading, setLoading] = useState(true);
8
+ const [runtimeConfig, setRuntimeConfig] = useState<any>(null);
9
+
10
+ useEffect(() => {
11
+ fetch('/runtime-config.json')
12
+ .then(res => res.json())
13
+ .then(config => { setRuntimeConfig(config); })
14
+ .catch(() => {
15
+ setRuntimeConfig({
16
+ api: 'http://localhost:5403',
17
+ app: 'http://localhost:5402',
18
+ design: 'http://localhost:5400',
19
+ ports: { api: 5403, app: 5402, design: 5400 }
20
+ });
21
+ });
22
+ }, []);
23
+
24
+ const fetchStatus = async () => {
25
+ if (!runtimeConfig) return;
26
+ try {
27
+ const res = await axios.get(`${runtimeConfig.api}/api/status`);
28
+ setState(res.data);
29
+ } catch (error) {
30
+ console.error(error);
31
+ } finally {
32
+ setLoading(false);
33
+ }
34
+ };
35
+
36
+ useEffect(() => {
37
+ if (runtimeConfig) {
38
+ fetchStatus();
39
+ const interval = setInterval(fetchStatus, 2000);
40
+ return () => clearInterval(interval);
41
+ }
42
+ }, [runtimeConfig]);
43
+
44
+ return { state, loading, runtimeConfig, fetchStatus };
45
+ };