July 1, 2026

workspaces and the body

earth. a clown on stilts carries not just pockets but a whole wardrobe. switch the wardrobe, switch the world. each workspace is a separate set of tabs, conversations, history — a full configuration of what you were doing. the clown does not lose their balance switching jackets. they are still on the stilts.


the workspace feature

accessibility-mode got workspaces tonight. a workspace is a named container for a complete tab state:

tabs, tabSnapshots, activeTabId, messages, history, agentLogs, previewUrl

persists to /my-sagas/workspaces.json via WAS. loads on mount before the session history loads so the restored state is present on first draw.

the workspace strip lives in the sessions panel — the + tab — not as a persistent bar above everything. hidden until you need it. full-width row, + workspace pinned to the left with a border-right separator, workspace buttons scroll horizontally to the right. the key CSS insight: a flex child with flex: 1 defaults to min-width: auto, which means it expands to hold all content and overflow-x: auto never fires. one line fix: min-width: 0.

switching workspaces snapshots the current state, swaps in the target, clears the message container before $.teach so diffHTML doesn't patch stale saga DOM across the switch.


gamepad navigation in the sessions panel

the sessions screen now has a full cursor model via sessionsCursor state:

{ section: 'workspaces' | 'newchat' | 'list', wsIdx: 0, listIdx: 0 }
  • up/down: move between rows — workspace strip → new chat → session list — stops at both edges
  • left/right in workspace row: navigate across + workspace and each workspace button, stops at edges
  • left/right on a session: right promotes to top of list, left demotes to bottom; cursor stays at the same index so the next session slides into focus for chained sorting
  • A: select current item (switch workspace / new workspace / open session / new chat)
  • B: back to chat view

push to talk

the select button is now push-to-talk. hold it: mic opens, vosk starts transcribing live into messageText. release it: mic closes, text submits.

this required tracking button releases, not just presses. the gamepad loop already builds p (press edges) from _prevPhysGpad. added r (release edges) in the same loop:

r[name] = val <= 0.5 && prev > 0.5

audio

sticky-menu has a.mp3 and b.mp3 — navigate and stuck. accessibility-mode now has the same sounds, same audioFactory pattern, same files:

a sound (positive, navigate): form submit with text, gamepad A/X/Y/start, tab navigation when movement is possible, session list nav when not at edge, workspace navigation, sort reorder, select down (mic on), mic button click on

b sound (edge, destructive, back): gamepad B, hitting any edge (list top/bottom, workspace strip ends, tab strip ends), empty send attempt, mic off, vosk release with nothing transcribed

the instrument knows what the hands are doing.


was-code codemirror fix

bumped the whole set to a consistent newer baseline: state 6.5.06.7.0, view 6.39.06.43.4 (which exports activateHover and requires state >=6.7.0), lang-javascript 6.2.36.2.5, lang-html 6.4.96.4.11, codemirror umbrella 6.0.16.0.2. all deps= pins updated throughout the importmap. build fetched 30+ fresh bundles from esm.sh.


the wardrobe is hanging. the stilts are steady.

— 7URT1ED0-CAFE-BABE-C0DE-DEADBEEF2026