Skip to content

🐛 Merge scoped preview updates and fix duplicate Vue instances - #9

Merged
badmike merged 1 commit into
mainfrom
fix/preview-scoped-updates
Aug 11, 2026
Merged

badmike merged 1 commit into
mainfrom
fix/preview-scoped-updates

Conversation

@badmike

@badmike badmike commented Aug 11, 2026

Copy link
Copy Markdown
Member

Why

Editing a block in the visual editor collapsed the preview to just that block, and Nuxt apps could fail hydration with "B10cks data API was not found in Vue injection context".

The editor sends CONTENT_UPDATE scoped to the edited block (payload = that block, carrying its id); only editing the root pushes the whole tree. PreviewStore assigned the payload as the new root, so any nested edit replaced the page.

What changed

@b10cks/client

  • PreviewStore.applyContentUpdate() / mergeContentUpdate() merge an update by id: no id → whole tree, root id → replace root, nested id → immutable in-place replace, unknown id → ignored (no notify). findPathById() backs the lookup.
  • New toRootBlock(entry){ ...entry.content, id, block }. An entry's content has no id of its own, so without it the root block is neither selectable via v-editable nor matchable by root-level updates.
  • getConfig() now includes the config entry's id, so v-editable="config" works for config-driven regions (header/footer). Breaking only for a config schema with its own id field, which is now shadowed.

@b10cks/vue

  • Injection keys use Symbol.for(...) so two copies of the package (Vite dep pre-bundling vs. SSR resolution) still match. Values stay per app instance.
  • Re-exports toRootBlock / RootBlock.

@b10cks/nuxt

  • Registers @b10cks/vue, @b10cks/client, @b10cks/richtext in vite.resolve.dedupe and optimizeDeps.exclude, and transpiles them by bare specifier — the previous resolver.resolve('@b10cks/vue') produced a nonexistent path under the module directory.
  • Auto-imports toRootBlock.

READMEs for all three packages updated with the new preview pattern.

Tests

New coverage for toRootBlock, findPathById, mergeContentUpdate, scoped/root/unknown applyContentUpdate, and the config id. pnpm lint, pnpm format, pnpm build, pnpm typecheck, pnpm test all pass locally (client: 75 tests).

Changeset: minor for @b10cks/client, @b10cks/vue, @b10cks/nuxt.

🤖 Generated with Claude Code

https://claude.ai/code/session_01E6isfVxvUpARWAFeWhxUko

The editor sends CONTENT_UPDATE scoped to the edited block, which the
preview store applied as the new root and collapsed the page to that
block. Merge by block id instead, and make the root block addressable.

- PreviewStore.applyContentUpdate / mergeContentUpdate merge by id
- toRootBlock(entry) carries the entry id into the root block
- getConfig() returns the config entry id so v-editable="config" works
- Vue injection keys use Symbol.for so a duplicated @b10cks/vue copy
  cannot break useB10cksApi() during hydration
- Nuxt dedupes/excludes the b10cks packages in Vite and transpiles them
  by bare specifier

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01E6isfVxvUpARWAFeWhxUko
@badmike
badmike merged commit 8014426 into main Aug 11, 2026
6 checks passed
@github-actions github-actions Bot mentioned this pull request Aug 24, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant