Skip to content

fix(blog): rich-text toolbar + inline link editor, and VTEX product picker#4882

Merged
aka-sacci-ccr merged 8 commits into
mainfrom
fix-blog-tiptap-toolbar
Jul 21, 2026
Merged

fix(blog): rich-text toolbar + inline link editor, and VTEX product picker#4882
aka-sacci-ccr merged 8 commits into
mainfrom
fix-blog-tiptap-toolbar

Conversation

@aka-sacci-ccr

@aka-sacci-ccr aka-sacci-ccr commented Jul 20, 2026

Copy link
Copy Markdown
Contributor

Blog CMS fixes, split across two commits.

1. Rich-text toolbar + inline link editor (fix)

The paragraph TipTap editor's formatting toolbar (bold/italic/underline/link) only appeared on keyboard interaction, not on mouse selection, and the link flow used a native window.prompt.

  • TipTap v3 no longer re-renders on transactions by default, so reading editor.isFocused/isActive() during render went stale — the toolbar never showed on mouse selection and mark highlights lagged. Now selected reactively via useEditorState.
  • Replaced the native window.prompt link dialog with an inline, Figma-like popover (Enter to apply, Esc to cancel, remove-link button). Extracted the shared ToolbarButton + new RichTextLinkControl, added normalizeLinkUrl (bare domains → https), and validated blog links with isSafeLinkUrl (previously unchecked).

2. VTEX product picker for ProductShelf/ProductCard (feat)

The shelf/card editors only accepted raw SKU ids typed by hand.

  • Added a Browse products dialog (VTEX-only for now, mirroring the preview path-param picker) to discover products by free search, category, or collection/cluster id — invoking vtex/loaders/intelligentSearch/productList.ts through the mesh preview-invoke proxy.
  • Toggling a product writes its SKU id via the existing writeProductListIds, so the section still receives Product[] and storage is unchanged.
  • Threaded the sandbox ref (org/vm/branch) from Post/Category editors → BlockDocumentBlockRowBlockEditor so blocks can invoke loaders. The picker only shows when a running sandbox is available; manual id entry remains the fallback.

Testing

  • bun test on the new pure logic (link normalization/validation, picker request-builders + payload mappers) — passing.
  • bun run fmt, oxlint, and workspace typecheck clean.
  • Picker exercised end-to-end against a running VTEX sandbox (search/category/cluster return real products; selected ids persist).

