quiver-cli 0.6.0 → 0.7.0

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 (97) hide show
  1. package/LICENSE +21 -0
  2. package/dist/cli.js +125 -43
  3. package/package.json +1 -1
  4. package/template/.agents/skills/agent-browser/SKILL.md +4 -9
  5. package/template/.agents/skills/apps/skybridge/SKILL.md +1 -1
  6. package/template/.agents/skills/code/improve/SKILL.md +9 -5
  7. package/template/.agents/skills/code/improve/references/audit-playbook.md +10 -10
  8. package/template/.agents/skills/code/improve/references/closing-the-loop.md +4 -3
  9. package/template/.agents/skills/code/improve/references/plan-template.md +5 -0
  10. package/template/.agents/skills/design/impeccable/SKILL.md +14 -24
  11. package/template/.agents/skills/design/impeccable/reference/animate.md +1 -1
  12. package/template/.agents/skills/design/impeccable/reference/bolder.md +1 -1
  13. package/template/.agents/skills/design/impeccable/reference/brand.md +2 -2
  14. package/template/.agents/skills/design/impeccable/reference/colorize.md +1 -1
  15. package/template/.agents/skills/design/impeccable/reference/critique.md +6 -6
  16. package/template/.agents/skills/design/impeccable/reference/delight.md +1 -1
  17. package/template/.agents/skills/design/impeccable/reference/distill.md +1 -1
  18. package/template/.agents/skills/design/impeccable/reference/document.md +1 -1
  19. package/template/.agents/skills/design/impeccable/reference/extract.md +1 -1
  20. package/template/.agents/skills/design/impeccable/reference/hooks.md +90 -0
  21. package/template/.agents/skills/design/impeccable/reference/init.md +5 -5
  22. package/template/.agents/skills/design/impeccable/reference/live.md +16 -16
  23. package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -1
  24. package/template/.agents/skills/design/impeccable/reference/polish.md +2 -2
  25. package/template/.agents/skills/design/impeccable/reference/quieter.md +1 -1
  26. package/template/.agents/skills/design/impeccable/reference/shape.md +2 -2
  27. package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +1 -1
  28. package/template/.agents/skills/design/impeccable/scripts/context.mjs +724 -33
  29. package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +1 -1
  30. package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +204 -0
  31. package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +57 -11
  32. package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +750 -0
  33. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +633 -46
  34. package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
  35. package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
  36. package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +43 -10
  37. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
  38. package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
  39. package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +1 -1
  40. package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
  41. package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +401 -46
  42. package/template/.agents/skills/design/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
  43. package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +661 -0
  44. package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +476 -0
  45. package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +1632 -0
  46. package/template/.agents/skills/design/impeccable/scripts/hook.mjs +61 -0
  47. package/template/.agents/skills/design/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
  48. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +638 -0
  49. package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
  50. package/template/.agents/skills/design/impeccable/scripts/lib/target-args.mjs +42 -0
  51. package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +49 -0
  52. package/template/.agents/skills/design/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
  53. package/template/.agents/skills/design/impeccable/scripts/live/manual-apply.mjs +939 -0
  54. package/template/.agents/skills/design/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
  55. package/template/.agents/skills/design/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
  56. package/template/.agents/skills/design/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +1 -1
  57. package/template/.agents/skills/design/impeccable/scripts/{live-ui-core.mjs → live/ui-core.mjs} +2 -1
  58. package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +36 -0
  59. package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +3 -3
  60. package/template/.agents/skills/design/impeccable/scripts/live-browser-dom.js +146 -0
  61. package/template/.agents/skills/design/impeccable/scripts/live-browser.js +1456 -599
  62. package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
  63. package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +2 -2
  64. package/template/.agents/skills/design/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
  65. package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +35 -9
  66. package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +2 -2
  67. package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +2 -2
  68. package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +18 -13
  69. package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +1 -1
  70. package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +77 -1264
  71. package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +2 -2
  72. package/template/.agents/skills/design/impeccable/scripts/live-target.mjs +30 -0
  73. package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +4 -4
  74. package/template/.agents/skills/design/impeccable/scripts/live.mjs +73 -22
  75. package/template/.agents/skills/writing/humanizer/SKILL.md +621 -0
  76. package/template/.agents/upstreams.json +17 -8
  77. package/template/.agents/skills/apps/skybridge/references/architecture.md +0 -175
  78. package/template/.agents/skills/apps/skybridge/references/copy-template.md +0 -24
  79. package/template/.agents/skills/apps/skybridge/references/csp.md +0 -33
  80. package/template/.agents/skills/apps/skybridge/references/deploy.md +0 -33
  81. package/template/.agents/skills/apps/skybridge/references/discover.md +0 -84
  82. package/template/.agents/skills/apps/skybridge/references/download-file.md +0 -77
  83. package/template/.agents/skills/apps/skybridge/references/fetch-and-render-data.md +0 -151
  84. package/template/.agents/skills/apps/skybridge/references/oauth.md +0 -115
  85. package/template/.agents/skills/apps/skybridge/references/open-external-links.md +0 -71
  86. package/template/.agents/skills/apps/skybridge/references/prompt-llm.md +0 -20
  87. package/template/.agents/skills/apps/skybridge/references/publish.md +0 -19
  88. package/template/.agents/skills/apps/skybridge/references/run-locally.md +0 -51
  89. package/template/.agents/skills/apps/skybridge/references/state-and-context.md +0 -151
  90. package/template/.agents/skills/apps/skybridge/references/ui-guidelines.md +0 -205
  91. package/template/.agents/skills/design/impeccable/scripts/cleanup-deprecated.mjs +0 -284
  92. package/template/.agents/skills/design/impeccable/scripts/impeccable-paths.mjs +0 -126
  93. /package/template/.agents/skills/design/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +0 -0
  94. /package/template/.agents/skills/design/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +0 -0
  95. /package/template/.agents/skills/design/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
  96. /package/template/.agents/skills/design/impeccable/scripts/{live-svelte-component.mjs → live/svelte-component.mjs} +0 -0
  97. /package/template/.agents/skills/design/impeccable/scripts/{live-sveltekit-adapter.mjs → live/sveltekit-adapter.mjs} +0 -0
@@ -57,6 +57,7 @@
57
57
  const Z = { highlight: 100001, bar: 100005, picker: 100007, toast: 100010 };
58
58
  const EASE = 'cubic-bezier(0.22, 1, 0.36, 1)'; // ease-out-quint
59
59
  const PREFIX = 'impeccable-live';
60
+ const PICK_CURSOR_STYLE_ID = PREFIX + '-pick-cursor-style';
60
61
  const MANUAL_APPLY_STATE_TTL_MS = 15 * 60 * 1000;
61
62
  const sessionState = window.__IMPECCABLE_LIVE_SESSION__?.createLiveBrowserSessionState({
62
63
  prefix: PREFIX,
@@ -81,45 +82,24 @@
81
82
  'html', 'head', 'body', 'script', 'style', 'link', 'meta', 'noscript', 'br', 'wbr',
82
83
  ]);
83
84
 
84
- // SVG icons stack above each chip label. All strokes use currentColor so the
85
- // icon recolors to C.brand when its chip is selected. 20x20 render, 24-viewBox,
86
- // 1.5 stroke - visually consistent with the Foundation grid on the homepage.
87
- const ICON_ATTRS = 'width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" style="display:block"';
88
- const ICONS = {
89
- impeccable: `<svg ${ICON_ATTRS}><path d="M4 20l4-1L18 9l-3-3L5 16z"/><path d="M14 7l3 3"/></svg>`,
90
- bolder: `<svg ${ICON_ATTRS}><rect x="6" y="12" width="4" height="7" rx="0.5"/><rect x="14" y="5" width="4" height="14" rx="0.5"/></svg>`,
91
- quieter: `<svg ${ICON_ATTRS}><rect x="6" y="5" width="4" height="14" rx="0.5"/><rect x="14" y="12" width="4" height="7" rx="0.5"/></svg>`,
92
- distill: `<svg ${ICON_ATTRS}><path d="M4 5h16l-6 8v7l-4-2v-5z"/></svg>`,
93
- polish: `<svg ${ICON_ATTRS}><path d="M15 3l1 3 3 1-3 1-1 3-1-3-3-1 3-1z"/><path d="M7 13l0.6 1.8 1.8 0.6-1.8 0.6-0.6 1.8-0.6-1.8-1.8-0.6 1.8-0.6z"/></svg>`,
94
- typeset: `<svg ${ICON_ATTRS}><path d="M5 6h14" stroke-width="2.6"/><path d="M5 12h9" stroke-width="1.9"/><path d="M5 18h5" stroke-width="1.3"/></svg>`,
95
- colorize: `<svg ${ICON_ATTRS}><circle cx="9" cy="10" r="5"/><circle cx="15" cy="10" r="5"/><circle cx="12" cy="15" r="5"/></svg>`,
96
- layout: `<svg ${ICON_ATTRS}><rect x="3" y="4" width="8" height="16" rx="0.5"/><rect x="13" y="4" width="8" height="7" rx="0.5"/><rect x="13" y="13" width="8" height="7" rx="0.5"/></svg>`,
97
- adapt: `<svg ${ICON_ATTRS}><rect x="2.5" y="5" width="12" height="11" rx="1"/><line x1="2.5" y1="19" x2="14.5" y2="19"/><rect x="16.5" y="8" width="5" height="11" rx="1"/></svg>`,
98
- animate: `<svg ${ICON_ATTRS}><path d="M3 18c4-4 6-10 10-10"/><path d="M13 8c3 0 5 5 8 10"/><circle cx="13" cy="8" r="1.6" fill="currentColor" stroke="none"/></svg>`,
99
- delight: `<svg ${ICON_ATTRS}><path d="M12 3l2 6 6 2-6 2-2 6-2-6-6-2 6-2z"/></svg>`,
100
- overdrive: `<svg ${ICON_ATTRS}><path d="M13 3L5 13h5l-1 8 9-12h-6z"/></svg>`,
101
- };
102
-
103
- const ACTIONS = [
104
- { value: 'impeccable', label: 'Freeform' },
105
- { value: 'bolder', label: 'Bolder' },
106
- { value: 'quieter', label: 'Quieter' },
107
- { value: 'distill', label: 'Distill' },
108
- { value: 'polish', label: 'Polish' },
109
- { value: 'typeset', label: 'Typeset' },
110
- { value: 'colorize', label: 'Colorize' },
111
- { value: 'layout', label: 'Layout' },
112
- { value: 'adapt', label: 'Adapt' },
113
- { value: 'animate', label: 'Animate' },
114
- { value: 'delight', label: 'Delight' },
115
- { value: 'overdrive', label: 'Overdrive' },
116
- ];
85
+ // Command vocabulary (values + labels + icons) comes from the canonical source,
86
+ // skill/scripts/live/vocabulary.mjs, which live-server.mjs serializes into
87
+ // window.__IMPECCABLE_VOCAB__ when it serves /live.js (same injection path as
88
+ // the token/port above, so it is always present here). The icons stack above
89
+ // each chip label and recolor to C.brand when selected (strokes use
90
+ // currentColor). ACTIONS drives the picker grid; ICONS maps value -> svg.
91
+ const VOCAB = Array.isArray(window.__IMPECCABLE_VOCAB__) ? window.__IMPECCABLE_VOCAB__ : [];
92
+ const ICONS = {};
93
+ const ACTIONS = VOCAB.map((c) => {
94
+ ICONS[c.value] = c.icon;
95
+ return { value: c.value, label: c.label };
96
+ });
117
97
 
118
98
  const LIVE_CHROME_MOUNT_CONTRACT = ['root', 'transport', 'state', 'actions'];
119
99
  const LIVE_UI_SURFACES = [
120
100
  { key: 'global-bottom-bar', ids: [PREFIX + '-global-bar', PREFIX + '-global-bar-brand', PREFIX + '-pick-toggle', PREFIX + '-insert-toggle', PREFIX + '-detect-toggle', PREFIX + '-detect-badge', PREFIX + '-design-toggle', PREFIX + '-page-chat', PREFIX + '-page-chat-input', PREFIX + '-page-chat-voice'] },
121
101
  { key: 'pending-copy-edit-dock', ids: [PREFIX + '-pending-dock'] },
122
- { key: 'element-selection-chrome', ids: [PREFIX + '-highlight', PREFIX + '-tooltip', PREFIX + '-bar', PREFIX + '-configure-input-wrap', PREFIX + '-input', PREFIX + '-configure-voice'] },
102
+ { key: 'element-selection-chrome', ids: [PREFIX + '-highlight', PREFIX + '-tooltip', PREFIX + '-bar', PREFIX + '-selection-pill', PREFIX + '-input', PREFIX + '-configure-voice', PREFIX + '-configure-bar-tooltip'] },
123
103
  { key: 'action-picker', ids: [PREFIX + '-picker'] },
124
104
  { key: 'edit-chrome', ids: [PREFIX + '-edit-badge'] },
125
105
  { key: 'generating-row', ids: [PREFIX + '-bar', PREFIX + '-shader'] },
@@ -152,6 +132,8 @@
152
132
  let currentPreviewFile = null;
153
133
  let currentPreviewMode = null;
154
134
  let recoveryWaitingForAnchor = false;
135
+ let pickedAnchorSnapshot = null;
136
+ let pendingVariantAnchorRetryObserver = null;
155
137
  let pendingAcceptedSession = null;
156
138
  let variantObserver = null;
157
139
  let variantSelectionInFlight = false;
@@ -170,6 +152,7 @@
170
152
  let scrollLockTargetY = null;
171
153
  let scrollLockRaf = null;
172
154
  let scrollLockAbort = null;
155
+ const SCROLL_ANCHOR_LOCK_ID = 'impeccable-scroll-anchor-lock';
173
156
 
174
157
  // Dedicated key for scroll position - SEPARATE from LS_KEY so that
175
158
  // saveSession's state updates don't clobber a carefully-captured scrollY.
@@ -216,93 +199,31 @@
216
199
  // Helpers
217
200
  //
218
201
 
219
- function own(el) {
220
- return el && (el.id?.startsWith(PREFIX) || el.closest?.('[id^="' + PREFIX + '"]'));
221
- }
222
-
223
- function pickable(el) {
224
- if (!el || el.nodeType !== 1) return false;
225
- if (SKIP_TAGS.has(el.tagName.toLowerCase())) return false;
226
- if (own(el)) return false;
227
- const r = el.getBoundingClientRect();
228
- return r.width >= 20 && r.height >= 20;
229
- }
230
-
231
- function desc(el) {
232
- if (!el) return '';
233
- let s = el.tagName.toLowerCase();
234
- if (el.id) s += '#' + el.id;
235
- else if (el.classList.length) s += '.' + [...el.classList].slice(0, 2).join('.');
236
- return s;
237
- }
238
-
239
- function rectIsUsableAnchor(rect) {
240
- return !!rect && rect.width > 0.5 && rect.height > 0.5;
241
- }
242
-
243
- function makeFrozenAnchor(el) {
244
- if (!el || !el.getBoundingClientRect) return null;
245
- const r = el.getBoundingClientRect();
246
- if (!rectIsUsableAnchor(r)) return null;
247
- const rect = {
248
- x: r.x, y: r.y,
249
- top: r.top, left: r.left,
250
- right: r.right, bottom: r.bottom,
251
- width: r.width, height: r.height,
252
- };
253
- return {
254
- __impeccableFrozenAnchor: true,
255
- tagName: el.tagName || 'DIV',
256
- id: el.id || '',
257
- classList: el.classList ? [...el.classList] : [],
258
- hasAttribute: () => false,
259
- getBoundingClientRect: () => rect,
260
- };
261
- }
262
-
263
- function id8() { return crypto.randomUUID().replace(/-/g, '').slice(0, 8); }
264
-
265
- function cssId(id) {
266
- if (window.CSS?.escape) return CSS.escape(id);
267
- return String(id).replace(/([ !"#$%&'()*+,./:;<=>?@[\\\]^`{|}~])/g, '\\$1');
268
- }
269
-
270
- function liveUiRoot() {
271
- const root = window.__IMPECCABLE_LIVE_UI_ROOT__;
272
- if (root && typeof root.appendChild === 'function') return root;
273
- return document.body;
274
- }
275
-
276
- function uiAppend(el) {
277
- liveUiRoot().appendChild(el);
278
- return el;
279
- }
280
-
281
- function uiAppendStyle(styleEl) {
282
- const root = liveUiRoot();
283
- if (root && root !== document.body) root.appendChild(styleEl);
284
- else document.head.appendChild(styleEl);
285
- return styleEl;
286
- }
287
-
288
- function uiGetById(id) {
289
- const root = liveUiRoot();
290
- if (root?.getElementById) {
291
- const found = root.getElementById(id);
292
- if (found) return found;
293
- }
294
- if (root?.querySelector) {
295
- const found = root.querySelector('#' + cssId(id));
296
- if (found) return found;
297
- }
298
- return document.getElementById(id);
299
- }
300
-
301
- function activeElementDeep() {
302
- let active = document.activeElement;
303
- while (active?.shadowRoot?.activeElement) active = active.shadowRoot.activeElement;
304
- return active;
202
+ const domHelpers = window.__IMPECCABLE_LIVE_DOM__?.createLiveBrowserDomHelpers({
203
+ prefix: PREFIX,
204
+ skipTags: SKIP_TAGS,
205
+ document,
206
+ });
207
+ if (!domHelpers) {
208
+ console.error('[impeccable] live-browser-dom.js was not loaded. Live mode cannot start safely.');
209
+ window.__IMPECCABLE_LIVE_INIT__ = false;
210
+ return;
305
211
  }
212
+ const {
213
+ own,
214
+ pickable,
215
+ desc,
216
+ rectIsUsableAnchor,
217
+ makeFrozenAnchor,
218
+ id8,
219
+ cssId,
220
+ liveUiRoot,
221
+ uiAppend,
222
+ uiAppendStyle,
223
+ uiGetById,
224
+ activeElementDeep,
225
+ defangOutsideHandlers,
226
+ } = domHelpers;
306
227
 
307
228
  window.__IMPECCABLE_LIVE_CHROME_CORE__ = {
308
229
  version: 1,
@@ -335,45 +256,6 @@
335
256
  }),
336
257
  };
337
258
 
338
- // Modal-aware chrome: keep our floating UI clickable inside Radix /
339
- // Headless UI / vaul portals.
340
- //
341
- // Two host-page behaviors break us when the picked element lives inside a
342
- // modal dialog:
343
- //
344
- // 1. Modal scroll-lock disables outside pointer events. Radix's
345
- // `DismissableLayer` sets `document.body.style.pointerEvents = 'none'`
346
- // while a modal is open and only restores `auto` on the layer. Our
347
- // chrome inherits `none` from <body> and becomes unclickable.
348
- // 2. The dialog's outside-interaction handler (Radix's
349
- // `usePointerDownOutside`) listens at document level and dismisses
350
- // the dialog whenever a `pointerdown` lands outside the layer node.
351
- // Our chrome is a sibling of <body>, so Radix classifies our clicks
352
- // as outside and tears the dialog down mid-task.
353
- //
354
- // We can't reliably re-parent our chrome into the dialog subtree (z-index
355
- // stacking, scroll containers, theming all become host-page concerns), so
356
- // we defang both behaviors at our root:
357
- //
358
- // - `pointer-events: auto !important` overrides the inherited `none`.
359
- // - Stop `pointerdown` / `mousedown` propagation so the document-level
360
- // dismiss listener never fires for our clicks.
361
- // - Stop `focusin` propagation so any focus shifts inside our chrome
362
- // don't read as "focus moved outside the dialog" to focus traps.
363
- //
364
- // Click events still bubble normally - only the early pointer/focus
365
- // signals that drive outside-interaction detection are silenced.
366
- function defangOutsideHandlers(rootEl, { setPointerEvents = true } = {}) {
367
- if (!rootEl) return;
368
- if (setPointerEvents) {
369
- rootEl.style.setProperty('pointer-events', 'auto', 'important');
370
- }
371
- const stop = (e) => e.stopPropagation();
372
- rootEl.addEventListener('pointerdown', stop);
373
- rootEl.addEventListener('mousedown', stop);
374
- rootEl.addEventListener('focusin', stop);
375
- }
376
-
377
259
  //
378
260
  // Highlight overlay
379
261
  //
@@ -405,31 +287,53 @@
405
287
  uiAppend(tooltipEl);
406
288
  }
407
289
 
290
+ function shouldShowHighlightTagTooltip() {
291
+ // Configure/edit carry the tag in the bar selection pill, so keep only the outline.
292
+ return state !== 'CONFIGURING' && state !== 'EDITING';
293
+ }
294
+
295
+ function hideHighlightTagTooltip() {
296
+ if (!tooltipEl) return;
297
+ tooltipEl.style.opacity = '0';
298
+ tooltipEl.style.display = 'none';
299
+ }
300
+
408
301
  function showHighlight(el) {
409
302
  if (!el || !highlightEl) return;
410
303
  if (el.hasAttribute?.('data-impeccable-insert-placeholder')) return;
411
304
  const r = el.getBoundingClientRect();
412
305
  const top = (r.top - 2) + 'px', left = (r.left - 2) + 'px';
413
306
  const width = (r.width + 4) + 'px', height = (r.height + 4) + 'px';
414
- const tipTop = r.top - 20;
415
- const tipY = (tipTop < 4 ? r.bottom + 4 : tipTop) + 'px';
416
- const tipX = Math.max(4, r.left) + 'px';
417
- tooltipEl.textContent = desc(el);
307
+ const showTagTooltip = shouldShowHighlightTagTooltip();
418
308
 
419
309
  const hiWasHidden = highlightEl.style.display === 'none' || highlightEl.style.opacity === '0';
420
310
  if (hiWasHidden) {
421
311
  // Snap to first target without animating from (0,0), then fade in.
422
312
  highlightEl.style.transition = 'none';
423
313
  Object.assign(highlightEl.style, { top, left, width, height, display: 'block' });
424
- tooltipEl.style.transition = 'none';
425
- Object.assign(tooltipEl.style, { top: tipY, left: tipX, display: 'block' });
426
314
  void highlightEl.offsetWidth;
427
315
  highlightEl.style.transition = HIGHLIGHT_TRANSITION;
428
316
  highlightEl.style.opacity = '1';
317
+ } else {
318
+ Object.assign(highlightEl.style, { top, left, width, height, display: 'block', opacity: '1' });
319
+ }
320
+
321
+ if (!showTagTooltip) {
322
+ hideHighlightTagTooltip();
323
+ return;
324
+ }
325
+
326
+ const tipTop = r.top - 20;
327
+ const tipY = (tipTop < 4 ? r.bottom + 4 : tipTop) + 'px';
328
+ const tipX = Math.max(4, r.left) + 'px';
329
+ tooltipEl.textContent = desc(el);
330
+ if (hiWasHidden) {
331
+ tooltipEl.style.transition = 'none';
332
+ Object.assign(tooltipEl.style, { top: tipY, left: tipX, display: 'block' });
333
+ void tooltipEl.offsetWidth;
429
334
  tooltipEl.style.transition = TOOLTIP_TRANSITION;
430
335
  tooltipEl.style.opacity = '1';
431
336
  } else {
432
- Object.assign(highlightEl.style, { top, left, width, height, display: 'block', opacity: '1' });
433
337
  Object.assign(tooltipEl.style, { top: tipY, left: tipX, display: 'block', opacity: '1' });
434
338
  }
435
339
  }
@@ -1132,8 +1036,8 @@
1132
1036
  boxShadow: BP.shadow,
1133
1037
  transition: 'box-shadow 0.2s ease, opacity 0.25s ' + EASE + ', transform 0.3s ' + EASE,
1134
1038
  fontFamily: FONT, fontSize: '13px', color: BP.text,
1135
- padding: '6px',
1136
- maxWidth: '520px', minWidth: '320px',
1039
+ padding: '5px',
1040
+ maxWidth: '560px', minWidth: '340px',
1137
1041
  });
