plans/012-hmr-utils-refresh.md
utils refresh (missing await)Executor instructions: Follow this plan step by step. Run every verification command and confirm the expected result. If anything in "STOP conditions" occurs, stop and report. When done, update the status row in
plans/README.md.Drift check (run first):
git diff --stat c63cb120..HEAD -- packages/slidev/node/vite/loaders.ts packages/slidev/node/options.tsOn a mismatch with the excerpts below, treat it as a STOP condition.
c63cb120, 2026-07-10On hot-update, the loader intends to refresh the derived utils
(indexHtml, define, getLayouts, katex/shiki options) after the deck data
changes. But it calls the async createDataUtils without await:
Object.assign(utils, createDataUtils(options)). Object.assign copies the
own-enumerable properties of a Promise (there are none), so the refresh does
nothing and the promise floats unhandled. It's a genuine no-op; a rejection
would surface as an unhandled promise rejection.
packages/slidev/node/vite/loaders.ts:232-233, inside the async handleHotUpdate(ctx):
Object.assign(data, newData) // works: newData is a resolved object
Object.assign(utils, createDataUtils(options)) // no-op: createDataUtils is async
packages/slidev/node/options.ts:82:
export async function createDataUtils(resolved: Omit<ResolvedSlidevOptions, 'utils'>): Promise<ResolvedSlidevUtils> { ... }
data and utils were destructured from options at loaders.ts:27
(const { data, mode, utils, withoutNotes } = options), so they are the same
object references as options.data/options.utils. Object.assign(data, newData)
therefore updates options.data in place, and a subsequent
createDataUtils(options) reads the fresh data.handleHotUpdate is already async and awaits other work (e.g.
ctx.server.reloadModule at :262), so awaiting here is safe.| Purpose | Command | Expected |
|---|---|---|
| Install | pnpm install | exit 0 |
| Build | pnpm build | exit 0 |
| Typecheck | pnpm typecheck | exit 0 |
| Lint | pnpm lint | exit 0 |
In scope:
packages/slidev/node/vite/loaders.ts (the single line at :233)Out of scope:
handleHotUpdate.fix/hmr-utils-refresh.fix(server): await utils refresh on hot update.Change loaders.ts:233 to:
Object.assign(utils, await createDataUtils(options))
Verify: grep -n "Object.assign(utils, await createDataUtils" packages/slidev/node/vite/loaders.ts
returns one match; there is no remaining Object.assign(utils, createDataUtils(options))
without await.
Verify: pnpm build && pnpm typecheck && pnpm lint exit 0.
handleHotUpdate requires a live Vite dev server to exercise, and the loader
has no unit harness today, so no automated test is added (loader testing is
plan 022). The change is a one-token correctness fix; verification is
typecheck/build plus a manual HMR sanity check if a dev deck is available
(pnpm demo:dev, edit a slide, confirm no unhandled-rejection warning and the
page updates).loaders.ts:233 uses await createDataUtils(options)Object.assign(utils, createDataUtils(...)) remainspnpm build, pnpm typecheck, pnpm lint exit 0loaders.ts modified (git status)plans/README.md status row updatedStop and report if:
setupShiki/setupKatex/setupIndexHtml). If that cost is unacceptable, the
correct answer may be to make the refresh conditional/incremental instead —
report this so it can be folded into plan 024 rather than shipping a slow
refresh.data changes that actually invalidate a util
(config/theme/features), coordinating with plan 024.options.data is the mutated reference so the refreshed
utils reflect the new deck.