import { describe, it, beforeEach, afterEach } from 'node:test'; import assert from 'node:assert/strict'; import { cpSync, mkdirSync, mkdtempSync, readFileSync, realpathSync, rmSync, writeFileSync, symlinkSync } from 'node:fs'; import { join, dirname } from 'node:path'; import { tmpdir } from 'node:os'; import { fileURLToPath } from 'node:url'; import { compileCheckVariants, extractMatchingSourceCss, removeSelectorsFromSvelteSource, findSvelteComponentManifest, inlineSvelteComponentAccept, mergeCssIntoSvelteSource, reindentPreservingStructure, scaffoldSvelteComponentSession, } from '../skill/scripts/live/svelte-component.mjs'; const __dirname = dirname(fileURLToPath(import.meta.url)); const REPO_NODE_MODULES = join(__dirname, '..', 'node_modules'); const ROUTE_SOURCE = `
    {#each stages as stage, i}
  1. {stage.label}

    {stage.detail}

  2. {/each}
`; function write(root, rel, content) { const abs = join(root, rel); mkdirSync(dirname(abs), { recursive: true }); writeFileSync(abs, content); } describe('svelte component scaffold + accept pipeline', () => { let tmp; beforeEach(() => { tmp = realpathSync(mkdtempSync(join(tmpdir(), 'impeccable-svelte-accept-'))); // The scaffolder resolves the app's svelte compiler; link this repo's. mkdirSync(join(tmp, 'node_modules'), { recursive: true }); try { symlinkSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp, 'node_modules', 'svelte'), 'dir'); } catch { cpSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp, 'node_modules', 'svelte'), { recursive: true }); } write(tmp, 'package.json', JSON.stringify({ name: 'app', dependencies: { svelte: '^5' } })); write(tmp, 'src/routes/+page.svelte', ROUTE_SOURCE); }); afterEach(() => { rmSync(tmp, { recursive: true, force: true }); }); function scaffold(id = 'testacc1') { // The picked element spans the
    block: lines 10-17 (1-indexed). const originalLines = ROUTE_SOURCE.split('\n').slice(9, 17); assert.match(originalLines[0], /
      /); return scaffoldSvelteComponentSession({ id, count: 2, sourceFile: 'src/routes/+page.svelte', sourceStartLine: 10, sourceEndLine: 17, originalLines, cwd: tmp, }); } it('scaffolds a v2 contract with the each collection as one structured prop', () => { const session = scaffold(); assert.equal(session.fallback, undefined); assert.equal(session.manifest.contractVersion, 2); const collection = session.propContract.find((c) => c.kind === 'collection'); assert.equal(collection.prop, 'stages'); assert.equal(collection.item.rootTag, 'li'); const v1 = readFileSync(join(tmp, session.componentDir, 'v1.svelte'), 'utf-8'); assert.match(v1, /\{#each stages as stage, i\}/); assert.match(v1, /\{stage\.label\}/); assert.match(v1, /let \{ stages = \[\] \} = \$props\(\)/); // Stub CSS is seeded from the route's matching rules. assert.match(v1, /border-top: 1px solid #333/); }); it('falls back to source-preview for markup with component tags', () => { const res = scaffoldSvelteComponentSession({ id: 'fallb1', count: 3, sourceFile: 'src/routes/+page.svelte', sourceStartLine: 1, sourceEndLine: 1, originalLines: [''], cwd: tmp, }); assert.equal(res.fallback, 'source-preview'); assert.match(res.reason, /component tag/); }); it('accept merges CSS instead of appending: superseded rules are replaced, dead branches pruned', () => { const session = scaffold('acc2'); // Agent authors variant 1: arrows instead of divider borders, one param. write(tmp, join(session.componentDir, 'v1.svelte'), `
        {#each stages as stage, i}
      1. {stage.label}

        {stage.detail}

      2. {/each}
      `); write(tmp, join(session.componentDir, 'params.json'), JSON.stringify({ 1: [ { id: 'depth', kind: 'range', min: 0, max: 20, step: 1, default: 6, label: 'Depth' }, { id: 'density', kind: 'steps', default: 'airy', label: 'Density', options: [ { value: 'airy', label: 'Airy' }, { value: 'snug', label: 'Snug' }, ] }, ], })); const manifest = findSvelteComponentManifest('acc2', tmp); const result = inlineSvelteComponentAccept(manifest, 1, { depth: 10, density: 'snug' }, tmp); assert.equal(result.handled, true, result.error); const out = readFileSync(join(tmp, 'src/routes/+page.svelte'), 'utf-8'); // Loop restored with original expressions, one each block only. assert.equal(out.split('{#each stages as stage, i}').length - 1, 1); // Superseded divider border is GONE (replaced, not shadowed). assert.doesNotMatch(out, /border-top: 1px solid #333/); assert.match(out, /clip-path/); // Exactly one .pit-board rule. assert.equal(out.split('.pit-board {').length - 1, 1); // Range baked with paren-aware substitution. assert.match(out, /padding: calc\(10 \+ 2px\)/); // Steps: chosen branch folded into the .stage rule, other branch dropped, // no data-p attributes anywhere. assert.match(out, /margin: 4px/); assert.equal(out.split(/\.stage \{/).length - 1, 1); assert.doesNotMatch(out, /margin: 16px/); assert.doesNotMatch(out, /data-p-/); assert.doesNotMatch(out, /var\(--p-/); // The untouched .footer rule survives. assert.match(out, /\.footer \{ color: gray; \}/); // Self-check reports clean. assert.equal(result.verify.clean, true, JSON.stringify(result.verify.findings)); }); it('preserves the variant markup indentation structure', () => { const session = scaffold('acc3'); write(tmp, join(session.componentDir, 'v1.svelte'), `
        {#each stages as stage, i}
      1. {stage.label}
      2. {/each}
      `); const manifest = findSvelteComponentManifest('acc3', tmp); const result = inlineSvelteComponentAccept(manifest, 1, null, tmp); assert.equal(result.handled, true, result.error); const out = readFileSync(join(tmp, 'src/routes/+page.svelte'), 'utf-8'); const lines = out.split('\n'); const deepIdx = lines.findIndex((l) => l.includes('
      ')); const labelIdx = lines.findIndex((l) => l.includes('span class="label"')); const deepIndent = lines[deepIdx].match(/^\s*/)[0].length; const labelIndent = lines[labelIdx].match(/^\s*/)[0].length; // Nested structure survives: label sits deeper than its parent div. assert.equal(labelIndent > deepIndent, true, `expected nesting, got ${deepIndent} vs ${labelIndent}`); }); it('reindentPreservingStructure keeps relative depth', () => { const out = reindentPreservingStructure([' ', ' ', ' '], ' '); assert.deepEqual(out, [' ', ' ', ' ']); }); it('mergeCssIntoSvelteSource creates a style block when none exists', () => { const { text } = mergeCssIntoSvelteSource('
      hi
      ', '.x { color: red; }'); assert.match(text, /`; const { css, supersedable } = extractMatchingSourceCss(route, ''); assert.match(css, /\.btn \{/); assert.match(css, /\.stage \{/); assert.doesNotMatch(css, /btn-primary/, '.btn must not seed .btn-primary'); assert.doesNotMatch(css, /\.stages/, '.stage must not seed .stages'); assert.deepEqual([...supersedable].sort(), ['.btn', '.stage']); }); it('tag rules seed the preview but are never supersedable', () => { const route = ``; const { css, supersedable } = extractMatchingSourceCss(route, '

      Title

      '); assert.match(css, /h1 \{ font-size/, 'bare tag rules that style the pick are seeded'); assert.match(css, /h1\.hero/, 'class rules still seed'); assert.doesNotMatch(css, /^p \{/m, 'unrelated tags are not seeded'); assert.doesNotMatch(css, /\.sidebar/); assert.deepEqual([...supersedable], ['h1.hero'], 'only class-matched selectors may be removed on accept'); }); }); describe('review regressions: preview-truth supersession (the Pitch mangle)', () => { const PITCH_SOURCE = `
      {#each verdicts as verdict}

      {verdict.label}

      {verdict.detail}

      {/each}
      `; it('removes seeded rules the variant did not re-declare and orders new base rules before media blocks', () => { const tmp2 = realpathSync(mkdtempSync(join(tmpdir(), 'impeccable-pitch-mangle-'))); try { mkdirSync(join(tmp2, 'node_modules'), { recursive: true }); try { symlinkSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp2, 'node_modules', 'svelte'), 'dir'); } catch { cpSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp2, 'node_modules', 'svelte'), { recursive: true }); } write(tmp2, 'package.json', JSON.stringify({ name: 'app' })); write(tmp2, 'src/lib/Pitch.svelte', PITCH_SOURCE); // Picked element: the .decisions block (lines 10-17, 1-indexed). const lines = PITCH_SOURCE.split('\n'); const startLine = lines.findIndex((l) => l.includes('class="decisions"')) + 1; const endLine = lines.findIndex((l, i) => i >= startLine && l.trim() === '
      ' && lines[i + 1]?.includes('')) + 1; const originalLines = lines.slice(startLine - 1, endLine); const session = scaffoldSvelteComponentSession({ id: 'pitchm1', count: 1, sourceFile: 'src/lib/Pitch.svelte', sourceStartLine: startLine, sourceEndLine: endLine, originalLines, cwd: tmp2, }); assert.equal(session.fallback, undefined, session.reason); // Seeded selectors recorded for accept-time supersession. assert.equal(session.manifest.seededSelectors.includes('.decisions'), true); // The agent's variant: a NEW class, no re-declaration of .decisions. write(tmp2, join(session.componentDir, 'v1.svelte'), `
      {#each verdicts as verdict}

      {verdict.label}

      {verdict.detail}

      {/each}
      `); const manifest = findSvelteComponentManifest('pitchm1', tmp2); const result = inlineSvelteComponentAccept(manifest, 1, null, tmp2); assert.equal(result.handled, true, result.error); const out = readFileSync(join(tmp2, 'src/lib/Pitch.svelte'), 'utf-8'); // The superseded grid rules are GONE: they never applied in the // preview the user approved, and the root keeps the old class. assert.doesNotMatch(out, /grid-template-columns: repeat\(3, 1fr\)/); assert.doesNotMatch(out, /\.decisions > \.cell/); assert.equal(result.css.superseded.includes('.decisions'), true); // The untouched sibling rule survives. assert.match(out, /\.pitch \{ padding: 40px; \}/); // Source media block survives for the surviving class... assert.match(out, /\.pitch \{ padding: 16px; \}/); // ...and no longer carries the superseded selector. assert.doesNotMatch(out, /\.decisions \{ grid-template-columns: 1fr; \}/); // New base rules sit BEFORE the source's @media block (cascade order). const baseIdx = out.indexOf('.disposition-board {'); const mediaIdx = out.indexOf('@media (max-width: 700px)'); assert.equal(baseIdx > -1 && mediaIdx > -1 && baseIdx < mediaIdx, true, `expected base rules before media, got base@${baseIdx} media@${mediaIdx}`); assert.equal(result.verify.clean, true, JSON.stringify(result.verify.findings)); } finally { rmSync(tmp2, { recursive: true, force: true }); } }); it('keeps seeded rules the variant re-declares', () => { const { text, removed } = removeSelectorsFromSvelteSource('
      x
      \n', new Set(['.b'])); assert.match(text, /\.a \{ color: red; \}/); assert.doesNotMatch(text, /color: blue/); assert.deepEqual(removed, ['.b']); }); }); describe('review regressions: publish-time compile gate', () => { it('flags a variant with a duplicate top-level style block, passes after the fix', () => { const tmp3 = realpathSync(mkdtempSync(join(tmpdir(), 'impeccable-compile-gate-'))); try { mkdirSync(join(tmp3, 'node_modules'), { recursive: true }); try { symlinkSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp3, 'node_modules', 'svelte'), 'dir'); } catch { cpSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp3, 'node_modules', 'svelte'), { recursive: true }); } write(tmp3, 'package.json', JSON.stringify({ name: 'app' })); write(tmp3, 'src/routes/+page.svelte', '
      \n
      hi
      \n
      \n'); const session = scaffoldSvelteComponentSession({ id: 'gate0001', count: 1, sourceFile: 'src/routes/+page.svelte', sourceStartLine: 2, sourceEndLine: 2, originalLines: ['
      hi
      '], cwd: tmp3, }); assert.equal(session.fallback, undefined, session.reason); // The exact field failure: the agent kept the seeded block and // appended its own second top-level `); const broken = compileCheckVariants('gate0001', tmp3); assert.equal(broken.ok, false); assert.equal(broken.checked, 1); assert.match(broken.failures[0].message, /single top-level/); assert.match(broken.failures[0].file, /gate0001\/v1\.svelte/); assert.equal(typeof broken.failures[0].line, 'number'); // Merged into one block: the gate opens. write(tmp3, join(session.componentDir, 'v1.svelte'), `
      hi
      `); const fixed = compileCheckVariants('gate0001', tmp3); assert.equal(fixed.ok, true, JSON.stringify(fixed.failures)); } finally { rmSync(tmp3, { recursive: true, force: true }); } }); }); describe('review regressions: shared-class supersession guard', () => { const SHARED_SOURCE = `
      Intro copy stays here.
        {#each items as item}
      • {item.name}
      • {/each}
      `; it('keeps a superseded selector whose class is still used outside the replaced region', () => { const tmp4 = realpathSync(mkdtempSync(join(tmpdir(), 'impeccable-shared-class-'))); try { mkdirSync(join(tmp4, 'node_modules'), { recursive: true }); try { symlinkSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp4, 'node_modules', 'svelte'), 'dir'); } catch { cpSync(join(REPO_NODE_MODULES, 'svelte'), join(tmp4, 'node_modules', 'svelte'), { recursive: true }); } write(tmp4, 'package.json', JSON.stringify({ name: 'app' })); write(tmp4, 'src/lib/Shared.svelte', SHARED_SOURCE); // Pick the
        block. Its items use .card, and so does // the intro div OUTSIDE the pick. const lines = SHARED_SOURCE.split('\n'); const startLine = lines.findIndex((l) => l.includes('class="list"')) + 1; const endLine = lines.findIndex((l) => l.trim() === '
      ') + 1; const originalLines = lines.slice(startLine - 1, endLine); const session = scaffoldSvelteComponentSession({ id: 'shared01', count: 1, sourceFile: 'src/lib/Shared.svelte', sourceStartLine: startLine, sourceEndLine: endLine, originalLines, cwd: tmp4, }); assert.equal(session.fallback, undefined, session.reason); assert.equal(session.manifest.seededSelectors.includes('.card'), true, 'the pick uses .card, so it seeds'); // The variant re-declares .list but NOT .card. write(tmp4, join(session.componentDir, 'v1.svelte'), `
        {#each items as item}
      • {item.name}
      • {/each}
      `); const manifest = findSvelteComponentManifest('shared01', tmp4); const result = inlineSvelteComponentAccept(manifest, 1, null, tmp4); assert.equal(result.handled, true, result.error); const out = readFileSync(join(tmp4, 'src/lib/Shared.svelte'), 'utf-8'); // .card is shared with the intro div outside the replaced region: // removing it would strip styling from markup this accept never // touched, so it must survive despite not being re-declared. assert.match(out, /\.card \{ border: 1px solid #999; border-radius: 8px; \}/); assert.equal(result.css.superseded.includes('.card'), false); // The re-declared .list took the variant's shape. assert.match(out, /\.list \{ display: flex/); } finally { rmSync(tmp4, { recursive: true, force: true }); } }); });