Show document connection status in the document header #297
- MA
## Integration and validation
Move document connection status into the current measured workspace header and compact document overlay. Keep the rich-text composer with its existing visible Connection lost notice and reconnect action, plus read-only empty-document copy. Preserve accessible full details, offline locking, reload, and one Reconnected announcement.
Reviewed merged workspace and PlanEditor boundaries before renewing exact pins. Formatting/lint/design checks, types, 20 focused tests and all 22 editing/responsive browser tests pass locally. Rebased onto latest main; final CI running. The original screenshots below show the earlier textarea composer; the document-status presentation remains representative.
<details> <summary>Original proposal and screenshots</summary>
## Why
When the WebSocket dropped, a floating "Reconnecting · Editing resumes once connected." pill appeared over the bottom-right of the document (on phones too). The editor, decisions and composer silently became read-only, so typing just did nothing, and the composer placeholder still invited a message. The error variant wrapped its label and truncated its detail at 22rem.
## What changed
- Status lives in **one place**: right-aligned in the document pane's header row, beside the Document / Decisions switcher. On phones it takes a row at the top of the document destination, only while it has something to say. The floating pill is gone. - States (`describeStatus` in `packages/editor/src/status.tsx`, unit-tested): - Connected and idle: nothing shown (the label stays in the live region for screen readers). - Loading / Planner working / first connection: the quiet dot, no text. - Reconnecting: `text-xs` secondary "Reconnecting…" with a warning dot that pulses (and stays still under reduced motion). "Editing resumes once connected." is in the tooltip. - Lost for more than 5s ("Offline"), refused ("No access"), closed ("Disconnected"), or could not open: destructive `text-xs` label that never wraps, plus a `btn-sm btn-ghost` **Reload**. The full detail is in a tooltip and kept for screen readers, never truncated inline. - While the connection is down, the editor body drops to secondary ink with a default cursor (`.plan[data-plan-offline]`). It comes back instantly on reconnect. - The chat composer's placeholder reads "Reconnecting…" while reconnecting. Send was already disabled. - `PlanEditor` reports its opened/failed state through a new `onState` callback, and the room renders `PlanStatus` into a new `Workspace` `status` slot. No protocol or connection-lifecycle changes. - The design-audit "Loading and status" specimen now shows the real `PlanStatus` in a header row next to the real view switcher: Connected, Loading, Busy, Reconnect, Error, No access.
## Screenshots
**Before: reconnecting (desktop)**, a pill floating over the prose

**Before: phone**

**After: connected and idle**, no status shown

**After: reconnecting**, in the header; prose dimmed

**After: offline after 5s**, with Reload

**After: refused connection**, with the detail in a tooltip

**After: full desktop, reconnecting** (composer reads "Reconnecting…")

**After: phone, reconnecting / chat composer**
 
