feat(nextjs,tanstack): v8-only StudioBridge and DecoDocument, Cypress-safe "." shortcut
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 coreSTUDIO_BRIDGE_SCRIPTbranch guarded bywindow.self!==window.top) is no longer rendered. Studio's v8 editor reaches the page through the Studio channel only. - Both render
StudioChannelfrom@decocms/blocks/previewwith the exactstudioOrigins()(option, elseDECO_STUDIO_ORIGIN, elsestudio.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 asvar w=window,p=w.parent; if(p!==w)return, which none of Cypress's frame-busting regexes match. It keeps the samedata-deco-studio-bridge/data-site/data-shortcut/data-path-template/data-page-idattributes and theStudioPagemarker 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) keepssite,editShortcut,page,nonceandstudioOrigins, and all of them still do something, so nothing needed a@deprecatedno-op.DecoDocumentProps/DecoPreviewPropsare unchanged.StudioBridgePropsis now its own interface with the same fields, no longerOmit<core StudioBridgeProps, "trustedOrigins">. @decocms/blocks/previewis untouched. ItsStudioBridge(v7 handler) stays exported for anyone who renders it directly. The shortcut script lives in each binding (packages/nextjs/src/studioShortcut.tsandpackages/tanstack/src/studioShortcut.ts), following the existing per-bindingstudio.tscopies.tests/studio-shortcut.test.tsasserts the two files are byte-identical.- The new shortcut already reads the field from
e.composedPath()[0], the shadow-root fix of #784, so the binding path gets it whether or not #784 lands first. #784 still matters for the coreStudioBridge. - 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 frompackages/rewriter/lib/regex-rewriter.ts, in both modes (first-party, andmodifyObstructiveThirdPartyCode's expanded ones), to the nextjsStudioBridgemarkup and to the tanstack shortcut + channel markup. The output is byte-identical. As a control, the same rewrite does change the core v7STUDIO_BRIDGE_SCRIPT.- The same file runs the shortcut in jsdom for both bindings. It checks: Ctrl+. opens
choose-editorwith 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 aStudioPagemarker wins at keypress time. react.test.tsxchecks thatDecoDocument's Studio scripts are exactly the shortcut and the channel.components.test.tsxchecks the same forStudioBridge, with nodata-originsand no v7 handler.- Run locally on Linux:
bun run typecheck,bun run lint,bun run lint:unused,skills:check,skills:readme:check,bun run buildand thenextjs-binding-smokebuild + smoke all pass. 233 tests pass acrosspackages/nextjs,packages/tanstack/src/react,packages/blocks/src/v8/previewandtests/studio-shortcut.test.ts. - osklen spike. deco-sites/osklenbr-faststore
feat/studio-bridge-bumpwas built withnext build+next starton packed 8.15.0 tarballs of this branch. Home,/about-usand 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 gotdeco:ready+deco:loadedx2, and the wireframe selected and outlined each section and ran html-swap. "." unframed went tochoose-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::rerenderto 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/previewis untouched and still exports its v7StudioBridge.- A v7-era Studio that still sends
scrollToComponentoreditor::rerendergets no answer; v8 Studio reaches the page through the channel only.