feat(site-editor): Localhost in the drafts list for Blocks v8 sites
Part of the v8 Studio stack (base: #7796). The last PR of the v8 Fast Preview + Localhost track, on top of #7874. PO decisions of 2026-10-11.
R5: a "Localhost" item in the drafts list for Blocks v8 sites, replacing Advanced → Local. Choosing it switches the editor to the developer's own deco serve. Schema and content come over the content protocol on localhost (not git or storage), and the preview is the app deco serve reports (describe.preview).
The tree of this branch is identical to feat/blocks-v8-hosted (b01649227, a normal merge of this branch, no force-push): git diff --stat origin/split/49-v8-localhost-item origin/feat/blocks-v8-hosted is empty, and both trees hash to b3469a0e2.
PO decision (2026-10-11): no site check. Localhost connects to whatever deco serve answers on the endpoint, even one running another site's folder. Studio doesn't encourage that, but no code blocks it (b7e4cf807 removed the check).
What
-
Draft selector (v8 only): a permanent Localhost row above "New draft" (
localhost-row.tsx). It is selected while adeco serveconnection is stored, and the trigger then reads "Localhost". Choosing any draft clears it, and so does "Stop using Localhost". -
Choosing it: the endpoint that answered last time is tried first, else
localhost:4545. If nothing is running yet, the editor's existing probe backoff (1 s up to 10 s) connects as soon asdeco servestarts, and reconnects on its own after a restart. -
Remembered per user and project: the key is
studio:deco-serve-connection:<userId>:<vmcpId>. The old per-project key only existed on this unreleased stack, so nothing is migrated. -
States, in plain words with one next step each:
State What the row shows Available "Edit the site running on your computer" Connecting Spinner and "Connecting…" Connected ✓ and "Connected to localhost:4545" Not running "Run deco servein your site's folder", the command with a copy button, and "We'll connect as soon as it starts"Chrome blocks access How to allow it Something answered that isn't usable Out-of-date deco serve, another protocol version, another program on the port, or an error, each with its next stepThe row's state comes from the editor's own content backend, so the two never disagree.
-
Preview pane: while there is no app to show (still connecting, or
deco servefound no app), it shows a guide instead of booting a sandbox or showing the hosted draft. -
Advanced: v8 has no Local tab; v7 keeps main's tab (#7874).
local-url-submit.tsand the v8 Local form are deleted.
Known limits
- There is no separate "Reconnecting" state. A connected site keeps working through a short restart (3 failed checks, about 7 s). After that the row shows the not-running guide, which already says it reconnects on its own.
Tests
localhost-row.test.tsx:localhostStatusmaps backend × permission to each state.- A render test for each state: available (choosing calls
onSelect), connecting, connected (Stop callsonTurnOff), not running (command plus Copy), needs permission, and a problem (out of date). - The per-user and per-project storage key.
tsc --noEmitforapps/web, oxlint, biome format and knip are clean. 193 test files undersections-editor,thread,main-panel-tabs,hooks,routes,i18n,libandchat/pillspass, one process per file, including the site-editor copy (jargon) test.- Not done: a full
BranchPickerrender test (it needs releases, members and branches mocked) and the manual run on the local spike (starting and stoppingdeco serve).
Stack
v8 Fast Preview track: previous: #7874 · next: #7876 (copy the site id; top of the stack)
Review fixes (94691da59)
- The old tunnel on v8:
useLocalPreviewUrlreturns no tunnel for a v8 project. A tunnel saved earlier no longer replaces the preview or the editor. v7 is unchanged. - Choosing Localhost: it connects to the endpoint this user last used for this project, else the default port. Nothing writes the global
/site-editorkey from here any more. - v7 draft selector: the v8-only hooks moved into
V8LocalhostRow, so the v7 picker runs none of them. - Preview tab: a gate clause that could never be false is gone.
- New tests:
useDecoServeConnection(per user and project, kept endpoint, v7 reads none, nothing written before the session loads), anduseLocalPreviewUrlon v7 vs v8.
Fixes after the PO's run (d5ff0e044..f46bfc4f3, plus 7fabb893d in #7872)
- The local preview recovers by itself (d2ae03678, f46bfc4f3). Before, if
deco serveanswered before the app (vite dev) was listening, or the app restarted, the frame stayed on Chrome's error page until a save or a reload. Now the preview checks the app from the browser with ano-corsHEAD and backoff (1 s up to 5 s while down, every 10 s while up). Until the app answers, it shows a "Waiting for your app" guide: "Your app atlocalhost:3000isn't answering yet…" and "The preview loads on its own as soon as your app answers." Then it loads the app. If the app goes away (2 failed checks in a row), the guide comes back, and the frame loads again on its own when the app returns./site-editoruses the same tab, so it gets this too. - Not-running replaces an old problem (f6f9855b3). The local probe used to keep its last answer through failed retries, so the row went on showing an old problem after
deco servestopped. Now the latest probe wins: one failed probe replaces an earlier problem straight away. A connected site gets a short grace period. A faileddescribepoll on a connected site probes again, so a stop is noticed without waiting for a save. - v8 copy and badge colours:
- On v8, the Preview server field no longer says "Required for Fast Preview" (7fabb893d, in #7872), because v8 has no Fast Preview switch. It says "Your live site's address. Studio shows your drafts on it before you publish." v7 keeps main's text.
- The header badge "Local server" is no longer red while it connects or waits for
deco serve(2496e1069). It stays neutral with a pulsing dot, and turns red only when something answered but can't be used: out of date, another program, or an error.
- No storage key without both ids (d5ff0e044).
useLocalStoragewrites its default on every read, so…:<userId>:keys holdingnullwere written before the project id was known. The Localhost connection now uses a scoped store with these rules:- It never writes on read.
- It doesn't read or write at all until both the user and the project are known.
- Turning Localhost off removes the key.
Tests added:
-
use-local-app-reachable.test.tsx: the backoff and grace rules; it waits, then reports the app up on its own; a new URL starts over. -
preview-tab.test.tsx: the guide while the app isn't answering or isn't checked yet; the frame once it answers; the guide when it goes away, then the frame when it's back; nothing is checked without Localhost. -
use-content-backend.test.ts(localServeBackend):- Any site's
deco serveconnects (no site check). - A connected site rides out 2 failures and becomes not-running at 3.
- An error from the server beats an earlier answer.
- Not-running, then connected again.
- Any site's
-
deco-serve-chip.test.ts: the badge is pending while connecting or waiting, and red only for real problems. -
use-deco-serve-connection.test.tsx: nothing written before the session loads or without a project id; a read stores nothing; a choice is stored only under:<userId>:<vmcpId>. -
preview-settings-rows.test.tsx(#7872): the v8 hint doesn't mention Fast Preview, and v7 keeps it. -
Every changed test file passes (one process per file).
tsc --noEmitforapps/web, oxlint and biome format are clean. -
The manual run on the spike was not done. The stack plus a test browser peaks at about 2.9 GB, over the 2 GB per-agent memory limit.
-
d7a3e8957: on a v8 site with no sandbox, the editor no longer spins on "Starting your preview" before the first edit. Nothing starts there, and there is no draft until the first edit, so it shows the published site with no spinner, then the draft as before.hostedDraftSession(v8 marker plus the GitHub backend) is the input; v7 and sandbox sessions are unchanged. Tests:preview-display.test.ts(hosted draft: no spinner before an edit, the draft after, v7 unchanged, Localhost/suspended still win) andcontent-backend.test.ts(hostedDraftSession). -
8517d30de:/site-editorand a project's Localhost Preview render ONE empty state,LocalServeEmptyState(local-serve-empty-state.tsx), with the same copy from one place (decoServe.waiting.*): "Waiting for your computer", the same intro, the same two steps, the Site editor guide / deco serve options / Troubleshooting links, and the moving dot./site-editorno longer has the Preview-tab/--previewparagraph, the older-site note, the Quickstart link, or the separate "Looking for deco serve…" screen. Problem states (out of date, another program, error) and the Chrome-blocked state look the same in both places. The "Look for deco serve on this computer" button stays, in the same spot, only while Chrome still needs a click before it asks for local-network permission. Choosing Localhost in a project already counts as that click. Tests:site-editor-guide.test.tsxchecks that both places render the same markup for "nothing answering" and for "another program on the port".preview-tab.test.tsxwas updated.origin/feat/blocks-v8-hostedwas updated by a normal merge (23410da81), and its tree matches split/49 (f81747205).
🤖 Generated with Claude Code