@bamptee/aia-code 0.10.0 → 1.0.1

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.
@@ -0,0 +1,160 @@
1
+ import React from 'react';
2
+ import { api } from '/main.js';
3
+
4
+ function YamlEditor({ title, loadFn, saveFn }) {
5
+ const [content, setContent] = React.useState('');
6
+ const [loading, setLoading] = React.useState(true);
7
+ const [saving, setSaving] = React.useState(false);
8
+ const [dirty, setDirty] = React.useState(false);
9
+ const [msg, setMsg] = React.useState(null);
10
+
11
+ React.useEffect(() => {
12
+ loadFn().then(c => setContent(c)).catch(() => setContent('')).finally(() => setLoading(false));
13
+ }, []);
14
+
15
+ async function save() {
16
+ setSaving(true);
17
+ setMsg(null);
18
+ try {
19
+ await saveFn(content);
20
+ setDirty(false);
21
+ setMsg({ type: 'ok', text: 'Saved.' });
22
+ } catch (e) {
23
+ setMsg({ type: 'err', text: e.message });
24
+ }
25
+ setSaving(false);
26
+ }
27
+
28
+ if (loading) return React.createElement('p', { className: 'text-slate-500 text-sm' }, 'Loading...');
29
+
30
+ return React.createElement('div', { className: 'space-y-2' },
31
+ React.createElement('div', { className: 'flex items-center justify-between' },
32
+ React.createElement('h3', { className: 'text-sm font-semibold text-slate-300' }, title),
33
+ React.createElement('div', { className: 'flex gap-2 items-center' },
34
+ dirty && React.createElement('span', { className: 'text-xs text-amber-400' }, 'unsaved'),
35
+ msg && React.createElement('span', { className: `text-xs ${msg.type === 'ok' ? 'text-emerald-400' : 'text-red-400'}` }, msg.text),
36
+ React.createElement('button', {
37
+ onClick: save,
38
+ disabled: saving || !dirty,
39
+ className: 'bg-aia-accent/20 text-aia-accent border border-aia-accent/30 rounded px-3 py-1 text-xs hover:bg-aia-accent/30 disabled:opacity-40',
40
+ }, saving ? '...' : 'Save'),
41
+ ),
42
+ ),
43
+ React.createElement('textarea', {
44
+ value: content,
45
+ onChange: e => { setContent(e.target.value); setDirty(true); },
46
+ spellCheck: false,
47
+ className: 'w-full h-72 bg-slate-900 border border-aia-border rounded p-3 text-sm text-slate-300 font-mono resize-y focus:border-aia-accent focus:outline-none',
48
+ })
49
+ );
50
+ }
51
+
52
+ function FileList({ title, files, selectedFile, onSelect }) {
53
+ if (!files.length) return null;
54
+ return React.createElement('div', { className: 'space-y-1' },
55
+ React.createElement('h4', { className: 'text-xs font-semibold text-slate-400 uppercase tracking-wider' }, title),
56
+ ...files.map(f =>
57
+ React.createElement('button', {
58
+ key: f,
59
+ onClick: () => onSelect(f),
60
+ className: `block w-full text-left px-2 py-1 text-sm rounded ${selectedFile === f ? 'bg-aia-accent/20 text-aia-accent' : 'text-slate-400 hover:text-slate-200 hover:bg-slate-800'}`,
61
+ }, f)
62
+ )
63
+ );
64
+ }
65
+
66
+ export function ConfigView() {
67
+ const [contextFiles, setContextFiles] = React.useState([]);
68
+ const [knowledgeCategories, setKnowledgeCategories] = React.useState([]);
69
+ const [selectedFile, setSelectedFile] = React.useState(null);
70
+ const [selectedType, setSelectedType] = React.useState(null); // 'context' | 'knowledge'
71
+ const [selectedCategory, setSelectedCategory] = React.useState(null);
72
+ const [logs, setLogs] = React.useState('');
73
+ const [showLogs, setShowLogs] = React.useState(false);
74
+
75
+ React.useEffect(() => {
76
+ api.get('/context').then(setContextFiles).catch(() => {});
77
+ api.get('/knowledge').then(setKnowledgeCategories).catch(() => {});
78
+ }, []);
79
+
80
+ function selectContext(f) {
81
+ setSelectedFile(f);
82
+ setSelectedType('context');
83
+ setSelectedCategory(null);
84
+ }
85
+
86
+ function selectKnowledge(cat, f) {
87
+ setSelectedFile(f);
88
+ setSelectedType('knowledge');
89
+ setSelectedCategory(cat);
90
+ }
91
+
92
+ async function loadLogs() {
93
+ try {
94
+ const data = await api.get('/logs');
95
+ setLogs(data.content || '(empty)');
96
+ setShowLogs(true);
97
+ } catch {}
98
+ }
99
+
100
+ return React.createElement('div', { className: 'space-y-6' },
101
+ React.createElement('h1', { className: 'text-xl font-bold text-slate-100' }, 'Configuration'),
102
+
103
+ // config.yaml
104
+ React.createElement(YamlEditor, {
105
+ title: 'config.yaml',
106
+ loadFn: async () => (await api.get('/config')).content,
107
+ saveFn: async (content) => api.put('/config', { content }),
108
+ }),
109
+
110
+ // Sidebar + editor for context/knowledge
111
+ React.createElement('div', { className: 'grid grid-cols-4 gap-4' },
112
+ // Sidebar
113
+ React.createElement('div', { className: 'col-span-1 space-y-4' },
114
+ React.createElement(FileList, {
115
+ title: 'Context',
116
+ files: contextFiles,
117
+ selectedFile: selectedType === 'context' ? selectedFile : null,
118
+ onSelect: selectContext,
119
+ }),
120
+ ...knowledgeCategories.map(cat =>
121
+ React.createElement(FileList, {
122
+ key: cat.name,
123
+ title: `Knowledge / ${cat.name}`,
124
+ files: cat.files,
125
+ selectedFile: selectedType === 'knowledge' && selectedCategory === cat.name ? selectedFile : null,
126
+ onSelect: f => selectKnowledge(cat.name, f),
127
+ })
128
+ ),
129
+ React.createElement('button', {
130
+ onClick: loadLogs,
131
+ className: 'text-xs text-slate-500 hover:text-slate-300 mt-4',
132
+ }, 'View execution logs'),
133
+ ),
134
+
135
+ // Editor
136
+ React.createElement('div', { className: 'col-span-3' },
137
+ selectedFile && selectedType === 'context' && React.createElement(YamlEditor, {
138
+ key: `ctx-${selectedFile}`,
139
+ title: `context/${selectedFile}`,
140
+ loadFn: async () => (await api.get(`/context/${selectedFile}`)).content,
141
+ saveFn: async (content) => api.put(`/context/${selectedFile}`, { content }),
142
+ }),
143
+ selectedFile && selectedType === 'knowledge' && React.createElement(YamlEditor, {
144
+ key: `kn-${selectedCategory}-${selectedFile}`,
145
+ title: `knowledge/${selectedCategory}/${selectedFile}`,
146
+ loadFn: async () => (await api.get(`/knowledge/${selectedCategory}/${selectedFile}`)).content,
147
+ saveFn: async (content) => api.put(`/knowledge/${selectedCategory}/${selectedFile}`, { content }),
148
+ }),
149
+ !selectedFile && !showLogs && React.createElement('p', { className: 'text-slate-500 text-sm' }, 'Select a file to edit.'),
150
+ showLogs && React.createElement('div', { className: 'space-y-2' },
151
+ React.createElement('div', { className: 'flex items-center justify-between' },
152
+ React.createElement('h3', { className: 'text-sm font-semibold text-slate-300' }, 'Execution Logs'),
153
+ React.createElement('button', { onClick: () => setShowLogs(false), className: 'text-xs text-slate-500 hover:text-slate-300' }, 'Close'),
154
+ ),
155
+ React.createElement('pre', { className: 'bg-slate-900 border border-aia-border rounded p-3 text-xs text-slate-400 overflow-auto max-h-96' }, logs),
156
+ ),
157
+ ),
158
+ ),
159
+ );
160
+ }
@@ -0,0 +1,201 @@
1
+ import React from 'react';
2
+ import { api, streamPost } from '/main.js';
3
+
4
+ const STATUS_CLASSES = {
5
+ done: 'step-done',
6
+ pending: 'step-pending',
7
+ 'in-progress': 'step-in-progress',
8
+ error: 'step-error',
9
+ };
10
+
11
+ function StepBadge({ step, status }) {
12
+ return React.createElement('span', {
13
+ className: `inline-block px-2 py-0.5 text-xs rounded border ${STATUS_CLASSES[status] || 'step-pending'}`,
14
+ }, step);
15
+ }
16
+
17
+ function FeatureCard({ feature }) {
18
+ const steps = feature.steps || {};
19
+ const doneCount = Object.values(steps).filter(s => s === 'done').length;
20
+ const totalCount = Object.keys(steps).length;
21
+
22
+ return React.createElement('a', {
23
+ href: `#/features/${feature.name}`,
24
+ className: 'block bg-aia-card border border-aia-border rounded-lg p-4 hover:border-aia-accent/50 transition-colors',
25
+ },
26
+ React.createElement('div', { className: 'flex items-center justify-between mb-3' },
27
+ React.createElement('h3', { className: 'text-slate-100 font-semibold' }, feature.name),
28
+ React.createElement('span', { className: 'text-xs text-slate-500' },
29
+ `${doneCount}/${totalCount} steps`
30
+ ),
31
+ ),
32
+ feature.current_step && React.createElement('p', { className: 'text-xs text-slate-400 mb-3' },
33
+ 'Current: ', React.createElement('span', { className: 'text-aia-accent' }, feature.current_step)
34
+ ),
35
+ React.createElement('div', { className: 'flex flex-wrap gap-1.5' },
36
+ ...Object.entries(steps).map(([step, status]) =>
37
+ React.createElement(StepBadge, { key: step, step, status })
38
+ )
39
+ )
40
+ );
41
+ }
42
+
43
+ function NewFeatureForm({ onCreated }) {
44
+ const [name, setName] = React.useState('');
45
+ const [err, setErr] = React.useState('');
46
+ const [loading, setLoading] = React.useState(false);
47
+
48
+ async function handleSubmit(e) {
49
+ e.preventDefault();
50
+ setErr('');
51
+ setLoading(true);
52
+ try {
53
+ await api.post('/features', { name });
54
+ setName('');
55
+ onCreated();
56
+ } catch (e) {
57
+ setErr(e.message);
58
+ } finally {
59
+ setLoading(false);
60
+ }
61
+ }
62
+
63
+ return React.createElement('form', { onSubmit: handleSubmit, className: 'flex gap-2 items-start' },
64
+ React.createElement('div', null,
65
+ React.createElement('input', {
66
+ type: 'text',
67
+ value: name,
68
+ onChange: e => setName(e.target.value),
69
+ placeholder: 'feature-name',
70
+ className: 'bg-aia-card border border-aia-border rounded px-3 py-1.5 text-sm text-slate-200 placeholder-slate-500 focus:border-aia-accent focus:outline-none',
71
+ }),
72
+ err && React.createElement('p', { className: 'text-red-400 text-xs mt-1' }, err),
73
+ ),
74
+ React.createElement('button', {
75
+ type: 'submit',
76
+ disabled: loading || !name,
77
+ className: 'bg-aia-accent/20 text-aia-accent border border-aia-accent/30 rounded px-3 py-1.5 text-sm hover:bg-aia-accent/30 disabled:opacity-40',
78
+ }, loading ? '...' : '+ New Feature')
79
+ );
80
+ }
81
+
82
+ function LogViewer({ logs }) {
83
+ const ref = React.useRef(null);
84
+ React.useEffect(() => {
85
+ if (ref.current) ref.current.scrollTop = ref.current.scrollHeight;
86
+ }, [logs]);
87
+
88
+ if (!logs.length) return null;
89
+ return React.createElement('pre', {
90
+ ref,
91
+ className: 'bg-black/50 border border-aia-border rounded p-3 text-xs text-slate-400 overflow-auto max-h-48 whitespace-pre-wrap',
92
+ }, logs.join(''));
93
+ }
94
+
95
+ function QuickTicketForm({ onDone }) {
96
+ const [name, setName] = React.useState('');
97
+ const [description, setDescription] = React.useState('');
98
+ const [apply, setApply] = React.useState(false);
99
+ const [running, setRunning] = React.useState(false);
100
+ const [err, setErr] = React.useState('');
101
+ const [logs, setLogs] = React.useState([]);
102
+
103
+ async function handleSubmit(e) {
104
+ e.preventDefault();
105
+ setErr('');
106
+ setRunning(true);
107
+ setLogs([]);
108
+
109
+ const res = await streamPost('/quick', { name, description, apply }, {
110
+ onLog: (text) => setLogs(prev => [...prev, text]),
111
+ onStatus: (data) => setLogs(prev => [...prev, `[${data.status}] ${data.name || ''}\n`]),
112
+ });
113
+
114
+ if (res.ok) {
115
+ setName('');
116
+ setDescription('');
117
+ setLogs([]);
118
+ onDone();
119
+ } else {
120
+ setErr(res.error);
121
+ }
122
+ setRunning(false);
123
+ }
124
+
125
+ return React.createElement('form', {
126
+ onSubmit: handleSubmit,
127
+ className: 'bg-aia-card border border-amber-500/30 rounded-lg p-4 space-y-3',
128
+ },
129
+ React.createElement('h3', { className: 'text-sm font-semibold text-amber-400' }, 'Quick Ticket'),
130
+ React.createElement('p', { className: 'text-xs text-slate-500' }, 'dev-plan \u2192 implement \u2192 review'),
131
+ React.createElement('div', { className: 'flex gap-2' },
132
+ React.createElement('input', {
133
+ type: 'text',
134
+ value: name,
135
+ onChange: e => setName(e.target.value),
136
+ placeholder: 'ticket-name',
137
+ disabled: running,
138
+ className: 'bg-slate-900 border border-aia-border rounded px-3 py-1.5 text-sm text-slate-200 placeholder-slate-500 focus:border-amber-400 focus:outline-none flex-shrink-0',
139
+ }),
140
+ React.createElement('input', {
141
+ type: 'text',
142
+ value: description,
143
+ onChange: e => setDescription(e.target.value),
144
+ placeholder: 'Short description (optional)',
145
+ disabled: running,
146
+ className: 'bg-slate-900 border border-aia-border rounded px-3 py-1.5 text-sm text-slate-200 placeholder-slate-500 focus:border-amber-400 focus:outline-none flex-1',
147
+ }),
148
+ ),
149
+ React.createElement('div', { className: 'flex items-center gap-4' },
150
+ React.createElement('label', { className: 'flex items-center gap-2 text-xs text-slate-400 cursor-pointer' },
151
+ React.createElement('input', {
152
+ type: 'checkbox',
153
+ checked: apply,
154
+ onChange: e => setApply(e.target.checked),
155
+ disabled: running,
156
+ }),
157
+ 'Agent mode (--apply)'
158
+ ),
159
+ React.createElement('button', {
160
+ type: 'submit',
161
+ disabled: running || !name,
162
+ className: 'bg-amber-500/20 text-amber-400 border border-amber-500/30 rounded px-4 py-1.5 text-sm hover:bg-amber-500/30 disabled:opacity-40',
163
+ }, running ? 'Running...' : 'Run Quick Ticket'),
164
+ ),
165
+ React.createElement(LogViewer, { logs }),
166
+ err && React.createElement('p', { className: 'text-red-400 text-xs' }, err),
167
+ );
168
+ }
169
+
170
+ export function Dashboard() {
171
+ const [features, setFeatures] = React.useState([]);
172
+ const [loading, setLoading] = React.useState(true);
173
+
174
+ async function load() {
175
+ try {
176
+ const data = await api.get('/features');
177
+ setFeatures(data);
178
+ } catch {}
179
+ setLoading(false);
180
+ }
181
+
182
+ React.useEffect(() => { load(); }, []);
183
+
184
+ return React.createElement('div', { className: 'space-y-6' },
185
+ React.createElement('div', { className: 'flex items-center justify-between' },
186
+ React.createElement('h1', { className: 'text-xl font-bold text-slate-100' }, 'Features'),
187
+ React.createElement(NewFeatureForm, { onCreated: load }),
188
+ ),
189
+
190
+ // Quick ticket
191
+ React.createElement(QuickTicketForm, { onDone: load }),
192
+
193
+ loading
194
+ ? React.createElement('p', { className: 'text-slate-500' }, 'Loading...')
195
+ : features.length === 0
196
+ ? React.createElement('p', { className: 'text-slate-500' }, 'No features yet. Create one to get started.')
197
+ : React.createElement('div', { className: 'grid grid-cols-1 md:grid-cols-2 lg:grid-cols-3 gap-4' },
198
+ ...features.map(f => React.createElement(FeatureCard, { key: f.name, feature: f }))
199
+ )
200
+ );
201
+ }
@@ -0,0 +1,278 @@
1
+ import React from 'react';
2
+ import { api, streamPost } from '/main.js';
3
+
4
+ const STATUS_CLASSES = {
5
+ done: 'step-done',
6
+ pending: 'step-pending',
7
+ 'in-progress': 'step-in-progress',
8
+ error: 'step-error',
9
+ };
10
+
11
+ const STATUS_ICONS = { done: '\u2713', pending: '\u00b7', 'in-progress': '\u25b6', error: '\u2717' };
12
+
13
+ function StepPill({ step, status, active, onClick }) {
14
+ return React.createElement('button', {
15
+ onClick,
16
+ className: `px-3 py-1.5 text-xs rounded border transition-all ${STATUS_CLASSES[status] || 'step-pending'} ${active ? 'ring-2 ring-aia-accent ring-offset-1 ring-offset-aia-bg' : 'hover:brightness-125'}`,
17
+ }, `${STATUS_ICONS[status] || ''} ${step}`);
18
+ }
19
+
20
+ function FileEditor({ name, filename, onSaved }) {
21
+ const [content, setContent] = React.useState('');
22
+ const [loading, setLoading] = React.useState(true);
23
+ const [saving, setSaving] = React.useState(false);
24
+ const [dirty, setDirty] = React.useState(false);
25
+
26
+ React.useEffect(() => {
27
+ setLoading(true);
28
+ setDirty(false);
29
+ api.get(`/features/${name}/files/${filename}`)
30
+ .then(data => setContent(data.content))
31
+ .catch(() => setContent(''))
32
+ .finally(() => setLoading(false));
33
+ }, [name, filename]);
34
+
35
+ async function save() {
36
+ setSaving(true);
37
+ try {
38
+ await api.put(`/features/${name}/files/${filename}`, { content });
39
+ setDirty(false);
40
+ if (onSaved) onSaved();
41
+ } catch {}
42
+ setSaving(false);
43
+ }
44
+
45
+ if (loading) return React.createElement('p', { className: 'text-slate-500 text-sm' }, 'Loading...');
46
+
47
+ return React.createElement('div', { className: 'flex flex-col gap-2' },
48
+ React.createElement('div', { className: 'flex items-center justify-between' },
49
+ React.createElement('span', { className: 'text-sm text-slate-400' }, filename),
50
+ React.createElement('div', { className: 'flex gap-2' },
51
+ dirty && React.createElement('span', { className: 'text-xs text-amber-400' }, 'unsaved'),
52
+ React.createElement('button', {
53
+ onClick: save,
54
+ disabled: saving || !dirty,
55
+ className: 'bg-aia-accent/20 text-aia-accent border border-aia-accent/30 rounded px-3 py-1 text-xs hover:bg-aia-accent/30 disabled:opacity-40',
56
+ }, saving ? 'Saving...' : 'Save'),
57
+ ),
58
+ ),
59
+ React.createElement('textarea', {
60
+ value: content,
61
+ onChange: e => { setContent(e.target.value); setDirty(true); },
62
+ spellCheck: false,
63
+ className: 'w-full h-96 bg-slate-900 border border-aia-border rounded p-3 text-sm text-slate-300 font-mono resize-y focus:border-aia-accent focus:outline-none',
64
+ })
65
+ );
66
+ }
67
+
68
+ function QuickRunButton({ name, onDone }) {
69
+ const [running, setRunning] = React.useState(false);
70
+ const [description, setDescription] = React.useState('');
71
+ const [expanded, setExpanded] = React.useState(false);
72
+ const [err, setErr] = React.useState(null);
73
+ const [logs, setLogs] = React.useState([]);
74
+
75
+ async function run() {
76
+ setRunning(true);
77
+ setErr(null);
78
+ setLogs([]);
79
+
80
+ const res = await streamPost(`/features/${name}/quick`, { description }, {
81
+ onLog: (text) => setLogs(prev => [...prev, text]),
82
+ onStatus: (data) => setLogs(prev => [...prev, `[${data.status}] ${data.mode || ''}\n`]),
83
+ });
84
+
85
+ if (res.ok) {
86
+ if (onDone) onDone();
87
+ } else {
88
+ setErr(res.error);
89
+ }
90
+ setRunning(false);
91
+ }
92
+
93
+ if (!expanded) {
94
+ return React.createElement('button', {
95
+ onClick: () => setExpanded(true),
96
+ className: 'bg-amber-500/20 text-amber-400 border border-amber-500/30 rounded px-3 py-1.5 text-xs hover:bg-amber-500/30',
97
+ }, 'Quick Ticket (dev-plan \u2192 implement \u2192 review)');
98
+ }
99
+
100
+ return React.createElement('div', { className: 'bg-slate-900 border border-amber-500/30 rounded p-4 space-y-3' },
101
+ React.createElement('h4', { className: 'text-sm font-semibold text-amber-400' }, 'Quick Ticket'),
102
+ React.createElement('p', { className: 'text-xs text-slate-500' }, 'Skips early steps, runs dev-plan \u2192 implement \u2192 review'),
103
+ React.createElement('input', {
104
+ type: 'text',
105
+ value: description,
106
+ onChange: e => setDescription(e.target.value),
107
+ placeholder: 'Optional description...',
108
+ disabled: running,
109
+ className: 'w-full bg-aia-card border border-aia-border rounded px-3 py-1.5 text-sm text-slate-200 placeholder-slate-500 focus:border-amber-400 focus:outline-none',
110
+ }),
111
+ React.createElement('div', { className: 'flex gap-2' },
112
+ React.createElement('button', {
113
+ onClick: run,
114
+ disabled: running,
115
+ className: 'bg-amber-500/20 text-amber-400 border border-amber-500/30 rounded px-4 py-1.5 text-sm hover:bg-amber-500/30 disabled:opacity-40',
116
+ }, running ? 'Running...' : 'Run Quick'),
117
+ React.createElement('button', {
118
+ onClick: () => setExpanded(false),
119
+ disabled: running,
120
+ className: 'text-slate-500 hover:text-slate-300 text-xs',
121
+ }, 'Cancel'),
122
+ ),
123
+ React.createElement(LogViewer, { logs }),
124
+ err && React.createElement('p', { className: 'text-red-400 text-xs' }, err),
125
+ );
126
+ }
127
+
128
+ function LogViewer({ logs }) {
129
+ const ref = React.useRef(null);
130
+ React.useEffect(() => {
131
+ if (ref.current) ref.current.scrollTop = ref.current.scrollHeight;
132
+ }, [logs]);
133
+
134
+ if (!logs.length) return null;
135
+ return React.createElement('pre', {
136
+ ref,
137
+ className: 'bg-black/50 border border-aia-border rounded p-3 text-xs text-slate-400 overflow-auto max-h-64 whitespace-pre-wrap',
138
+ }, logs.join(''));
139
+ }
140
+
141
+ function RunPanel({ name, step, onDone }) {
142
+ const [description, setDescription] = React.useState('');
143
+ const [apply, setApply] = React.useState(false);
144
+ const [running, setRunning] = React.useState(false);
145
+ const [result, setResult] = React.useState(null);
146
+ const [err, setErr] = React.useState(null);
147
+ const [logs, setLogs] = React.useState([]);
148
+
149
+ async function run() {
150
+ setRunning(true);
151
+ setResult(null);
152
+ setErr(null);
153
+ setLogs([]);
154
+
155
+ const res = await streamPost(`/features/${name}/run/${step}`, { description, apply }, {
156
+ onLog: (text) => setLogs(prev => [...prev, text]),
157
+ onStatus: (data) => setLogs(prev => [...prev, `[${data.status}] ${data.step || ''}\n`]),
158
+ });
159
+
160
+ if (res.ok) {
161
+ setResult('Step completed.');
162
+ if (onDone) onDone();
163
+ } else {
164
+ setErr(res.error);
165
+ }
166
+ setRunning(false);
167
+ }
168
+
169
+ async function reset() {
170
+ try {
171
+ await api.post(`/features/${name}/reset/${step}`);
172
+ if (onDone) onDone();
173
+ } catch (e) {
174
+ setErr(e.message);
175
+ }
176
+ }
177
+
178
+ return React.createElement('div', { className: 'bg-slate-900 border border-aia-border rounded p-4 space-y-3' },
179
+ React.createElement('h4', { className: 'text-sm font-semibold text-slate-300' }, `Run: ${step}`),
180
+ React.createElement('input', {
181
+ type: 'text',
182
+ value: description,
183
+ onChange: e => setDescription(e.target.value),
184
+ placeholder: 'Optional description...',
185
+ disabled: running,
186
+ className: 'w-full bg-aia-card border border-aia-border rounded px-3 py-1.5 text-sm text-slate-200 placeholder-slate-500 focus:border-aia-accent focus:outline-none',
187
+ }),
188
+ React.createElement('div', { className: 'flex items-center gap-4' },
189
+ React.createElement('label', { className: 'flex items-center gap-2 text-xs text-slate-400 cursor-pointer' },
190
+ React.createElement('input', {
191
+ type: 'checkbox',
192
+ checked: apply,
193
+ onChange: e => setApply(e.target.checked),
194
+ disabled: running,
195
+ className: 'rounded',
196
+ }),
197
+ 'Agent mode (--apply)'
198
+ ),
199
+ React.createElement('button', {
200
+ onClick: run,
201
+ disabled: running,
202
+ className: 'bg-emerald-500/20 text-emerald-400 border border-emerald-500/30 rounded px-4 py-1.5 text-sm hover:bg-emerald-500/30 disabled:opacity-40',
203
+ }, running ? 'Running...' : 'Run Step'),
204
+ React.createElement('button', {
205
+ onClick: reset,
206
+ disabled: running,
207
+ className: 'text-slate-500 hover:text-slate-300 text-xs',
208
+ }, 'Reset'),
209
+ ),
210
+ React.createElement(LogViewer, { logs }),
211
+ result && React.createElement('p', { className: 'text-emerald-400 text-xs' }, result),
212
+ err && React.createElement('p', { className: 'text-red-400 text-xs' }, err),
213
+ );
214
+ }
215
+
216
+ export function FeatureDetail({ name }) {
217
+ const [feature, setFeature] = React.useState(null);
218
+ const [loading, setLoading] = React.useState(true);
219
+ const [activeFile, setActiveFile] = React.useState('init.md');
220
+ const [activeStep, setActiveStep] = React.useState(null);
221
+
222
+ async function load() {
223
+ try {
224
+ const data = await api.get(`/features/${name}`);
225
+ setFeature(data);
226
+ } catch {}
227
+ setLoading(false);
228
+ }
229
+
230
+ React.useEffect(() => { load(); }, [name]);
231
+
232
+ if (loading) return React.createElement('p', { className: 'text-slate-500' }, 'Loading...');
233
+ if (!feature) return React.createElement('p', { className: 'text-red-400' }, `Feature "${name}" not found.`);
234
+
235
+ const steps = feature.steps || {};
236
+
237
+ return React.createElement('div', { className: 'space-y-6' },
238
+ // Header
239
+ React.createElement('div', { className: 'flex items-center gap-3' },
240
+ React.createElement('a', { href: '#/', className: 'text-slate-500 hover:text-slate-300' }, '\u2190'),
241
+ React.createElement('h1', { className: 'text-xl font-bold text-slate-100' }, name),
242
+ feature.current_step && React.createElement('span', { className: 'text-xs bg-aia-accent/20 text-aia-accent px-2 py-0.5 rounded' }, feature.current_step),
243
+ ),
244
+
245
+ // Quick run
246
+ React.createElement(QuickRunButton, { name, onDone: load }),
247
+
248
+ // Pipeline
249
+ React.createElement('div', { className: 'flex flex-wrap gap-2' },
250
+ ...Object.entries(steps).map(([step, status]) =>
251
+ React.createElement(StepPill, {
252
+ key: step,
253
+ step,
254
+ status,
255
+ active: activeStep === step,
256
+ onClick: () => { setActiveStep(step); setActiveFile(`${step}.md`); },
257
+ })
258
+ )
259
+ ),
260
+
261
+ // Run panel
262
+ activeStep && React.createElement(RunPanel, { name, step: activeStep, onDone: load }),
263
+
264
+ // File tabs
265
+ React.createElement('div', { className: 'flex gap-1 border-b border-aia-border' },
266
+ ...(feature.files || []).filter(f => f.endsWith('.md') || f.endsWith('.yaml')).map(f =>
267
+ React.createElement('button', {
268
+ key: f,
269
+ onClick: () => setActiveFile(f),
270
+ className: `px-3 py-1.5 text-xs border-b-2 transition-colors ${activeFile === f ? 'border-aia-accent text-aia-accent' : 'border-transparent text-slate-500 hover:text-slate-300'}`,
271
+ }, f)
272
+ )
273
+ ),
274
+
275
+ // Editor
276
+ activeFile && React.createElement(FileEditor, { key: `${name}-${activeFile}`, name, filename: activeFile, onSaved: load }),
277
+ );
278
+ }
@@ -0,0 +1,44 @@
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>AIA - AI Architecture Assistant</title>
7
+ <script src="https://cdn.tailwindcss.com"></script>
8
+ <script>
9
+ tailwind.config = {
10
+ theme: {
11
+ extend: {
12
+ colors: {
13
+ 'aia-bg': '#0f172a',
14
+ 'aia-card': '#1e293b',
15
+ 'aia-border': '#334155',
16
+ 'aia-accent': '#38bdf8',
17
+ }
18
+ }
19
+ }
20
+ }
21
+ </script>
22
+ <script type="importmap">
23
+ {
24
+ "imports": {
25
+ "react": "https://esm.sh/react@19?dev",
26
+ "react-dom/client": "https://esm.sh/react-dom@19/client?dev",
27
+ "react/jsx-runtime": "https://esm.sh/react@19/jsx-runtime?dev"
28
+ }
29
+ }
30
+ </script>
31
+ <style>
32
+ body { font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace; }
33
+ .step-done { @apply bg-emerald-500/20 text-emerald-400 border-emerald-500/30; }
34
+ .step-pending { @apply bg-slate-700/50 text-slate-400 border-slate-600; }
35
+ .step-in-progress { @apply bg-amber-500/20 text-amber-400 border-amber-500/30; }
36
+ .step-error { @apply bg-red-500/20 text-red-400 border-red-500/30; }
37
+ textarea { tab-size: 2; }
38
+ </style>
39
+ </head>
40
+ <body class="bg-aia-bg text-slate-200 min-h-screen">
41
+ <div id="root"></div>
42
+ <script type="module" src="/main.js"></script>
43
+ </body>
44
+ </html>