Rip out the dead isolated-preview mode and the private repo's job

Comparing this branch's live against main's turned up two whole features that
never made sense here. -2,466 lines.

1. The isolated source-artifact preview was never switched on.

`scaffoldSourceArtifactSession` is only reachable via live-wrap's `--isolated`,
and nothing passes it: not the server's preflight, not live.md, nothing. Proved
it end-to-end — the default wrap writes markers straight into real source and
creates no previews/ session. So the mode was wired through three modules,
carried its own accept/discard branches, browser branches, server metadata
resolution, preview-mode classifier entry, and test suites, and none of it could
run.

Worse, live.md documented it as the active path and told the agent "The true
source is only the publisher's hash fence and must remain byte-identical until
Accept." That is false: the wrapper lands in source at scaffold time and each
revision rewrites it. An agent following that sentence believes source is
protected when it isn't, and the leftover artifacts are what made accept resolve
the wrong file in the first real run. live.md now describes what actually
happens, including that markers are visible in source until Accept or Discard.

Removed: source-artifact.mjs, --isolated, the preflight's isolated option, the
accept/discard branches, four dead browser branches, the server's previews/
resolution, the classifier entry, and their tests. Kept the previews/ gitignore
pattern: an ignore line for a directory that cannot exist is free, and a test
pins it.

2. Quality judging belongs to the private evals repo, which says so.

runner/live/README.md there is explicit: the public repo owns protocol
correctness, framework coverage, timing, source commit, recovery, and a
rubric-free evidence bundle; the private repo owns the task corpus, baselines,
comparative judges, and release-quality decisions — "Do not add quality rubrics,
competitor comparisons, or broad fixture corpora to the public Live benchmark."

This branch added exactly those: an LLM judge scoring 1-10 on "off-brand,
generic-AI" (live-rendered-quality.mjs, judge-live-rendered.mjs), a
cross-provider comparison with a BRAND_CONTRACT rubric (live-provider-benchmark
.mjs, benchmark-live-providers.mjs), and a brand-fidelity fixture corpus. All
removed, with bench:live:providers and their suite entries.

Also removed tests/framework-fixtures/README.md's "External quality-eval
fixtures" section: it documented a bench:live workflow using --fixture-dir,
--agent=codex, --action and --evidence-bundle, none of which benchmark-live.mjs
implements, plus an evidenceCapture block nothing reads.

