mirror of
https://github.com/pbakaus/impeccable.git
synced 2026-09-15 23:56:29 +03:00
fix(live): readable freeform input on dark bar + tools/live-loop.mjs
The configure row's text input filled its background with translucent
magenta (BP.accentSoft) on focus. Composited against the dark bar surface
this produced a murky purple where the browser's default placeholder
gray washed out — flagged in a real session as "godawful styling, gray
text on dark magenta really hurts my eyes". Fix: focus state shows an
accent-colored border only, no fill; placeholder color is set explicitly
to BP.textDim via a one-shot stylesheet so it reads in both themes.
tests/live-e2e/agent.mjs: runAgentLoop's wrapTarget now accepts either a
static {classes,tag,elementId} (test fixture mode) OR a function that
derives the target from each generate event (real-use mode where the
picked element is unknown ahead of time).
tools/live-loop.mjs: standalone runner that attaches the LLM agent to a
running live-server. Used as a test-harness shortcut for validating live
mode out of band; in production the user's coding agent (Claude Code,
Cursor, etc.) plays this role directly via the live skill spec.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Opus 4.7
parent
74f16d6310
commit
6e96f62803
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -905,7 +905,12 @@
|
||||
pill.addEventListener('click', (e) => { e.stopPropagation(); toggleActionPicker(); });
|
||||
row.appendChild(pill);
|
||||
|
||||
// Freeform input
|
||||
// Freeform input. Focus state shows an accent-colored border only —
|
||||
// an earlier version tinted the background with `BP.accentSoft`, which
|
||||
// composited against the dark bar surface to a murky purple where the
|
||||
// browser's default placeholder gray was unreadable. Placeholder color
|
||||
// is set explicitly via a one-shot stylesheet keyed off this input's id
|
||||
// so it picks up the bar's `textDim` token in both themes.
|
||||
const input = document.createElement('input');
|
||||
input.id = PREFIX + '-input';
|
||||
input.type = 'text';
|
||||
@@ -916,15 +921,20 @@
|
||||
border: '1px solid transparent', background: 'transparent',
|
||||
fontFamily: FONT, fontSize: '12px', color: BP.text,
|
||||
outline: 'none',
|
||||
transition: 'border-color 0.15s ease, background 0.15s ease',
|
||||
transition: 'border-color 0.15s ease',
|
||||
});
|
||||
if (!document.getElementById(PREFIX + '-input-style')) {
|
||||
const s = document.createElement('style');
|
||||
s.id = PREFIX + '-input-style';
|
||||
s.textContent =
|
||||
'#' + PREFIX + '-input::placeholder { color: ' + BP.textDim + '; opacity: 1; }';
|
||||
document.head.appendChild(s);
|
||||
}
|
||||
input.addEventListener('focus', () => {
|
||||
input.style.borderColor = BP.hairline;
|
||||
input.style.background = BP.accentSoft;
|
||||
input.style.borderColor = BP.accent;
|
||||
});
|
||||
input.addEventListener('blur', () => {
|
||||
input.style.borderColor = 'transparent';
|
||||
input.style.background = 'transparent';
|
||||
});
|
||||
input.addEventListener('keydown', (e) => {
|
||||
if (e.key === 'Enter') { e.stopPropagation(); e.preventDefault(); handleGo(); return; }
|
||||
|
||||
@@ -318,12 +318,17 @@ export async function runAgentLoop({
|
||||
log(`generate id=${event.id} action=${event.action} count=${event.count}`);
|
||||
try {
|
||||
// 1. Wrap the original element in the variant scaffold (deterministic CLI)
|
||||
// wrapTarget can be a static {classes, tag, elementId} (test fixtures
|
||||
// know what they pick) or a function (event) => target (real-use
|
||||
// sessions: the agent must derive the selector from the picked
|
||||
// element on the fly).
|
||||
const target = typeof wrapTarget === 'function' ? wrapTarget(event) : wrapTarget;
|
||||
const wrapInfo = await runWrap({
|
||||
tmp,
|
||||
scriptsDir,
|
||||
id: event.id,
|
||||
count: event.count,
|
||||
...wrapTarget,
|
||||
...target,
|
||||
});
|
||||
log(`wrapped: ${wrapInfo.file} insertLine=${wrapInfo.insertLine}`);
|
||||
|
||||
|
||||
@@ -0,0 +1,85 @@
|
||||
/**
|
||||
* Attach an LLM-backed agent loop to a live-server that is already running
|
||||
* in the current working directory. Reads port + token from the PID file
|
||||
* (.impeccable-live.json) that live-server.mjs --background writes on boot.
|
||||
*
|
||||
* Usage (from the project root):
|
||||
* ANTHROPIC_API_KEY=... node tools/live-loop.mjs
|
||||
*
|
||||
* Optional:
|
||||
* IMPECCABLE_E2E_LLM_MODEL=claude-sonnet-4-6 (default: claude-haiku-4-5)
|
||||
*
|
||||
* Stop with Ctrl-C; the live-server keeps running until you call
|
||||
* `node source/skills/impeccable/scripts/live-server.mjs stop`.
|
||||
*/
|
||||
|
||||
import fs from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
import { runAgentLoop } from '../tests/live-e2e/agent.mjs';
|
||||
import { createLlmAgent } from '../tests/live-e2e/agents/llm-agent.mjs';
|
||||
|
||||
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
||||
const REPO_ROOT = path.join(__dirname, '..');
|
||||
const SCRIPTS_DIR = path.join(REPO_ROOT, 'source', 'skills', 'impeccable', 'scripts');
|
||||
const PID_FILE = path.join(REPO_ROOT, '.impeccable-live.json');
|
||||
|
||||
if (!fs.existsSync(PID_FILE)) {
|
||||
console.error(
|
||||
`No live-server PID file at ${PID_FILE}. Start one first:\n` +
|
||||
` node source/skills/impeccable/scripts/live-server.mjs --background\n` +
|
||||
` node source/skills/impeccable/scripts/live-inject.mjs --port <PORT>`,
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const { port, token } = JSON.parse(fs.readFileSync(PID_FILE, 'utf-8'));
|
||||
console.log(`Attaching agent loop to live-server on :${port}`);
|
||||
|
||||
const agent = await createLlmAgent({
|
||||
model: process.env.IMPECCABLE_E2E_LLM_MODEL,
|
||||
log: (m) => console.log('[llm] ' + m),
|
||||
});
|
||||
if (!agent) {
|
||||
console.error('ANTHROPIC_API_KEY is not set. Set it and re-run.');
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
const controller = new AbortController();
|
||||
|
||||
process.on('SIGINT', () => {
|
||||
console.log('\nStopping agent loop (live-server stays running).');
|
||||
controller.abort();
|
||||
setTimeout(() => process.exit(0), 200);
|
||||
});
|
||||
process.on('SIGTERM', () => controller.abort());
|
||||
|
||||
console.log(
|
||||
`Agent ready (model=${process.env.IMPECCABLE_E2E_LLM_MODEL || 'claude-haiku-4-5'}).\n` +
|
||||
`Pick an element in the browser and hit Go. Ctrl-C to stop.`,
|
||||
);
|
||||
|
||||
await runAgentLoop({
|
||||
tmp: REPO_ROOT,
|
||||
scriptsDir: SCRIPTS_DIR,
|
||||
port,
|
||||
token,
|
||||
agent,
|
||||
signal: controller.signal,
|
||||
log: (m) => console.log('[agent] ' + m),
|
||||
// Per-event wrap target derived from the picked element's payload.
|
||||
// Preference: id > first class > tag-only.
|
||||
wrapTarget: (event) => {
|
||||
const el = event.element || {};
|
||||
const out = {};
|
||||
if (el.id) out.elementId = el.id;
|
||||
else if (Array.isArray(el.classes) && el.classes.length > 0) {
|
||||
// live-wrap matches when ALL listed classes are present on the source
|
||||
// node, so include only the first to maximize match likelihood.
|
||||
out.classes = el.classes[0];
|
||||
}
|
||||
if (el.tagName) out.tag = el.tagName.toLowerCase();
|
||||
return out;
|
||||
},
|
||||
});
|
||||
Reference in New Issue
Block a user