1138
1042
  uiAppend(barEl);
1139
1043
  defangOutsideHandlers(barEl);
@@ -1141,14 +1045,29 @@
1141
1045
 
1142
1046
  function positionBar() {
1143
1047
  if (!barEl) return;
1144
- const anchor = resolveBarAnchor();
1145
- if (!anchor) return;
1146
- const r = anchor.getBoundingClientRect();
1147
1048
  const barH = barEl.offsetHeight || 44;
1148
1049
  const barW = barEl.offsetWidth || 380;
1149
1050
  const GLOBAL_BAR_RESERVE = 64; // global bar height + bottom margin + breathing room
1150
1051
  const GAP = 8;
1151
1052
 
1053
+ // Recovery pins to document.body when the picked element is off-screen or
1054
+ // missing. Center the generating bar above the global bar instead of
1055
+ // stacking a duplicate toast in the same slot.
1056
+ if (recoveryWaitingForAnchor) {
1057
+ const barRect = globalBarEl?.getBoundingClientRect();
1058
+ const reserve = barRect && barRect.height > 0
1059
+ ? Math.max(GLOBAL_BAR_RESERVE, window.innerHeight - barRect.top + 12)
1060
+ : GLOBAL_BAR_RESERVE;
1061
+ const top = window.innerHeight - barH - reserve;
1062
+ const left = Math.max(GAP, (window.innerWidth - barW) / 2);
1063
+ Object.assign(barEl.style, { top: top + 'px', left: left + 'px' });
1064
+ return;
1065
+ }
1066
+
1067
+ const anchor = resolveBarAnchor();
1068
+ if (!anchor) return;
1069
+ const r = anchor.getBoundingClientRect();
1070
+
1152
1071
  // Prefer below the element; fall back to above; if neither fits (element
1153
1072
  // taller than viewport), pin to a stable viewport anchor so the bar
1154
1073
  // doesn't teleport between top and bottom as the user scrolls.
@@ -1176,8 +1095,14 @@
1176
1095
  if (mode === 'configure') {
1177
1096
  barEl.appendChild(configureKind === 'insert' ? buildInsertConfigureRow() : buildConfigureRow());
1178
1097
  if (configureKind === 'insert') syncInsertCreateButton();
1179
- } else if (mode === 'generating') barEl.appendChild(buildGeneratingRow());
1180
- else if (mode === 'cycling') barEl.appendChild(buildCyclingRow());
1098
+ applyConfigureBarChrome();
1099
+ } else {
1100
+ restorePickerBarChrome();
1101
+ if (mode === 'generating') {
1102
+ if (recoveryWaitingForAnchor) dismissToast();
1103
+ barEl.appendChild(buildGeneratingRow());
1104
+ } else if (mode === 'cycling') barEl.appendChild(buildCyclingRow());
1105
+ }
1181
1106
  barEl.style.display = 'block';
1182
1107
  positionBar();
1183
1108
  requestAnimationFrame(() => {
@@ -1197,6 +1122,7 @@
1197
1122
  setTimeout(() => { if (barEl && hideSeq === barHideSeq) barEl.style.display = 'none'; }, 250);
1198
1123
  hideActionPicker();
1199
1124
  closeTunePopover();
1125
+ hideConfigureBarTooltip();
1200
1126
  if (state === 'EDITING') restoreInlineEditDrafts();
1201
1127
  disableInlineEdit();
1202
1128
  }
@@ -1205,39 +1131,475 @@
1205
1131
  if (!barEl || barEl.style.display === 'none') return;
1206
1132
  if (mode === 'cycling' && !ensureCyclingRenderable('update-bar')) return;
1207
1133
  barEl.innerHTML = '';
1208
- // Reset bar styling to the kinpaku picker palette
1209
- barEl.style.background = BP.surface;
1210
- barEl.style.border = '1px solid ' + BP.border;
1211
- barEl.style.boxShadow = BP.shadow;
1212
1134
  if (mode === 'configure') {
1213
1135
  barEl.appendChild(configureKind === 'insert' ? buildInsertConfigureRow() : buildConfigureRow());
1214
1136
  if (configureKind === 'insert') syncInsertCreateButton();
1215
- } else if (mode === 'generating') barEl.appendChild(buildGeneratingRow());
1216
- else if (mode === 'cycling') barEl.appendChild(buildCyclingRow());
1217
- else if (mode === 'saving') barEl.appendChild(buildSavingRow());
1218
- else if (mode === 'confirmed') {
1219
- barEl.appendChild(buildConfirmedRow());
1220
- barEl.style.background = 'oklch(95% 0.05 145)';
1221
- barEl.style.border = '1px solid oklch(75% 0.12 145 / 0.4)';
1137
+ applyConfigureBarChrome();
1138
+ } else {
1139
+ restorePickerBarChrome();
1140
+ if (mode === 'generating') barEl.appendChild(buildGeneratingRow());
1141
+ else if (mode === 'cycling') barEl.appendChild(buildCyclingRow());
1142
+ else if (mode === 'saving') barEl.appendChild(buildSavingRow());
1143
+ else if (mode === 'confirmed') {
1144
+ barEl.appendChild(buildConfirmedRow());
1145
+ barEl.style.background = 'oklch(95% 0.05 145)';
1146
+ barEl.style.border = '1px solid oklch(75% 0.12 145 / 0.4)';
1147
+ }
1222
1148
  }
1223
1149
  syncPageChatFocus('update-bar-content');
1224
1150
  }
1225
1151
 
1226
- // Configure row
1152
+ // Configure row: the floating bar surface IS the input; modifier pills sit left of the field.
1153
+
1154
+ const CONFIGURE_BAR_H = '36px';
1155
+ // Compact selection pill + 7px inset balances vertical centering in the 36px bar.
1156
+ const CONFIGURE_BAR_INSET = '7px';
1157
+ const CONFIGURE_PILL_RADIUS = '7px';
1158
+ const CONFIGURE_SELECTION_PILL_BORDER = '1px solid oklch(70% 0.12 188)';
1159
+ const CONFIGURE_SELECTION_PILL_PAD = '1px 4px';
1160
+ const CONFIGURE_ROW_FONT_SIZE = '12px';
1161
+ const CONFIGURE_ROW_TRACK_H = '18px';
1162
+ const CONFIGURE_PILL_PAD_Y = '3px';
1163
+ const CONFIGURE_BAR_SURFACE = 'oklch(15% 0.008 95)';
1164
+ const CONFIGURE_PILL_TEXT = 'oklch(94% 0.02 82)';
1165
+ const ICON_CONFIGURE_SUBMIT =
1166
+ '<svg width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M5 12h14"/><path d="m12 5 7 7-7 7"/></svg>';
1167
+
1168
+ function applyConfigureBarChrome() {
1169
+ if (!barEl) return;
1170
+ barEl.dataset.configureSurface = 'true';
1171
+ barEl.style.padding = '0';
1172
+ barEl.style.background = CONFIGURE_BAR_SURFACE;
1173
+ barEl.style.overflow = 'hidden';
1174
+ syncConfigureInputChrome();
1175
+ }
1176
+
1177
+ function restorePickerBarChrome() {
1178
+ if (!barEl) return;
1179
+ barEl.dataset.configureSurface = 'false';
1180
+ barEl.removeAttribute('data-input-focused');
1181
+ barEl.removeAttribute('data-voice-listening');
1182
+ barEl.style.padding = '5px';
1183
+ barEl.style.background = BP.surface;
1184
+ barEl.style.overflow = '';
1185
+ barEl.style.border = '1px solid ' + BP.border;
1186
+ barEl.style.borderColor = BP.border;
1187
+ barEl.style.boxShadow = BP.shadow;
1188
+ }
1227
1189
 
1228
1190
  function syncConfigureInputChrome() {
1229
- const wrap = uiGetById(PREFIX + '-configure-input-wrap');
1230
- const input = uiGetById(PREFIX + '-input');
1231
- if (!wrap || !input) return;
1191
+ const input = uiGetById(PREFIX + '-input') || uiGetById(PREFIX + '-insert-input');
1192
+ const surface = barEl?.dataset.configureSurface === 'true' ? barEl : null;
1193
+ if (!surface || !input) return;
1232
1194
  const focused = activeElementDeep() === input;
1233
- wrap.dataset.inputFocused = focused ? 'true' : 'false';
1234
- wrap.dataset.voiceListening = (voiceListening && voiceCtx?.mode === 'configure') ? 'true' : 'false';
1235
- wrap.style.borderColor = (voiceListening && voiceCtx?.mode === 'configure')
1195
+ const listening = voiceListening && voiceCtx?.mode === 'configure';
1196
+ surface.dataset.inputFocused = focused ? 'true' : 'false';
1197
+ surface.dataset.voiceListening = listening ? 'true' : 'false';
1198
+ surface.style.borderColor = listening
1236
1199
  ? BP.patinaSoft
1237
- : (focused ? BP.accentSoft : BP.hairline);
1200
+ : (focused ? BP.accentSoft : BP.border);
1201
+ surface.style.boxShadow = BP.shadow;
1202
+ }
1203
+
1204
+ function configureBarPalette() {
1205
+ return BP || barPaletteForTheme(detectPageTheme());
1206
+ }
1207
+
1208
+ function configureRowTextMetrics(extra = {}) {
1209
+ return {
1210
+ fontFamily: FONT,
1211
+ fontSize: CONFIGURE_ROW_FONT_SIZE,
1212
+ fontWeight: '500',
1213
+ lineHeight: CONFIGURE_ROW_TRACK_H,
1214
+ ...extra,
1215
+ };
1216
+ }
1217
+
1218
+ function configureInputFieldStyle(extra = {}) {
1219
+ return {
1220
+ flex: '1', minWidth: '0', width: '100%',
1221
+ padding: '0', margin: '0',
1222
+ border: 'none', background: 'transparent',
1223
+ boxSizing: 'border-box',
1224
+ height: CONFIGURE_ROW_TRACK_H,
1225
+ color: CONFIGURE_PILL_TEXT,
1226
+ caretColor: CONFIGURE_PILL_TEXT,
1227
+ outline: 'none',
1228
+ ...configureRowTextMetrics(),
1229
+ ...extra,
1230
+ };
1231
+ }
1232
+
1233
+ function configureInputShellStyle() {
1234
+ return {
1235
+ display: 'flex', alignItems: 'center', gap: '6px',
1236
+ flex: '1', minWidth: '0', height: '100%',
1237
+ padding: '0 6px 0 ' + CONFIGURE_BAR_INSET,
1238
+ };
1239
+ }
1240
+
1241
+ function configureSelectionPillStyle(extra = {}) {
1242
+ const P = configureBarPalette();
1243
+ return {
1244
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
1245
+ gap: '2px', height: 'auto', flexShrink: '0',
1246
+ padding: CONFIGURE_SELECTION_PILL_PAD,
1247
+ boxSizing: 'border-box',
1248
+ border: CONFIGURE_SELECTION_PILL_BORDER,
1249
+ borderRadius: CONFIGURE_PILL_RADIUS,
1250
+ background: 'transparent',
1251
+ color: P.patina,
1252
+ cursor: 'pointer',
1253
+ transition: 'background 0.15s ease, color 0.15s ease, border-color 0.15s ease',
1254
+ whiteSpace: 'nowrap',
1255
+ ...configureRowTextMetrics({
1256
+ fontFamily: MONO, fontWeight: '600', letterSpacing: '-0.01em',
1257
+ }),
1258
+ ...extra,
1259
+ };
1260
+ }
1261
+
1262
+ function configureModifierPillStyle(extra = {}) {
1263
+ const P = configureBarPalette();
1264
+ return {
1265
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
1266
+ gap: '2px', height: 'auto', minHeight: CONFIGURE_ROW_TRACK_H,
1267
+ padding: CONFIGURE_PILL_PAD_Y + ' 8px', flexShrink: '0',
1268
+ boxSizing: 'border-box',
1269
+ border: '1px solid transparent',
1270
+ borderRadius: CONFIGURE_PILL_RADIUS,
1271
+ background: 'transparent',
1272
+ color: P.textDim, cursor: 'pointer',
1273
+ transition: 'background 0.15s ease, color 0.15s ease, border-color 0.15s ease',
1274
+ whiteSpace: 'nowrap',
1275
+ ...configureRowTextMetrics(),
1276
+ ...extra,
1277
+ };
1278
+ }
1279
+
1280
+ function configureInlineControlStyle(extra = {}) {
1281
+ const P = configureBarPalette();
1282
+ return {
1283
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
1284
+ gap: '2px', height: CONFIGURE_ROW_TRACK_H, flexShrink: '0',
1285
+ padding: '0', margin: '0',
1286
+ boxSizing: 'border-box',
1287
+ border: 'none', borderRadius: '0',
1288
+ background: 'transparent',
1289
+ color: P.textDim, cursor: 'pointer',
1290
+ transition: 'color 0.12s ease, background 0.12s ease',
1291
+ whiteSpace: 'nowrap',
1292
+ ...configureRowTextMetrics(),
1293
+ ...extra,
1294
+ };
1295
+ }
1296
+
1297
+ function bindConfigureInlineControlHover(btn, controlsLocked) {
1298
+ btn.addEventListener('mouseenter', () => {
1299
+ if (controlsLocked) return;
1300
+ const P = configureBarPalette();
1301
+ btn.style.color = P.text;
1302
+ });
1303
+ btn.addEventListener('mouseleave', () => {
1304
+ if (controlsLocked) return;
1305
+ btn.style.color = configureBarPalette().textDim;
1306
+ });
1307
+ }
1308
+
1309
+ function bindConfigureModifierPillHover(btn, controlsLocked) {
1310
+ btn.addEventListener('mouseenter', () => {
1311
+ if (controlsLocked) return;
1312
+ const P = configureBarPalette();
1313
+ btn.style.color = P.text;
1314
+ btn.style.background = P.toggleActive;
1315
+ });
1316
+ btn.addEventListener('mouseleave', () => {
1317
+ if (controlsLocked) return;
1318
+ const P = configureBarPalette();
1319
+ btn.style.color = P.textDim;
1320
+ btn.style.background = 'transparent';
1321
+ });
1238
1322
  }
1239
1323
 
