Skip to content

Workspace polish round 1: New session, verdict colour, anchored popovers, send-order filmstrip, phone swipe - #8

Merged
cailmdaley merged 19 commits into
mainfrom
design/document-workspace
Oct 5, 2026
Merged

cailmdaley merged 19 commits into
mainfrom
design/document-workspace

Conversation

@cailmdaley

Copy link
Copy Markdown
Owner

This is the first polish round on the document workspace, from Cail's first day of use. It also adds swiping between pages on the phone.

Opus 5.5 on behalf of Cail.

What changes

  • New session is back. When a session can be resumed, Resume ↵ is the primary verb and a quieter New session (⌥↵) sits beside it. Before, the fresh-start verb was hidden whenever a session was resumable. New session still asks before cutting a live worker.

  • Verdicts carry colour. Temper is verdigris and Discard is the Desk's deletion red (--kbn-discarded). The colours are the same everywhere a verdict appears: the Desk card, the fiber page's act zone, the navbar pair and the undo toast. They are mixed against the paper in force, so they stay legible under every theme.

  • Popovers stay with their trigger. A new anchoredPopover.ts places each panel in the browser's top layer, flips and shifts it inside the viewport (and above the phone's bottom bar), and follows the trigger. It is used by the Meeting menu, the document menu, the channel switcher, the parent search and the in-flight ⋯ menu. The agent and effort pickers open an anchored list on desktop; a finger keeps the platform picker.

  • The filmstrip runs in send order: the constitution first, then documents declared in the body and never sent, in body order, then sent documents by first delivery, oldest first. A re-send keeps its place and gets the fresh mark.

  • The floating worker plate is gone. Worker state lives in one place, the fiber page's act-zone pill, the same component the sidebar card uses. While a fiber awaits review, a compact Temper/Discard pair sits beside the fiber name in the navbar, or in the page sheet on the phone.

  • The sidebar toggle is one 220 ms slide, animating only translate and opacity. It is instant under reduced motion.

  • Keys: c (and .) opens the conversation; s (and ⌘\) toggles the Constitutions sidebar.

  • Phone swipe. A horizontal swipe on the page itself steps pages: the page follows the finger, resists at the ends, and settles from the release speed. The swipe yields to:

    • edge starts, so Safari's back gesture still works;
    • vertical scroll;
    • pinch zoom;
    • content that pans sideways: scrollable tables and code, sliders, media, fields, and decks or carousels that set touch-action or prevent the touch.

    Sandboxed reports recognise the swipe inside the frame and send bounded travel through the bridge. The parent accepts it only from the selected, active frame. A swipe whose release is lost is cancelled.

  • Phone layout:

    • neighbouring pages are flat;
    • wide markdown tables scroll inside themselves;
    • the bottom bar steps aside for the keyboard;
    • the settings path truncates cleanly;
    • the top bar stays one row in landscape.
New session beside Resume Navbar verdict pair
Meeting menu anchored Effort picker anchored

Sidebar slide, frame by frame:

The screenshots come from the fixture harness. Live-data shots and the iOS Simulator recordings are in the felt constitution ai-futures/felt/constitutions/document-workspace (shots-7/).

Review

An independent Opus review found no trust-boundary escape:

  • the new swipe bridge message can move a hostile report at most one page;
  • conversation stays out of the keys a report can forward;
  • the navbar and page-sheet verdict pairs share the one undo queue.

It confirmed three bugs, all fixed with tests that failed before the fix:

  • a swipe whose release was lost left the page track parked;
  • decks and carousels inside reports also paged the reader;
  • the parent search didn't re-anchor when its results changed.

A fourth fix measures both swipe axes in screen space inside frames. Without it, a sideways swipe read as vertical whenever the phone top bar moved the frame.

Verification

