* rename Activity tab label to Agent View
Renames the user-visible tab label from 'Activity' to 'Agent View' in
the sidebar nav and the activity-page titlebar (plus matching tooltip
and aria-label). Internal view ID, store slice names, and component
filenames remain 'activity' to avoid behavioral risk — this is a pure
copy change.
* rename activity view labels to agents
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* Drop the per-workspace AGENTS toggle in the sidebar
The chevron header above each card's inline agent list re-litigated a
decision the global "inline-agents" view option already controls, and
sat one row above the agent it labelled — so aiming for the agent and
hitting the chevron collapsed the section the user wanted to interact
with. Collapsed state was session-only, so there was no payoff for the
misclick.
Always render the inline agent list when the global toggle is on; never
render it when it's off. The wrapper keeps a stopPropagation handler so
clicks on the empty row gutter don't reach WorktreeCard's outer
activate/edit-meta handlers, plus role="group" + aria-label="Agents" so
screen-reader users still get a labeled region.
Also drops the leading pl-1 on DashboardAgentRow so the freed row gets
a touch more horizontal room. DashboardAgentRow's only call site is
this inline list, so there's no asymmetry to fix elsewhere.
* Added more tests
---------
Co-authored-by: Jinjing <6427696+AmethystLiang@users.noreply.github.com>
* fix(activity): polish thread-row hover, restore stable layout, widen list
The activity thread list had several hover regressions: the cursor wasn't
indicating clickability, hovering shifted the row contents because the
bell-toggle replaced the timestamp under the cursor, and the timestamp
tooltip could get stuck open after the trigger faded out (Radix didn't
dismiss because opacity:0 left it interactable). The Jump-to-workspace
button in the right pane header also read as easy-to-miss chrome.
Surface per-card actions on hover (Open + Mark-unread) in a reserved
slot so the worktree-name's flex width doesn't reflow when the buttons
appear. Drop the redundant header button. Bump the default thread list
width 340 → 480px (range 320–720) and let prompts wrap to 3 lines so
the cards work as the primary surface — the terminal is supplementary.
Mark-unread only appears for already-read threads (selecting an unread
thread auto-marks it read, so a "mark read" button would be redundant).
The remove-events-count badge change was already staged locally and is
included to keep the row chrome consistent with the new layout.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): replace 'Open' label with ExternalLink icon-only button
Co-authored-by: Orca <help@stably.ai>
* fix(activity): move Mark-unread into the bell slot on hover
Co-authored-by: Orca <help@stably.ai>
* fix(activity): match WorktreeCard bell pattern in thread rows
Co-authored-by: Orca <help@stably.ai>
* fix(activity): drop AGENTS heading, add overflow menu with Mark all read
The 'AGENTS' uppercase header was redundant — the activity titlebar
already names the surface. The 'Mark all read' button was also stuck in
the titlebar far from the threads it acts on.
Drop the heading and surface 'Mark all read' as a per-list overflow menu
(MoreVertical) next to the Filter input + unread-only toggle. The action
disables when there's nothing unread to mark.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(tasks): remove duplicate top band on Tasks page
The Activity-page work in #1703 tightened App.tsx's `workspaceActive` to
require `activeView === 'terminal'`, so App.tsx now keeps its full-width
titlebar on the Tasks page. TaskPage's own `workspaceActive` was still
just `activeWorktreeId !== null`, so it kept rendering its own 36px
placeholder strip below the App titlebar — producing the doubled band.
TaskPage only mounts when `activeView === 'tasks'`, where App.tsx never
hides its titlebar, so the placeholder strip is always redundant. Drop
it along with the now-unused `sidebarOpen` / `activeWorktreeId` reads.
Co-authored-by: Orca <help@stably.ai>
* fix(tasks): remove stale titlebar comment
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
Clear initializedHandlesRef on reconnect so the auto-resubscribed scrollback frame re-inits xterm instead of being dropped by the has(handle) guard.
Co-authored-by: orca-bug-scan-bot <orca-bug-scan-bot@stably.ai>
* feat(activity): isolate the agent's pane in the activity terminal view
When the activity page hosts a tab that has split panes, only the agent's
specific pane should be visible — not the whole tab. Implemented as a
transient layout override using the existing applyExpandedLayoutTo helper
with a separate snapshot ref, so it never touches the user-facing
expanded-pane state or the persisted layout snapshot. Closing the
activity page restores the original split layout untouched.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): pass isolatedPaneId from the live tab-group render path
TabGroupPanel renders the actual TerminalPane in tab-group mode (the
default modern path); Terminal.tsx is the legacy single-group fallback.
Without this, isolatedPaneId never reached TerminalPane, so the activity
view kept showing all split panes side-by-side.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): refit terminals when isolation override is removed
The activity-isolation useLayoutEffect only called safeFit on the apply
path, so removing isolation (or hitting the early-return when
applyExpandedLayoutTo returned false) left xterm sized for the
single-pane geometry until an unrelated event triggered another fit.
Extract scheduleRefit() and run it from all three branches.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* Move mobile settings to beta section
Co-authored-by: Orca <help@stably.ai>
* Add mobile setup guidance
Co-authored-by: Orca <help@stably.ai>
* Use global App Store URL for mobile
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(activity): shrink thread-row title and stop branch-name truncation
Move the time + count + unread cluster up onto the title row so the
secondary row is full-width for the repo badge and branch name (long
branch names were being clipped by the right cluster). Also drop the
title from 13px to 11px and the branch metadata to match.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): swap "New" badge for BellDot icon on unread thread rows
The bell-with-dot already represents unread elsewhere (filter toggle,
thread-row hover swap), so the text badge was redundant.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): cluster unread BellDot in the top-right with timestamp
Match the position of the existing hover-bell so unread cues live in the
row's time/action column instead of inline with the title text.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): fill the unread bell and merge it into the timestamp slot
The static BellDot now lives in the same right-most slot as the timestamp,
so on hover the slot's contents (bell + time) fade out and only the
hover-toggle button fades in — no more double-bell on hover. Also
fill="currentColor" so the unread bell reads as a solid indicator.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* perf(runtime): compare mobile session sync key by reference instead of stringifying large maps
`getRuntimeMobileSessionSyncKey` used to `JSON.stringify` `terminalLayoutsByTabId` and `runtimePaneTitlesByTabId` whole, which scaled with the lifetime accumulation of tabs in a workspace and pinned the main thread for ~750ms per click in workspaces with hundreds of tabs while an agent was working. The maps reallocate on real changes and stay reference-stable otherwise, so the key now holds them by reference and only pre-serializes the small projected shapes that need value-level comparison. Subscriber gate also early-returns when those two maps are reference-stable, so the common click path (`updateTabTitle` reallocating `tabsByWorktree`) skips the key build entirely.
See docs/agent-working-pane-typing-lag.md for the trace and root-cause writeup.
Co-authored-by: Orca <help@stably.ai>
* docs: correct misleading comment on the relevant-fields gate
The earlier comment claimed adding terminalLayoutsByTabId /
runtimePaneTitlesByTabId to the gate prevented updateTabTitle from
falling through. That's wrong — updateTabTitle reallocates
tabsByWorktree, which is a separate field already in the gate and
already fails the AND-chain. The two added fields actually catch a
different path: layout-only mutations like setTabLayout that don't
touch any other gate field. Comment now describes the real intent:
the gate is a strict superset of every input to
getRuntimeMobileSessionSyncKey, so passing it guarantees the key is
unchanged without materializing one.
Doc updated to match.
Co-authored-by: Orca <help@stably.ai>
* chore: drop perf writeup from branch (kept in commit history)
Co-authored-by: Orca <help@stably.ai>
* test(runtime): pin down by-reference invariant of mobile sync key comparator
Reworks the existing reference-stable test to use two distinct AppState instances sharing every comparator-checked map, and adds a negative test that detects a regression to deep equality on terminalLayoutsByTabId. Extracts a makeSharedOverrides() helper so tests can isolate a single field without makeState's fresh-default churn defeating the assertion.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>
* fix(activity): align unread dot with row icon
Anchor the unread mini-dot to the icon's own bounding box (relative
inline-flex with -top-1 -left-1) instead of the 24x24 cell. The previous
absolute positioning was relative to the cell, so the dot landed visibly
off-center against the centered AgentStateDot/Plus glyph. Adds a
ring-2 ring-background halo for contrast against the row's tinted unread
background. Applies to both AgentEventRow and WorktreeEventRow.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): drop top padding so content sits flush with titlebar
Outer wrapper used py-3, plus the right-pane <section> had pt-2 and the
thread-list header had py-2 — the stacked top whitespace produced a
visible gap above the first row against the titlebar. The
ActivityTitlebarControls bar already provides the breathing-room band.
Drop the wrapper's top padding (py-3 → pb-3), drop the right-pane
section's pt-2, and tighten the thread-list header to pt-1.5 pb-2 so the
WORKTREES label aligns with the right-pane worktree title row.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): make event row the click target for navigate + ack
The inline "Jump to agent" button only rendered when agentAlive was
true, so retained-done rows had no jump affordance, and in compact mode
it was hover-only. Users naturally click anywhere on the row expecting
it to navigate.
Move the navigate-and-ack logic onto the row's onClick (with role,
tabIndex, and Enter/Space keyboard support) and drop the inline buttons.
Per the design doc (option 1), drop the agentAlive gate entirely:
activateAndRevealWorktree is safe unconditionally and
activateTabAndFocusPane silently no-ops on a missing tab id, so a
stale-tab click is a soft no-op.
Mirror the same pattern on WorktreeEventRow so clicking a "Worktree
created" row navigates to that worktree.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): bell glyph for unread + larger row state icon
The unread mini-dot stacked on the AgentStateDot competed with the dot
itself for the eye and read like a status badge on the agent state.
Replace it with a small BellDot glyph at the icon's top-right corner so
the unread cue rhymes with the bell button on ThreadRow — one unread
vocabulary across both surfaces.
While here, bump the row state icon to a new 'lg' AgentStateDot size
(18px) so the green check sits center-of-mass in its 32px column instead
of floating small and high. Bump the WorktreeEventRow Plus glyph to
match.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): drop left padding so thread list reaches the window edge
The page wrapper used px-4, which left a 16px gap to the left of the
thread list — visually inconsistent with how sidebars abut the window
chrome elsewhere. Switch to pr-4 (keep right padding for the right
pane). Inner thread-list and right-pane padding remain unchanged.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): use left-edge bar for unread instead of an icon overlay
Mini-dot and bell-glyph attempts both crowded the AgentStateDot/Plus
icon column. Switch to the same left-edge primary bar that ThreadRow
already uses for unread — a row-level cue keeps the icon column clean
and unifies the unread vocabulary across both panes.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): page extends to both edges, restore right-pane top padding, rename to "workspace"
- Drop horizontal padding on the page wrapper (pr-4 → none) so the
thread list reaches the left edge and the right pane reaches the
right edge — matches how sidebars abut the chrome elsewhere.
- Restore a small top padding (pt-2) on the right-pane title row so
the workspace heading isn't pinned to the titlebar. Earlier Fix 2
over-corrected by removing it entirely.
- Rename user-facing "Worktree(s)" → "Workspace(s)" (column label,
event title "Worktree created", description copy, "Jump to
worktree" button, empty state). Internal identifiers stay as
worktree* since that's the data-model name.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): remove redundant "Jump to workspace" header button
Clicking any event row already calls markThreadRead +
activateAndRevealWorktree (plus a tab focus, so it's a strict superset
of what the header button did). The button's only unique behavior was
"go to the workspace without picking a tab" — thin justification when
the latest event is one row down. cursor-pointer + hover already signal
that rows are clickable.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): wrap agent summary instead of truncating
The summary line used 'truncate' with a max-width, hiding the rest of
the agent's message behind an ellipsis. The Activity page is meant to
let users scan what each agent said without leaving the surface, so
truncation defeats its purpose. Switch to break-words +
whitespace-pre-wrap so the full message renders and multi-line output
keeps its line breaks.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): clamp agent summary to 3 lines
Showing the full message made tall rows for chatty agents. Cap at 3
lines (line-clamp-3) — long enough to convey what happened, short
enough that rows stay scannable. Users open the workspace if they want
the full transcript.
Co-authored-by: Orca <help@stably.ai>
* feat(activity): switch left list from workspaces to agent panes
Restructure the activity feed so each thread is one *agent pane* (a
terminal tab + pane id) rather than one workspace. Internal model goes
from WorktreeThread → AgentPaneThread keyed on paneKey.
Renderer changes:
- Left list: each row shows the pane title (with agent icon + repo
badge), then the workspace name as secondary context. Section label
changes "Workspaces" → "Agents".
- Right pane: header shows the pane title, agent icon, repo badge, and
the workspace name beneath. Drops the Today/Yesterday/Earlier day
grouping per design — the relative timestamp on each row is enough
orientation, and a flat chronological list scans more cleanly.
- Drops the workspace-created event kind entirely (and the
WorktreeEventRow + ActivityRow union dispatcher), since the surface
is now strictly agent activity.
Side effects:
- Mark-read no longer needs the locallyReadEventIds layer or the
worktree-unread store calls (markWorktreeUnread/clearWorktreeUnread).
Acknowledge by paneKey is the only persistence path.
- Removes Plus and groupForTimestamp (unused after the refactor).
Co-authored-by: Orca <help@stably.ai>
* feat(activity): label panes like the per-workspace agents dropdown
Pane labels now follow the same hierarchy DashboardAgentRow uses inside
the WorktreeCardAgents dropdown:
customTitle (user rename) > non-default OSC title > last prompt
> defaultTitle / "Terminal"
The prompt fallback is the important one — agents that haven't been
renamed and don't set an OSC title now render with what the user asked
the agent to do (e.g. "Fix the unread dot alignment") instead of the
generic "Terminal 1" placeholder. Matches the visual + naming pattern
on each workspace card so the activity page reads as a flat-feed
extension of that surface.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): smaller titles, allow 2-line prompt clamp
Long prompts were getting truncated at one line, hiding most of the
ask. Switch the thread row title from truncate to line-clamp-2, with
break-words so long words wrap, and tighten size to text-[13px] +
leading-snug so two clamped lines don't dominate the row vertically.
Anchor the agent icon to the first line via items-start + pt-[3px].
Right-pane heading drops text-base → text-sm to match.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): right-pane title gets the same 2-line clamp + smaller size
Mirror the ThreadRow change on the right-pane heading: line-clamp-2 +
break-words + leading-snug so a long prompt title shows two lines
instead of a single-line ellipsis. The repo badge moves down to the
secondary line (next to the workspace name) so it doesn't shift with
the title height.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): match WorktreeCard's selected/hover/unread cues
Three stacked tints (selected + unread row tint + hover) made an unread
hovered row look identical to a selected row, and hover even fought
selected. Mirror WorktreeCard:
- selected → solid black/white tint + faint shadow, hover suppressed
(the active class wins so the row stays visually fixed)
- non-selected → only then does hover apply (bg-accent/40)
- unread → weight + left-edge primary bar carry the cue; row tint
removed (matches WorktreeCard's "weight alone carries the unread
signal")
Co-authored-by: Orca <help@stably.ai>
* feat(activity): portal selected agent terminal
Co-authored-by: Orca <help@stably.ai>
* fix(activity): keep completed entries after terminal input
Co-authored-by: Orca <help@stably.ai>
* fix(activity): tighten portal target, unread count, and selection edges
- activity-terminal-portal: replace document.body MutationObserver with a
module-level pub/sub registry; the page publishes the target via a ref
callback, Terminal subscribes. Removes body-wide DOM observation.
- Terminal: memoize activityTerminalPortal so WorktreeSplitSurface's
React.memo bail-out is preserved across unrelated Terminal re-renders.
- ActivityPrototypePage: cap events per-pane (5) so a chatty pane can't
push quiet panes off the left list; render an empty-state when a
retained thread's tab is gone; skip workspace mutation in
activateThreadTerminal when the tab is no longer live; keep the
selected thread visible in unread-only mode after auto-mark-read.
- ActivityTitlebarControls: walk stateHistory in the unread accumulator
(mirrors the new feed semantics); drop worktree-created counting since
the new feed has no surface to dismiss them.
- AgentStateDot: remove the dead 'lg' size variant.
- App: hide worktree sidebar on the Activity view and include it in the
back/forward shortcut + button cluster.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): double-buffer portal slots to remove cold-mount flash
Switching threads on the Activity page used to expose xterm's empty
canvas for ~133ms while a newly-mounted TerminalPane attached. Replace
the single portal target with a double-buffered set of slots: keep the
previously displayed terminal visible while the next selected terminal
mounts in an invisible same-size slot, then flip slots once the staged
slot contains the selected terminal DOM with xterm's screen and PTY
binding (with one extra frame only after observing an empty mount).
Skips publishing a portal target when the selected retained thread no
longer has a live tab so the previous tab's target doesn't linger.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): remove single-option Display style settings dropdown
The activity page's gear menu only held a "Compact list" toggle. Drop
the dropdown along with the leftSidebarCompact state, the
ActivityDensity type, and the unused dropdown-menu / Settings imports.
The thread list keeps the compact layout (the previous default).
Co-authored-by: Orca <help@stably.ai>
* fix(activity): vertically center Jump to workspace button
The header row uses items-start so a 2-line prompt clamps without the
badge jumping. self-center on the button overrides that just for the
action so it sits in the middle of the title block instead of pinned to
the top edge.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): prefer prompt over OSC live title for thread label
paneTitleForEvent put the non-default tab.title ahead of the prompt, so
agent CLIs that set "Claude Code" / "Codex" via OSC pinned every row
and the right-pane heading to the agent name. The dashboard row this
function claims to mirror uses the prompt directly. Re-order to:
customTitle (explicit rename) → prompt → non-default liveTitle →
defaultTitle, so sending "hi" actually shows "hi" in the title.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): drop extra row height when prompt is one line
The thread row's right column stacked time over the count badge with a
gap, which forced the row to ~48px even when the left column's prompt
was a single line. Collapse the right column into one horizontal
cluster (count + time/bell) so single-line rows stay tight, while
two-line prompts still drive the row taller as before.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): add top/bottom padding to thread rows
Bump py-1.5 → py-2.5 so single-line rows breathe and don't sit flush
against the row dividers.
Co-authored-by: Orca <help@stably.ai>
* fix(activity): even out optical row padding (top read heavier than bottom)
Symmetric py-2.5 looked top-heavy because the title's leading-snug adds
internal whitespace above the cap-height that the secondary row
doesn't have below it. Trim the top to pt-2 / pb-2.5 so the row reads
balanced.
Co-authored-by: Orca <help@stably.ai>
---------
Co-authored-by: Orca <help@stably.ai>