1240
- // Insert mode helpers (mirrors skill/scripts/live-insert-ui.mjs)
1324
+ let configureBarTooltipEl = null;
1325
+
1326
+ function ensureConfigureBarTooltip() {
1327
+ if (configureBarTooltipEl) return configureBarTooltipEl;
1328
+ const P = configureBarPalette();
1329
+ configureBarTooltipEl = el('div', {
1330
+ position: 'fixed',
1331
+ display: 'none',
1332
+ zIndex: String(Z.bar + 7),
1333
+ pointerEvents: 'none',
1334
+ maxWidth: 'min(360px, calc(100vw - 16px))',
1335
+ padding: '6px 9px',
1336
+ borderRadius: '7px',
1337
+ background: P.chatSurface,
1338
+ border: '1px solid ' + P.hairline,
1339
+ boxShadow: P.shadow,
1340
+ color: P.text,
1341
+ fontFamily: FONT,
1342
+ fontSize: '11px',
1343
+ fontWeight: '500',
1344
+ lineHeight: '1.35',
1345
+ letterSpacing: '0.01em',
1346
+ whiteSpace: 'normal',
1347
+ wordBreak: 'break-word',
1348
+ });
1349
+ configureBarTooltipEl.id = PREFIX + '-configure-bar-tooltip';
1350
+ uiAppend(configureBarTooltipEl);
1351
+ return configureBarTooltipEl;
1352
+ }
1353
+
1354
+ function showConfigureBarTooltip(anchor, message) {
1355
+ if (!anchor || !message) return;
1356
+ const tip = ensureConfigureBarTooltip();
1357
+ tip.textContent = message;
1358
+ tip.style.transition = 'none';
1359
+ tip.style.display = 'block';
1360
+ tip.style.opacity = '1';
1361
+ const r = anchor.getBoundingClientRect();
1362
+ const tipW = tip.offsetWidth;
1363
+ const tipH = tip.offsetHeight;
1364
+ const left = Math.max(8, Math.min(window.innerWidth - tipW - 8, r.left + r.width / 2 - tipW / 2));
1365
+ const top = Math.max(8, r.top - tipH - 8);
1366
+ tip.style.left = left + 'px';
1367
+ tip.style.top = top + 'px';
1368
+ }
1369
+
1370
+ function hideConfigureBarTooltip() {
1371
+ if (!configureBarTooltipEl) return;
1372
+ configureBarTooltipEl.style.display = 'none';
1373
+ configureBarTooltipEl.style.opacity = '0';
1374
+ }
1375
+
1376
+ function selectionTagLabel(el) {
1377
+ if (!el) return '';
1378
+ if (el.hasAttribute?.('data-impeccable-insert-placeholder')) return 'slot';
1379
+ return el.tagName.toLowerCase();
1380
+ }
1381
+
1382
+ function elementPath(el, maxDepth = 8) {
1383
+ if (!el) return '';
1384
+ const parts = [];
1385
+ let node = el;
1386
+ while (node && node.nodeType === 1 && node !== document.body) {
1387
+ let part = node.tagName.toLowerCase();
1388
+ if (node.id) part += '#' + node.id;
1389
+ else if (node.classList?.length) part += '.' + [...node.classList].slice(0, 2).join('.');
1390
+ parts.unshift(part);
1391
+ node = node.parentElement;
1392
+ if (parts.length >= maxDepth) break;
1393
+ }
1394
+ return parts.join(' \u203a ');
1395
+ }
1396
+
1397
+ function variantCountTooltipText(count) {
1398
+ const n = Number(count) || selectedCount;
1399
+ const word = n === 1 ? 'variant' : 'variants';
1400
+ return 'Click to change \u00b7 ' + n + ' ' + word;
1401
+ }
1402
+
1403
+ function removeConfigureSelection() {
1404
+ hideConfigureBarTooltip();
1405
+ if (configureKind === 'insert') {
1406
+ cancelInsertConfigure();
1407
+ return;
1408
+ }
1409
+ selectedElement = null;
1410
+ exitConfigureToPicking('selection-pill-remove', { clearHover: true });
1411
+ }
1412
+
1413
+ function buildSelectionPill({ el: targetEl, controlsLocked }) {
1414
+ const tag = selectionTagLabel(targetEl);
1415
+ const path = elementPath(targetEl);
1416
+ const P = configureBarPalette();
1417
+ const pill = el('button', configureSelectionPillStyle({ minWidth: '32px' }));
1418
+ pill.id = PREFIX + '-selection-pill';
1419
+ pill.type = 'button';
1420
+ pill.setAttribute('aria-label', 'Selected element: ' + tag);
1421
+ pill.disabled = controlsLocked;
1422
+ pill.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
1423
+ pill.style.opacity = controlsLocked ? '0.58' : '1';
1424
+ pill.style.flexShrink = '0';
1425
+
1426
+ const faceStack = el('span', {
1427
+ display: 'grid', placeItems: 'center',
1428
+ width: '100%', minWidth: '1.25em',
1429
+ lineHeight: CONFIGURE_ROW_TRACK_H,
1430
+ });
1431
+ const tagFace = el('span', {
1432
+ gridArea: '1 / 1',
1433
+ transition: 'opacity 0.12s ease',
1434
+ color: P.patina,
1435
+ });
1436
+ const clearFace = el('span', {
1437
+ gridArea: '1 / 1',
1438
+ opacity: '0',
1439
+ transition: 'opacity 0.12s ease',
1440
+ color: 'oklch(58% 0.15 35)',
1441
+ });
1442
+ tagFace.textContent = tag;
1443
+ clearFace.textContent = '\u00D7';
1444
+ faceStack.appendChild(tagFace);
1445
+ faceStack.appendChild(clearFace);
1446
+ pill.appendChild(faceStack);
1447
+
1448
+ const setArmed = (armed) => {
1449
+ tagFace.style.opacity = armed ? '0' : '1';
1450
+ clearFace.style.opacity = armed ? '1' : '0';
1451
+ pill.style.background = armed ? P.toggleActive : 'transparent';
1452
+ pill.style.border = CONFIGURE_SELECTION_PILL_BORDER;
1453
+ pill.setAttribute('aria-label', armed ? 'Clear selection' : 'Selected element: ' + tag);
1454
+ };
1455
+ const arm = () => {
1456
+ if (controlsLocked) {
1457
+ showConfigureBarTooltip(pill, 'Apply is still running');
1458
+ return;
1459
+ }
1460
+ setArmed(true);
1461
+ if (path) showConfigureBarTooltip(pill, path);
1462
+ };
1463
+ const disarm = () => {
1464
+ hideConfigureBarTooltip();
1465
+ setArmed(false);
1466
+ };
1467
+ pill.addEventListener('mouseenter', arm);
1468
+ pill.addEventListener('mouseleave', disarm);
1469
+ pill.addEventListener('focus', arm);
1470
+ pill.addEventListener('blur', disarm);
1471
+ pill.addEventListener('click', (e) => {
1472
+ e.stopPropagation();
1473
+ if (controlsLocked) { showManualApplyBusyToast(); return; }
1474
+ removeConfigureSelection();
1475
+ });
1476
+ return pill;
1477
+ }
1478
+
1479
+ function bindConfigureCountPillTooltip(count, controlsLocked) {
1480
+ count.removeAttribute('title');
1481
+ count.addEventListener('mouseenter', () => {
1482
+ if (controlsLocked) {
1483
+ showConfigureBarTooltip(count, 'Apply is still running');
1484
+ return;
1485
+ }
1486
+ showConfigureBarTooltip(count, variantCountTooltipText(selectedCount));
1487
+ });
1488
+ count.addEventListener('mouseleave', hideConfigureBarTooltip);
1489
+ }
1490
+
1491
+ function buildConfigureActionControl({ controlsLocked, onClick }) {
1492
+ const control = el('button', configureInlineControlStyle());
1493
+ const label = document.createElement('span');
1494
+ label.textContent = actionLabel();
1495
+ const caret = el('span', {
1496
+ fontSize: '10px', lineHeight: '1',
1497
+ marginLeft: '2px', pointerEvents: 'none',
1498
+ color: 'inherit',
1499
+ });
1500
+ caret.textContent = '\u25BE';
1501
+ caret.setAttribute('aria-hidden', 'true');
1502
+ control.appendChild(label);
1503
+ control.appendChild(caret);
1504
+ control.disabled = controlsLocked;
1505
+ control.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
1506
+ control.style.opacity = controlsLocked ? '0.58' : '1';
1507
+ bindConfigureInlineControlHover(control, controlsLocked);
1508
+ control.addEventListener('click', onClick);
1509
+ return control;
1510
+ }
1511
+
1512
+ const VARIANT_COUNT_MIN = 1;
1513
+ const VARIANT_COUNT_MAX = 4;
1514
+
1515
+ function cycleSelectedCount() {
1516
+ if (selectedCount >= VARIANT_COUNT_MAX) selectedCount = VARIANT_COUNT_MIN;
1517
+ else selectedCount += 1;
1518
+ return selectedCount;
1519
+ }
1520
+
1521
+ function buildConfigureCountControl({ controlsLocked, onClick }) {
1522
+ const count = el('button', configureInlineControlStyle({
1523
+ fontFamily: MONO, fontWeight: '600', letterSpacing: '0',
1524
+ }));
1525
+ count.textContent = '\u00D7' + selectedCount;
1526
+ count.disabled = controlsLocked;
1527
+ count.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
1528
+ count.style.opacity = controlsLocked ? '0.58' : '1';
1529
+ bindConfigureInlineControlHover(count, controlsLocked);
1530
+ bindConfigureCountPillTooltip(count, controlsLocked);
1531
+ count.addEventListener('click', onClick);
1532
+ return count;
1533
+ }
1534
+
1535
+ function buildConfigureVoiceButton({ id, controlsLocked, onClick }) {
1536
+ const voiceBtn = el('button', {
1537
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
1538
+ boxSizing: 'border-box',
1539
+ width: CONFIGURE_BAR_H, height: '100%', flexShrink: '0',
1540
+ padding: '0', margin: '0',
1541
+ border: 'none', borderRight: '1px solid ' + BP.hairline,
1542
+ borderRadius: '0', background: 'transparent',
1543
+ color: BP.textDim, cursor: 'pointer',
1544
+ transition: 'color 0.12s ease, background 0.12s ease',
1545
+ });
1546
+ voiceBtn.id = id;
1547
+ voiceBtn.type = 'button';
1548
+ voiceBtn.setAttribute('aria-label', 'Voice input');
1549
+ voiceBtn.innerHTML = ICON_PAGE_VOICE;
1550
+ voiceBtn.disabled = controlsLocked;
1551
+ voiceBtn.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
1552
+ voiceBtn.style.opacity = controlsLocked ? '0.58' : '1';
1553
+ voiceBtn.addEventListener('mousedown', (e) => e.stopPropagation());
1554
+ voiceBtn.addEventListener('click', onClick);
1555
+ return voiceBtn;
1556
+ }
1557
+
1558
+ function buildConfigureTrailingCluster(controls, voiceBtn, submitBtn) {
1559
+ const cluster = el('div', {
1560
+ display: 'inline-flex', alignItems: 'stretch', flexShrink: '0',
1561
+ height: '100%', borderLeft: '1px solid ' + BP.hairline,
1562
+ });
1563
+ if (controls.length) {
1564
+ const controlsWrap = el('div', {
1565
+ display: 'inline-flex', alignItems: 'center', gap: '8px',
1566
+ padding: '0 10px', flexShrink: '0', height: '100%',
1567
+ });
1568
+ controls.forEach((control) => controlsWrap.appendChild(control));
1569
+ cluster.appendChild(controlsWrap);
1570
+ }
1571
+ voiceBtn.style.borderLeft = '1px solid ' + BP.hairline;
1572
+ cluster.appendChild(voiceBtn);
1573
+ cluster.appendChild(submitBtn);
1574
+ return cluster;
1575
+ }
1576
+
1577
+ function buildConfigureSubmitButton({ controlsLocked, onClick, ariaLabel }) {
1578
+ const btn = el('button', {
1579
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
1580
+ boxSizing: 'border-box', width: CONFIGURE_BAR_H, height: CONFIGURE_BAR_H,
1581
+ padding: '0', flexShrink: '0',
1582
+ border: 'none', borderLeft: '1px solid ' + BP.hairline,
1583
+ borderRadius: '0',
1584
+ background: BP.accent, color: C.ink,
1585
+ cursor: controlsLocked ? 'not-allowed' : 'pointer',
1586
+ transition: 'filter 0.12s ease, transform 0.1s ease',
1587
+ });
1588
+ btn.type = 'button';
1589
+ btn.setAttribute('aria-label', ariaLabel);
1590
+ btn.innerHTML = ICON_CONFIGURE_SUBMIT;
1591
+ btn.disabled = controlsLocked;
1592
+ btn.style.opacity = controlsLocked ? '0.58' : '1';
1593
+ if (controlsLocked) btn.title = 'Apply is still running';
1594
+ btn.addEventListener('mouseenter', () => { if (!controlsLocked) btn.style.filter = 'brightness(1.1)'; });
1595
+ btn.addEventListener('mouseleave', () => btn.style.filter = 'none');
1596
+ btn.addEventListener('mousedown', () => { if (!controlsLocked) btn.style.transform = 'scale(0.97)'; });
1597
+ btn.addEventListener('mouseup', () => btn.style.transform = 'scale(1)');
1598
+ btn.addEventListener('click', onClick);
1599
+ return btn;
1600
+ }
1601
+
1602
+ // Insert mode helpers (mirrors skill/scripts/live/insert-ui.mjs)
1241
1603
 
1242
1604
  function detectInsertAxisFromStyle(style) {
1243
1605
  const display = style?.display || 'block';
@@ -1554,21 +1916,55 @@
1554
1916
  syncPageInteractionCursor();
1555
1917
  }
1556
1918
 
1557
- let pageInteractionCursorActive = false;
1919
+ /**
1920
+ * Drive the page-level pick / insert cursor through the textContent of one
1921
+ * injected <style>, never by mutating <html> (className or inline style).
1922
+ * Frameworks that server-render the <html>/<body> roots (Next.js App Router)
1923
+ * report a React 19 hydration mismatch when the client adds an attribute the
1924
+ * server HTML never emitted, so a `class`/inline `style` toggled on
1925
+ * `document.documentElement` trips "a tree hydrated but some attributes ...
1926
+ * didn't match" on the next Fast-Refresh re-render. Keying the cursor off a
1927
+ * stable-id <style> keeps the effect off the hydrated host elements (same
1928
+ * shape as the scroll-anchor lock). A falsy cursor clears the rule.
1929
+ */
1930
+ function setPageInteractionCursor(cursor) {
1931
+ let style = document.getElementById(PICK_CURSOR_STYLE_ID);
1932
+ if (!cursor) {
1933
+ if (style) style.textContent = '';
1934
+ return;
1935
+ }
1936
+ if (!style) {
1937
+ style = document.createElement('style');
1938
+ style.id = PICK_CURSOR_STYLE_ID;
1939
+ // Styles the host page, not the chrome - inside the adapter's shadow UI
1940
+ // root (uiAppendStyle's target) these selectors would match nothing.
1941
+ (document.head || document.documentElement).appendChild(style);
1942
+ }
1943
+ style.textContent =
1944
+ '* { cursor: ' + cursor + ' !important; }\n'
1945
+ + '[id^="' + PREFIX + '"],\n'
1946
+ + '[id^="' + PREFIX + '"] * { cursor: revert !important; }';
1947
+ }
1558
1948
 
1559
- /** Page-level cursor while insert mode is choosing a before/after edge. */
1949
+ /** Page-level cursor while pick or insert mode is targeting page elements. */
1560
1950
  function syncPageInteractionCursor() {
1561
- let next = '';
1562
- if (state === 'PICKING' && insertActive) {
1563
- next = insertHoverAnchor ? cursorForInsertAxis(insertHoverAxis || 'column') : '';
1564
- }
1565
- if (next) {
1566
- document.documentElement.style.cursor = next;
1567
- pageInteractionCursorActive = true;
1568
- } else if (pageInteractionCursorActive) {
1569
- document.documentElement.style.cursor = '';
1570
- pageInteractionCursorActive = false;
1951
+ let cursor = '';
1952
+ if (state === 'PICKING' && pickActive && !insertActive) {
1953
+ cursor = 'crosshair';
1954
+ } else if (state === 'PICKING' && insertActive && insertHoverAnchor) {
1955
+ cursor = cursorForInsertAxis(insertHoverAxis || 'column');
1571
1956
  }
1957
+ setPageInteractionCursor(cursor);
1958
+ }
1959
+
1960
+ /**
1961
+ * Single entry point for interaction-state transitions. The pick-mode
1962
+ * crosshair is derived from `state`, so a bare `state = ...` assignment
1963
+ * leaves the page cursor out of sync with the mode it advertises.
1964
+ */
1965
+ function setLiveState(next) {
1966
+ state = next;
1967
+ syncPageInteractionCursor();
1572
1968
  }
1573
1969
 
1574
1970
  /** Element used to position the floating bar / shader during a session. */
@@ -1889,69 +2285,35 @@
1889
2285
  }
1890
2286
  }
1891
2287
 
2288
+ /** Stylesheet shared by the replace and insert configure rows. */
2289
+ function ensureConfigureInputStyle() {
2290
+ if (uiGetById(PREFIX + '-configure-input-style')) return;
2291
+ const s = document.createElement('style');
2292
+ s.id = PREFIX + '-configure-input-style';
2293
+ s.textContent =
2294
+ '@keyframes impeccable-configure-voice-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }' +
2295
+ '#' + PREFIX + '-input, #' + PREFIX + '-insert-input { box-sizing: border-box; height: ' + CONFIGURE_ROW_TRACK_H + '; line-height: ' + CONFIGURE_ROW_TRACK_H + '; padding: 0; margin: 0; caret-color: ' + CONFIGURE_PILL_TEXT + '; }' +
2296
+ '#' + PREFIX + '-input::placeholder, #' + PREFIX + '-insert-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }' +
2297
+ '#' + PREFIX + '-configure-voice[data-listening="true"] svg, #' + PREFIX + '-insert-voice[data-listening="true"] svg { animation: impeccable-configure-voice-pulse 1.1s ease-in-out infinite; }' +
2298
+ '@media (prefers-reduced-motion: reduce) { #' + PREFIX + '-configure-voice[data-listening="true"] svg, #' + PREFIX + '-insert-voice[data-listening="true"] svg { animation: none; opacity: 1; } }' +
2299
+ '#' + PREFIX + '-configure-voice:hover, #' + PREFIX + '-insert-voice:hover { background: oklch(27% 0 0); color: ' + BP.accent + '; }';
2300
+ uiAppendStyle(s);
2301
+ }
2302
+
1892
2303
  function buildConfigureRow() {
1893
2304
  const controlsLocked = pendingApplyInFlight === true;
1894
2305
  const row = el('div', {
1895
- display: 'flex', alignItems: 'center', gap: '6px',
2306
+ display: 'flex', alignItems: 'stretch', width: '100%', height: CONFIGURE_BAR_H,
1896
2307
  });
1897
2308
 
1898
- // Action pill - dark graphite chip (matches kinpaku-kit .live-demo-ctx-pill)
1899
- const pill = el('button', {
1900
- display: 'inline-flex', alignItems: 'center', gap: '4px',
1901
- padding: '5px 10px', borderRadius: '6px',
1902
- background: BP.chatSurface, color: BP.text,
1903
- fontFamily: FONT, fontSize: '12px', fontWeight: '500',
1904
- border: '1px solid ' + BP.hairline, cursor: 'pointer',
1905
- transition: 'background 0.12s ease, border-color 0.12s ease, transform 0.1s ease',
1906
- whiteSpace: 'nowrap', flexShrink: '0',
1907
- });
1908
- pill.textContent = actionLabel() + ' \u25BE';
1909
- pill.disabled = controlsLocked;
1910
- pill.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
1911
- pill.style.opacity = controlsLocked ? '0.58' : '1';
1912
- if (controlsLocked) pill.title = 'Apply is still running';
1913
- pill.addEventListener('mouseenter', () => {
1914
- if (controlsLocked) return;
1915
- pill.style.background = BP.accentSoft;
1916
- pill.style.borderColor = BP.accent;
1917
- });
1918
- pill.addEventListener('mouseleave', () => {
1919
- if (controlsLocked) return;
1920
- pill.style.background = BP.chatSurface;
1921
- pill.style.borderColor = BP.hairline;
1922
- });
1923
- pill.addEventListener('mousedown', () => { if (!controlsLocked) pill.style.transform = 'scale(0.97)'; });
1924
- pill.addEventListener('mouseup', () => pill.style.transform = 'scale(1)');
1925
- pill.addEventListener('click', (e) => {
1926
- e.stopPropagation();
1927
- if (controlsLocked) { showManualApplyBusyToast(); return; }
1928
- toggleActionPicker();
1929
- });
1930
- row.appendChild(pill);
1931
-
1932
- // Prompt field - same chat-surface chrome as the bottom Steer bar
1933
- const inputWrap = el('div', {
1934
- display: 'inline-flex', alignItems: 'center',
1935
- flex: '1', minWidth: '0', height: '28px',
1936
- borderRadius: '7px',
1937
- background: BP.chatSurface,
1938
- border: '1px solid ' + BP.hairline,
1939
- overflow: 'hidden',
1940
- transition: 'border-color 0.15s ease',
1941
- });
1942
- inputWrap.id = PREFIX + '-configure-input-wrap';
2309
+ const inputShell = el('div', configureInputShellStyle());
1943
2310
 
1944
2311
  const input = document.createElement('input');
1945
2312
  input.id = PREFIX + '-input';
1946
2313
  input.type = 'text';
1947
- input.placeholder = selectedAction === 'impeccable' ? 'describe what you want…' : 'refine further (optional)…';
2314
+ input.placeholder = '';
1948
2315
  input.setAttribute('aria-label', 'Describe the change');
1949
- Object.assign(input.style, {
1950
- flex: '1', minWidth: '0', width: '100%',
1951
- padding: '0 6px', border: 'none', background: 'transparent',
1952
- fontFamily: FONT, fontSize: '11.5px', color: BP.text,
1953
- outline: 'none',
1954
- });
2316
+ Object.assign(input.style, configureInputFieldStyle());
1955
2317
  input.disabled = controlsLocked;
1956
2318
  if (controlsLocked) {
1957
2319
  input.placeholder = 'apply is running...';
@@ -1959,33 +2321,31 @@
1959
2321
  input.style.opacity = '0.58';
1960
2322
  }
1961
2323
 
1962
- const voiceBtn = el('button', {
1963
- display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
1964
- padding: '0', boxSizing: 'border-box',
1965
- width: '28px', height: '28px', flexShrink: '0',
1966
- border: 'none', background: 'transparent',
1967
- color: BP.textDim, cursor: 'pointer',
1968
- transition: 'color 0.12s ease, background 0.12s ease',
2324
+ const action = buildConfigureActionControl({
2325
+ controlsLocked,
2326
+ onClick: (e) => {
2327
+ e.stopPropagation();
2328
+ if (controlsLocked) { showManualApplyBusyToast(); return; }
2329
+ toggleActionPicker();
2330
+ },
1969
2331
  });
1970
- voiceBtn.id = PREFIX + '-configure-voice';
1971
- voiceBtn.type = 'button';
1972
- voiceBtn.setAttribute('aria-label', 'Voice input');
1973
- voiceBtn.innerHTML = ICON_PAGE_VOICE;
1974
- voiceBtn.disabled = controlsLocked;
1975
- voiceBtn.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
1976
- voiceBtn.style.opacity = controlsLocked ? '0.58' : '1';
1977
2332
 
1978
- if (!uiGetById(PREFIX + '-configure-input-style')) {
1979
- const s = document.createElement('style');
1980
- s.id = PREFIX + '-configure-input-style';
1981
- s.textContent =
1982
- '@keyframes impeccable-configure-voice-pulse { 0%, 100% { opacity: 0.55; } 50% { opacity: 1; } }' +
1983
- '#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }' +
1984
- '#' + PREFIX + '-configure-voice[data-listening="true"] svg { animation: impeccable-configure-voice-pulse 1.1s ease-in-out infinite; }' +
1985
- '@media (prefers-reduced-motion: reduce) { #' + PREFIX + '-configure-voice[data-listening="true"] svg { animation: none; opacity: 1; } }' +
1986
- '#' + PREFIX + '-configure-voice:hover { background: oklch(78% 0.12 82 / 0.12); }';
1987
- uiAppendStyle(s);
1988
- }
2333
+ const count = buildConfigureCountControl({
2334
+ controlsLocked,
2335
+ onClick: (e) => {
2336
+ e.stopPropagation();
2337
+ if (controlsLocked) { showManualApplyBusyToast(); return; }
2338
+ count.textContent = '\u00D7' + cycleSelectedCount();
2339
+ if (count.matches(':hover')) {
2340
+ showConfigureBarTooltip(count, variantCountTooltipText(selectedCount));
2341
+ }
2342
+ },
2343
+ });
2344
+
2345
+ inputShell.appendChild(buildSelectionPill({ el: selectedElement, controlsLocked }));
2346
+ inputShell.appendChild(input);
2347
+
2348
+ ensureConfigureInputStyle();
1989
2349
 
1990
2350
  input.addEventListener('focus', () => syncConfigureInputChrome());
1991
2351
  input.addEventListener('blur', () => syncConfigureInputChrome());
@@ -1995,81 +2355,33 @@
1995
2355
  e.stopPropagation();
1996
2356
  e.preventDefault();
1997
2357
  input.blur();
1998
- disableInlineEdit();
1999
- hideBar();
2000
- renderEditBadge('hidden');
2001
- state = 'PICKING';
2002
- syncPageChatFocus('configure-input-escape');
2358
+ exitConfigureToPicking('configure-input-escape');
2003
2359
  return;
2004
2360
  }
2005
- // Let arrow keys pass through to the element picker when the input is empty
2006
2361
  if ((e.key === 'ArrowUp' || e.key === 'ArrowDown') && !input.value) return;
2007
2362
  e.stopPropagation();
2008
2363
  });
2009
2364
 
