Commit graph

710 commits

Author SHA1 Message Date
brooklyn!
b69c2d2fcd
Merge pull request #55456 from NousResearch/bb/desktop-split-controller
refactor(desktop): thin desktop-controller by extracting session-list actions
2026-06-30 02:19:03 -05:00
brooklyn!
116acf3821
Merge pull request #55455 from NousResearch/bb/desktop-split-composer
refactor(desktop): extract composer pure helpers into composer-utils
2026-06-30 02:18:41 -05:00
brooklyn!
61211967e1
Merge pull request #55453 from NousResearch/bb/desktop-split-sidebar
refactor(desktop): split sidebar/index.tsx god file into focused modules
2026-06-30 02:18:32 -05:00
brooklyn!
374d38f09d
Merge pull request #55451 from NousResearch/bb/desktop-split-thread
refactor(desktop): split thread.tsx god file into focused modules
2026-06-30 02:17:56 -05:00
Brooklyn Nicholson
03311abe49 fix(desktop): make ⌘K / session-switcher HUDs ignore titlebar drag band
The top-center floating HUDs (command palette + session switcher) pin at
top-3, overlapping the titlebar's `[-webkit-app-region:drag]` bands. Drag
regions win hit-testing over the DOM regardless of z-index, so the top of
each surface — the search input — swallowed clicks, leaving only a ~2px
strip focusable. Add `[-webkit-app-region:no-drag]` to the shared
HUD_SURFACE so the whole surface is interactive.
2026-06-30 02:06:30 -05:00
Brooklyn Nicholson
25c7900fb5 refactor(desktop): thin desktop-controller by extracting session-list actions
DesktopController is a route root that had grown a controller's worth of
session-list plumbing inline. Extract the cohesive fetch/paging cluster into
a focused hook and a tested pure helper, per AGENTS.md's "keep route roots
thin" guidance:

- use-session-list-actions.ts: refreshSessions / loadMoreSessions /
  loadMoreSessionsForProfile / loadMoreMessagingForPlatform / refreshCronJobs
  (plus the private cron/messaging refreshers, sessionsToKeep, and the
  excluded-source constants)
- desktop-controller-utils.ts: pure sameCronSignature helper (+ unit tests)

Pure restructuring, no behavior change. desktop-controller.tsx: 1,441 -> 1,233.
2026-06-30 01:34:12 -05:00
Brooklyn Nicholson
dd659c8d17 refactor(desktop): extract composer pure helpers into composer-utils
Pull ChatBar's module-level pure helpers, constants, and the QueueEditState
type out of the 2.3k-line composer/index.tsx into a focused, testable
composer-utils.ts sibling:

- constants: COMPOSER_STACK_BREAKPOINT_PX, COMPOSER_SINGLE_LINE_MAX_PX,
  COMPOSER_FADE_BACKGROUND, DRAFT_PERSIST_DEBOUNCE_MS
- helpers: pickPlaceholder, COMPLETION_ACTIONS, slashChipKindForItem,
  slashArgStage, slashCommandToken, cloneAttachments
- type: QueueEditState

Pure restructuring, no behavior change; adds unit tests for the slash helpers.
(The ChatBar component itself is a single tightly-coupled megacomponent; a
deeper hook-based decomposition is left for a dedicated follow-up.)
2026-06-30 01:30:52 -05:00
Brooklyn Nicholson
88e29e35bd refactor(desktop): split sidebar/index.tsx god file into focused modules
Behavior-preserving extraction of the 1,963-line ChatSidebar file into the
existing sidebar/ sibling-module convention:

- order.ts: add pure orderByIds / reconcileOrderIds / sameIds helpers (+ tests)
- reorderable-list.tsx: the generic ReorderableList + useSortableBindings DnD
  primitive
- section-states.tsx: SidebarSessionSkeletons / SidebarBlankState /
  SidebarPinnedEmptyState
- sessions-section.tsx: SidebarSectionHeader + the large SidebarSessionsSection
  renderer + its sortable row wrappers

