Skip to content

feat(nextjs,tanstack): v8-only StudioBridge and DecoDocument, Cypress-safe "." shortcut - #806

Merged
tlgimenes merged 1 commit into
mainfrom
feat/studiobridge-v8-only
Nov 10, 2026
Merged

tlgimenes merged 1 commit into
mainfrom
feat/studiobridge-v8-only

Conversation

@tlgimenes

@tlgimenes tlgimenes commented Nov 10, 2026 •

Copy link
Copy Markdown
Contributor

Not merged — waiting for review.

Summary

@decocms/nextjs's StudioBridge and @decocms/tanstack's DecoDocument / DecoPreview become v8 only:

  • v7's framed handler (scrollToComponent, editor::rerender, the core STUDIO_BRIDGE_SCRIPT branch guarded by window.self!==window.top) is no longer rendered. Studio's v8 editor reaches the page through the Studio channel only.
  • Both render StudioChannel from @decocms/blocks/preview with the exact studioOrigins() (option, else DECO_STUDIO_ORIGIN, else studio.decocms.com + studio-stg.decocms.com), as before.
  • v7's "." edit shortcut stays (Studio still serves /choose-editor), as a separate inline script, studioShortcut.ts. It is off in a frame. It reads the frame state as var w=window,p=w.parent; if(p!==w)return, which none of Cypress's frame-busting regexes match. It keeps the same data-deco-studio-bridge / data-site / data-shortcut / data-path-template / data-page-id attributes and the StudioPage marker lookup.

Why: Cypress's modifyObstructiveCode rewrites window.self!==window.top everywhere in proxied HTML. That includes the RSC payload, where a Server Component's inline script travels as a length-prefixed text row. The row comes out one byte longer than its prefix, React's Flight client misreads the stream, and every page throws an uncaught Connection closed.. This broke the osklen Cypress suite on deco-sites/osklenbr-faststore#1388, which is why that site rendered StudioChannel by hand.

Decisions taken

  • No props change and no type break. StudioBridgeProps (nextjs) keeps site, editShortcut, page, nonce and studioOrigins, and all of them still do something, so nothing needed a @deprecated no-op. DecoDocumentProps / DecoPreviewProps are unchanged. StudioBridgeProps is now its own interface with the same fields, no longer Omit<core StudioBridgeProps, "trustedOrigins">.
  • @decocms/blocks/preview is untouched. Its StudioBridge (v7 handler) stays exported for anyone who renders it directly. The shortcut script lives in each binding (packages/nextjs/src/studioShortcut.ts and packages/tanstack/src/studioShortcut.ts), following the existing per-binding studio.ts copies. tests/studio-shortcut.test.ts asserts the two files are byte-identical.
  • The new shortcut already reads the field from e.composedPath()[0], the shadow-root fix of fix(blocks): keep the "." shortcut out of fields inside a shadow root #784, so the binding path gets it whether or not fix(blocks): keep the "." shortcut out of fields inside a shadow root #784 lands first. fix(blocks): keep the "." shortcut out of fields inside a shadow root #784 still matters for the core StudioBridge.
  • The shortcut uses its own global guard (__decoStudioShortcut).
  • Conventional type feat: (minor).

How to spike it

bun install && bun x vitest run tests/studio-shortcut.test.ts packages/nextjs packages/tanstack/src/react
cd examples/nextjs-binding-smoke && bun run build && bun run smoke   # "the bridge is v8 only" check

On a Next.js site: render <StudioBridge site="…" /> and curl a page. It should contain data-deco-studio-channel and data-shortcut, and no scrollToComponent and no window.self!==window.top. Load the page through Cypress (or the regex rewrite) and confirm there is no "Connection closed.". Pressing "." outside a frame goes to studio.decocms.com/choose-editor?.... In Studio's frame the editor still gets deco:ready and loads its modules.

