synabun 2026.4.21 → 2026.4.25-beta.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.
Files changed (59) hide show
  1. package/CHANGELOG.md +161 -881
  2. package/README.md +1 -1
  3. package/hooks/claude-code/prompt-submit.mjs +39 -18
  4. package/hooks/claude-code/session-start.mjs +10 -3
  5. package/hooks/claude-code/shared.mjs +39 -2
  6. package/hooks/claude-code/stop.mjs +33 -6
  7. package/mcp-server/src/index.ts +9 -1
  8. package/mcp-server/src/services/neural-interface.ts +42 -14
  9. package/mcp-server/src/services/profiles.ts +8 -7
  10. package/mcp-server/src/tools/browser-advanced.ts +12 -10
  11. package/mcp-server/src/tools/browser-interact.ts +39 -38
  12. package/mcp-server/src/tools/browser-navigate.ts +12 -4
  13. package/mcp-server/src/tools/loop.ts +7 -3
  14. package/mcp-server/src/tools/profile.ts +19 -1
  15. package/neural-interface/lib/loop-logger.js +103 -0
  16. package/neural-interface/lib/mcp-installer.js +109 -0
  17. package/neural-interface/lib/plugin-installer.js +264 -0
  18. package/neural-interface/package.json +1 -1
  19. package/neural-interface/public/shared/api.js +66 -17
  20. package/neural-interface/public/shared/cdx/cdx-icons.js +5 -4
  21. package/neural-interface/public/shared/cdx/cdx-panel.js +44 -14
  22. package/neural-interface/public/shared/cdx/cdx-render.js +170 -43
  23. package/neural-interface/public/shared/cdx/cdx-styles.js +23 -10
  24. package/neural-interface/public/shared/cdx/cdx-tabs.js +384 -70
  25. package/neural-interface/public/shared/constants.js +3 -2
  26. package/neural-interface/public/shared/html-shell.js +32 -11
  27. package/neural-interface/public/shared/ocp/ocp-panel.js +45 -28
  28. package/neural-interface/public/shared/ocp/ocp-render.js +50 -43
  29. package/neural-interface/public/shared/ocp/ocp-tabs.js +693 -82
  30. package/neural-interface/public/shared/styles.css +635 -0
  31. package/neural-interface/public/shared/ui-automation-studio.js +38 -12
  32. package/neural-interface/public/shared/ui-claude-panel.js +216 -21
  33. package/neural-interface/public/shared/ui-file-explorer.js +38 -18
  34. package/neural-interface/public/shared/ui-menubar.js +34 -10
  35. package/neural-interface/public/shared/ui-navbar.js +8 -6
  36. package/neural-interface/public/shared/ui-schedules-studio.js +1464 -0
  37. package/neural-interface/public/shared/ui-settings.js +274 -50
  38. package/neural-interface/public/shared/ui-sidepanel-layout.js +18 -7
  39. package/neural-interface/public/shared/ui-sidepanel-tray.js +287 -0
  40. package/neural-interface/public/shared/ui-sync.js +9 -5
  41. package/neural-interface/public/shared/ui-terminal.js +76 -27
  42. package/neural-interface/public/shared/ui-tool-updates.js +49 -15
  43. package/neural-interface/public/shared/ui-tutorial-steps.js +4 -2
  44. package/neural-interface/public/shared/ui-tutorial.js +24 -18
  45. package/neural-interface/public/shared/ui-whiteboard.js +79 -31
  46. package/neural-interface/public/shared/vterm-buffer.js +9 -8
  47. package/neural-interface/public/variant/2d/main.js +9 -7
  48. package/neural-interface/public/variant/3d/main.js +10 -8
  49. package/neural-interface/server.js +1281 -252
  50. package/package.json +1 -1
  51. package/skills/leonardo/SKILL.md +5 -1
  52. package/skills/leonardo/modules/image-prompter.md +5 -1
  53. package/skills/leonardo/modules/video-prompter.md +5 -1
  54. package/skills/synabun/SKILL.md +44 -26
  55. package/skills/synabun/modules/audit.md +40 -36
  56. package/skills/synabun/modules/changelog.md +22 -18
  57. package/skills/synabun/modules/memorize.md +19 -15
  58. package/skills/synabun/modules/schedule.md +285 -0
  59. package/CLAUDE.md +0 -217
