From a9151cce9a52e3dc1de95f8fe673bb3f2fa41416 Mon Sep 17 00:00:00 2001 From: eleith Date: Tue, 3 Nov 2026 15:20:37 -0800 Subject: [PATCH] [web] make previews explicit and progress replay-safe --- .changeset/explicit-replay-safe-previews.md | 5 + .../lib/components/editor/PieceToolbar.svelte | 30 +++-- .../components/editor/PieceToolbar.test.ts | 14 ++- apps/web/src/lib/components/progress.test.ts | 106 +++++++++++++++++ apps/web/src/lib/components/progress.ts | 37 ++++++ .../piece/[...path]/preview/+page.server.ts | 38 ++++--- .../piece/[...path]/preview/+page.svelte | 16 ++- .../[...path]/preview/[jobId]/+layout.svelte | 45 ++------ .../[...path]/preview/[jobId]/+page.svelte | 11 +- .../preview/[jobId]/WorkflowConsole.svelte | 7 +- .../preview/[jobId]/WorkflowConsole.test.ts | 16 +++ .../[...path]/preview/[jobId]/page.test.ts | 13 +++ .../[...path]/preview/page.server.test.ts | 36 ++++-- .../(auth)/admin/publish/report.test.ts | 107 ------------------ .../src/routes/(auth)/admin/publish/report.ts | 56 +-------- docs/web/README.md | 4 +- 16 files changed, 293 insertions(+), 248 deletions(-) create mode 100644 .changeset/explicit-replay-safe-previews.md create mode 100644 apps/web/src/lib/components/progress.test.ts diff --git a/.changeset/explicit-replay-safe-previews.md b/.changeset/explicit-replay-safe-previews.md new file mode 100644 index 00000000..0eae241d --- /dev/null +++ b/.changeset/explicit-replay-safe-previews.md @@ -0,0 +1,5 @@ +--- +"@luzzle/web": patch +--- + +Start previews through an explicit POST action instead of page loading or link preloading. Merge replayed preview logs by identity and show the latest attempt of each phase so working previews do not fail with duplicate rendering keys. diff --git a/apps/web/src/lib/components/editor/PieceToolbar.svelte b/apps/web/src/lib/components/editor/PieceToolbar.svelte index ee83923f..5f946ee3 100644 --- a/apps/web/src/lib/components/editor/PieceToolbar.svelte +++ b/apps/web/src/lib/components/editor/PieceToolbar.svelte @@ -1,6 +1,7 @@ + +
+ undefined}> +

Preview the saved contents of {data.file}.

+
+ +
+
+
diff --git a/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/+layout.svelte b/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/+layout.svelte index b1c1e4b6..d5bff3dd 100644 --- a/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/+layout.svelte +++ b/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/+layout.svelte @@ -5,6 +5,11 @@ import { resolve } from '$app/paths' import type { LayoutProps } from './$types' import { encodeArchivePath } from '#lib/pieces/navigation.js' + import { + mergePhaseLogs, + type ProgressPhase, + type JobProgressLog + } from '#lib/components/progress.js' import ArrowLeftIcon from 'virtual:icons/ph/arrow-left' import PieceOverview from '#lib/pieces/viewer/PieceOverview.svelte' @@ -15,39 +20,14 @@ type PreviewStatus = 'enqueued' | 'running' | 'completed' | 'failed' | 'expired' - type PhaseProgress = { - phase: string - status: string - started_at: number - finished_at: number | null - message: string | null - } - type PhaseLog = { - job_id: string - phase: string - line_number: number - ts: number - level: string - message: string - } - let status = $state( data.status === 'waiting' ? 'enqueued' : data.status === 'expired' ? 'expired' : data.status ) let errorMessage = $state(data.errorMessage ?? '') let streamDisconnected = $state(false) - const initialPhases = data.phases ?? [] - const initialLogs: Record = {} - if (data.logs) { - for (const log of data.logs) { - if (!initialLogs[log.phase]) initialLogs[log.phase] = [] - initialLogs[log.phase].push(log) - } - } - - let phases = $state(initialPhases) - let logs = $state>(initialLogs) + let phases = $state(data.phases ?? []) + let logs = $state(mergePhaseLogs({}, data.logs ?? [])) let eventSource: EventSource | null = null @@ -79,15 +59,8 @@ }) eventSource.addEventListener('log', (e) => { - const newLogs: PhaseLog[] = JSON.parse(e.data) - const updated = { ...logs } - for (const log of newLogs) { - if (!updated[log.phase]) { - updated[log.phase] = [] - } - updated[log.phase] = [...updated[log.phase], log] - } - logs = updated + const newLogs: JobProgressLog[] = JSON.parse(e.data) + logs = mergePhaseLogs(logs, newLogs) }) eventSource.addEventListener('done', (e) => { diff --git a/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/+page.svelte b/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/+page.svelte index ac09e7b4..4e0d2e6a 100644 --- a/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/+page.svelte +++ b/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/+page.svelte @@ -1,5 +1,5 @@ {#if data.status === 'completed'} diff --git a/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/WorkflowConsole.svelte b/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/WorkflowConsole.svelte index 1adf90b3..99a66f72 100644 --- a/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/WorkflowConsole.svelte +++ b/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/WorkflowConsole.svelte @@ -1,6 +1,6 @@