## Testing
- `bun test packages/editor apps/web`: 926 pass, 0 fail (includes the new `status.test.ts`) - `bun run types`: pass - `bun scripts/check-design-record.ts`: pass - `bun run ci`: dprint, oxlint and tokens pass. The design-contract step fails only on the review hashes below. - New E2E `a lost connection is said in the document header and the composer` in `e2e/editing.e2e.ts`. Local E2E is suspended for this run, so it relies on CI's browser-integration job. - Checked by hand against a fake-GitHub server, using `page.routeWebSocket` to drop sockets and a 403 socket probe for the refused state.
## Needs a design-contract review
`bun run ci` reports "reviewed dynamic owner changed" for two hash-pinned files. I have not edited the hashes.
| File | Exception file | New SHA-256 | | --- | --- | --- | | `apps/web/src/workspace.tsx` | `scripts/design-contract/exceptions/dynamic-web.json` | `7c052ef4490039912ced781a06421d350ec856117b906b588d93b57175393f2d` | | `packages/editor/src/plan-editor.tsx` | `scripts/design-contract/exceptions/dynamic-editor.json` | `58658e07dfd6c7eae85a405c4b4679653347b5814dd33ff91530c66decd4f3cb` |
Both are safe: `workspace.tsx` only gains a static wrapper and a status slot, and `plan-editor.tsx` only adds a boolean `data-plan-offline` attribute and removes the old status render. Neither adds dynamic class or style logic.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
</details>
- MA
**Review:** Looks good
Checked in a real browser (fake GitHub, `routeWebSocket` drops) at 1440x900 and 390x844. No protocol or lifecycle changes: `onState` and `status` are pure UI plumbing, matching AGENTS.md.
Verified - Idle: status box is 0x0 but stays `role=status aria-live=polite` with "Ready" for screen readers. Nothing drawn. - Blip: offline for 2.5s then restored shows only "Reconnecting…" and never "Offline"; returns to hidden on reconnect. - Offline for 6s: destructive "Offline" + Reload, no wrapping, composer placeholder "Reconnecting…", Send disabled. Reload navigates and recovers. - Reduced motion: dot animation is `none`; normal motion pulses. Phone: status takes a row only when notice/alert, no horizontal scroll. - Prose and decision card titles drop to secondary ink; cards do not look disabled beyond that (their Discard/Save were already hidden while locked).
Findings (non-blocking) 1. `packages/editor/src/status.tsx` (label span with `data-tooltip`): the tooltip is hover-only. The label is not focusable (`tabIndex -1`) and the Reload button has no tooltip, so tabbing to Reload shows nothing, and touch users on the phone never see the detail. Screen readers get it via the sr-only detail, so this is sighted keyboard/touch only. Fix: put `data-tooltip={detail}` on the Reload button too (the global tooltip already shows on focus), or show the detail inline on phones. 2. `apps/web/src/theme.css` `.workspace-status-row`: on phones the row goes from 0 to 36px when "Reconnecting…" appears, shifting the document down and back on every short blip. Consider reserving the row or overlaying; desktop does not shift. 3. Live region is not atomic, so each state change announces label plus detail ("Reconnecting… Editing resumes once connected."), and "Ready" on recovery. Acceptable, but a flapping connection will be chatty; consider announcing only the label. 4. Cosmetic: "Offline" and the Reload button sit flush at the right edge with a very tight gap on desktop. 5. The two design-contract review hashes (`workspace.tsx`, `plan-editor.tsx`) look safe: static wrapper/slot and a boolean data attribute only.
- MA
Applied the review follow-ups in cdab153d:
1. **Detail reachable without hover:** Reload now carries the detail as its tooltip (shown on focus) and as its accessible description. A text button opts into the global tooltip with `data-tooltip-detail`. On coarse pointers, the phone row writes the detail as a second `text-xs` line under the label. 2. **No push-down on phones:** the status row now overlays the top of the document (absolute, page background, hairline below) instead of being inserted. 3. **Live region:** a separate label-only element (`role=status`, `aria-atomic`) announces Reconnecting…, Offline and the other alert labels, but never the detail. It stays silent on initial load (no "Ready") and says "Reconnected" once after an outage (`announcement()`, unit-tested). The e2e now asserts all three. 4. **Spacing:** the label and Reload are a `gap-2` equivalent apart (spacing tokens). On phones, Reload sits at the row's end inside 1rem padding.
The `workspace.tsx` review hash in the PR body is updated to `7c052ef4490039912ced781a06421d350ec856117b906b588d93b57175393f2d`. The `plan-editor.tsx` hash is unchanged.
**Phone, reconnecting (overlay, detail inline on touch)**

**Phone, offline**

**Desktop, Reload focused (detail tooltip)**

