Files
pbakaus_impeccable/tests/live-accept.test.mjs
T
Paul BakausandClaude c7b67b3832 Drop the live generator subagent; fix the artifact decoy that broke accept
The first real Claude Code Live run failed, and the subagent was not the cause.

Root cause: progressive publication stages each revision as
`.impeccable/live/artifacts/<id>-r<n>.<source-ext>`, nothing ever deleted them,
and findSessionFile's walker skipped only node_modules/.git/dist/build. It
searches src, app, pages, ... then `.`; a project whose source is not under one of
those (this repo's own site lives in site/pages/) falls through to the `.` walk,
where dot-directories sort before letters. So accept found the artifact instead of
the real file. Two outcomes, both reproduced: where isGeneratedFile returns true
it declines with mode: 'fallback' (what the run hit, after which the agent
hand-carbonized several hundred lines across three stylesheets, including
unrequested drive-by edits); where it returns false, accept writes the variant
into the throwaway artifact and reports handled: true while real source never
changes.

The E2E suite could not have caught this. Every fixture puts source under `src/`,
which is searched before the `.` walk can reach `.impeccable`. Five framework
fixtures and three progressive scenarios pass because of fixture layout, not
because the path works. I read that as evidence and shouldn't have.

- Never search `.impeccable`: it is Impeccable's own state, never project source.
- Retire a session's staged artifacts on accept/discard, so they cannot outlive
  the session and become a decoy for anything else that walks the tree.
- Regression tests use a site/pages layout with artifacts present. All three fail
  against the previous code.

Generator subagent removed, on both harnesses:
The parent must hand-compress the design system into the handoff, and compression
is lossy. Measured on the real run: a 6,826-char handoff carrying exactly one
token reference, after the parent had itself read kinpaku-tokens.css. The subagent
then spent 3 of its first 9 turns hunting DESIGN.md, gave up, and emitted 0
var(--token) uses and 22 raw oklch literals — violating its own spec's "Never
invent raw colors when tokens exist" — including a 1:1 gold-on-gold contrast bug.
Isolation is not a benefit here; knowing the design system is the job. Generation
stays in the main thread, which already holds the tokens and writes them from the
first byte, so carbonize is a move rather than a translation.

Copy edits keep their subagent: applying a known set of ops to a named file is
self-contained, so an isolated context costs nothing. That is the line.

Progressive delivery stays for Codex and Claude Code, main-thread driven. Claude
Code keeps the full benefit because its poll is a background task. Codex's poll
blocks the foreground, so with no subagent the user sees variant 1 early via HMR
but cannot accept it until the trio finishes; that is the cost of the
simplification and it is worth naming.

Prepared with AI assistance under maintainer direction.

Co-Authored-By: Claude <noreply@anthropic.com>
2026-07-17 18:56:23 -07:00

768 lines
36 KiB
JavaScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* Tests for live-accept.mjs — the deterministic accept/discard helper.
* Run with: node --test tests/live-accept.test.mjs
*/
import { describe, it, beforeEach, afterEach } from 'node:test';
import assert from 'node:assert/strict';
import { existsSync, mkdirSync, mkdtempSync, realpathSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
import { dirname, join, resolve } from 'node:path';
import { tmpdir } from 'node:os';
import { fileURLToPath } from 'node:url';
import { execFileSync, spawnSync } from 'node:child_process';
import { scaffoldSourceArtifactSession } from '../skill/scripts/live/source-artifact.mjs';
import { sourceLockPath } from '../skill/scripts/live/source-lock.mjs';
const __dirname = dirname(fileURLToPath(import.meta.url));
const ACCEPT = resolve(__dirname, '..', 'skill/scripts/live-accept.mjs');
const WRAP = resolve(__dirname, '..', 'skill/scripts/live-wrap.mjs');
function runAccept(cwd, args) {
try {
const out = execFileSync('node', [ACCEPT, ...args], {
cwd,
encoding: 'utf-8',
stdio: ['ignore', 'pipe', 'pipe'],
});
return JSON.parse(out.trim());
} catch (err) {
const body = err.stdout?.toString().trim() || err.stderr?.toString().trim() || '';
return JSON.parse(body || '{}');
}
}
// The failure that broke the first real Claude Code Live run. Progressive
// publication stages `.impeccable/live/artifacts/<id>-r<n>.<source-ext>`, which
// carries the session marker. findSessionFile walks `src`, `app`, `pages`, ... and
// then `.`; a project whose source is not under one of those (this repo's own site
// lives in `site/pages/`) falls through to the `.` walk, where dot-directories sort
// before letters — so the artifact was found before the real file.
describe('live-accept — marker search must ignore Impeccable state', () => {
let tmp;
beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), 'impeccable-accept-decoy-')); });
afterEach(() => rmSync(tmp, { recursive: true, force: true }));
const SOURCE = [
'<main>',
'<!-- impeccable-variants-start ab12cd34 -->',
'<div data-impeccable-variant="original">ORIGINAL</div>',
'<div data-impeccable-variant="1">VARIANT ONE</div>',
'<!-- impeccable-variants-end ab12cd34 -->',
'</main>',
'',
].join('\n');
function seed({ revisions = 3 } = {}) {
mkdirSync(join(tmp, 'site', 'pages'), { recursive: true });
mkdirSync(join(tmp, '.impeccable', 'live', 'artifacts'), { recursive: true });
writeFileSync(join(tmp, 'site', 'pages', 'index.astro'), SOURCE);
for (let r = 1; r <= revisions; r += 1) {
writeFileSync(join(tmp, '.impeccable', 'live', 'artifacts', `ab12cd34-r${r}.astro`), SOURCE);
}
}
it('accepts into real source when a staged artifact carries the same marker', () => {
seed();
const result = runAccept(tmp, ['--id', 'ab12cd34', '--variant', '1']);
assert.equal(result.handled, true, JSON.stringify(result));
assert.equal(
result.file,
'site/pages/index.astro',
'accept must resolve the project file, not the .impeccable artifact decoy',
);
const source = readFileSync(join(tmp, 'site', 'pages', 'index.astro'), 'utf-8');
assert.match(source, /VARIANT ONE/);
assert.doesNotMatch(source, /impeccable-variants-start/, 'the wrapper must be gone from real source');
});
it('retires the sessions staged artifacts and leaves other sessions alone', () => {
seed();
const dir = join(tmp, '.impeccable', 'live', 'artifacts');
writeFileSync(join(dir, 'ffff0000-r1.astro'), SOURCE);
runAccept(tmp, ['--id', 'ab12cd34', '--variant', '1']);
assert.equal(existsSync(join(dir, 'ab12cd34-r1.astro')), false, 'own artifacts must not outlive the session');
assert.equal(existsSync(join(dir, 'ab12cd34-r3.astro')), false);
assert.equal(existsSync(join(dir, 'ffff0000-r1.astro')), true, 'another sessions artifacts must survive');
});
it('discards into real source with an artifact decoy present', () => {
seed({ revisions: 1 });
const result = runAccept(tmp, ['--id', 'ab12cd34', '--discard']);
assert.equal(result.handled, true, JSON.stringify(result));
assert.equal(result.file, 'site/pages/index.astro');
assert.match(readFileSync(join(tmp, 'site', 'pages', 'index.astro'), 'utf-8'), /ORIGINAL/);
});
});
describe('live-accept — session id validation', () => {
let tmp;
beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), 'impeccable-accept-id-')); });
afterEach(() => rmSync(tmp, { recursive: true, force: true }));
// --id becomes a path segment for the accept receipt. Traversal here wrote
// JSON to arbitrary absolute paths (e.g. `--id ../../../../etc/evil`).
for (const id of ['../../../../etc/evil', 'a/b', '..', 'a\\b', '']) {
it(`refuses --id ${JSON.stringify(id)} without writing a receipt`, () => {
const res = spawnSync('node', [ACCEPT, '--id', id, '--discard'], {
cwd: tmp,
encoding: 'utf-8',
});
assert.equal(res.status, 1, 'must exit non-zero');
assert.match(res.stderr, /Invalid --id|Missing --id/);
assert.equal(existsSync(join(tmp, '.impeccable', 'live', 'accept-receipts')), false);
});
}
it('still accepts a well-formed id', () => {
const res = spawnSync('node', [ACCEPT, '--id', 'ab12cd34', '--discard'], {
cwd: tmp,
encoding: 'utf-8',
});
assert.doesNotMatch(res.stderr || '', /Invalid --id/);
});
// --variant is interpolated into a RegExp and into the markup written back to
// source. `.*` matched the `original` block first, so the CLI reported a
// successful accept while actually restoring the original.
for (const variant of ['.*', '[12]', 'original', '1e2', '']) {
it(`refuses --variant ${JSON.stringify(variant)} rather than matching by regex`, () => {
writeFileSync(join(tmp, 'page.html'), [
'<!-- impeccable-variants-start ab12cd34 -->',
'<div data-impeccable-variant="original">ORIGINAL CONTENT</div>',
'<div data-impeccable-variant="1">VARIANT ONE</div>',
'<!-- impeccable-variants-end ab12cd34 -->',
'',
].join('\n'));
const res = spawnSync('node', [ACCEPT, '--id', 'ab12cd34', '--variant', variant], {
cwd: tmp,
encoding: 'utf-8',
});
assert.equal(res.status, 1);
assert.match(res.stderr, /Invalid --variant|Need --discard/);
assert.match(
readFileSync(join(tmp, 'page.html'), 'utf-8'),
/impeccable-variants-start/,
'a rejected variant must leave the wrapper untouched',
);
});
}
});
describe('live-accept — isolated source artifacts', () => {
let tmp;
beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), 'impeccable-accept-isolated-')); });
afterEach(() => { rmSync(tmp, { recursive: true, force: true }); });
function scaffold(id) {
const original = '<main>\n <section class="hero"><h1>Original</h1></section>\n</main>\n';
writeFileSync(join(tmp, 'page.html'), original);
const session = scaffoldSourceArtifactSession({
id,
count: 2,
sourceFile: 'page.html',
sourceStartLine: 2,
sourceEndLine: 2,
originalSource: '<section class="hero"><h1>Original</h1></section>',
previewContent: `<main>
<!-- impeccable-variants-start ${id} -->
<div data-impeccable-variants="${id}" data-impeccable-variant-count="2" style="display: contents">
<div data-impeccable-variant="original"><section class="hero"><h1>Original</h1></section></div>
<div data-impeccable-variant="1"><section class="hero"><h1>Accepted one</h1></section></div>
<div data-impeccable-variant="2"><section class="hero"><h1>Accepted two</h1></section></div>
</div>
<!-- impeccable-variants-end ${id} -->
</main>
`,
cwd: tmp,
});
return { original, session };
}
it('accepts one preview into true source exactly once', () => {
const { session } = scaffold('isolatedaccept');
const result = runAccept(tmp, ['--id', 'isolatedaccept', '--variant', '2']);
assert.equal(result.handled, true, JSON.stringify(result));
const source = readFileSync(join(tmp, 'page.html'), 'utf-8');
assert.match(source, /Accepted two/);
assert.doesNotMatch(source, /Accepted one|data-impeccable-variant/);
assert.equal(existsSync(join(tmp, session.sessionDir)), false);
});
it('discards the preview instantly without touching true source', () => {
const { original, session } = scaffold('isolateddiscard');
const result = runAccept(tmp, ['--id', 'isolateddiscard', '--discard']);
assert.equal(result.handled, true, JSON.stringify(result));
assert.equal(readFileSync(join(tmp, 'page.html'), 'utf-8'), original);
assert.equal(existsSync(join(tmp, session.sessionDir)), false);
});
// reference/live.md routes on `mode`: without it the agent is told "manual
// cleanup: read file, find markers, edit". There are no markers in source for
// an isolated preview, so every failure here must self-describe as mode:error.
it('marks a failed artifact accept as mode:error, not a manual handoff', () => {
scaffold('isolatedmissing');
const result = runAccept(tmp, ['--id', 'isolatedmissing', '--variant', '9']);
assert.equal(result.handled, false, JSON.stringify(result));
assert.equal(result.mode, 'error', 'the agent must not be told to hand-edit a source file with no markers');
assert.equal(result.previewMode, 'source-artifact');
});
it('marks an artifact accept blocked by the source lock as mode:error', () => {
const { original } = scaffold('isolatedlockacc');
const realTmp = realpathSync(tmp);
const lockPath = sourceLockPath(join(realTmp, 'page.html'), realTmp);
mkdirSync(dirname(lockPath), { recursive: true });
writeFileSync(lockPath, JSON.stringify({
owner: 'generation:isolatedlockacc:1', token: 'other', pid: process.pid, at: Date.now(),
}) + '\n');
const result = runAccept(tmp, ['--id', 'isolatedlockacc', '--variant', '1']);
assert.equal(result.handled, false, JSON.stringify(result));
assert.equal(result.mode, 'error');
assert.equal(result.error, 'source_locked');
assert.equal(readFileSync(join(tmp, 'page.html'), 'utf-8'), original, 'source must be untouched');
});
// Every other discard path (Vue, Svelte, plain wrapper) takes the source lock.
// This one deleted the preview bare, so it could pull the artifact out from
// under an in-flight publisher instead of serializing behind it.
it('serializes the discard behind a publisher holding the source lock', () => {
const { original, session } = scaffold('isolatedlocked');
// realpath: on macOS mkdtemp hands back /var/... while the child process's
// cwd resolves to /private/var/..., and the lock digest hashes the absolute
// path. Hash the same string the child will.
const realTmp = realpathSync(tmp);
const lockPath = sourceLockPath(join(realTmp, 'page.html'), realTmp);
mkdirSync(dirname(lockPath), { recursive: true });
// A live holder: process.pid is alive, so the lock is not stale.
writeFileSync(lockPath, JSON.stringify({
owner: 'generation:isolatedlocked:1', token: 'other', pid: process.pid, at: Date.now(),
}) + '\n');
const result = runAccept(tmp, ['--id', 'isolatedlocked', '--discard']);
assert.equal(result.handled, false, JSON.stringify(result));
assert.equal(result.error, 'source_locked');
assert.equal(
existsSync(join(tmp, session.sessionDir)),
true,
'the preview must survive: deleting it under the publisher is the race',
);
assert.equal(readFileSync(join(tmp, 'page.html'), 'utf-8'), original);
});
});
describe('live-accept — style-element edge cases', () => {
let tmp;
beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), 'impeccable-accept-test-')); });
afterEach(() => { rmSync(tmp, { recursive: true, force: true }); });
// Historical bug: extractVariant flipped into "inStyle" mode on <style and
// scanned for </style> line-by-line. JSX self-closing <style ... /> has no
// separate closer, so it got stuck forever and missed data-impeccable-variant
// divs that came after.
it('finds the accepted variant after a JSX self-closing <style /> block', () => {
const html = `<body>
<!-- impeccable-variants-start SELFC -->
<div data-impeccable-variants="SELFC" data-impeccable-variant-count="3" style="display: contents">
<div data-impeccable-variant="original">
<p class="hook">original text</p>
</div>
<style data-impeccable-css="SELFC" dangerouslySetInnerHTML={{ __html: '@scope ([data-impeccable-variant="1"]) { .hook { color: red; } }' }} />
<div data-impeccable-variant="1">
<p class="hook">variant one</p>
</div>
<div data-impeccable-variant="2" style="display: none">
<p class="hook">variant two</p>
</div>
<div data-impeccable-variant="3" style="display: none">
<p class="hook">variant three</p>
</div>
</div>
<!-- impeccable-variants-end SELFC -->
</body>`;
writeFileSync(join(tmp, 'page.html'), html);
const result = runAccept(tmp, ['--id', 'SELFC', '--variant', '2']);
assert.equal(result.handled, true, `accept should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
// Self-closing style has no extractable CSS body, so there's nothing to carbonize —
// no carbonize block, no data-impeccable-variant wrapper (it would serve no purpose).
assert.ok(!after.includes('impeccable-carbonize-start'), 'no carbonize block (self-closing style has no body)');
assert.ok(!after.includes('impeccable-variants-start'), 'variant markers removed');
assert.ok(after.includes('variant two'), 'variant 2 content kept');
assert.ok(!after.includes('variant three'), 'other variant content dropped');
assert.ok(!after.includes('variant one'), 'other variant content dropped');
assert.ok(!after.includes('original text'), 'original content dropped');
});
it('replays a durable receipt when Accept is retried after source was already written', () => {
const html = `<body>
<!-- impeccable-variants-start RECEIPT1 -->
<div data-impeccable-variants="RECEIPT1" data-impeccable-variant-count="2" style="display: contents">
<div data-impeccable-variant="original"><p>original</p></div>
<style data-impeccable-css="RECEIPT1" />
<div data-impeccable-variant="1"><p>accepted once</p></div>
<div data-impeccable-variant="2" style="display: none"><p>other</p></div>
</div>
<!-- impeccable-variants-end RECEIPT1 -->
</body>`;
writeFileSync(join(tmp, 'page.html'), html);
const first = runAccept(tmp, ['--id', 'RECEIPT1', '--variant', '1']);
const afterFirst = readFileSync(join(tmp, 'page.html'), 'utf-8');
const replay = runAccept(tmp, ['--id', 'RECEIPT1', '--variant', '1']);
assert.equal(first.handled, true);
assert.equal(replay.handled, true);
assert.equal(replay.alreadyApplied, true);
assert.equal(replay.file, 'page.html');
assert.equal(readFileSync(join(tmp, 'page.html'), 'utf-8'), afterFirst);
const conflict = runAccept(tmp, ['--id', 'RECEIPT1', '--variant', '2']);
assert.equal(conflict.handled, false);
assert.equal(conflict.error, 'accept_receipt_conflict');
});
// Variant: same-line <style>…</style> block should also be treated as a
// single skipped unit; the line has both open and close tags.
it('finds the accepted variant after a single-line <style>…</style> block', () => {
const html = `<body>
<!-- impeccable-variants-start ONELINE -->
<div data-impeccable-variants="ONELINE" data-impeccable-variant-count="3" style="display: contents">
<div data-impeccable-variant="original"><p class="hook">original</p></div>
<style data-impeccable-css="ONELINE">@scope ([data-impeccable-variant="1"]) { .hook { color: red; } }</style>
<div data-impeccable-variant="1"><p class="hook">variant one</p></div>
<div data-impeccable-variant="2" style="display: none"><p class="hook">variant two</p></div>
<div data-impeccable-variant="3" style="display: none"><p class="hook">variant three</p></div>
</div>
<!-- impeccable-variants-end ONELINE -->
</body>`;
writeFileSync(join(tmp, 'page.html'), html);
const result = runAccept(tmp, ['--id', 'ONELINE', '--variant', '3']);
assert.equal(result.handled, true, `accept should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
assert.ok(after.includes('data-impeccable-variant="3"'), 'accepted wrapper for variant 3 present');
assert.ok(after.includes('variant three'), 'variant 3 content kept');
assert.ok(!after.includes('variant two'), 'other variant content dropped');
});
// Baseline: the standard multi-line <style>...</style> case must keep working.
it('finds the accepted variant after a multi-line <style>…</style> block (regression baseline)', () => {
const html = `<body>
<!-- impeccable-variants-start MULTI -->
<div data-impeccable-variants="MULTI" data-impeccable-variant-count="3" style="display: contents">
<div data-impeccable-variant="original"><p class="hook">original</p></div>
<style data-impeccable-css="MULTI">
@scope ([data-impeccable-variant="1"]) { .hook { color: red; } }
@scope ([data-impeccable-variant="2"]) { .hook { color: green; } }
</style>
<div data-impeccable-variant="1"><p class="hook">variant one</p></div>
<div data-impeccable-variant="2" style="display: none"><p class="hook">variant two</p></div>
</div>
<!-- impeccable-variants-end MULTI -->
</body>`;
writeFileSync(join(tmp, 'page.html'), html);
const result = runAccept(tmp, ['--id', 'MULTI', '--variant', '1']);
assert.equal(result.handled, true, `accept should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
assert.ok(after.includes('data-impeccable-variant="1"'), 'accepted wrapper for variant 1 present');
assert.ok(after.includes('variant one'), 'variant 1 content kept');
});
// Regression: the agent writes JSX <style>{`…`}</style> and live-accept's
// extractCss used to capture the `{` … `` ` ``}` template-literal punctuation
// as CSS content. handleAccept then re-wrapped with another `{` …
// `` ` ``}`, producing nested template literals (`<style>{`{`@scope…`}`}`)
// that oxc rejects with "Expected `}` but found `@`". extractCss must
// strip the JSX wrap regardless of where the agent placed it.
it('carbonize does not double-wrap when the variants block uses JSX template literals on their own lines', () => {
const tsx = `export default function App() {\n` +
` return (\n` +
` <main>\n` +
` <>\n` +
` {/* impeccable-variants-start TPL */}\n` +
` <div data-impeccable-variants="TPL" data-impeccable-variant-count="2" style={{ display: 'contents' }}>\n` +
` <div data-impeccable-variant="original"><p className="hook">orig</p></div>\n` +
` <style data-impeccable-css="TPL">\n` +
" {`\n" +
` @scope ([data-impeccable-variant="1"]) { .hook { color: red; } }\n` +
` @scope ([data-impeccable-variant="2"]) { .hook { color: green; } }\n` +
" `}\n" +
` </style>\n` +
` <div data-impeccable-variant="1"><p className="hook">variant one</p></div>\n` +
` <div data-impeccable-variant="2" style={{ display: 'none' }}><p className="hook">variant two</p></div>\n` +
` </div>\n` +
` {/* impeccable-variants-end TPL */}\n` +
` </>\n` +
` </main>\n` +
` );\n` +
`}\n`;
writeFileSync(join(tmp, 'App.tsx'), tsx);
const result = runAccept(tmp, ['--id', 'TPL', '--variant', '1']);
assert.equal(result.handled, true, `accept should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'App.tsx'), 'utf-8');
// Exactly one `{` opener after the carbonized <style ...> tag — not two.
const carbonStyleMatch = after.match(/<style data-impeccable-css="TPL">([\s\S]*?)<\/style>/);
assert.ok(carbonStyleMatch, 'carbonize <style> block present');
const inner = carbonStyleMatch[1];
// Inner must open with one `{` ... and end with one ` `` ... — no nesting.
const openCount = (inner.match(/\{`/g) || []).length;
const closeCount = (inner.match(/`\}/g) || []).length;
assert.equal(openCount, 1, `expected exactly one {\` opener, got ${openCount}`);
assert.equal(closeCount, 1, `expected exactly one \`} closer, got ${closeCount}`);
// CSS content survived intact.
assert.ok(inner.includes('@scope ([data-impeccable-variant="1"])'), 'variant-1 scope kept');
assert.match(
after,
/\n <div data-impeccable-variant="1" style=\{\{ display: 'contents' \}\}>\n <p className="hook">variant one<\/p>\n <\/div>/,
'accepted JSX content is indented inside the temporary carbonize variant wrapper',
);
});
// Same shape, but the agent put `{`` and ``\`}` attached to first/last CSS
// lines instead of on dedicated lines. Tests the inline-strip branch.
it('carbonize does not double-wrap when JSX template-literal punctuation hugs the CSS lines', () => {
const tsx = `export default function App() {\n` +
` return (\n` +
` <main>\n` +
` <>\n` +
` {/* impeccable-variants-start INLINE */}\n` +
` <div data-impeccable-variants="INLINE" data-impeccable-variant-count="2" style={{ display: 'contents' }}>\n` +
` <div data-impeccable-variant="original"><p className="hook">orig</p></div>\n` +
` <style data-impeccable-css="INLINE">\n` +
" {`@scope ([data-impeccable-variant=\"1\"]) { .hook { color: red; } }\n" +
" @scope ([data-impeccable-variant=\"2\"]) { .hook { color: green; } }`}\n" +
` </style>\n` +
` <div data-impeccable-variant="1"><p className="hook">variant one</p></div>\n` +
` <div data-impeccable-variant="2" style={{ display: 'none' }}><p className="hook">variant two</p></div>\n` +
` </div>\n` +
` {/* impeccable-variants-end INLINE */}\n` +
` </>\n` +
` </main>\n` +
` );\n` +
`}\n`;
writeFileSync(join(tmp, 'App.tsx'), tsx);
const result = runAccept(tmp, ['--id', 'INLINE', '--variant', '1']);
assert.equal(result.handled, true, `accept should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'App.tsx'), 'utf-8');
const inner = after.match(/<style data-impeccable-css="INLINE">([\s\S]*?)<\/style>/)[1];
const openCount = (inner.match(/\{`/g) || []).length;
const closeCount = (inner.match(/`\}/g) || []).length;
assert.equal(openCount, 1, `expected one {\` opener, got ${openCount}`);
assert.equal(closeCount, 1, `expected one \`} closer, got ${closeCount}`);
assert.ok(inner.includes('@scope ([data-impeccable-variant="1"])'), 'variant-1 scope kept');
});
it('carbonize preserves nested JSX indentation when the wrapper starts at column 0', () => {
const tsx = `<div data-impeccable-variants="ROOTIND" data-impeccable-variant-count="2" style={{ display: 'contents' }}>
{/* impeccable-variants-start ROOTIND */}
<div data-impeccable-variant="original">
<section className="hook">
<span>original</span>
</section>
</div>
<style data-impeccable-css="ROOTIND">{\`@scope ([data-impeccable-variant="1"]) { .hook { color: red; } }\`}</style>
<div data-impeccable-variant="1">
<section className="hook">
<span>nested text</span>
</section>
</div>
<div data-impeccable-variant="2" style={{ display: 'none' }}>
<section className="hook">
<span>variant two</span>
</section>
</div>
{/* impeccable-variants-end ROOTIND */}
</div>
`;
writeFileSync(join(tmp, 'Root.tsx'), tsx);
const result = runAccept(tmp, ['--id', 'ROOTIND', '--variant', '1']);
assert.equal(result.handled, true, `accept should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'Root.tsx'), 'utf-8');
assert.match(
after,
/<div data-impeccable-variant="1" style=\{\{ display: 'contents' \}\}>\n <section className="hook">\n <span>nested text<\/span>\n <\/section>\n <\/div>/,
'column-0 JSX accept preserves relative indentation inside the carbonize variant wrapper',
);
});
// Cursor Bugbot regression (PR #118 review): the JSX wrapper places
// marker comments INSIDE the outer <div>, so block.start sits 2 spaces
// deeper than the original element. Using block.start as the deindent
// base on JSX accept/discard pushes every restored line 2 spaces too far
// right. The fix anchors the indent on `replaceRange.start` (the outer
// wrapper line), which is at the original element's indent level for
// both HTML and JSX.
it('discard restores JSX content at the original indent (no 2-space drift from marker-inside layout)', () => {
// Run the real wrap CLI so we exercise the JSX-marker-inside-wrapper
// layout end to end, not a hand-rolled approximation.
const tsx = `export default function App() {
return (
<main>
<aside className="card">
<h1 className="hero-title">Hero</h1>
</aside>
</main>
);
}`;
writeFileSync(join(tmp, 'App.tsx'), tsx);
execFileSync('node', [WRAP, '--id', 'INDENTDISC', '--count', '3', '--classes', 'card', '--tag', 'aside', '--file', join(tmp, 'App.tsx')], {
cwd: tmp,
encoding: 'utf-8',
});
runAccept(tmp, ['--id', 'INDENTDISC', '--discard']);
const after = readFileSync(join(tmp, 'App.tsx'), 'utf-8');
// The aside opener should land at exactly 6 spaces — same as the
// original — and the <h1> child at 8 (preserved relative depth).
// The earlier 6/6/6 collapse was caused by `originalLines.map(l =>
// indent + ' ' + l.trimStart())` in live-wrap stripping ALL
// leading whitespace before reindenting; the fix strips only the
// COMMON minimum so the relative structure is preserved.
assert.match(after, /^ <aside className="card">$/m,
`<aside> opener must be at 6-space indent (was 8 before outer-indent fix), got:\n${after}`);
assert.match(after, /^ <h1 className="hero-title">Hero<\/h1>$/m,
`<h1> child must be at 8-space indent — relative depth preserved through wrap+discard. Got:\n${after}`);
assert.match(after, /^ <\/aside>$/m,
`</aside> closer must be back at 6-space indent. Got:\n${after}`);
});
it('expandReplaceRange handles multi-line self-closing <div /> inside the wrapped element', () => {
// Cursor Bugbot regression: per-line depth tracking in
// `expandReplaceRange` couldn't see across line boundaries, so a
// multi-line self-closing JSX `<div\n className="spacer"\n/>` got
// counted as +1 with no compensating -1. The wrapper's outer </div>
// never matched the depth-zero condition; replace-range stopped at
// block.end (the marker comment), leaving the wrapper's outer </div>
// orphaned in the file after accept/discard — and worse, an
// unrelated <div className="next-card"> right after the wrapper got
// its own </div> mis-counted as the wrapper close.
const tsx = `export default function App() {
return (
<main>
<aside className="card">
<h1>Hi</h1>
<div
className="spacer"
/>
<p>Body</p>
</aside>
<div className="next-card">After</div>
</main>
);
}`;
writeFileSync(join(tmp, 'App.tsx'), tsx);
execFileSync('node', [WRAP, '--id', 'MULTILINESC', '--count', '3', '--classes', 'card', '--tag', 'aside', '--file', join(tmp, 'App.tsx')], {
cwd: tmp,
encoding: 'utf-8',
});
const result = runAccept(tmp, ['--id', 'MULTILINESC', '--discard']);
assert.equal(result.handled, true, `discard should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'App.tsx'), 'utf-8');
// The wrapper scaffold must be fully gone — no orphan </div> from
// the outer wrapper, and no impeccable markers/data attributes.
assert.ok(!after.includes('data-impeccable-variants'),
`outer wrapper div must be fully removed; got:\n${after}`);
assert.ok(!after.includes('data-impeccable-variant'),
`original-div wrapper must be fully removed; got:\n${after}`);
assert.ok(!after.includes('impeccable-variants-start'),
`start marker must be removed; got:\n${after}`);
// The unrelated <div className="next-card">After</div> sibling
// must survive intact — Bugbot's worst-case scenario was the depth
// walk eating its </div> as the wrapper close.
assert.ok(after.includes('<div className="next-card">After</div>'),
`unrelated next-card sibling must be preserved; got:\n${after}`);
// The multi-line self-closing div inside the original element must
// survive too.
assert.match(after, /<div\s*\n\s*className="spacer"\s*\n\s*\/>/m,
`multi-line self-closing <div /> inside original must survive; got:\n${after}`);
});
it('expandReplaceRange finds JSX wrapper openers with long multi-line attributes', () => {
const extraAttrs = Array.from({ length: 18 }, (_, i) => ` data-extra-${i}="x"`).join('\n');
const tsx = `export default function App() {
return (
<main>
<div
className="impeccable-preview-shell"
${extraAttrs}
data-impeccable-variants="LONGOPEN"
data-impeccable-variant-count="2"
style={{ display: 'contents' }}
>
{/* impeccable-variants-start LONGOPEN */}
{/* Original */}
<div data-impeccable-variant="original">
<aside className="card">
<h1>Original</h1>
</aside>
</div>
{/* Variants: insert below this line */}
<div data-impeccable-variant="1"><aside className="card"><h1>Variant</h1></aside></div>
{/* impeccable-variants-end LONGOPEN */}
</div>
<div className="next-card">After</div>
</main>
);
}`;
writeFileSync(join(tmp, 'App.tsx'), tsx);
const result = runAccept(tmp, ['--id', 'LONGOPEN', '--discard']);
assert.equal(result.handled, true, `discard should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'App.tsx'), 'utf-8');
assert.doesNotMatch(after, /data-impeccable-variants/);
assert.doesNotMatch(after, /impeccable-variants-start/);
assert.match(after, /<aside className="card">\s*<h1>Original<\/h1>\s*<\/aside>/m);
assert.ok(after.includes('<div className="next-card">After</div>'));
});
it('expandReplaceRange ignores unrelated prior end markers while finding the current JSX wrapper', () => {
const tsx = `export default function App() {
return (
<main>
<div data-impeccable-variants="ACTIVE" data-impeccable-variant-count="2" style={{ display: 'contents' }}>
<div className="historical-marker-note">
{/* impeccable-variants-end OLD */}
</div>
{/* impeccable-variants-start ACTIVE */}
{/* Original */}
<div data-impeccable-variant="original">
<aside className="card">
<h1>Original</h1>
</aside>
</div>
{/* Variants: insert below this line */}
<div data-impeccable-variant="1"><aside className="card"><h1>Variant</h1></aside></div>
{/* impeccable-variants-end ACTIVE */}
</div>
<div className="next-card">After</div>
</main>
);
}`;
writeFileSync(join(tmp, 'App.tsx'), tsx);
const result = runAccept(tmp, ['--id', 'ACTIVE', '--discard']);
assert.equal(result.handled, true, `discard should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'App.tsx'), 'utf-8');
assert.doesNotMatch(after, /data-impeccable-variants/);
assert.doesNotMatch(after, /impeccable-variants-start ACTIVE/);
assert.doesNotMatch(after, /impeccable-variants-end OLD/);
assert.match(after, /<aside className="card">\s*<h1>Original<\/h1>\s*<\/aside>/m);
assert.ok(after.includes('<div className="next-card">After</div>'));
});
it('accept (no carbonize, raw HTML) restores at the original indent on JSX', () => {
// Manually craft a wrapped file in the JSX-marker-inside layout — this
// mirrors what wrap produces, but lets us exercise accept's indent
// logic without a full live cycle.
const tsx = `export default function App() {
return (
<main>
<div data-impeccable-variants="INDENTACC" data-impeccable-variant-count="3" style={{ display: "contents" }}>
{/* impeccable-variants-start INDENTACC */}
{/* Original */}
<div data-impeccable-variant="original">
<aside className="card">
<h1 className="hero-title">Hero</h1>
</aside>
</div>
{/* Variants: insert below this line */}
<div data-impeccable-variant="1"><aside className="card variant-one"><h1 className="hero-title">Hero</h1></aside></div>
{/* impeccable-variants-end INDENTACC */}
</div>
</main>
);
}`;
writeFileSync(join(tmp, 'App.tsx'), tsx);
runAccept(tmp, ['--id', 'INDENTACC', '--variant', '1']);
const after = readFileSync(join(tmp, 'App.tsx'), 'utf-8');
// The accepted aside (variant-one) should land at 6-space indent, the
// same place the wrapper <div> sat — not 2 spaces deeper.
assert.match(after, /^ <aside className="card variant-one">/m,
`accepted <aside> must land at 6-space indent (the wrapper's level), got:\n${after}`);
});
// Discard must restore the original element after a self-closing <style />,
// proving extractOriginal also survives the style pattern.
it('discard restores the original element after a JSX self-closing <style />', () => {
const html = `<body>
<!-- impeccable-variants-start DISC -->
<div data-impeccable-variants="DISC" data-impeccable-variant-count="2" style="display: contents">
<div data-impeccable-variant="original"><p class="hook">ORIGINAL CONTENT</p></div>
<style data-impeccable-css="DISC" dangerouslySetInnerHTML={{ __html: '@scope ([data-impeccable-variant="1"]) { .hook { color: red; } }' }} />
<div data-impeccable-variant="1"><p class="hook">variant one</p></div>
<div data-impeccable-variant="2" style="display: none"><p class="hook">variant two</p></div>
</div>
<!-- impeccable-variants-end DISC -->
</body>`;
writeFileSync(join(tmp, 'page.html'), html);
const result = runAccept(tmp, ['--id', 'DISC', '--discard']);
assert.equal(result.handled, true, `discard should succeed: ${JSON.stringify(result)}`);
const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
assert.ok(after.includes('ORIGINAL CONTENT'), 'original restored');
assert.ok(!after.includes('impeccable-variants-start'), 'wrapper markers gone');
assert.ok(!after.includes('variant one'), 'variants dropped');
});
});
describe('live-accept — insert sessions', () => {
let tmp;
beforeEach(() => { tmp = mkdtempSync(join(tmpdir(), 'impeccable-accept-insert-')); });
afterEach(() => { rmSync(tmp, { recursive: true, force: true }); });
const insertHtml = (id) => `<main>
<section class="hero">Hero block</section>
<!-- impeccable-variants-start ${id} -->
<div data-impeccable-variants="${id}" data-impeccable-mode="insert" data-impeccable-variant-count="2" style="display: contents">
<!-- Variants: insert below this line -->
<style data-impeccable-css="${id}">@scope ([data-impeccable-variant="1"]) { .cta { color: red; } }</style>
<div data-impeccable-variant="1"><p class="cta">Variant one</p></div>
<div data-impeccable-variant="2" style="display: none"><p class="cta">Variant two</p></div>
</div>
<!-- impeccable-variants-end ${id} -->
<section class="footer">Footer</section>
</main>`;
it('discard removes an insert wrapper without touching anchor sections', () => {
writeFileSync(join(tmp, 'page.html'), insertHtml('insaaa01'));
const result = runAccept(tmp, ['--id', 'insaaa01', '--discard']);
assert.equal(result.handled, true, JSON.stringify(result));
const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
assert.ok(after.includes('Hero block'));
assert.ok(after.includes('Footer'));
assert.ok(!after.includes('impeccable-variants-start'));
assert.ok(!after.includes('Variant one'));
});
it('accept keeps the chosen insert variant and drops the wrapper', () => {
writeFileSync(join(tmp, 'page.html'), insertHtml('insbbb02'));
const result = runAccept(tmp, ['--id', 'insbbb02', '--variant', '2']);
assert.equal(result.handled, true, JSON.stringify(result));
const after = readFileSync(join(tmp, 'page.html'), 'utf-8');
assert.ok(after.includes('Variant two'));
assert.ok(!after.includes('Variant one'));
assert.ok(!after.includes('impeccable-variants-start'));
assert.ok(after.includes('Hero block'));
assert.ok(after.includes('Footer'));
});
});