Observed on this head:

  • npm test: 1,534 tests passed under each pinned time zone, on Node 25 and on Node 22 (CI's runtime).
  • npm run typecheck: clean.
  • npm run e2e: 91/91.
  • make build SKIP_CLI=1: passes.

The phone swipe was exercised in iOS 26.5 Simulator Safari, on the harness and on live data. Native PDF pages keep their own touches, so the bottom bar steps past them. Whether Safari's real edge-back gesture still works was not verified in the Simulator.

🤖 Generated with Claude Code

https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67

cailmdaley and others added 19 commits October 5, 2026 13:10
Resume ↵ stays the primary verb; a fresh start is the quieter secondary
verb (⌥↵) instead of disappearing. Every column state gets a tested verb
set, and the workspace harness fiber carries a resumable session.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
One recipe in the board's pigments: Temper in verdigris, Discard in the
deletion red (--kbn-discarded), tinted fill, ink and border mixed against
the paper and ink in force, with hover and focus states. The Desk card,
the act zone, the review plate and the undo toast all wear it; an e2e
checks hue and AA contrast on default and Night Chart paper.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
anchoredPopover places a panel against the control that opened it,
flips or shifts it to stay inside the viewport and above the phone's
bottom bar, follows it through scrolling, and promotes it to the top
layer so no ancestor overflow clips it and no transformed stage moves
it. The Meeting menu, the in-flight verdict menu, the parent search,
the document menu and the channel switcher all use it. The agent and
effort selects open an anchored list on a fine pointer (the select
stays the value and its change event the commit; a finger keeps the
platform picker).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
…elivery

A channel reads its fiber page first, then documents declared in the
body and never sent (body order), then sent documents by first delivery,
oldest first, then sends of unknown time. A re-send keeps its place and
gains the fresh mark. Unit and e2e tests that assumed recency order
assert send order instead.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
One touch recognizer serves the reader and sandboxed reports: a one-finger
swipe that starts 24 px inside either edge, turns decisively sideways within
10 px and keeps clear of content that pans sideways (scrollable regions,
sliders, media, editing, a live selection, pinch zoom). The track follows the
finger with the neighbour flat beside it, resists past the ends, and settles
on a velocity-scaled release curve. Reports recognise the swipe in-frame and
send bounded travel through the document bridge; the reader decides.

Wide markdown tables scroll inside themselves on the phone so the page keeps
its width and its swipe.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…keyboard

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rs, keyboard-aware page bar

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The fiber page's act zone carries the worker pill, drawn with the same
plate and wording as the sidebar card, and nothing else draws worker
state in the reader. Awaiting-review Temper and Discard ride the navbar
as a compact pair beside the fiber's name on every delivered page; on
the phone they sit beside the page sheet's heading.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
… the phone

The folded settings line keeps each fact whole and lets the host:path
yield to an ellipsis, so it never runs under History. The page-count
row clips the separator that leads any wrapped line. Docs name the
fiber page's worker pill and the navbar verdict pair; the landscape
phone keeps a one-row top bar (the pair lives in the page sheet).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
The layout lands at once; the sidebar slides in or out on translate and
opacity while the stage and tab strip glide back from where they were,
on the standard curve. Cards no longer fly from the Desk on a toggle
(they still do on arrival), the covered left neighbour fades with the
column instead of blinking, and reduced motion lands instantly. An e2e
checks the slide's duration, its properties, a constant page width and
one-way motion.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
…orker pill

The pair is built once per reviewing fiber state rather than on every
paint, so a repaint never swaps buttons under the pointer, and both it
and the act zone's worker pill disappear when hidden instead of leaving
an empty row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
c clicks the worker pill's own destination on the Desk and in the
reader, with . as its alias; the sidebar moves to s (⌘\\ unchanged).
conversation stays out of the document-forwarded allowlist, so a report
cannot open it, and the hostile-key tests post c to prove it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
…hored popovers and pickers, send-order filmstrip, worker plate retired, 220 ms sidebar slide, c opens conversation

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>

# Conflicts:
#	ui/src/board/workspace/Reader.ts
Under full-suite load the phone reader's open outlasted the 2.5 s state poll.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
A latched page swipe parked the track at its follow offset whenever its
release never reached the reader: a live report replaced or suspended
mid-swipe, a selection or channel change, a release arriving without a
measurable stage, or a report that moves once and never ends. The reader
now cancels in place on selection, show and hide, on an unmeasurable
release, and after 500 ms without movement; DocumentHost turns the
departure of the frame that started a swipe into a cancel.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
A reveal.js deck (touch-action pinch-zoom or pan-y) or a carousel that
handles its own touches also paged the reader. The page-swipe recognizer
now treats content as claimed when its touch-action withholds either pan
from the browser, and decides the latch in a bubble-phase window
listener, after the content's handlers, dropping a touch the content has
already prevented. The reader's bottom bar opts back in with
data-ws-swipe="on".

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
The parent-search dropdown was placed once, on its first render; when it
opened above a field low in the viewport, taller later results grew down
over the field. anchorPopover's release now carries reposition(), and the
dropdown calls it on every render of its results.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
dx used screenX but dy used clientY, so a frame shifting under the finger
(the phone top bar hiding or showing) read as vertical travel and a sideways
swipe was rejected.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01LwX9WpuD7VnV6mfmN9eb67
@cailmdaley
cailmdaley merged commit bf03872 into main Oct 5, 2026
4 checks passed
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