Note: the storefront-side rendering of the shelf (resolving ids → products on the site's blog route) lives in the site repo, not here — this PR is the CMS/editor side only.

🤖 Generated with Claude Code


Summary by cubic

Fixes the blog rich-text toolbar and link flow (now with a same-tab/new-tab toggle), adds a VTEX product picker with rich product cards, adds a heading level selector for ProductShelf titles, and flags incomplete posts to block preview until required fields are filled.

  • New Features

    • Product picker dialog: search by name/ID, browse by category, or by collection/cluster — invokes vtex/loaders/intelligentSearch/productList.ts and vtex/loaders/categories/tree.ts via the mesh preview-invoke proxy. Shows only with a running sandbox; manual ID input remains the fallback. Shelf shows a sortable product grid (drag to reorder, hover to remove) and ProductCard shows a compact selected-product row; still persists SKU IDs only via a new useProductsByIds lookup.
    • ProductShelf title: choose Normal/H1/H2/H3; the level is stored inline (e.g. <h1>Title</h1>) and the editor previews the size.
    • Post validation: highlight missing fields (Title, Slug, Category, Excerpt, Cover image) in the list and editor, show an issues badge, and disable “See preview” until fixed.
  • Bug Fixes

    • Rich-text toolbar now selects state via useEditorState, so it appears on mouse selection and mark highlights stay in sync.
    • Replaced the window.prompt link flow with an inline popover (Enter to apply, Esc to cancel, remove link) and added a same-tab/new-tab toggle that sets target (and rel for new-tab). Cleared TipTap’s default link HTMLAttributes so each link’s target controls behavior. Added normalizeLinkUrl and validated URLs with isSafeLinkUrl.
    • Removed unused REQUIRED_POST_FIELDS export.

Written for commit 8acbdef. Summary will update on new commits.

Review in cubic

decobot and others added 8 commits July 17, 2026 15:18
…editor

The TipTap v3 editor no longer re-renders on transactions by default, so
reading `editor.isFocused`/`isActive()` during render went stale — the
toolbar never appeared on mouse selection and mark highlights lagged. Select
this state reactively via `useEditorState`.

Replace the native `window.prompt()` link dialog with an inline, Figma-like
popover (Enter to apply, Esc to cancel, remove-link button). Extract the
shared `ToolbarButton` and new `RichTextLinkControl` into one module, add
`normalizeLinkUrl` (bare domains default to https), and validate blog links
with `isSafeLinkUrl` (previously unchecked).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
The shelf/card editors only accepted raw SKU ids typed by hand. Add a
"Browse products" dialog (VTEX-only for now, like the preview path-param
picker) to discover products by free search, category, or collection/cluster
id — invoking `vtex/loaders/intelligentSearch/productList.ts` through the mesh
preview-invoke proxy. Toggling a product writes its SKU id via the existing
writeProductListIds, so the section still receives Product[] and storage is
unchanged.

Thread the sandbox ref (org/vm/branch) from Post/Category editors down through
BlockDocument → BlockRow → BlockEditor so the blocks can invoke loaders; the
picker only shows when a running sandbox is available, with manual id entry as
the fallback. Pure request-builders and payload mappers are unit-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the raw SKU-id inputs with resolved product cards (thumbnail + name +
id): the stored ids are resolved back to products via a batched productList
invoke (new `useProductsByIds` hook, sharing `invokeLoader` with the picker
dialog). The shelf shows a responsive card grid with drag-to-reorder and
hover-to-remove, plus a dashed "Add products" empty state; ProductCard shows a
compact selected-product row. Manual id entry remains the fallback when no
sandbox is running.

Also fix the picker modal: swap the Radix ScrollArea (whose display:table
wrapper defeated truncation and let long names overflow) for a width-bounded
native scroll, add horizontal padding, and clamp product names to two lines.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Let the shelf title be Normal / H1 / H2 / H3. The level is stored inside the
value itself — H1 saves `<h1>Text</h1>`, Normal saves the bare `Text` — so the
section (which renders the title as HTML and already styles
`.section-title h1/h2/h3`) picks up the heading with no site change. The editor
parses the stored value back into (level, text), previews the chosen size, and
re-wraps on edit. Pure parse/format helpers are unit-tested.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
A post autosaves on every change, so it can end up missing required fields.
Add `missingPostFields` (Title, Slug, Category, Excerpt, Cover image) shared by
the list and the editor. Incomplete posts render red in the list with a warning
icon (tooltip listing what's missing), and the editor shows an "N issues"
badge and disables "See preview" until the post is complete — mirroring the
category preview gate, now for every required field.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
# Conflicts:
#	apps/mesh/src/web/components/sandbox/content/content-browser.tsx
CI knip flagged the export as dead — the field labels are inlined in
missingPostFields, so the constant had no consumer.
The link popover now has a "Same tab / New tab" toggle that sets the anchor's
`target` (and `rel` for new-tab links). The TipTap link extension forced
`target="_blank"` + rel on every link via its default HTMLAttributes, so a
same-tab link was impossible; clear those defaults in both editors
(paragraph block + sections-editor field) and drive target from each link's
own attribute. New links default to a new tab; editing an existing link
reflects its current target.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@aka-sacci-ccr
aka-sacci-ccr merged commit 7f96c03 into main Jul 21, 2026
14 checks passed
@aka-sacci-ccr
aka-sacci-ccr deleted the fix-blog-tiptap-toolbar branch July 21, 2026 15:09
decocms Bot pushed a commit that referenced this pull request Jul 21, 2026
PR: #4882 fix(blog): rich-text toolbar + inline link editor, and VTEX product picker
Bump type: patch

- decocms (apps/mesh/package.json): 4.82.19 -> 4.82.20

Deploy-Scope: web
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.

2 participants