`,
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);
});
// 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 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 block', () => {
const html = `
original
variant one
variant two
variant three
`;
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 case must keep working.
it('finds the accepted variant after a multi-line block (regression baseline)', () => {
const html = `
original
variant one
variant two
`;
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 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 (`\n` +
`
variant one
\n` +
`
variant two
\n` +
` \n` +
` {/* impeccable-variants-end TPL */}\n` +
` >\n` +
` \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 \n` +
`
, 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 (
);
}`;
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
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, /^
// never matched the depth-zero condition; replace-range stopped at
// block.end (the marker comment), leaving the wrapper's outer
// orphaned in the file after accept/discard — and worse, an
// unrelated
right after the wrapper got
// its own
mis-counted as the wrapper close.
const tsx = `export default function App() {
return (
After
);
}`;
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 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
After
sibling
// must survive intact — Bugbot's worst-case scenario was the depth
// walk eating its as the wrapper close.
assert.ok(after.includes('
After
'),
`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, //m,
`multi-line self-closing 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 (
{/* impeccable-variants-start LONGOPEN */}
{/* Original */}
{/* Variants: insert below this line */}
{/* impeccable-variants-end LONGOPEN */}
After
);
}`;
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, /