index.tsx now holds only the ChatSidebar component (1,963 -> 1,416 lines).
2026-06-30 01:26:37 -05:00
Brooklyn Nicholson
7ff6908a59 refactor(desktop): split thread.tsx god file into focused modules
Behavior-preserving extraction of the 1,942-line thread.tsx transcript
renderer into co-located sibling modules, matching the existing flat
assistant-ui/ convention:

- thread-content.ts / thread-timestamp.ts: pure helpers (+ unit tests)
- thread-types.ts: shared RestoreMessageTarget
- thread-status.tsx: loading / stall / background-resume indicators
- thread-message-parts.tsx: reasoning + tool part components
- assistant-message.tsx, system-message.tsx, user-message.tsx,
  user-edit-composer.tsx: the message renderers

thread.tsx now holds only the Thread route component (1,942 -> 119 lines).
Also drops a dead readAloudAudio module variable (no references).
2026-06-30 01:21:55 -05:00
Brooklyn Nicholson
04639adace feat(desktop): flag already-installed themes in the install pickers
The Cmd-K "Install theme…" palette listed Marketplace themes with no hint
that you already had them, and clicking one re-downloaded + re-installed a
theme you owned. The Appearance settings grid already detected this, but by
parsing theme descriptions inline on every render — plumbing that never made
it to the palette.

Lift it into one reactive source and reuse it everywhere:
- $marketplaceInstalls (computed over $userThemes): extensionId -> installed
  theme, derived once via marketplaceIdOf and memoized, instead of rebuilding
  a Set per render.
- Both install surfaces now mark owned rows installed and, on click,
  re-activate the installed theme rather than re-fetching it.
- Drops the duplicated description-parsing in settings and the per-session
  "installed here" state in both surfaces (the store is the source of truth,
  so previously-installed themes show correctly too).
2026-06-29 23:29:20 -05:00
Brooklyn Nicholson
30cd39dc56 refactor(desktop): collapse stroll-direction coin to a single draw
DRY: the roomier-side bias computed its probability two ways
(STROLL_TOWARD_ROOM and 1 - STROLL_TOWARD_ROOM). One draw XNOR'd against
the roomier side says the same thing more plainly.
2026-06-29 22:53:38 -05:00
Brooklyn Nicholson
b7322f946d feat(desktop): calmer, more realistic pet roam + split roam modules
The floating pet wandered almost constantly: every idle beat picked a new
walk and hops fired ~45% of the time, so it read as nervous rather than
alive. Make movement the exception, not the default, and split the
overgrown roam hook into focused modules.

Behavior (per ambient game-AI: GameAIPro ch.36 + idle/wander state
machines):
- Loaf, don't pace: most decision beats just keep resting (REST_CHANCE
  0.62) instead of always re-walking.
- Memoryless dwell: pauses now draw from an exponential distribution
  (mostly short rests, the occasional long loaf) instead of a uniform
  1.8-5.2s window, so the cadence never reads as a metronome.
- Hops dialed back 0.45 -> 0.2 (the jumpiest, noisiest motion).

Structure (no god-file; a hook should own one narrow job):
- roam-behavior.ts - what to do & when (dwellMs, chooseMove,
  pickStrollTarget) + tuning. Pure, rng-injectable.
- roam-geometry.ts - where it can stand (snapshotLedges, overlayLedge,
  resolveLedge, overlapsX, groundTop). DOM measurement + pure ledge math.
- use-pet-roam.ts - the physics/RAF loop only.

