tryassay 0.22.0 → 0.22.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 (130) hide show
  1. package/demo/css/style.css +495 -836
  2. package/demo/index.html +40 -184
  3. package/demo/js/chat.js +385 -142
  4. package/demo/js/preview.js +456 -0
  5. package/demo/js/sse-client.js +262 -135
  6. package/demo/js/state.js +11 -1
  7. package/demo/js/timeline.js +57 -371
  8. package/dist/api/server.d.ts +2 -0
  9. package/dist/api/server.js +63 -19
  10. package/dist/api/server.js.map +1 -1
  11. package/dist/cli.js +2 -0
  12. package/dist/cli.js.map +1 -1
  13. package/dist/commands/assess.d.ts +2 -0
  14. package/dist/commands/assess.js +132 -164
  15. package/dist/commands/assess.js.map +1 -1
  16. package/dist/commands/demo.js +259 -9
  17. package/dist/commands/demo.js.map +1 -1
  18. package/dist/lib/__tests__/arithmetic-quick-test.d.ts +6 -0
  19. package/dist/lib/__tests__/arithmetic-quick-test.js +197 -0
  20. package/dist/lib/__tests__/arithmetic-quick-test.js.map +1 -0
  21. package/dist/lib/__tests__/arithmetic-real-llm-test.d.ts +13 -0
  22. package/dist/lib/__tests__/arithmetic-real-llm-test.js +284 -0
  23. package/dist/lib/__tests__/arithmetic-real-llm-test.js.map +1 -0
  24. package/dist/lib/__tests__/arithmetic-value-demo.d.ts +10 -0
  25. package/dist/lib/__tests__/arithmetic-value-demo.js +193 -0
  26. package/dist/lib/__tests__/arithmetic-value-demo.js.map +1 -0
  27. package/dist/lib/__tests__/flow-to-claims.test.d.ts +1 -0
  28. package/dist/lib/__tests__/flow-to-claims.test.js +91 -0
  29. package/dist/lib/__tests__/flow-to-claims.test.js.map +1 -0
  30. package/dist/lib/__tests__/formal-verifier-api-misuse.test.d.ts +9 -0
  31. package/dist/lib/__tests__/formal-verifier-api-misuse.test.js +391 -0
  32. package/dist/lib/__tests__/formal-verifier-api-misuse.test.js.map +1 -0
  33. package/dist/lib/__tests__/formal-verifier-arithmetic.test.d.ts +7 -0
  34. package/dist/lib/__tests__/formal-verifier-arithmetic.test.js +318 -0
  35. package/dist/lib/__tests__/formal-verifier-arithmetic.test.js.map +1 -0
  36. package/dist/lib/__tests__/intent-extractor.test.d.ts +1 -0
  37. package/dist/lib/__tests__/intent-extractor.test.js +97 -0
  38. package/dist/lib/__tests__/intent-extractor.test.js.map +1 -0
  39. package/dist/lib/__tests__/intent-reviewer.test.d.ts +1 -0
  40. package/dist/lib/__tests__/intent-reviewer.test.js +55 -0
  41. package/dist/lib/__tests__/intent-reviewer.test.js.map +1 -0
  42. package/dist/lib/__tests__/mr-gsm8k-benchmark.d.ts +11 -0
  43. package/dist/lib/__tests__/mr-gsm8k-benchmark.js +224 -0
  44. package/dist/lib/__tests__/mr-gsm8k-benchmark.js.map +1 -0
  45. package/dist/lib/anthropic.js +25 -33
  46. package/dist/lib/anthropic.js.map +1 -1
  47. package/dist/lib/assessment-reporter.js +9 -13
  48. package/dist/lib/assessment-reporter.js.map +1 -1
  49. package/dist/lib/claim-extractor.js +10 -19
  50. package/dist/lib/claim-extractor.js.map +1 -1
  51. package/dist/lib/code-verifier.js +16 -36
  52. package/dist/lib/code-verifier.js.map +1 -1
  53. package/dist/lib/constraint-engine.js +10 -19
  54. package/dist/lib/constraint-engine.js.map +1 -1
  55. package/dist/lib/formal-verifier.d.ts +1 -1
  56. package/dist/lib/formal-verifier.js +454 -0
  57. package/dist/lib/formal-verifier.js.map +1 -1
  58. package/dist/lib/guided-generator.js +19 -37
  59. package/dist/lib/guided-generator.js.map +1 -1
  60. package/dist/lib/intent-extractor.d.ts +47 -0
  61. package/dist/lib/intent-extractor.js +432 -0
  62. package/dist/lib/intent-extractor.js.map +1 -0
  63. package/dist/lib/intent-reviewer.d.ts +14 -0
  64. package/dist/lib/intent-reviewer.js +148 -0
  65. package/dist/lib/intent-reviewer.js.map +1 -0
  66. package/dist/lib/intent-types.d.ts +89 -0
  67. package/dist/lib/intent-types.js +5 -0
  68. package/dist/lib/intent-types.js.map +1 -0
  69. package/dist/lib/inventory-extractor.js +9 -22
  70. package/dist/lib/inventory-extractor.js.map +1 -1
  71. package/dist/lib/llm-provider.d.ts +23 -0
  72. package/dist/lib/llm-provider.js +130 -0
  73. package/dist/lib/llm-provider.js.map +1 -0
  74. package/dist/lib/remediator.js +20 -28
  75. package/dist/lib/remediator.js.map +1 -1
  76. package/dist/lib/requirements-generator.js +14 -19
  77. package/dist/lib/requirements-generator.js.map +1 -1
  78. package/dist/lib/spec-synthesizer.js +10 -19
  79. package/dist/lib/spec-synthesizer.js.map +1 -1
  80. package/dist/runtime/app-create-orchestrator.d.ts +5 -1
  81. package/dist/runtime/app-create-orchestrator.js +114 -39
  82. package/dist/runtime/app-create-orchestrator.js.map +1 -1
  83. package/dist/runtime/check-catalog.js +5 -3
  84. package/dist/runtime/check-catalog.js.map +1 -1
  85. package/dist/runtime/check-definitions.d.ts +10 -0
  86. package/dist/runtime/check-definitions.js +52 -2
  87. package/dist/runtime/check-definitions.js.map +1 -1
  88. package/dist/runtime/composition-verifier.js +8 -12
  89. package/dist/runtime/composition-verifier.js.map +1 -1
  90. package/dist/runtime/gap-detector.js +8 -10
  91. package/dist/runtime/gap-detector.js.map +1 -1
  92. package/dist/runtime/input-validator.d.ts +7 -0
  93. package/dist/runtime/input-validator.js +162 -0
  94. package/dist/runtime/input-validator.js.map +1 -0
  95. package/dist/runtime/model-router.d.ts +10 -0
  96. package/dist/runtime/model-router.js +42 -0
  97. package/dist/runtime/model-router.js.map +1 -0
  98. package/dist/runtime/pattern-extractor.js +8 -10
  99. package/dist/runtime/pattern-extractor.js.map +1 -1
  100. package/dist/runtime/planner.js +11 -16
  101. package/dist/runtime/planner.js.map +1 -1
  102. package/dist/runtime/prompt-guard.d.ts +2 -0
  103. package/dist/runtime/prompt-guard.js +180 -0
  104. package/dist/runtime/prompt-guard.js.map +1 -0
  105. package/dist/runtime/prompt-safety-analyzer.js +8 -13
  106. package/dist/runtime/prompt-safety-analyzer.js.map +1 -1
  107. package/dist/runtime/reasoner.js +19 -33
  108. package/dist/runtime/reasoner.js.map +1 -1
  109. package/dist/runtime/rule-meta-verifier.js +9 -11
  110. package/dist/runtime/rule-meta-verifier.js.map +1 -1
  111. package/dist/runtime/safe-executor.d.ts +23 -0
  112. package/dist/runtime/safe-executor.js +151 -0
  113. package/dist/runtime/safe-executor.js.map +1 -0
  114. package/dist/runtime/specialized-agent.js +10 -14
  115. package/dist/runtime/specialized-agent.js.map +1 -1
  116. package/dist/runtime/strategy-library.js +8 -10
  117. package/dist/runtime/strategy-library.js.map +1 -1
  118. package/dist/runtime/supabase-experience-store.js.map +1 -1
  119. package/dist/runtime/supabase-provisioner.d.ts +35 -0
  120. package/dist/runtime/supabase-provisioner.js +192 -0
  121. package/dist/runtime/supabase-provisioner.js.map +1 -0
  122. package/dist/runtime/types.d.ts +88 -0
  123. package/dist/sdk/forward-verify.js +16 -33
  124. package/dist/sdk/forward-verify.js.map +1 -1
  125. package/package.json +1 -1
  126. package/demo/data/demo-events.json +0 -103
  127. package/demo/js/demo-mode.js +0 -107
  128. package/demo/js/orb.js +0 -634
  129. package/demo/js/question-cards.js +0 -207
  130. package/demo/js/voice.js +0 -154
