Skip to content

feat(cms): fast preview via preview server + branch-aware new chat - #5215

Closed
tlgimenes wants to merge 8 commits into
mainfrom
feat/cms-fast-preview
Closed

feat(cms): fast preview via preview server + branch-aware new chat#5215
tlgimenes wants to merge 8 commits into
mainfrom
feat/cms-fast-preview

Conversation

@tlgimenes

@tlgimenes tlgimenes commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Summary

CMS Fast Preview (render drafts against an always-on preview server, no dev-server wait), a reworked agent-settings CMS section, a New chat fix (always creates, on the current branch), and a set of header UX improvements (branch label, truncation priority, and small-screen icon-only actions).


1. Fast Preview

  • Renders the current working-tree draft against an always-on preview server via /live/previews (decofile pushed as a per-request, ALS-isolated override) — no waiting for the sandbox dev server.
  • buildInstantPagePreviewUrl() builds the cross-origin GET URL with a URL-size cap; past it, falls back to the published route.
  • Fast Preview never swaps to the sandbox surface — it previews only on the preview server for the whole lifecycle (even once the dev server is up / crashed). The "waking" pill tracks the sandbox itself (FS/handle → blocks.gen.json readable), not the dev server.
  • Fast Preview off: prior behavior kept — published site while booting, then swap to the sandbox once its dev server runs.
  • URL-bar domain follows what's in the iframe (buildPreviewLabel); open-in-new-tab is hidden unless a real sandbox iframe is shown.

2. Settings — CMS section

  • New CMS section: Preview (preview-server URL + Fast Preview switch, gated on the URL) and Publishing (moved in; extracted PublishPolicyField). Business-friendly copy, en + pt-br.

3. New chat — always creates, on the current branch

  • The New chat button now always creates a fresh thread (removed the empty-chat reuse short-circuit from both entry points), on the agent + branch currently being viewed (was hardcoded staging). createNewTask threads the branch through (panel-actions + keyboard paths). "Open agent X" flows still reuse an empty chat, branch-agnostically. No backend change — the wire schema already accepts branch.

4. Header / small-screen UX

  • Branch button: always shows the current branch (truncated); it now sits in its own shrinkable slot so it truncates before the address bar (which is flex-1, shrinks last), while the publish/⋯ actions stay shrink-0 and never clip.
  • Below lg (< 1024px) the header collapses to icon-only + tooltip: the branch button, the primary action button (icon keyed off button.actionGitPullRequest / RefreshCw01 / AlertTriangle / CheckCircle / MessageCircle01), the side Publish button (Upload01), and the Develop/Live toggle (Code01 / Globe01). Loading pills collapse to spinner-only; plain status pills (Up to date / Published / Awaiting review) keep their text. Desktop is unchanged.

Testing

  • Unit: preview-display.test.ts (13), preview-label.test.ts (7), section-preview-url.test.ts, reusable-new-chat.test.ts (8).
  • bun run check (web + shared) and bun run lint pass; formatted with Biome. Layout/copy-only changes have no unit tier.

Notes / follow-ups

  • In Fast Preview mode the sandbox iframe is never shown, so same-origin in-iframe editor overlays (visual editor, click-to-select) don't activate on the cross-origin preview server; Blocks-panel editing (via blocks.gen.json) still works.
  • Instant render is static (renderToString) — no hydration/JS interactivity/in-page navigation. Lifting the URL-size cap (POST proxy / draft cookie) is a natural follow-up for large sites.
  • merge-split ("Publish to <base>") renders via MergeSplitButton and is still text below lg (not yet iconified).
  • Possible follow-up (discussed, not in this PR): when Fast Preview is on, defer bun install + the dev script at boot (the decofile is a dep-free FS merge) and boot them lazily on first need — behind a default-off flag.

🤖 Generated with Claude Code

Deco Studio and others added 8 commits July 24, 2026 14:55
Preview / Fast Preview
- Add Fast Preview: render the current draft against an always-on preview
  server via /live/previews (pushing the working-tree decofile as a
  per-request override) instead of waiting for the sandbox dev server.
- buildInstantPagePreviewUrl() builds the cross-origin GET URL with a
  URL-size cap (falls back to the published route past it).
- Fast Preview NEVER swaps to the sandbox surface — it previews only on the
  preview server; the waking pill tracks the sandbox itself (FS/handle), not
  the dev server. Fast Preview off keeps the published-fallback-then-swap
  behavior.
- URL-bar domain follows the iframe (buildPreviewLabel); open-in-new-tab is
  hidden unless a real sandbox iframe is shown.

Settings
- New CMS section: Preview (preview server URL + Fast Preview switch) +
  Publishing (moved in, extracted PublishPolicyField). Business-friendly copy,
  en + pt-br.

New chat: inherit the current branch
- The New chat button now creates the thread on the branch/sandbox being
  viewed (not the hardcoded staging default). findReusableNewChat gains an
  optional, branch-aware match; createNewTask threads the branch through.
  "Open agent X" flows stay branch-agnostic.