Tests: deterministic, rng-seeded unit coverage for the decision + geometry
helpers (behavior contracts, not snapshots).
2026-06-29 22:51:09 -05:00
Brooklyn Nicholson
596b813c9b feat(desktop): add read-replies-aloud toggle and wire auto-speak 2026-06-29 15:22:37 -05:00
Brooklyn Nicholson
fcdc05c891 feat(desktop): add auto-speak watcher hook 2026-06-29 15:22:37 -05:00
Brooklyn Nicholson
572c7dbd93 feat(desktop): add read-replies-aloud composer strings 2026-06-29 15:22:37 -05:00
Brooklyn Nicholson
09abbf8a63 feat(desktop): mirror voice.auto_tts into an $autoSpeakReplies store 2026-06-29 15:22:37 -05:00
Brooklyn Nicholson
bff91f978f feat(desktop): type voice.auto_tts in desktop config 2026-06-29 15:22:37 -05:00
Brooklyn Nicholson
a1e699ae55 feat(desktop): roaming pet patrols the base of an open overlay
When a full-screen route overlay (settings/profiles/cron/agents/command-center) is up, the pet's walkable surface swaps to a single ledge at the overlay card's bottom edge — derived from OverlayView's shared inset, not measured — so it patrols there; closing the overlay restores the normal surfaces and it drops back down.
2026-06-29 14:57:26 -05:00
Brooklyn Nicholson
0e2a5a3206 feat(desktop): ground the roaming pet — sprite-paced walk + feet on surface
Walk speed is derived from the sprite's animation loop + on-screen size (one body-width per loop) instead of a fixed px/s, so it steps rather than glides; the pet also sinks a few px so its feet meet the surface instead of hovering.
2026-06-29 14:47:37 -05:00
Brooklyn Nicholson
b72c9e1b2c feat(desktop): add pet roam opt-in toggle + i18n 2026-06-29 14:26:02 -05:00
Brooklyn Nicholson
4da744ef9b feat(desktop): let the pet perch on the status bar and profile rail
Tag both bars with data-slots; the roam loop stands on the status bar's top edge (not over it) and treats the profile rail as a climbable ledge.
2026-06-29 14:26:02 -05:00
Brooklyn Nicholson
7d3c1d55f4 feat(desktop): wire roaming into the floating pet 2026-06-29 14:26:02 -05:00
Brooklyn Nicholson
a8f1d9cc76 feat(desktop): add surface-aware pet wander loop
usePetRoam re-measures ledges from the live DOM each beat and walks/hops/falls between them, driving DOM position imperatively (no per-frame re-render).
2026-06-29 14:26:02 -05:00
Brooklyn Nicholson
964ec680cc feat(desktop): pick directional run row from travel direction
roamWalkRow() prefers running-left/running-right rows, falling back to the generic running row with a mirror for pets that lack them.
2026-06-29 14:26:02 -05:00
Brooklyn Nicholson
c6d6a1c30d feat(desktop): add pet roam + motion/direction store signals
Opt-in $petRoam (localStorage), $petMotion (run/jump pose) and $petRoamDir (-1/0/1) feed the shared $petState only while the agent is at rest ($petAtRest), so a wander never overrides real activity.
2026-06-29 14:26:02 -05:00
Brooklyn Nicholson
7a6b3cb923 fix(desktop): show Gateway statusbar tooltip via composed trigger Slots
The Gateway item is the only statusbar entry with variant === 'menu'.
Since da73223f4 wrapped every render branch in `Tip`, the menu branch
nested `<DropdownMenu>` (a Radix Root that renders no DOM node) inside
`Tip`'s `<TooltipTrigger asChild>`. With no element to attach to, Radix
could never wire hover listeners, so the tooltip silently never showed.

`Tip` also can't be moved inside `DropdownMenuTrigger asChild` (the shape
proposed in #54859): it's a plain component, not a Slot-forwarding one, so
the trigger's injected ref/handlers would land on `TooltipContent` instead
of the button and break the menu's click + popper anchoring.

Fix by composing both trigger Slots directly onto a single <button>
(`TooltipTrigger asChild` over `DropdownMenuTrigger asChild`), the pattern
already used in profile-switcher.tsx, and skip the tooltip wrapper entirely
when the item has no title.

Supersedes #54859.