- MA
CI on 6fcfcc5c: e2e and container pass. The validation job fails only on the two design-contract review hashes listed in the PR body. The first run on cdab153d failed because `e2e/smoke.e2e.ts` found the status by its old `[aria-live][data-level]` selector, which I've fixed.
lavaman131referenced 9d011073dc44last month on GitHub
lavaman131referenced 7f6cfcdc3abelast month on GitHub
MaggieAppletonhead-ref-force-pushed 0f5fbe8206f4last month on GitHub
MaggieAppletonhead-ref-force-pushed 871fe538482dlast month on GitHub
- MA
Confirmed from failed `ci` run `37618138076`: the first meaningful failure is `Format, lint, and enforce design tokens`, and the only hard failure is stale reviewed dynamic-owner exceptions in `apps/web/src/workspace.tsx` (lines 154, 165, 183, 192, 334, 528, 540) and `packages/editor/src/plan-editor.tsx` (lines 347, 359). `oxlint` emitted warnings only, and `e2e` plus `container` were green on this head.
I did not push a speculative fix because this runner cannot install or run the pinned Bun 1.4.2 toolchain: `bun` is absent here, and downloading the public 1.4.2 release binary was blocked, so I could not rerun `bun install --frozen-lockfile`, `bun run fix`, or `bun run ci` locally to verify an exact reviewed-hash renewal.
Next action for a human: on a machine with Bun 1.4.2 available, check out `871fe538482da8f16872433ade82f255c16f73c9`, run `bun install --frozen-lockfile` and `bun run ci`, inspect the `workspace.tsx` and `plan-editor.tsx` data-flow changes, and renew the exact entries in `scripts/design-contract/exceptions/dynamic-web.json` and `scripts/design-contract/exceptions/dynamic-editor.json` only if that review still looks safe.
`babysit-head:871fe538482da8f16872433ade82f255c16f73c9`
> [!WARNING] > <details> > <summary>Firewall blocked 1 domain</summary> > > The following domain was blocked by the firewall during workflow execution: > > - `releaseassets.githubusercontent.com` > > To allow these domains, add them to the `network.allowed` list in your workflow frontmatter: > > ```yaml > network: > allowed: > - defaults > - "releaseassets.githubusercontent.com" > ``` > > See [Network Configuration](https://github.github.com/gh-aw/reference/network/) for more information. > > </details>
> Generated by [PR CI fixer](https://github.com/githubnext/chopin/actions/runs/37618994335) · gpt54 · 59.8 AIC · ⌖ 5.54 AIC · ⊞ 16.4K · [◷](https://github.com/search?q=repo%3Agithubnext%2Fchopin+%22gh-aw-workflow-call-id%3A+githubnext%2Fchopin%2Fpr-ci-fix%22&type=issues)
<!-- gh-aw-agentic-workflow: PR CI fixer, engine: codex, model: gpt-5.4, id: 37618994335, workflow_id: pr-ci-fix, run: https://github.com/githubnext/chopin/actions/runs/37618994335 --> <!-- gh-aw-workflow-call-id: githubnext/chopin/pr-ci-fix -->
MaggieAppletonhead-ref-force-pushed 0b238a624149last month on GitHub
MaggieAppletonhead-ref-force-pushed be4aff6ac7a5last month on GitHub
MaggieAppletonhead-ref-force-pushed 4fe0c18172c8last month on GitHub
- MA
Confirmed from failed `ci` run `37632483684`: the current head still matches `4fe0c18172c8d44304f5bffdfd2c0799dcefb2d0`, `e2e` and `container` are green, and the only failing check is `format, lint, types, tests`. Based on the current PR state and the existing validation failure description, this still looks like reviewed design-contract exception drift in `apps/web/src/workspace.tsx` and `packages/editor/src/plan-editor.tsx`, not a runtime regression in the connection-status behavior itself.
I did not queue a commit from this runner because the pinned local toolchain is unavailable here: `bun` and `mise` are both missing, so I could not run the required `bun install --frozen-lockfile`, `bun run fix`, or `bun run ci` reproduction and verification steps.
Next action for a human: on a machine with Bun 1.4.2 available, check out `4fe0c18172c8d44304f5bffdfd2c0799dcefb2d0`, rerun `bun install --frozen-lockfile` and `bun run ci`, then renew the exact reviewed exceptions in `scripts/design-contract/exceptions/dynamic-web.json` and `scripts/design-contract/exceptions/dynamic-editor.json` only if those ownership changes still look safe.
`babysit-head:4fe0c18172c8d44304f5bffdfd2c0799dcefb2d0`
> Generated by [PR CI fixer](https://github.com/githubnext/chopin/actions/runs/37627304799) · gpt54 · 35.4 AIC · ⌖ 4.99 AIC · ⊞ 16.4K · [◷](https://github.com/search?q=repo%3Agithubnext%2Fchopin+%22gh-aw-workflow-call-id%3A+githubnext%2Fchopin%2Fpr-ci-fix%22&type=issues)
<!-- gh-aw-agentic-workflow: PR CI fixer, engine: codex, model: gpt-5.4, id: 37627304799, workflow_id: pr-ci-fix, run: https://github.com/githubnext/chopin/actions/runs/37627304799 --> <!-- gh-aw-workflow-call-id: githubnext/chopin/pr-ci-fix -->
MaggieAppletonhead-ref-force-pushed 4ac0bbed1e02last month on GitHub
MaggieAppletonreferenced this in Edit the document title in place in the header #343last month on GitHub
MaggieAppletonreferenced this in Keep workspace chrome still when switching documents #346last month on GitHub
MaggieAppletonreferenced this in Undo and redo each person's own document edits #352last month on GitHub
MaggieAppletonhead-ref-force-pushed b9310bbfca76last month on GitHub
MaggieAppletonmerged 2adfa7eb9380last month on GitHub
MaggieAppletonhead-ref-deletedlast month on GitHub
MaggieAppletonreferenced this in Wait out connection blips and recover as soon as the network returns #385last month on GitHub