2010
- voiceBtn.addEventListener('mousedown', (e) => e.stopPropagation());
2011
- voiceBtn.addEventListener('click', (e) => {
2012
- e.stopPropagation();
2013
- if (controlsLocked) { showManualApplyBusyToast(); return; }
2014
- toggleConfigureVoice();
2365
+ const voiceBtn = buildConfigureVoiceButton({
2366
+ id: PREFIX + '-configure-voice',
2367
+ controlsLocked,
2368
+ onClick: (e) => {
2369
+ e.stopPropagation();
2370
+ if (controlsLocked) { showManualApplyBusyToast(); return; }
2371
+ toggleConfigureVoice();
2372
+ },
2015
2373
  });
2016
2374
 
2017
- inputWrap.appendChild(input);
2018
- inputWrap.appendChild(voiceBtn);
2019
- row.appendChild(inputWrap);
2020
- syncConfigureInputChrome();
2021
-
2022
- // Variant count toggle
2023
- const count = el('button', {
2024
- display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
2025
- boxSizing: 'border-box', height: '28px', padding: '0 6px',
2026
- borderRadius: '5px',
2027
- border: '1px solid ' + BP.hairline, background: 'transparent',
2028
- fontFamily: MONO, fontSize: '11px', fontWeight: '600',
2029
- color: BP.textDim, cursor: 'pointer',
2030
- transition: 'color 0.12s ease, border-color 0.12s ease',
2031
- flexShrink: '0', whiteSpace: 'nowrap',
2375
+ const go = buildConfigureSubmitButton({
2376
+ controlsLocked,
2377
+ ariaLabel: 'Generate variants',
2378
+ onClick: (e) => { e.stopPropagation(); handleGo(); },
2032
2379
  });
2033
- count.textContent = '\u00D7' + selectedCount;
2034
- count.title = 'Variants: click to change';
2035
- count.disabled = controlsLocked;
2036
- count.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
2037
- count.style.opacity = controlsLocked ? '0.58' : '1';
2038
- if (controlsLocked) count.title = 'Apply is still running';
2039
- count.addEventListener('mouseenter', () => { if (!controlsLocked) { count.style.color = BP.text; count.style.borderColor = BP.text; } });
2040
- count.addEventListener('mouseleave', () => { if (!controlsLocked) { count.style.color = BP.textDim; count.style.borderColor = BP.hairline; } });
2041
- count.addEventListener('click', (e) => {
2042
- e.stopPropagation();
2043
- if (controlsLocked) { showManualApplyBusyToast(); return; }
2044
- selectedCount = selectedCount >= 4 ? 2 : selectedCount + 1;
2045
- count.textContent = '\u00D7' + selectedCount;
2046
- });
2047
- row.appendChild(count);
2048
2380
 
2049
- // Go button
2050
- const go = el('button', {
2051
- display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
2052
- boxSizing: 'border-box', height: '28px', padding: '0 12px',
2053
- borderRadius: '6px',
2054
- border: 'none', background: BP.accent, color: C.ink,
2055
- fontFamily: FONT, fontSize: '12px', fontWeight: '600',
2056
- cursor: 'pointer',
2057
- transition: 'filter 0.12s ease, transform 0.1s ease',
2058
- flexShrink: '0', whiteSpace: 'nowrap',
2059
- });
2060
- go.textContent = 'Go \u2192';
2061
- go.disabled = controlsLocked;
2062
- go.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
2063
- go.style.opacity = controlsLocked ? '0.58' : '1';
2064
- if (controlsLocked) go.title = 'Apply is still running';
2065
- go.addEventListener('mouseenter', () => { if (!controlsLocked) go.style.filter = 'brightness(1.1)'; });
2066
- go.addEventListener('mouseleave', () => go.style.filter = 'none');
2067
- go.addEventListener('mousedown', () => { if (!controlsLocked) go.style.transform = 'scale(0.97)'; });
2068
- go.addEventListener('mouseup', () => go.style.transform = 'scale(1)');
2069
- go.addEventListener('click', (e) => { e.stopPropagation(); handleGo(); });
2070
- row.appendChild(go);
2071
-
2072
- // Auto-focus input after a beat
2381
+ row.appendChild(inputShell);
2382
+ row.appendChild(buildConfigureTrailingCluster([action, count], voiceBtn, go));
2383
+ syncConfigureInputChrome();
2384
+
2073
2385
  if (!controlsLocked) setTimeout(() => input.focus(), 60);
2074
2386
 
2075
2387
  return row;
@@ -2078,34 +2390,20 @@
2078
2390
  function buildInsertConfigureRow() {
2079
2391
  const controlsLocked = pendingApplyInFlight === true;
2080
2392
  const row = el('div', {
2081
- display: 'flex', alignItems: 'center', gap: '6px',
2393
+ display: 'flex', alignItems: 'stretch', width: '100%', height: CONFIGURE_BAR_H,
2082
2394
  });
2395
+ row.addEventListener('pointerdown', (e) => e.stopPropagation());
2396
+ row.addEventListener('mousedown', (e) => e.stopPropagation());
2397
+ row.addEventListener('click', (e) => e.stopPropagation());
2083
2398
 
2084
- const inputWrap = el('div', {
2085
- display: 'inline-flex', alignItems: 'center',
2086
- flex: '1', minWidth: '0', height: '28px',
2087
- borderRadius: '7px',
2088
- background: BP.chatSurface,
2089
- border: '1px solid ' + BP.hairline,
2090
- overflow: 'hidden',
2091
- transition: 'border-color 0.15s ease',
2092
- });
2093
- inputWrap.id = PREFIX + '-insert-input-wrap';
2094
- inputWrap.addEventListener('pointerdown', (e) => e.stopPropagation());
2095
- inputWrap.addEventListener('mousedown', (e) => e.stopPropagation());
2096
- inputWrap.addEventListener('click', (e) => e.stopPropagation());
2399
+ const inputShell = el('div', configureInputShellStyle());
2097
2400
 
2098
2401
  const input = document.createElement('input');
2099
2402
  input.id = PREFIX + '-insert-input';
2100
2403
  input.type = 'text';
2101
- input.placeholder = 'describe what to insert…';
2404
+ input.placeholder = '';
2102
2405
  input.setAttribute('aria-label', 'Describe the new element');
2103
- Object.assign(input.style, {
2104
- flex: '1', minWidth: '0', width: '100%',
2105
- padding: '0 6px', border: 'none', background: 'transparent',
2106
- fontFamily: FONT, fontSize: '11.5px', color: BP.text,
2107
- outline: 'none',
2108
- });
2406
+ Object.assign(input.style, configureInputFieldStyle());
2109
2407
  input.disabled = controlsLocked;
2110
2408
  if (controlsLocked) {
2111
2409
  input.placeholder = 'apply is running...';
@@ -2113,20 +2411,22 @@
2113
2411
  input.style.opacity = '0.58';
2114
2412
  }
2115
2413
 
2116
- const voiceBtn = el('button', {
2117
- display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
2118
- padding: '0', boxSizing: 'border-box',
2119
- width: '28px', height: '28px', flexShrink: '0',
2120
- border: 'none', background: 'transparent',
2121
- color: BP.textDim, cursor: 'pointer',
2414
+ const count = buildConfigureCountControl({
2415
+ controlsLocked,
2416
+ onClick: (e) => {
2417
+ e.stopPropagation();
2418
+ if (controlsLocked) { showManualApplyBusyToast(); return; }
2419
+ count.textContent = '\u00D7' + cycleSelectedCount();
2420
+ if (count.matches(':hover')) {
2421
+ showConfigureBarTooltip(count, variantCountTooltipText(selectedCount));
2422
+ }
2423
+ },
2122
2424
  });
2123
- voiceBtn.id = PREFIX + '-insert-voice';
2124
- voiceBtn.type = 'button';
2125
- voiceBtn.setAttribute('aria-label', 'Voice input');
2126
- voiceBtn.innerHTML = ICON_PAGE_VOICE;
2127
- voiceBtn.disabled = controlsLocked;
2128
- voiceBtn.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
2129
- voiceBtn.style.opacity = controlsLocked ? '0.58' : '1';
2425
+
2426
+ inputShell.appendChild(buildSelectionPill({ el: selectedElement, controlsLocked }));
2427
+ inputShell.appendChild(input);
2428
+
2429
+ ensureConfigureInputStyle();
2130
2430
 
2131
2431
  input.addEventListener('input', () => syncInsertCreateButton());
2132
2432
  input.addEventListener('pointerdown', (e) => e.stopPropagation());
@@ -2148,48 +2448,31 @@
2148
2448
  }
2149
2449
  e.stopPropagation();
2150
2450
  });
2151
- voiceBtn.addEventListener('mousedown', (e) => e.stopPropagation());
2152
- voiceBtn.addEventListener('click', (e) => {
2153
- e.stopPropagation();
2154
- if (controlsLocked) { showManualApplyBusyToast(); return; }
2155
- toggleConfigureVoice();
2156
- });
2157
-
2158
- inputWrap.appendChild(input);
2159
- inputWrap.appendChild(voiceBtn);
2160
- row.appendChild(inputWrap);
2451
+ input.addEventListener('focus', () => syncConfigureInputChrome());
2452
+ input.addEventListener('blur', () => syncConfigureInputChrome());
2161
2453
 
2162
- const count = el('button', {
2163
- display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
2164
- boxSizing: 'border-box', height: '28px', padding: '0 6px',
2165
- borderRadius: '5px',
2166
- border: '1px solid ' + BP.hairline, background: 'transparent',
2167
- fontFamily: MONO, fontSize: '11px', fontWeight: '600',
2168
- color: BP.textDim, cursor: 'pointer', flexShrink: '0', whiteSpace: 'nowrap',
2169
- });
2170
- count.textContent = '\u00D7' + selectedCount;
2171
- count.disabled = controlsLocked;
2172
- count.style.cursor = controlsLocked ? 'not-allowed' : 'pointer';
2173
- count.style.opacity = controlsLocked ? '0.58' : '1';
2174
- count.addEventListener('click', (e) => {
2175
- e.stopPropagation();
2176
- if (controlsLocked) { showManualApplyBusyToast(); return; }
2177
- selectedCount = selectedCount >= 4 ? 2 : selectedCount + 1;
2178
- count.textContent = '\u00D7' + selectedCount;
2454
+ const voiceBtn = buildConfigureVoiceButton({
2455
+ id: PREFIX + '-insert-voice',
2456
+ controlsLocked,
2457
+ onClick: (e) => {
2458
+ e.stopPropagation();
2459
+ if (controlsLocked) { showManualApplyBusyToast(); return; }
2460
+ toggleConfigureVoice();
2461
+ },
2179
2462
  });
2180
- row.appendChild(count);
2181
2463
 
2182
- const create = el('button', {
2183
- display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
2184
- boxSizing: 'border-box', height: '28px', padding: '0 12px',
2185
- borderRadius: '6px',
2186
- border: 'none', background: BP.accent, color: C.ink,
2187
- fontFamily: FONT, fontSize: '12px', fontWeight: '600',
2188
- flexShrink: '0', whiteSpace: 'nowrap',
2464
+ const create = buildConfigureSubmitButton({
2465
+ controlsLocked,
2466
+ ariaLabel: 'Create variants',
2467
+ onClick: (e) => {
2468
+ e.preventDefault();
2469
+ e.stopPropagation();
2470
+ if (controlsLocked) { showManualApplyBusyToast(); return; }
2471
+ if (!isInsertCreateEnabled(create)) return;
2472
+ handleInsertCreate();
2473
+ },
2189
2474
  });
2190
2475
  create.id = PREFIX + '-insert-create';
2191
- create.textContent = 'Create \u2192';
2192
- create.disabled = controlsLocked;
2193
2476
  create.addEventListener('mouseenter', () => {
2194
2477
  if (controlsLocked) return;
2195
2478
  if (isInsertCreateEnabled(create)) {
@@ -2199,15 +2482,10 @@
2199
2482
  showInsertCreateTooltip(create, insertCreateDisabledReason(insertCreateGateState(input)));
2200
2483
  });
2201
2484
  create.addEventListener('mouseleave', hideInsertCreateTooltip);
2202
- create.addEventListener('click', (e) => {
2203
- e.preventDefault();
2204
- e.stopPropagation();
2205
- if (controlsLocked) { showManualApplyBusyToast(); return; }
2206
- if (!isInsertCreateEnabled(create)) return;
2207
- handleInsertCreate();
2208
- });
2209
- row.appendChild(create);
2485
+ row.appendChild(inputShell);
2486
+ row.appendChild(buildConfigureTrailingCluster([count], voiceBtn, create));
2210
2487
  syncInsertCreateButton(create, input);
2488
+ syncConfigureInputChrome();
2211
2489
  if (!controlsLocked) setTimeout(() => input.focus(), 60);
2212
2490
  return row;
2213
2491
  }
@@ -2404,12 +2682,12 @@
2404
2682
  });
2405
2683
  const check = el('span', {
2406
2684
  fontSize: '15px', lineHeight: '1', flexShrink: '0',
2407
- color: 'oklch(45% 0.15 145)',
2685
+ color: 'oklch(45% 0.18 145)',
2408
2686
  });
2409
2687
  check.textContent = '\u2713';
2410
2688
  row.appendChild(check);
2411
2689
  const label = el('span', {
2412
- fontSize: '12px', color: 'oklch(35% 0.1 145)', fontWeight: '600',
2690
+ fontSize: '12px', color: 'oklch(49% 0.08 188)', fontWeight: '600',
2413
2691
  });
2414
2692
  label.textContent = 'Variant applied';
2415
2693
  row.appendChild(label);
@@ -2497,7 +2775,7 @@
2497
2775
  position: 'fixed', zIndex: Z.picker,
2498
2776
  display: 'none', opacity: '0',
2499
2777
  transform: 'scale(0.96) translateY(4px)',
2500
- transformOrigin: 'bottom left',
2778
+ transformOrigin: 'bottom right',
2501
2779
  transition: 'opacity 0.18s ' + EASE + ', transform 0.2s ' + EASE,
2502
2780
  background: P.surface,
2503
2781
  border: '1px solid ' + P.border,
@@ -2573,14 +2851,18 @@
2573
2851
  chip.style.background = isActive ? P.accentSoft : 'transparent';
2574
2852
  chip.style.color = isActive ? P.accent : P.text;
2575
2853
  });
2576
- // Position above the bar
2854
+ // Position above the bar, right-aligned to the configure bar edge.
2577
2855
  const barRect = barEl.getBoundingClientRect();
2578
2856
  const pickerH = 170; // approximate; grows with icon + label rows
2579
2857
  let top = barRect.top - pickerH - 6;
2580
2858
  if (top < 8) top = barRect.bottom + 6;
2859
+ pickerEl.style.display = 'block';
2860
+ const pickerW = pickerEl.offsetWidth;
2861
+ let left = barRect.right - pickerW;
2862
+ left = Math.max(8, Math.min(left, window.innerWidth - pickerW - 8));
2581
2863
  Object.assign(pickerEl.style, {
2582
- top: top + 'px', left: barRect.left + 'px',
2583
- display: 'block',
2864
+ top: top + 'px',
2865
+ left: left + 'px',
2584
2866
  });
2585
2867
  requestAnimationFrame(() => {
2586
2868
  pickerEl.style.opacity = '1';
@@ -3063,7 +3345,7 @@
3063
3345
 
3064
3346
  function enterEditingMode() {
3065
3347
  if (pendingApplyInFlight) { showManualApplyBusyToast(); return; }
3066
- state = 'EDITING';
3348
+ setLiveState('EDITING');
3067
3349
  hideBar();
3068
3350
  hideAnnotOverlay();
3069
3351
  renderEditBadge('editing');
@@ -3096,7 +3378,7 @@
3096
3378
  function cancelEditing() {
3097
3379
  restoreInlineEditDrafts();
3098
3380
  disableInlineEdit();
3099
- state = 'CONFIGURING';
3381
+ setLiveState('CONFIGURING');
3100
3382
  showBar('configure');
3101
3383
  showAnnotOverlay(selectedElement);
3102
3384
  renderEditBadge('idle');
@@ -3110,12 +3392,33 @@
3110
3392
  hideAnnotOverlay();
3111
3393
  clearAnnotations();
3112
3394
  renderEditBadge('hidden');
3113
- state = 'PICKING';
3395
+ setLiveState('PICKING');
3114
3396
  hoveredElement = null;
3115
3397
  hideHighlight();
3116
3398
  syncPageChatFocus('editing-outside-click');
3117
3399
  }
3118
3400
 
3401
+ function teardownConfigureChrome() {
3402
+ hideConfigureBarTooltip();
3403
+ // hideBar() restores unsaved EDITING drafts before it disables inline
3404
+ // edit; disabling here first would wipe the draft metadata it needs.
3405
+ hideBar();
3406
+ stopScrollTracking();
3407
+ hideAnnotOverlay();
3408
+ clearAnnotations();
3409
+ renderEditBadge('hidden');
3410
+ }
3411
+
3412
+ function exitConfigureToPicking(reason, opts = {}) {
3413
+ teardownConfigureChrome();
3414
+ setLiveState('PICKING');
3415
+ if (opts.clearHover) {
3416
+ hoveredElement = null;
3417
+ hideHighlight();
3418
+ }
3419
+ syncPageChatFocus(reason);
3420
+ }
3421
+
3119
3422
  // Prefer the leaf's own id/class; if it has neither (e.g. a bare <em>),
3120
3423
  // climb to the nearest ancestor with one. The CLI uses tag+class together,
3121
3424
  // so tag must come from the same node as the locator.
@@ -3337,7 +3640,7 @@
3337
3640
  updatePendingCounter(stashResult.pendingCount || 0);
3338
3641
  maybeShowFirstSaveToast();
3339
3642
  disableInlineEdit();
3340
- state = 'CONFIGURING';
3643
+ setLiveState('CONFIGURING');
3341
3644
  showBar('configure');
3342
3645
  showAnnotOverlay(selectedElement);
3343
3646
  renderEditBadge('idle');
@@ -4017,6 +4320,12 @@
4017
4320
  // Edit content badge - floating button at element top-right to enter EDITING mode
4018
4321
  //
4019
4322
 
4323
+ const EDIT_COPY_LABEL = 'Edit copy';
4324
+ const EDIT_COPY_ICON =
4325
+ '<svg width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true">' +
4326
+ '<path d="M17 3a2.85 2.83 0 1 1 4 4L7.5 20.5 2 22l1.5-5.5Z"/><path d="m15 5 4 4"/>' +
4327
+ '</svg>';
4328
+
4020
4329
  function usesShadowChromeRoot() {
4021
4330
  const root = liveUiRoot();
4022
4331
  return root && root !== document.body && root.host && root.host.id === PREFIX + '-root';
@@ -4167,7 +4476,7 @@
4167
4476
  editBadgeProxyRoot.appendChild(proxy);
4168
4477
  editBadgeProxyByTarget.set(target, proxy);
4169
4478
  }
4170
- proxy.title = target.title || target.textContent || 'Edit copy';
4479
+ proxy.title = target.title || target.getAttribute('aria-label') || target.textContent || EDIT_COPY_LABEL;
4171
4480
  styleEditBadgeProxy(proxy, target);
4172
4481
  }
4173
4482
  }
@@ -4207,13 +4516,16 @@
4207
4516
  }
4208
4517
  const r = selectedElement.getBoundingClientRect();
4209
4518
  const bw = editBadgeEl.offsetWidth;
4519
+ // Match showHighlight's 2px outset so the badge right edge lines up with the outline.
4520
+ const outlineRight = r.right + 2;
4210
4521
  editBadgeEl.style.top = Math.max(4, r.top - 28) + 'px';
4211
- editBadgeEl.style.left = Math.min(window.innerWidth - bw - 4, r.right - bw) + 'px';
4522
+ editBadgeEl.style.left = Math.min(window.innerWidth - bw - 4, outlineRight - bw) + 'px';
4212
4523
  syncEditBadgeHitProxies();
4213
4524
  }
4214
4525
 
4215
4526
  function renderEditBadge(mode) {
4216
4527
  if (mode === 'hidden' || !editBadgeEl) {
4528
+ hideConfigureBarTooltip();
4217
4529
  if (editBadgeEl) editBadgeEl.style.display = 'none';
4218
4530
  syncEditBadgeHitProxies();
4219
4531
  return;
@@ -4251,16 +4563,36 @@
4251
4563
  const disabled = mode === 'idle-disabled';
4252
4564
  editBadgeEl.innerHTML = '';
4253
4565
  const btn = document.createElement('button');
4254
- btn.textContent = 'Edit copy';
4255
- Object.assign(btn.style, calloutStyle(disabled ? MUTED : ACCENT, disabled ? HAIRLINE : ACCENT));
4566
+ btn.type = 'button';
4567
+ btn.innerHTML = EDIT_COPY_ICON;
4568
+ btn.setAttribute('aria-label', EDIT_COPY_LABEL);
4569
+ Object.assign(btn.style, calloutStyle(
4570
+ disabled ? MUTED : PRIMARY_TEXT,
4571
+ disabled ? HAIRLINE : ACCENT,
4572
+ ));
4573
+ Object.assign(btn.style, {
4574
+ padding: '4px',
4575
+ minWidth: '22px',
4576
+ width: '22px',
4577
+ height: '22px',
4578
+ minHeight: '22px',
4579
+ display: 'inline-flex',
4580
+ alignItems: 'center',
4581
+ justifyContent: 'center',
4582
+ lineHeight: '0',
4583
+ letterSpacing: '0',
4584
+ background: disabled ? SURFACE : ACCENT,
4585
+ });
4256
4586
  if (disabled) {
4257
4587
  btn.style.cursor = 'not-allowed';
4258
4588
  btn.style.opacity = '0.55';
4259
4589
  btn.disabled = true;
4260
- btn.title = 'Edit copy is disabled while the current copy edit is applying';
4590
+ const disabledTip = EDIT_COPY_LABEL + ' is disabled while the current copy edit is applying';
4591
+ btn.addEventListener('mouseenter', () => showConfigureBarTooltip(btn, disabledTip));
4592
+ btn.addEventListener('mouseleave', hideConfigureBarTooltip);
4261
4593
  } else {
4262
- btn.addEventListener('mouseenter', () => { btn.style.background = ACCENT; btn.style.color = PRIMARY_TEXT; });
4263
- btn.addEventListener('mouseleave', () => { btn.style.background = SURFACE; btn.style.color = ACCENT; });
4594
+ btn.addEventListener('mouseenter', () => showConfigureBarTooltip(btn, EDIT_COPY_LABEL));
4595
+ btn.addEventListener('mouseleave', hideConfigureBarTooltip);
4264
4596
  btn.onclick = enterEditingMode;
4265
4597
  }
4266
4598
  editBadgeEl.appendChild(btn);
@@ -4276,9 +4608,8 @@
4276
4608
  cancel.onclick = cancelEditing;
4277
4609
  const save = document.createElement('button');
4278
4610
  save.textContent = 'Save';
4279
- Object.assign(save.style, calloutStyle(ACCENT));
4280
- save.addEventListener('mouseenter', () => { save.style.background = ACCENT; save.style.color = PRIMARY_TEXT; });
4281
- save.addEventListener('mouseleave', () => { save.style.background = SURFACE; save.style.color = ACCENT; });
4611
+ Object.assign(save.style, calloutStyle(PRIMARY_TEXT, ACCENT));
4612
+ save.style.background = ACCENT;
4282
4613
  save.onclick = applyEditing;
4283
4614
  editBadgeEl.append(cancel, save);
4284
4615
  }
@@ -4514,29 +4845,142 @@
4514
4845
  return doc.getElementById('impeccable-anchor')?.firstElementChild || null;
4515
4846
  }
4516
4847
 
4848
+ function normalizeElementClassName(el) {
4849
+ if (!el) return '';
4850
+ const raw = el.getAttribute?.('class');
4851
+ if (typeof raw === 'string') return raw.trim();
4852
+ if (el.className != null) {
4853
+ const cls = el.className;
4854
+ if (typeof cls === 'string') return cls.trim();
4855
+ if (typeof cls.baseVal === 'string') return cls.baseVal.trim();
4856
+ }
4857
+ return '';
4858
+ }
4859
+
4860
+ function buildPickedAnchorSnapshot(el) {
4861
+ if (!el || el.nodeType !== 1) return null;
4862
+ return {
4863
+ tag: el.tagName,
4864
+ id: el.id || '',
4865
+ classes: [...el.classList],
4866
+ text: (el.textContent || '').trim().slice(0, 120),
4867
+ };
4868
+ }
4869
+
4870
+ function isUsableInjectionAnchor(el) {
4871
+ return !!el
4872
+ && el.parentElement
4873
+ && document.body.contains(el)
4874
+ && !own(el)
4875
+ && !el.closest?.('[data-impeccable-variants]');
4876
+ }
4877
+
4878
+ function elementMatchesOriginalMarkup(liveEl, origContent) {
4879
+ if (!isUsableInjectionAnchor(liveEl) || !origContent) return false;
4880
+ // A matching id is decisive on its own: ids are unique, while the source
4881
+ // tag and class names may not survive the build (component tags, hashed
4882
+ // CSS-module class names).
4883
+ if (origContent.id) return liveEl.id === origContent.id;
4884
+ if (liveEl.tagName !== origContent.tagName) return false;
4885
+
4886
+ const origClasses = normalizeElementClassName(origContent).split(/\s+/).filter(Boolean)
4887
+ .filter((name) => /^[A-Za-z_-][\w-]*$/.test(name));
4888
+ if (origClasses.length > 0 && !origClasses.every((name) => liveEl.classList.contains(name))) return false;
4889
+
4890
+ const origText = (origContent.textContent || '').trim();
4891
+ if (origClasses.length === 0 && origText.length >= 4) {
4892
+ const liveText = (liveEl.textContent || '').trim();
4893
+ const needle = origText.slice(0, Math.min(40, origText.length));
4894
+ if (!liveText.includes(needle) && !(liveText.length >= 4 && origText.includes(liveText.slice(0, 40)))) return false;
4895
+ }
4896
+ return true;
4897
+ }
4898
+
4899
+ function findLiveElementFromAnchorSnapshot(snapshot) {
4900
+ if (!snapshot) return null;
4901
+ const tag = String(snapshot.tag || '').toLowerCase();
4902
+ if (!tag) return null;
4903
+ if (snapshot.id) {
4904
+ const byId = document.getElementById(snapshot.id);
4905
+ if (isUsableInjectionAnchor(byId)) return byId;
4906
+ }
4907
+ const classes = (snapshot.classes || []).filter((name) => /^[A-Za-z_-][\w-]*$/.test(name));
4908
+ const needle = (snapshot.text || '').trim();
4909
+ const candidates = [...document.getElementsByTagName(tag)];
4910
+ for (const c of candidates) {
4911
+ if (!isUsableInjectionAnchor(c)) continue;
4912
+ if (classes.length > 0 && !classes.every((name) => c.classList.contains(name))) continue;
4913
+ if (!snapshot.id && classes.length === 0 && needle.length >= 4) {
4914
+ const text = (c.textContent || '').trim();
4915
+ if (!text.includes(needle.slice(0, 40)) && !(text.length >= 4 && needle.includes(text.slice(0, 40)))) continue;
4916
+ }
4917
+ return c;
4918
+ }
4919
+ return null;
4920
+ }
4921
+
4517
4922
  function findLiveElementForOriginalMarkup(originalMarkup) {
4518
4923
  const origContent = parseOriginalMarkupElement(originalMarkup);
4519
4924
  if (!origContent) return null;
4520
4925
 
4521
4926
  const tag = origContent.tagName.toLowerCase();
4522
- const cls = origContent.className;
4523
- let liveEl = null;
4927
+ const cls = normalizeElementClassName(origContent);
4928
+ const candidates = [...document.getElementsByTagName(tag)];
4929
+
4524
4930
  if (origContent.id) {
4525
- liveEl = document.getElementById(origContent.id);
4526
- } else if (cls) {
4527
- const candidates = document.querySelectorAll(tag + '.' + cls.split(' ')[0]);
4528
- for (const c of candidates) {
4529
- if (c.className === cls && !own(c)) { liveEl = c; break; }
4530
- }
4531
- if (!liveEl) {
4532
- const expectedClasses = String(cls).split(/\s+/).filter(Boolean);
4931
+ const byId = document.getElementById(origContent.id);
4932
+ if (elementMatchesOriginalMarkup(byId, origContent)) return byId;
4933
+ }
4934
+
4935
+ if (cls) {
4936
+ const expectedClasses = cls.split(/\s+/).filter((name) => /^[A-Za-z_-][\w-]*$/.test(name));
4937
+ if (expectedClasses.length > 0) {
4533
4938
  for (const c of candidates) {
4534
- if (own(c)) continue;
4535
- if (expectedClasses.every((name) => c.classList.contains(name))) { liveEl = c; break; }
4939
+ if (!isUsableInjectionAnchor(c)) continue;
4940
+ if (expectedClasses.every((name) => c.classList.contains(name))) return c;
4536
4941
  }
4537
4942
  }
4538
4943
  }
4539
- return liveEl;
4944
+
4945
+ const origText = (origContent.textContent || '').trim();
4946
+ if (origText.length >= 4) {
4947
+ const needle = origText.slice(0, 40);
4948
+ let best = null;
4949
+ let bestLen = Infinity;
4950
+ for (const c of candidates) {
4951
+ if (!isUsableInjectionAnchor(c)) continue;
4952
+ const text = (c.textContent || '').trim();
4953
+ if (!text.includes(needle) && !(text.length >= 4 && origText.includes(text.slice(0, 40)))) continue;
4954
+ if (text.length < bestLen) { best = c; bestLen = text.length; }
4955
+ }
4956
+ if (best) return best;
4957
+ }
4958
+
4959
+ return null;
4960
+ }
4961
+
4962
+ function resolveLiveInjectionAnchor(originalMarkup) {
4963
+ const origContent = parseOriginalMarkupElement(originalMarkup);
4964
+ if (!origContent) return null;
4965
+
4966
+ const attempts = [
4967
+ selectedElement,
4968
+ findLiveElementFromAnchorSnapshot(pickedAnchorSnapshot),
4969
+ findLiveElementForOriginalMarkup(originalMarkup),
4970
+ ];
4971
+ for (const candidate of attempts) {
4972
+ if (elementMatchesOriginalMarkup(candidate, origContent)) return candidate;
4973
+ }
4974
+
4975
+ if (isUsableInjectionAnchor(selectedElement) && selectedElement.tagName === origContent.tagName) {
4976
+ const origClasses = normalizeElementClassName(origContent).split(/\s+/).filter(Boolean);
4977
+ if (origContent.id && selectedElement.id === origContent.id) return selectedElement;
4978
+ if (origClasses.length === 0) return selectedElement;
4979
+ const overlap = origClasses.filter((name) => selectedElement.classList.contains(name));
4980
+ if (overlap.length >= 1) return selectedElement;
4981
+ }
4982
+
4983
+ return null;
4540
4984
  }
4541
4985
 
4542
4986
  function isSvelteInsertManifest(manifest) {
@@ -4548,7 +4992,45 @@
4548
4992
  const anchor = findInsertAnchorInDom();
4549
4993
  if (anchor?.parentElement) return anchor;
4550
4994
  }
4551
- return findLiveElementForOriginalMarkup(manifest?.originalMarkup || manifest?.anchorMarkup || '');
4995
+ return resolveLiveInjectionAnchor(manifest?.originalMarkup || manifest?.anchorMarkup || '');
4996
+ }
4997
+
4998
+ function waitForVariantAnchorAndRetry({ filePath, sessionId, srcWrapper, checkpointReason }) {
4999
+ if (pendingVariantAnchorRetryObserver) pendingVariantAnchorRetryObserver.disconnect();
5000
+ const origContent = srcWrapper?.querySelector('[data-impeccable-variant="original"] > :first-child');
5001
+ if (!origContent) return;
5002
+ const originalMarkup = origContent.outerHTML;
5003
+
5004
+ pendingVariantAnchorRetryObserver = new MutationObserver(() => {
5005
+ // Retry once either the anchor element or the session wrapper shows up.
5006
+ // A wrapper can land incomplete ("wrap HMR landed, variant insert did
5007
+ // not"); injectVariantsFromSource owns both cases - it replaces an
5008
+ // existing wrapper from source and clears recoveryWaitingForAnchor.
5009
+ const wrapperLanded = !!document.querySelector('[data-impeccable-variants="' + sessionId + '"]');
5010
+ if (!wrapperLanded) {
5011
+ const liveEl = resolveLiveInjectionAnchor(originalMarkup);
5012
+ if (!liveEl?.parentElement) return;
5013
+ }
5014
+ pendingVariantAnchorRetryObserver.disconnect();
5015
+ pendingVariantAnchorRetryObserver = null;
5016
+ injectVariantsFromSource(filePath, sessionId);
5017
+ });
5018
+ pendingVariantAnchorRetryObserver.observe(document.body, { childList: true, subtree: true });
5019
+ if (checkpointReason) queueCheckpoint(checkpointReason);
5020
+ }
5021
+
5022
+ function enterRecoveryWaitingForAnchor({ filePath, sessionId, srcWrapper, checkpointReason, trackScroll }) {
5023
+ recoveryWaitingForAnchor = true;
5024
+ selectedElement = document.body;
5025
+ setLiveState('GENERATING');
5026
+ showBar('generating');
5027
+ if (trackScroll !== false) startScrollTracking();
5028
+ saveSession();
5029
+ if (srcWrapper && filePath && sessionId) {
5030
+ waitForVariantAnchorAndRetry({ filePath, sessionId, srcWrapper, checkpointReason });
5031
+ } else if (checkpointReason) {
5032
+ queueCheckpoint(checkpointReason);
5033
+ }
4552
5034
  }
4553
5035
 
4554
5036
  function loadSvelteRuntime(runtimeModule) {
@@ -4584,6 +5066,157 @@
4584
5066
  }
4585
5067
  }
4586
5068
 
5069
+ async function loadSvelteComponentVariantSource(manifest, variantNum) {
5070
+ const dir = String(manifest?.componentDir || '').replace(/^\/+/, '');
5071
+ if (!dir || !variantNum) return '';
5072
+ const sourcePath = dir + '/v' + variantNum + '.svelte';
5073
+ const url = 'http://localhost:' + PORT + '/source?token=' + TOKEN + '&path=' + encodeURIComponent(sourcePath);
5074
+ try {
5075
+ const res = await fetch(url);
5076
+ if (!res.ok) return '';
5077
+ return await res.text();
5078
+ } catch {
5079
+ return '';
5080
+ }
5081
+ }
5082
+
5083
+ function extractSvelteComponentStyle(source) {
5084
+ const match = String(source || '').match(/<style\b[^>]*>([\s\S]*?)<\/style\s*>/i);
5085
+ return match ? match[1].trim() : '';
5086
+ }
5087
+
5088
+ async function applySvelteComponentVariantStyle(variantNum) {
5089
+ if (!svelteComponentSession || !variantNum) return;
5090
+ const { manifest, sessionId } = svelteComponentSession;
5091
+ const source = await loadSvelteComponentVariantSource(manifest, variantNum);
5092
+ const css = extractSvelteComponentStyle(source);
5093
+ removeSvelteComponentVariantStyle(svelteComponentSession);
5094
+ if (!css) return;
5095
+ const scopedCss = scopeCssToSveltePreview(css, sessionId);
5096
+ if (!scopedCss) return;
5097
+ const style = document.createElement('style');
5098
+ style.dataset.impeccableSvelteComponentStyle = sessionId;
5099
+ style.dataset.impeccableVariant = String(variantNum);
5100
+ style.textContent = scopedCss;
5101
+ document.head.appendChild(style);
5102
+ svelteComponentSession.styleEl = style;
5103
+ }
5104
+
5105
+ function removeSvelteComponentVariantStyle(session = svelteComponentSession) {
5106
+ const style = session?.styleEl;
5107
+ if (style?.parentNode) style.parentNode.removeChild(style);
5108
+ if (session) session.styleEl = null;
5109
+ }
5110
+
5111
+ function scopeCssToSveltePreview(css, sessionId) {
5112
+ const prefix = '[data-impeccable-variants="' + String(sessionId).replace(/"/g, '\\"') + '"] ';
5113
+ return scopeCssBlock(String(css || ''), prefix).trim();
5114
+ }
5115
+
5116
+ function scopeCssBlock(css, prefix) {
5117
+ let out = '';
5118
+ let i = 0;
5119
+ while (i < css.length) {
5120
+ const open = css.indexOf('{', i);
5121
+ if (open === -1) {
5122
+ out += css.slice(i);
5123
+ break;
5124
+ }
5125
+ const semi = css.indexOf(';', i);
5126
+ if (semi !== -1 && semi < open) {
5127
+ out += css.slice(i, semi + 1);
5128
+ i = semi + 1;
5129
+ continue;
5130
+ }
5131
+ const prelude = css.slice(i, open).trim();
5132
+ const close = findMatchingCssBrace(css, open);
5133
+ if (close === -1) {
5134
+ out += css.slice(i);
5135
+ break;
5136
+ }
5137
+ const body = css.slice(open + 1, close);
5138
+ if (shouldScopeNestedCssAtRule(prelude)) {
5139
+ out += prelude + ' {\n' + scopeCssBlock(body, prefix) + '\n}';
5140
+ } else if (prelude.startsWith('@')) {
5141
+ out += prelude + ' {' + body + '}';
5142
+ } else {
5143
+ out += prefixCssSelectors(prelude, prefix) + ' {' + body + '}';
5144
+ }
5145
+ i = close + 1;
5146
+ }
5147
+ return out;
5148
+ }
5149
+
5150
+ function shouldScopeNestedCssAtRule(prelude) {
5151
+ return /^@(media|supports|container|layer)\b/i.test(prelude || '');
5152
+ }
5153
+
5154
+ function findMatchingCssBrace(css, openIndex) {
5155
+ let depth = 0;
5156
+ let quote = '';
5157
+ for (let i = openIndex; i < css.length; i++) {
5158
+ const ch = css[i];
5159
+ const prev = css[i - 1];
5160
+ if (quote) {
5161
+ if (ch === quote && prev !== '\\') quote = '';
5162
+ continue;
5163
+ }
5164
+ if (ch === '"' || ch === "'") {
5165
+ quote = ch;
5166
+ } else if (ch === '{') {
5167
+ depth++;
5168
+ } else if (ch === '}') {
5169
+ depth--;
5170
+ if (depth === 0) return i;
5171
+ }
5172
+ }
5173
+ return -1;
5174
+ }
5175
+
5176
+ function prefixCssSelectors(prelude, prefix) {
5177
+ return splitCssSelectorList(prelude)
5178
+ .map((selector) => {
5179
+ const s = unwrapSvelteGlobalSelector(selector.trim());
5180
+ if (!s) return '';
5181
+ if (s.startsWith(prefix.trim())) return s;
5182
+ if (s.startsWith(':host')) return s.replace(/^:host\b/, prefix.trim());
5183
+ return prefix + s;
5184
+ })
5185
+ .filter(Boolean)
5186
+ .join(', ');
5187
+ }
5188
+
5189
+ function splitCssSelectorList(selectorList) {
5190
+ const selectors = [];
5191
+ let start = 0;
5192
+ let depth = 0;
5193
+ let quote = '';
5194
+ for (let i = 0; i < selectorList.length; i++) {
5195
+ const ch = selectorList[i];
5196
+ const prev = selectorList[i - 1];
5197
+ if (quote) {
5198
+ if (ch === quote && prev !== '\\') quote = '';
5199
+ continue;
5200
+ }
5201
+ if (ch === '"' || ch === "'") {
5202
+ quote = ch;
5203
+ } else if (ch === '(' || ch === '[') {
5204
+ depth++;
5205
+ } else if ((ch === ')' || ch === ']') && depth > 0) {
5206
+ depth--;
5207
+ } else if (ch === ',' && depth === 0) {
5208
+ selectors.push(selectorList.slice(start, i));
5209
+ start = i + 1;
5210
+ }
5211
+ }
5212
+ selectors.push(selectorList.slice(start));
5213
+ return selectors;
5214
+ }
5215
+
5216
+ function unwrapSvelteGlobalSelector(selector) {
5217
+ return selector.replace(/:global\(([^()]*)\)/g, '$1');
5218
+ }
5219
+
4587
5220
  function buildSveltePropValuesFromLiveElement(liveEl, manifest) {
4588
5221
  const contract = manifest?.propContract || [];
4589
5222
  const values = {};
@@ -4620,6 +5253,7 @@
4620
5253
  });
4621
5254
  svelteComponentSession.mountedVariant = variantNum;
4622
5255
  svelteComponentSession.runtime = runtime;
5256
+ await applySvelteComponentVariantStyle(variantNum);
4623
5257
  if (state === 'CYCLING') syncCyclingControls();
4624
5258
  const nextAnchor = getMountedSvelteComponentAnchor(svelteComponentSession);
4625
5259
  if (nextAnchor) {
@@ -4653,6 +5287,7 @@
4653
5287
  function teardownSvelteComponentSession(restoreOriginal) {
4654
5288
  if (!svelteComponentSession) return;
4655
5289
  const { wrapperEl, detachedOriginal, runtime, mountedInstance } = svelteComponentSession;
5290
+ removeSvelteComponentVariantStyle(svelteComponentSession);
4656
5291
  if (mountedInstance && runtime?.unmount) {
4657
5292
  try { runtime.unmount(mountedInstance); } catch { /* non-fatal */ }
4658
5293
  }
@@ -4692,6 +5327,7 @@
4692
5327
  if (mountedInstance && runtime?.unmount) {
4693
5328
  try { runtime.unmount(mountedInstance); } catch { /* non-fatal */ }
4694
5329
  }
5330
+ removeSvelteComponentVariantStyle(svelteComponentSession);
4695
5331
  wrapperEl.parentElement.replaceChild(committed, wrapperEl);
4696
5332
  svelteComponentSession = null;
4697
5333
  svelteRuntimePromise = null;
@@ -4715,7 +5351,7 @@
4715
5351
  previewFile: manifestPath,
4716
5352
  previewMode: 'svelte-component',
4717
5353
  });
4718
- if (state !== 'CYCLING') state = 'GENERATING';
5354
+ if (state !== 'CYCLING') setLiveState('GENERATING');
4719
5355
 
4720
5356
  const existingWrapper = document.querySelector('[data-impeccable-variants="' + sessionId + '"]');
4721
5357
  if (existingWrapper && svelteComponentSession?.sessionId === sessionId) {
@@ -4725,7 +5361,7 @@
4725
5361
  expectedVariants = arrivedVariants;
4726
5362
  visibleVariant = visibleVariant > 0 && visibleVariant <= arrivedVariants ? visibleVariant : 1;
4727
5363
  await mountSvelteComponentVariant(visibleVariant || 1);
4728
- state = 'CYCLING';
5364
+ setLiveState('CYCLING');
4729
5365
  showOrUpdateCyclingBar();
4730
5366
  saveSession();
4731
5367
  return;
@@ -4741,15 +5377,8 @@
4741
5377
  visibleVariant = visibleVariant > 0 && visibleVariant <= arrivedVariants
4742
5378
  ? visibleVariant
4743
5379
  : (savedVisibleVariant > 0 && savedVisibleVariant <= arrivedVariants ? savedVisibleVariant : 1);
4744
- selectedElement = document.body;
4745
- state = 'GENERATING';
4746
- recoveryWaitingForAnchor = true;
4747
- showBar('generating');
4748
- startScrollTracking();
4749
- saveSession();
4750
- queueCheckpoint('svelte_component_anchor_missing');
5380
+ enterRecoveryWaitingForAnchor({ checkpointReason: 'svelte_component_anchor_missing', trackScroll: true });
4751
5381
  waitForSvelteComponentTargetAndRetry({ manifestPath, sessionId, manifest });
4752
- showToast('Variants ready. Reveal the selected element to resume.', 15000);
4753
5382
  return;
4754
5383
  }
4755
5384
 
@@ -4812,7 +5441,7 @@
4812
5441
  }
4813
5442
 
4814
5443
  selectedElement = mountTarget.firstElementChild || mountTarget;
4815
- state = 'CYCLING';
5444
+ setLiveState('CYCLING');
4816
5445
  recoveryWaitingForAnchor = false;
4817
5446
  hideShaderOverlay();
4818
5447
  showOrUpdateCyclingBar();
@@ -4861,6 +5490,7 @@
4861
5490
  hideShaderOverlay();
4862
5491
  if (variantObserver) { variantObserver.disconnect(); variantObserver = null; }
4863
5492
  if (pendingSvelteComponentRetryObserver) { pendingSvelteComponentRetryObserver.disconnect(); pendingSvelteComponentRetryObserver = null; }
5493
+ if (pendingVariantAnchorRetryObserver) { pendingVariantAnchorRetryObserver.disconnect(); pendingVariantAnchorRetryObserver = null; }
4864
5494
  stopScrollLock();
4865
5495
  clearSession();
4866
5496
  clearHandled();
@@ -4870,7 +5500,7 @@
4870
5500
  arrivedVariants = 0;
4871
5501
  visibleVariant = 0;
4872
5502
  selectedElement = null;
4873
- state = 'PICKING';
5503
+ setLiveState('PICKING');
4874
5504
  hideBar();
4875
5505
  if (message) showToast(message, 5000);
4876
5506
  }
@@ -4901,7 +5531,7 @@
4901
5531
  const block = startIdx !== -1 && endIdx !== -1 && endIdx > startIdx
4902
5532
  ? html.slice(startIdx + startMark.length, endIdx).trim()
4903
5533
  : html;
4904
- const doc = parser.parseFromString(block, 'text/html');
5534
+ const doc = parser.parseFromString(normalizeSourceFallbackBlock(block, filePath), 'text/html');
4905
5535
  srcWrapper = doc.querySelector('[data-impeccable-variants="' + sessionId + '"]');
4906
5536
  if (!srcWrapper) {
4907
5537
  console.warn('[impeccable] Variant wrapper not found in source file.');
@@ -4919,21 +5549,26 @@
4919
5549
  const origContent = srcWrapper.querySelector('[data-impeccable-variant="original"] > :first-child');
4920
5550
  if (!origContent) return;
4921
5551
 
4922
- const liveEl = findLiveElementForOriginalMarkup(origContent.outerHTML);
5552
+ const liveEl = resolveLiveInjectionAnchor(origContent.outerHTML);
4923
5553
  if (!liveEl) {
4924
5554
  console.warn('[impeccable] Could not find original element in live DOM.');
4925
- selectedElement = document.body;
4926
- recoveryWaitingForAnchor = true;
4927
- state = 'GENERATING';
4928
- showBar('generating');
4929
- saveSession();
4930
- showToast('Variants ready. Reveal the selected element to resume.', 15000);
5555
+ enterRecoveryWaitingForAnchor({
5556
+ filePath,
5557
+ sessionId,
5558
+ srcWrapper,
5559
+ checkpointReason: 'variant_anchor_missing',
5560
+ trackScroll: false,
5561
+ });
4931
5562
  return;
4932
5563
  }
4933
5564
 
4934
5565
  liveEl.parentElement.replaceChild(wrapper, liveEl);
4935
5566
  }
4936
5567
  recoveryWaitingForAnchor = false;
5568
+ if (pendingVariantAnchorRetryObserver) {
5569
+ pendingVariantAnchorRetryObserver.disconnect();
5570
+ pendingVariantAnchorRetryObserver = null;
5571
+ }
4937
5572
 
4938
5573
  // Update state: count variants, preserving the user's current variant
4939
5574
  // when a late HMR/source reinjection lands after they have cycled.
@@ -4954,7 +5589,7 @@
4954
5589
  // Update selectedElement to the visible variant's content
4955
5590
  selectedElement = pickVariantContent(wrapper, visibleVariant) || wrapper.parentElement;
4956
5591
 
4957
- state = 'CYCLING';
5592
+ setLiveState('CYCLING');
4958
5593
  recoveryWaitingForAnchor = false;
4959
5594
  hideShaderOverlay();
4960
5595
  showOrUpdateCyclingBar();
@@ -4970,6 +5605,44 @@
4970
5605
  });
4971
5606
  }
4972
5607
 
5608
+ function normalizeSourceFallbackBlock(block, filePath) {
5609
+ if (!/\.[cm]?[jt]sx$/i.test(String(filePath || ''))) return block;
5610
+ return String(block)
5611
+ .replace(
5612
+ /<style\b([^>]*)>\s*\{\s*`([\s\S]*?)`\s*\}\s*<\/style>/g,
5613
+ (_match, attrs, css) => '<style' + attrs + '>' + css + '</style>',
5614
+ )
5615
+ .replace(/\bclassName\s*=\s*\{\s*`([^`]*?)`\s*\}/g, (_match, value) => {
5616
+ const literalClasses = value.replace(/\$\{[^}]*\}/g, ' ').replace(/\s+/g, ' ').trim();
5617
+ return literalClasses ? 'class="' + escapeHtml(literalClasses) + '"' : '';
5618
+ })
5619
+ .replace(/\bclassName\s*=/g, 'class=')
5620
+ .replace(/\sstyle=\{\{([\s\S]*?)\}\}/g, (_match, body) => {
5621
+ const css = jsxStyleObjectToCss(body);
5622
+ return css ? ' style="' + escapeHtml(css) + '"' : '';
5623
+ });
5624
+ }
5625
+
5626
+ function jsxStyleObjectToCss(body) {
5627
+ const declarations = [];
5628
+ const re = /(["'][^"']+["']|[A-Za-z_$][\w$-]*)\s*:\s*(?:"([^"]*)"|'([^']*)'|(-?\d+(?:\.\d+)?))/g;
5629
+ let match;
5630
+ while ((match = re.exec(String(body || '')))) {
5631
+ const prop = jsxStylePropToCss(match[1]);
5632
+ const value = match[2] ?? match[3] ?? match[4] ?? '';
5633
+ if (!prop || value === '') continue;
5634
+ declarations.push(prop + ': ' + value);
5635
+ }
5636
+ return declarations.join('; ');
5637
+ }
5638
+
5639
+ function jsxStylePropToCss(prop) {
5640
+ let out = String(prop || '').trim().replace(/^["']|["']$/g, '');
5641
+ if (!out) return '';
5642
+ if (out.startsWith('--')) return out;
5643
+ return out.replace(/[A-Z]/g, (ch) => '-' + ch.toLowerCase()).replace(/^-ms-/, '-ms-');
5644
+ }
5645
+
4973
5646
  function buildSvelteExpressionTextMap(sourceOriginal, liveOriginal) {
4974
5647
  const map = new Map();
4975
5648
  if (!sourceOriginal || !liveOriginal) return map;
@@ -5143,10 +5816,22 @@
5143
5816
 
5144
5817
  try { history.scrollRestoration = 'manual'; } catch {}
5145
5818
 
5146
- const prevHtmlAnchor = document.documentElement.style.overflowAnchor;
5147
- const prevBodyAnchor = document.body.style.overflowAnchor;
5148
- document.documentElement.style.overflowAnchor = 'none';
5149
- document.body.style.overflowAnchor = 'none';
5819
+ // Suppress the browser's scroll-anchoring on the scroll root so it can't
5820
+ // fight our manual scroll correction. Apply this as a stylesheet rule, not
5821
+ // as inline `style` on <html>/<body>: those elements are server-rendered by
5822
+ // frameworks like Next.js App Router, and mutating their inline style makes
5823
+ // React 19 report a hydration mismatch on the next Fast-Refresh re-render.
5824
+ // A <style> rule has the same computed effect without touching any hydrated
5825
+ // element's attributes. Like the inline version, it is recreated on every
5826
+ // startScrollLock call, so reload survival (driven by the persisted scroll
5827
+ // key) is unaffected.
5828
+ let anchorLockStyle = document.getElementById(SCROLL_ANCHOR_LOCK_ID);
5829
+ if (!anchorLockStyle) {
5830
+ anchorLockStyle = document.createElement('style');
5831
+ anchorLockStyle.id = SCROLL_ANCHOR_LOCK_ID;
5832
+ anchorLockStyle.textContent = 'html,body{overflow-anchor:none !important;}';
5833
+ (document.head || document.documentElement).appendChild(anchorLockStyle);
5834
+ }
5150
5835
 
5151
5836
  const correct = (why) => {
5152
5837
  scrollLockRaf = null;
@@ -5181,8 +5866,7 @@
5181
5866
 
5182
5867
  scrollLockAbort = new AbortController();
5183
5868
  scrollLockAbort.signal.addEventListener('abort', () => {
5184
- document.documentElement.style.overflowAnchor = prevHtmlAnchor;
5185
- document.body.style.overflowAnchor = prevBodyAnchor;
5869
+ document.getElementById(SCROLL_ANCHOR_LOCK_ID)?.remove();
5186
5870
  }, { once: true });
5187
5871
  const sig = { signal: scrollLockAbort.signal };
5188
5872
  // Track whether the most recent scroll came from a user gesture. We
@@ -5322,7 +6006,7 @@
5322
6006
  if (expected > 0) expectedVariants = expected;
5323
6007
 
5324
6008
  if (arrivedVariants >= expectedVariants && expectedVariants > 0) {
5325
- state = 'CYCLING';
6009
+ setLiveState('CYCLING');
5326
6010
  recoveryWaitingForAnchor = false;
5327
6011
  hideShaderOverlay();
5328
6012
  if (wrapper.dataset.impeccableMode === 'insert') finalizeInsertSession();
@@ -5408,7 +6092,8 @@
5408
6092
  syncAgentPollingUi(!!msg.agentPolling);
5409
6093
  startAgentStatusPoll();
5410
6094
  restoreFromActiveSessions(msg.activeSessions, 'sse_connected');
5411
- if (state === 'IDLE' && (pickActive || insertActive)) state = 'PICKING';
6095
+ if (state === 'IDLE' && (pickActive || insertActive)) setLiveState('PICKING');
6096
+ syncPageInteractionCursor();
5412
6097
  syncPageChatFocus('sse-connected');
5413
6098
  break;
5414
6099
  case 'agent_polling':
@@ -5434,7 +6119,7 @@
5434
6119
  // Variants already arrived via HMR → normal transition.
5435
6120
  if (arrivedVariants >= expectedVariants && expectedVariants > 0) {
5436
6121
  if (state === 'GENERATING') {
5437
- state = 'CYCLING';
6122
+ setLiveState('CYCLING');
5438
6123
  showOrUpdateCyclingBar();
5439
6124
  disableInlineEdit();
5440
6125
  refreshParamsPanel();
@@ -5443,7 +6128,11 @@
5443
6128
  }
5444
6129
  // Source fallback when HMR did not land variants in this tab.
5445
6130
  if (msg.file && msg.id && state === 'GENERATING' && msg.id === currentSessionId) {
5446
- injectVariantsFromSource(msg.file, msg.id);
6131
+ setTimeout(() => {
6132
+ if (arrivedVariants >= expectedVariants && expectedVariants > 0) return;
6133
+ if (state !== 'GENERATING' || msg.id !== currentSessionId) return;
6134
+ injectVariantsFromSource(msg.file, msg.id);
6135
+ }, 750);
5447
6136
  break;
5448
6137
  }
5449
6138
  // Variants are in source but not in the DOM yet. Common when the
@@ -5480,7 +6169,7 @@
5480
6169
  case 'error':
5481
6170
  if (pendingAcceptedSession?.id && msg.id === pendingAcceptedSession.id) {
5482
6171
  pendingAcceptedSession = null;
5483
- state = 'CYCLING';
6172
+ setLiveState('CYCLING');
5484
6173
  updateBarContent('cycling');
5485
6174
  showToast('Could not complete accept cleanup. Try Accept again.', 5000);
5486
6175
  break;
@@ -5490,7 +6179,7 @@
5490
6179
  showToast('Error: ' + msg.message, 5000);
5491
6180
  hideBar();
5492
6181
  renderEditBadge('hidden');
5493
- state = 'PICKING';
6182
+ setLiveState('PICKING');
5494
6183
  break;
5495
6184
  }
5496
6185
  };
@@ -5528,15 +6217,18 @@
5528
6217
  // transient disconnect as an explicit discard.
5529
6218
  selectedElement = null;
5530
6219
  selectedAction = 'impeccable';
5531
- state = recoveryState;
6220
+ setLiveState(recoveryState);
5532
6221
  if (currentSessionId) saveSession();
5533
6222
  }
5534
6223
 
5535
6224
  function sendEvent(msg, opts) {
5536
6225
  msg.token = TOKEN;
5537
6226
  function handleFailure(err) {
5538
- console.error('[impeccable] Failed to send event:', err);
5539
- if (opts && opts.throwOnError) throw err;
6227
+ if (opts && opts.throwOnError) {
6228
+ console.error('[impeccable] Failed to send event:', err);
6229
+ throw err;
6230
+ }
6231
+ console.debug('[impeccable] Dropped optional live event:', err);
5540
6232
  return null;
5541
6233
  }
5542
6234
  return fetch('http://localhost:' + PORT + '/events', {
@@ -5667,15 +6359,7 @@
5667
6359
  && !selectedElement.contains(e.target)
5668
6360
  ) {
5669
6361
  if (configureKind === 'insert') { cancelInsertConfigure(); return; }
5670
- hideBar();
5671
- stopScrollTracking();
5672
- hideAnnotOverlay();
5673
- clearAnnotations();
5674
- renderEditBadge('hidden');
5675
- state = 'PICKING';
5676
- hoveredElement = null;
5677
- hideHighlight();
5678
- syncPageChatFocus('configure-outside-click');
6362
+ exitConfigureToPicking('configure-outside-click', { clearHover: true });
5679
6363
  return;
5680
6364
  }
5681
6365
  if (state === 'PICKING' && insertActive) {
@@ -5692,13 +6376,12 @@
5692
6376
  hideInsertLine();
5693
6377
  configureKind = 'insert';
5694
6378
  selectedElement = placeholder;
5695
- state = 'CONFIGURING';
6379
+ setLiveState('CONFIGURING');
5696
6380
  hideHighlight();
5697
6381
  clearAnnotations();
5698
6382
  showAnnotOverlay(placeholder);
5699
6383
  showBar('configure');
5700
6384
  startScrollTracking();
5701
- syncPageInteractionCursor();
5702
6385
  return;
5703
6386
  }
5704
6387
  if (state !== 'PICKING' || !pickActive) return;
@@ -5711,7 +6394,7 @@
5711
6394
  e.preventDefault();
5712
6395
  e.stopPropagation();
5713
6396
  selectedElement = hoveredElement;
5714
- state = 'CONFIGURING';
6397
+ setLiveState('CONFIGURING');
5715
6398
  showHighlight(selectedElement);
5716
6399
  clearAnnotations();
5717
6400
  showAnnotOverlay(selectedElement);
@@ -5796,6 +6479,16 @@
5796
6479
  sendEvent({ type: 'prefetch', pageUrl: path });
5797
6480
  }
5798
6481
 
6482
+ function shouldPassthroughElementNav(deepActive, e) {
6483
+ if (!deepActive || !own(deepActive)) return false;
6484
+ if (e.key !== 'ArrowUp' && e.key !== 'ArrowDown') return false;
6485
+ if (!/^(INPUT|TEXTAREA)$/.test(deepActive.tagName || '')) return false;
6486
+ if (deepActive.value) return false;
6487
+ if (deepActive.id === PREFIX + '-input' && state === 'CONFIGURING') return true;
6488
+ if (deepActive.id === PREFIX + '-page-chat-input' && state === 'PICKING') return true;
6489
+ return false;
6490
+ }
6491
+
5799
6492
  function handleKeyDown(e) {
5800
6493
  // When the annotation input is focused, let it handle its own keys.
5801
6494
  if (annotEditing && annotEditing.input && e.target === annotEditing.input) return;
@@ -5804,13 +6497,17 @@
5804
6497
  deepActive
5805
6498
  && own(deepActive)
5806
6499
  && /^(INPUT|TEXTAREA|SELECT)$/.test(deepActive.tagName || '')
6500
+ && !shouldPassthroughElementNav(deepActive, e)
5807
6501
  ) {
5808
6502
  return;
5809
6503
  }
6504
+ if (isPageEditableElement(deepActive) && !isInlineEditActive(deepActive)) {
6505
+ return;
6506
+ }
5810
6507
  // While a contenteditable text-leaf is focused, let the browser handle
5811
6508
  // all keys except Escape. Escape cancels the current edit (restores
5812
6509
  // original text) and blurs without saving, staying in CONFIGURING.
5813
- if (e.target.isContentEditable && inlineEditRows.some((r) => r.el === e.target)) {
6510
+ if (e.target.isContentEditable && isInlineEditActive(e.target)) {
5814
6511
  if (e.key !== 'Escape') return;
5815
6512
  e.preventDefault();
5816
6513
  e.stopPropagation();
@@ -5842,14 +6539,15 @@
5842
6539
  if (state === 'EDITING') { cancelEditing(); return; }
5843
6540
  if (state === 'CONFIGURING') {
5844
6541
  if (configureKind === 'insert') { cancelInsertConfigure(); return; }
5845
- disableInlineEdit(); hideBar(); stopScrollTracking(); hideAnnotOverlay(); clearAnnotations(); renderEditBadge('hidden'); state = 'PICKING'; syncPageChatFocus('escape-from-configure'); return;
6542
+ exitConfigureToPicking('escape-from-configure');
6543
+ return;
5846
6544
  }
5847
6545
  if (state === 'CYCLING') { handleDiscard(); return; }
5848
6546
  if (state === 'SAVING' || state === 'CONFIRMED') return; // don't interrupt
5849
6547
  if (state === 'PICKING') {
5850
6548
  if (insertActive) toggleInsert();
5851
6549
  else if (pickActive) togglePick();
5852
- else { hideHighlight(); state = 'IDLE'; }
6550
+ else { hideHighlight(); setLiveState('IDLE'); }
5853
6551
  return;
5854
6552
  }
5855
6553
  }
@@ -5873,7 +6571,7 @@
5873
6571
  } else if (e.key === 'Enter') {
5874
6572
  e.preventDefault();
5875
6573
  selectedElement = hoveredElement;
5876
- state = 'CONFIGURING';
6574
+ setLiveState('CONFIGURING');
5877
6575
  showHighlight(selectedElement);
5878
6576
  clearAnnotations();
5879
6577
  showAnnotOverlay(selectedElement);
@@ -5934,6 +6632,7 @@
5934
6632
  // screenshot is uploaded (or capture fails - we still emit, just without
5935
6633
  // screenshotPath).
5936
6634
  const elForCapture = selectedElement;
6635
+ pickedAnchorSnapshot = buildPickedAnchorSnapshot(elForCapture);
5937
6636
  const captureRect = elForCapture.getBoundingClientRect();
5938
6637
  const snapshot = {
5939
6638
  comments: annotState.comments.map(c => ({ x: c.x, y: c.y, text: c.text })),
@@ -5954,7 +6653,7 @@
5954
6653
  hideAnnotOverlay();
5955
6654
  clearAnnotations();
5956
6655
 
5957
- state = 'GENERATING';
6656
+ setLiveState('GENERATING');
5958
6657
  // Disable the Edit badge: starting a manual text edit mid-generation would
5959
6658
  // conflict with the variant wrap that's about to land in the same DOM
5960
6659
  // region. Only swap if the badge was visible - picked elements with no
@@ -5979,7 +6678,7 @@
5979
6678
  clearInsertPicking();
5980
6679
  configureKind = 'replace';
5981
6680
  selectedElement = null;
5982
- state = insertActive ? 'PICKING' : 'IDLE';
6681
+ setLiveState(insertActive ? 'PICKING' : 'IDLE');
5983
6682
  hideHighlight();
5984
6683
  syncPageChatFocus('insert-configure-cancel');
5985
6684
  }
@@ -6029,7 +6728,7 @@
6029
6728
  hideAnnotOverlay();
6030
6729
  clearAnnotations();
6031
6730
 
6032
- state = 'GENERATING';
6731
+ setLiveState('GENERATING');
6033
6732
  showBar('generating');
6034
6733
  startScrollTracking();
6035
6734
  saveSession();
@@ -6787,7 +7486,7 @@ void main() {
6787
7486
  || acceptWrapper?.dataset?.impeccablePreview === 'svelte-component';
6788
7487
  const acceptedSnapshot = snapshotAcceptedVariantDom(acceptedSessionId, acceptedVariant);
6789
7488
 
6790
- state = 'SAVING';
7489
+ setLiveState('SAVING');
6791
7490
  updateBarContent('saving');
6792
7491
  pendingAcceptedSession = {
6793
7492
  id: acceptedSessionId,
@@ -6802,7 +7501,7 @@ void main() {
6802
7501
  .then(() => {})
6803
7502
  .catch(() => {
6804
7503
  if (pendingAcceptedSession?.id === acceptedSessionId) pendingAcceptedSession = null;
6805
- state = 'CYCLING';
7504
+ setLiveState('CYCLING');
6806
7505
  showOrUpdateCyclingBar();
6807
7506
  showToast('Could not confirm accept with the live server. Session kept for recovery; try Accept again.', 5000);
6808
7507
  });
@@ -6821,7 +7520,7 @@ void main() {
6821
7520
  if (pending.isSvelteComponent) {
6822
7521
  commitAcceptedSvelteComponentToDom(pending.id);
6823
7522
  }
6824
- state = 'CONFIRMED';
7523
+ setLiveState('CONFIRMED');
6825
7524
  updateBarContent('confirmed');
6826
7525
  scheduleAcceptCleanup(pending);
6827
7526
  return true;
@@ -6829,7 +7528,14 @@ void main() {
6829
7528
 
6830
7529
  function scheduleAcceptCleanup(accepted) {
6831
7530
  setTimeout(function() {
6832
- if (!accepted?.isSvelteComponent) ensureAcceptedDomClean(accepted);
7531
+ if (!accepted?.isSvelteComponent && !acceptedDomAlreadyClean(accepted)) {
7532
+ setTimeout(function() {
7533
+ if (pendingAcceptedSession?.id !== accepted?.id) return;
7534
+ if (!accepted?.isSvelteComponent) ensureAcceptedDomClean(accepted);
7535
+ cleanupAcceptedSession();
7536
+ }, 1800);
7537
+ return;
7538
+ }
6833
7539
  cleanupAcceptedSession();
6834
7540
  }, 1200);
6835
7541
  }
@@ -6858,29 +7564,42 @@ void main() {
6858
7564
  function acceptedDomAlreadyClean(pending) {
6859
7565
  if (!pending?.acceptedSelector) return false;
6860
7566
  const matches = [...document.querySelectorAll(pending.acceptedSelector)];
6861
- return matches.some((el) => !el.closest('[data-impeccable-variants],[data-impeccable-variant]'));
7567
+ return matches.length > 0
7568
+ && matches.every((el) => !el.closest('[data-impeccable-variants],[data-impeccable-variant],[data-impeccable-carbonize]'));
6862
7569
  }
6863
7570
 
6864
7571
  function ensureAcceptedDomClean(pending) {
7572
+ if (acceptedDomAlreadyClean(pending)) return;
6865
7573
  const sessionId = pending?.id;
6866
7574
  const variantId = pending?.variant;
6867
- const wrapper = document.querySelector('[data-impeccable-variants="' + sessionId + '"]');
6868
- const accepted = wrapper?.querySelector?.('[data-impeccable-variant="' + variantId + '"]');
6869
- if (!wrapper) {
7575
+ const wrappers = findAcceptedRuntimeWrappers(sessionId);
7576
+ if (wrappers.length === 0) {
6870
7577
  restoreAcceptedDomFromSnapshot(pending);
6871
7578
  return;
6872
7579
  }
6873
- if (!accepted) {
7580
+ for (const wrapper of wrappers) {
7581
+ if (!wrapper?.isConnected) continue;
7582
+ const accepted = wrapper.querySelector?.('[data-impeccable-variant="' + variantId + '"]');
7583
+ if (!accepted) {
7584
+ wrapper.remove();
7585
+ continue;
7586
+ }
7587
+ const parent = wrapper.parentElement;
7588
+ if (!parent) continue;
7589
+ while (accepted.firstChild) {
7590
+ parent.insertBefore(accepted.firstChild, wrapper);
7591
+ }
6874
7592
  wrapper.remove();
6875
- restoreAcceptedDomFromSnapshot(pending);
6876
- return;
6877
7593
  }
6878
- const parent = wrapper.parentElement;
6879
- if (!parent) return;
6880
- while (accepted.firstChild) {
6881
- parent.insertBefore(accepted.firstChild, wrapper);
6882
- }
6883
- wrapper.remove();
7594
+ if (!acceptedDomAlreadyClean(pending)) restoreAcceptedDomFromSnapshot(pending);
7595
+ }
7596
+
7597
+ function findAcceptedRuntimeWrappers(sessionId) {
7598
+ if (!sessionId) return [];
7599
+ return [...new Set([
7600
+ ...document.querySelectorAll('[data-impeccable-variants="' + sessionId + '"]'),
7601
+ ...document.querySelectorAll('[data-impeccable-carbonize="' + sessionId + '"]'),
7602
+ ])];
6884
7603
  }
6885
7604
 
6886
7605
  function restoreAcceptedDomFromSnapshot(pending) {
@@ -6925,7 +7644,7 @@ void main() {
6925
7644
  selectedAction = 'impeccable';
6926
7645
  pendingAcceptedSession = null;
6927
7646
  renderEditBadge('hidden');
6928
- state = 'PICKING';
7647
+ setLiveState('PICKING');
6929
7648
  }
6930
7649
 
6931
7650
  function commitAcceptedVariantToDom(sessionId, variantId) {
@@ -6977,6 +7696,7 @@ void main() {
6977
7696
  currentPreviewFile = null;
6978
7697
  currentPreviewMode = null;
6979
7698
  recoveryWaitingForAnchor = false;
7699
+ pickedAnchorSnapshot = null;
6980
7700
  }
6981
7701
 
6982
7702
  function rememberSessionFileMeta(meta = {}) {
@@ -7001,6 +7721,7 @@ void main() {
7001
7721
  if (!saved) return;
7002
7722
  rememberSessionFileMeta(saved);
7003
7723
  if (saved.insertPlaceholder) insertPlaceholderSnapshot = saved.insertPlaceholder;
7724
+ if (saved.pickedAnchor) pickedAnchorSnapshot = saved.pickedAnchor;
7004
7725
  if (saved.action) selectedAction = saved.action;
7005
7726
  if (saved.count) selectedCount = saved.count;
7006
7727
  if (saved.previewMode) currentPreviewMode = saved.previewMode;
@@ -7067,9 +7788,10 @@ void main() {
7067
7788
  || clampVariantIndex(serverSession?.visibleVariant, arrivedVariants || expectedVariants)
7068
7789
  || (arrivedVariants > 0 ? 1 : 0);
7069
7790
 
7070
- selectedElement = document.body;
7071
- state = 'GENERATING';
7072
- recoveryWaitingForAnchor = true;
7791
+ const restoredAnchor = findLiveElementFromAnchorSnapshot(pickedAnchorSnapshot);
7792
+ selectedElement = restoredAnchor || document.body;
7793
+ setLiveState('GENERATING');
7794
+ recoveryWaitingForAnchor = !restoredAnchor;
7073
7795
  showBar('generating');
7074
7796
  startScrollTracking();
7075
7797
  if (variantObserver) variantObserver.disconnect();
@@ -7085,7 +7807,6 @@ void main() {
7085
7807
  return true;
7086
7808
  }
7087
7809
 
7088
- showToast('Variants ready. Reveal the selected element to resume.', 15000);
7089
7810
  return true;
7090
7811
  }
7091
7812
 
@@ -7114,6 +7835,7 @@ void main() {
7114
7835
  pageUrl: location.pathname,
7115
7836
  paramValues: { ...paramsCurrentValues },
7116
7837
  insertPlaceholder: insertPlaceholderSnapshot || undefined,
7838
+ pickedAnchor: pickedAnchorSnapshot || undefined,
7117
7839
  });
7118
7840
  }
7119
7841
 
@@ -7173,6 +7895,7 @@ void main() {
7173
7895
  hideHighlight();
7174
7896
  stopScrollTracking();
7175
7897
  if (variantObserver) { variantObserver.disconnect(); variantObserver = null; }
7898
+ if (pendingVariantAnchorRetryObserver) { pendingVariantAnchorRetryObserver.disconnect(); pendingVariantAnchorRetryObserver = null; }
7176
7899
  stopScrollLock();
7177
7900
  clearScrollY();
7178
7901
  finalizeInsertSession();
@@ -7182,15 +7905,21 @@ void main() {
7182
7905
  currentSessionId = null;
7183
7906
  selectedAction = 'impeccable';
7184
7907
  renderEditBadge('hidden');
7185
- state = 'PICKING';
7908
+ setLiveState('PICKING');
7186
7909
  }
7187
7910
 
7188
7911
  //
7189
7912
  // Toast
7190
7913
  //
7191
7914
 
7915
+ function dismissToast() {
7916
+ if (!toastEl) return;
7917
+ toastEl.remove();
7918
+ toastEl = null;
7919
+ }
7920
+
7192
7921
  function showToast(message, duration) {
7193
- if (toastEl) toastEl.remove();
7922
+ dismissToast();
7194
7923
  // Stack the toast above the global bar (which sits at bottom:14px) so
7195
7924
  // the two never overlap. Read the bar's actual rect - its height varies
7196
7925
  // with hover-expanded labels - and fall back to a sensible default
@@ -7279,7 +8008,7 @@ void main() {
7279
8008
  : (savedVisibleVariant > 0 && savedVisibleVariant <= arrivedVariants ? savedVisibleVariant : 1);
7280
8009
  selectedElement = resolveSvelteComponentAnchor()
7281
8010
  || wrapper.parentElement;
7282
- state = 'CYCLING';
8011
+ setLiveState('CYCLING');
7283
8012
  hideShaderOverlay();
7284
8013
  showBar('cycling');
7285
8014
  startScrollTracking();
@@ -7315,7 +8044,7 @@ void main() {
7315
8044
  const isInsert = wrapper.dataset.impeccableMode === 'insert';
7316
8045
  const visEl = visibleVariant > 0 ? pickVariantContent(wrapper, visibleVariant) : null;
7317
8046
  const origEl = pickVariantContent(wrapper, 'original');
7318
- state = resumedState;
8047
+ setLiveState(resumedState);
7319
8048
  if (isInsert && resumedState === 'GENERATING' && arrivedVariants === 0) {
7320
8049
  selectedElement = ensureInsertPlaceholder() || findInsertAnchorInDom() || wrapper;
7321
8050
  } else {
@@ -7472,16 +8201,18 @@ void main() {
7472
8201
  let voiceInterimBase = '';
7473
8202
  /** @type {{ mode: 'steer'|'configure', input: HTMLInputElement, submit: () => void, beforeStart?: () => void } | null} */
7474
8203
  let voiceCtx = null;
7475
- const PAGE_CHAT_COLLAPSED_W = '88px';
8204
+ const PAGE_CHAT_COLLAPSED_W = '104px';
7476
8205
  const PAGE_CHAT_PROCESSING_W = '76px';
8206
+ const PAGE_CHAT_PLACEHOLDER_COLLAPSED = 'Steer…';
8207
+ const PAGE_CHAT_PLACEHOLDER_EXPANDED = 'Steer the page…';
7477
8208
  const STEER_AWAIT_TIMEOUT_MS = 120000;
7478
8209
  const AGENT_STATUS_POLL_MS = 5000;
7479
- const AGENT_DISCONNECTED_MARK = 'oklch(56% 0.032 82 / 0.78)';
8210
+ const AGENT_DISCONNECTED_MARK = 'oklch(62% 0 0 / 0.78)';
7480
8211
  const AGENT_DISCONNECTED_TIP = 'Agent disconnected - run live-poll.mjs to connect';
7481
8212
  const GLOBAL_BAR_SECTION_GAP = 8;
7482
8213
  const GLOBAL_BAR_INNER_GAP = 2;
7483
8214
  const GLOBAL_BAR_INNER_PAD_LEFT = 2;
7484
- const PAGE_CHAT_EXPANDED_W = 'min(280px, 38vw)';
8215
+ const PAGE_CHAT_EXPANDED_MAX_W = 280;
7485
8216
  const ICON_PAGE_CHAT =
7486
8217
  '<svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M21 15a2 2 0 0 1-2 2H7l-4 4V5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2z"/></svg>';
7487
8218
  const ICON_PAGE_VOICE =
@@ -7543,8 +8274,8 @@ void main() {
7543
8274
  // Neutral hairline for internal control borders / dividers (was a warm
7544
8275
  // gold rule that read as muddy champagne edges on the pill / input / count).
7545
8276
  hairline: 'oklch(92% 0 0 / 0.12)',
7546
- text: 'oklch(84% 0.035 82)',
7547
- textDim: 'oklch(63% 0.024 82)',
8277
+ text: 'oklch(91% 0 0)',
8278
+ textDim: 'oklch(72% 0 0)',
7548
8279
  accent: C.brand,
7549
8280
  accentSoft: C.brandSoft,
7550
8281
  exitHover: 'oklch(58% 0.15 35 / 0.18)',
@@ -7561,16 +8292,65 @@ void main() {
7561
8292
  return barPaletteForTheme(globalBarEl?.dataset.theme || detectPageTheme());
7562
8293
  }
7563
8294
 
8295
+ function globalBarModeToggles() {
8296
+ return [
8297
+ uiGetById(PREFIX + '-pick-toggle'),
8298
+ uiGetById(PREFIX + '-insert-toggle'),
8299
+ uiGetById(PREFIX + '-detect-toggle'),
8300
+ uiGetById(PREFIX + '-design-toggle'),
8301
+ ].filter(Boolean);
8302
+ }
8303
+
8304
+ function applyGlobalBarLabelState(expandInactive, forceCollapse = false) {
8305
+ globalBarModeToggles().forEach((toggle) => {
8306
+ if (forceCollapse) toggle._collapseLabel?.(true);
8307
+ else if (expandInactive || toggle.dataset.active === 'true') toggle._expandLabel?.();
8308
+ else toggle._collapseLabel?.();
8309
+ });
8310
+ }
8311
+
8312
+ function syncGlobalBarExpandedLabels(expanded = globalBarEl?.matches(':hover')) {
8313
+ const expandInactive = !!(expanded && !pageChatExpanded);
8314
+ applyGlobalBarLabelState(expandInactive, pageChatExpanded);
8315
+
8316
+ if (expandInactive && globalBarEl && globalBarEl.scrollWidth > window.innerWidth - 16) {
8317
+ applyGlobalBarLabelState(false);
8318
+ }
8319
+ }
8320
+
8321
+ function pageChatCollapsedWidthPx() {
8322
+ const parsed = parseFloat(PAGE_CHAT_COLLAPSED_W);
8323
+ return Number.isFinite(parsed) ? parsed : 104;
8324
+ }
8325
+
8326
+ function pageChatExpandedWidth() {
8327
+ if (!pageChatEl || !globalBarEl) return PAGE_CHAT_EXPANDED_MAX_W + 'px';
8328
+ const currentChatWidth = pageChatEl.getBoundingClientRect().width || pageChatCollapsedWidthPx();
8329
+ const barWidth = Math.max(globalBarEl.getBoundingClientRect().width || 0, globalBarEl.scrollWidth || 0);
8330
+ const nonChatWidth = Math.max(0, barWidth - currentChatWidth);
8331
+ const available = window.innerWidth - 16 - nonChatWidth;
8332
+ const next = Math.max(pageChatCollapsedWidthPx(), Math.min(PAGE_CHAT_EXPANDED_MAX_W, available));
8333
+ return Math.round(next) + 'px';
8334
+ }
8335
+
8336
+ function syncPageChatExpandedWidth() {
8337
+ if (!pageChatEl || !pageChatExpanded) return;
8338
+ pageChatEl.style.width = pageChatExpandedWidth();
8339
+ }
8340
+
7564
8341
  function syncPageChatChrome() {
7565
8342
  if (!pageChatEl) return;
7566
8343
  const P = pageChatPalette();
8344
+ const inputFocused = pageChatInput && activeElementDeep() === pageChatInput;
7567
8345
  pageChatEl.style.background = P.chatSurface;
7568
- pageChatEl.style.borderColor = steerLocked
7569
- ? P.patinaSoft
7570
- : (pageChatExpanded ? P.accentSoft : P.hairline);
8346
+ pageChatEl.style.borderColor = 'transparent';
7571
8347
  if (pageChatHint) pageChatHint.style.color = steerLocked ? P.patinaPale : P.textDim;
7572
8348
  const chatIcon = pageChatEl?.firstElementChild;
7573
- if (chatIcon) chatIcon.style.color = steerLocked ? P.patinaPale : P.textDim;
8349
+ if (chatIcon) {
8350
+ chatIcon.style.color = steerLocked
8351
+ ? P.patinaPale
8352
+ : (inputFocused || pageChatExpanded ? P.text : P.textDim);
8353
+ }
7574
8354
  if (pageChatInput) pageChatInput.style.color = P.text;
7575
8355
  if (pageChatVoiceBtn) {
7576
8356
  const listening = pageChatVoiceBtn.dataset.listening === 'true';
@@ -7593,6 +8373,21 @@ void main() {
7593
8373
  && !steerLocked;
7594
8374
  }
7595
8375
 
8376
+ function isPageEditableElement(el) {
8377
+ if (!el || own(el)) return false;
8378
+ if (/^(INPUT|TEXTAREA|SELECT)$/.test(el.tagName || '')) return true;
8379
+ return !!el.isContentEditable;
8380
+ }
8381
+
8382
+ function isInlineEditActive(el) {
8383
+ return !!el && inlineEditRows.some((r) => r.el === el);
8384
+ }
8385
+
8386
+ function isPageEditableActive() {
8387
+ const active = activeElementDeep();
8388
+ return isPageEditableElement(active) && !isInlineEditActive(active);
8389
+ }
8390
+
7596
8391
  function pageHasHostTextSelection() {
7597
8392
  const sel = window.getSelection?.();
7598
8393
  if (!sel || sel.isCollapsed) return false;
@@ -7606,6 +8401,7 @@ void main() {
7606
8401
  function shouldSteerAutoFocus() {
7607
8402
  return shouldFocusSteerChat()
7608
8403
  && !steerFocusSuspended
8404
+ && !isPageEditableActive()
7609
8405
  && performance.now() >= steerFocusPauseUntil;
7610
8406
  }
7611
8407
 
@@ -7741,24 +8537,44 @@ void main() {
7741
8537
  function syncPageChatFocusRing() {
7742
8538
  if (!pageChatEl || !pageChatInput) return;
7743
8539
  const focused = activeElementDeep() === pageChatInput;
8540
+ const typingReady = focused && !steerLocked;
7744
8541
  pageChatEl.dataset.inputFocused = focused ? 'true' : 'false';
7745
- const P = pageChatPalette();
7746
- pageChatEl.style.borderColor = steerLocked
7747
- ? P.patinaSoft
7748
- : (pageChatExpanded ? P.accentSoft : P.hairline);
7749
8542
  pageChatEl.style.boxShadow = 'none';
7750
- if (pageChatHint) {
7751
- pageChatHint.style.color = steerLocked
7752
- ? P.patinaPale
7753
- : ((!pageChatExpanded && focused) ? P.patinaPale : P.textDim);
8543
+
8544
+ if (pageChatExpanded) {
8545
+ pageChatInput.placeholder = PAGE_CHAT_PLACEHOLDER_EXPANDED;
8546
+ pageChatInput.style.width = '';
8547
+ pageChatInput.style.padding = '0 6px';
8548
+ pageChatInput.style.opacity = steerLocked ? '0.72' : '1';
8549
+ pageChatInput.style.pointerEvents = steerLocked ? 'none' : 'auto';
8550
+ return;
7754
8551
  }
7755
- if (!pageChatExpanded) {
7756
- pageChatInput.style.width = '0';
7757
- pageChatInput.style.padding = '0';
7758
- pageChatInput.style.opacity = '0';
7759
- pageChatInput.style.pointerEvents = focused ? 'auto' : 'none';
7760
- if (pageChatHint) pageChatHint.style.visibility = '';
8552
+
8553
+ if (typingReady) {
8554
+ // Collapsed type-to-steer: show the real input + caret instead of a
8555
+ // truncated patina "Steer" label with an invisible focused field.
8556
+ pageChatInput.placeholder = PAGE_CHAT_PLACEHOLDER_COLLAPSED;
8557
+ if (pageChatHint) {
8558
+ pageChatHint.style.display = 'none';
8559
+ pageChatHint.style.opacity = '0';
8560
+ }
8561
+ pageChatInput.style.width = '';
8562
+ pageChatInput.style.padding = '0 4px';
8563
+ pageChatInput.style.opacity = '1';
8564
+ pageChatInput.style.pointerEvents = 'auto';
8565
+ return;
8566
+ }
8567
+
8568
+ pageChatInput.placeholder = PAGE_CHAT_PLACEHOLDER_COLLAPSED;
8569
+ if (pageChatHint) {
8570
+ pageChatHint.style.display = '';
8571
+ pageChatHint.style.opacity = '1';
8572
+ pageChatHint.style.visibility = '';
7761
8573
  }
8574
+ pageChatInput.style.width = '0';
8575
+ pageChatInput.style.padding = '0';
8576
+ pageChatInput.style.opacity = '0';
8577
+ pageChatInput.style.pointerEvents = 'none';
7762
8578
  }
7763
8579
 
7764
8580
  function focusSteerChat(reason) {
@@ -7778,6 +8594,7 @@ void main() {
7778
8594
  try { window.focus(); } catch { /* embed may block */ }
7779
8595
  try { pageChatInput.focus({ preventScroll: true }); } catch { pageChatInput.focus(); }
7780
8596
  syncPageChatFocusRing();
8597
+ syncPageChatChrome();
7781
8598
  steerFocusLog('focusSteerChat result', {
7782
8599
  reason,
7783
8600
  before: steerFocusTargetLabel(before),
@@ -7822,8 +8639,10 @@ void main() {
7822
8639
  if (!pageChatEl || !pageChatInput) return false;
7823
8640
  pageChatExpanded = true;
7824
8641
  pageChatEl.dataset.expanded = 'true';
7825
- pageChatEl.style.width = PAGE_CHAT_EXPANDED_W;
8642
+ syncGlobalBarExpandedLabels(false);
8643
+ pageChatEl.style.width = pageChatExpandedWidth();
7826
8644
  pageChatEl.style.cursor = steerLocked ? 'default' : 'text';
8645
+ pageChatInput.placeholder = PAGE_CHAT_PLACEHOLDER_EXPANDED;
7827
8646
  if (pageChatHint) {
7828
8647
  pageChatHint.style.display = 'none';
7829
8648
  pageChatHint.style.opacity = '0';
@@ -7835,6 +8654,20 @@ void main() {
7835
8654
  return true;
7836
8655
  }
7837
8656
 
8657
+ function armPageChatForTyping(opts = {}) {
8658
+ if (!pageChatEl || !pageChatInput || steerLocked) return false;
8659
+ const expand = opts.expand !== false;
8660
+ const focus = opts.focus !== false;
8661
+ if (expand && !pageChatExpanded) {
8662
+ preparePageChatInputForTyping();
8663
+ syncPageChatChrome();
8664
+ }
8665
+ if (focus) return focusPageChatInput('arm-page-chat');
8666
+ syncPageChatFocusRing();
8667
+ syncPageChatChrome();
8668
+ return true;
8669
+ }
8670
+
7838
8671
  function focusPageChatInput(reason) {
7839
8672
  if (!preparePageChatInputForTyping() || steerLocked) return false;
7840
8673
  try { pageChatInput.focus({ preventScroll: true }); } catch { pageChatInput.focus(); }
@@ -7902,7 +8735,7 @@ void main() {
7902
8735
  pageChatEl.setAttribute('aria-label', 'Steer the page');
7903
8736
  pageChatExpanded = keepExpanded;
7904
8737
  pageChatEl.dataset.expanded = keepExpanded ? 'true' : 'false';
7905
- pageChatEl.style.width = keepExpanded ? PAGE_CHAT_EXPANDED_W : PAGE_CHAT_COLLAPSED_W;
8738
+ pageChatEl.style.width = keepExpanded ? pageChatExpandedWidth() : PAGE_CHAT_COLLAPSED_W;
7906
8739
  pageChatEl.style.cursor = 'pointer';
7907
8740
  if (pageChatInput) {
7908
8741
  pageChatInput.disabled = false;
@@ -7993,7 +8826,9 @@ void main() {
7993
8826
  if (pageChatEl) pageChatEl.dataset.voiceListening = listening ? 'true' : 'false';
7994
8827
  syncPageChatChrome();
7995
8828
  } else if (voiceCtx?.mode === 'configure') {
7996
- const voiceBtn = uiGetById(PREFIX + '-configure-voice');
8829
+ // The bar shows either the replace row's voice button or the insert
8830
+ // row's - both run voice through the 'configure' mode.
8831
+ const voiceBtn = uiGetById(PREFIX + '-configure-voice') || uiGetById(PREFIX + '-insert-voice');
7997
8832
  if (voiceBtn) {
7998
8833
  voiceBtn.dataset.active = listening ? 'true' : 'false';
7999
8834
  voiceBtn.dataset.listening = listening ? 'true' : 'false';
@@ -8214,6 +9049,7 @@ void main() {
8214
9049
  pageChatEl.dataset.expanded = 'false';
8215
9050
  pageChatEl.style.width = PAGE_CHAT_COLLAPSED_W;
8216
9051
  pageChatEl.style.cursor = 'pointer';
9052
+ syncGlobalBarExpandedLabels(globalBarEl?.matches(':hover'));
8217
9053
  if (blur) {
8218
9054
  pageChatInput.blur();
8219
9055
  pageChatInput.style.pointerEvents = 'none';
@@ -8235,7 +9071,7 @@ void main() {
8235
9071
  height: '28px', margin: '0 4px 0 ' + (GLOBAL_BAR_SECTION_GAP - GLOBAL_BAR_INNER_GAP) + 'px',
8236
9072
  borderRadius: '7px',
8237
9073
  background: P.chatSurface,
8238
- border: '1px solid ' + P.hairline,
9074
+ border: '1px solid transparent',
8239
9075
  overflow: 'hidden',
8240
9076
  cursor: 'pointer',
8241
9077
  flexShrink: '0',
@@ -8266,13 +9102,14 @@ void main() {
8266
9102
  pageChatInput = document.createElement('input');
8267
9103
  pageChatInput.id = PREFIX + '-page-chat-input';
8268
9104
  pageChatInput.type = 'text';
8269
- pageChatInput.placeholder = 'Steer the page…';
9105
+ pageChatInput.placeholder = PAGE_CHAT_PLACEHOLDER_COLLAPSED;
8270
9106
  pageChatInput.setAttribute('aria-label', 'Steer the page');
8271
9107
  Object.assign(pageChatInput.style, {
8272
9108
  flex: '1', minWidth: '0', width: '0',
8273
9109
  padding: '0', border: 'none', background: 'transparent',
8274
9110
  fontFamily: FONT, fontSize: '11.5px', color: P.text,
8275
9111
  outline: 'none', opacity: '0', pointerEvents: 'none',
9112
+ caretColor: P.accent,
8276
9113
  transition: 'opacity 0.15s ease',
8277
9114
  });
8278
9115
 
@@ -8306,19 +9143,24 @@ void main() {
8306
9143
  '#' + PREFIX + '-page-chat[data-voice-listening="true"] { border-color: oklch(70% 0.12 188 / 0.45); }' +
8307
9144
  '#' + PREFIX + '-page-chat-voice[data-listening="true"] svg { animation: impeccable-voice-pulse 1.1s ease-in-out infinite; }' +
8308
9145
  '@media (prefers-reduced-motion: reduce) { #' + PREFIX + '-page-chat-voice[data-listening="true"] svg { animation: none; opacity: 1; } }' +
8309
- '#' + PREFIX + '-page-chat-input::placeholder { color: oklch(63% 0.024 82); opacity: 1; }' +
9146
+ '#' + PREFIX + '-page-chat-input::placeholder { color: oklch(72% 0 0); opacity: 1; }' +
9147
+ '#' + PREFIX + '-page-chat-input { caret-color: oklch(84% 0.19 80.46); }' +
9148
+ '#' + PREFIX + '-page-chat[data-input-focused="true"]:not([data-expanded="true"]) #' + PREFIX + '-page-chat-input::placeholder { color: oklch(72% 0 0); }' +
8310
9149
  '#' + PREFIX + '-page-chat-voice:hover { background: oklch(78% 0.12 82 / 0.12); }';
8311
9150
  uiAppendStyle(s);
8312
9151
  }
8313
9152
 
8314
- pageChatEl.addEventListener('pointerdown', keepSteerPointerInside);
9153
+ pageChatEl.addEventListener('pointerdown', (e) => {
9154
+ keepSteerPointerInside(e);
9155
+ if (steerLocked || pageChatVoiceBtn.contains(e.target)) return;
9156
+ armPageChatForTyping({ expand: true, focus: false });
9157
+ });
8315
9158
  pageChatEl.addEventListener('mousedown', keepSteerPointerInside);
8316
9159
  pageChatEl.addEventListener('click', (e) => {
8317
9160
  keepSteerPointerInside(e);
8318
9161
  if (steerLocked) return;
8319
9162
  if (pageChatVoiceBtn.contains(e.target)) return;
8320
- expandPageChat({ focus: false });
8321
- focusPageChatInput('page-chat-click');
9163
+ armPageChatForTyping({ expand: true, focus: true });
8322
9164
  });
8323
9165
 
8324
9166
  pageChatVoiceBtn.addEventListener('pointerdown', keepSteerPointerInside);
@@ -8341,7 +9183,9 @@ void main() {
8341
9183
  });
8342
9184
 
8343
9185
  pageChatInput.addEventListener('focus', () => {
9186
+ steerInputWasFocused = true;
8344
9187
  syncPageChatFocusRing();
9188
+ syncPageChatChrome();
8345
9189
  });
8346
9190
 
8347
9191
  pageChatInput.addEventListener('blur', () => {
@@ -8505,6 +9349,7 @@ void main() {
8505
9349
  zIndex: Z.bar + 5,
8506
9350
  display: 'flex', alignItems: 'stretch',
8507
9351
  gap: '0',
9352
+ width: 'max-content',
8508
9353
  background: P.surface,
8509
9354
  border: '1px solid ' + P.border,
8510
9355
  borderRadius: '8px',
@@ -8512,6 +9357,8 @@ void main() {
8512
9357
  fontFamily: FONT, fontSize: '12px', lineHeight: '1',
8513
9358
  opacity: '0',
8514
9359
  overflow: 'hidden', // clip the full-bleed brand mark to the bar radius
9360
+ maxWidth: 'calc(100vw - 16px)',
9361
+ boxSizing: 'border-box',
8515
9362
  transition: 'opacity 0.3s ' + EASE + ', transform 0.3s ' + EASE,
8516
9363
  });
8517
9364
  globalBarEl.id = PREFIX + '-global-bar';
@@ -8541,7 +9388,7 @@ void main() {
8541
9388
  const agentDot = el('span', {
8542
9389
  position: 'absolute', right: '-1px', bottom: '7px',
8543
9390
  width: '6px', height: '6px', borderRadius: '50%',
8544
- background: 'oklch(78% 0.14 75)',
9391
+ background: 'oklch(77% 0.13 82)',
8545
9392
  boxShadow: '0 0 0 2px ' + P.surface,
8546
9393
  display: 'none', pointerEvents: 'none',
8547
9394
  });
@@ -8560,6 +9407,7 @@ void main() {
8560
9407
  const inner = el('div', {
8561
9408
  display: 'flex', alignItems: 'center',
8562
9409
  padding: '4px 5px 4px ' + GLOBAL_BAR_INNER_PAD_LEFT + 'px', gap: GLOBAL_BAR_INNER_GAP + 'px',
9410
+ flex: '0 0 auto',
8563
9411
  });
8564
9412
  inner.id = PREFIX + '-global-bar-inner';
8565
9413
  globalBarEl.appendChild(inner);
@@ -8568,7 +9416,10 @@ void main() {
8568
9416
  function makeIconBtn({ id, svg, label, ariaLabel, labelFont, onClick }) {
8569
9417
  const b = el('button', {
8570
9418
  position: 'relative',
8571
- display: 'inline-flex', alignItems: 'center',
9419
+ display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
9420
+ boxSizing: 'border-box',
9421
+ flex: '0 0 auto',
9422
+ minWidth: '30px',
8572
9423
  padding: '6px 8px', borderRadius: '7px',
8573
9424
  border: 'none', background: 'transparent',
8574
9425
  color: P.textDim, fontFamily: FONT, fontSize: '11.5px', fontWeight: '500',
@@ -8587,8 +9438,8 @@ void main() {
8587
9438
  if (!labelEl) return;
8588
9439
  labelEl.style.maxWidth = '120px'; labelEl.style.opacity = '1'; labelEl.style.marginLeft = '6px'; labelEl.style.transform = 'translateX(0)';
8589
9440
  };
8590
- const collapse = () => {
8591
- if (!labelEl || b.dataset.active === 'true') return;
9441
+ const collapse = (force = false) => {
9442
+ if (!labelEl || (!force && b.dataset.active === 'true')) return;
8592
9443
  labelEl.style.maxWidth = '0'; labelEl.style.opacity = '0'; labelEl.style.marginLeft = '0'; labelEl.style.transform = 'translateX(-4px)';
8593
9444
  };
8594
9445
  // Per-button hover only changes color (no layout). The label expand/
@@ -8643,11 +9494,11 @@ void main() {
8643
9494
  // DESIGN.md panel toggle - quartet of color squares as the mark.
8644
9495
  const designBtn = makeIconBtn({
8645
9496
  id: PREFIX + '-design-toggle',
8646
- svg: `<span style="display:inline-grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;width:14px;height:14px;border-radius:3px;overflow:hidden;box-shadow:inset 0 0 0 1px oklch(58% 0.065 82 / 0.55);flex-shrink:0">
9497
+ svg: `<span style="display:inline-grid;grid-template-columns:1fr 1fr;grid-template-rows:1fr 1fr;width:14px;height:14px;border-radius:3px;overflow:hidden;box-shadow:inset 0 0 0 1px oklch(92% 0 0 / 0.13);flex-shrink:0">
8647
9498
  <span style="background:oklch(84% 0.19 80.46)"></span>
8648
9499
  <span style="background:oklch(70% 0.12 188)"></span>
8649
- <span style="background:oklch(84% 0.035 82)"></span>
8650
- <span style="background:oklch(34% 0.014 82)"></span>
9500
+ <span style="background:oklch(91% 0 0)"></span>
9501
+ <span style="background:oklch(34% 0 0)"></span>
8651
9502
  </span>`,
8652
9503
  label: 'DESIGN.md',
8653
9504
  ariaLabel: 'Toggle DESIGN.md panel',
@@ -8839,6 +9690,7 @@ void main() {
8839
9690
  width: '1px', height: '18px',
8840
9691
  background: P.hairline,
8841
9692
  margin: '0 4px 0 2px',
9693
+ flexShrink: '0',
8842
9694
  });