Branch button
- Always show the current branch label (truncated) on the picker and locked
  chip, in the chat composer too.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The New chat button reused an existing empty "New chat" (navigating to it)
instead of creating one. Remove the reuse short-circuit from both entry
points (header NewChatCrumb + sidebar handleNewThread) so every click creates
a fresh thread on the current agent + branch.

- Drop the now-dead isThreadEmpty helper + its MCP client, and the reuse
  branch in NewChatCrumb (with its unused session/setTaskId).
- findReusableNewChat no longer takes a branch (no remaining caller passes
  one); revert its branch-aware tests. It stays branch-agnostic for the
  "open agent X" flows that still reuse (navigate-to-agent, org-home, picker).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The branch selector lived inside the header's shrink-0 "never clip" right
cluster, so it held full width while the centered address bar (page selector)
truncated. Give the branch its own min-w-0 shrinkable slot so it yields first;
keep the real actions (Edit / Submit / Publish / ⋯) shrink-0 and as the only
thing rightRef measures, so the tab-count budget is unchanged. The address bar
is flex-1 (basis 0) and now shrinks last.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Below `lg` (< 1024px) hide the branch label on both the picker and the locked
chip, leaving an icon-only control; the branch name stays available via the
already-wired tooltip. Mirrors how the view tabs collapse their labels
(`max-md:hidden`). Only the branch button qualifies — the other header actions
(Publish / Submit, Develop / Live) are text-only with no icon fallback, and
Open-in-board is already icon-only.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Give the text-only header actions leading icons so they collapse to icon-only
below `lg` (< 1024px), with the label kept in the tooltip; desktop is unchanged
(text label at `lg`+):

- Primary action button: icon keyed off `button.action` (create-pr →
  GitPullRequest, rebase/reopen → RefreshCw01, fix-checks → AlertTriangle,
  mark-ready → CheckCircle, resolve-comments → MessageCircle01). Loading pills
  collapse to spinner-only; plain status pills (Up to date / Published /
  Awaiting review) keep their text.
- Side Publish button: Upload01 icon below lg.
- Develop / Live toggle: Code01 / Globe01 icons, labels hidden below lg (title
  tooltip + aria-label retained).

merge-split ("Publish to <base>") renders via MergeSplitButton and is left as
text for now.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
In Fast Preview the draft is embedded in the iframe URL (`instantDraftUrl`),
so a Blocks edit — which optimistically updates the decofile into a new URL —
should re-navigate the frame. It didn't: the shared reload path can't
`.reload()` a cross-origin frame, and the same-URL src assignment is a no-op.

Drive it explicitly: when the instant URL changes (i.e. the decofile changed),
point the frame at the fresh URL. Uses the already-correct optimistic decofile
(revert-safe — no refetch of a possibly-committed stale blocks.gen.json), and is
naturally debounced by the existing write debounce (useDebouncedSaveBlock).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
The GET-with-decofile-in-URL approach fell over on real storefronts: the
decofile blows past the URL/edge request-line limit, so `instantDraftUrl`
came back null and the preview silently showed the published page.

Render server-side through the sandbox daemon instead:
- New public daemon route `GET /_deco/fast-preview` (packages/sandbox/daemon):
  merges `.deco/blocks/*` (dep-free, no dev server), POSTs it to the site's
  production `/live/previews/<component>` (which already accepts a JSON
  `__decofile` body — no production change, no CORS, no URL cap), and returns
  the HTML with an injected `<base href>` so assets resolve against production.
  All async (fs + fetch) per the daemon's single-thread rule.
- Thread `productionUrl` (from `metadata.productionUrl`) to the daemon config:
  Workload → SANDBOX_START → buildConfigPayload / link-daemon → daemon
  `Application.productionUrl`.
- Web: the Fast Preview frame now loads the daemon route (`previewUrl` origin)
  via `buildFastPreviewDaemonUrl` — the decofile is NOT in the URL. Falls back
  to the published page while the sandbox provisions. Removes the old
  `buildInstantPagePreviewUrl` GET builder + its test.

Save → refresh: on a `.deco/*` write the daemon emits `file-changed`; the
events gate is relaxed so Fast Preview reacts even with no dev server, and the
handler bumps a nonce (new daemon URL → the frame re-navigates → the daemon
re-reads the just-saved blocks). No decofile refetch in that mode (the daemon
reads disk directly; refetching could revert an optimistic edit).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Fast Preview was falling back to `new URL(path, productionUrl)` (the published
page, e.g. fila.vtex.app) whenever the daemon render URL wasn't ready — a page
not yet matched, or the sandbox still provisioning. That fallback shouldn't
exist: Fast Preview is the daemon draft render or nothing.

resolvePreviewDisplay now handles Fast Preview up-front: once the sandbox handle
exists it renders on the daemon origin (`previewUrl`), and until then it shows
the booting overlay — never `productionUrl`. preview.tsx drops the published
fallback in that mode and shows the overlay while the daemon URL is null.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@tlgimenes

Copy link
Copy Markdown
Contributor Author

Split into three focused PRs (same commits, cleanly separated by concern — the file sets are disjoint):

Closing this in favor of those.

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