Skip to content

feat(room): waitMark, a host's mark for the room's waits (R10) - #48

Merged
jamesyong-42 merged 2 commits into
mainfrom
feat/room-wait-mark
Nov 12, 2026
Merged

jamesyong-42 merged 2 commits into
mainfrom
feat/room-wait-mark

Conversation

@jamesyong-42

@jamesyong-42 jamesyong-42 commented Nov 9, 2026 •

Copy link
Copy Markdown
Member

What

A host input, waitMark (R10, RoomWaitMark), that lets a host hand the room its own mark for the room's
waits. With one, the room draws its waits in that mark. With none, every wait stays as rosetta's app shows it today.
The reference host passes none, so the app reads byte for byte as before.

  • mark(place, { resting }) draws the mark for one place, "view", "column" or "card". The mark is at rest
    while the work it stands for is paused.
  • carried says the host's own hold already stands where the room's first one comes. The room's first hold
    then stands at once, so the mark is never faded out and shown again.

What changes when a mark is given:

  • The view's waits are one hold. Reaching the index, then the cold start before its first project
    (HistoryRoom.tsx, components/ui/Hold.tsx):
    • the mark stands at the centre of the room's box;
    • one line 26 under it: connectingTitle, or the step's own title with the ellipsis of work under way
      ("Finding your history…", "Discovering conversations…");
    • its amount 4 under that ("1,333 files checked", a paused import's "80 sessions found"), in tabular figures. The
      first step's title stands alone, because its amount ("Looking for conversations…") counts nothing.
  • The hold's timing (lib/use-standing-hold.ts):
    • It is late, as the veil is: nothing for 120ms.
    • Once it has shown it stands at least 800ms, then fades over 280ms (120ms under reduced motion).
    • What comes next waits for it.
    • A new line crossfades in place, 140ms out and 240ms in.
  • A failure is no wait. An index that has stopped answering keeps the room's own face.
  • The import card wears the mark where IMPORTING HISTORY stood, with the step beside it (IngestStatus.tsx):
    • its words keep to the step's title and its count;
    • a fraction the step knows is a colourless 2px hairline, and a step that knows none draws no bar;
    • the detail, the amount, the recency line, the connection and the timer fold under Agent details, which now
      stands through search too;
    • paused updates stand the mark at rest.
    • The card without a mark is unchanged. Its live regions, footer and agent list are now parts that both cards
      draw.
  • A conversation's wait is a hold in its column (TranscriptLoading.tsx), its sketch and sweep gone. "Read
    available messages" stays under its words. A load that failed keeps its own face and Try again.
  • Nothing sweeps beside the mark (Meter.tsx). A meter that knows no amount draws nothing, a veil keeps its
    words, and a meter that knows still fills.
  • PAUSED_UPDATES holds the card's paused words in one place (lib/ingest-activity.ts). The card and the hold both
    read them, so they cannot drift.

Why

VibeField's History mounts this room. Its loads elsewhere (the Setup Assistant's) are one look: the field's living
mark and one line under it. Today History's waits are each in their own look:

  • the connecting face's sweep;
  • the cold start's centred card;
  • the import card's eyebrow and its --vf-select bar;
  • the transcript's sketch and sweep;
  • the veils' sweeps.

The card's bar is selection blue, against the room's own word for its meter ("The room's colourless meter",
Meter.tsx).