8843
9695
  inner.appendChild(divider);
8844
9696
 
@@ -8855,6 +9707,7 @@ void main() {
8855
9707
  display: 'inline-flex', alignItems: 'center', justifyContent: 'center',
8856
9708
  padding: '0', boxSizing: 'border-box',
8857
9709
  width: '24px', height: '24px', borderRadius: '6px',
9710
+ flexShrink: '0',
8858
9711
  border: 'none', background: 'transparent',
8859
9712
  color: P.textDim, fontFamily: FONT, fontSize: '0', lineHeight: '0',
8860
9713
  cursor: 'pointer', transition: 'color 0.12s ease, background 0.12s ease',
@@ -8867,16 +9720,16 @@ void main() {
8867
9720
  exitBtn.addEventListener('click', () => { sendEvent({ type: 'exit' }); teardown(); });
8868
9721
  inner.appendChild(exitBtn);
8869
9722
 
8870
- // Bar-level hover: expand every toggle's label at once; collapse on leave.
9723
+ // Bar-level hover: expand mode labels unless Steer is using the space.
8871
9724
  // Buttons with dataset.active="true" ignore collapse (their label stays).
8872
- const toggles = [pickBtn, insertBtn, detectBtn, designBtn];
8873
9725
  globalBarEl.addEventListener('mouseenter', () => {
8874
- toggles.forEach((t) => t._expandLabel && t._expandLabel());
9726
+ syncGlobalBarExpandedLabels(true);
9727
+ syncPageChatExpandedWidth();
8875
9728
  schedulePendingDockPosition();
8876
9729
  setTimeout(schedulePendingDockPosition, 260);
8877
9730
  });
8878
9731
  globalBarEl.addEventListener('mouseleave', () => {
8879
- toggles.forEach((t) => t._collapseLabel && t._collapseLabel());
9732
+ syncGlobalBarExpandedLabels(false);
8880
9733
  schedulePendingDockPosition();
8881
9734
  setTimeout(schedulePendingDockPosition, 260);
8882
9735
  });
@@ -8894,6 +9747,7 @@ void main() {
8894
9747
  pendingDockResizeObserver.observe(globalBarEl);
8895
9748
  }
8896
9749
  window.addEventListener('resize', positionPendingDock);
9750
+ window.addEventListener('resize', syncPageChatExpandedWidth);
8897
9751
 
8898
9752
  requestAnimationFrame(() => {
8899
9753
  globalBarEl.style.opacity = '1';
@@ -8940,9 +9794,7 @@ void main() {
8940
9794
  // If the bar is currently under the cursor, keep all labels expanded -
8941
9795
  // otherwise clicking a toggle that deactivates (e.g. closing DESIGN.md)
8942
9796
  // would collapse its label while the user's mouse is still on the bar.
8943
- if (globalBarEl && globalBarEl.matches(':hover')) {
8944
- [pickToggle, insertToggle, detectToggle, designToggle].forEach((t) => t?._expandLabel?.());
8945
- }
9797
+ syncGlobalBarExpandedLabels(globalBarEl && globalBarEl.matches(':hover'));
8946
9798
 
8947
9799
  if (detectBadge) {
8948
9800
  detectBadge.style.display = (detectActive && detectCount > 0) ? 'inline' : 'none';
@@ -9008,14 +9860,15 @@ void main() {
9008
9860
  cancelInsertConfigure();
9009
9861
  return;
9010
9862
  }
9863
+ teardownConfigureChrome();
9011
9864
  hideHighlight();
9012
- hideBar();
9013
9865
  hideActionPicker();
9014
9866
  selectedElement = null;
9867
+ hoveredElement = null;
9015
9868
  configureKind = 'replace';
9016
- if (state === 'PICKING' || state === 'CONFIGURING') state = 'IDLE';
9869
+ if (state === 'PICKING' || state === 'CONFIGURING') setLiveState('IDLE');
9017
9870
  } else {
9018
- if (state === 'IDLE') state = 'PICKING';
9871
+ if (state === 'IDLE') setLiveState('PICKING');
9019
9872
  }
9020
9873
  syncPageChatFocus('toggle-pick');
9021
9874
  }
@@ -9031,10 +9884,10 @@ void main() {
9031
9884
  selectedElement = null;
9032
9885
  configureKind = 'replace';
9033
9886
  if (state === 'CONFIGURING') cancelInsertConfigure();
9034
- else if (state === 'IDLE' || state === 'PICKING') state = 'PICKING';
9887
+ else if (state === 'IDLE' || state === 'PICKING') setLiveState('PICKING');
9035
9888
  } else {
9036
9889
  clearInsertPicking();
9037
- if (state === 'PICKING' && !pickActive) state = 'IDLE';
9890
+ if (state === 'PICKING' && !pickActive) setLiveState('IDLE');
9038
9891
  }
9039
9892
  saveInteractionPrefs();
9040
9893
  updateGlobalBarState();
@@ -9115,6 +9968,7 @@ void main() {
9115
9968
  pageChatVoiceBtn = null;
9116
9969
  pageChatExpanded = false;
9117
9970
  if (insertCreateTooltipEl) { insertCreateTooltipEl.remove(); insertCreateTooltipEl = null; }
9971
+ if (configureBarTooltipEl) { configureBarTooltipEl.remove(); configureBarTooltipEl = null; }
9118
9972
  if (highlightEl) { highlightEl.remove(); highlightEl = null; }
9119
9973
  if (tooltipEl) { tooltipEl.remove(); tooltipEl = null; }
9120
9974
  if (barEl) { barEl.remove(); barEl = null; }
@@ -9128,7 +9982,8 @@ void main() {
9128
9982
  window.removeEventListener('message', onDetectMessage);
9129
9983
  // Remove detection overlays
9130
9984
  window.postMessage({ source: 'impeccable-command', action: 'remove' }, '*');
9131
- state = 'IDLE';
9985
+ setLiveState('IDLE');
9986
+ document.getElementById(PICK_CURSOR_STYLE_ID)?.remove();
9132
9987
  window.__IMPECCABLE_LIVE_INIT__ = false;
9133
9988
  console.log('[impeccable] Live mode exited.');
9134
9989
  }
@@ -9228,8 +10083,8 @@ void main() {
9228
10083
  meta: 'oklch(55% 0 0)',
9229
10084
  hairline: 'oklch(88% 0 0)',
9230
10085
  hairlineSoft: 'oklch(92% 0 0)',
9231
- amber: 'oklch(70% 0.13 65)', // stale-hint accent
9232
- amberBg: 'oklch(95% 0.05 80)',
10086
+ amber: 'oklch(77% 0.13 82)', // stale-hint accent
10087
+ amberBg: 'oklch(89% 0.055 84)',
9233
10088
  };
9234
10089
 
9235
10090
  function designPanelCss(BP) {
@@ -9320,7 +10175,7 @@ void main() {
9320
10175
  }
9321
10176
  .empty strong { color: ${DP.ink}; display: block; margin-bottom: 6px; font-size: 14px; }
9322
10177
  .empty code { font-family: ${MONO}; background: ${DP.canvas}; padding: 1px 6px; border-radius: 4px; font-size: 12px; color: ${DP.ink}; }
9323
- .error { color: oklch(45% 0.15 25); }
10178
+ .error { color: oklch(58% 0.15 35); }
9324
10179
 
9325
10180
  /* Stale hint */
9326
10181
  .stale {
@@ -9472,8 +10327,8 @@ void main() {
9472
10327
  content: ''; position: absolute; left: 4px; top: 13px;
9473
10328
  width: 8px; height: 8px; border-radius: 50%;
9474
10329
  }
9475
- .coll .do::before { background: oklch(62% 0.16 145); }
9476
- .coll .dont::before { background: oklch(58% 0.22 25); }
10330
+ .coll .do::before { background: oklch(45% 0.18 145); }
10331
+ .coll .dont::before { background: oklch(58% 0.15 35); }
9477
10332
 
9478
10333
  .coll .overview-body {
9479
10334
  font-size: 12px; line-height: 1.55; color: ${DP.ink2};
@@ -10305,6 +11160,8 @@ void main() {
10305
11160
  console.log('[impeccable] Resumed active variant session ' + currentSessionId + ' (' + arrivedVariants + '/' + expectedVariants + ' variants).');
10306
11161
  }
10307
11162
 
11163
+ if (state === 'IDLE' && (pickActive || insertActive)) setLiveState('PICKING');
11164
+ syncPageInteractionCursor();
10308
11165
  syncPageChatFocus('init-complete');
10309
11166
  }
10310
11167