Skip to content

Multi-machine project UX: remote assets, logical pickers, machine filter, unified selection styling - #159

Merged
badcuban merged 13 commits into
mainfrom
feature/multi-machine-ux
Aug 15, 2026
Merged

Multi-machine project UX: remote assets, logical pickers, machine filter, unified selection styling#159
badcuban merged 13 commits into
mainfrom
feature/multi-machine-ux

Conversation

@badcuban

Copy link
Copy Markdown
Collaborator

Improves how the app behaves when a second computer is connected, and unifies the selection styling of every value-picking menu along the way.

Remote machines

  • Remote icons and images fixed. Saved computers authenticate over the WebSocket, so browser img/fetch requests against their HTTP routes came back 401 — broken project icons and chat image previews for every remote machine. All asset bytes now travel over the authenticated RPC for saved environments (environmentRequiresRpcAssetTransport), and ProjectFavicon is RPC-only for every environment, which also lets it distinguish "no icon" from a real one.
  • Monogram fallback icons. Projects with no icon file get a deterministic tinted first-letter tile (hue hashed from the checkout path) instead of the shared grey folder; the neutral folder holds while the fetch is pending.
  • Logical Switch-project menu. The composer's project picker lists one row per repository across machines (shared useSidebarProjectSnapshots hook, same grouping as the sidebar) instead of duplicate physical entries. Once any remote machine is connected, every row declares where it lives with compact glyphs — monitor (this device), cloud (remote, with a mono count when on several), or both — with instant tooltips naming the machines.
  • Machine filter. The sidebar scope menu grows a Machine section (This device / each saved computer) that composes with the project scope as an intersection: the project list narrows to the scoped machine, counts follow, and a machine-only scope shows the machine's own glyph and name in the trigger.
  • Machine identity everywhere else: thread rows keep the cloud glyph only (name in tooltip and hover card), and the hover card uses the same monitor/cloud pair as every other surface.
  • Agents panel: a hand-run terminal command no longer masquerades as an agent — the panel and its live-count indicators count only agent-initiated work; the terminal strip and the header activity popover keep showing the user's own shells.

Menu selection design system

Value-picking menus (Menu radio items, Selects, comboboxes, the model picker) now share one selection treatment: rows rest muted, the chosen row carries a faint --pick-selected fill inside a hairline inset ring with full-strength text, hover is the stronger borderless wash. Check marks and their indicator columns are gone (the trigger already names the value); the model picker keeps its primary-tinted name as its extra mark.

Notes

  • Fixes a crash: the Machine section's group label rendered outside a MenuGroup, which threw the moment a second machine was known.
  • Tested against a simulated second machine (throwaway server + pairing); verified fmt/lint/typecheck, 2201 unit and 471 browser tests on the merged tree.

- Fetch icons and attachment bytes over the authenticated WebSocket RPC
  for every saved environment, not just relay-paired ones: a saved
  computer's HTTP routes are cross-origin and a browser img/fetch cannot
  carry the WebSocket credential, so remote icons and chat images 401'd.
- ProjectFavicon is RPC-only for all environments and gains a monogram
  fallback (tinted first letter, hue hashed from the checkout path) for
  projects with no icon file; the neutral folder holds while the fetch
  is pending.
- The composer's Switch project menu lists logical projects via the new
  useSidebarProjectSnapshots hook: one row per repository across
  machines, with a machine label on remote-only entries.
- Thread rows (live and wrapped) show the machine name beside the cloud
  icon once the visible list spans more than one machine.
- The sidebar scope menu grows a Machine section (persisted in
  uiStateStore) filtering threads, drafts, wrapped entries, and counts.
A hand-run command in a thread terminal (vp run dev:desktop) drew a
branch in the Agents tab and inflated the closed-panel live count, so a
dev server read as an agent. The panel and the indicators that
advertise it now count only agent-initiated work: provider-managed runs
and detected agent processes. The user's terminals keep their surfaces
— the terminal strip and the header's activity popover, which still
receives the unfiltered run list.
The Machine section header rendered outside a MenuGroup, and Base UI's
group parts throw without that context — so the whole scope dropdown
crashed the moment a second machine was known, which is exactly when
the section exists. Covered by a browser test that opens the menu with
two machines, since the context check only fires at render.
- A machine filter narrows the scope menu's project list to projects
  with a checkout on that machine, and the project scope lapses to All
  projects when its project has no presence there — the two scopes
  intersect instead of crossing into an empty list.
