Add Svelte-native live mode adapter (#179)

* Fix live preview state for framework components

* Complete stateful live preview coverage

* Record Svelte manual validation

* Fix Svelte live mode adapter

* Fix live Steer apply flow

* Fix Svelte live variant refresh recovery

* Fix live exit bar teardown

* Consolidate Svelte live DeepSeek sweep

* Reconcile Svelte live browser after main rebase

* Fix live accept review regressions

* Fix carbonize column-zero indentation

* Fix live poll lease expiry flake

* Fix Svelte shader preview capture
This commit is contained in:
Abdul Wahab
2026-06-02 00:08:57 -07:00
committed by GitHub
parent 69b5f3af49
commit 6163ca0529
212 changed files with 51520 additions and 4992 deletions
+139 -7
View File
@@ -42,6 +42,10 @@ import {
} from './live-manual-edits-buffer.mjs';
import { buildManualEditEvidence } from './live-manual-edit-evidence.mjs';
import { commitManualEdits } from './live-commit-manual-edits.mjs';
import {
applyDeferredSvelteComponentAccepts,
removeAllSvelteComponentSessions,
} from './live-svelte-component.mjs';
const __dirname = path.dirname(fileURLToPath(import.meta.url));
// PRODUCT.md / DESIGN.md live wherever context.mjs resolves. The generated
@@ -103,6 +107,7 @@ const MIN_MANUAL_EDIT_APPLY_CHUNK_SIZE = 1;
const MAX_MANUAL_EDIT_APPLY_CHUNK_SIZE = 20;
const MANUAL_APPLY_COMPACT_TEXT_LIMIT = 240;
const MANUAL_APPLY_COMPACT_NEARBY_LIMIT = 4;
const POLL_LEASE_EXPIRY_TIMER_GRACE_MS = 2;
const DEBUG_MANUAL_EDIT_EVENTS = /^(1|true|yes)$/i.test(process.env.IMPECCABLE_LIVE_DEBUG_EVENTS || '');
function tombstoneTimedOutApplyId(eventId, details = {}) {
@@ -897,6 +902,8 @@ function leaseEvent(entry, leaseMs) {
return entry.event;
}
entry.leaseUntil = Date.now() + leaseMs;
scheduleLeaseFlush();
broadcastAgentPollingIfChanged();
return entry.event;
}
@@ -907,9 +914,16 @@ function acknowledgePendingEvent(id) {
const acknowledged = state.pendingEvents[idx].event;
state.pendingEvents.splice(idx, 1);
scheduleLeaseFlush();
broadcastAgentPollingIfChanged();
return acknowledged;
}
function findPendingEventById(id) {
if (!id) return null;
const entry = state.pendingEvents.find((item) => item.event?.id === id);
return entry?.event || null;
}
function manualApplyReplyCommand(eventOrId = 'EVENT_ID') {
const id = typeof eventOrId === 'string' ? eventOrId : eventOrId?.id || 'EVENT_ID';
return `live-poll.mjs --reply ${id} done --data '<json>'`;
@@ -955,6 +969,42 @@ function summarizePendingEventForStatus(entry) {
return summary;
}
function summarizeActiveSessionForClient(snapshot = {}) {
return {
id: snapshot.id,
phase: snapshot.phase,
pageUrl: snapshot.pageUrl ?? null,
sourceFile: snapshot.sourceFile ?? null,
previewFile: snapshot.previewFile ?? null,
previewMode: snapshot.previewMode ?? null,
expectedVariants: snapshot.expectedVariants ?? 0,
arrivedVariants: snapshot.arrivedVariants ?? 0,
visibleVariant: snapshot.visibleVariant ?? null,
checkpointRevision: snapshot.checkpointRevision ?? 0,
paramValues: snapshot.paramValues || {},
};
}
function activeSessionSummaries() {
if (!state.sessionStore) return [];
return state.sessionStore.listActiveSessions().map((snapshot) => summarizeActiveSessionForClient(snapshot));
}
function cancelQueuedAnonymousExitEvents() {
let removed = 0;
for (let i = state.pendingEvents.length - 1; i >= 0; i -= 1) {
const event = state.pendingEvents[i]?.event;
if (event?.type !== 'exit' || event.id) continue;
state.pendingEvents.splice(i, 1);
removed += 1;
}
if (removed > 0) {
scheduleLeaseFlush();
broadcastAgentPollingIfChanged();
}
return removed;
}
function cancelPendingManualApplyEvents(pageUrl, reason = 'manual_edit_discarded') {
const canceledById = new Map();
const shouldCancel = (event) => event?.type === 'manual_edit_apply' && (!pageUrl || event.pageUrl === pageUrl);
@@ -1001,7 +1051,6 @@ function scheduleLeaseFlush() {
clearTimeout(state.leaseTimer);
state.leaseTimer = null;
}
if (state.pendingPolls.length === 0) return;
const now = Date.now();
const nextLeaseUntil = state.pendingEvents
.map((entry) => entry.leaseUntil || 0)
@@ -1011,7 +1060,8 @@ function scheduleLeaseFlush() {
state.leaseTimer = setTimeout(() => {
state.leaseTimer = null;
flushPendingPolls();
}, Math.max(0, nextLeaseUntil - now));
broadcastAgentPollingIfChanged();
}, Math.max(0, nextLeaseUntil - now + POLL_LEASE_EXPIRY_TIMER_GRACE_MS));
}
function flushPendingPolls() {
@@ -1032,7 +1082,9 @@ function flushPendingPolls() {
}
function agentPollingConnected() {
return state.pendingPolls.length > 0;
const now = Date.now();
return state.pendingPolls.length > 0
|| state.pendingEvents.some((entry) => entry.leaseUntil && entry.leaseUntil > now);
}
function broadcastAgentPollingIfChanged() {
@@ -1318,7 +1370,7 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) {
if (p === '/status') {
const token = url.searchParams.get('token');
if (token !== state.token) { res.writeHead(401, { 'Content-Type': 'application/json' }); res.end(JSON.stringify({ error: 'Unauthorized' })); return; }
const sessions = state.sessionStore ? state.sessionStore.listActiveSessions() : [];
const sessions = activeSessionSummaries();
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
status: 'ok',
@@ -1423,6 +1475,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) {
if (p === '/events' && req.method === 'GET') {
const token = url.searchParams.get('token');
if (token !== state.token) { res.writeHead(401); res.end('Unauthorized'); return; }
clearTimeout(state.exitTimer);
state.exitTimer = null;
cancelQueuedAnonymousExitEvents();
res.writeHead(200, {
'Content-Type': 'text/event-stream',
'Cache-Control': 'no-cache',
@@ -1432,10 +1487,10 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) {
type: 'connected',
hasProjectContext: hasProjectContext(),
agentPolling: agentPollingConnected(),
activeSessions: activeSessionSummaries(),
}) + '\n\n');
state.sseClients.add(res);
clearTimeout(state.exitTimer);
// Keepalive: SSE comment every 30s prevents silent connection drops.
const heartbeat = setInterval(() => {
@@ -1827,6 +1882,9 @@ function createRequestHandler({ detectScript, sessionPath, livePath }) {
return;
}
}
if (msg.type === 'exit') {
cleanupSvelteComponentSessionsBeforeExit();
}
if (msg.type !== 'checkpoint') {
enqueueEvent(msg);
}
@@ -1905,6 +1963,36 @@ function handlePollGet(req, res, url) {
});
}
function sessionFileMetadataFromPollReply(file) {
if (!file || typeof file !== 'string') return { file };
const normalized = file.split(path.sep).join('/');
const base = { file: normalized };
if (!normalized.endsWith('/manifest.json') && normalized !== 'manifest.json') return base;
if (!normalized.includes('node_modules/.impeccable-live/') && !normalized.includes('src/lib/impeccable/')) return base;
let full;
try {
full = path.resolve(process.cwd(), normalized);
const rel = path.relative(process.cwd(), full);
if (!rel || rel.startsWith('..') || path.isAbsolute(rel)) return base;
} catch {
return base;
}
try {
const manifest = JSON.parse(fs.readFileSync(full, 'utf-8'));
if (manifest?.previewMode !== 'svelte-component' || !manifest.sourceFile) return base;
return {
file: String(manifest.sourceFile).split(path.sep).join('/'),
sourceFile: String(manifest.sourceFile).split(path.sep).join('/'),
previewFile: normalized,
previewMode: 'svelte-component',
};
} catch {
return base;
}
}
function handlePollPost(req, res) {
let body = '';
req.on('data', (c) => { body += c; });
@@ -1965,6 +2053,16 @@ function handlePollPost(req, res) {
res.end(JSON.stringify({ error: 'stale_manual_edit_apply_reply', ...rollback }));
return;
}
const pendingEventBeforeAck = findPendingEventById(msg.id);
if (pendingEventBeforeAck?.type === 'steer' && msg.type === 'steer_done'
&& !msg.file && !(typeof msg.message === 'string' && msg.message.trim())) {
res.writeHead(400, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({
error: 'steer_done_requires_file_or_message',
hint: 'Reply with --file after writing source, or include a message explaining an intentional no-op.',
}));
return;
}
const acknowledgedEvent = acknowledgePendingEvent(msg.id);
let skipJournalReply = false;
let existingSession = null;
@@ -1987,6 +2085,7 @@ function handlePollPost(req, res) {
}));
return;
}
const replyFileMeta = sessionFileMetadataFromPollReply(msg.file);
if (state.sessionStore && msg.id && !skipJournalReply) {
try {
const eventType = msg.type === 'steer_done'
@@ -2001,7 +2100,10 @@ function handlePollPost(req, res) {
state.sessionStore.appendEvent({
type: eventType,
id: msg.id,
file: msg.file,
file: replyFileMeta.file,
sourceFile: replyFileMeta.sourceFile,
previewFile: replyFileMeta.previewFile,
previewMode: replyFileMeta.previewMode,
message: msg.message,
sourceEventType: acknowledgedEvent?.type,
carbonize: msg.data?.carbonize === true,
@@ -2010,7 +2112,16 @@ function handlePollPost(req, res) {
}
flushPendingPolls();
// Forward the reply to the browser via SSE
broadcast({ type: msg.type || 'done', id: msg.id, message: msg.message, file: msg.file, data: msg.data });
broadcast({
type: msg.type || 'done',
id: msg.id,
message: msg.message,
file: msg.file,
sourceFile: replyFileMeta.sourceFile,
previewFile: replyFileMeta.previewFile,
previewMode: replyFileMeta.previewMode,
data: msg.data,
});
res.writeHead(200, { 'Content-Type': 'application/json' });
res.end(JSON.stringify({ ok: true }));
});
@@ -2023,6 +2134,7 @@ function handlePollPost(req, res) {
let httpServer = null;
function shutdown() {
cleanupSvelteComponentSessionsBeforeExit();
removeLiveServerInfo(process.cwd());
if (state.leaseTimer) clearTimeout(state.leaseTimer);
state.leaseTimer = null;
@@ -2037,6 +2149,25 @@ function shutdown() {
process.exit(0);
}
function cleanupSvelteComponentSessionsBeforeExit() {
try {
removeAllSvelteComponentSessions(process.cwd());
} catch (err) {
console.warn('[impeccable] Svelte component session cleanup failed:', err.message);
}
}
function applyLegacyDeferredAcceptsOnStartup() {
try {
const result = applyDeferredSvelteComponentAccepts(process.cwd());
if (result.applied > 0 || result.failed > 0) {
console.log('[impeccable] applied legacy deferred Svelte component accepts:', JSON.stringify(result));
}
} catch (err) {
console.warn('[impeccable] legacy deferred Svelte component accept apply failed:', err.message);
}
}
// ---------------------------------------------------------------------------
// Main
// ---------------------------------------------------------------------------
@@ -2162,6 +2293,7 @@ rollbackManualApplyTransaction({
cwd: process.cwd(),
reason: 'manual_edit_server_start_recovered_abandoned_transaction',
});
applyLegacyDeferredAcceptsOnStartup();
restorePendingEventsFromStore();
pruneStaleManualApplyEvidence(process.cwd());
const portArg = args.find(a => a.startsWith('--port='));