This keeps the room's waits the room's own: their steps, their words, their place ("Progress describes its named
step and never covers available content"). It takes only the mark a host places in them, as the room already takes
connectingTitle and the consent slot. VibeField's ruling: track HG, HG-W1…W5 and HG-L1, DESIGN.md §8 "History".

Asks (the owner's to rule)

  • A1, one input with places, mark(place, { resting }), rather than three node props. The room asks for the
    mark where it waits, and the host draws it at the size the place needs (VibeField: 99 wide in a hold, 40 on the
    card).
  • A2, a mark means a hold. With a mark, the connecting face and the cold start become the hold. Without one, the
    room's faces are untouched. Is that coupling right, or should the hold be its own option?

Tests

clients/app/test/room-wait-mark.test.mjs, 9 tests, server renders and the timing's pure function:

  • with no mark, the room keeps its own waits;
  • in a mark, the connecting face is a hold: the host's title and words, no sweep, no face;
  • carried: no late;
  • the floor: gone before it shows, standing 800ms, then leaving;
  • the cold start's words:
    • "Finding your history…" alone;
    • "Discovering conversations…" over its count;
    • a paused import's title and count at rest;
    • paused updates' words at rest;
  • the card: the mark, the step, the title and its count, the hairline only for a fraction, the rest under Agent
    details, at rest while paused;
  • meters and veils beside the mark;
  • the column hold, its act, and a failed load's own face.

Run on the author's Mac, one file at a time, on Node 26.5.0 and npm 12.0.1 after npm ci --ignore-scripts:

  • the protocol's and the room's builds (tools/npm/build.mjs). The room's build is its typecheck, with
    declarations.
  • room-wait-mark 9/9, dom-contract 15/15, import-state 21/21, ingest-progress 11/11, room-meters 5/5,
    room-not-answering 3/3, room-package 4/4 and room-search-key 4/4. These are every app test that imports a module
    this changes, or walks the room's sources.

Proved by rosetta CI at this PR, not run here:

  • the rest of npm test and the app's build;
  • the docs check and check:packages;
  • the Electron fixtures (room-host, cold-import).

No server render drives the crossfade's effect, and no fixture covers it.

Out of scope

  • The reader's (TranscriptReader) own opening face: VibeField's Peek keeps it as it is.
  • The rows' and the charts' waits: they keep their own ink and words.
  • The release: this rides the next one, 0.1.6.

🤖 Generated with Claude Code


Autofix with [code]smith
Open in [code]smith · Need help on this PR? Tag @codesmith-bot with what you need.

jamesyong-42 and others added 2 commits November 8, 2026 01:07
…view's waits one hold, the import card in the mark, a conversation's wait a hold in its column

A host that loads in its own mark can hand the room that mark, and the room's waits then read as the host's. VibeField
loads in its living mark with one line under it (track HG: HG-W1…W5 and HG-L1, its DESIGN.md §8 "History"). With no
mark every wait is as rosetta's app shows it, and the reference host passes none.

- `RoomWaitMark` (`host-inputs.ts`, exported with `RoomWaitPlace`):
  - `mark(place, { resting })` draws the mark for `"view"`, `"column"` or `"card"`, at rest while the work it stands
    for is paused;
  - `carried` says the host's own hold already stands where the room's first one comes.
- The view's waits are one hold (`HistoryRoom.tsx`, `components/ui/Hold.tsx`, `styles/hold.css`):
  - reaching the index: `connectingTitle` over `connectionError ?? unavailable`, the face's own words;
  - then the cold start before the first project (`coldStartHold`): the step's title, with the ellipsis of work
    under way, over its amount. The first step's title stands alone. A paused import and paused updates stand at
    rest.
  - An index that has stopped answering keeps its face: a failure is no wait.
- The hold's timing (`lib/use-standing-hold.ts`; `afterWait` is its pure part):
  - late 120ms; once shown it stands 800ms, then fades over 280ms (120ms under reduced motion);
  - what comes next waits for it;
  - a new line crossfades in place, 140ms out and 240ms in; words that come back while their line leaves come in
    again at once.
- The import card in the mark (`IngestStatus.tsx`, `styles/ingest.css`):
  - the mark where IMPORTING HISTORY stood, the step beside it;
  - the title over its count (`readableText` where the step counts conversations, else its amount);
  - a fraction the step knows as a colourless 2px hairline, none for one it does not;
  - the detail, the amount, the recency line, the connection, the timer, the controls and the agents under Agent
    details.
  - The unmarked card is unchanged. Its live regions, footer and agent list are now shared parts (`StatusLive`,
    `StatusFooter`, `AgentList`), drawn by both cards.
- A conversation's wait (`TranscriptLoading.tsx`) is a hold in its column, with Read available messages under its
  words. A failed load keeps its own face and Try again.
- `Meter.tsx`: beside a mark, an amount nobody knows draws nothing. A fraction still fills, and a veil keeps its
  words.
- `PAUSED_UPDATES` (`lib/ingest-activity.ts`) holds the paused words, which the card and the hold both read.
- `README.md` documents the input.
- Tests: `clients/app/test/room-wait-mark.test.mjs`, 9 tests (server renders and `afterWait`).
  - Run here, each file alone: room-wait-mark 9/9, dom-contract 15/15, import-state 21/21, ingest-progress 11/11,
    room-meters 5/5, room-not-answering 3/3, room-package 4/4, room-search-key 4/4.
  - The protocol's and the room's builds (`tools/npm/build.mjs`) ran too; the room's is its typecheck, with
    declarations.
  - Not run here, so CI's: the rest of `npm test`, the app's build, the docs check, `check:packages` and the
    Electron fixtures (room-host, cold-import).
  - No server render drives the crossfade's effect, and no fixture covers it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jamesyong-42
jamesyong-42 merged commit cead42a into main Nov 12, 2026
8 checks passed
@jamesyong-42
jamesyong-42 deleted the feat/room-wait-mark branch November 12, 2026 23:44
This was referenced Nov 12, 2026
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