Refresh the Impeccable product experience

Rework the landing page proof, steering demo, feature grid, slop catalog, detector coverage, theming, Live workflow, and responsive behavior.\n\nAI-assisted implementation by OpenAI Codex.
This commit is contained in:
Paul Bakaus
2026-07-15 23:29:47 -07:00
parent 8682c85c57
commit bbed6eef08
553 changed files with 8903 additions and 97987 deletions
+11 -323
View File
@@ -27,10 +27,6 @@ import { join } from 'node:path';
import { execFile } from 'node:child_process';
import { promisify } from 'node:util';
import { completionTypeForAcceptResult } from '../../skill/scripts/live/completion.mjs';
import {
prepareGenerationArtifact,
publishGenerationArtifact,
} from '../../skill/scripts/live/generation-publisher.mjs';
const execFileP = promisify(execFile);
@@ -1329,25 +1325,15 @@ async function spliceVariantsIntoWrapper({ tmp, wrapInfo, sessionId, output }) {
styleMode: wrapInfo.styleMode,
});
const endMarkerIdx = lines.findIndex((line, index) =>
index > markerIdx && line.includes('impeccable-variants-end ' + sessionId),
);
if (endMarkerIdx === -1) {
throw new Error('end marker not found in ' + wrapInfo.file);
}
const tailIdx = wrapInfo.commentSyntax.open === '{/*'
? endMarkerIdx
: endMarkerIdx - 1;
const next = [
...lines.slice(0, markerIdx + 1),
block,
...lines.slice(tailIdx),
...lines.slice(markerIdx + 1),
];
await fs.writeFile(filePath, next.join('\n'), 'utf-8');
}
async function writeSvelteComponentVariants({ tmp, wrapInfo, event, output, writeParams = true }) {
async function writeSvelteComponentVariants({ tmp, wrapInfo, event, output }) {
const manifestPath = path.join(tmp, wrapInfo.file);
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf-8'));
const componentDir = path.join(tmp, manifest.componentDir);
@@ -1387,168 +1373,7 @@ async function writeSvelteComponentVariants({ tmp, wrapInfo, event, output, writ
paramsByVariant[String(variantId)] = Array.isArray(variant.params) ? variant.params : [];
}
if (writeParams) {
await fs.writeFile(path.join(componentDir, 'params.json'), JSON.stringify(paramsByVariant, null, 2) + '\n', 'utf-8');
}
manifest.arrivedVariants = output.variants.length;
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
}
async function publishSvelteComponentVariants({ tmp, wrapInfo, event, output, writeParams = true }) {
const prepared = prepareGenerationArtifact({
id: event.id,
sourceFile: wrapInfo.file,
cwd: tmp,
});
if (!prepared.ok) throw new Error(`Svelte publication prepare failed: ${prepared.error}`);
await writeSvelteComponentVariants({
tmp,
wrapInfo: { ...wrapInfo, file: prepared.artifactFile },
event,
output,
writeParams,
});
const published = publishGenerationArtifact({
id: event.id,
epoch: prepared.epoch,
sourceFile: wrapInfo.file,
artifactFile: prepared.artifactFile,
expectedSourceHash: prepared.expectedSourceHash,
arrivedVariants: output.variants.length,
expectedVariants: event.count,
cwd: tmp,
});
if (!published.ok) throw new Error(`Svelte publication failed: ${published.error}`);
return published;
}
async function writeVueComponentVariants({ tmp, wrapInfo, event, output, writeParams = true }) {
const manifestPath = path.join(tmp, wrapInfo.file);
const manifest = JSON.parse(await fs.readFile(manifestPath, 'utf-8'));
const componentDir = path.join(tmp, manifest.componentDir);
const contract = Array.isArray(manifest.propContract) ? manifest.propContract : [];
const textValues = extractTextPieces(event.element?.outerHTML || event.element?.textContent || '');
const paramsByVariant = {};
for (let i = 0; i < output.variants.length; i++) {
const variantId = i + 1;
const variant = output.variants[i];
let markup = substituteLiveTextWithProps(variant.innerHtml || '', contract, textValues).trim();
for (const entry of contract) {
markup = markup.replaceAll(`{${entry.prop}}`, `{{ ${entry.prop} }}`);
}
const css = svelteCssForVariant(output.scopedCss || '', variantId, firstTagName(markup) || 'div');
const propsScript = contract.length > 0
? ['<script setup>', 'defineProps({', ...contract.map((entry) => ` ${entry.prop}: { default: '' },`), '});', '</script>', '']
: [];
const component = [
...propsScript,
'<template>',
markup || '<div></div>',
'</template>',
'',
'<style scoped>',
css || ':where(*) {}',
'</style>',
'',
].join('\n');
await fs.writeFile(path.join(componentDir, `v${variantId}.vue`), component, 'utf-8');
paramsByVariant[String(variantId)] = Array.isArray(variant.params) ? variant.params : [];
}
if (writeParams) {
await fs.writeFile(path.join(componentDir, 'params.json'), JSON.stringify(paramsByVariant, null, 2) + '\n', 'utf-8');
}
manifest.arrivedVariants = output.variants.length;
await fs.writeFile(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
}
async function publishVueComponentVariants({ tmp, wrapInfo, event, output, writeParams = true }) {
const prepared = prepareGenerationArtifact({ id: event.id, sourceFile: wrapInfo.file, cwd: tmp });
if (!prepared.ok) throw new Error(`Vue publication prepare failed: ${prepared.error}`);
await writeVueComponentVariants({
tmp,
wrapInfo: { ...wrapInfo, file: prepared.artifactFile },
event,
output,
writeParams,
});
const published = publishGenerationArtifact({
id: event.id,
epoch: prepared.epoch,
sourceFile: wrapInfo.file,
artifactFile: prepared.artifactFile,
expectedSourceHash: prepared.expectedSourceHash,
arrivedVariants: output.variants.length,
expectedVariants: event.count,
cwd: tmp,
});
if (!published.ok) throw new Error(`Vue publication failed: ${published.error}`);
return published;
}
async function publishSourceVariants({ tmp, wrapInfo, event, output }) {
const prepared = prepareGenerationArtifact({
id: event.id,
sourceFile: wrapInfo.file,
cwd: tmp,
});
if (!prepared.ok) throw new Error(`Source publication prepare failed: ${prepared.error}`);
await spliceVariantsIntoWrapper({
tmp,
wrapInfo: { ...wrapInfo, file: prepared.artifactFile },
sessionId: event.id,
output,
});
const published = publishGenerationArtifact({
id: event.id,
epoch: prepared.epoch,
sourceFile: wrapInfo.file,
artifactFile: prepared.artifactFile,
expectedSourceHash: prepared.expectedSourceHash,
arrivedVariants: output.variants.length,
expectedVariants: event.count,
cwd: tmp,
});
if (!published.ok) throw new Error(`Source publication failed: ${published.error}`);
return published;
}
async function publishVariantProgress({
base,
token,
event,
wrapInfo,
arrivedVariants,
signal,
revision = 1,
publicationKind = 'variants',
}) {
const previewMode = wrapInfo.previewMode || 'source';
await fetch(`${base}/events`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
token,
type: 'checkpoint',
id: event.id,
revision,
revisionDomain: 'publication',
phase: 'cycling',
reason: 'variants_progress',
arrivedVariants,
expectedVariants: event.count,
sourceFile: wrapInfo.sourceFile || wrapInfo.file,
previewFile: wrapInfo.file,
previewMode,
publicationKind,
}),
signal,
});
await fs.writeFile(path.join(componentDir, 'params.json'), JSON.stringify(paramsByVariant, null, 2) + '\n', 'utf-8');
}
function variantMarkupHasVisibleContent(markup) {
@@ -1682,11 +1507,6 @@ export async function runAgentLoop({
agent,
signal,
log = () => {},
trace = () => {},
progressive = false,
progressiveDelayMs = 0,
progressiveInitialCount = 1,
atomicDelayMs = 0,
wrapTarget = { classes: 'hero-title', tag: 'h1' },
steerSourceFile,
steerTarget,
@@ -1710,8 +1530,6 @@ export async function runAgentLoop({
if (event.type === 'prefetch') continue;
if (event.type === 'connected') continue;
trace('agent.event.received', { id: event.id, type: event.type, clientSentAt: event.clientSentAt ?? null });
if (event.type === 'steer') {
log(`steer id=${event.id} message=${JSON.stringify(event.message)}`);
try {
@@ -1760,16 +1578,7 @@ export async function runAgentLoop({
log(`generate id=${event.id} mode=${isInsert ? 'insert' : 'replace'}${isInsert ? '' : ` action=${event.action}`} count=${event.count}`);
try {
let wrapInfo;
if (event.scaffold) {
wrapInfo = event.scaffold;
trace('agent.scaffold.reused', {
id: event.id,
file: wrapInfo.file,
previewMode: wrapInfo.previewMode || 'source',
durationMs: event.scaffoldDurationMs ?? null,
});
} else if (isInsert) {
trace('agent.scaffold.start', { id: event.id, mode: 'insert' });
if (isInsert) {
const insertTarget = insertTargetFromEvent(event);
wrapInfo = await runInsert({
tmp,
@@ -1778,9 +1587,7 @@ export async function runAgentLoop({
count: event.count,
...insertTarget,
});
trace('agent.scaffold.end', { id: event.id, file: wrapInfo.file, previewMode: wrapInfo.previewMode || 'source' });
} else {
trace('agent.scaffold.start', { id: event.id, mode: 'replace' });
// 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
@@ -1799,154 +1606,41 @@ export async function runAgentLoop({
...target,
text,
});
trace('agent.scaffold.end', { id: event.id, file: wrapInfo.file, previewMode: wrapInfo.previewMode || 'source' });
}
log(`scaffolded: ${wrapInfo.file} insertLine=${wrapInfo.insertLine}`);
// 2. Agent generates variant content (LLM-pluggable seam).
// Providers may expose a true split path so variant 1 is written before
// the request for the remaining variants completes.
trace('agent.generate.start', { id: event.id, count: event.count });
const splitProgressive = progressive
&& typeof agent.generateFirstVariant === 'function'
&& typeof agent.generateRemainingVariants === 'function'
&& event.count > 1;
let output;
let firstOutput;
if (splitProgressive) {
firstOutput = normalizeVariantOutput(
await agent.generateFirstVariant(event, { wrapTarget, wrapInfo }),
wrapInfo,
);
firstOutput = {
...firstOutput,
variants: firstOutput.variants.slice(0, 1).map((variant) => ({ ...variant, params: [] })),
};
trace('agent.generate.first_ready', { id: event.id, count: firstOutput.variants.length });
trace('agent.first_variant.write.start', { id: event.id, file: wrapInfo.file });
if (wrapInfo.previewMode === 'svelte-component') {
await publishSvelteComponentVariants({ tmp, wrapInfo, event, output: firstOutput, writeParams: false });
} else if (wrapInfo.previewMode === 'vue-component') {
await publishVueComponentVariants({ tmp, wrapInfo, event, output: firstOutput, writeParams: false });
} else {
await publishSourceVariants({ tmp, wrapInfo, event, output: firstOutput });
}
await publishVariantProgress({
base,
token,
event,
wrapInfo,
arrivedVariants: firstOutput.variants.length,
signal,
});
trace('agent.first_variant.write.end', { id: event.id, file: wrapInfo.file });
output = normalizeVariantOutput(
await agent.generateRemainingVariants(event, { wrapTarget, wrapInfo, firstOutput }),
wrapInfo,
);
trace('agent.generate.end', { id: event.id, count: output?.variants?.length || 0 });
} else {
output = normalizeVariantOutput(
await agent.generateVariants(event, { wrapTarget, wrapInfo }),
wrapInfo,
);
if (!progressive && atomicDelayMs > 0) {
await new Promise((resolve) => setTimeout(resolve, atomicDelayMs));
}
trace('agent.generate.first_ready', { id: event.id, count: output?.variants?.length || 0 });
if (!progressive || output.variants.length <= 1) {
trace('agent.generate.end', { id: event.id, count: output?.variants?.length || 0 });
}
if (progressive && output.variants.length > 1) {
const initialCount = Math.max(1, Math.min(
Number(progressiveInitialCount) || 1,
output.variants.length - 1,
));
firstOutput = {
...output,
variants: output.variants
.slice(0, initialCount)
.map((variant) => ({ ...variant, params: [] })),
};
trace('agent.first_variant.write.start', { id: event.id, file: wrapInfo.file });
if (wrapInfo.previewMode === 'svelte-component') {
await publishSvelteComponentVariants({ tmp, wrapInfo, event, output: firstOutput, writeParams: false });
} else if (wrapInfo.previewMode === 'vue-component') {
await publishVueComponentVariants({ tmp, wrapInfo, event, output: firstOutput, writeParams: false });
} else {
await publishSourceVariants({ tmp, wrapInfo, event, output: firstOutput });
}
await publishVariantProgress({
base,
token,
event,
wrapInfo,
arrivedVariants: firstOutput.variants.length,
signal,
});
trace('agent.first_variant.write.end', { id: event.id, file: wrapInfo.file });
if (progressiveDelayMs > 0) {
await new Promise((resolve) => setTimeout(resolve, progressiveDelayMs));
}
trace('agent.generate.end', { id: event.id, count: output?.variants?.length || 0 });
}
}
// 2. Agent generates variant content (LLM-pluggable seam)
let output = await agent.generateVariants(event, { wrapTarget, wrapInfo });
output = normalizeVariantOutput(output, wrapInfo);
if (output.variants.length !== event.count) {
log(`warning: agent returned ${output.variants.length} variants, expected ${event.count}`);
}
// 3. Write the complete set into the deterministic preview target.
trace('agent.write.start', { id: event.id, file: wrapInfo.file });
// 3. Write variants into the deterministic preview target.
if (wrapInfo.previewMode === 'svelte-component') {
await publishSvelteComponentVariants({ tmp, wrapInfo, event, output, writeParams: true });
} else if (wrapInfo.previewMode === 'vue-component') {
await publishVueComponentVariants({ tmp, wrapInfo, event, output, writeParams: true });
} else if (progressive) {
await publishSourceVariants({ tmp, wrapInfo, event, output });
await writeSvelteComponentVariants({ tmp, wrapInfo, event, output });
} else {
await spliceVariantsIntoWrapper({ tmp, wrapInfo, sessionId: event.id, output });
}
trace('agent.write.end', { id: event.id, file: wrapInfo.file });
if (progressive) {
await publishVariantProgress({
base,
token,
event,
wrapInfo,
arrivedVariants: output.variants.length,
signal,
revision: 2,
publicationKind: 'params',
});
}
if (process.env.IMPECCABLE_E2E_DEBUG) {
const post = await fs.readFile(path.join(tmp, wrapInfo.file), 'utf-8');
log(`--- post-splice (variants written) ---\n${post}`);
}
// 4. Tell the server we're done (broadcasts SSE done → browser settles to CYCLING)
trace('agent.reply.start', { id: event.id });
await fetch(`${base}/poll`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, type: 'done', sourceEventType: 'generate', id: event.id, file: wrapInfo.file }),
body: JSON.stringify({ token, type: 'done', id: event.id, file: wrapInfo.file }),
signal,
});
trace('agent.reply.end', { id: event.id });
} catch (err) {
if (signal.aborted) return;
if (isExpectedGenerationCancellation(err)) {
trace('agent.generate.canceled', { id: event.id, reason: 'stale_generation_epoch' });
log('generate canceled after Accept/Discard: ' + err.message);
continue;
}
trace('agent.generate.error', { id: event.id, message: err.message });
log('generate failed: ' + err.message);
await fetch(`${base}/poll`, {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({ token, type: 'error', sourceEventType: 'generate', id: event.id, message: err.message }),
body: JSON.stringify({ token, type: 'error', id: event.id, message: err.message }),
signal,
}).catch(() => {});
}
@@ -2046,7 +1740,6 @@ export async function runAgentLoop({
body: JSON.stringify({
token,
type: completionType,
sourceEventType: 'accept',
id: event.id,
file: acceptResult.file,
message: acceptResult.error,
@@ -2076,7 +1769,6 @@ export async function runAgentLoop({
body: JSON.stringify({
token,
type: completionType,
sourceEventType: 'discard',
id: event.id,
file: discardResult.file,
message: discardResult.error,
@@ -2095,10 +1787,6 @@ export async function runAgentLoop({
}
}
export function isExpectedGenerationCancellation(error) {
return /(?:^|\b)stale_generation_epoch(?:\b|$)/.test(String(error?.message || error || ''));
}
async function runPollReply({ tmp, scriptsDir, id, status, message, data }) {
const args = [path.join(scriptsDir, 'live-poll.mjs'), '--reply', id, status];
if (data !== undefined) args.push('--data', JSON.stringify(data));
+25 -76
View File
@@ -192,7 +192,6 @@ const STEER_SYSTEM_INSTRUCTIONS = [
* @property {string=} model Override the selected provider's default model.
* @property {string=} baseURL Override the provider API base URL.
* @property {object=} config Pre-resolved provider config from resolveLlmAgentConfig().
* @property {boolean=} includeLiveSpec Attach the full live.md reference. Defaults to true; latency benchmarks disable it to export only the synthetic element contract.
* @property {(msg: string) => void=} log Optional logger for debug output.
*/
@@ -241,22 +240,14 @@ export async function createLlmAgent(opts = {}) {
const { apiKey, baseURL, model, provider } = config;
const log = opts.log || (() => {});
const liveMd = opts.includeLiveSpec === false ? null : await fs.readFile(LIVE_MD_PATH, 'utf-8');
const liveMd = await fs.readFile(LIVE_MD_PATH, 'utf-8');
const client = new Anthropic({ apiKey, ...(baseURL ? { baseURL } : {}) });
const systemBlocks = (instructions) => [
{
type: 'text',
text: liveMd ? instructions : instructions.replace(/\n\nCONTEXT —[^\n]+$/, ''),
},
...(liveMd ? [{ type: 'text', text: liveMd, cache_control: { type: 'ephemeral' } }] : []),
];
return {
async generateVariants(event, context = {}) {
const isInsert = event.mode === 'insert';
const baseUserMessage = [
`Produce variants for the following ${isInsert ? 'insert request' : 'pick'}. Reply with the JSON object only — no prose.`,
progressiveVariantGuidance(event),
'',
'```json',
JSON.stringify(buildVariantRequestPayload(event, context), null, 2),
@@ -265,7 +256,6 @@ export async function createLlmAgent(opts = {}) {
let userMessage = baseUserMessage;
for (let attempt = 0; attempt < MANUAL_EDIT_RESPONSE_MAX_ATTEMPTS; attempt += 1) {
const lastAttempt = attempt + 1 >= MANUAL_EDIT_RESPONSE_MAX_ATTEMPTS;
let response;
try {
response = await client.messages.create(
@@ -273,10 +263,15 @@ export async function createLlmAgent(opts = {}) {
model,
temperature: 0,
max_tokens: 16000,
// When present, live.md is the final cacheable stable prefix.
// Benchmarks omit it so external payloads contain only the
// synthetic element contract and per-run event.
system: systemBlocks(VARIANT_SYSTEM_INSTRUCTIONS),
system: [
{ type: 'text', text: VARIANT_SYSTEM_INSTRUCTIONS },
// Cacheable: the entire stable prefix (instructions + spec) is
// cached up to this breakpoint. The user message holds all the
// per-call volatile content. DeepSeek compatibility support is
// provider-reported and best-effort; the usage log below tells us
// whether cache reads/writes actually happened.
{ type: 'text', text: liveMd, cache_control: { type: 'ephemeral' } },
],
messages: [{ role: 'user', content: userMessage }],
},
{
@@ -285,7 +280,7 @@ export async function createLlmAgent(opts = {}) {
},
);
} catch (err) {
if (lastAttempt) throw err;
if (attempt === 1) throw err;
log(`variant request failed; retrying: ${err.message}`);
userMessage = [
baseUserMessage,
@@ -305,7 +300,7 @@ export async function createLlmAgent(opts = {}) {
`provider=${provider} model=${model} attempt=${attempt + 1} input=${inputTokens} output=${outputTokens} cache_read=${cacheRead} cache_write=${cacheWrite}`,
);
if (!response || !Array.isArray(response.content)) {
if (lastAttempt) throw new Error('LLM agent: provider returned an empty variant response');
if (attempt === 1) throw new Error('LLM agent: provider returned an empty variant response');
log('variant response validation failed; retrying: provider returned an empty response');
userMessage = [
baseUserMessage,
@@ -325,7 +320,7 @@ export async function createLlmAgent(opts = {}) {
try {
parsed = parseVariantResponse(text);
} catch (err) {
if (lastAttempt) throw err;
if (attempt === 1) throw err;
log(`variant response validation failed; retrying: ${err.message.split('\n')[0]}`);
userMessage = [
baseUserMessage,
@@ -337,13 +332,11 @@ export async function createLlmAgent(opts = {}) {
continue;
}
const validationError = validateVariantCount(parsed, event)
|| validateProgressiveVariantOutput(parsed, event)
|| (isInsert
? validateInsertVariantOutput(parsed, event)
: (validateVariantVisibleCopy(parsed, event.element) || validateVariantMaterialChange(parsed, event.element)));
const validationError = isInsert
? validateInsertVariantOutput(parsed, event)
: (validateVariantVisibleCopy(parsed, event.element) || validateVariantMaterialChange(parsed, event.element));
if (!validationError) return parsed;
if (lastAttempt) throw new Error(`LLM agent: ${validationError}`);
if (attempt === 1) throw new Error(`LLM agent: ${validationError}`);
log(`variant validation failed; retrying: ${validationError}`);
if (isInsert) {
@@ -418,7 +411,10 @@ export async function createLlmAgent(opts = {}) {
model,
temperature: 0,
max_tokens: 16000,
system: systemBlocks(MANUAL_EDIT_SYSTEM_INSTRUCTIONS),
system: [
{ type: 'text', text: MANUAL_EDIT_SYSTEM_INSTRUCTIONS },
{ type: 'text', text: liveMd, cache_control: { type: 'ephemeral' } },
],
messages: [{ role: 'user', content: userMessage }],
},
{
@@ -546,7 +542,10 @@ export async function createLlmAgent(opts = {}) {
const response = await client.messages.create({
model,
max_tokens: 4096,
system: systemBlocks(STEER_SYSTEM_INSTRUCTIONS),
system: [
{ type: 'text', text: STEER_SYSTEM_INSTRUCTIONS },
{ type: 'text', text: liveMd, cache_control: { type: 'ephemeral' } },
],
messages: [{ role: 'user', content: userMessage }],
});
@@ -673,7 +672,6 @@ export function buildVariantRequestPayload(event, context = {}) {
action: event?.action,
freeformPrompt: event?.freeformPrompt,
count: event?.count,
progressive: event?.progressive,
element: isInsert ? null : {
outerHTML: event?.element?.outerHTML,
tagName: event?.element?.tagName,
@@ -693,31 +691,6 @@ export function buildVariantRequestPayload(event, context = {}) {
};
}
export function progressiveVariantGuidance(event = {}) {
if (event.progressive?.phase === 'first') {
return [
'PROGRESSIVE FIRST DELIVERY:',
`- Return exactly ${event.count} variant now.`,
'- Return params: [] for this variant; tunable parameters are generated in the final phase.',
'- The innerHtml must be materially different from the picked source, not merely paired with different CSS.',
'- For a bare-text element, preserve the full exact copy in one child span inside the unchanged root tag/class.',
].join('\n');
}
if (event.progressive?.phase === 'remaining') {
return [
'PROGRESSIVE FINAL DELIVERY:',
`- Return the complete final set of exactly ${event.count} variants, including variant 1.`,
'- progressive.firstVariant is the already-visible variant 1. Keep its innerHtml exactly unchanged and add its deferred params now.',
...(event.progressive.omitFirstVariantCss ? [
'- Variant 1 CSS is already published and immutable. Do not repeat or modify any scopedCss rule for data-impeccable-variant="1"; return scopedCss rules for variants 2+ only.',
] : []),
'- Generate the remaining distinct variants and their params in the other array positions.',
'- Every remaining variant innerHtml must be materially changed too; for bare text, wrap the full exact copy in one child span with a distinct class instead of relying on CSS alone.',
].join('\n');
}
return '';
}
/**
* Parse and validate a model response into the variant-output schema. Throws
* with a `Parsed (first 500 chars): ...` echo on every schema failure so the
@@ -877,30 +850,6 @@ export function validateInsertVariantOutput(parsed, event = {}) {
return null;
}
export function validateVariantCount(parsed, event = {}) {
const expected = Number(event.count);
if (!Number.isInteger(expected) || expected < 1) return 'event count must be a positive integer';
const actual = Array.isArray(parsed?.variants) ? parsed.variants.length : 0;
return actual === expected ? null : `expected exactly ${expected} variants, received ${actual}`;
}
export function validateProgressiveVariantOutput(parsed, event = {}) {
if (event.progressive?.phase === 'first') {
const hasEarlyParams = (parsed.variants || []).some((variant) => Array.isArray(variant.params) && variant.params.length > 0);
return hasEarlyParams ? 'progressive first delivery must defer params with an empty params array' : null;
}
if (event.progressive?.phase === 'remaining' && event.progressive.firstVariant?.innerHtml) {
const expected = String(event.progressive.firstVariant.innerHtml).trim();
const actual = String(parsed.variants?.[0]?.innerHtml || '').trim();
if (actual !== expected) return 'progressive final delivery must preserve variant 1 innerHtml exactly';
if (event.progressive.omitFirstVariantCss && /\[data-impeccable-variant\s*=\s*["']1["'][^\]]*\]/.test(parsed.scopedCss || '')) {
return 'progressive final delivery must omit already-published variant 1 CSS';
}
return null;
}
return null;
}
export function validateVariantMaterialChange(parsed, element) {
const originalHtml = normalizeVariantHtml(element?.outerHTML || '');
if (!originalHtml) return null;
+20 -92
View File
@@ -14,7 +14,7 @@
*/
import { execFileSync, spawn } from 'node:child_process';
import { cpSync, existsSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { cpSync, mkdirSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs';
import { tmpdir } from 'node:os';
import { join, dirname } from 'node:path';
import { fileURLToPath } from 'node:url';
@@ -32,7 +32,8 @@ export { SCRIPTS_DIR, FIXTURES_DIR, REPO_ROOT };
// Stage
// ---------------------------------------------------------------------------
export function stageFixture(name, fixture, { fixtureRoot = join(FIXTURES_DIR, name) } = {}) {
export function stageFixture(name, fixture) {
const fixtureRoot = join(FIXTURES_DIR, name);
const gitignore = readFileSync(join(fixtureRoot, 'gitignore.txt'), 'utf-8');
const tmp = mkdtempSync(join(tmpdir(), 'impeccable-e2e-'));
@@ -55,7 +56,6 @@ export function runInstall(tmp, command, { timeoutMs = readTimeoutEnv('IMPECCABL
const installArgs = addNpmInstallDefaults(cmd, args);
try {
execFileSync(cmd, installArgs, { cwd: tmp, stdio: 'inherit', timeout: timeoutMs });
repairMissingRollupOptionalBinary(tmp, { timeoutMs });
} catch (err) {
if (err.signal === 'SIGTERM' || err.signal === 'SIGKILL' || err.killed) {
err.message = `fixture dependency install timed out after ${timeoutMs}ms: ${cmd} ${installArgs.join(' ')}`;
@@ -64,26 +64,11 @@ export function runInstall(tmp, command, { timeoutMs = readTimeoutEnv('IMPECCABL
}
}
function repairMissingRollupOptionalBinary(tmp, { timeoutMs }) {
if (process.platform !== 'darwin' || process.arch !== 'arm64') return;
const rollupPackage = join(tmp, 'node_modules', 'rollup', 'package.json');
const nativePackage = join(tmp, 'node_modules', '@rollup', 'rollup-darwin-arm64', 'package.json');
if (!existsSync(rollupPackage) || existsSync(nativePackage)) return;
const version = JSON.parse(readFileSync(rollupPackage, 'utf-8')).version;
execFileSync('npm', [
'install', '--no-save', '--no-audit', '--no-fund', '--no-progress',
`@rollup/rollup-darwin-arm64@${version}`,
], { cwd: tmp, stdio: 'inherit', timeout: timeoutMs });
}
function addNpmInstallDefaults(cmd, args) {
if (cmd !== 'npm') return args;
if (!['install', 'ci'].includes(args[0])) return args;
const out = [...args];
// npm can omit platform-specific Rollup binaries unless optional
// dependencies are requested explicitly (npm/cli#4828). Astro/Vite then
// fail before Live starts on fresh staged fixtures.
for (const flag of ['--no-progress', '--include=optional']) {
for (const flag of ['--prefer-offline', '--no-progress']) {
if (!out.some((arg) => arg === flag || arg.startsWith(flag + '='))) out.push(flag);
}
return out;
@@ -215,57 +200,29 @@ export async function stopDevServer(child) {
* @param {object} opts
* @param {string} opts.name fixture name
* @param {object} opts.fixture fixture.json contents
* @param {string=} opts.fixtureRoot fixture directory; defaults to the public framework fixture tree
* @param {import('playwright').Browser} opts.browser shared browser instance
* @param {object} opts.agent VariantAgent (defaults to fake)
* @param {object|function=} opts.wrapTarget live-wrap target or event mapper
* @param {(context: object) => Promise<object|void>} [opts.startWorker]
* Optional production worker factory. Return {stop, done}; when used,
* omit `agent` so the deterministic in-process loop is not started.
* @param {(context: object) => Promise<void>|void} [opts.prepareTmp]
* @param {(msg: string) => void} [opts.log]
*/
export async function bootFixtureSession({
name,
fixture,
fixtureRoot,
browser,
agent,
wrapTarget,
startWorker,
prepareTmp,
log = () => {},
trace = () => {},
progressive = false,
progressiveDelayMs = 0,
progressiveInitialCount = 1,
atomicDelayMs = 0,
keepTmp = false,
}) {
export async function bootFixtureSession({ name, fixture, browser, agent, wrapTarget, log = () => {} }) {
const runtime = fixture.runtime;
if (!runtime) throw new Error(`fixture ${name} has no runtime block`);
const tmp = stageFixture(name, fixture, { fixtureRoot });
const tmp = stageFixture(name, fixture);
let live;
let dev;
let agentAbort;
let agentDone;
let externalWorker;
let ctx;
const teardown = async () => {
try { if (ctx) await ctx.close(); } catch {}
try { if (agentAbort) agentAbort.abort(); } catch {}
try { if (agentDone) await agentDone.catch(() => {}); } catch {}
try { if (externalWorker?.stop) await externalWorker.stop(); } catch {}
try { if (externalWorker?.done) await externalWorker.done.catch(() => {}); } catch {}
try { if (dev?.child) await stopDevServer(dev.child); } catch {}
try { if (live) stopLiveServer(tmp); } catch {}
if (!keepTmp) {
try { rmSync(tmp, { recursive: true, force: true }); } catch {}
} else {
log(`kept staged fixture at ${tmp}`);
}
try { rmSync(tmp, { recursive: true, force: true }); } catch {}
};
const stopLiveForDeferredWork = () => {
@@ -276,67 +233,41 @@ export async function bootFixtureSession({
try {
const startedAt = Date.now();
if (prepareTmp) await prepareTmp({ tmp, fixture, scriptsDir: SCRIPTS_DIR, trace, log });
trace('setup.install.start', { fixture: name });
log(`installing deps`);
runInstall(tmp, runtime.install);
trace('setup.install.end', { fixture: name });
log(`deps installed in ${formatDuration(Date.now() - startedAt)}`);
const liveStartedAt = Date.now();
trace('setup.live_server.start', { fixture: name });
log(`starting live-server`);
live = startLiveServer(tmp);
trace('setup.live_server.end', { fixture: name, port: live.port });
log(`live-server ready in ${formatDuration(Date.now() - liveStartedAt)}`);
if (startWorker) {
trace('setup.worker.start', { fixture: name });
externalWorker = await startWorker({ tmp, fixture, scriptsDir: SCRIPTS_DIR, live, trace, log });
trace('setup.worker.end', { fixture: name });
}
const injectStartedAt = Date.now();
trace('setup.inject.start', { fixture: name });
log(`live-inject --port ${live.port}`);
const injectResult = runInject(tmp, live.port);
if (!injectResult.ok) throw new Error('live-inject failed: ' + JSON.stringify(injectResult));
trace('setup.inject.end', { fixture: name, files: injectResult.files || injectResult.pageFiles || [] });
log(`live-inject complete in ${formatDuration(Date.now() - injectStartedAt)}`);
const devStartedAt = Date.now();
trace('setup.dev_server.start', { fixture: name });
log(`spawning dev server: ${runtime.devCommand.join(' ')}`);
dev = startDevServer(tmp, runtime);
const { port: devPort } = await dev.ready;
trace('setup.dev_server.end', { fixture: name, port: devPort });
log(`dev server ready on ${devPort} in ${formatDuration(Date.now() - devStartedAt)}`);
// Agent loop runs concurrently — abort on teardown.
if (agent) {
agentAbort = new AbortController();
const loopOptions = {
tmp,
scriptsDir: SCRIPTS_DIR,
port: live.port,
token: live.token,
agent,
wrapTarget,
signal: agentAbort.signal,
trace,
progressive,
progressiveDelayMs,
progressiveInitialCount,
atomicDelayMs,
steerSourceFile: runtime.steer?.sourceFile,
steerTarget: runtime.steer?.target,
};
const loops = [runAgentLoop({ ...loopOptions, log: (m) => log('[worker] ' + m) })];
if (progressive) {
loops.push(runAgentLoop({ ...loopOptions, log: (m) => log('[supervisor] ' + m) }));
}
agentDone = Promise.all(loops);
}
agentAbort = new AbortController();
agentDone = runAgentLoop({
tmp,
scriptsDir: SCRIPTS_DIR,
port: live.port,
token: live.token,
agent,
wrapTarget,
signal: agentAbort.signal,
log: (m) => log('[agent] ' + m),
steerSourceFile: runtime.steer?.sourceFile,
steerTarget: runtime.steer?.target,
});
const scheme = runtime.scheme || 'http';
ctx = await browser.newContext({
@@ -352,12 +283,10 @@ export async function bootFixtureSession({
});
const pageStartedAt = Date.now();
trace('setup.page_load.start', { fixture: name });
await page.goto(`${scheme}://127.0.0.1:${devPort}`, {
waitUntil: 'domcontentloaded',
timeout: 30_000,
});
trace('setup.page_load.end', { fixture: name });
log(`page loaded in ${formatDuration(Date.now() - pageStartedAt)}`);
return {
@@ -366,7 +295,6 @@ export async function bootFixtureSession({
ctx,
dev,
live,
worker: externalWorker,
consoleErrors,
stopLiveServer: stopLiveForDeferredWork,
teardown,
+4 -58
View File
@@ -424,23 +424,7 @@ export async function pickElement(page, selector, opts = {}) {
if (visible) break;
await resetPickMode(page);
if (attempt === 2) {
const snapshot = await page.evaluate(({ selector, barSel, pickSel }) => {
const target = document.querySelector(selector);
const rect = target?.getBoundingClientRect();
const hit = rect ? document.elementFromPoint(rect.x + rect.width / 2, rect.y + rect.height / 2) : null;
const query = window.__impeccableLiveQuery || ((sel) => document.querySelector(sel));
const bar = query(barSel);
const pick = query(pickSel);
return {
liveState: window.__IMPECCABLE_LIVE_STATE__ || null,
target: target ? { tag: target.tagName, classes: target.className, rect: rect?.toJSON?.() || null } : null,
hit: hit ? { tag: hit.tagName, classes: hit.className, text: (hit.textContent || '').slice(0, 80) } : null,
pickActive: pick?.dataset.active || null,
bar: bar ? { display: bar.style.display, text: bar.textContent } : null,
debugState: window.__IMPECCABLE_LIVE_CHROME_CORE__?.debugState?.() || null,
};
}, { selector, barSel: BAR_ID, pickSel: PICK_TOGGLE_ID }).catch((error) => ({ error: error.message }));
throw new Error(`pick did not open configure bar for ${selector}: ${JSON.stringify(snapshot)}`);
await page.waitForSelector(BAR_ID, { state: 'visible', timeout: 1 });
}
}
// Wait specifically for the Configure-row submit button to be in the bar.
@@ -544,36 +528,6 @@ export async function setCount(page, count) {
throw new Error(`could not cycle count to ${count}`);
}
/** Select a named Impeccable sub-command from the configure-row picker. */
export async function selectAction(page, action) {
const pickerSelector = '#impeccable-live-picker';
const opened = await page.evaluate(({ barSel, pickerSel }) => {
const query = window.__impeccableLiveQuery || ((selector) => document.querySelector(selector));
const bar = query(barSel);
const picker = query(pickerSel);
const actionControl = [...(bar?.querySelectorAll('button') || [])]
.find((button) => (button.textContent || '').includes('\u25BE'));
if (!actionControl || !picker) return false;
actionControl.click();
return true;
}, { barSel: BAR_ID, pickerSel: pickerSelector });
if (!opened) throw new Error('could not open Live action picker');
await page.waitForFunction((selector) => {
const picker = window.__impeccableLiveQuery(selector);
return picker && picker.style.display !== 'none';
}, pickerSelector, { timeout: 5_000 });
const selected = await page.evaluate(({ pickerSel, value }) => {
const picker = window.__impeccableLiveQuery(pickerSel);
const chip = picker?.querySelector(`button[data-action="${CSS.escape(value)}"]`);
if (!chip) return false;
chip.click();
return true;
}, { pickerSel: pickerSelector, value: action });
if (!selected) throw new Error(`Live action ${JSON.stringify(action)} is unavailable`);
}
/**
* Click Go. Browser POSTs the generate event; the agent picks it up. Headed
* browser runs can occasionally accept the click without leaving configure
@@ -624,14 +578,7 @@ export async function waitForCycling(page, expectedCount, { timeout = 30_000 } =
// Counter format: "1/3", "2/3" etc. Look for any "i/N" with N matching.
const m = text.match(/(\d+)\s*\/\s*(\d+)/);
if (!m) return false;
const wrapper = window.__impeccableLiveQuery('[data-impeccable-variants]');
const debugState = window.__IMPECCABLE_LIVE_CHROME_CORE__?.debugState?.();
const arrived = /^(?:svelte|vue)-component$/.test(wrapper?.dataset.impeccablePreview || '')
? Number(debugState?.arrivedVariants || 0)
: wrapper
? wrapper.querySelectorAll('[data-impeccable-variant]:not([data-impeccable-variant="original"])').length
: 0;
return parseInt(m[2], 10) === expected && arrived >= expected;
return parseInt(m[2], 10) === expected;
},
{ barSel: BAR_ID, expected: expectedCount },
{ timeout },
@@ -643,7 +590,7 @@ export async function waitForCycling(page, expectedCount, { timeout = 30_000 } =
const root = window.__IMPECCABLE_LIVE_CHROME_CORE__?.root?.() || window.__IMPECCABLE_LIVE_UI_ROOT__ || null;
const bar = query(barSel);
const toast = query('#impeccable-live-toast');
const wrapper = query('[data-impeccable-variants]');
const wrapper = document.querySelector('[data-impeccable-variants]');
return {
liveInit: window.__IMPECCABLE_LIVE_INIT__,
adapter: window.__IMPECCABLE_LIVE_ADAPTER__,
@@ -804,8 +751,7 @@ async function ensureVisibleVariant(page, expectedVariant) {
*/
export async function clickDiscard(page) {
// The discard button has just a "✕" glyph as text content.
if (await dispatchBarButton(page, '✕')) return;
await clickBarButton(page, '✕');
await page.locator(`${BAR_ID} button`, { hasText: '✕' }).click();
}
export async function clickEditCopy(page) {