Evidence

  • tests/studio-shortcut.test.ts (16 tests) applies Cypress's own regexes from packages/rewriter/lib/regex-rewriter.ts, in both modes (first-party, and modifyObstructiveThirdPartyCode's expanded ones), to the nextjs StudioBridge markup and to the tanstack shortcut + channel markup. The output is byte-identical. As a control, the same rewrite does change the core v7 STUDIO_BRIDGE_SCRIPT.
  • The same file runs the shortcut in jsdom for both bindings. It checks: Ctrl+. opens choose-editor with site/domain/path/pathTemplate/pageId; a plain "." and Ctrl+Shift+E are handled; the shortcut is inactive in a frame; editShortcut={false} turns it off; typing in an input, and in an input inside an open shadow root, is left alone; and a StudioPage marker wins at keypress time.
  • react.test.tsx checks that DecoDocument's Studio scripts are exactly the shortcut and the channel. components.test.tsx checks the same for StudioBridge, with no data-origins and no v7 handler.
  • Run locally on Linux: bun run typecheck, bun run lint, bun run lint:unused, skills:check, skills:readme:check, bun run build and the nextjs-binding-smoke build + smoke all pass. 233 tests pass across packages/nextjs, packages/tanstack/src/react, packages/blocks/src/v8/preview and tests/studio-shortcut.test.ts.
  • osklen spike. deco-sites/osklenbr-faststore feat/studio-bridge-bump was built with next build + next start on packed 8.15.0 tarballs of this branch. Home, /about-us and a PLP served markup that all 9 Cypress rewriter regexes leave alone. The Cypress-rewrite Playwright harness showed no page errors (no "Connection closed."). A fake Studio parent at studio.decocms.com got deco:ready + deco:loaded x2, and the wireframe selected and outlined each section and ran html-swap. "." unframed went to choose-editor?site=osklenbr-faststore-studio&…. "." in a field typed a ".". In a frame, "." did nothing.

Known limitations

  • A v7-era Studio that still sends scrollToComponent / editor::rerender to a v8 site gets no answer. v8 Studio doesn't send them: it loads its wireframe and html-swap modules through the channel.
  • Other sites that carry their own copy of the v7 bridge script are not touched by this PR.

🤖 Generated with Claude Code


Summary by cubic

Makes @decocms/nextjs's StudioBridge and @decocms/tanstack's DecoDocument/DecoPreview v8-only: v7's framed handler is gone, and the "." edit shortcut is now a script of its own that never compares the window with top or parent. Cypress's modifyObstructiveCode rewrites such comparisons in proxied HTML; when the RSC payload carries the script, the rewrite changes its length and every page fails with an uncaught "Connection closed.".

Details

  • Renders the Studio channel plus the "." shortcut; props, defaults, and outside-a-frame behavior are unchanged.
  • @decocms/blocks/preview is untouched and still exports its v7 StudioBridge.
  • A v7-era Studio that still sends scrollToComponent or editor::rerender gets no answer; v8 Studio reaches the page through the channel only.

Written for commit c5716dd. Summary will update on new commits.

View guided diff Turn on auto-fix

…-safe "." shortcut

@decocms/nextjs's StudioBridge and @decocms/tanstack's DecoDocument /
DecoPreview no longer render v7's framed handler (scrollToComponent,
editor::rerender), whose inline script compares window.self with
window.top. Cypress's modifyObstructiveCode rewrites that comparison in
the proxied HTML, including inside the RSC payload that carries the
script, so a Next.js page's Flight stream comes out one byte off and
every page throws "Connection closed.".

Both now render the v8 Studio channel (exact studioOrigins()) plus v7's
"." edit shortcut as a script of its own (studioShortcut.ts, the same
file in both bindings) that matches none of Cypress's rewrite regexes
and keeps the shadow-root field check. Props are unchanged.
@decocms/blocks/preview is untouched.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@tlgimenes
tlgimenes marked this pull request as ready for review November 10, 2026 15:19
@tlgimenes
tlgimenes requested a review from a team November 10, 2026 15:19
@tlgimenes
tlgimenes merged commit 4e1581d into main Nov 10, 2026
5 checks passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 8.15.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant