Skip to content

fix(deploy): ship data/ so Docker images keep their baked plugin previews - #7028

Open
jiulongche wants to merge 1 commit into
nexu-io:mainfrom
jiulongche:fix/docker-copy-plugin-previews-manifest
Open

fix(deploy): ship data/ so Docker images keep their baked plugin previews#7028
jiulongche wants to merge 1 commit into
nexu-io:mainfrom
jiulongche:fix/docker-copy-plugin-previews-manifest

Conversation

@jiulongche

Copy link
Copy Markdown
Contributor

Fixes #7018

Why

I run a self-hosted Docker deployment. Chasing blank thumbnails in the home rail, I found that no plugin was using its baked preview — all 461 were rendering as live, scaled iframes, which plugin-preview-bakes.ts opens by noting is "GPU-expensive at scale" and is the whole reason the bakes exist.

The cause is one missing COPY. @lefarcen confirmed it in #7018 and labelled it help wanted / good first issue.

What users will see

On Docker/self-hosted deployments, plugin cards in the home gallery show their baked poster and hover clip again instead of running a live iframe each. That is the same rendering the packaged app has always had; those users see no change.

Operators additionally get one log line if the manifest is ever missing again, instead of a gallery that is merely slower for no stated reason.

The chain

deploy/Dockerfile copies skills, design-systems, craft, prompt-templates, assets and plugins/_official. data/ is the one content directory missing from that list, and it holds the manifest CI commits for exactly this purpose:

$ docker exec <daemon> ls /app/data/plugin-previews
ls: /app/data/plugin-previews: No such file or directory

From there everything degrades quietly:

  • resolvePluginPreviewsDir() defaults to <projectRoot>/data/plugin-previews
  • loadManifest() finds no manifest.json and returns {}
  • applyBakedPreviews() early-returns the records untouched, so nothing gets an od.bakedPreview
  • inferPluginPreview(record, { preferBaked: true }) falls through to the live-HTML branch

Measured on 0.19.2 with 461 registered plugins: the checked-in manifest has 176 entries, and 0 records carried od.bakedPreview. With data/ present the same daemon reports 176 — deck 79, prototype 79, video 10, scenario 4, image 1, audio 1, template 2.

The packaged app reaches the manifest through its own packaging path, which is why this only bites Docker/self-hosted deployments and why it has gone unreported.

The second change

loadManifest() now warns once per path when the manifest is absent. That is not scope creep so much as finishing a thought already in the file — the malformed branch carries this comment:

// A malformed/unreadable manifest would otherwise silently disable every baked preview with no trace; surface it so it's diagnosable.

…and warns accordingly, while the absent branch returned {} with no message. Same failure, same lack of trace. The notice is deduplicated per path because loadManifest runs on every plugin listing.

Surface area

  • UI
  • Keyboard shortcut
  • CLI / env var
  • API / contract
  • Extension point
  • i18n keys
  • New top-level dependency
  • Default behavior change
  • None — packaging fix plus a diagnostic log line and their regression specs.

Screenshots

n/a — no UI surface. The visible effect is which of two existing render paths a card takes, and the counts above measure that directly.

Bug fix verification

  • Test paths: e2e/tests/docker-image-content-directories.test.ts, apps/daemon/tests/plugin-preview-bakes.test.ts
  • Red on main, green on this branch: yes

Pre-fix output:

× copies every runtime-resolved content directory into both stages
    AssertionError: build stage should COPY data: expected 'ARG NODE_IMAGE=docker.io/library/node…' to match /^COPY data \.\/data$/
× ships the checked-in plugin preview manifest
× says so when there is no manifest at all, instead of disabling bakes silently
    AssertionError: expected [] to have a length of 1 but got +0

The Dockerfile assertion lives in e2e/tests/ per the root AGENTS.md rule that repository-resource consistency checks belong there. It covers the other four runtime-resolved directories too, not just data/, so the next one to go missing fails on the way in rather than in someone's deployment. assets is deliberately excluded: the runtime stage copies it selectively (assets/frames, assets/community-pets), so a whole-directory assertion would be wrong.

The daemon spec calls applyBakedPreviews twice against the same manifest-less directory, so it pins the dedup as well as the notice.

Validation

  • pnpm --filter @open-design/daemon exec vitest run tests/plugin-preview-bakes.test.ts — 6 passed
  • pnpm --filter @open-design/e2e exec vitest run tests/docker-image-content-directories.test.ts — 2 passed
  • pnpm --filter @open-design/daemon typecheck — pass
  • pnpm guard — pass

I have not built the image in CI here; the Dockerfile change is two COPY lines mirroring the ones directly above them, and the spec asserts both stages.

Not addressed here

#7019 is the rendering bug this one exposes — with no manifest, every card lands on the live-iframe path, and deck tiles there position outside their frame. Separate PR (#7026). Fixing this issue hides that symptom for deck (79 of 80 are baked) but does not resolve it: 285 of the 461 bundled plugins have no bake at all, and user-installed plugins never do.

…iews

`deploy/Dockerfile` copies skills, design-systems, craft, prompt-templates,
assets and plugins/_official, but not data/ — so
`data/plugin-previews/manifest.json`, which CI commits precisely so deployments
can find the bakes, is absent at runtime. `resolvePluginPreviewsDir()` defaults
to <projectRoot>/data/plugin-previews, `loadManifest()` returns {},
`applyBakedPreviews()` returns the records untouched, and
`inferPluginPreview(record, { preferBaked: true })` falls through to the live
iframe for every plugin.

Measured on 0.19.2 with 461 registered plugins: the checked-in manifest has 176
entries and 0 records carried od.bakedPreview. With data/ present the same
daemon reports 176 — deck 79, prototype 79, video 10, scenario 4, image 1,
audio 1, template 2. The packaged app reaches the manifest through its own
packaging path, so only Docker/self-hosted deployments are affected.

Also warn once per path when the manifest is absent. `loadManifest()` already
carries the reasoning for the malformed case — «would otherwise silently
disable every baked preview with no trace; surface it so it's diagnosable» —
but the absent branch returned {} with no message, which is the same failure
with the same lack of trace.

Fixes nexu-io#7018
@lefarcen
lefarcen requested a review from nettee August 18, 2026 06:51
@lefarcen lefarcen added size/M PR changes 100-300 lines risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps type/bugfix Bug fix labels Aug 18, 2026
@lefarcen lefarcen added the needs-validation Runtime change detected; needs human or /explore agent validation. label Aug 18, 2026
@lefarcen

Copy link
Copy Markdown
Contributor

Nice catch on the missing data/ copy path.

This PR changes a user-visible runtime behavior in Docker/self-hosted deployments, so it needs a manual QA pass before merge — please hold off self-merging for now; we'll loop QA in once it's merge-ready.

@nettee nettee left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@jiulongche I reviewed every changed range and verified the Docker manifest is copied through both build stages, the one-per-path missing-manifest diagnostic is covered, and the focused daemon/e2e tests, typechecks, repository guard, and Dockerfile check all pass. This is a focused, well-tested fix—thanks for tracing the self-hosted regression and adding the regression coverage.

🔁 Powered by Looper · runner=reviewer · agent=codex · An autonomous AI dev team for your GitHub repos.

@lefarcen
lefarcen requested a review from AmyShang-alt August 18, 2026 07:06
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs-validation Runtime change detected; needs human or /explore agent validation. risk/high High risk: apps/desktop, daemon, auth, migration, workflows, package deps size/M PR changes 100-300 lines type/bugfix Bug fix

Projects

None yet

Development

Successfully merging this pull request may close these issues.

[Bug]: Docker images ship without data/plugin-previews/manifest.json, silently disabling every baked plugin preview

4 participants