From 8f3f31245858bf1f0fa27618be945debec1c7560 Mon Sep 17 00:00:00 2001 From: eleith Date: Tue, 3 Nov 2026 16:24:06 -0800 Subject: [PATCH 1/2] [web] share preview storage with the web container --- .changeset/shared-preview-storage.md | 5 +++++ apps/web/docker-compose.yml | 1 + apps/web/scripts/compose-mounts.test.ts | 14 ++++++++++++++ docs/web/deployment.md | 10 ++++++++-- docs/web/development.md | 2 +- docs/web/docker-compose.yml | 1 + 6 files changed, 30 insertions(+), 3 deletions(-) create mode 100644 .changeset/shared-preview-storage.md create mode 100644 apps/web/scripts/compose-mounts.test.ts diff --git a/.changeset/shared-preview-storage.md b/.changeset/shared-preview-storage.md new file mode 100644 index 00000000..01af4264 --- /dev/null +++ b/.changeset/shared-preview-storage.md @@ -0,0 +1,5 @@ +--- +"@luzzle/web": patch +--- + +Share worker-generated preview files with the web service through a read-only preview mount in the production Compose examples. Document the directory setup and mount update required for existing deployments. diff --git a/apps/web/docker-compose.yml b/apps/web/docker-compose.yml index d83dcce9..2a400dad 100644 --- a/apps/web/docker-compose.yml +++ b/apps/web/docker-compose.yml @@ -12,6 +12,7 @@ services: - "3000:3000" volumes: - ./demo/assets/pieces:/app/assets/pieces + - ./demo/assets/previews:/app/assets/previews:ro - ./demo/config:/app/config:ro - ./demo/data:/app/data - ./demo/content:/app/content diff --git a/apps/web/scripts/compose-mounts.test.ts b/apps/web/scripts/compose-mounts.test.ts new file mode 100644 index 00000000..7499e86d --- /dev/null +++ b/apps/web/scripts/compose-mounts.test.ts @@ -0,0 +1,14 @@ +import { readFileSync } from 'node:fs' +import { parse } from 'yaml' +import { expect, test } from 'vitest' + +// Deployment-contract coverage, not a running-container smoke test. +test.each([ + ['../docker-compose.yml', './demo/assets'], + ['../../../docs/web/docker-compose.yml', './assets'] +])('%s shares preview storage between worker and web', (file, assets) => { + const { services } = parse(readFileSync(new URL(file, import.meta.url), 'utf8')) + expect(services['luzzle-worker'].volumes).toContain(`${assets}:/app/assets`) + expect(services['luzzle-web'].volumes).toContain(`${assets}/previews:/app/assets/previews:ro`) + expect(services['luzzle-web'].volumes).toContain(`${assets}/pieces:/app/assets/pieces`) +}) diff --git a/docs/web/deployment.md b/docs/web/deployment.md index c4df9944..18d7dc0e 100644 --- a/docs/web/deployment.md +++ b/docs/web/deployment.md @@ -7,7 +7,7 @@ images. No checkout or custom build is needed. ```sh mkdir luzzle && cd luzzle -mkdir -p config archive assets/pieces data content rclone static nginx/cache +mkdir -p config archive assets/pieces assets/previews data content rclone static nginx/cache touch .env ``` @@ -55,7 +55,13 @@ Changes to `.env` or mounts require To update: back up your data, update `LUZZLE_VERSION` if pinned, then pull and start again. -## Preview cleanup +## Preview storage and cleanup + +The worker writes previews to `assets/previews`; the web service needs the same +directory mounted read-only. For existing deployments, first create this directory +with ownership and write permissions matching the worker's configured UID/GID. +Then add the preview mount from the Compose example and recreate `luzzle-web`; +updating the image alone does not change mounts. The worker removes expired previews and app logs daily around 02:00 UTC. Finished jobs are retained for seven days; workflow history stays. diff --git a/docs/web/development.md b/docs/web/development.md index fccd5f35..e334392a 100644 --- a/docs/web/development.md +++ b/docs/web/development.md @@ -22,7 +22,7 @@ Set `LUZZLE_AUTH_SECRET`, `LUZZLE_AUTH_PASSWORD` and, if using AI, `GOOGLE_API_K Generate a secret with `openssl rand -hex 32`. Keep `.env` out of Git. ```sh -mkdir -p demo/assets/pieces demo/data nginx/cache nginx/logs +mkdir -p demo/assets/pieces demo/assets/previews demo/data nginx/cache nginx/logs docker compose -f docker-compose.dev.yml -f docker-compose.dev.local.yml up --build ``` diff --git a/docs/web/docker-compose.yml b/docs/web/docker-compose.yml index 8f97ed3b..b304a13c 100644 --- a/docs/web/docker-compose.yml +++ b/docs/web/docker-compose.yml @@ -17,6 +17,7 @@ services: - ./config:/app/config:ro - ./archive:/app/archive - ./assets/pieces:/app/assets/pieces + - ./assets/previews:/app/assets/previews:ro - ./data:/app/data - ./content:/app/content:ro healthcheck: -- 2.54.0 From 99f793f149f96999e11b708144a2c42aa03360c0 Mon Sep 17 00:00:00 2001 From: eleith Date: Tue, 3 Nov 2026 16:24:06 -0800 Subject: [PATCH 2/2] [web] scale complete icon compositions in shared viewports --- .changeset/uniform-icon-viewport.md | 5 ++ .../src/lib/pieces/viewer/IconViewport.svelte | 52 ++++++++++++++++ .../lib/pieces/viewer/IconViewport.test.ts | 59 +++++++++++++++++++ .../preview/[jobId]/icon/+page.svelte | 21 +------ .../[...path]/published/icon/+page.svelte | 26 +------- 5 files changed, 120 insertions(+), 43 deletions(-) create mode 100644 .changeset/uniform-icon-viewport.md create mode 100644 apps/web/src/lib/pieces/viewer/IconViewport.svelte create mode 100644 apps/web/src/lib/pieces/viewer/IconViewport.test.ts diff --git a/.changeset/uniform-icon-viewport.md b/.changeset/uniform-icon-viewport.md new file mode 100644 index 00000000..fac5f768 --- /dev/null +++ b/.changeset/uniform-icon-viewport.md @@ -0,0 +1,5 @@ +--- +"@luzzle/web": patch +--- + +Use the same responsive icon viewport for Preview and Published views, scaling each complete composition uniformly instead of shrinking its individual parts on narrow screens. diff --git a/apps/web/src/lib/pieces/viewer/IconViewport.svelte b/apps/web/src/lib/pieces/viewer/IconViewport.svelte new file mode 100644 index 00000000..1345d0ce --- /dev/null +++ b/apps/web/src/lib/pieces/viewer/IconViewport.svelte @@ -0,0 +1,52 @@ + + +
+
+
+ +
+
+
+ + diff --git a/apps/web/src/lib/pieces/viewer/IconViewport.test.ts b/apps/web/src/lib/pieces/viewer/IconViewport.test.ts new file mode 100644 index 00000000..158c6921 --- /dev/null +++ b/apps/web/src/lib/pieces/viewer/IconViewport.test.ts @@ -0,0 +1,59 @@ +import { expect, test, vi } from 'vitest' +import { render } from 'svelte/server' +import IconViewport from './IconViewport.svelte' +import { publishedPiece as rawPublishedPiece } from './fixtures/published' + +const piece = { + ...rawPublishedPiece, + templates: { + icon: { name: 'default', fields: { image: 'cover' } }, + page: { name: 'default', fields: { image: 'cover' } }, + opengraph: { name: 'default', fields: { image: 'cover' } } + } +} + +const { page } = vi.hoisted(() => ({ + page: { + data: { + preview: false, + job: 'preview-job', + config: { url: { app: '/garden', luzzle_assets: '/cdn' } } + } + } +})) +vi.mock('$app/state', () => ({ page })) +vi.mock('$app/paths', () => ({ + resolve: (route: string, params: Record) => + route.replace(/\[(?:\.\.\.)?(\w+)\]/g, (_, name: string) => params[name]) +})) +// The synchronous renderer stub checks the viewport's SSR contract and real asset helpers. +// Node SSR cannot verify zoom layout, natural template heights, 3D paint/shadows, +// resize/orientation updates, or ResizeObserver mounting and cleanup in a browser. +vi.mock('#lib/pieces/components/icon.svelte', async () => ({ + default: (await import('./fixtures/IconRenderer.svelte')).default +})) + +test.each([ + { preview: false, src: '/cdn/pieces/assets/published/cover.jpg' }, + { preview: true, src: '/admin/preview/preview-job/asset/published/cover.jpg' } +])('SSR keeps eager 500px rendering and resource URLs (preview=$preview)', (state) => { + page.data.preview = state.preview + const html = render(IconViewport, { props: { piece } }).body + + expect(html.match(/]*class="icon-frame\b[^>]*>/)?.[0] + expect(frame).toBeDefined() + expect(frame).toContain('width: 500px; zoom: 1') + // Height belongs to the selected template, not a shared 750px canvas. + expect(frame).not.toContain('height:') +}) diff --git a/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/icon/+page.svelte b/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/icon/+page.svelte index 77943393..062b73de 100644 --- a/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/icon/+page.svelte +++ b/apps/web/src/routes/(auth)/admin/piece/[...path]/preview/[jobId]/icon/+page.svelte @@ -1,27 +1,10 @@ {#if data.status === 'completed' && data.piece} -
- -
+ {/if} - - diff --git a/apps/web/src/routes/(auth)/admin/piece/[...path]/published/icon/+page.svelte b/apps/web/src/routes/(auth)/admin/piece/[...path]/published/icon/+page.svelte index 54a53678..249a5c6c 100644 --- a/apps/web/src/routes/(auth)/admin/piece/[...path]/published/icon/+page.svelte +++ b/apps/web/src/routes/(auth)/admin/piece/[...path]/published/icon/+page.svelte @@ -1,29 +1,7 @@ -
-
- -
-
- - + -- 2.54.0