@@ -109,7 +109,7 @@ function extractCompactAssistantSummary(text) {
109
109
  .replace(/\s+/g, ' ')
110
110
  .trim();
111
111
  if (!value) return 'Working through the next step';
112
- return value.length > 160 ? `${value.slice(0, 157).trimEnd()}…` : value;
112
+ return value;
113
113
  }
114
114
 
115
115
  // ═══════════════════════════════════════════
@@ -1091,8 +1091,8 @@ export function createMessageShell(kind, label, { phase = '' } = {}) {
1091
1091
  head.appendChild(labelEl);
1092
1092
  const phaseEl = document.createElement('div');
1093
1093
  phaseEl.className = 'cxp-msg-phase';
1094
- phaseEl.hidden = !normalizedPhase;
1095
- phaseEl.textContent = normalizedPhase === 'commentary' ? 'Commentary' : 'Final';
1094
+ phaseEl.hidden = normalizedPhase !== 'final_answer';
1095
+ phaseEl.textContent = normalizedPhase === 'final_answer' ? 'Final' : '';
1096
1096
  head.appendChild(phaseEl);
1097
1097
  card.appendChild(head);
1098
1098
 
@@ -1181,18 +1181,19 @@ export function renderPostPlanActions(tab = _ctx.boundTab, headerText = null) {
1181
1181
 
1182
1182
  const note = document.createElement('div');
1183
1183
  note.className = 'cxp-post-plan-note';
1184
- note.textContent = 'Plan ready. Continue into implementation, compact before proceeding, or reopen the plan in the editor.';
1184
+ note.textContent = 'Plan ready. Continue into implementation, keep planning, compact, or reopen the plan in the editor.';
1185
1185
  card.appendChild(note);
1186
1186
 
1187
1187
  const actions = document.createElement('div');
1188
1188
  actions.className = 'cxp-post-plan-actions';
1189
1189
 
1190
1190
  const continueBtn = _ctx.createRequestButton('Continue with implementation', 'primary');
1191
+ const continuePlanningBtn = _ctx.createRequestButton('Continue planning', 'secondary');
1191
1192
  const compactBtn = _ctx.createRequestButton('Compact context', 'secondary');
1192
1193
  const editBtn = _ctx.createRequestButton('Edit plan', 'secondary');
1193
1194
 
1194
1195
  const setBusy = (busy) => {
1195
- [continueBtn, compactBtn, editBtn].forEach((btn) => { btn.disabled = busy; });
1196
+ [continueBtn, continuePlanningBtn, compactBtn, editBtn].forEach((btn) => { btn.disabled = busy; });
1196
1197
  card.style.opacity = busy ? '0.55' : '1';
1197
1198
  card.style.pointerEvents = busy ? 'none' : 'auto';
1198
1199
  };
@@ -1205,15 +1206,23 @@ export function renderPostPlanActions(tab = _ctx.boundTab, headerText = null) {
1205
1206
  : 'Continue with the implementation based on the approved plan.';
1206
1207
  tab.planMode = false;
1207
1208
  tab.showPostPlanActions = false;
1209
+ tab.planApprovalPending = false;
1210
+ tab.planTurnActive = false;
1211
+ tab.lastPlanTurnId = '';
1208
1212
  tab.postPlanHeader = 'PLAN COMPLETE';
1209
1213
  if (typeof _ctx.setShowPostPlanActions === 'function') _ctx.setShowPostPlanActions(false);
1214
+ if (typeof _ctx.setPlanApprovalPending === 'function') _ctx.setPlanApprovalPending(false);
1215
+ if (typeof _ctx.setPlanTurnActive === 'function') _ctx.setPlanTurnActive(false);
1216
+ if (typeof _ctx.setLastPlanTurnId === 'function') _ctx.setLastPlanTurnId('');
1210
1217
  if (typeof _ctx.setPostPlanHeader === 'function') _ctx.setPostPlanHeader('PLAN COMPLETE');
1211
1218
  _ctx.syncToolbarState();
1212
1219
  const sent = _ctx.dispatchPrompt(finalPrompt, { tab, forcePlanModePrefix: false });
1213
1220
  if (!sent) {
1214
1221
  tab.planMode = true;
1215
1222
  tab.showPostPlanActions = true;
1223
+ tab.planApprovalPending = true;
1216
1224
  if (typeof _ctx.setShowPostPlanActions === 'function') _ctx.setShowPostPlanActions(true);
1225
+ if (typeof _ctx.setPlanApprovalPending === 'function') _ctx.setPlanApprovalPending(true);
1217
1226
  _ctx.syncToolbarState();
1218
1227
  renderPostPlanActions(tab);
1219
1228
  return;
@@ -1223,6 +1232,22 @@ export function renderPostPlanActions(tab = _ctx.boundTab, headerText = null) {
1223
1232
  });
1224
1233
  });
1225
1234
 
1235
+ continuePlanningBtn.addEventListener('click', () => {
1236
+ _ctx.withTab(tab, () => {
1237
+ if (_ctx.running) return;
1238
+ tab.showPostPlanActions = false;
1239
+ if (typeof _ctx.setShowPostPlanActions === 'function') _ctx.setShowPostPlanActions(false);
1240
+ _ctx.syncToolbarState();
1241
+ removePostPlanCards(tab.messagesEl);
1242
+ updatePostPlanTranscriptMeta(tab);
1243
+ const planText = tab.planContent || '';
1244
+ const prompt = planText
1245
+ ? `Continue refining the plan below based on new requirements or feedback:\n\n${planText}`
1246
+ : 'Continue planning. Please extend or revise the current plan based on new requirements or feedback.';
1247
+ _ctx.dispatchPrompt(prompt, { tab, forcePlanModePrefix: true });
1248
+ });
1249
+ });
1250
+
1226
1251
  compactBtn.addEventListener('click', () => {
1227
1252
  _ctx.withTab(tab, () => {
1228
1253
  if (_ctx.running || _ctx.startingThread || _ctx.compacting) return;
@@ -1240,22 +1265,29 @@ export function renderPostPlanActions(tab = _ctx.boundTab, headerText = null) {
1240
1265
  try {
1241
1266
  let filePath = tab.planFilePath || '';
1242
1267
  if (!filePath) {
1243
- const planText = _ctx.capturePlanContent(tab);
1244
- if (!planText) {
1245
- noFile();
1246
- return;
1268
+ filePath = typeof _ctx.ensurePlanFile === 'function' ? await _ctx.ensurePlanFile(tab) : '';
1269
+ if (!filePath) {
1270
+ const planText = _ctx.capturePlanContent(tab);
1271
+ if (!planText) {
1272
+ noFile();
1273
+ return;
1274
+ }
1275
+ const res = await fetch('/api/create-plan', {
1276
+ method: 'POST',
1277
+ headers: { 'Content-Type': 'application/json' },
1278
+ body: JSON.stringify({ content: planText, cwd: tab.project || _ctx.project || '' }),
1279
+ });
1280
+ const result = await res.json();
1281
+ if (!res.ok || !result?.ok || !result.path) {
1282
+ noFile();
1283
+ return;
1284
+ }
1285
+ filePath = result.path;
1247
1286
  }
1248
- const res = await fetch('/api/create-plan', {
1249
- method: 'POST',
1250
- headers: { 'Content-Type': 'application/json' },
1251
- body: JSON.stringify({ content: planText }),
1252
- });
1253
- const result = await res.json();
1254
- if (!res.ok || !result?.ok || !result.path) {
1287
+ if (!filePath) {
1255
1288
  noFile();
1256
1289
  return;
1257
1290
  }
1258
- filePath = result.path;
1259
1291
  tab.planFilePath = filePath;
1260
1292
  if (_ctx.isActiveTab(tab)) {
1261
1293
  if (typeof _ctx.setPlanFilePath === 'function') _ctx.setPlanFilePath(filePath);
@@ -1268,7 +1300,7 @@ export function renderPostPlanActions(tab = _ctx.boundTab, headerText = null) {
1268
1300
  }
1269
1301
  });
1270
1302
 
1271
- actions.append(continueBtn, compactBtn, editBtn);
1303
+ actions.append(continueBtn, continuePlanningBtn, compactBtn, editBtn);
1272
1304
  card.appendChild(actions);
1273
1305
  body.appendChild(card);
1274
1306
  cardWrap.appendChild(body);
@@ -1492,6 +1524,76 @@ function hasOwnField(value, key) {
1492
1524
  return !!value && Object.prototype.hasOwnProperty.call(value, key);
1493
1525
  }
1494
1526
 
1527
+ function normalizeReasoningText(value) {
1528
+ if (typeof value === 'string') return value;
1529
+ if (value == null) return '';
1530
+ if (Array.isArray(value)) {
1531
+ return value.map(normalizeReasoningText).filter(Boolean).join('\n');
1532
+ }
1533
+ if (typeof value === 'object') {
1534
+ for (const key of ['text', 'summaryText', 'summary_text', 'content', 'message']) {
1535
+ if (typeof value[key] === 'string') return value[key];
1536
+ }
1537
+ return extractStructuredText(value);
1538
+ }
1539
+ return String(value);
1540
+ }
1541
+
1542
+ function normalizeReasoningLines(value) {
1543
+ if (value == null) return [];
1544
+ const values = Array.isArray(value) ? value : [value];
1545
+ return values
1546
+ .map((entry) => normalizeReasoningText(entry).trim())
1547
+ .filter(Boolean);
1548
+ }
1549
+
1550
+ function reasoningTextFromKeys(item, keys) {
1551
+ if (!item || typeof item !== 'object') return [];
1552
+ for (const key of keys) {
1553
+ if (!hasOwnField(item, key)) continue;
1554
+ const lines = normalizeReasoningLines(item[key]);
1555
+ if (lines.length) return lines;
1556
+ }
1557
+ return [];
1558
+ }
1559
+
1560
+ function isCompleteStatus(status) {
1561
+ if (!status) return false;
1562
+ if (typeof status === 'string') {
1563
+ return /^(complete|completed|done|success|failed|errored)$/i.test(status.trim());
1564
+ }
1565
+ if (typeof status === 'object') {
1566
+ return Object.keys(status).some((key) => isCompleteStatus(key));
1567
+ }
1568
+ return false;
1569
+ }
1570
+
1571
+ function isReasoningComplete(item, hasIncomingText = false) {
1572
+ return isCompleteStatus(item?.status)
1573
+ || !!item?.completedAt
1574
+ || !!item?.completed_at
1575
+ || item?.durationMs != null
1576
+ || item?.duration_ms != null
1577
+ || (hasIncomingText && !_ctx.running);
1578
+ }
1579
+
1580
+ function getReasoningPreviewText(state) {
1581
+ const summaryText = Array.isArray(state?.summaryLines)
1582
+ ? state.summaryLines.map((line) => String(line || '').trim()).filter(Boolean).join(' ')
1583
+ : '';
1584
+ const contentText = Array.isArray(state?.contentLines)
1585
+ ? state.contentLines.map((line) => String(line || '').trim()).filter(Boolean).join(' ')
1586
+ : '';
1587
+ return _ctx.cleanPreview(summaryText || contentText || 'Working through the next step');
1588
+ }
1589
+
1590
+ function updateReasoningChrome(state, { complete = false } = {}) {
1591
+ if (!state) return;
1592
+ if (state.summaryEl) state.summaryEl.textContent = getReasoningPreviewText(state);
1593
+ if (state.pillEl) state.pillEl.textContent = complete ? 'complete' : 'live';
1594
+ if (state.el) state.el.classList.toggle('cxp-reasoning-done', !!complete);
1595
+ }
1596
+
1495
1597
  function cacheCollapsedCardState(state, item, itemType) {
1496
1598
  if (!state || !item) return;
1497
1599
  switch (itemType) {
@@ -1774,8 +1876,8 @@ export function ensureReasoningState(item) {
1774
1876
  summary.innerHTML = `
1775
1877
  <span class="cxp-card-icon">${ICON_SPARK}</span>
1776
1878
  <span class="cxp-card-titles">
1777
- <span class="cxp-card-title">Reasoning</span>
1778
- <span class="cxp-card-subtitle">Intermediate thought stream</span>
1879
+ <span class="cxp-card-title">Reasoning Summary</span>
1880
+ <span class="cxp-card-subtitle">Working through the next step</span>
1779
1881
  </span>
1780
1882
  <span class="cxp-think-dots"><span></span><span></span><span></span></span>
1781
1883
  <span class="cxp-status-pill">live</span>
@@ -1789,6 +1891,7 @@ export function ensureReasoningState(item) {
1789
1891
  details.open = true;
1790
1892
  details.addEventListener('toggle', () => {
1791
1893
  details.dataset.expanded = details.open ? '1' : '0';
1894
+ if (state && !state.suppressToggleTracking) state.userToggled = true;
1792
1895
  _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
1793
1896
  });
1794
1897
 
@@ -1797,9 +1900,12 @@ export function ensureReasoningState(item) {
1797
1900
  type: 'reasoning',
1798
1901
  el: details,
1799
1902
  bodyEl: body,
1800
- summaryEl: summary.querySelector('.cxp-status-pill'),
1903
+ summaryEl: summary.querySelector('.cxp-card-subtitle'),
1904
+ pillEl: summary.querySelector('.cxp-status-pill'),
1801
1905
  summaryLines: [],
1802
1906
  contentLines: [],
1907
+ userToggled: false,
1908
+ suppressToggleTracking: false,
1803
1909
  };
1804
1910
  _ctx.items.set(item.id, state);
1805
1911
  return state;
@@ -2146,30 +2252,36 @@ export function updateItemFromData(item) {
2146
2252
  if (phase) state.el.dataset.phase = phase;
2147
2253
  else delete state.el.dataset.phase;
2148
2254
  }
2149
- if (state.labelEl) state.labelEl.textContent = phase === 'commentary' ? 'Codex Commentary' : 'Codex';
2255
+ if (state.labelEl) state.labelEl.textContent = 'Codex';
2150
2256
  if (state.phaseEl) {
2151
- state.phaseEl.hidden = !phase;
2152
- state.phaseEl.textContent = phase === 'commentary' ? 'Commentary' : 'Final';
2257
+ state.phaseEl.hidden = phase !== 'final_answer';
2258
+ state.phaseEl.textContent = phase === 'final_answer' ? 'Final' : '';
2153
2259
  }
2154
2260
  setMarkdownBuffer(state, resolveFinalMarkdownText(state, item.text), true);
2155
2261
  break;
2156
2262
  }
2157
2263
  case 'reasoning': {
2158
- const nextSummaryLines = Array.isArray(item.summary) && item.summary.length
2159
- ? [...item.summary]
2160
- : state.summaryLines;
2161
- const nextContentLines = Array.isArray(item.content) && item.content.length
2162
- ? [...item.content]
2163
- : state.contentLines;
2164
- state.summaryLines = Array.isArray(nextSummaryLines) ? nextSummaryLines : [];
2165
- state.contentLines = Array.isArray(nextContentLines) ? nextContentLines : [];
2166
- const summaryText = state.summaryLines.join(' • ');
2167
- if (state.summaryEl) state.summaryEl.textContent = summaryText || 'done';
2168
- state.bodyEl.textContent = getReasoningBodyText(state);
2169
- if (state.el) {
2170
- state.el.classList.add('cxp-reasoning-done');
2264
+ const incomingSummaryLines = reasoningTextFromKeys(item, ['summary', 'summaryText', 'summary_text']);
2265
+ const incomingContentLines = reasoningTextFromKeys(item, ['content', 'text']);
2266
+ const hasIncomingText = incomingSummaryLines.length > 0 || incomingContentLines.length > 0;
2267
+ if (incomingSummaryLines.length) state.summaryLines = incomingSummaryLines;
2268
+ if (incomingContentLines.length) state.contentLines = incomingContentLines;
2269
+ const complete = isReasoningComplete(item, hasIncomingText);
2270
+ updateReasoningChrome(state, { complete });
2271
+ if (state.bodyEl) state.bodyEl.textContent = getReasoningBodyText(state, { complete });
2272
+ if (state.el && complete && !state.userToggled) {
2273
+ state.suppressToggleTracking = true;
2171
2274
  state.el.open = false;
2172
2275
  state.el.dataset.expanded = '0';
2276
+ setTimeout(() => { state.suppressToggleTracking = false; }, 0);
2277
+ } else if (state.el && !complete) {
2278
+ state.el.classList.remove('cxp-reasoning-done');
2279
+ if (!state.userToggled) {
2280
+ state.suppressToggleTracking = true;
2281
+ state.el.open = true;
2282
+ state.el.dataset.expanded = '1';
2283
+ setTimeout(() => { state.suppressToggleTracking = false; }, 0);
2284
+ }
2173
2285
  }
2174
2286
  break;
2175
2287
  }
@@ -2346,25 +2458,33 @@ export function appendPlanDelta(itemId, delta) {
2346
2458
  setMarkdownBuffer(state, state.buffer, false);
2347
2459
  }
2348
2460
 
2349
- export function getReasoningBodyText(state) {
2350
- if (!state) return '(reasoning complete)';
2461
+ export function getReasoningBodyText(state, { complete = false } = {}) {
2462
+ if (!state) return complete ? '(reasoning complete)' : 'Waiting for reasoning summary...';
2351
2463
  const contentText = Array.isArray(state.contentLines) ? state.contentLines.join('').trim() : '';
2352
2464
  if (contentText) return contentText;
2353
2465
  const summaryText = Array.isArray(state.summaryLines)
2354
2466
  ? state.summaryLines.map((line) => String(line || '').trim()).filter(Boolean).join('\n')
2355
2467
  : '';
2356
- return summaryText || '(reasoning complete)';
2468
+ return summaryText || (complete ? '(reasoning complete)' : 'Waiting for reasoning summary...');
2357
2469
  }
2358
2470
 
2359
2471
  export function appendReasoningDelta(itemId, delta, isSummary = false, summaryIndex = null) {
2360
2472
  const state = ensureReasoningState({ id: itemId });
2473
+ state.el?.classList.remove('cxp-reasoning-done');
2474
+ if (state.pillEl) state.pillEl.textContent = 'live';
2475
+ if (!state.userToggled && state.el && !state.el.open) {
2476
+ state.suppressToggleTracking = true;
2477
+ state.el.open = true;
2478
+ state.el.dataset.expanded = '1';
2479
+ setTimeout(() => { state.suppressToggleTracking = false; }, 0);
2480
+ }
2361
2481
  if (isSummary) {
2362
2482
  const idx = Number.isFinite(summaryIndex) ? Math.max(0, summaryIndex) : state.summaryLines.length;
2363
2483
  state.summaryLines[idx] = (state.summaryLines[idx] || '') + (delta || '');
2364
- if (state.summaryEl) state.summaryEl.textContent = state.summaryLines.join('').trim() || 'live';
2365
2484
  } else {
2366
2485
  state.contentLines.push(delta || '');
2367
2486
  }
2487
+ updateReasoningChrome(state, { complete: false });
2368
2488
  state.bodyEl.textContent = getReasoningBodyText(state);
2369
2489
  _ctx.scrollEnd();
2370
2490
  _ctx.scheduleThreadSnapshotSave(_ctx.boundTab);
@@ -2374,6 +2494,7 @@ export function appendReasoningSummaryPart(itemId, summaryIndex) {
2374
2494
  const state = ensureReasoningState({ id: itemId });
2375
2495
  const idx = Number.isFinite(summaryIndex) ? Math.max(0, summaryIndex) : state.summaryLines.length;
2376
2496
  if (state.summaryLines[idx] == null) state.summaryLines[idx] = '';
2497
+ updateReasoningChrome(state, { complete: false });
2377
2498
  state.bodyEl.textContent = getReasoningBodyText(state);
2378
2499
  }
2379
2500
 
@@ -2452,9 +2573,15 @@ export function resolveHistoryRenderSource(thread, fallbackItems = null, storedS
2452
2573
  const snapshotAtLeastAsComplete = !!snapshot
2453
2574
  && snapshot.itemCount > 0
2454
2575
  && snapshot.itemCount >= structuredItemCount;
2455
- const snapshotTrusted = snapshotSourceType === 'sdk' || snapshotSourceType === 'live';
2576
+ // Trust the snapshot regardless of original source: SDK rebuild path was
2577
+ // observed to lose live render fidelity (streaming partials, MCP card state,
2578
+ // post-plan/post-compaction overlays). The snapshot is the most faithful
2579
+ // record of what the user saw, so prefer it when it's complete and fresh.
2580
+ const snapshotTrusted = !!snapshotSourceType;
2581
+ // Active threads still get the snapshot when no live SSE stream is reattaching
2582
+ // — covers the fresh-browser-load case where the thread was previously
2583
+ // running but the page reloaded before completion.
2456
2584
  const canPreferSnapshot = !!snapshot
2457
- && thread?.status?.type !== 'active'
2458
2585
  && snapshotTrusted
2459
2586
  && snapshotFreshEnough
2460
2587
  && snapshotAtLeastAsComplete;
@@ -249,6 +249,10 @@ export function injectStyles() {
249
249
  .cxp-effort-dots i.lit { opacity: 1; transform: scale(1.2); }
250
250
  .cxp-toolbar-toggle[data-effort="off"] .cxp-effort-dots { display: none; }
251
251
 
252
+ #cxp-effort-toggle[data-effort="minimal"] { color: rgba(255,255,255,0.3); }
253
+ #cxp-effort-toggle[data-effort="minimal"]::after { transform: scaleX(0.25); background: rgba(255,255,255,0.16); }
254
+ #cxp-effort-toggle[data-effort="minimal"] .cxp-effort-dots i.lit { background: rgba(255,255,255,0.3); }
255
+
252
256
  #cxp-effort-toggle[data-effort="low"] { color: rgba(255,255,255,0.35); }
253
257
  #cxp-effort-toggle[data-effort="low"]::after { transform: scaleX(0.4); background: rgba(255,255,255,0.2); }
254
258
  #cxp-effort-toggle[data-effort="low"] .cxp-effort-dots i.lit { background: rgba(255,255,255,0.35); }
@@ -261,6 +265,10 @@ export function injectStyles() {
261
265
  #cxp-effort-toggle[data-effort="high"]::after { transform: scaleX(0.8); background: rgba(255,255,255,0.4); }
262
266
  #cxp-effort-toggle[data-effort="high"] .cxp-effort-dots i.lit { background: rgba(255,255,255,0.5); }
263
267
 
268
+ #cxp-effort-toggle[data-effort="xhigh"] { color: rgba(255,255,255,0.7); }
269
+ #cxp-effort-toggle[data-effort="xhigh"]::after { transform: scaleX(1); background: rgba(255,255,255,0.5); }
270
+ #cxp-effort-toggle[data-effort="xhigh"] .cxp-effort-dots i.lit { background: rgba(255,255,255,0.65); }
271
+
264
272
  /* ── Plan toggle active — blue underline ── */
265
273
  #cxp-plan-toggle.active { color: rgba(130, 175, 255, 0.85); }
266
274
  #cxp-plan-toggle.active::after { transform: scaleX(1); background: rgba(130, 175, 255, 0.6); }
@@ -1079,15 +1087,8 @@ export function injectStyles() {
1079
1087
  text-transform: uppercase;
1080
1088
  letter-spacing: 0.05em;
1081
1089
  }
1082
- .cxp-msg-assistant[data-phase="commentary"] .cxp-msg-card {
1083
- padding: 10px 12px 12px;
1084
- border-radius: 14px;
1085
- border: 1px solid rgba(255,255,255,0.05);
1086
- background: linear-gradient(180deg, rgba(255,255,255,0.04), rgba(255,255,255,0.015));
1087
- }
1088
- .cxp-msg-assistant[data-phase="commentary"] .cxp-msg-label,
1089
- .cxp-msg-assistant[data-phase="commentary"] .cxp-msg-phase {
1090
- color: rgba(255,255,255,0.58);
1090
+ .cxp-msg-assistant[data-phase="commentary"] .cxp-msg-head {
1091
+ display: none;
1091
1092
  }
1092
1093
  .cxp-msg-assistant[data-phase="final_answer"] .cxp-msg-phase {
1093
1094
  color: rgba(115, 213, 167, 0.85);
@@ -1631,6 +1632,18 @@ export function injectStyles() {
1631
1632
  }
1632
1633
  .cxp-reasoning summary .cxp-card-titles {
1633
1634
  flex: 1;
1635
+ min-width: 0;
1636
+ }
1637
+ .cxp-reasoning:not(.cxp-reasoning-done) {
1638
+ border-color: rgba(149, 209, 255, 0.11);
1639
+ background: rgba(149, 209, 255, 0.035);
1640
+ }
1641
+ .cxp-reasoning.cxp-reasoning-done {
1642
+ background: rgba(255,255,255,0.025);
1643
+ }
1644
+ .cxp-reasoning.cxp-reasoning-done .cxp-status-pill {
1645
+ color: rgba(255,255,255,0.45);
1646
+ background: rgba(255,255,255,0.035);
1634
1647
  }
1635
1648
  .cxp-reasoning-body {
1636
1649
  padding: 0 11px 11px;
@@ -1639,7 +1652,7 @@ export function injectStyles() {
1639
1652
  color: rgba(255,255,255,0.78);
1640
1653
  font-size: 11.5px;
1641
1654
  line-height: 1.6;
1642
- max-height: 300px;
1655
+ max-height: min(360px, 42vh);
1643
1656
  overflow-y: auto;
1644
1657
  }
1645
1658
  .cxp-ask {