- Thread rows keep only the cloud glyph for remote threads; the machine
  name lives in the tooltip and hover card. Drops the span-aware label
  plumbing.
- The hover card's machine row uses the same glyph pair as every other
  machine surface: monitor for this device, cloud for a remote machine
  (was laptop/server).
"All projects · <machine>" truncated at sidebar widths to the half
that said nothing. A machine-only scope now puts the machine's glyph in
the trigger's icon slot and its name as the whole text; "All projects"
is reserved for the unfiltered default, and a project scope keeps its
" · machine" suffix.
The machine's name truncated to nothing at the menu's row width. A
remote-only project now carries the cloud glyph, a repo on both
machines carries monitor+cloud, and local-only rows stay unmarked —
the same vocabulary as the thread rows and the scope trigger, with the
machine names one hover away in the title.
- MenuRadioItem defaults to the fill variant, now with the text half:
  rows rest muted, the selected row carries a neutral fill and
  full-strength text, hover is the stronger fill. The check variant's
  indicator column cost width and misaligned rows (the Run on menu
  indented its selected row past its siblings). Exported as
  MENU_PICK_ITEM_* so MenuItem-based pickers (scope menu, Switch
  project) draw from the same definition; their check glyphs are gone.
- Switch project presence glyphs: once any remote machine is connected
  every row declares where it lives — monitor, cloud, or both, with a
  mono count when a project spans several remotes. Unmarked rows are
  ambiguous only when machines can differ, so with no remote connected
  no glyph renders at all.
The desktop device and checkout pickers are Selects, not menus, so they
kept their leading check and its reserved gutter. SelectItem now
renders indicator-less with the house treatment (muted rows, neutral
fill + full text when selected, stronger fill on hover); hideIndicator
is gone along with its call sites — the combobox keeps its own prop
for the ref search.

The model picker keeps its primary-tinted name as the selection mark
per design, drops the inline check, and gains the same resting fill.
Selected and hover were two alphas of the same white wash, near
indistinguishable at menu size. New --pick-selected token: on dark it
darkens the row (recessed, chosen) while hover lightens — different
directions, not different strengths; on light it stays a step apart
from hover with the full-strength text carrying the rest. All four
picker surfaces (menu fill items, MenuRadioItem, SelectItem, model
rows) consume the one token.
The recessed dark fill read too heavy. The chosen row now differs from
hover in structure instead of shade: a faint wash with a --border inset
hairline against the borderless hover fill. Token values drop to 3%/4%.
…nstant

ComboboxItem now carries the house selection treatment (faint
pick-selected fill + hairline inset ring + full text), so the ref
search's current row matches every other picker; the model row drops
its now-redundant copy. The Switch project presence glyphs swap the
native title for a zero-delay TooltipWrapper — the glyphs are the only
thing naming the machines, so the hover dwell read as unlabelled.
@vercel

vercel Bot commented Aug 15, 2026

Copy link
Copy Markdown

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

1 Skipped Deployment
Project Deployment Actions Updated (UTC)
threadlines Skipped Skipped Aug 15, 2026 6:49am

Request Review

@github-actions github-actions Bot added vouch:trusted PR author is trusted by repo permissions or the VOUCHED list. size:XL labels Aug 15, 2026
Local typecheck replayed a stale cache and let the incomplete
RepositoryIdentity fixture through; CI's cold run caught it.
@badcuban
badcuban merged commit ecbb68d into main Aug 15, 2026
15 checks passed
@badcuban
badcuban deleted the feature/multi-machine-ux branch August 15, 2026 08:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size:XL vouch:trusted PR author is trusted by repo permissions or the VOUCHED list.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant