dryymatt commited on
Commit
9c322c2
·
verified ·
1 Parent(s): 5f99f62

Upload static/wizard.js

Browse files
Files changed (1) hide show
  1. static/wizard.js +312 -142
static/wizard.js CHANGED
@@ -1,14 +1,18 @@
1
  /**
2
  * ╔══════════════════════════════════════════════════════╗
3
- * ║ OMNI-VIBE STUDIO — Client Runtime
4
- * ║ Fluid Thinking · Wizard Pulse · E2E Handshake
 
5
  * ╚══════════════════════════════════════════════════════╝
6
  */
7
 
8
  const S = {
9
- sessionId: null, fullCode: '', previewUnlocked: false,
10
  publishReady: false, publishData: null, handshakeVerified: false,
 
 
11
  };
 
12
  const D = {
13
  prompt: document.getElementById('prompt-input'),
14
  vibeBtn: document.getElementById('vibe-btn'),
@@ -16,23 +20,113 @@ const D = {
16
  charCount: document.getElementById('char-count'),
17
  previewFrame: document.getElementById('preview-frame'),
18
  previewOverlay: document.getElementById('preview-overlay'),
19
- previewBadge: document.getElementById('preview-badge'),
20
  previewTabBtn: document.getElementById('preview-tab-btn'),
21
  panelForge: document.getElementById('panel-forge'),
22
  panelPreview: document.getElementById('panel-preview'),
23
  publishBtn: document.getElementById('publish-btn'),
24
- publishBar: document.getElementById('publish-bar'),
25
- pubIcon: document.getElementById('pub-icon'),
26
- pubText: document.getElementById('pub-text'),
 
27
  publishResult: document.getElementById('publish-result'),
28
  liveLink: document.getElementById('live-link'),
29
  wizardHat: document.getElementById('wizard-hat'),
30
- thoughtBar: document.getElementById('thought-bar'),
31
- thoughtText: document.getElementById('thought-text'),
32
- thoughtModels: document.getElementById('thought-models'),
 
33
  copyBtn: document.getElementById('copy-btn'),
34
  };
35
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
36
  /* ═══ TAB SWITCHING ═══ */
37
  document.querySelectorAll('.tab-btn').forEach(btn => {
38
  btn.addEventListener('click', () => {
@@ -45,41 +139,44 @@ document.querySelectorAll('.tab-btn').forEach(btn => {
45
  });
46
 
47
  function unlockPreview() {
48
- if (S.previewUnlocked) return;
49
- S.previewUnlocked = true;
50
  D.previewTabBtn.disabled = false;
51
  D.previewTabBtn.classList.remove('locked');
52
- D.previewTabBtn.classList.add('unlocked');
53
- D.previewTabBtn.querySelector('.tab-icon').textContent = '👁️';
54
  }
55
 
56
- /* ═══ WIZARD PULSEhigh-velocity, 600ms ═══ */
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  function pulseWizard() {
58
  D.wizardHat.classList.add('pulse');
 
59
  }
60
  function stopWizard(state) {
61
- D.wizardHat.classList.remove('pulse');
62
- D.wizardHat.classList.remove('stable','error','published');
63
  if (state) D.wizardHat.classList.add(state);
64
  }
65
 
66
- /* ═══ SWARM THOUGHT PROCESS — single line ═══ */
67
- function showThought() {
68
- D.thoughtBar.style.display = 'flex';
69
- requestAnimationFrame(() => D.thoughtBar.classList.add('visible'));
70
- }
71
- function updateThought(text, models) {
72
- D.thoughtText.textContent = text;
73
- if (models?.length) {
74
- D.thoughtModels.innerHTML = models.map(m =>
75
- `<span>${m.model_id.split('/').pop()}</span>`
76
- ).join('');
77
- }
78
- }
79
- function hideThought() {
80
- D.thoughtBar.classList.remove('visible');
81
- }
82
-
83
  /* ═══ SSE STREAMING ═══ */
84
  async function* streamFromServer(prompt) {
85
  const resp = await fetch('/api/stream', {
@@ -105,7 +202,7 @@ async function* streamFromServer(prompt) {
105
  }
106
  }
107
 
108
- /* ═══ IFRAME ═══ */
109
  function updateIframe(code) {
110
  const blob = new Blob([code], {type:'text/html'});
111
  const url = URL.createObjectURL(blob);
@@ -113,6 +210,8 @@ function updateIframe(code) {
113
  D.previewFrame._blobUrl = url;
114
  D.previewFrame.src = url;
115
  D.previewOverlay.classList.add('hidden');
 
 
116
  }
117
 
118
  function appendCode(chunk) {
@@ -124,42 +223,95 @@ function appendCode(chunk) {
124
  D.charCount.textContent = `${S.fullCode.length.toLocaleString()} chars`;
125
  }
126
 
127
- /* ═══ END-TO-END HANDSHAKE VERIFICATION ═══ */
128
- async function verifyHandshake(url) {
129
- try {
130
- const resp = await fetch('/api/verify-handshake', {
131
- method:'POST',
132
- headers:{'Content-Type':'application/json'},
133
- body:JSON.stringify({url}),
134
- });
135
- const data = await resp.json();
136
- S.handshakeVerified = data.verified;
137
- return data;
138
- } catch {
139
- S.handshakeVerified = false;
140
- return {verified:false, error:'Handshake unreachable'};
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
141
  }
142
  }
143
 
144
- /* ═══ PUBLISH GATE ═══ */
145
- function setPublishState(ready, text, icon) {
146
- S.publishReady = ready;
147
- D.publishBtn.disabled = !ready;
148
- if (ready) {
149
- D.publishBar.classList.add('unlocked');
150
- D.pubIcon.textContent = icon || '🔓';
151
- D.pubText.textContent = text || 'Ready to publish';
152
- } else {
153
- D.publishBar.classList.remove('unlocked');
154
- D.pubIcon.textContent = icon || '🔒';
155
- D.pubText.textContent = text || 'Validate to unlock publishing';
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
156
  }
 
157
  }
158
 
159
- /* ═══ MAIN GENERATION ═══ */
160
- async function generate() {
161
  const prompt = D.prompt.value.trim(); if (!prompt) return;
162
- S.fullCode = ''; S.previewUnlocked = false; S.publishReady = false; S.handshakeVerified = false;
 
 
 
 
163
 
164
  // Reset
165
  document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
@@ -168,102 +320,87 @@ async function generate() {
168
  D.panelForge.classList.add('active');
169
  D.previewTabBtn.disabled = true;
170
  D.previewTabBtn.classList.add('locked');
171
- D.previewTabBtn.classList.remove('unlocked');
172
- D.previewTabBtn.querySelector('.tab-icon').textContent = '🔒';
173
 
174
- D.codeContent.innerHTML = '<span class="placeholder">Streaming…</span>';
175
  D.charCount.textContent = '';
176
  D.previewOverlay.classList.remove('hidden');
177
  D.previewFrame.src = 'about:blank';
178
  D.publishResult.style.display = 'none';
179
- setPublishState(false, 'Building…', '🔨');
180
  D.vibeBtn.disabled = true;
181
 
182
- // Wizard pulse start
 
183
  pulseWizard();
184
- showThought();
185
- updateThought('Analyzing prompt…', []);
186
 
187
  try {
188
  for await (const ev of streamFromServer(prompt)) {
189
- // Phase updates → single thought line
190
  if (ev.phase) {
191
- const phaseMap = {
192
- 'pose': 'Selecting models…',
193
- 'generate': 'Generating code…',
194
- 'audit': 'Verifying output…',
195
- 'heal': 'Self-healing errors…',
196
- 'sandbox': 'Validating sandbox…',
197
- };
198
- updateThought(phaseMap[ev.phase] || ev.phase, []);
199
  }
200
-
201
- // Pose plan → show model badges
202
  if (ev.pose) {
203
- const models = [];
204
- if (ev.pose.architect?.stack?.backend) models.push({model_id:'postgres+oauth'});
205
- models.push({model_id:'liquid-glass'});
206
- updateThought('Swarm dispatched', models);
207
  }
208
-
209
- // Code streaming
210
  if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; }
211
  if (ev.partial) updateIframe(ev.partial);
212
 
213
- // Sandbox result
214
  if (ev.sandbox) {
215
  stopWizard(ev.status === 'stable' ? 'stable' : ev.status === 'error' ? 'error' : null);
216
  if (ev.status === 'stable' || ev.status === 'published') {
217
  unlockPreview();
218
- setPublishState(true, 'Sandbox stable', '🔓');
219
- updateThought('Build complete', [{model_id:'ready'}]);
 
 
220
  } else if (ev.status === 'error') {
221
- setPublishState(false, 'Sandbox errors found', '❌');
222
- updateThought('Errors detected', []);
223
  }
224
  }
225
  }
226
  } catch (err) {
227
- console.error('Stream error:', err);
228
  stopWizard('error');
229
- updateThought(`Error: ${err.message}`, []);
 
230
  } finally {
231
  D.vibeBtn.disabled = false;
232
  if (!S.publishReady) stopWizard(null);
233
- setTimeout(hideThought, 2000);
 
234
  }
235
  }
236
 
237
- /* ═══ PUBLISH WITH HANDSHAKE ═══ */
238
- async function publish() {
239
- if (!S.publishReady) return;
240
 
241
- // Phase 1: Verify handshake
242
- D.publishBtn.classList.add('verifying');
243
- D.publishBtn.querySelector('.spinner').style.display = 'inline-block';
244
- updateThought('Verifying server handshake…', [{model_id:'litheat.app'}]);
245
- showThought();
246
 
247
- const hk = await verifyHandshake('litheat.app');
248
- if (!hk.verified) {
249
- updateThought('Handshake failed — retrying…', []);
 
250
  // Retry once
251
- await new Promise(r => setTimeout(r, 1000));
252
- const hk2 = await verifyHandshake('litheat.app');
253
- if (!hk2.verified) {
254
- updateThought('⚠ Server unreachable — publishing anyway', []);
255
- D.publishBtn.classList.remove('verifying');
256
- D.vibeBtn.querySelector('.spinner')?.style && (D.publishBtn.querySelector('.spinner').style.display = 'none');
 
 
257
  }
258
- } else {
259
- updateThought('✓ Handshake verified', [{model_id:'litheat.app'}]);
260
  }
261
 
262
  // Phase 2: Publish
263
- const repoName = `omni-vibe-${Date.now().toString(36)}`;
264
- D.publishBtn.disabled = true;
265
- D.publishBtn.classList.add('verifying');
266
- updateThought('Deploying to production…', [{model_id:'ghost-deploy'}]);
267
 
268
  try {
269
  const resp = await fetch('/api/publish', {
@@ -275,44 +412,77 @@ async function publish() {
275
 
276
  if (r.success) {
277
  S.publishData = r;
278
- stopWizard('published');
279
- setPublishState(true, 'Published!', '🌟');
280
- D.publishBtn.classList.add('gold');
281
- D.publishResult.style.display = 'block';
282
- const liveUrl = r.spaces?.url || r.tunnel_url || '#';
283
- D.liveLink.href = liveUrl;
284
- D.liveLink.textContent = liveUrl;
285
- updateThought('✓ Live — production ready', [{model_id:'deployed'}]);
286
- D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'});
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
287
  } else {
288
- updateThought(`Publish error: ${r.error || r.space_error || 'Unknown'}`, []);
289
- setPublishState(true, 'Publish failed — retry', '⚠');
290
  }
291
  } catch (err) {
292
- updateThought(`Publish error: ${err.message}`, []);
293
- setPublishState(true, 'Publish failed — retry', '⚠');
294
- } finally {
295
- D.publishBtn.disabled = !S.publishReady;
296
- D.publishBtn.classList.remove('verifying');
297
- setTimeout(hideThought, 3000);
298
  }
299
  }
300
 
 
 
 
 
 
301
  /* ═══ EVENT BINDINGS ═══ */
302
- D.vibeBtn.addEventListener('click', generate);
303
- D.publishBtn.addEventListener('click', publish);
304
  D.prompt.addEventListener('keydown', e => {
305
- if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); generate(); }
306
  });
307
  D.copyBtn.addEventListener('click', async () => {
308
  if (!S.fullCode) return;
309
  try { await navigator.clipboard.writeText(S.fullCode); } catch {}
310
  });
311
 
312
- /* ═══ AUTO-RESIZE TEXTAREA ═══ */
313
  D.prompt.addEventListener('input', () => {
314
  D.prompt.style.height = 'auto';
315
- D.prompt.style.height = Math.min(D.prompt.scrollHeight, 120) + 'px';
316
  });
317
 
318
- console.log('🧙‍♂️ Omni-Vibe Studio Fluid Thinking Client ready');
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  /**
2
  * ╔══════════════════════════════════════════════════════╗
3
+ * ║ OBSIDIAN FORGE — Client Runtime
4
+ * ║ RLM Thought-Stream · Context Folding
5
+ * ║ Quantum Sandbox · Silent Handshake · Deploy ║
6
  * ╚══════════════════════════════════════════════════════╝
7
  */
8
 
9
  const S = {
10
+ sessionId: null, fullCode: '', previewReady: false,
11
  publishReady: false, publishData: null, handshakeVerified: false,
12
+ handshakeUrl: null, deployedUrl: null,
13
+ quantumWindow: null, // Reference to dedicated preview tab
14
  };
15
+
16
  const D = {
17
  prompt: document.getElementById('prompt-input'),
18
  vibeBtn: document.getElementById('vibe-btn'),
 
20
  charCount: document.getElementById('char-count'),
21
  previewFrame: document.getElementById('preview-frame'),
22
  previewOverlay: document.getElementById('preview-overlay'),
 
23
  previewTabBtn: document.getElementById('preview-tab-btn'),
24
  panelForge: document.getElementById('panel-forge'),
25
  panelPreview: document.getElementById('panel-preview'),
26
  publishBtn: document.getElementById('publish-btn'),
27
+ deployBar: document.getElementById('deploy-bar'),
28
+ deployDot: document.getElementById('deploy-dot'),
29
+ deployText: document.getElementById('deploy-text'),
30
+ deployLabel: document.getElementById('deploy-label'),
31
  publishResult: document.getElementById('publish-result'),
32
  liveLink: document.getElementById('live-link'),
33
  wizardHat: document.getElementById('wizard-hat'),
34
+ orbitRing: document.getElementById('orbit-ring'),
35
+ thoughtStream: document.getElementById('thought-stream'),
36
+ streamText: document.getElementById('stream-text'),
37
+ popoutBtn: document.getElementById('popout-btn'),
38
  copyBtn: document.getElementById('copy-btn'),
39
  };
40
 
41
+ /* ═══ RLM THOUGHT-STREAM NARRATION ═══ */
42
+ const RLM_NARRATIVES = {
43
+ pose: [
44
+ 'Recursively partitioning the Liquid Glass layers...',
45
+ 'Calibrating the RLM feedback loop for structural perfection...',
46
+ 'Analyzing architectural constraints through the Obsidian lens...',
47
+ 'Dispatching sub-agents to the Quantum Sandbox...',
48
+ 'Mapping design topology across the neural manifold...',
49
+ ],
50
+ generate: [
51
+ 'Synthing frontend lattice from compressed semantic schemas...',
52
+ 'Folding context vectors into the DOM manifold...',
53
+ 'Applying Aetheric CSS transforms to the structural lattice...',
54
+ 'Generating Liquid Glass components from RLM state...',
55
+ 'Weaving the Obsidian gradient across the output surface...',
56
+ ],
57
+ audit: [
58
+ 'Auditing code integrity through recursive type propagation...',
59
+ 'Verifying HTML topology against the Liquid Glass schema...',
60
+ 'Scanning for structural anomalies in the generated lattice...',
61
+ 'Cross-referencing output against the RLM design constraints...',
62
+ ],
63
+ heal: [
64
+ 'Applying Reflect-Select healing to identified anomalies...',
65
+ 'Self-correcting structural deviations in real-time...',
66
+ 'Merging healed fragments into the coherent output stream...',
67
+ ],
68
+ sandbox: [
69
+ 'Validating sandbox integrity through the Quantum Sandbox...',
70
+ 'Performing final consistency check on the generated artifact...',
71
+ 'Sealing the output in the Obsidian forge...',
72
+ ],
73
+ done: [
74
+ 'RLM recursion complete. Output stable.',
75
+ 'Forge cycle terminated — artifact ready.',
76
+ 'All sub-agents returned. Sandbox synchronized.',
77
+ ],
78
+ deploy: [
79
+ 'Initiating Ghost Deploy protocol...',
80
+ 'Resolving DNS handshake with litheat.app...',
81
+ 'Verifying deployment endpoint reachability...',
82
+ 'Propagating artifact to production surface...',
83
+ ],
84
+ };
85
+
86
+ let _narrativeIdx = {};
87
+ function pickNarrative(phase) {
88
+ const pool = RLM_NARRATIVES[phase] || RLM_NARRATIVES['pose'];
89
+ if (!_narrativeIdx[phase]) _narrativeIdx[phase] = 0;
90
+ const idx = _narrativeIdx[phase]++ % pool.length;
91
+ return pool[idx];
92
+ }
93
+
94
+ function showThoughtStream() {
95
+ D.thoughtStream.style.display = 'flex';
96
+ requestAnimationFrame(() => D.thoughtStream.classList.add('visible'));
97
+ }
98
+ function addThoughtSentence(phase) {
99
+ showThoughtStream();
100
+ const sentence = pickNarrative(phase);
101
+ const el = document.createElement('span');
102
+ el.className = 'sentence';
103
+ el.textContent = sentence;
104
+ D.streamText.appendChild(el);
105
+ // Auto-scroll
106
+ D.streamText.scrollTop = D.streamText.scrollHeight;
107
+ }
108
+ function clearThoughtStream() {
109
+ D.streamText.innerHTML = '';
110
+ D.thoughtStream.classList.remove('visible');
111
+ }
112
+
113
+ /* ═══ CONTEXT FOLDING — localStorage persistence ═══ */
114
+ const CTX_KEY = 'obsidian-forge-context';
115
+ function foldContext(data) {
116
+ try {
117
+ const ctx = JSON.parse(localStorage.getItem(CTX_KEY) || '{}');
118
+ Object.assign(ctx, data, {_folded: Date.now()});
119
+ localStorage.setItem(CTX_KEY, JSON.stringify(ctx));
120
+ } catch {}
121
+ }
122
+ function unfoldContext() {
123
+ try {
124
+ const ctx = JSON.parse(localStorage.getItem(CTX_KEY) || '{}');
125
+ if (ctx.lastPrompt) D.prompt.value = ctx.lastPrompt;
126
+ return ctx;
127
+ } catch { return {}; }
128
+ }
129
+
130
  /* ═══ TAB SWITCHING ═══ */
131
  document.querySelectorAll('.tab-btn').forEach(btn => {
132
  btn.addEventListener('click', () => {
 
139
  });
140
 
141
  function unlockPreview() {
142
+ if (S.previewReady) return;
143
+ S.previewReady = true;
144
  D.previewTabBtn.disabled = false;
145
  D.previewTabBtn.classList.remove('locked');
146
+ D.previewTabBtn.querySelector('.tab-label').textContent = '🔮 View Preview';
 
147
  }
148
 
149
+ /* ═══ QUANTUM SANDBOXdedicated persistent tab ═══ */
150
+ function launchQuantumSandbox(code) {
151
+ const blob = new Blob([code], {type:'text/html'});
152
+ const url = URL.createObjectURL(blob);
153
+ if (S.quantumWindow && !S.quantumWindow.closed) {
154
+ S.quantumWindow.location.href = url;
155
+ S.quantumWindow.focus();
156
+ } else {
157
+ S.quantumWindow = window.open(url, 'obsidian-quantum-sandbox', 'width=1400,height=900');
158
+ }
159
+ return S.quantumWindow;
160
+ }
161
+ function syncQuantumSandbox(code) {
162
+ if (S.quantumWindow && !S.quantumWindow.closed) {
163
+ const blob = new Blob([code], {type:'text/html'});
164
+ const url = URL.createObjectURL(blob);
165
+ S.quantumWindow.location.href = url;
166
+ }
167
+ }
168
+
169
+ /* ═══ WIZARD PULSE ═══ */
170
  function pulseWizard() {
171
  D.wizardHat.classList.add('pulse');
172
+ D.orbitRing.classList.add('forging');
173
  }
174
  function stopWizard(state) {
175
+ D.wizardHat.classList.remove('pulse','stable','error','deployed');
176
+ D.orbitRing.classList.remove('forging');
177
  if (state) D.wizardHat.classList.add(state);
178
  }
179
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
180
  /* ═══ SSE STREAMING ═══ */
181
  async function* streamFromServer(prompt) {
182
  const resp = await fetch('/api/stream', {
 
202
  }
203
  }
204
 
205
+ /* ═══ IFRAME + QUANTUM ═══ */
206
  function updateIframe(code) {
207
  const blob = new Blob([code], {type:'text/html'});
208
  const url = URL.createObjectURL(blob);
 
210
  D.previewFrame._blobUrl = url;
211
  D.previewFrame.src = url;
212
  D.previewOverlay.classList.add('hidden');
213
+ // Sync quantum tab
214
+ syncQuantumSandbox(code);
215
  }
216
 
217
  function appendCode(chunk) {
 
223
  D.charCount.textContent = `${S.fullCode.length.toLocaleString()} chars`;
224
  }
225
 
226
+ /* ═══ DEPLOY GATE ═══ */
227
+ function setDeployState(state) {
228
+ D.deployBar.classList.remove('ready','verified');
229
+ D.deployDot.classList.remove('ready','verifying','deployed');
230
+ D.publishBtn.classList.remove('ready','deploying','deployed');
231
+ D.deployLabel.textContent = 'Deploy';
232
+
233
+ switch(state) {
234
+ case 'ready':
235
+ D.deployBar.classList.add('ready');
236
+ D.deployDot.classList.add('ready');
237
+ D.publishBtn.classList.add('ready');
238
+ D.publishBtn.disabled = false;
239
+ D.deployText.textContent = 'Sandbox stable';
240
+ break;
241
+ case 'verifying':
242
+ D.deployDot.classList.add('verifying');
243
+ D.publishBtn.classList.add('deploying');
244
+ D.publishBtn.disabled = true;
245
+ D.deployLabel.textContent = 'Verifying…';
246
+ D.deployText.textContent = 'Resolving litheat.app';
247
+ break;
248
+ case 'deploying':
249
+ D.deployDot.classList.add('verifying');
250
+ D.publishBtn.classList.add('deploying');
251
+ D.publishBtn.disabled = true;
252
+ D.deployLabel.textContent = 'Deploying…';
253
+ D.deployText.textContent = 'Ghost Deploy active';
254
+ break;
255
+ case 'deployed':
256
+ D.deployBar.classList.add('verified');
257
+ D.deployDot.classList.add('deployed');
258
+ D.publishBtn.classList.add('deployed');
259
+ D.publishBtn.disabled = false;
260
+ D.deployLabel.textContent = 'Live ◈';
261
+ D.deployText.textContent = 'Verified — Production ready';
262
+ break;
263
+ default:
264
+ D.publishBtn.disabled = true;
265
+ D.deployText.textContent = 'RLM idle';
266
  }
267
  }
268
 
269
+ /* ═══ SILENT HANDSHAKE ═══ */
270
+ async function silentHandshake(urls) {
271
+ for (const url of urls) {
272
+ try {
273
+ const resp = await fetch('/api/verify-handshake', {
274
+ method:'POST',
275
+ headers:{'Content-Type':'application/json'},
276
+ body:JSON.stringify({url}),
277
+ });
278
+ const data = await resp.json();
279
+ if (data.verified) {
280
+ S.handshakeVerified = true;
281
+ return {verified:true, url:data.url, latency:data.latency_ms};
282
+ }
283
+ } catch {}
284
+ }
285
+ return {verified:false};
286
+ }
287
+
288
+ /* ═══ POLL DEPLOYED URL UNTIL 200 ═══ */
289
+ async function pollUntilLive(url, maxAttempts=15) {
290
+ for (let i = 0; i < maxAttempts; i++) {
291
+ try {
292
+ const resp = await fetch('/api/verify-handshake', {
293
+ method:'POST',
294
+ headers:{'Content-Type':'application/json'},
295
+ body:JSON.stringify({url}),
296
+ });
297
+ const data = await resp.json();
298
+ if (data.verified && data.checks?.status_code === 200) {
299
+ return {live:true, url, latency:data.latency_ms};
300
+ }
301
+ } catch {}
302
+ await new Promise(r => setTimeout(r, 2000));
303
  }
304
+ return {live:false, url};
305
  }
306
 
307
+ /* ═══ MAIN FORGE ═══ */
308
+ async function forge() {
309
  const prompt = D.prompt.value.trim(); if (!prompt) return;
310
+ S.fullCode = ''; S.previewReady = false; S.publishReady = false;
311
+ S.handshakeVerified = false; S.handshakeUrl = null; S.deployedUrl = null;
312
+
313
+ // Context Folding: persist user choice
314
+ foldContext({lastPrompt: prompt});
315
 
316
  // Reset
317
  document.querySelectorAll('.tab-btn').forEach(b => b.classList.remove('active'));
 
320
  D.panelForge.classList.add('active');
321
  D.previewTabBtn.disabled = true;
322
  D.previewTabBtn.classList.add('locked');
323
+ D.previewTabBtn.querySelector('.tab-label').textContent = '🔒 Preview';
 
324
 
325
+ D.codeContent.innerHTML = '<span class="placeholder">// Obsidian Forge invoking RLM recursion…</span>';
326
  D.charCount.textContent = '';
327
  D.previewOverlay.classList.remove('hidden');
328
  D.previewFrame.src = 'about:blank';
329
  D.publishResult.style.display = 'none';
330
+ setDeployState('idle');
331
  D.vibeBtn.disabled = true;
332
 
333
+ // Wizard pulse + thought-stream
334
+ clearThoughtStream();
335
  pulseWizard();
336
+ addThoughtSentence('pose');
 
337
 
338
  try {
339
  for await (const ev of streamFromServer(prompt)) {
 
340
  if (ev.phase) {
341
+ addThoughtSentence(ev.phase);
 
 
 
 
 
 
 
342
  }
 
 
343
  if (ev.pose) {
344
+ addThoughtSentence('pose'); // Extra narration for swarm dispatch
 
 
 
345
  }
 
 
346
  if (ev.chunk) { appendCode(ev.chunk); S.fullCode += ev.chunk; }
347
  if (ev.partial) updateIframe(ev.partial);
348
 
 
349
  if (ev.sandbox) {
350
  stopWizard(ev.status === 'stable' ? 'stable' : ev.status === 'error' ? 'error' : null);
351
  if (ev.status === 'stable' || ev.status === 'published') {
352
  unlockPreview();
353
+ setDeployState('ready');
354
+ addThoughtSentence('done');
355
+ // Auto-launch quantum sandbox on stability
356
+ if (S.fullCode) launchQuantumSandbox(S.fullCode);
357
  } else if (ev.status === 'error') {
358
+ setDeployState('idle');
 
359
  }
360
  }
361
  }
362
  } catch (err) {
363
+ console.error('Forge error:', err);
364
  stopWizard('error');
365
+ addThoughtSentence('heal'); // "Self-correcting..."
366
+ D.deployText.textContent = `Error: ${err.message}`;
367
  } finally {
368
  D.vibeBtn.disabled = false;
369
  if (!S.publishReady) stopWizard(null);
370
+ // Persist output context
371
+ foldContext({lastOutputLen: S.fullCode.length, lastForged: Date.now()});
372
  }
373
  }
374
 
375
+ /* ═══ DEPLOY WITH SILENT HANDSHAKE ═══ */
376
+ async function deploy() {
377
+ if (!S.publishReady && !S.fullCode) return;
378
 
379
+ // Phase 1: Silent DNS handshake — verify litheat.app FIRST
380
+ setDeployState('verifying');
381
+ clearThoughtStream();
382
+ addThoughtSentence('deploy');
 
383
 
384
+ const hk = await silentHandshake(['litheat.app', 'huggingface.co']);
385
+ if (hk.verified) {
386
+ addThoughtSentence('deploy');
387
+ } else {
388
  // Retry once
389
+ await new Promise(r => setTimeout(r, 1500));
390
+ const hk2 = await silentHandshake(['litheat.app']);
391
+ if (hk2.verified) {
392
+ addThoughtSentence('deploy');
393
+ } else {
394
+ D.deployText.textContent = 'Handshake failed — retry';
395
+ setDeployState('ready');
396
+ return;
397
  }
 
 
398
  }
399
 
400
  // Phase 2: Publish
401
+ setDeployState('deploying');
402
+ const repoName = `obsidian-${Date.now().toString(36)}`;
403
+ addThoughtSentence('deploy');
 
404
 
405
  try {
406
  const resp = await fetch('/api/publish', {
 
412
 
413
  if (r.success) {
414
  S.publishData = r;
415
+ const rawUrl = r.spaces?.url || r.tunnel_url || '';
416
+ S.deployedUrl = rawUrl;
417
+
418
+ // Phase 3: Poll until 200 — do NOT show URL until verified
419
+ D.deployText.textContent = 'Polling deployment…';
420
+ D.deployLabel.textContent = 'Checking…';
421
+ const live = await pollUntilLive(rawUrl);
422
+
423
+ if (live.live) {
424
+ // Only now reveal the URL
425
+ stopWizard('deployed');
426
+ setDeployState('deployed');
427
+ D.publishResult.style.display = 'block';
428
+ D.liveLink.href = live.url;
429
+ D.liveLink.textContent = live.url;
430
+ foldContext({lastDeployUrl: live.url, lastDeployed: Date.now()});
431
+ addThoughtSentence('deploy');
432
+ D.publishResult.scrollIntoView({behavior:'smooth',block:'nearest'});
433
+ } else {
434
+ // Still show but warn
435
+ stopWizard('deployed');
436
+ setDeployState('deployed');
437
+ D.publishResult.style.display = 'block';
438
+ D.liveLink.href = rawUrl;
439
+ D.liveLink.textContent = rawUrl + ' (propagating)';
440
+ D.deployText.textContent = 'Deployed — warming up';
441
+ }
442
  } else {
443
+ D.deployText.textContent = `Error: ${r.error || r.space_error || 'Unknown'}`;
444
+ setDeployState('ready');
445
  }
446
  } catch (err) {
447
+ D.deployText.textContent = `Error: ${err.message}`;
448
+ setDeployState('ready');
 
 
 
 
449
  }
450
  }
451
 
452
+ /* ═══ QUANTUM SANDBOX POPOUT ═══ */
453
+ D.popoutBtn.addEventListener('click', () => {
454
+ if (S.fullCode) launchQuantumSandbox(S.fullCode);
455
+ });
456
+
457
  /* ═══ EVENT BINDINGS ═══ */
458
+ D.vibeBtn.addEventListener('click', forge);
459
+ D.publishBtn.addEventListener('click', deploy);
460
  D.prompt.addEventListener('keydown', e => {
461
+ if (e.key==='Enter' && !e.shiftKey) { e.preventDefault(); forge(); }
462
  });
463
  D.copyBtn.addEventListener('click', async () => {
464
  if (!S.fullCode) return;
465
  try { await navigator.clipboard.writeText(S.fullCode); } catch {}
466
  });
467
 
468
+ /* ═══ AUTO-RESIZE ═══ */
469
  D.prompt.addEventListener('input', () => {
470
  D.prompt.style.height = 'auto';
471
+ D.prompt.style.height = Math.min(D.prompt.scrollHeight, 110) + 'px';
472
  });
473
 
474
+ /* ═══ CONTEXT FOLDING: restore on load ═══ */
475
+ (function restoreContext() {
476
+ const ctx = unfoldContext();
477
+ if (ctx.lastPrompt) {
478
+ D.prompt.value = ctx.lastPrompt;
479
+ D.prompt.style.height = 'auto';
480
+ D.prompt.style.height = Math.min(D.prompt.scrollHeight, 110) + 'px';
481
+ }
482
+ })();
483
+
484
+ console.log('◈ Obsidian Forge — Kinetic RLM Environment ready');
485
+ console.log(' RLM: Recursive Language Modeling active');
486
+ console.log(' Context Folding: localStorage persistence');
487
+ console.log(' Quantum Sandbox: dedicated tab sync');
488
+ console.log(' Handshake: silent DNS + poll-until-live');