Co-authored-by: wnuuee1 <wnuuee1@users.noreply.github.com>
2026-06-29 13:48:56 -05:00
brooklyn!
929dd9c0d7
Merge pull request #55033 from NousResearch/bb/subagent-watch-readonly
feat(desktop): read-only spectator transcript for subagent watch windows
2026-06-29 12:09:53 -05:00
Brooklyn Nicholson
7cf6758e33 feat(desktop): read-only spectator transcript for subagent watch windows
Subagent session pop-outs (`watch=1`) spectate a run driven elsewhere, so
editing/steering the transcript from there makes no sense. Gate the composer
and the user-bubble mutations on `isWatchWindow()`:

- hide the composer (folds into `showChatBar`)
- user prompts become a read-only button that toggles the 2-line clamp so long
  prompts stay fully readable, instead of opening the edit composer
- drop the stop/restore actions and the checkpoint branch-picker

Keyed off the narrow `isWatchWindow()` (not `isSecondaryWindow()`), so the
new-session and cmd-click pop-outs are unaffected.
2026-06-29 12:06:25 -05:00
Austin Pickett
fd324562d3 feat(desktop): add context usage breakdown popover
Let users click the status bar context indicator to see how tokens are
split across system prompt, tools, rules, skills, MCP, and conversation.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-29 09:18:10 -04:00
teknium1
f860492842 test(desktop): match multiline spawn(ps, fullArgs) via regex like sibling sites
The bootstrap-runner PowerShell spawn is formatted multiline (spawn(\n  ps,\n  fullArgs,...), so the literal substring 'spawn(ps, fullArgs' never matched and the assertion was failing on main independent of #54635. Convert it to a whitespace-tolerant regex like every other call-site assertion in this file.
2026-06-28 23:59:32 -07:00
emozilla
aa2ae36c3f fix(desktop): launch Windows backend as console python so child consoles are inherited, not flashed
The recurring Windows desktop console-flash bug (#54220) is governed by the
*parent's* console, not by each child spawn. The desktop backend was launched as
GUI-subsystem pythonw.exe, which has no console at all — so every
console-subsystem child it spawns (git, gh, cmd, wmic, powershell, ...) had to
allocate its own console, flashing a window. That is why the fix had become an
endless per-call-site sweep of CREATE_NO_WINDOW flags: each leaf spawn was
papering over a missing console on the root.

Launch the backend as the venv's console python.exe instead. Under the existing
hiddenWindowsChildOptions() wrapper (windowsHide: true -> CREATE_NO_WINDOW) the
backend owns a single *windowless* console, and every descendant spawn inherits
it instead of allocating a visible one. This makes "no flashing windows" a
property of the one backend launch rather than a flag that must be remembered at
every spawn site — including spawns inside third-party libraries that no
call-site sweep can reach.

Verified on Windows 11 25H2 (Windows Terminal default): with the per-site hide
flag forcibly neutered, the canonical culprits (git/gh/cmd/wmic/powershell)
spawned naively and none flashed, while the same naive spawn from the old
console-less pythonw parent did flash — isolating the parent console as the cause.

Two premises behind the old pythonw approach did not hold up on current Windows
and are dropped here:
- The venv Scripts\python.exe uv shim, under CREATE_NO_WINDOW, re-execs base
  python *windowless* — it does not flash a conhost (the #52239 concern), so the
  base-pythonw detour is unnecessary.
- Console python restores stdout, so the backend announces its port on the normal
  HERMES_DASHBOARD_READY stdout line; the pythonw-only ready-file side channel is
  no longer needed and the readyFile opt-in is removed.

Removes the now-dead pythonw machinery (getNoConsoleVenvPython, toNoConsolePython,
applyWindowsNoConsoleSpawnHints, readVenvHome) and updates the test to assert the
new invariant: backend command is never pythonw, both backend spawns still go
through hiddenWindowsChildOptions, and no backend opts into the ready-file path.

Scope: this fixes the high-frequency backend-descendant flash classes. The
updater/UAC handoff (#54543) and embedded-terminal PTY accumulation (#53555)
classes have separate root causes and are unaffected.
2026-06-28 23:59:32 -07:00
brooklyn!
388268ecde
Merge pull request #54568 from NousResearch/bb/shared-websocket-layer
refactor(desktop+dashboard): shared WebSocket layer + decouple desktop from dashboard (hermes serve)
2026-06-28 23:43:49 -05:00
Brooklyn Nicholson
1c0fa12edb feat(desktop): persist & restore terminal tabs + scrollback across relaunch
User terminal tabs and their recent scrollback now survive an app restart
(VS Code parity). Tabs, active selection, cwd, and a serialized scrollback
snapshot are written to localStorage on every change; on launch the tabs
reopen with their history replayed above a fresh shell. Processes are NOT
revived — a new shell starts one line below the restored block.

- Capture: SerializeAddon snapshots the buffer on a 750ms leading-edge
  throttle, so a `cmd; quit` lands on disk before teardown; the snapshot is
  trimmed of its trailing idle prompt (no "double prompt" on restore) and
  capped (200 scrollback lines / 48k chars) to stay under the storage budget.
- Teardown guard: app quit/reload kills the PTYs from the main process,
  firing onExit in the renderer, but React skips effect cleanups on teardown
  so the per-instance `disposed` flag never flips. A pagehide/beforeunload
  flag stops onExit from calling closeTerminal() and wiping the persisted
  tabs right before relaunch restores them. A real `exit`/Ctrl-D still closes.
- Agent mirror tabs stay runtime-only — only user tabs persist.
2026-06-28 22:12:29 -05:00
Brooklyn Nicholson
e684b808ad fix(desktop): route old runtimes through dashboard when serve is absent
`hermes serve` is newer than the desktop binary's release cadence, so a new
app launched against an un-upgraded managed install / PATH `hermes` would
crash on an unknown subcommand and brick the user mid-upgrade. Detect whether
the resolved runtime registers `serve` (fast source read of its dashboard.py,
with a one-time CLI probe fallback) and rewrite the backend argv to the legacy
`dashboard --no-open` only when it does not. Happy path (current runtimes)
pays nothing and still spawns `serve`.

- electron/backend-command.cjs: pure serve/dashboard argv helpers + serve-
  source detection (unit-tested in backend-command.test.cjs)
- main.cjs: backendSupportsServe() cache + getBackendArgsForRuntime() guard at
  both backend spawn sites; expose `root` from the Windows venv unwrap so the
  fast source check covers Windows too
- docs: note the backward-compat fallback in README, desktop.md, AGENTS.md
2026-06-28 22:10:42 -05:00
Brooklyn Nicholson
dff491a2b9 feat(cli): add headless hermes serve backend; desktop no longer launches dashboard
The desktop app spawned `hermes dashboard --no-open` as its backend, which
made the dashboard look like a desktop prerequisite. Add a dedicated headless
`hermes serve` command that boots the same gateway (shared cmd_dashboard /
start_server) but never opens a browser, and point the desktop backend spawn
exclusively at it. dashboard and serve are now independent surfaces — neither
launches the other.

- subcommands/dashboard.py: factor shared server args; add `serve` parser
  (always headless; accepts legacy --no-open as a no-op)
- main.py: register serve in _BUILTIN_SUBCOMMANDS + coalesce set + gui-log
  detection; extend stale-backend reaper patterns to match `serve`
- desktop electron: spawn `serve`, rename dashboardArgs -> backendArgs,
  update comments + windows-child-process test assertions
- docs: desktop README, desktop.md (incl. remote-backend), AGENTS.md, and
  cli-commands.md now describe `hermes serve` as the desktop/headless backend
2026-06-28 22:04:22 -05:00
Brooklyn Nicholson
f019a999d8 docs: clarify desktop is self-contained, not dependent on the dashboard
The desktop app spawns a headless `hermes dashboard --no-open` backend and
talks to it through the shared @hermes/shared WebSocket client — it never
runs or requires the browser dashboard UI. Spell this out in the desktop
README, the desktop docs page, and AGENTS.md so "dashboard" stops reading
as a desktop prerequisite.
2026-06-28 21:50:33 -05:00
Brooklyn Nicholson
ae465e9fb8 Merge branch 'main' of github.com:NousResearch/hermes-agent into bb/desktop-multiterminal 2026-06-28 21:37:52 -05:00
Brooklyn Nicholson
1a1e00f37e fix(desktop): stop injecting ctrl-l into terminal startup
Remove the prompt-gap cleanup that sent Ctrl-L into the user's shell; it could
render as literal ^L and create the exact top-line gap it was meant to hide.
Keep first-prompt cleanup renderer-side only, and parse short ESC charset
sequences so the initial newline stripper does not disarm early.

Also add a Close all action to the terminal tab context menu.
2026-06-28 21:33:20 -05:00
Brooklyn Nicholson
216ace4bf3 style(shared): apply workspace formatter to websocket helpers
Run the package-appropriate Prettier config on the shared WebSocket files so
the extracted helpers match the surrounding desktop/shared TypeScript style.
2026-06-28 21:30:43 -05:00
Brooklyn Nicholson
5a2906a11b chore(desktop): keep the diff surgical
Revert the repo-wide prettier churn the earlier fmt pass pulled into files
unrelated to this work; run prettier/eslint scoped to the touched files only.
2026-06-28 21:30:14 -05:00
Brooklyn Nicholson
6776b2f9b5 feat(desktop): live gateway popout + statusbar/command-center polish
- Gateway status popout: flatten the header to stacked connection + inference
  statuses with system-panel and restart actions (reusing the shared
  runGatewayRestart helper). The recent-activity tail is now live while the
  popout is open via the shared LogView (WS connection churn filtered), and the
  icon / "View all logs" link dismiss the popover.
- Statusbar "menu" items accept a menuContent(close) render fn over a now
  controlled DropdownMenu, so popover content can close itself.
- Drop the always-on gateway-log poll from useStatusSnapshot (logs are fetched
  by the popout only while open).
- SearchField → text-xs to match Input/Select (controlVariants).
- Command center: remove the usage/system section dividers, swap the sessions
  nav icon (Pin → MessageCircle), small padding tweaks.
2026-06-28 21:26:15 -05:00
Brooklyn Nicholson
5a4bdfda50 fix(shared): close websocket clients deterministically
Ensure intentional client closes mark the transport closed and reject pending
RPCs immediately instead of relying on a browser close event that can be
ignored after the socket reference is cleared.
2026-06-28 21:25:12 -05:00
Brooklyn Nicholson
6c52e4a318 fix(desktop): match agent terminal scrollback to user tabs
Keep read-only agent terminal tabs visually and behaviorally aligned with normal
terminal tabs by using the same 1,000-line scrollback cap.
2026-06-28 21:22:17 -05:00
Brooklyn Nicholson
dfb561a3ae refactor(desktop+dashboard): extract shared WebSocket/JSON-RPC layer
The Electron desktop app and the web dashboard each carried their own
copy of the tui_gateway JSON-RPC WebSocket client plus near-identical
auth'd WS-URL construction. The dashboard's copy was the historical
source of the "is the dashboard required to run the desktop app?"
confusion, since the two surfaces looked coupled.

Consolidate the genuinely shared transport into the existing
framework-agnostic `@hermes/shared` package so both surfaces consume it
independently — neither app depends on the other:

- Move `resolveGatewayWsUrl` + `GatewayReauthRequiredError` (single-use
  OAuth ticket re-mint vs long-lived token fallback) into
  `@hermes/shared`; desktop now imports them directly.
- Add `buildHermesWebSocketUrl`, one base-path/scheme/auth-aware URL
  builder, and route every dashboard WS endpoint through it
  (`/api/ws`, `/api/events`, `/api/pty`, plugin WS URLs).
- Reduce the dashboard `GatewayClient` to a thin subclass of the shared
  `JsonRpcGatewayClient`, deleting ~210 lines of duplicated pending-call
  /event-dispatch/connect plumbing while keeping its dashboard-specific
  ticket-vs-token auth selection.
- Drop the stale "start it with --tui" chat banner, which implied the
  dashboard flag was required.

Behavior is preserved on both surfaces; the dashboard additionally
inherits the shared client's 15s connect timeout (previously
desktop-only), so a hung connect now fails fast instead of pinning the
composer in "connecting".
2026-06-28 21:20:35 -05:00
Brooklyn Nicholson
adacb16d62 fix(desktop): make agent terminal tabs fully readable
Register read-only agent terminals with the same renderer-side terminal reader
as user terminals so read_terminal works on whichever tab is active.

Also bring agent xterm rendering closer to user-terminal parity (unicode 11,
web links, font weights/spacing) and make the gateway sink wiring resilient if
only one terminal event sink was already installed.
2026-06-28 21:18:49 -05:00
Brooklyn Nicholson
e117cfdff0 feat(desktop): live agent terminals + agent-driven tab close
Make the read-only agent terminal mirrors stream in real time and give
the agent a desktop-only way to dismiss its own tabs.

- Stream background output live: the local reader used a blocking
  read(4096) that buffered small periodic output until EOF, so agent
  tabs only "filled in" at process exit. Switch to buffer.read1(4096)
  (decoded) for incremental chunks.
- Route agent.terminal.output / terminal.close to the window that owns
  the process (its gateway session) instead of an empty session id, so
  events actually reach the desktop renderer.
- Add close_terminal: a HERMES_DESKTOP-gated tool (sibling of
  read_terminal) that drops a process's read-only tab WITHOUT killing it
  via process_registry.on_close; output keeps buffering and the user can
  reopen from the status stack.
- ⌘W now closes a focused agent tab: mark the agent instance
  data-terminal and focus it on activation so isFocusWithin routes there.
- ensureTerminal() no longer spawns an extra user shell when a tab
  already exists (e.g. opening a background task from the status stack).
2026-06-28 21:15:14 -05:00
Brooklyn Nicholson
9f02eea1d2 style(desktop): prettier + eslint pass
Repo-wide `npm run fmt` + `eslint --fix`; also drop two unused destructured
params in titlebar-overlay-width.cjs so the lint run is clean.
2026-06-28 21:04:43 -05:00
Brooklyn Nicholson
317b94871b chore(desktop): drop dead overlay primitives
Remove zero-consumer overlay code surfaced while auditing the primitive set:
OverlayNewButton (orphaned once "New" moved into PanelAddButton), OverlayCard /
overlayCardClass, and the unused overlay-search-input module. Leaves three
intentional layers: OverlayView (base), Panel (master/detail), and
OverlaySplitLayout (settings/command-center nav→content).
2026-06-28 21:03:04 -05:00
Brooklyn Nicholson
991220747f feat(desktop): unify non-settings overlays under a shared Panel primitive
Extract the agents/trace overlay chrome into overlays/panel.tsx and adopt it
across the Cron, Profiles, and Agents overlays so they share one layout
(centered card, header, master/detail list with built-in search, kebab row
actions, big "+" footer, empty state) instead of three ad-hoc split layouts.

Also in this pass:
- OverlayView insets equidistantly on every side (was top/left-only, which
  left a large left gutter on narrow windows).
- Form-control chrome: input border/background/recessed-inset are now
  per-mode theme-var knobs (--dt-input-border/-bg/-inset) — resting borders
  blend in, strengthen on hover, and go solid on focus / while a Select is open.
- Thread-timeline popover reuses the shared dropdown surface (1:1 with the
  kebab menus) and scrolls the hovered prompt into view.
2026-06-28 20:56:52 -05:00
Brooklyn Nicholson
5d661a3ad7 fix(desktop): show the agent command before terminal output arrives
Seed read-only agent terminal tabs with the background command immediately, so
they never open as a blank pane while stdout is pending or a live stream races
startup. Snapshot fallback now preserves that command header and appends only
missing output without duplicating live chunks.
2026-06-28 19:39:20 -05:00