Skip to content

Quiet the machine status, and make a lost connection calm - #792

Merged
lavaman131 merged 4 commits into
mainfrom
quiet-planner-and-offline
Nov 6, 2026
Merged

lavaman131 merged 4 commits into
mainfrom
quiet-planner-and-offline

Conversation

@lavaman131

Copy link
Copy Markdown
Collaborator

Summary

A healthy Chopin says nothing. How the machine behind a document is doing now lives on the document header's existing machine button, in plain words that name no Chopin concept; a failure someone can act on also warns on the composer's top edge; and a lost connection is one calm notice at the top of the page. Nothing sits under the composer and nothing about the connection sits in it, so the composer is the same size in every state.

The words are the user's, and plain: no "Planner", "attach", "detach", "held", "Decisions" or "@chopin" in any status, button name, strip, notice, Build tooltip or refusal, or screen reader announcement. A unit test walks every state, glyph and place that says them, Build's reasons and refusals included, and fails on any of those words; its pattern is itself tested against each banned term, "detached" among them.

State by state, where it is told, and in what words:

State Machine button (header, or phone top bar) Composer's top edge for a person with the button Composer's top edge for a person with no button Screen reader (one live region)
Healthy Its own connected dot, nothing added nothing nothing nothing
Starting The dot becomes a thin petrol spinner. Name: Runs on Build box. Connecting to Build box nothing Connecting to Build box (@mona's machine when the machine is not theirs) the same sentence, once
Can't connect Warning dot. Menu opens onto the bold title Can't connect to Build box, the reason exactly as the machine gave it, Tried 3 times, last at 6:29 PM. Still trying. and an accent How to fix Soft warning surface: Can't connect to Build box. How to fix (reason and tries for a screen reader) The same, with the reason and the tries drawn, each on its own line Can't connect to Build box., once
Disconnected Muted grey dot. Build box is offline. Your work is kept. nothing the same sentence once
Stepped away Muted grey dot. @mona's machine stepped away. Your work is kept until 10:31 AM. nothing the same sentence once
No machine No dot at all: the bare icon of a button with no machine, never the machine's green. Name and menu say No machine is connected. nothing the same sentence once

A lost connection is the page, not the machine: a warning InlineNotice at the top of the document's content, after a 1.5 s grace period, You're offline. Your changes are kept and will sync when you reconnect. Retry only after 4 failed reconnect attempts. On compact windows and phones it is one element above Chat and the document.

Where a state is told: one decision

When the person has the machine button, the button and its menu carry every state, and the composer's top edge is used only for a machine that cannot be connected to, the one state a person can act on. When the person does not have the button (no machine paired or shared, a title bar that does not show one, a phone whose top bar an open child has taken), the composer's top edge is the fallback carrier for every state, always and not only while a message is addressed to Chopin, and for a machine that cannot be connected to it also draws the reason inline. One pure function, plannerPlacement(status, hasButton), makes that decision for the button, its menu and the strip, so they cannot disagree; room-workspace.tsx computes hasButton from the same conditions that draw the button. A lost connection hides the button without moving where the state is told; the offline notice speaks for the connection meanwhile.

The machine button with no machine connected: no dot

When a document's machine says No machine is connected. the machine button now draws no status dot: the bare icon in tertiary ink, exactly as the button of a person who has no machine at all. It used to keep the machine's own dot, so the connected green sat beside those words. I chose no dot over the muted dot from the status-graphics rules in apps/web/DESIGN.md: success is reserved for a connected machine, and the muted tertiary dot already means a machine that is there but unreachable (offline, stepped away), which is not true of a document with nothing connected to it. Absence is drawn as absence, the existing treatment for a button with no machine, and it is the quieter of the two. Every state that is not healthy now draws the Planner's own dot or none, and that dot carries no data-state at all, so the machine's green cannot come back through the cascade. plannerGlyph maps none to "none" and MachineGlyph draws nothing for it; the design audit plate takes its dots from the same function.

Build says it in machine words too

The Build plan tooltip and the refusal after pressing it used to say Reconnect the Planner to build and Connect a Planner to this document to build. They now say Reconnect the machine to build and Connect a machine to this document to build (with a stop when it is the refusal shown after a press), the same machine-first words as Connect a machine to get a reply. I wrote "the machine", not "your machine", because the document can be running on a machine a teammate shares, which the person pressing Build cannot reconnect. The connect copy is one constant, CONNECT_MACHINE_TO_BUILD, shared by the tooltip, the refusal and the key Build plan uses to offer how to connect a machine, so copy and key cannot drift apart. status-copy.test.tsx walks every reason Build plan can be inert while drafting and every refusal draftRefusalCopy can give.

What changed

  • Wire (apps/web/src/wire.ts): reports an outage (onOutage) when a connection is lost, each failed attempt in it, and when it opens again; automatic and manual attempts both count and only an open socket resets the count. New in this round: the status becomes reconnecting the moment the socket closes, not after the HTTP probe that asks why; the probe has a 4 s timeout so a probe that never answers (a network that drops packets) cannot stall retries; the online event wakes a wire that is waiting on the probe as well as on the backoff timer. The jittered exponential backoff is unchanged.
  • isOffline (packages/editor/src/connection-notice.ts): follows the outage the connection reports, not only its status, so a hung probe still shows the notice after the grace period. A slow first connection, a refusal and a deleted document are still not an outage.
  • Offline notice (apps/web/src/offline-notice.tsx): the decision (grace, Retry, the one polite word) is held in useOfflineNotice in RoomWorkspace, above wherever the notice is drawn; OfflineNotice only eases open and shut from what it is told, so a notice mounted again somewhere else is simply there. Workspace draws it once above Chat and the document where they take turns (pageNoticePlace), so switching tabs during an outage neither restarts the grace period, nor remounts the alert, nor announces it again.
  • Phone: the notice floats under the top bar, as before, but its height, as it eases, is published as --phone-notice-height and added to every pane's top inset (--phone-pane-top), so the first of the Document's or Chat's content starts below it instead of under it. It sits above the panes (z-index 20) so Retry can be pressed.
  • Planner status (apps/web/src/chat/planner-status.ts, pure): one model for the button's name and tooltip, its menu, the composer's strip and the live region, with the plain words above. The failure reason is kept exactly as the machine reported it: not trimmed and no stop added or removed (a path that ends in a slash stays a path); layout and separate lines carry the separation instead. plannerPlacement is the placement decision; plannerAnnouncement is the once-per-entered-state rule.
  • Live region (chat/planner-announcement.tsx): one visually hidden polite role="status" in RoomWorkspace, independent of the button or the strip, that says each problem state's sentence once as it is entered, nothing while healthy, nothing as a state clears, and nothing as the attempt count rises. The strips carry no live role of their own (silent), so nothing is announced twice.
  • Composer (composer.tsx): the dead attachment slot under the composer is gone. A strip may show its detail lines (the reason, then the tries) or hand them to a screen reader only.
  • Refusals shown in Chat (apps/server/src/harness/remote/attachments.ts, docs/agent-connection.md): the @chopin and comment refusals are machine-first and plain: Connect a machine to get a reply. Choose one with the machine button, or run chopin connect on a machine that can reply., @mona's machine is offline, so it can't reply now. Your work is kept and picks up when it's back., Still connecting to @mona's machine. Try again in a moment., Can't connect to @mona's machine: (3 attempts in a row). It keeps trying; try again once that is fixed. The error codes (planner-not-attached) are unchanged, and Build's matching of these messages (draftRefusalCopy) is unchanged; only the copy it returns is plain now (below).
  • Literal text: ASCII apostrophes everywhere, exactly as the user wrote them.
  • Design audit plates, docs and DESIGN.md: Conversation shows the strip for each state with and without the button, the machine button's dot and menu note, and Loading and status shows the offline notice with and without Retry.
  • Design-contract review hashes renewed for the four owner files whose data flow was re-read (no new dynamic classes).
  • Review round: the machine button with no machine connected, Build's reasons and refusals in machine words, and the leak check now catching "detach" (below, and in the two sections above).

Motion

The offline notice and the composer's strip are the only things that move: the grid track eases from 0 to its height in 250 ms and back in 200 ms while the content fades and settles, so what is below is pushed a frame at a time. The e2e traces the document's and the composer's top edge on every animation frame and asserts they pass through intermediate values; with prefers-reduced-motion nothing is passed through. Clearing is quiet: no check mark, no "Back online", no toast.

Accessibility

  • The offline notice is an alert once, as it appears; switching between Chat and the document does not insert it again (the e2e marks the element and watches for added alerts). A polite status region stays mounted and says "Reconnected" once when the notice has gone, because the document was read-only and is not now; it is hidden from sight, and a blip that never showed the notice says nothing.
  • The machine's state is announced by one polite region, once per state entered. The strips and the button are not live regions, so the e2e asserts exactly one live element carries each sentence, and that the recorded announcements have no repeats as the attempt count rises.
  • The machine button's accessible name and tooltip carry the state; for a machine that cannot be connected to its description is the reason and tries, each as its own block. The menu's note is a group named Connection, and How to fix is a menu item, so the arrow keys reach it; on a phone it is in the sheet.

Research, and what was copied

Placement, scope and copy follow Primer's patterns; colour and components are Chopin's own (apps/web/DESIGN.md).

Primer

Apple Human Interface Guidelines, Progress indicators, https://developer.apple.com/design/human-interface-guidelines/progress-indicators: indicators are transient and specific, in a consistent place (the machine button).

Notion, Linear and Figma (research, 2026-11-05; the vendors document very little of this): changes are kept locally and sync on their own when the connection returns (Linear docs, https://linear.app/docs/get-the-app; Notion help, https://www.notion.com/help/guides/working-offline-in-notion-everything-you-need-to-know; Figma, https://help.figma.com/hc/en-us/articles/360040328553-What-can-I-do-offline-in-Figma). We kept Chopin's existing 1.5 s grace period rather than copy any vendor's number, and Retry, which appears only after repeated failures, is ours.

Honest residuals and decisions worth a second look

  • "Your changes are kept" and lost edits. The offline copy is the user's literal text and stays. When a document is rebuilt on the server while someone is offline, their offline edits are dropped on reconnect and said to be (Edits not saved with Dismiss, in the header's status, not the notice, which is gone by then). The e2e now asserts the notice is gone, that this alert is the only alert, that it is in view and shows its message, but it is a small chip rather than a banner, so a person who read the offline notice may feel the two contradict. A louder lost-edits treatment is a separate decision.
  • First announcement may name a login. The live region is keyed on the state, so it is not repeated when the machine's name arrives. If the machine list has not loaded when the state first arrives, the first sentence names the owner's login (Connecting to @mona's machine) rather than the machine; the button reads the name once it is known.
  • A viewer with no composer (read-only, archived) cannot be shown the strip; they hear the live region only. They cannot send, so the state does not affect them.
  • Untouched, and still saying Planner or Decisions. The plain-words rule is applied to every status, name, strip, notice, tooltip and refusal this change writes or edits, Build's included. These user-facing strings sit near it but were not edited, and still name Chopin's own terms: the Chat transcript's system lines from the server (The Planner disconnected. Its workflows and open Decisions are kept ...) and the agent connection's refusals to an attaching client (forbiddenMessage, ... its Planner was released, A document has one Planner at a time); the run cards' notes and Pause labels (It carries on when the Planner reconnects, Pause when the Planner reconnects); the composer's picker hints (Sends to the Planner, which will reply, Room only. Add @chopin to ask the Planner) and the @chopin mention itself; the busy labels Planner is working (document status), Planner working (Chat tab) and Document, Planner writing; the Decisions tab and Showing Decisions: N to answer; The Planner job could not be retried; the Machines page's Planner and runs Planners labels; and the setup popover that Build plan opens when no machine is connected (Connect Chopin, Planner command, Open your Planner and attach this document), a terminal setup guide for a custom backend that this change does not touch. Two design audit captions (the Conversation plate's description and the Workflow runs plate's) still say Planner too; the audit labels this change wrote do not. Whether any of these should follow is a decision for a deliberate change, and several are visible in the same Chat as the new strip.
  • Known, deferred: two offline notices beside each other. When a child document is open beside its parent, each draws its own offline notice (on a phone the child's is drawn in flow in its own pane while the parent's floats under the top bar, and only the parent's reserves inset). That is the existing per-room status duplication, not introduced here, and it is not addressed in this change.
  • "Chopin unavailable" while offline. The composer's existing info strip, shown when the composer has no agent, still appears above it during an outage (see the offline screenshot), so a person offline reads the notice and that strip. It was there before and is not edited here; it should probably be hidden while the offline notice is shown.
  • The Reload fallback after three failed Retry presses stays gone: Retry stays offered, and the browser's reload is always there.

Verification

Run on the final working tree, with the client built from it. This round's checks first, then what the earlier commits ran that was not repeated.

This round:

  • bun test apps/web packages scripts e2e/focus-color.test.ts: 2639 passed, 3 skipped, 0 failed (five more than before: the rewritten no-machine test, the iterate-every-unhealthy-state test, the leak-check self-test, Build's reasons and refusals, and Build plan's tooltip with its connect key). bun test scripts again after renewing the design-contract hash: 137 passed, 1 skipped, 0 failed. The new tests were each seen red first: the button tests drew data-state="connected" beside No machine is connected., the Build tests still returned the Planner copy, and the leak check was shown to miss "detached" with the old pattern; the audit plate test and the leak check also fail when the fix is taken back out.
  • bun run types: passed (14 packages and the e2e project).
  • bun run fix then bun run ci: passed. dprint, oxlint (71 warnings and 0 errors, as before), tokens, design contract, design record and Impeccable (no new findings). The contract asked for the reviewed hash of machine-button.tsx to be renewed; its one dynamic class (presence.className, finite state classes from the shared presence hook) is untouched, so only the hash changed.
  • bun run design:behavior: 22 passed, including the build blockers plate whose Connect Chopin trigger is still found by the new copy.
  • Browser, on the built client (E2E_SKIP_BUILD=1 bun run e2e planner-attachment planner-picker machine-choice machines connection implementation-launcher): 93 passed and 10 failed. All 10 failures (8 in implementation-launcher, 2 in machines) end in fatal: either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured, this machine's global commit-signing config. GIT_CONFIG_GLOBAL=/dev/null with implementation-launcher and machines only: 32 passed, 0 failed, so they are environmental. The 93 include the new real-app test: with a machine that cannot reply, the button is named Runs on desk. No machine is connected., has no .machine-glyph-dot and no [data-state] on desktop and on a phone, its menu still says Connected for the machine's own row, and the page's live region says it once.
  • Screenshots opened and reviewed, below.
  • Not run: design:browser (Linux visual baselines are not regenerated), the full bun test (carried below from the earlier commits), test:postgres, the Docker image build, and the whole e2e suite (six specs here); GitHub's checks run on this pull request.

Carried from the earlier commits and not repeated: the full bun test gave 7092 passed, 22 skipped, 101 failed, 1 error, every one in apps/connector or apps/server/src/experiments/routes.test.ts with the same signing error, and 332 passed with GIT_CONFIG_GLOBAL=/dev/null; the whole 15-spec browser run gave 184 passed before this round.

Screenshots

Every machine state on the button: only Connected keeps the green dot, No machine is connected shows the bare icon

The offline notice at the top of the document

On a phone the notice sits under the top bar and the document starts below it

Can't connect: the machine button's menu with the reason as reported, the tries and How to fix, and the warning on the composer's top edge

A machine that cannot reply: the machine button is the bare icon with no dot

The composer's top edge in each state, with and without the machine button

Edits not saved after an outage: the one alert left once the notice is gone

The full set was captured with CHOPIN_SHOTS=/tmp/chopin-shots-quiet-offline (and a script against the design audit) on desktop and on a 390 px touch phone, opened and reviewed. Not committed. The design audit plates were regenerated this round, and the real-app shots were retaken by the browser run above.

Offline: offline-desktop.png, offline-retry-desktop.png, offline-phone.png, offline-retry-phone.png, offline-phone-document.png (real Document content below the notice), offline-phone-chat.png (real Chat content below the notice), lost-edits-after-offline-desktop.png.
With the machine button: planner-starting-desktop.png, planner-starting-phone.png, planner-failing-desktop.png, planner-failing-menu-desktop.png, planner-failing-phone.png, planner-failing-sheet-phone.png, planner-attached-desktop.png, planner-attached-phone.png, and, new, planner-none-button-desktop.png and planner-none-button-phone.png (a machine that cannot reply: the button is the bare icon with no dot).
With no machine button, the composer's top edge carrying the state: planner-starting-teammate-desktop.png, planner-starting-teammate-phone.png, planner-failing-teammate-desktop.png, planner-failing-teammate-phone.png, link-drop-disconnected-desktop.png, link-drop-disconnected-phone.png, planner-held-desktop.png, planner-held-phone.png, planner-none-desktop.png, planner-none-phone.png.
Design audit: audit-presence-desktop.png and audit-presence-phone.png (only the healthy cell has the machine's green; No machine connected: no dot shows the bare icon beside No machine is connected.), audit-composer-states-desktop.png, audit-composer-states-phone.png, audit-build-blockers-desktop.png, audit-build-blockers-phone.png (the Connect Chopin trigger), audit-loading-status-desktop.png, audit-loading-status-phone.png.

Deferred

  • Whether the transcript's system lines, run cards, composer hints, busy labels and the other untouched strings listed above should drop Planner and Decisions too.
  • Parent and child documents each drawing their own offline notice when a child is open beside its parent (pre-existing).
  • A louder treatment for edits that were not saved after an outage.
  • Refresh the Linux design:browser baselines for the Conversation and Loading plates.

Assistant-model: Claude Sonnet 5.5
Assistant-workflow: goal (run 19ea4b06-26bd-4515-94f7-1201b0f8463c)
Assistant-verification: bun test apps/web packages scripts e2e/focus-color.test.ts passed: 2639 pass, 3 skip, 0 fail, five more than before; bun test scripts passed again after the design-contract hash was renewed (137 pass, 1 skip)
Assistant-verification: the new and rewritten cases were each seen red first: the button cases drew data-state="connected" for no machine and for every unhealthy state, the Build cases returned the Planner copy, and the old pattern let "detached" through; taking plannerGlyph's none back to undefined fails the button and audit plate cases, and swapping a Build refusal back to Planner words fails the Build case
Assistant-verification: bun run types passed: every package and the e2e project type-check
Assistant-verification: bun run fix then bun run ci passed: dprint, oxlint (71 warnings, 0 errors, as before), tokens, design contract (the reviewed hash of machine-button.tsx renewed, its one dynamic class unchanged), design record and Impeccable show no new findings
Assistant-verification: bun run design:behavior passed: 22 keyboard, focus, reduced-motion, reflow and build-blocker checks at two widths
Assistant-verification: E2E_SKIP_BUILD=1 bun run e2e planner-attachment planner-picker machine-choice machines connection implementation-launcher on a client built from this tree gave 93 passed and 10 failed, all 10 (8 in implementation-launcher, 2 in machines) with "either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured", this machine's global commit signing; the 93 include the new no-dot button test on desktop and phone
Assistant-verification: GIT_CONFIG_GLOBAL=/dev/null bun run e2e implementation-launcher machines passed: 32 passed, 0 failed, so the 10 are environmental
Assistant-verification: screenshot review passed: audit-presence desktop and phone (only the healthy cell keeps the machine's green, and No machine connected shows the bare icon with no dot, its computed colours read back from the page), audit-composer-states and audit-build-blockers desktop, and the real-app planner-none-button desktop and phone, planner-starting and planner-failing-menu, were opened and checked against DESIGN.md
Assistant-verification: not run: design:browser (Linux visual baselines are not regenerated), the full bun test, bun run test:postgres, the Docker image build, the whole e2e suite (six specs only), and GitHub's checks on a pushed head
User-preference: Status UI stays quiet when things are healthy and calm when they aren't, like Notion
Co-authored-by: Alex Lavaee lavaman131@github.com
Assistant-duration: 20m 54s

…when it can't connect, and say a lost connection once at the top of the document

A healthy Chopin now says nothing. The "Planner: <login> attached" line under the
composer is gone, and so is the connection label inside it, so the composer is the
same size in every state. What is left is told where a person already looks.

The Planner's state is on the document header's machine button (and in the phone
top bar): its dot becomes a thin petrol spinner while the machine is being
connected to, a warning where it cannot be, and a muted grey while the Planner is
away. The button's name and the top of its menu say it in words about the machine,
such as "Connecting to Build box" or "Can’t connect to Build box" with the reason in
full, what has been tried and a "How to fix" link, now documented in
docs/chopin-cli.md. A machine that cannot be connected to also warns on the
composer's existing attached top strip, which gains a warning tone and eases open
and shut with the shared collapse motion; the other states, and a Planner that is
not attached, are told there only while a message is addressed to Chopin, so a
person with no machine button still hears of them when it matters. The status model
is one pure module, so the button, its menu and the strip cannot disagree.

A lost connection is one InlineNotice in the warning roles at the top of the
document's content (the top of Chat where Chat is all that shows, and just under the
floating top bar on a phone): "You’re offline. Your changes are kept and will sync
when you reconnect." It waits out the existing 1.5 second grace period so a blip
shows nothing, and Retry joins it only after four failed reconnect attempts. Wire
now reports an outage and counts each failed attempt, automatic or a person's,
reusing its existing jittered backoff; the timing and decisions are pure and unit
tested. The header status, the Reconnect to Reload fallback and the composer's label
and button are removed, and nothing confirms a state that clears. A screen reader
hears the notice once as it appears and "Reconnected" once when it is gone.

Placement and copy follow Primer's degraded-experience, loading and notification
messaging patterns and Apple's progress-indicator guidance, in Chopin's own
tokens and components; the research and the vendors' documented offline behaviour
are cited in the pull request. The e2e traces the notice and the strip frame by
frame, so they are shown to ease and not jump, and to settle at once under reduced
motion. The design audit plates, DESIGN.md and the docs describe the new places.

Assistant-model: Claude Sonnet 5.5
Assistant-workflow: goal (run 19ea4b06-26bd-4515-94f7-1201b0f8463c)
Assistant-verification: bun test (apps/web, packages, scripts) passed: 2617 tests across 322 files, including the new unit tests for outage counting, timing, planner status and the strips
Assistant-verification: bun test (full, as run here) failed: connector and routes tests need an unsigned git config (332 pass with GIT_CONFIG_GLOBAL=/dev/null) and the memory adapter flaked under load and passes alone
Assistant-verification: bun run types passed: every package and the e2e project type-check
Assistant-verification: bun run ci passed: dprint, oxlint, tokens, design contract, design record and Impeccable show no new findings
Assistant-verification: bun run design:behavior passed: 22 keyboard, focus, reduced-motion and reflow checks at two widths
Assistant-verification: bun run e2e (14 specs) passed: 137 passed on the built client, the offline notice, Retry, reduced motion, the machine button states and the composer strips in Chromium
Assistant-verification: bun run e2e machines.e2e.ts passed: 2 tests that failed only on commit signing pass with GIT_CONFIG_GLOBAL=/dev/null
Assistant-verification: screenshot review passed: desktop and phone shots of each state were opened and checked against DESIGN.md
Assistant-verification: design:browser unavailable: Linux visual baselines were not regenerated
User-preference: Status UI stays quiet when things are healthy and calm when they aren't, like Notion
Co-authored-by: Alex Lavaee <lavaman131@github.com>
Assistant-duration: 72m 28s
…and keep a lost connection one calm notice that never covers the page

The status words are now plain. A machine that is starting, cannot be connected to,
is offline, has stepped away or is missing is told as "Connecting to Build box",
"Can't connect to Build box", "Build box is offline. Your work is kept.", "Build box
stepped away. Your work is kept until 10:31 AM." and "No machine is connected.",
with ASCII apostrophes and none of Chopin's own terms: no Planner, attach, held or
Decisions, and no mention of Chopin. A unit test walks every state, glyph and place
that says them and fails on any of those words. The refusals shown in Chat for an
@chopin message or a comment are machine-first and plain too ("Connect a machine to
get a reply", "Can't connect to @mona's machine: <reason>"), with their error codes
unchanged, and docs/agent-connection.md says the same.

One pure function now decides where a state is told, so the machine button, its menu
and the composer's strip cannot disagree. A person with the button reads every state
there and the composer warns only for a machine that cannot be connected to. A person
without it (no machine, a title bar that does not show one, a phone whose top bar an
open child has taken) reads every state on the composer's top edge, always, and for a
machine that cannot be connected to the reason is drawn beneath it. The reason is kept
exactly as the machine reported it, with no trimming and no stop added or taken away.
One polite live region owned by the room announces each problem state once as it is
entered and nothing as it clears; the strips carry no live role of their own, so
nothing is said twice.

A lost connection is shown even while the request that asks why it was lost never
answers: the outage the wire reports, not its status, drives the notice, the status
turns to reconnecting as soon as the socket closes, the probe gives up after four
seconds, and the network coming back wakes a wire that is waiting on it. The notice's
timing and announcement are decided above wherever it is drawn, and Workspace draws it
once above Chat and the document, so switching between them during an outage neither
restarts its grace period nor announces it again. On a phone it floats under the top
bar and its height is added to every pane's top inset, so it never covers the first
heading or the first message, and it sits above the panes so Retry can be pressed.

The composer's unused slot under the field is removed. The design audit plates, the
docs and DESIGN.md show the strip with and without the button. The e2e checks the
notice against real Document and Chat content on a phone, in a compact window, through
tab switches, with a hung probe, and that the lost-edits alert is the one left after a
rebuild. Residuals, honestly: the offline copy says changes are kept while edits made
across a server rebuild are dropped and said to be in a small header alert, and
transcript lines, run cards and Build tooltips written before this change still say
Planner and Decisions.

Assistant-model: Claude Sonnet 5.5
Assistant-workflow: goal (run 19ea4b06-26bd-4515-94f7-1201b0f8463c)
Assistant-verification: bun test (apps/web, packages, scripts) passed: 2634 tests, including the new tests for placement, once-only announcements, verbatim reasons, plain words, the hung-probe wire and the single page notice
Assistant-verification: bun test (full, as run here) failed: 101 connector and routes tests need an unsigned git config (332 pass with GIT_CONFIG_GLOBAL=/dev/null), and no other test failed
Assistant-verification: bun run types passed: every package and the e2e project type-check
Assistant-verification: bun run ci passed: dprint, oxlint (no new warning), tokens, design contract, design record and Impeccable show no new findings
Assistant-verification: bun run design:behavior passed: 22 keyboard, focus, reduced-motion and reflow checks at two widths
Assistant-verification: bun run e2e (15 specs) passed: 184 passed on the built client, including the hung probe, the phone notice over real content, tab switching, the strips with and without the button and the live region
Assistant-verification: e2e phone overlap check passed: it failed with the notice's inset removed and passes with it
Assistant-verification: screenshot review passed: desktop and phone shots of each state, with and without the machine button, and of Document and Chat during an outage were opened and checked against DESIGN.md
Assistant-verification: design:browser unavailable: Linux visual baselines were not regenerated
User-preference: Status UI stays quiet when things are healthy and calm when they aren't, like Notion
Co-authored-by: Alex Lavaee <lavaman131@github.com>
Assistant-duration: 1h 0m 11s converged, estimated 3h
…hat Build needs in machine words, and make the plain-words check catch detach

Three gaps in the previous commit.

When a document's machine said "No machine is connected." its button still drew the
machine's own connected dot, so a green dot sat beside those words. The button now draws no
dot at all for that state: the bare icon in tertiary ink, as for a person who has no machine.
The status-graphics rules decide it: success is for a connected machine and the muted dot
already means a machine that is there but unreachable, which a document with nothing
connected is not, so absence is drawn as absence. Every state that is not healthy now draws
the Planner's own dot or none, and that dot no longer carries the machine's `data-state` at
all, so the green cannot come back through the cascade. The design audit plate takes its
dots from the same `plannerGlyph` as the button, and its labels speak of the machine. A real
app test with a connected machine that cannot reply checks the button on desktop and phone.

Build plan's tooltip and the refusal after pressing it still said "Reconnect the Planner to
build" and "Connect a Planner to this document to build". They now say "Reconnect the
machine to build" and "Connect a machine to this document to build", the same machine-first
words as "Connect a machine to get a reply". It is the machine, not "your machine", because
the document may run on a machine a teammate shares. The connect copy is one constant shared
by the tooltip, the refusal and the key Build plan uses to offer how to connect a machine, so
they cannot drift apart. The plain-words test now walks every reason Build plan waits and
every refusal Build shows, and Build plan's tooltip with its connect key.

The plain-words pattern missed "detached", whose letters are `tach`, not `attach`. It now
matches planner, detach, attach, held, decisions and @chopin in any form, and a test shows
it flags each banned term, "detached" and "reattach" among them, and none of the plain words.

The docs and DESIGN.md say the same. Strings that name the Planner or Decisions and were not
edited by this change (transcript lines, run cards, composer hints, busy labels, the setup
popover Build plan opens) are listed in the pull request as untouched, and so is the known
duplication of an offline notice on a parent and its open child.

Assistant-model: Claude Sonnet 5.5
Assistant-workflow: goal (run 19ea4b06-26bd-4515-94f7-1201b0f8463c)
Assistant-verification: bun test apps/web packages scripts e2e/focus-color.test.ts passed: 2639 pass, 3 skip, 0 fail, five more than before; bun test scripts passed again after the design-contract hash was renewed (137 pass, 1 skip)
Assistant-verification: the new and rewritten cases were each seen red first: the button cases drew data-state="connected" for no machine and for every unhealthy state, the Build cases returned the Planner copy, and the old pattern let "detached" through; taking plannerGlyph's none back to undefined fails the button and audit plate cases, and swapping a Build refusal back to Planner words fails the Build case
Assistant-verification: bun run types passed: every package and the e2e project type-check
Assistant-verification: bun run fix then bun run ci passed: dprint, oxlint (71 warnings, 0 errors, as before), tokens, design contract (the reviewed hash of machine-button.tsx renewed, its one dynamic class unchanged), design record and Impeccable show no new findings
Assistant-verification: bun run design:behavior passed: 22 keyboard, focus, reduced-motion, reflow and build-blocker checks at two widths
Assistant-verification: E2E_SKIP_BUILD=1 bun run e2e planner-attachment planner-picker machine-choice machines connection implementation-launcher on a client built from this tree gave 93 passed and 10 failed, all 10 (8 in implementation-launcher, 2 in machines) with "either user.signingkey or gpg.ssh.defaultKeyCommand needs to be configured", this machine's global commit signing; the 93 include the new no-dot button test on desktop and phone
Assistant-verification: GIT_CONFIG_GLOBAL=/dev/null bun run e2e implementation-launcher machines passed: 32 passed, 0 failed, so the 10 are environmental
Assistant-verification: screenshot review passed: audit-presence desktop and phone (only the healthy cell keeps the machine's green, and No machine connected shows the bare icon with no dot, its computed colours read back from the page), audit-composer-states and audit-build-blockers desktop, and the real-app planner-none-button desktop and phone, planner-starting and planner-failing-menu, were opened and checked against DESIGN.md
Assistant-verification: not run: design:browser (Linux visual baselines are not regenerated), the full bun test, bun run test:postgres, the Docker image build, the whole e2e suite (six specs only), and GitHub's checks on a pushed head
User-preference: Status UI stays quiet when things are healthy and calm when they aren't, like Notion
Co-authored-by: Alex Lavaee <lavaman131@github.com>
Assistant-duration: 20m 54s
…he teammate's strip fits

On GitHub's runners the phone test for a machine that cannot start measured the teammate's
composer strip while the Chat pane they had just tapped was still sliding in, its left edge
at -229 px and then -67 px on the retries, so it failed on all three tries. The check now
retries until the strip is inside the viewport, as Playwright's own assertions wait, and a
strip that never fits still fails it.

Assistant-model: Claude Opus 5.5
Assistant-workflow: inline
Assistant-verification: GitHub e2e shard 2/12 failed: planner-picker.e2e.ts:644 at expectInsideViewport with the strip's left at -229 and -67 px on its retries
Assistant-verification: E2E_SKIP_BUILD=1 bun run e2e planner-picker -g "keeps failing to start" --repeat-each 4 --workers 1 passed: 4 passed (parallel repeats collide on one Postgres database name, an environment limit)
Co-authored-by: Alex Lavaee <lavaman131@github.com>
@lavaman131
lavaman131 merged commit 86c5b69 into main Nov 6, 2026
24 checks passed
@lavaman131
lavaman131 deleted the quiet-planner-and-offline branch November 6, 2026 02:08
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