quiver-cli 0.5.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.
- package/LICENSE +21 -0
- package/dist/cli.js +193 -51
- package/package.json +1 -1
- package/template/.agents/skills/agent-browser/SKILL.md +4 -9
- package/template/.agents/skills/apps/skybridge/SKILL.md +1 -1
- package/template/.agents/skills/code/improve/SKILL.md +9 -5
- package/template/.agents/skills/code/improve/references/audit-playbook.md +10 -10
- package/template/.agents/skills/code/improve/references/closing-the-loop.md +4 -3
- package/template/.agents/skills/code/improve/references/plan-template.md +5 -0
- package/template/.agents/skills/design/impeccable/SKILL.md +14 -24
- package/template/.agents/skills/design/impeccable/reference/animate.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/bolder.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/brand.md +2 -2
- package/template/.agents/skills/design/impeccable/reference/colorize.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/critique.md +6 -6
- package/template/.agents/skills/design/impeccable/reference/delight.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/distill.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/document.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/extract.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/hooks.md +90 -0
- package/template/.agents/skills/design/impeccable/reference/init.md +5 -5
- package/template/.agents/skills/design/impeccable/reference/live.md +16 -16
- package/template/.agents/skills/design/impeccable/reference/overdrive.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/polish.md +2 -2
- package/template/.agents/skills/design/impeccable/reference/quieter.md +1 -1
- package/template/.agents/skills/design/impeccable/reference/shape.md +2 -2
- package/template/.agents/skills/design/impeccable/scripts/context-signals.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/context.mjs +724 -33
- package/template/.agents/skills/design/impeccable/scripts/critique-storage.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/detector/browser/injected/index.mjs +204 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/cli/main.mjs +57 -11
- package/template/.agents/skills/design/impeccable/scripts/detector/design-system.mjs +750 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns-browser.js +633 -46
- package/template/.agents/skills/design/impeccable/scripts/detector/detect-antipatterns.mjs +7 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/browser/detect-url.mjs +29 -4
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/regex/detect-text.mjs +43 -10
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/css-cascade.mjs +29 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/engines/static-html/detect-html.mjs +27 -1
- package/template/.agents/skills/design/impeccable/scripts/detector/node/file-system.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/detector/registry/antipatterns.mjs +29 -0
- package/template/.agents/skills/design/impeccable/scripts/detector/rules/checks.mjs +401 -46
- package/template/.agents/skills/design/impeccable/scripts/detector/shared/inline-ignores.mjs +148 -0
- package/template/.agents/skills/design/impeccable/scripts/hook-admin.mjs +661 -0
- package/template/.agents/skills/design/impeccable/scripts/hook-before-edit.mjs +476 -0
- package/template/.agents/skills/design/impeccable/scripts/hook-lib.mjs +1632 -0
- package/template/.agents/skills/design/impeccable/scripts/hook.mjs +61 -0
- package/template/.agents/skills/design/impeccable/scripts/{design-parser.mjs → lib/design-parser.mjs} +8 -1
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-config.mjs +638 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/impeccable-paths.mjs +128 -0
- package/template/.agents/skills/design/impeccable/scripts/lib/target-args.mjs +42 -0
- package/template/.agents/skills/design/impeccable/scripts/live/browser-script-parts.mjs +49 -0
- package/template/.agents/skills/design/impeccable/scripts/{live-event-validation.mjs → live/event-validation.mjs} +6 -5
- package/template/.agents/skills/design/impeccable/scripts/live/manual-apply.mjs +939 -0
- package/template/.agents/skills/design/impeccable/scripts/live/manual-edit-routes.mjs +357 -0
- package/template/.agents/skills/design/impeccable/scripts/{live-manual-edits-buffer.mjs → live/manual-edits-buffer.mjs} +1 -1
- package/template/.agents/skills/design/impeccable/scripts/{live-session-store.mjs → live/session-store.mjs} +1 -1
- package/template/.agents/skills/design/impeccable/scripts/{live-ui-core.mjs → live/ui-core.mjs} +2 -1
- package/template/.agents/skills/design/impeccable/scripts/live/vocabulary.mjs +36 -0
- package/template/.agents/skills/design/impeccable/scripts/live-accept.mjs +3 -3
- package/template/.agents/skills/design/impeccable/scripts/live-browser-dom.js +146 -0
- package/template/.agents/skills/design/impeccable/scripts/live-browser.js +1456 -599
- package/template/.agents/skills/design/impeccable/scripts/live-commit-manual-edits.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-complete.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-discard-manual-edits.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/live-inject.mjs +35 -9
- package/template/.agents/skills/design/impeccable/scripts/live-insert.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-manual-edit-evidence.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-poll.mjs +18 -13
- package/template/.agents/skills/design/impeccable/scripts/live-resume.mjs +1 -1
- package/template/.agents/skills/design/impeccable/scripts/live-server.mjs +77 -1264
- package/template/.agents/skills/design/impeccable/scripts/live-status.mjs +2 -2
- package/template/.agents/skills/design/impeccable/scripts/live-target.mjs +30 -0
- package/template/.agents/skills/design/impeccable/scripts/live-wrap.mjs +4 -4
- package/template/.agents/skills/design/impeccable/scripts/live.mjs +73 -22
- package/template/.agents/skills/writing/humanizer/SKILL.md +621 -0
- package/template/.agents/upstreams.json +17 -8
- package/template/.agents/skills/apps/skybridge/references/architecture.md +0 -175
- package/template/.agents/skills/apps/skybridge/references/copy-template.md +0 -24
- package/template/.agents/skills/apps/skybridge/references/csp.md +0 -33
- package/template/.agents/skills/apps/skybridge/references/deploy.md +0 -33
- package/template/.agents/skills/apps/skybridge/references/discover.md +0 -84
- package/template/.agents/skills/apps/skybridge/references/download-file.md +0 -77
- package/template/.agents/skills/apps/skybridge/references/fetch-and-render-data.md +0 -151
- package/template/.agents/skills/apps/skybridge/references/oauth.md +0 -115
- package/template/.agents/skills/apps/skybridge/references/open-external-links.md +0 -71
- package/template/.agents/skills/apps/skybridge/references/prompt-llm.md +0 -20
- package/template/.agents/skills/apps/skybridge/references/publish.md +0 -19
- package/template/.agents/skills/apps/skybridge/references/run-locally.md +0 -51
- package/template/.agents/skills/apps/skybridge/references/state-and-context.md +0 -151
- package/template/.agents/skills/apps/skybridge/references/ui-guidelines.md +0 -205
- package/template/.agents/skills/design/impeccable/scripts/cleanup-deprecated.mjs +0 -284
- package/template/.agents/skills/design/impeccable/scripts/impeccable-paths.mjs +0 -126
- /package/template/.agents/commands/{tf-readme.md → terraform-readme.md} +0 -0
- /package/template/.agents/skills/design/impeccable/scripts/{is-generated.mjs → lib/is-generated.mjs} +0 -0
- /package/template/.agents/skills/design/impeccable/scripts/{live-completion.mjs → live/completion.mjs} +0 -0
- /package/template/.agents/skills/design/impeccable/scripts/{live-insert-ui.mjs → live/insert-ui.mjs} +0 -0
- /package/template/.agents/skills/design/impeccable/scripts/{live-svelte-component.mjs → live/svelte-component.mjs} +0 -0
- /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
|
-
//
|
|
85
|
-
//
|
|
86
|
-
//
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
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 + '-
|
|
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
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
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
|
|
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: '
|
|
1136
|
-
maxWidth: '
|
|
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
|
-
|
|
1180
|
-
else
|
|
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
|
-
|
|
1216
|
-
else
|
|
1217
|
-
|
|
1218
|
-
|
|
1219
|
-
barEl.appendChild(
|
|
1220
|
-
|
|
1221
|
-
|
|
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
|
|
1230
|
-
const
|
|
1231
|
-
if (!
|
|
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
|
-
|
|
1234
|
-
|
|
1235
|
-
|
|
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.
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
1949
|
+
/** Page-level cursor while pick or insert mode is targeting page elements. */
|
|
1560
1950
|
function syncPageInteractionCursor() {
|
|
1561
|
-
let
|
|
1562
|
-
if (state === 'PICKING' && insertActive) {
|
|
1563
|
-
|
|
1564
|
-
}
|
|
1565
|
-
|
|
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: '
|
|
2306
|
+
display: 'flex', alignItems: 'stretch', width: '100%', height: CONFIGURE_BAR_H,
|
|
1896
2307
|
});
|
|
1897
2308
|
|
|
1898
|
-
|
|
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 =
|
|
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
|
|
1963
|
-
|
|
1964
|
-
|
|
1965
|
-
|
|
1966
|
-
|
|
1967
|
-
|
|
1968
|
-
|
|
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
|
-
|
|
1979
|
-
|
|
1980
|
-
|
|
1981
|
-
|
|
1982
|
-
|
|
1983
|
-
|
|
1984
|
-
|
|
1985
|
-
|
|
1986
|
-
|
|
1987
|
-
|
|
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
|
-
|
|
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
|
|
2011
|
-
|
|
2012
|
-
|
|
2013
|
-
|
|
2014
|
-
|
|
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
|
-
|
|
2018
|
-
|
|
2019
|
-
|
|
2020
|
-
|
|
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
|
-
|
|
2050
|
-
|
|
2051
|
-
|
|
2052
|
-
|
|
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: '
|
|
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
|
|
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 = '
|
|
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
|
|
2117
|
-
|
|
2118
|
-
|
|
2119
|
-
|
|
2120
|
-
|
|
2121
|
-
|
|
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
|
-
|
|
2124
|
-
|
|
2125
|
-
|
|
2126
|
-
|
|
2127
|
-
|
|
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
|
-
|
|
2152
|
-
|
|
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
|
|
2163
|
-
|
|
2164
|
-
|
|
2165
|
-
|
|
2166
|
-
|
|
2167
|
-
|
|
2168
|
-
|
|
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 =
|
|
2183
|
-
|
|
2184
|
-
|
|
2185
|
-
|
|
2186
|
-
|
|
2187
|
-
|
|
2188
|
-
|
|
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
|
-
|
|
2203
|
-
|
|
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.
|
|
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(
|
|
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
|
|
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',
|
|
2583
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 ||
|
|
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,
|
|
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.
|
|
4255
|
-
|
|
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
|
-
|
|
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', () =>
|
|
4263
|
-
btn.addEventListener('mouseleave',
|
|
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.
|
|
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
|
|
4523
|
-
|
|
4927
|
+
const cls = normalizeElementClassName(origContent);
|
|
4928
|
+
const candidates = [...document.getElementsByTagName(tag)];
|
|
4929
|
+
|
|
4524
4930
|
if (origContent.id) {
|
|
4525
|
-
|
|
4526
|
-
|
|
4527
|
-
|
|
4528
|
-
|
|
4529
|
-
|
|
4530
|
-
|
|
4531
|
-
if (
|
|
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 (
|
|
4535
|
-
if (expectedClasses.every((name) => c.classList.contains(name)))
|
|
4939
|
+
if (!isUsableInjectionAnchor(c)) continue;
|
|
4940
|
+
if (expectedClasses.every((name) => c.classList.contains(name))) return c;
|
|
4536
4941
|
}
|
|
4537
4942
|
}
|
|
4538
4943
|
}
|
|
4539
|
-
|
|
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
|
|
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')
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 =
|
|
5552
|
+
const liveEl = resolveLiveInjectionAnchor(origContent.outerHTML);
|
|
4923
5553
|
if (!liveEl) {
|
|
4924
5554
|
console.warn('[impeccable] Could not find original element in live DOM.');
|
|
4925
|
-
|
|
4926
|
-
|
|
4927
|
-
|
|
4928
|
-
|
|
4929
|
-
|
|
4930
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5147
|
-
|
|
5148
|
-
|
|
5149
|
-
|
|
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.
|
|
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
|
-
|
|
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))
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
5539
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 &&
|
|
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
|
-
|
|
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();
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
|
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.
|
|
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
|
|
6868
|
-
|
|
6869
|
-
if (!wrapper) {
|
|
7575
|
+
const wrappers = findAcceptedRuntimeWrappers(sessionId);
|
|
7576
|
+
if (wrappers.length === 0) {
|
|
6870
7577
|
restoreAcceptedDomFromSnapshot(pending);
|
|
6871
7578
|
return;
|
|
6872
7579
|
}
|
|
6873
|
-
|
|
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
|
-
|
|
6879
|
-
|
|
6880
|
-
|
|
6881
|
-
|
|
6882
|
-
|
|
6883
|
-
|
|
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
|
-
|
|
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
|
-
|
|
7071
|
-
|
|
7072
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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 = '
|
|
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(
|
|
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
|
|
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(
|
|
7547
|
-
textDim: 'oklch(
|
|
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 =
|
|
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)
|
|
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
|
-
|
|
7751
|
-
|
|
7752
|
-
|
|
7753
|
-
|
|
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
|
-
|
|
7756
|
-
|
|
7757
|
-
|
|
7758
|
-
|
|
7759
|
-
pageChatInput.
|
|
7760
|
-
if (pageChatHint)
|
|
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
|
-
|
|
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 ?
|
|
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
|
-
|
|
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 '
|
|
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 =
|
|
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(
|
|
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',
|
|
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
|
-
|
|
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(
|
|
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(
|
|
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(
|
|
8650
|
-
<span style="background:oklch(34% 0
|
|
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
|
|
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
|
-
|
|
9726
|
+
syncGlobalBarExpandedLabels(true);
|
|
9727
|
+
syncPageChatExpandedWidth();
|
|
8875
9728
|
schedulePendingDockPosition();
|
|
8876
9729
|
setTimeout(schedulePendingDockPosition, 260);
|
|
8877
9730
|
});
|
|
8878
9731
|
globalBarEl.addEventListener('mouseleave', () => {
|
|
8879
|
-
|
|
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
|
-
|
|
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')
|
|
9869
|
+
if (state === 'PICKING' || state === 'CONFIGURING') setLiveState('IDLE');
|
|
9017
9870
|
} else {
|
|
9018
|
-
if (state === 'IDLE')
|
|
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')
|
|
9887
|
+
else if (state === 'IDLE' || state === 'PICKING') setLiveState('PICKING');
|
|
9035
9888
|
} else {
|
|
9036
9889
|
clearInsertPicking();
|
|
9037
|
-
if (state === 'PICKING' && !pickActive)
|
|
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
|
-
|
|
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(
|
|
9232
|
-
amberBg: 'oklch(
|
|
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(
|
|
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(
|
|
9476
|
-
.coll .dont::before { background: oklch(58% 0.
|
|
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
|
|