Loading source
Pulling the file list, source metadata, and syntax-aware rendering for this listing.
Source from repo
One-time setup that gathers your project's design context and saves it to CLAUDE.md for future sessions.
Files
Skill
Size
Entrypoint
Format
Open file
Syntax-highlighted preview of this file as included in the skill package.
scripts/live-svelte-component.mjs
1/**2* Svelte live-mode component injection helpers.3*4* Variants are real .svelte components under node_modules/.impeccable-live/<session-id>/.5* The browser mounts them via Svelte 5 mount(); accept inlines the chosen6* variant back into the route source with props mapped to original bindings.7*/89import fs from 'node:fs';10import path from 'node:path';11import os from 'node:os';12import { createHash } from 'node:crypto';1314export const SVELTE_COMPONENT_ROOT = 'node_modules/.impeccable-live';15export const SVELTE_RUNTIME_FILE = `${SVELTE_COMPONENT_ROOT}/__runtime.js`;16export const DEFERRED_ACCEPTS_FILE = '.impeccable/live/deferred-svelte-component-accepts.json';1718const MUSTACHE_RE = /\{([^{}]+)\}/g;1920export function shouldUseSvelteComponentInjection(filePath) {21if (/^(0|false|no)$/i.test(process.env.IMPECCABLE_LIVE_SVELTE_COMPONENT || '')) return false;22return path.extname(filePath).toLowerCase() === '.svelte';23}2425export function componentSessionDir(id, cwd = process.cwd()) {26return path.join(cwd, SVELTE_COMPONENT_ROOT, id);27}2829export function manifestPathForSession(id, cwd = process.cwd()) {30return path.join(componentSessionDir(id, cwd), 'manifest.json');31}3233export function ensureRuntimeHelper(cwd = process.cwd()) {34const file = path.join(cwd, SVELTE_RUNTIME_FILE);35if (fs.existsSync(file)) return file;36fs.mkdirSync(path.dirname(file), { recursive: true });37fs.writeFileSync(file, `export { mount, unmount } from 'svelte';\n`, 'utf-8');38return file;39}4041/**42* Extract ordered unique mustache expressions from markup (not inside <!-- -->).43*/44export function extractMustacheExpressions(text) {45const expressions = [];46const seen = new Set();47const lines = String(text || '').split('\n');48for (const line of lines) {49const trimmed = line.trim();50if (trimmed.startsWith('<!--')) continue;51let match;52MUSTACHE_RE.lastIndex = 0;53while ((match = MUSTACHE_RE.exec(line)) !== null) {54const expr = match[1].trim();55if (!expr || seen.has(expr)) continue;56seen.add(expr);57expressions.push(expr);58}59}60return expressions;61}6263export function buildPropContract(expressions) {64return expressions.map((expr, index) => {65const derived = derivePropName(expr, index);66return {67prop: derived,68expr,69placeholder: `{${expr}}`,70};71});72}7374function derivePropName(expr, index) {75const tail = expr.match(/(?:\.|\[)(\w+)\s*\]?$/);76if (tail && tail[1] && /^[A-Za-z_$][\w$]*$/.test(tail[1])) {77return tail[1];78}79return `prop${index}`;80}8182export function substituteExprsWithProps(markup, contract) {83let out = String(markup || '');84for (const entry of contract) {85out = out.split(entry.placeholder).join(`{${entry.prop}}`);86}87return out;88}8990export function substitutePropsWithExprs(markup, contract) {91let out = String(markup || '');92for (const entry of contract) {93out = out.split(`{${entry.prop}}`).join(`{${entry.expr}}`);94}95return out;96}9798export function parseSvelteComponentFile(content) {99const text = String(content || '');100const scriptMatch = text.match(/^([\s\S]*?)<script\b[^>]*>[\s\S]*?<\/script>/i);101const withoutScript = scriptMatch ? text.slice(scriptMatch[0].length) : text;102const styleMatch = withoutScript.match(/<style\b[^>]*>[\s\S]*?<\/style\s*>/i);103const styleBlock = styleMatch ? styleMatch[0] : '';104const markup = styleMatch105? withoutScript.slice(0, styleMatch.index).trim()106: withoutScript.trim();107const cssLines = styleBlock108? styleBlock109.replace(/^<style\b[^>]*>/i, '')110.replace(/<\/style\s*>$/i, '')111.split('\n')112.map((line) => line.trimEnd())113: [];114while (cssLines.length > 0 && cssLines[0].trim() === '') cssLines.shift();115while (cssLines.length > 0 && cssLines[cssLines.length - 1].trim() === '') cssLines.pop();116return { markup, cssLines, styleBlock };117}118119function buildPropsScript(contract) {120if (contract.length === 0) {121return '<script>\n /** @type {Record<string, never>} */\n let {} = $props();\n</script>\n';122}123const names = contract.map((c) => c.prop).join(', ');124const typeFields = contract.map((c) => ` ${c.prop}: string;`).join('\n');125return `<script>\n /** @type {{\n${typeFields}\n }} */\n let { ${names} } = $props();\n</script>\n`;126}127128function buildVariantStub(variantNum, originalWithProps, contract) {129const propsComment = contract.length > 0130? `\n<!-- Props: ${contract.map((c) => `${c.prop} <- {${c.expr}}`).join(', ')} -->\n`131: '';132return `${buildPropsScript(contract)}${propsComment}${originalWithProps.trim()}\n\n<style>\n /* Variant ${variantNum}: add scoped CSS here */\n</style>\n`;133}134135function buildInsertVariantStub(variantNum) {136return `${buildPropsScript([])}<div class="impeccable-insert-preview">Insert variant ${variantNum}</div>\n\n<style>\n .impeccable-insert-preview { display: block; }\n</style>\n`;137}138139export function scaffoldSvelteComponentSession({140id,141count,142sourceFile,143sourceStartLine,144sourceEndLine,145originalLines,146cwd = process.cwd(),147}) {148ensureRuntimeHelper(cwd);149const dir = componentSessionDir(id, cwd);150fs.mkdirSync(dir, { recursive: true });151152const originalMarkup = originalLines.join('\n');153const contract = buildPropContract(extractMustacheExpressions(originalMarkup));154const originalWithProps = substituteExprsWithProps(originalMarkup, contract);155156const manifest = {157id,158previewMode: 'svelte-component',159sourceFile: sourceFile.split(path.sep).join('/'),160sourceStartLine,161sourceEndLine,162count,163propContract: contract,164originalMarkup,165componentDir: path.relative(cwd, dir).split(path.sep).join('/'),166runtimeModule: `/${SVELTE_RUNTIME_FILE}`,167};168169fs.writeFileSync(path.join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');170171for (let n = 1; n <= count; n++) {172const variantFile = path.join(dir, `v${n}.svelte`);173if (!fs.existsSync(variantFile)) {174fs.writeFileSync(variantFile, buildVariantStub(n, originalWithProps, contract), 'utf-8');175}176}177178return {179manifest,180manifestFile: path.relative(cwd, path.join(dir, 'manifest.json')).split(path.sep).join('/'),181componentDir: manifest.componentDir,182propContract: contract,183};184}185186export function scaffoldSvelteComponentInsertSession({187id,188count,189sourceFile,190insertLine,191position,192anchorStartLine,193anchorEndLine,194anchorLines,195cwd = process.cwd(),196}) {197ensureRuntimeHelper(cwd);198const dir = componentSessionDir(id, cwd);199fs.mkdirSync(dir, { recursive: true });200201const anchorMarkup = (anchorLines || []).join('\n');202const manifest = {203id,204mode: 'insert',205previewMode: 'svelte-component',206sourceFile: sourceFile.split(path.sep).join('/'),207insertLine,208position,209anchorStartLine,210anchorEndLine,211originalMarkup: anchorMarkup,212anchorMarkup,213count,214propContract: [],215componentDir: path.relative(cwd, dir).split(path.sep).join('/'),216runtimeModule: `/${SVELTE_RUNTIME_FILE}`,217};218219fs.writeFileSync(path.join(dir, 'manifest.json'), JSON.stringify(manifest, null, 2) + '\n', 'utf-8');220221for (let n = 1; n <= count; n++) {222const variantFile = path.join(dir, `v${n}.svelte`);223if (!fs.existsSync(variantFile)) {224fs.writeFileSync(variantFile, buildInsertVariantStub(n), 'utf-8');225}226}227228return {229manifest,230manifestFile: path.relative(cwd, path.join(dir, 'manifest.json')).split(path.sep).join('/'),231componentDir: manifest.componentDir,232propContract: [],233};234}235236export function findSvelteComponentManifest(id, cwd = process.cwd()) {237const direct = manifestPathForSession(id, cwd);238if (fs.existsSync(direct)) {239return readManifest(direct);240}241const root = path.join(cwd, SVELTE_COMPONENT_ROOT);242if (!fs.existsSync(root)) return null;243for (const entry of fs.readdirSync(root, { withFileTypes: true })) {244if (!entry.isDirectory()) continue;245const candidate = path.join(root, entry.name, 'manifest.json');246if (!fs.existsSync(candidate)) continue;247try {248const manifest = readManifest(candidate);249if (manifest?.id === id) return { ...manifest, manifestPath: candidate };250} catch { /* skip */ }251}252return null;253}254255export function readManifest(manifestPath) {256const data = JSON.parse(fs.readFileSync(manifestPath, 'utf-8'));257return {258...data,259manifestPath,260};261}262263export function resolveSourceFile(sourceFile, cwd = process.cwd()) {264if (!sourceFile || path.isAbsolute(sourceFile)) {265throw new Error('Invalid svelte-component source file');266}267const full = path.resolve(cwd, sourceFile);268const rel = path.relative(cwd, full);269if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) {270throw new Error('Svelte-component source file escapes project root');271}272if (!fs.existsSync(full)) {273throw new Error('Svelte-component source file not found: ' + sourceFile);274}275return full;276}277278function appendCssToSvelteStyle(lines, cssLines) {279const closeIdx = findLastStyleCloseLine(lines);280const prepared = ['', ...cssLines.map((line) => (line.trim() === '' ? '' : ' ' + line.trimStart()))];281if (closeIdx === -1) {282return [...lines, '', '<style>', ...prepared.slice(1), '</style>'];283}284return [285...lines.slice(0, closeIdx),286...prepared,287...lines.slice(closeIdx),288];289}290291function findLastStyleCloseLine(lines) {292for (let i = lines.length - 1; i >= 0; i--) {293if (/<\/style\s*>/.test(lines[i])) return i;294}295return -1;296}297298function bakeParamValuesInCss(cssLines, paramValues) {299if (!paramValues || Object.keys(paramValues).length === 0) return cssLines;300return cssLines.map((line) => {301let out = line;302for (const [key, value] of Object.entries(paramValues)) {303const varName = `--p-${key}`;304out = out.replace(new RegExp(`var\\(${escapeRegExp(varName)}(?:,\\s*[^)]+)?\\)`, 'g'), String(value));305}306return out;307});308}309310function sanitizeAcceptedSvelteCss(cssLines, variantNum, paramValues = null, rootTag = 'div') {311const css = String((cssLines || []).join('\n'));312if (!/data-impeccable-variant|impeccable-variant-ready/.test(css)) return cssLines;313314const rules = parseCssRules(css);315const output = [];316for (const rule of rules) {317appendSanitizedCssRule(output, rule, variantNum, paramValues, rootTag);318}319return output.join('\n')320.split('\n')321.map((line) => line.trimEnd())322.filter((line) => line.trim() !== '');323}324325function appendSanitizedCssRule(output, rule, variantNum, paramValues, rootTag) {326const prelude = rule.prelude.trim();327const body = rule.body.trim();328if (!prelude || !body || /--impeccable-variant-ready\s*:/.test(body)) return;329330if (/^@scope\b/i.test(prelude)) {331if (/data-impeccable-variant/.test(prelude) && !selectorHasVariant(prelude, variantNum)) return;332const inner = parseCssRules(body);333for (const innerRule of inner) {334const rewrittenPrelude = rewriteAcceptedSvelteSelector(innerRule.prelude, variantNum, paramValues, rootTag, true);335if (!rewrittenPrelude || /--impeccable-variant-ready\s*:/.test(innerRule.body)) continue;336output.push(formatCssRule(rewrittenPrelude, innerRule.body.trim()));337}338return;339}340341const rewrittenPrelude = rewriteAcceptedSvelteSelector(prelude, variantNum, paramValues, rootTag, false);342if (!rewrittenPrelude) return;343output.push(formatCssRule(rewrittenPrelude, body));344}345346function parseCssRules(css) {347const rules = [];348const text = String(css || '');349let i = 0;350while (i < text.length) {351while (i < text.length && /\s/.test(text[i])) i++;352const preludeStart = i;353while (i < text.length && text[i] !== '{') i++;354if (i >= text.length) break;355const prelude = text.slice(preludeStart, i).trim();356i++;357const bodyStart = i;358let depth = 1;359let quote = null;360let comment = false;361while (i < text.length && depth > 0) {362const ch = text[i];363const next = text[i + 1];364if (comment) {365if (ch === '*' && next === '/') {366comment = false;367i += 2;368continue;369}370i++;371continue;372}373if (quote) {374if (ch === '\\') {375i += 2;376continue;377}378if (ch === quote) quote = null;379i++;380continue;381}382if (ch === '/' && next === '*') {383comment = true;384i += 2;385continue;386}387if (ch === '"' || ch === "'") {388quote = ch;389i++;390continue;391}392if (ch === '{') depth++;393else if (ch === '}') depth--;394i++;395}396const body = text.slice(bodyStart, Math.max(bodyStart, i - 1));397if (prelude) rules.push({ prelude, body });398}399return rules;400}401402function rewriteAcceptedSvelteSelector(prelude, variantNum, paramValues, rootTag, fromScope) {403const selectors = splitSelectorList(prelude);404const rewritten = [];405for (const selector of selectors) {406const next = rewriteAcceptedSvelteSelectorPart(selector, variantNum, paramValues, rootTag, fromScope);407if (next) rewritten.push(next);408}409return rewritten.join(', ');410}411412function rewriteAcceptedSvelteSelectorPart(selector, variantNum, paramValues, rootTag, fromScope) {413let out = selector.trim();414const hasVariant = /data-impeccable-variant/.test(out);415if (hasVariant && !selectorHasVariant(out, variantNum)) return '';416if (hasVariant) {417out = out.replace(variantSelectorRegex(variantNum), '');418out = out.replace(/\[data-impeccable-variant=(["']).*?\1\]/g, '');419}420421const paramResult = rewriteParamSelectors(out, paramValues);422if (!paramResult.keep) return '';423out = paramResult.selector;424425out = out426.replace(/:scope(?:\[[^\]]+\])?\s*>\s*/g, '')427.replace(/:scope(?:\[[^\]]+\])?/g, rootTag || '')428.replace(/\s+/g, ' ')429.trim();430431out = out.replace(/^[>+~]\s*/, '').trim();432if (!out && (hasVariant || fromScope)) return rootTag || ':global(*)';433return out;434}435436function rewriteParamSelectors(selector, paramValues) {437let keep = true;438const next = selector.replace(/\[data-p-([A-Za-z0-9_-]+)(?:=(["'])(.*?)\2)?\]/g, (_match, key, _quote, expected) => {439if (!paramValues || !Object.prototype.hasOwnProperty.call(paramValues, key)) return '';440const actual = paramValues[key];441if (expected != null && String(actual) !== String(expected)) {442keep = false;443return '';444}445if (expected == null && (actual === false || actual == null || actual === 'false' || actual === 'off' || actual === '0')) {446keep = false;447return '';448}449return '';450});451return { keep, selector: next };452}453454function splitSelectorList(prelude) {455const selectors = [];456let start = 0;457let bracket = 0;458let paren = 0;459let quote = null;460for (let i = 0; i < prelude.length; i++) {461const ch = prelude[i];462if (quote) {463if (ch === '\\') i++;464else if (ch === quote) quote = null;465continue;466}467if (ch === '"' || ch === "'") {468quote = ch;469continue;470}471if (ch === '[') bracket++;472else if (ch === ']') bracket = Math.max(0, bracket - 1);473else if (ch === '(') paren++;474else if (ch === ')') paren = Math.max(0, paren - 1);475else if (ch === ',' && bracket === 0 && paren === 0) {476selectors.push(prelude.slice(start, i));477start = i + 1;478}479}480selectors.push(prelude.slice(start));481return selectors;482}483484function selectorHasVariant(selector, variantNum) {485return variantSelectorRegex(variantNum).test(selector);486}487488function variantSelectorRegex(variantNum) {489return new RegExp(`\\[data-impeccable-variant=(["'])${escapeRegExp(String(variantNum))}\\1\\]`, 'g');490}491492function formatCssRule(selector, body) {493return `${selector} { ${body.trim()} }`;494}495496function escapeRegExp(value) {497return String(value).replace(/[.*+?^${}()|[\]\\]/g, '\\$&');498}499500export function inlineSvelteComponentAccept(manifest, variantNum, paramValues = null, cwd = process.cwd()) {501const sourceFile = resolveSourceFile(manifest.sourceFile, cwd);502const variantPath = path.join(cwd, manifest.componentDir, `v${variantNum}.svelte`);503const resultBase = {504file: manifest.sourceFile,505sourceFile: manifest.sourceFile,506previewMode: 'svelte-component',507componentDir: manifest.componentDir,508carbonize: false,509};510if (!fs.existsSync(variantPath)) {511return { handled: false, error: `Variant ${variantNum} not found`, ...resultBase };512}513514const { markup, cssLines } = parseSvelteComponentFile(fs.readFileSync(variantPath, 'utf-8'));515if (manifest.mode === 'insert') {516return inlineSvelteComponentInsertAccept({517manifest,518markup,519cssLines,520variantNum,521paramValues,522sourceFile,523resultBase,524cwd,525});526}527528const rootTag = matchOpeningTag(markup)?.tag || 'div';529const contract = manifest.propContract || [];530const mergedMarkup = mergeOriginalTopLevelAttrs(markup, manifest.originalMarkup || '');531const restoredMarkup = substitutePropsWithExprs(mergedMarkup, contract)532.split('\n')533.map((line) => line.trimEnd());534535const sourceContent = fs.readFileSync(sourceFile, 'utf-8');536const sourceLines = sourceContent.split('\n');537const start = Number(manifest.sourceStartLine) - 1;538const end = Number(manifest.sourceEndLine) - 1;539if (!Number.isInteger(start) || !Number.isInteger(end) || start < 0 || end < start || end >= sourceLines.length) {540return { handled: false, error: 'Invalid source line range for ' + manifest.sourceFile, ...resultBase };541}542543const indent = sourceLines[start].match(/^(\s*)/)?.[1] || '';544const indentedMarkup = restoredMarkup.map((line) => {545if (line.trim() === '') return '';546return indent + line.trimStart();547});548549let newLines = [550...sourceLines.slice(0, start),551...indentedMarkup,552...sourceLines.slice(end + 1),553];554555const sanitizedCss = sanitizeAcceptedSvelteCss(cssLines, variantNum, paramValues, rootTag);556const bakedCss = bakeParamValuesInCss(sanitizedCss, paramValues);557if (bakedCss.length > 0) {558newLines = appendCssToSvelteStyle(newLines, bakedCss);559}560561try {562fs.writeFileSync(sourceFile, newLines.join('\n'), 'utf-8');563} catch (err) {564return { handled: false, error: 'Failed to write Svelte source: ' + err.message, ...resultBase };565}566removeSvelteComponentSession(manifest.id, cwd);567568return {569handled: true,570...resultBase,571};572}573574function inlineSvelteComponentInsertAccept({575manifest,576markup,577cssLines,578variantNum,579paramValues,580sourceFile,581resultBase,582cwd,583}) {584if (!svelteMarkupHasVisibleContent(markup)) {585return { handled: false, error: 'Accepted Svelte insert variant is empty', ...resultBase };586}587if (/\bdata-impeccable-[\w-]*\s*=/.test(markup)) {588return { handled: false, error: 'Accepted Svelte insert variant contains preview-only data-impeccable attributes', ...resultBase };589}590591const rootTag = matchOpeningTag(markup)?.tag || 'div';592const restoredMarkup = String(markup || '')593.split('\n')594.map((line) => line.trimEnd());595const sourceContent = fs.readFileSync(sourceFile, 'utf-8');596const sourceLines = sourceContent.split('\n');597const insertIndex = Number(manifest.insertLine) - 1;598if (!Number.isInteger(insertIndex) || insertIndex < 0 || insertIndex > sourceLines.length) {599return { handled: false, error: 'Invalid insert line for ' + manifest.sourceFile, ...resultBase };600}601602const nearbyLine = sourceLines[insertIndex] ?? sourceLines[insertIndex - 1] ?? '';603const indent = nearbyLine.match(/^(\s*)/)?.[1] || '';604const indentedMarkup = restoredMarkup.map((line) => {605if (line.trim() === '') return '';606return indent + line.trimStart();607});608609let newLines = [610...sourceLines.slice(0, insertIndex),611...indentedMarkup,612...sourceLines.slice(insertIndex),613];614615const sanitizedCss = sanitizeAcceptedSvelteCss(cssLines, variantNum, paramValues, rootTag);616const bakedCss = bakeParamValuesInCss(sanitizedCss, paramValues);617if (bakedCss.length > 0) {618newLines = appendCssToSvelteStyle(newLines, bakedCss);619}620621try {622fs.writeFileSync(sourceFile, newLines.join('\n'), 'utf-8');623} catch (err) {624return { handled: false, error: 'Failed to write Svelte source: ' + err.message, ...resultBase };625}626removeSvelteComponentSession(manifest.id, cwd);627628return {629handled: true,630...resultBase,631};632}633634function svelteMarkupHasVisibleContent(markup) {635const text = String(markup || '')636.replace(/<script[\s\S]*?<\/script>/gi, '')637.replace(/<style[\s\S]*?<\/style>/gi, '')638.replace(/<!--[\s\S]*?-->/g, '')639.replace(/<[^>]+>/g, ' ')640.replace(/\s+/g, ' ')641.trim();642if (text.length > 0) return true;643return /<(img|svg|canvas|video|audio|picture|input|button|select|textarea)\b/i.test(markup || '');644}645646function mergeOriginalTopLevelAttrs(markup, originalMarkup) {647const variantOpen = matchOpeningTag(markup);648const originalOpen = matchOpeningTag(originalMarkup);649if (!variantOpen || !originalOpen) return markup;650if (variantOpen.tag.toLowerCase() !== originalOpen.tag.toLowerCase()) return markup;651652const variantAttrs = parseAttrSegments(variantOpen.attrs);653const originalAttrs = parseAttrSegments(originalOpen.attrs);654const additions = [];655let attrs = variantOpen.attrs;656657const originalClass = originalAttrs.get('class');658const variantClass = variantAttrs.get('class');659if (originalClass && variantClass) {660const merged = mergeStaticClassAttr(originalClass, variantClass);661if (merged) {662attrs = attrs.slice(0, variantClass.start) + merged + attrs.slice(variantClass.end);663variantAttrs.set('class', { ...variantClass, raw: merged });664}665} else if (originalClass && !variantClass) {666additions.push(originalClass.raw);667}668669for (const [name, attr] of originalAttrs) {670if (name === 'class') continue;671if (!variantAttrs.has(name)) additions.push(attr.raw);672}673674if (additions.length === 0 && attrs === variantOpen.attrs) return markup;675const nextOpen = variantOpen.prefix676+ variantOpen.tag677+ attrs678+ additions.map((attr) => ' ' + attr.trim()).join('')679+ variantOpen.close;680return markup.slice(0, variantOpen.index) + nextOpen + markup.slice(variantOpen.index + variantOpen.raw.length);681}682683function matchOpeningTag(markup) {684const match = String(markup || '').match(/^(\s*<)([A-Za-z][\w:-]*)([^>]*?)(\/?>)/);685if (!match) return null;686return {687raw: match[0],688prefix: match[1],689tag: match[2],690attrs: match[3] || '',691close: match[4],692index: match.index || 0,693};694}695696function parseAttrSegments(attrs) {697const out = new Map();698const re = /([A-Za-z_:][\w:.-]*)(?:\s*=\s*(?:"[^"]*"|'[^']*'|\{[^}]*\}|[^\s"'>=]+))?/g;699let match;700while ((match = re.exec(attrs))) {701const raw = match[0];702const name = match[1];703out.set(name, {704name,705raw,706start: match.index,707end: match.index + raw.length,708});709}710return out;711}712713function mergeStaticClassAttr(originalClass, variantClass) {714const originalValue = originalClass.raw.match(/class\s*=\s*(["'])(.*?)\1/);715const variantValue = variantClass.raw.match(/class\s*=\s*(["'])(.*?)\1/);716if (!originalValue || !variantValue) return null;717const quote = variantValue[1];718const classes = [719...variantValue[2].split(/\s+/),720...originalValue[2].split(/\s+/),721].filter(Boolean);722return `class=${quote}${[...new Set(classes)].join(' ')}${quote}`;723}724725export function removeSvelteComponentSession(id, cwd = process.cwd()) {726const dir = componentSessionDir(id, cwd);727try {728fs.rmSync(dir, { recursive: true, force: true });729} catch { /* non-fatal */ }730}731732export function removeAllSvelteComponentSessions(cwd = process.cwd()) {733const root = path.join(cwd, SVELTE_COMPONENT_ROOT);734if (!fs.existsSync(root)) return;735for (const entry of fs.readdirSync(root, { withFileTypes: true })) {736if (!entry.isDirectory()) continue;737if (entry.name.startsWith('__')) continue;738try {739fs.rmSync(path.join(root, entry.name), { recursive: true, force: true });740} catch { /* non-fatal */ }741}742}743744export function deferredAcceptsPath(cwd = process.cwd()) {745const key = createHash('sha1').update(path.resolve(cwd)).digest('hex').slice(0, 16);746return path.join(os.tmpdir(), 'impeccable-live', key, 'deferred-svelte-component-accepts.json');747}748749export function readDeferredAccepts(cwd = process.cwd()) {750const file = deferredAcceptsPath(cwd);751try {752return JSON.parse(fs.readFileSync(file, 'utf-8'));753} catch {754return { accepts: [] };755}756}757758export function writeDeferredAccept(entry, cwd = process.cwd()) {759const file = deferredAcceptsPath(cwd);760fs.mkdirSync(path.dirname(file), { recursive: true });761const data = readDeferredAccepts(cwd);762data.accepts = (data.accepts || []).filter((item) => item.id !== entry.id);763data.accepts.push({ ...entry, createdAt: new Date().toISOString() });764fs.writeFileSync(file, JSON.stringify(data, null, 2) + '\n', 'utf-8');765}766767export function applyDeferredSvelteComponentAccepts(cwd = process.cwd()) {768const file = deferredAcceptsPath(cwd);769const data = readDeferredAccepts(cwd);770const pending = Array.isArray(data.accepts) ? data.accepts : [];771const results = [];772const remaining = [];773for (const entry of pending) {774try {775const manifest = findSvelteComponentManifest(entry.id, cwd);776if (!manifest) {777results.push({ id: entry.id, ok: false, error: 'manifest not found' });778remaining.push(entry);779continue;780}781const result = inlineSvelteComponentAccept(782manifest,783entry.variantNum,784entry.paramValues || null,785cwd,786);787results.push({ id: entry.id, ok: result.handled !== false, result });788if (result.handled === false) remaining.push(entry);789} catch (err) {790results.push({ id: entry.id, ok: false, error: err.message });791remaining.push(entry);792}793}794if (remaining.length > 0) {795fs.writeFileSync(file, JSON.stringify({ accepts: remaining }, null, 2) + '\n', 'utf-8');796} else {797try { fs.rmSync(file, { force: true }); } catch {}798}799return { applied: results.filter((r) => r.ok).length, failed: results.filter((r) => !r.ok).length, results };800}801802export function buildSvelteComponentCssAuthoring(count) {803const variantNumbers = Array.from({ length: count }, (_, i) => i + 1);804return {805mode: 'svelte-component',806styleTag: null,807strategy: 'component-style-block',808rulePattern: '.semantic-class { ... }',809selectorExamples: variantNumbers.map(() => '.expense-row { padding: 22px; }'),810requirements: [811'Write each variant as a real Svelte component file (v1.svelte, v2.svelte, ...).',812'Keep the prop names from propContract; bind dynamic text with {propName}, not literal snapshot text.',813'Put variant CSS in the component <style> block using semantic class selectors.',814'Author param-driven CSS against var(--p-<id>, default) and [data-p-<id>] using :global(...) so the runtime knob values reach the mounted root.',815'Declare params in componentDir/params.json keyed by variant number (e.g. {"1": [...], "2": [...]}), NOT as a data-impeccable-params attribute.',816'Do not use @scope or data-impeccable-variant selectors in component files.',817'Do not edit the route source file during generation; only edit files under componentDir.',818],819forbidden: [820'Do not use @scope blocks in Svelte component variants.',821'Do not copy live DOM snapshot text into markup when propContract provides bindings.',822'Do not add data-impeccable-* attributes inside component files. Svelte parses { in attribute values as an expression, so data-impeccable-params with JSON breaks the build; use componentDir/params.json instead.',823],824paramsFile: 'params.json',825};826}827