Kept: timing benchmarks (the public repo's half of that boundary), progressive
publication, the source lock, poll lanes, and Nuxt/Vue component previews.

Coverage note: deleting the isolated suites took the only tests for
`source_locked` classification with them, so the plain wrapper path — now the
only non-component preview — gets equivalent accept and discard coverage. Both
new tests fail if mode:'error' is removed.

Prepared with AI assistance under maintainer direction.

Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
Paul Bakaus
2026-07-17 19:17:38 -07:00
co-authored by Claude
parent c7b67b3832
commit c654acb005
35 changed files with 76 additions and 2471 deletions
+2 -95
View File
@@ -30,10 +30,6 @@ import {
inlineVueComponentAccept,
retireVueComponentSession,
} from './live/vue-component.mjs';
import {
findSourceArtifactManifest,
removeSourceArtifactSession,
} from './live/source-artifact.mjs';
import { removeGenerationArtifacts } from './live/generation-publisher.mjs';
const EXTENSIONS = ['.html', '.jsx', '.tsx', '.vue', '.svelte', '.astro'];
@@ -173,78 +169,15 @@ Output (JSON):
}
// Find the file containing this session's markers
const sourceArtifactManifest = findSourceArtifactManifest(id, process.cwd());
const found = sourceArtifactManifest ? null : findSessionFile(id, process.cwd());
const found = findSessionFile(id, process.cwd());
const svelteComponentManifest = found ? null : findSvelteComponentManifest(id, process.cwd());
const vueComponentManifest = found || svelteComponentManifest ? null : findVueComponentManifest(id, process.cwd());
if (!found && !sourceArtifactManifest && !svelteComponentManifest && !vueComponentManifest) {
if (!found && !svelteComponentManifest && !vueComponentManifest) {
console.log(JSON.stringify({ handled: false, error: 'Session markers not found for id: ' + id }));
process.exit(0);
}
if (sourceArtifactManifest) {
if (isDiscard) {
// Take the source lock like every other discard path. The journalled
// discard already fences publication, so this cannot admit a write to a
// discarded session; what it prevents is deleting the preview out from
// under a publisher mid-critical-section, which turns its clean
// stale_generation_epoch into an ENOENT crash.
let result;
try {
result = withSourceLockSync(
sourceArtifactManifest.sourcePath,
'discard:' + id,
() => {
removeSourceArtifactSession(id, process.cwd());
return { handled: true };
},
{ waitMs: ACCEPT_LOCK_WAIT_MS },
);
} catch (err) {
result = operationFailure(err);
}
emitResult({
...result,
file: sourceArtifactManifest.sourceFile,
sourceFile: sourceArtifactManifest.sourceFile,
previewMode: sourceArtifactManifest.previewMode,
carbonize: false,
});
return;
}
let result;
try {
result = withSourceLockSync(
sourceArtifactManifest.sourcePath,
'accept:' + id,
() => acceptSourceArtifact(sourceArtifactManifest, variantNum, paramValues),
{ waitMs: ACCEPT_LOCK_WAIT_MS },
);
} catch (err) {
result = operationFailure(err);
}
if (result.handled !== false) {
removeSourceArtifactSession(id, process.cwd());
try {
scrubManualEditsAgainstOriginalBlock(result.acceptedOriginalText || '', process.cwd(), pageUrl);
} catch {}
}
delete result.acceptedOriginalText;
if (result.carbonize) {
result.todo = 'REQUIRED before next poll: carbonize cleanup in ' + sourceArtifactManifest.sourceFile + '. See reference/live.md "Required after accept".';
}
emitResult({
handled: result.handled !== false,
file: sourceArtifactManifest.sourceFile,
sourceFile: sourceArtifactManifest.sourceFile,
previewMode: sourceArtifactManifest.previewMode,
...result,
});
return;
}
if (vueComponentManifest) {
if (isDiscard) {
let result;
@@ -649,32 +582,6 @@ function buildAcceptedWrappedSource(id, variantNum, lines, targetFile, paramValu
};
}
function acceptSourceArtifact(manifest, variantNum, paramValues) {
const source = fs.readFileSync(manifest.sourcePath, 'utf-8');
const preview = fs.readFileSync(manifest.previewPath, 'utf-8');
const original = String(manifest.originalSource || '');
if (!original) return { handled: false, error: 'source_artifact_original_missing' };
const first = source.indexOf(original);
if (first < 0) return { handled: false, error: 'source_artifact_original_changed' };
if (source.indexOf(original, first + original.length) >= 0) {
return { handled: false, error: 'source_artifact_original_ambiguous' };
}
const wrapped = source.slice(0, first) + preview + source.slice(first + original.length);
const built = buildAcceptedWrappedSource(
manifest.id,
variantNum,
wrapped.split('\n'),
manifest.sourcePath,
paramValues,
);
if (built.handled === false) return built;
fs.writeFileSync(manifest.sourcePath, built.content, 'utf-8');
return {
handled: true,
carbonize: built.carbonize,
acceptedOriginalText: built.acceptedOriginalText,
};
}
function readSourceShadowPreviewMeta(content, id) {
const escaped = escapeRegExp(id);
+2 -17
View File
@@ -4923,10 +4923,6 @@
return mode === 'svelte-component' || mode === 'vue-component';
}
function isSourceArtifactPreviewMode(mode) {
return mode === 'source-artifact';
}
function parseOriginalMarkupElement(originalMarkup) {
const parser = new DOMParser();
const doc = parser.parseFromString('<div id="impeccable-anchor">' + originalMarkup + '</div>', 'text/html');
@@ -5630,9 +5626,7 @@
injectSvelteComponentsFromManifest(filePath, sessionId);
return;
}
rememberSessionFileMeta(isSourceArtifactPreviewMode(currentPreviewMode)
? { previewFile: filePath, previewMode: currentPreviewMode }
: { file: filePath });
rememberSessionFileMeta({ file: filePath });
const url = 'http://localhost:' + PORT + '/source?token=' + TOKEN + '&path=' + encodeURIComponent(filePath);
fetch(url)
.then(r => { if (!r.ok) throw new Error(r.status); return r.text(); })
@@ -6340,8 +6334,6 @@
rememberSessionFileMeta(msg);
if (isFrameworkComponentPreviewMode(msg.previewMode) && msg.previewFile) {
injectSvelteComponentsFromManifest(msg.previewFile, msg.id);
} else if (isSourceArtifactPreviewMode(msg.previewMode) && msg.previewFile) {
injectVariantsFromSource(msg.previewFile, msg.id);
} else if ((msg.previewMode === 'source' || !msg.previewMode) && (msg.previewFile || msg.file)) {
// Give normal framework HMR the first chance to reconcile its
// own managed tree. Nuxt route-module HMR can skip intermediate
@@ -8021,13 +8013,6 @@ void main() {
const previewFile = normalizeSessionPath(meta.previewFile);
const previewMode = meta.previewMode || (isSvelteComponentManifestPath(previewFile || file) ? 'svelte-component' : null);
if (isSourceArtifactPreviewMode(previewMode)) {
currentPreviewMode = previewMode;
currentPreviewFile = previewFile || file || currentPreviewFile;
currentSourceFile = sourceFile || currentSourceFile;
return;
}
if (isFrameworkComponentPreviewMode(previewMode) || isSvelteComponentManifestPath(file)) {
currentPreviewMode = isFrameworkComponentPreviewMode(previewMode) ? previewMode : 'svelte-component';
currentPreviewFile = previewFile || (isSvelteComponentManifestPath(file) ? file : currentPreviewFile);
@@ -8126,7 +8111,7 @@ void main() {
saveSession();
queueCheckpoint(reason || 'browser_restore_without_wrapper');
const restoreFile = isFrameworkComponentPreviewMode(currentPreviewMode) || isSourceArtifactPreviewMode(currentPreviewMode)
const restoreFile = isFrameworkComponentPreviewMode(currentPreviewMode)
? currentPreviewFile
: (currentSourceFile || currentPreviewFile);
if (restoreFile) {
+4 -12
View File
@@ -998,16 +998,11 @@ function sessionFileMetadataFromPollReply(file) {
if (!file || typeof file !== 'string') return { file };
const normalized = file.split(path.sep).join('/');
const base = { file: normalized };
const sourceArtifactPreview = normalized.includes('.impeccable/live/previews/')
&& !normalized.endsWith('/manifest.json');
const metadataFile = sourceArtifactPreview
? normalized.slice(0, normalized.lastIndexOf('/') + 1) + 'manifest.json'
: normalized;
const metadataFile = normalized;
if (!metadataFile.endsWith('/manifest.json') && metadataFile !== 'manifest.json') return base;
if (!metadataFile.includes('node_modules/.impeccable-live/')
&& !metadataFile.includes('src/lib/impeccable/')
&& !metadataFile.includes('/.impeccable-live/')
&& !metadataFile.includes('.impeccable/live/previews/')) return base;
&& !metadataFile.includes('/.impeccable-live/')) return base;
let full;
try {
@@ -1020,15 +1015,12 @@ function sessionFileMetadataFromPollReply(file) {
try {
const manifest = JSON.parse(fs.readFileSync(full, 'utf-8'));
if (!['svelte-component', 'vue-component', 'source-artifact'].includes(manifest?.previewMode)
if (!['svelte-component', 'vue-component'].includes(manifest?.previewMode)
|| !manifest.sourceFile) return base;
const previewFile = manifest.previewMode === 'source-artifact'
? String(manifest.previewFile || normalized).split(path.sep).join('/')
: normalized;
return {
file: String(manifest.sourceFile).split(path.sep).join('/'),
sourceFile: String(manifest.sourceFile).split(path.sep).join('/'),
previewFile,
previewFile: normalized,
previewMode: manifest.previewMode,
};
} catch {
+4 -32
View File
@@ -25,10 +25,6 @@ import {
scaffoldVueComponentSession,
shouldUseVueComponentInjection,
} from './live/vue-component.mjs';
import {
SOURCE_ARTIFACT_PREVIEW_MODE,
scaffoldSourceArtifactSession,
} from './live/source-artifact.mjs';
const EXTENSIONS = ['.html', '.jsx', '.tsx', '.vue', '.svelte', '.astro'];
@@ -59,8 +55,6 @@ Optional:
--page-url URL Current page URL. Required when pending manual edits may
affect the picked source block. Pending edits are filtered
to this page so an edit on /a doesn't bleed into /b.
--isolated Keep ordinary HTML/JSX/Astro source untouched during
preview; write the wrapper to an isolated Live artifact.
--help Show this help message
Output (JSON):
@@ -79,7 +73,6 @@ The agent should insert variant HTML at insertLine.`);
const filePath = argVal(args, '--file');
const text = argVal(args, '--text');
const pageUrl = argVal(args, '--page-url');
const isolated = args.includes('--isolated');
if (!id) { console.error('Missing --id'); process.exit(1); }
if (!elementId && !classes && !query) {
@@ -302,7 +295,6 @@ The agent should insert variant HTML at insertLine.`);
const useSvelteComponent = shouldUseSvelteComponentInjection(targetFile);
const useVueComponent = !useSvelteComponent && shouldUseVueComponentInjection(targetFile);
const useFrameworkComponent = useSvelteComponent || useVueComponent;
const useSourceArtifact = isolated && !useFrameworkComponent;
// Wrapper attributes differ by syntax. HTML allows plain string attrs;
// JSX requires object-literal style and parses string attrs as HTML (which
@@ -321,11 +313,8 @@ The agent should insert variant HTML at insertLine.`);
// tuck both marker comments INSIDE it. accept/discard then expands its
// replacement range to include the wrapper's `<div>` open / close lines
// so the entire scaffold gets removed cleanly.
const sourceArtifactAttr = useSourceArtifact
? ' data-impeccable-preview="' + SOURCE_ARTIFACT_PREVIEW_MODE + '"'
: '';
const wrapperLines = isJsx ? [
indent + '<div data-impeccable-variants="' + id + '" data-impeccable-variant-count="' + count + '"' + sourceArtifactAttr + ' ' + styleContents + '>',
indent + '<div data-impeccable-variants="' + id + '" data-impeccable-variant-count="' + count + '"' + ' ' + styleContents + '>',
indent + ' ' + commentSyntax.open + ' impeccable-variants-start ' + id + ' ' + commentSyntax.close,
indent + ' ' + commentSyntax.open + ' Original ' + commentSyntax.close,
indent + ' <div data-impeccable-variant="original">',
@@ -336,7 +325,7 @@ The agent should insert variant HTML at insertLine.`);
indent + '</div>',
] : [
indent + commentSyntax.open + ' impeccable-variants-start ' + id + ' ' + commentSyntax.close,
indent + '<div data-impeccable-variants="' + id + '" data-impeccable-variant-count="' + count + '"' + sourceArtifactAttr + ' ' + styleContents + '>',
indent + '<div data-impeccable-variants="' + id + '" data-impeccable-variant-count="' + count + '"' + ' ' + styleContents + '>',
indent + ' ' + commentSyntax.open + ' Original ' + commentSyntax.close,
indent + ' <div data-impeccable-variant="original">',
originalIndented,
@@ -353,7 +342,6 @@ The agent should insert variant HTML at insertLine.`);
let insertLine;
let svelteSession = null;
let vueSession = null;
let sourceArtifactSession = null;
if (useSvelteComponent) {
// Svelte/SvelteKit resets component-local state on markup HMR updates.
@@ -390,21 +378,6 @@ The agent should insert variant HTML at insertLine.`);
outputStartLine = 1;
outputEndLine = 1;
insertLine = 1;
} else if (useSourceArtifact) {
sourceArtifactSession = scaffoldSourceArtifactSession({
id,
count,
sourceFile: relTargetFile,
sourceStartLine: startLine + 1,
sourceEndLine: endLine + 1,
originalSource: sourceOriginalLines.join('\n'),
previewContent: wrapperLines.join('\n'),
cwd: process.cwd(),
});
outputFile = path.resolve(process.cwd(), sourceArtifactSession.previewFile);
outputStartLine = 1;
outputEndLine = wrapperLines.length + (originalLines.length - 1);
insertLine = 6 + (originalLines.length - 1) + 1;
} else {
// Replace the original element with the wrapper
const newLines = [
@@ -430,13 +403,12 @@ The agent should insert variant HTML at insertLine.`);
const vueComponentAuthoring = useVueComponent ? buildVueComponentCssAuthoring(count) : null;
const componentSession = svelteSession || vueSession;
const componentPreviewMode = useSvelteComponent ? 'svelte-component' : useVueComponent ? 'vue-component' : undefined;
const previewMode = componentPreviewMode || (useSourceArtifact ? SOURCE_ARTIFACT_PREVIEW_MODE : undefined);
const previewMode = componentPreviewMode;
console.log(JSON.stringify({
file: outputRelFile,
sourceFile: useFrameworkComponent || useSourceArtifact ? relTargetFile : undefined,
sourceFile: useFrameworkComponent ? relTargetFile : undefined,
previewMode,
previewManifest: sourceArtifactSession?.manifestFile,
componentDir: componentSession?.componentDir,
propContract: componentSession?.propContract,
sourceStartLine: useFrameworkComponent ? startLine + 1 : undefined,
+5 -7
View File
@@ -1,13 +1,11 @@
// A preview whose variants live outside the user's source: component modules or
// an isolated artifact. These keep the real file untouched until Accept, so a
// failed accept leaves nothing in source for the agent to hand-edit and must be
// reported as a failure rather than reference/live.md's manual-cleanup handoff.
// Previously only `svelte-component` was special-cased here, so the same failure
// on a Vue or isolated-artifact preview was acknowledged as a success.
// A preview whose variants live in component modules rather than in the user's
// source. These leave no markers in the real file, so a failed accept gives the
// agent nothing to hand-edit and must be reported as a failure rather than
// reference/live.md's manual-cleanup handoff. Previously only `svelte-component`
// was special-cased, so the same failure on a Vue preview read as success.
const PREVIEW_MODES_WITHOUT_SOURCE_MARKERS = new Set([
'svelte-component',
'vue-component',
'source-artifact',
]);
export function completionTypeForAcceptResult(eventType, acceptResult) {
+2 -4
View File
@@ -5,7 +5,7 @@ import { promisify } from 'node:util';
const execFileAsync = promisify(execFile);
const PREFLIGHT_TIMEOUT_MS = 15_000;
export function buildGenerationPreflight(event, scriptsDir, { isolated = false } = {}) {
export function buildGenerationPreflight(event, scriptsDir) {
if (!event || event.type !== 'generate' || !event.id) return null;
const isInsert = event.mode === 'insert';
@@ -14,7 +14,6 @@ export function buildGenerationPreflight(event, scriptsDir, { isolated = false }
const script = path.join(scriptsDir, isInsert ? 'live-insert.mjs' : 'live-wrap.mjs');
const args = [script, '--id', event.id, '--count', String(event.count || 3)];
if (!isInsert && isolated) args.push('--isolated');
if (isInsert) args.push('--position', target.position);
if (target.elementId) args.push('--element-id', target.elementId);
if (target.classes) args.push('--classes', target.classes);
@@ -39,9 +38,8 @@ export async function runGenerationPreflight(event, {
scriptsDir,
execFileImpl = execFileAsync,
timeoutMs = PREFLIGHT_TIMEOUT_MS,
isolated = false,
} = {}) {
const command = buildGenerationPreflight(event, scriptsDir, { isolated });
const command = buildGenerationPreflight(event, scriptsDir);
if (!command) {
return { ok: false, skipped: true, reason: 'insufficient_locator' };
}
+6 -39
View File
@@ -4,10 +4,6 @@ import { createHash } from 'node:crypto';
import { createLiveSessionStore } from './session-store.mjs';
import { withSourceLockSync } from './source-lock.mjs';
import { getLiveDir, safeSessionId } from '../lib/impeccable-paths.mjs';
import {
SOURCE_ARTIFACT_PREVIEW_MODE,
findSourceArtifactManifest,
} from './source-artifact.mjs';
export function sha256(value) {
return createHash('sha256').update(value).digest('hex');
@@ -43,9 +39,7 @@ export function prepareGenerationArtifact({ id, sourceFile, cwd = process.cwd()
const componentTarget = readComponentPublicationTarget(requestedPath, cwd, id);
if (componentTarget?.error) return componentTarget;
const sourceArtifactTarget = componentTarget ? null : readSourceArtifactPublicationTarget(requestedPath, cwd, id);
if (sourceArtifactTarget?.error) return sourceArtifactTarget;
const sourcePath = componentTarget?.sourcePath || sourceArtifactTarget?.sourcePath || requestedPath;
const sourcePath = componentTarget?.sourcePath || requestedPath;
try {
return withSourceLockSync(sourcePath, 'generation-prepare:' + id, () => {
@@ -56,9 +50,7 @@ export function prepareGenerationArtifact({ id, sourceFile, cwd = process.cwd()
return failure('stale_generation_epoch', { canceled: true, phase: snapshot.phase });
}
const source = fs.readFileSync(sourcePath, 'utf-8');
const artifactBase = sourceArtifactTarget
? fs.readFileSync(sourceArtifactTarget.previewPath, 'utf-8')
: source;
const artifactBase = source;
const revision = Number(snapshot.publishedRevision || 0) + 1;
const artifactDir = path.join(getLiveDir(cwd), 'artifacts');
if (componentTarget) {
@@ -84,10 +76,6 @@ export function prepareGenerationArtifact({ id, sourceFile, cwd = process.cwd()
epoch: Number(snapshot.generationEpoch || 1),
revision,
sourceFile: relative(cwd, sourcePath),
...(sourceArtifactTarget ? {
previewFile: relative(cwd, sourceArtifactTarget.previewPath),
previewMode: SOURCE_ARTIFACT_PREVIEW_MODE,
} : {}),
artifactFile: relative(cwd, artifactPath),
expectedSourceHash: sha256(source),
};
@@ -124,8 +112,6 @@ export function publishGenerationArtifact({
const componentTarget = readComponentPublicationTarget(requestedPath, cwd, id);
if (componentTarget?.error) return componentTarget;
const sourceArtifactTarget = componentTarget ? null : readSourceArtifactPublicationTarget(requestedPath, cwd, id);
if (sourceArtifactTarget?.error) return sourceArtifactTarget;
const artifactManifest = readJson(artifactPath);
const isComponentArtifact = isComponentPreviewMode(artifactManifest?.previewMode);
if (Boolean(componentTarget) !== isComponentArtifact) {
@@ -134,7 +120,7 @@ export function publishGenerationArtifact({
if (componentTarget && componentTarget.manifest.previewMode !== artifactManifest?.previewMode) {
return failure('artifact_preview_mode_mismatch');
}
const sourcePath = componentTarget?.sourcePath || sourceArtifactTarget?.sourcePath || requestedPath;
const sourcePath = componentTarget?.sourcePath || requestedPath;
try {
return withSourceLockSync(sourcePath, 'generation:' + id + ':' + epoch, () => {
@@ -167,9 +153,7 @@ export function publishGenerationArtifact({
});
}
const stablePreview = sourceArtifactTarget
? fs.readFileSync(sourceArtifactTarget.previewPath, 'utf-8')
: current;
const stablePreview = current;
const artifact = fs.readFileSync(artifactPath, 'utf-8');
if (!artifact.includes('data-impeccable-variants="' + id + '"')) {
return failure('artifact_missing_session_wrapper');
@@ -200,7 +184,7 @@ export function publishGenerationArtifact({
const commitStale = staleGenerationFailure(commitSnapshot, epoch);
if (commitStale) return commitStale;
const artifactHash = sha256(artifact);
const publishPath = sourceArtifactTarget?.previewPath || sourcePath;
const publishPath = sourcePath;
atomicReplace(publishPath, artifact);
const revision = Number(commitSnapshot.publishedRevision || 0) + 1;
store.appendEvent({
@@ -210,10 +194,6 @@ export function publishGenerationArtifact({
revision,
digest: artifactHash,
sourceFile: relative(cwd, sourcePath),
...(sourceArtifactTarget ? {
previewFile: relative(cwd, publishPath),
previewMode: SOURCE_ARTIFACT_PREVIEW_MODE,
} : {}),
arrivedVariants: delivered,
expectedVariants: Number(expectedVariants || snapshot.expectedVariants || delivered),
publicationKind: publicationKind || 'variants',
@@ -226,10 +206,6 @@ export function publishGenerationArtifact({
revision,
digest: artifactHash,
sourceFile: relative(cwd, sourcePath),
...(sourceArtifactTarget ? {
previewFile: relative(cwd, publishPath),
previewMode: SOURCE_ARTIFACT_PREVIEW_MODE,
} : {}),
arrivedVariants: delivered,
expectedVariants: Number(expectedVariants || snapshot.expectedVariants || delivered),
publicationKind: publicationKind || 'variants',
@@ -366,7 +342,7 @@ function publishComponentArtifact({
}
// Check the fence before writing anything. The prepare→publish gap is exactly
// where an Accept lands, and the source-artifact path above rechecks before
// where an Accept lands, and the non-component path above rechecks before
// its only write. Without the same check here, a canceled generation still
// scattered variant files across the generated component dir and left them
// there — the `stale_generation_epoch` returns below have no rollback.
@@ -467,15 +443,6 @@ function readComponentPublicationTarget(manifestPath, cwd, id) {
return { manifest, manifestPath, sourcePath, componentPath };
}
function readSourceArtifactPublicationTarget(requestedPath, cwd, id) {
const manifest = findSourceArtifactManifest(id, cwd);
if (!manifest) return null;
if (path.resolve(requestedPath) !== path.resolve(manifest.previewPath)) {
return failure('source_artifact_preview_mismatch');
}
return manifest;
}
function componentManifestMismatch(target, artifact) {
for (const field of COMPONENT_MANIFEST_FIELDS) {
if (JSON.stringify(target[field] ?? null) !== JSON.stringify(artifact[field] ?? null)) return field;
-74
View File
@@ -1,74 +0,0 @@
import fs from 'node:fs';
import path from 'node:path';
import { getLiveDir, safeSessionId } from '../lib/impeccable-paths.mjs';
export const SOURCE_ARTIFACT_PREVIEW_MODE = 'source-artifact';
export function scaffoldSourceArtifactSession({
id,
count,
sourceFile,
sourceStartLine,
sourceEndLine,
originalSource,
previewContent,
cwd = process.cwd(),
} = {}) {
safeSessionId(id);
const sourcePath = resolveInside(cwd, sourceFile);
if (!sourcePath || !fs.existsSync(sourcePath)) throw new Error('source artifact target missing');
const sessionDir = path.join(getLiveDir(cwd), 'previews', id);
const extension = path.extname(sourcePath) || '.html';
const previewPath = path.join(sessionDir, 'preview' + extension);
const manifestPath = path.join(sessionDir, 'manifest.json');
fs.mkdirSync(sessionDir, { recursive: true });
const manifest = {
id,
count: Number(count || 1),
previewMode: SOURCE_ARTIFACT_PREVIEW_MODE,
sourceFile: relative(cwd, sourcePath),
previewFile: relative(cwd, previewPath),
sourceStartLine: Number(sourceStartLine),
sourceEndLine: Number(sourceEndLine),
originalSource: String(originalSource || ''),
};
fs.writeFileSync(previewPath, String(previewContent || ''), 'utf-8');
fs.writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + '\n', 'utf-8');
return { ...manifest, manifestFile: relative(cwd, manifestPath), sessionDir: relative(cwd, sessionDir) };
}
export function findSourceArtifactManifest(id, cwd = process.cwd()) {
try { safeSessionId(id); } catch { return null; }
const manifestPath = path.join(getLiveDir(cwd), 'previews', id, 'manifest.json');
let manifest;
try { manifest = JSON.parse(fs.readFileSync(manifestPath, 'utf-8')); } catch { return null; }
if (manifest?.id !== id || manifest?.previewMode !== SOURCE_ARTIFACT_PREVIEW_MODE) return null;
const sourcePath = resolveInside(cwd, manifest.sourceFile);
const previewPath = resolveInside(cwd, manifest.previewFile);
if (!sourcePath || !previewPath || !fs.existsSync(sourcePath) || !fs.existsSync(previewPath)) return null;
return { ...manifest, manifestPath, sourcePath, previewPath };
}
export function removeSourceArtifactSession(id, cwd = process.cwd()) {
try { safeSessionId(id); } catch { return false; }
const sessionDir = path.join(getLiveDir(cwd), 'previews', id);
if (!fs.existsSync(sessionDir)) return false;
fs.rmSync(sessionDir, { recursive: true, force: true });
return true;
}
function resolveInside(cwd, value) {
if (!value || typeof value !== 'string') return null;
const root = path.resolve(cwd);
const resolved = path.resolve(root, value);
const rel = path.relative(root, resolved);
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return null;
return resolved;
}
function relative(cwd, value) {
return path.relative(cwd, value).split(path.sep).join('/');
}