package/demo/js/chat.js CHANGED
@@ -1,98 +1,62 @@
1
- // chat.js — Conversational chat for requirements gathering
2
- // Renders in #plan-overlay, replaces question-cards.js for planning phase
1
+ // chat.js — Chat-first interface for requirements gathering + build
2
+ // Renders in #chat-panel (left side of split screen)
3
3
 
4
4
  const AppState = window.AppState;
5
5
 
6
6
  let chatConfig = { apiBase: '', apiKey: '' };
7
7
  let currentSessionId = null;
8
8
  let pendingCardAnswers = [];
9
+ let pendingAttachments = [];
10
+
11
+ const MAX_FILE_SIZE = 500 * 1024; // 500KB
12
+ const ALLOWED_EXTENSIONS = ['.txt', '.md', '.markdown', '.csv', '.json'];
13
+
14
+ // ── Public API ──────────────────────────────────────────────
9
15
 
10
- /**
11
- * Configure API connection (called from sse-client.js)
12
- */
13
16
  export function configureChat(apiBase, apiKey) {
14
17
  chatConfig.apiBase = apiBase || '';
15
18
  chatConfig.apiKey = apiKey || '';
16
19
  }
17
20
 
18
- /**
19
- * Open the chat interface in #plan-overlay.
20
- * Called once when the first chat_message SSE event arrives.
21
- */
22
- export function openChat(sessionId) {
23
- const overlay = document.getElementById('plan-overlay');
24
- if (!overlay) return;
21
+ export function initChat(apiBase, apiKey) {
22
+ chatConfig.apiBase = apiBase || '';
23
+ chatConfig.apiKey = apiKey || '';
24
+
25
+ renderWelcomeMessage();
26
+ wireInputHandlers();
27
+ }
25
28
 
29
+ export function openChat(sessionId) {
26
30
  currentSessionId = sessionId;
27
31
  pendingCardAnswers = [];
28
-
29
- overlay.innerHTML = `
30
- <div class="chat-container">
31
- <div class="chat-messages" id="chat-messages"></div>
32
- <div class="chat-input-bar">
33
- <input
34
- type="text"
35
- id="chat-input"
36
- placeholder="Type a message..."
37
- autocomplete="off"
38
- >
39
- <button id="chat-send-btn">Send</button>
40
- </div>
41
- </div>
42
- `;
43
-
44
- overlay.classList.add('visible');
45
-
46
- // Bind send button
47
- const sendBtn = document.getElementById('chat-send-btn');
48
- const chatInput = document.getElementById('chat-input');
49
-
50
- sendBtn.addEventListener('click', () => sendMessage());
51
- chatInput.addEventListener('keydown', (e) => {
52
- if (e.key === 'Enter') sendMessage();
53
- });
54
-
55
- // Hide the header input bar during chat
56
- const header = document.getElementById('header');
57
- if (header) header.classList.add('chat-active');
58
32
  }
59
33
 
60
- /**
61
- * Add an architect message to the chat.
62
- * Called on each chat_message SSE event.
63
- */
64
34
  export function addArchitectMessage(data) {
65
- let messagesEl = document.getElementById('chat-messages');
66
- if (!messagesEl) {
67
- // Chat not open yet — open it first
35
+ const messagesEl = document.getElementById('chat-messages');
36
+ if (!messagesEl) return;
37
+
38
+ if (!currentSessionId) {
68
39
  openChat(data.sessionId || window.__appId);
69
- messagesEl = document.getElementById('chat-messages');
70
- if (!messagesEl) return; // openChat failed, bail
71
40
  }
72
41
 
73
42
  const msgDiv = document.createElement('div');
74
43
  msgDiv.className = 'chat-msg chat-msg-architect';
75
44
 
76
- // Message text
77
45
  const textDiv = document.createElement('div');
78
46
  textDiv.className = 'chat-msg-text';
79
47
  textDiv.textContent = data.content;
80
48
  msgDiv.appendChild(textDiv);
81
49
 
82
- // Structured cards (if any)
83
50
  if (data.cards && data.cards.length > 0) {
84
51
  const cardsDiv = document.createElement('div');
85
52
  cardsDiv.className = 'chat-cards';
86
-
87
53
  for (const card of data.cards) {
88
54
  const cardEl = renderCard(card);
89
55
  cardsDiv.appendChild(cardEl);
90
56
  }
91
-
92
57
  msgDiv.appendChild(cardsDiv);
93
58
  }
94
59
 
95
- // "Start Building" button (if ready)
96
60
  if (data.readiness && data.readiness.ready) {
97
61
  const buildBtn = document.createElement('button');
98
62
  buildBtn.className = 'chat-build-btn';
@@ -111,25 +75,374 @@ export function addArchitectMessage(data) {
111
75
  messagesEl.scrollTop = messagesEl.scrollHeight;
112
76
  }
113
77
 
114
- /**
115
- * Close the chat and proceed to build phase.
116
- */
117
78
  export function closeChat() {
118
- const overlay = document.getElementById('plan-overlay');
119
- if (overlay) {
120
- overlay.classList.remove('visible');
121
- setTimeout(() => { overlay.innerHTML = ''; }, 500);
79
+ currentSessionId = null;
80
+ pendingCardAnswers = [];
81
+ }
82
+
83
+ export function lockChat() {
84
+ const inputArea = document.getElementById('chat-input-area');
85
+ if (inputArea) inputArea.classList.add('hidden');
86
+
87
+ const messagesEl = document.getElementById('chat-messages');
88
+ if (messagesEl) {
89
+ const separator = document.createElement('div');
90
+ separator.className = 'activity-separator';
91
+ separator.innerHTML = '<span>Build started</span>';
92
+ messagesEl.appendChild(separator);
93
+ messagesEl.scrollTop = messagesEl.scrollHeight;
122
94
  }
95
+ }
123
96
 
124
- // Restore header input bar
125
- const header = document.getElementById('header');
126
- if (header) header.classList.remove('chat-active');
97
+ export function unlockChat() {
98
+ const inputArea = document.getElementById('chat-input-area');
99
+ if (inputArea) inputArea.classList.remove('hidden');
100
+
101
+ const chatInput = document.getElementById('chat-input');
102
+ const sendBtn = document.getElementById('chat-send-btn');
103
+ const attachBtn = document.getElementById('attach-btn');
104
+
105
+ if (chatInput) {
106
+ chatInput.disabled = false;
107
+ chatInput.value = '';
108
+ chatInput.placeholder = 'Build something else...';
109
+ }
110
+ if (sendBtn) sendBtn.disabled = false;
111
+ if (attachBtn) attachBtn.disabled = false;
127
112
 
128
113
  currentSessionId = null;
129
- pendingCardAnswers = [];
114
+ pendingAttachments = [];
115
+ clearAttachmentChips();
116
+ }
117
+
118
+ // ── Initialization ──────────────────────────────────────────
119
+
120
+ function renderWelcomeMessage() {
121
+ const messagesEl = document.getElementById('chat-messages');
122
+ if (!messagesEl) return;
123
+
124
+ const msgDiv = document.createElement('div');
125
+ msgDiv.className = 'chat-msg chat-msg-architect';
126
+
127
+ const textDiv = document.createElement('div');
128
+ textDiv.className = 'chat-msg-text';
129
+ textDiv.textContent = "What would you like to build? Describe your app and I'll help design it. You can also attach a specification document.";
130
+ msgDiv.appendChild(textDiv);
131
+
132
+ messagesEl.appendChild(msgDiv);
133
+ }
134
+
135
+ function wireInputHandlers() {
136
+ const sendBtn = document.getElementById('chat-send-btn');
137
+ const chatInput = document.getElementById('chat-input');
138
+ const attachBtn = document.getElementById('attach-btn');
139
+ const fileInput = document.getElementById('file-input');
140
+
141
+ if (sendBtn) {
142
+ sendBtn.addEventListener('click', () => handleSend());
143
+ }
144
+ if (chatInput) {
145
+ chatInput.addEventListener('keydown', (e) => {
146
+ if (e.key === 'Enter') handleSend();
147
+ });
148
+ }
149
+ if (attachBtn && fileInput) {
150
+ attachBtn.addEventListener('click', () => fileInput.click());
151
+ fileInput.addEventListener('change', (e) => handleFileSelected(e));
152
+ }
153
+ }
154
+
155
+ // ── Send Routing ────────────────────────────────────────────
156
+
157
+ function handleSend() {
158
+ if (currentSessionId === null) {
159
+ sendInitialMessage();
160
+ } else {
161
+ sendMessage();
162
+ }
130
163
  }
131
164
 
132
- // ── Internal ──
165
+ // ── Initial Message (creates session) ───────────────────────
166
+
167
+ function buildAppDescription(prompt) {
168
+ const name = prompt
169
+ .replace(/^(build|create|make|generate)\s+(a|an|me|my)?\s*/i, '')
170
+ .split(/[.,!?\n]/)[0]
171
+ .trim()
172
+ .slice(0, 50)
173
+ .replace(/\s+/g, '-')
174
+ .toLowerCase() || 'my-app';
175
+
176
+ const features = [];
177
+ const lower = prompt.toLowerCase();
178
+ if (/auth|login|signup|sign.?up|register|password/.test(lower)) features.push('User authentication with email/password');
179
+ if (/todo|task|checklist/.test(lower)) features.push('Todo/task CRUD with completion tracking');
180
+ if (/blog|post|article|cms/.test(lower)) features.push('Blog post management with markdown');
181
+ if (/chat|message|real.?time/.test(lower)) features.push('Real-time chat messaging');
182
+ if (/e.?commerce|shop|cart|product|payment/.test(lower)) features.push('Product catalog with shopping cart');
183
+ if (/dashboard|admin|analytics/.test(lower)) features.push('Admin dashboard with analytics');
184
+ if (/api|rest|endpoint/.test(lower)) features.push('REST API endpoints');
185
+ if (/search|filter/.test(lower)) features.push('Search and filtering');
186
+ if (/upload|file|image/.test(lower)) features.push('File upload handling');
187
+ if (/notification|email|alert/.test(lower)) features.push('Notification system');
188
+ if (features.length === 0) features.push(prompt);
189
+
190
+ let framework = 'next.js';
191
+ let database = 'supabase';
192
+ if (/express|node/.test(lower)) framework = 'express';
193
+ if (/svelte/.test(lower)) framework = 'sveltekit';
194
+ if (/electron|desktop/.test(lower)) framework = 'electron';
195
+ if (/postgres(?!.*supa)/.test(lower)) database = 'postgresql';
196
+ if (/sqlite/.test(lower)) database = 'sqlite';
197
+
198
+ return {
199
+ name,
200
+ description: prompt,
201
+ techStack: { language: 'typescript', framework, database, styling: 'tailwind' },
202
+ features,
203
+ };
204
+ }
205
+
206
+ async function sendInitialMessage() {
207
+ const chatInput = document.getElementById('chat-input');
208
+ if (!chatInput) return;
209
+
210
+ const text = chatInput.value.trim();
211
+ if (!text && pendingAttachments.length === 0) return;
212
+
213
+ // Render user bubble
214
+ renderUserBubble(text, pendingAttachments);
215
+
216
+ // Disable input during session creation
217
+ chatInput.value = '';
218
+ chatInput.disabled = true;
219
+ const sendBtn = document.getElementById('chat-send-btn');
220
+ const attachBtn = document.getElementById('attach-btn');
221
+ if (sendBtn) sendBtn.disabled = true;
222
+ if (attachBtn) attachBtn.disabled = true;
223
+
224
+ AppState.reset();
225
+
226
+ const appDesc = buildAppDescription(text);
227
+ const headers = { 'Content-Type': 'application/json' };
228
+ if (chatConfig.apiKey) headers['Authorization'] = `Bearer ${chatConfig.apiKey}`;
229
+
230
+ const body = { ...appDesc, autoApprove: false };
231
+ if (pendingAttachments.length > 0) {
232
+ body.documents = pendingAttachments.map(a => ({ name: a.name, content: a.content }));
233
+ }
234
+
235
+ const attachments = [...pendingAttachments];
236
+ pendingAttachments = [];
237
+ clearAttachmentChips();
238
+
239
+ try {
240
+ const res = await fetch(`${chatConfig.apiBase}/api/v1/app/create`, {
241
+ method: 'POST',
242
+ headers,
243
+ body: JSON.stringify(body),
244
+ });
245
+
246
+ if (!res.ok) {
247
+ const err = await res.text();
248
+ throw new Error(`API ${res.status}: ${err}`);
249
+ }
250
+
251
+ const data = await res.json();
252
+ window.__appId = data.id || data.sessionId;
253
+ currentSessionId = window.__appId;
254
+
255
+ window.__connectSSE(window.__appId);
256
+ AppState.update({ phase: 'initializing' });
257
+ AppState.addLog({ type: 'system', message: `Session started: ${window.__appId}` });
258
+
259
+ // Re-enable input for chat conversation
260
+ chatInput.disabled = false;
261
+ chatInput.placeholder = 'Type a message...';
262
+ if (sendBtn) sendBtn.disabled = false;
263
+ if (attachBtn) attachBtn.disabled = false;
264
+ } catch (err) {
265
+ console.error('[Chat] Session creation error:', err.message);
266
+ showErrorInChat(`Failed to start session: ${err.message}`);
267
+ chatInput.disabled = false;
268
+ if (sendBtn) sendBtn.disabled = false;
269
+ if (attachBtn) attachBtn.disabled = false;
270
+ // Restore attachments on failure
271
+ pendingAttachments = attachments;
272
+ if (attachments.length > 0) {
273
+ for (const a of attachments) renderAttachmentChip(a.name);
274
+ }
275
+ }
276
+ }
277
+
278
+ // ── Ongoing Chat Messages ───────────────────────────────────
279
+
280
+ async function sendMessage() {
281
+ const input = document.getElementById('chat-input');
282
+ if (!input) return;
283
+
284
+ const message = input.value.trim();
285
+ if (!message && pendingCardAnswers.length === 0 && pendingAttachments.length === 0) return;
286
+
287
+ const sessionId = currentSessionId || window.__appId;
288
+ if (!sessionId) return;
289
+
290
+ renderUserBubble(message, pendingAttachments);
291
+ input.value = '';
292
+
293
+ const body = { message: message || '' };
294
+ if (pendingCardAnswers.length > 0) {
295
+ body.cardAnswers = [...pendingCardAnswers];
296
+ pendingCardAnswers = [];
297
+ }
298
+ if (pendingAttachments.length > 0) {
299
+ body.documents = pendingAttachments.map(a => ({ name: a.name, content: a.content }));
300
+ pendingAttachments = [];
301
+ clearAttachmentChips();
302
+ }
303
+
304
+ // Disable card options after sending
305
+ const allCards = document.querySelectorAll('.chat-card');
306
+ allCards.forEach(card => {
307
+ card.querySelectorAll('.chat-card-option').forEach(opt => {
308
+ opt.style.pointerEvents = 'none';
309
+ opt.style.opacity = '0.6';
310
+ });
311
+ });
312
+
313
+ const headers = { 'Content-Type': 'application/json' };
314
+ if (chatConfig.apiKey) headers['Authorization'] = `Bearer ${chatConfig.apiKey}`;
315
+
316
+ try {
317
+ const res = await fetch(`${chatConfig.apiBase}/api/v1/app/${sessionId}/chat`, {
318
+ method: 'POST',
319
+ headers,
320
+ body: JSON.stringify(body),
321
+ });
322
+ if (!res.ok) {
323
+ console.error('[Chat] Send failed:', res.status);
324
+ showErrorInChat(`Message failed to send (${res.status})`);
325
+ }
326
+ } catch (err) {
327
+ console.error('[Chat] Send error:', err);
328
+ showErrorInChat(`Message failed: ${err.message}`);
329
+ }
330
+ }
331
+
332
+ // ── File Attachment ─────────────────────────────────────────
333
+
334
+ function showErrorInChat(message) {
335
+ const messagesEl = document.getElementById('chat-messages');
336
+ if (!messagesEl) return;
337
+
338
+ const msgDiv = document.createElement('div');
339
+ msgDiv.className = 'chat-msg chat-msg-architect';
340
+
341
+ const textDiv = document.createElement('div');
342
+ textDiv.className = 'chat-msg-text';
343
+ textDiv.style.color = 'var(--accent-red)';
344
+ textDiv.textContent = message;
345
+ msgDiv.appendChild(textDiv);
346
+
347
+ messagesEl.appendChild(msgDiv);
348
+ messagesEl.scrollTop = messagesEl.scrollHeight;
349
+ }
350
+
351
+ function handleFileSelected(event) {
352
+ const file = event.target.files?.[0];
353
+ if (!file) return;
354
+
355
+ // Reset input so same file can be re-selected
356
+ event.target.value = '';
357
+
358
+ const ext = '.' + file.name.split('.').pop().toLowerCase();
359
+ if (!ALLOWED_EXTENSIONS.includes(ext)) {
360
+ showErrorInChat(`Unsupported file type: ${ext}. Allowed: ${ALLOWED_EXTENSIONS.join(', ')}`);
361
+ return;
362
+ }
363
+
364
+ if (file.size > MAX_FILE_SIZE) {
365
+ showErrorInChat(`File too large: ${(file.size / 1024).toFixed(0)}KB. Max: ${MAX_FILE_SIZE / 1024}KB`);
366
+ return;
367
+ }
368
+
369
+ const reader = new FileReader();
370
+ reader.onload = () => {
371
+ pendingAttachments.push({ name: file.name, content: reader.result });
372
+ renderAttachmentChip(file.name);
373
+ };
374
+ reader.readAsText(file);
375
+ }
376
+
377
+ function renderAttachmentChip(filename) {
378
+ const preview = document.getElementById('attachment-preview');
379
+ if (!preview) return;
380
+
381
+ preview.classList.remove('hidden');
382
+
383
+ const chip = document.createElement('div');
384
+ chip.className = 'attachment-chip';
385
+ chip.dataset.filename = filename;
386
+
387
+ const nameSpan = document.createElement('span');
388
+ nameSpan.textContent = filename;
389
+ chip.appendChild(nameSpan);
390
+
391
+ const removeBtn = document.createElement('button');
392
+ removeBtn.className = 'attachment-chip-remove';
393
+ removeBtn.textContent = '\u00d7';
394
+ removeBtn.addEventListener('click', () => {
395
+ pendingAttachments = pendingAttachments.filter(a => a.name !== filename);
396
+ chip.remove();
397
+ if (pendingAttachments.length === 0) {
398
+ preview.classList.add('hidden');
399
+ }
400
+ });
401
+ chip.appendChild(removeBtn);
402
+
403
+ preview.appendChild(chip);
404
+ }
405
+
406
+ function clearAttachmentChips() {
407
+ const preview = document.getElementById('attachment-preview');
408
+ if (!preview) return;
409
+ preview.innerHTML = '';
410
+ preview.classList.add('hidden');
411
+ }
412
+
413
+ // ── User Bubble ─────────────────────────────────────────────
414
+
415
+ function renderUserBubble(text, attachments) {
416
+ const messagesEl = document.getElementById('chat-messages');
417
+ if (!messagesEl) return;
418
+ if (!text && (!attachments || attachments.length === 0)) return;
419
+
420
+ const msgDiv = document.createElement('div');
421
+ msgDiv.className = 'chat-msg chat-msg-user';
422
+
423
+ if (text) {
424
+ const textDiv = document.createElement('div');
425
+ textDiv.className = 'chat-msg-text';
426
+ textDiv.textContent = text;
427
+ msgDiv.appendChild(textDiv);
428
+ }
429
+
430
+ if (attachments && attachments.length > 0) {
431
+ for (const att of attachments) {
432
+ const attDiv = document.createElement('div');
433
+ attDiv.className = 'chat-msg-text';
434
+ attDiv.style.fontSize = '12px';
435
+ attDiv.style.opacity = '0.7';
436
+ attDiv.textContent = `\uD83D\uDCCE ${att.name}`;
437
+ msgDiv.appendChild(attDiv);
438
+ }
439
+ }
440
+
441
+ messagesEl.appendChild(msgDiv);
442
+ messagesEl.scrollTop = messagesEl.scrollHeight;
443
+ }
444
+
445
+ // ── Cards ───────────────────────────────────────────────────
133
446
 
134
447
  function renderCard(card) {
135
448
  const cardEl = document.createElement('div');
@@ -176,99 +489,29 @@ function renderCard(card) {
176
489
 
177
490
  function handleCardOptionClick(optEl, card) {
178
491
  const cardId = optEl.dataset.cardId;
179
- const label = optEl.dataset.label;
180
492
  const isMulti = optEl.dataset.multi === 'true';
181
493
 
182
494
  if (isMulti) {
183
495
  optEl.classList.toggle('selected');
184
496
  } else {
185
- // Deselect siblings
186
497
  const siblings = optEl.parentElement.querySelectorAll('.chat-card-option');
187
498
  siblings.forEach(s => s.classList.remove('selected'));
188
499
  optEl.classList.add('selected');
189
500
  }
190
501
 
191
- // Update pending card answers
192
502
  const existingIdx = pendingCardAnswers.findIndex(a => a.questionId === cardId);
193
- if (existingIdx >= 0) {
194
- pendingCardAnswers.splice(existingIdx, 1);
195
- }
503
+ if (existingIdx >= 0) pendingCardAnswers.splice(existingIdx, 1);
196
504
 
197
- // Collect all selected options for this card
198
505
  const cardEl = optEl.closest('.chat-card');
199
506
  const selectedOpts = cardEl.querySelectorAll('.chat-card-option.selected');
200
507
  const selectedLabels = Array.from(selectedOpts).map(el => el.dataset.label);
201
508
 
202
509
  if (selectedLabels.length > 0) {
203
- pendingCardAnswers.push({
204
- questionId: cardId,
205
- selectedOptions: selectedLabels,
206
- });
510
+ pendingCardAnswers.push({ questionId: cardId, selectedOptions: selectedLabels });
207
511
  }
208
512
  }
209
513
 
210
- async function sendMessage() {
211
- const input = document.getElementById('chat-input');
212
- if (!input) return;
213
-
214
- const message = input.value.trim();
215
- if (!message && pendingCardAnswers.length === 0) return;
216
-
217
- const sessionId = currentSessionId || window.__appId;
218
- if (!sessionId) return;
219
-
220
- // Add user message to chat
221
- const messagesEl = document.getElementById('chat-messages');
222
- if (messagesEl && message) {
223
- const msgDiv = document.createElement('div');
224
- msgDiv.className = 'chat-msg chat-msg-user';
225
- const textDiv = document.createElement('div');
226
- textDiv.className = 'chat-msg-text';
227
- textDiv.textContent = message;
228
- msgDiv.appendChild(textDiv);
229
- messagesEl.appendChild(msgDiv);
230
- messagesEl.scrollTop = messagesEl.scrollHeight;
231
- }
232
-
233
- // Clear input
234
- input.value = '';
235
-
236
- // Build request body
237
- const body = { message: message || '' };
238
- if (pendingCardAnswers.length > 0) {
239
- body.cardAnswers = [...pendingCardAnswers];
240
- pendingCardAnswers = [];
241
- }
242
-
243
- // Disable all card options after submitting
244
- const allCards = document.querySelectorAll('.chat-card');
245
- allCards.forEach(card => {
246
- card.querySelectorAll('.chat-card-option').forEach(opt => {
247
- opt.style.pointerEvents = 'none';
248
- opt.style.opacity = '0.6';
249
- });
250
- });
251
-
252
- // Send to API
253
- const headers = { 'Content-Type': 'application/json' };
254
- if (chatConfig.apiKey) headers['Authorization'] = `Bearer ${chatConfig.apiKey}`;
255
-
256
- try {
257
- const res = await fetch(`${chatConfig.apiBase}/api/v1/app/${sessionId}/chat`, {
258
- method: 'POST',
259
- headers,
260
- body: JSON.stringify(body),
261
- });
262
-
263
- if (!res.ok) {
264
- console.error('[Chat] Send failed:', res.status);
265
- AppState.addLog({ level: 'error', message: `Chat send failed: ${res.status}` });
266
- }
267
- } catch (err) {
268
- console.error('[Chat] Send error:', err);
269
- AppState.addLog({ level: 'error', message: `Chat error: ${err.message}` });
270
- }
271
- }
514
+ // ── Build Trigger ───────────────────────────────────────────
272
515
 
273
516
  async function startBuilding() {
274
517
  const sessionId = currentSessionId || window.__appId;
@@ -287,6 +530,6 @@ async function startBuilding() {
287
530
  console.error('[Chat] Start build error:', err);
288
531
  }
289
532
 
290
- closeChat();
533
+ lockChat();
291
534
  AppState.addLog({ level: 'success', message: 'Requirements confirmed — starting build...' });
292
535
  }