Skip to content

✨ Ops Control rework + keyboard accessibility layer#105

Merged
arach merged 7 commits into
mainfrom
mission-control-ux
May 15, 2026
Merged

✨ Ops Control rework + keyboard accessibility layer#105
arach merged 7 commits into
mainfrom
mission-control-ux

Conversation

@arach
Copy link
Copy Markdown
Owner

@arach arach commented May 14, 2026

Summary

  • Ops > Control rework. New left panel with primary All / Scout / Native source chips, search input, and an inline-expand agent list synced exactly to the canvas-visible set. Canvas tile clicks (scout and native) now open the FocusOverlay instead of jumping; the overlay is glass-styled and split into three preview tabs (Profile · Activity · Message) with per-tab jump-offs in the top-right of the tab row.
  • Keyboard accessibility + vim layer. Centralized hooks in lib/keyboard-nav.ts: list arrow nav with j/k/g/G, focus traps, slash-to-search, pane navigation via [ / ], roving tabindex. Applied across left panels, agents roster, Tail, Atop, SessionObserve scrubber. Shift+? toggles a global keyboard cheat-sheet overlay.
  • AskRow polish. Replaced the loud COMPLETED pill / red-named actor with a row-header agent name (no actorColor on text) + small right-aligned status glyph (✓ / • / ⚠). Quieter title and summary.
  • Scene store scaffold. lib/scene-store.ts lands the data primitive and matcher engine for a future custom-layout / saved-destinations feature (per the design proposal). No UI consumes it yet.

Not in this PR (intentionally deferred)

The mesh canvas spec-card redesign (MeshCanvas.tsx, mesh-screen.css) and the server-side scenes endpoints (setup.ts, create-openscout-web-server.ts) were also touched in this session, but those files had pre-session modifications I didn't author. Splitting them out into a clean follow-up PR rather than sweeping unrelated work in here.

Test plan

  • Press ? from anywhere — keyboard help overlay appears, Esc closes
  • / focuses the panel search; from search jumps to first item; j / k walk the list
  • [ / ] move focus between left / center / right panes
  • Ops > Control: source chips re-filter both canvas and left-panel list in lock-step; "0 visible" no longer wrong for Native
  • Click a native session tile — opens FocusOverlay (not Tail)
  • Inside overlay: Profile/Activity/Message tabs switch; Open profile ↗ and Open in Tail ↗ live top-right; Tail jump-off pre-filters by agent name
  • Command view "Just finished" rows: agent name reads as primary, no red names, small green ✓ on the right

arach added 3 commits May 14, 2026 13:04
P1 fixes: focusable jump-to-live divider in TailView, keyboard-controllable
SessionObserve scrubber (arrows / Home / End / Shift+arrow), proper
role=dialog + aria-modal + focus restoration on FocusOverlay and AtopView
drawer.

Shared hooks in lib/keyboard-nav.ts:
- useListArrowNav handles ↓/↑/Home/End plus vim j/k/g/G
- useFocusTrap for modal Tab cycling and focus restoration
- useSlashToFocus binds global / to focus a search input (input-guarded)
- usePaneNav binds [ and ] to walk panes by data-pane attr
- rovingTabIndex helper for canonical aria listbox tabindex

Applied across Channels/Conversation/Messages left panels and the agents
roster. AppShell mounts the help overlay and the pane-nav listener; panes
get data-pane="left|center|right".

KeyboardHelpOverlay toggles on Shift+? — a categorized cheat sheet with
glass styling, focus trap, Esc dismiss.
Hoist Mission Control filter/search state into mission-control-store
(activityFilter, sourceFilter, recentWindowMs, query, focusedId,
visibleAgents). The canvas writes the merged scout+native visible set
after each filter pass so the left panel renders the exact same agents
that are on screen.

New ScoutMissionControlLeftPanel routed via LeftPanel for ops+mission:
- Primary chips for Source (All / Scout / Native), full-width prominent
- Search input bound to the store; / focuses, ↓ hands off to first item
- Secondary ghost chips for Activity + recent-window time
- Click an agent → expands the row inline with MODEL / AT / STATE /
  SOURCE specs and two explicit actions (Focus on canvas, Open ↗).
  No silent navigation.

Canvas filter bar gets a matching search input bound to the same store.

FocusOverlay rework:
- Glass treatment: rgba(0,0,0,0.32) + blur(18) saturate(140), contained
  glass panel inside (88% surface, hairline border, 6px radius, drop
  shadow). Pulled from mesh floating-panel pattern.
- Three tabs: Profile / Activity / Message with an accent underline.
  Profile is a 2-col spec snapshot; Activity keeps SessionObserve;
  Message has two clearly-labeled action cards (Steer / Ask) with
  descriptions instead of mystery buttons.
- Per-tab jump-off button lives in the tab row top-right
  (Open profile ↗ / Open in Tail ↗) so the preview content can breathe
  all the way to the bottom of the panel.
- Tile clicks (both scout and native) now open the overlay; the bug
  where native tiles jumped to general Tail is fixed. Open in Tail
  pre-filters by agent name via tailQuery.
AskRow (Command view "Just finished" / "In flight"):
- Drop the loud pill from the left column; promote the agent name to a
  proper row header (uppercase mono, letterspaced, ink). No more
  actorColor on text — Hudson Claude rendered red read as an error.
- Move status to a right-aligned glyph: green ✓ (done), accent • (active),
  red ⚠ (failed). Status label survives as aria-label / title.
- Smaller title (12px) and summary (11px) with single-line clamp so the
  row reads as a quiet glance, not a wall.

Scaffold packages/web/client/lib/scene-store.ts (Scene primitive):
- Types mirror what the design proposal landed on: Scene, SceneZone,
  SceneMatcher, SceneAgentOverride, SceneBody, with surface discriminant
  pre-wired for future surfaces beyond mission-control.
- useSceneStore via useSyncExternalStore (same pattern as
  mission-control-store and mesh-view-store).
- Pure matcher engine: matchesZone + assignAgentsToZones with
  first-match precedence and UNASSIGNED_ZONE_ID bucket.
- Debounced PUT /api/ui/scenes save (server side ships in a follow-up
  PR — file has no consumers yet so this is dead code on its own).
@vercel
Copy link
Copy Markdown

vercel Bot commented May 14, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
openscout Skipped Skipped May 14, 2026 6:11pm

The Message tab is now a real composer instead of two jump-off buttons.
Type, hit ⌘↩ to Steer, ⌘⇧↩ to Ask. Sends directly via /api/send or
/api/ask using the agent's conversation id — no context switch, no
re-typing in the conversation view.

- Optimistic-style inline confirmation ("Steered ↗ Open thread") for
  ~1.8s after a successful send, with a link to open the thread.
- Surfaces send errors inline (rare but the textarea stays loaded so
  the user can retry).
- Explicit "Open conversation ↗" jump-off remains in the tab row
  top-right for users who'd rather compose in the full thread view.
- Renamed Tell → Steer everywhere (matches the user's mental model).

Keyboard 1 / 2 / 3 inside the overlay switch tabs (Profile / Activity /
Message). Input-guarded so typing digits in the composer does not jump
tabs. Layered on top of the existing focus trap.
Cmd/Ctrl/Shift-click on a canvas tile or a left-panel row toggles
selection; plain click keeps its existing meaning (focus the agent /
expand the row inline). Selection lives in mission-control-store as
selectedIds.

When anything is selected, the left panel's count row swaps for a
selection bar (accent-tinted with top + bottom hairline rules):
"N SELECTED · [Tail ↗] [Clear]". Tail navigates to ops/tail with a
pipe-joined tailQuery (e.g. "hudson|arach|narrative-studio").

TailView.matchesFilter now splits the query on '|' and OR-matches the
terms against its existing haystack (summary / project / sessionId /
source / harness / attribution). Single-term queries behave exactly as
before — apple-console-style multi-filter as a strict superset.

Tiles get .s-mission-tile--selected (accent border + outer accent ring)
and left-panel rows get .ml-row--selected (accent-tinted background +
inset accent left rail). Count row picks up "⌘-click to select" hint
when no selection is active.
MissionControlView keyboard:
- Esc when no focus overlay is open now clears the multi-selection
  (overlay-Esc behavior unchanged).
- ⌘A / Ctrl-A toggles select-all-visible: selects every agent in the
  current visible set, or clears if everything is already selected.
- Input-guarded so it doesn't fire while typing in the canvas search
  or the inline composer.

Left panel count-row hint reflects the new shortcut: "⌘-click · ⌘A all".

ActivityRow (Command view's "Live activity" side stream) had the same
actorColor-on-text issue we fixed on AskRow — names hashing to red read
as warnings. Switched to plain ink color, mono, weight 500, slight
letter-spacing. Dropped the now-unused actorColor import.
Replace the full SessionObserve embed (turn-by-turn trace + 4 sidebar
panels + playback scrubber) with a preview-scaled body that fits the
"see, then jump in" model:

- Micro-stats strip at the top: TURNS / TOOLS / EDITS / CONTEXT in a
  tabular 4-cell grid. Pulled from observe.metadata.usage plus event
  counts; context falls back to (totalTokens / contextWindowTokens) if
  contextUsage isn't available.
- Compact list of the last 14 events (newest first): relative time,
  kind glyph (▸ tool, · think, ? ask, ✉ msg, ↑ boot), kind label, then
  the event summary on a single clamped line. Color per kind for the
  glyph only — text stays ink so nothing reads as a warning.
- Empty state when no events yet: "No recent events yet. Activity will
  land here as the session runs."

The full SessionObserve is still one tap away:
- Open in Tail ↗ (tab-row jump-off, pre-filtered to the agent)
- Open profile ↗ (Profile tab's jump-off → agents detail page)

Dropped the SessionObserve component import from MissionControlView
(only the type is referenced now).
@arach arach merged commit 12d98ef into main May 15, 2026
2 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