Treat the appearance setting as the rendered base size so the type ramp scales predictably. Native starts one pixel larger while web and desktop preserve the authored 14px base, with legacy values migrated on load.
Keep the last painted canvas width while a pane resize is active, then repaint during layout before the settled width reaches the screen. This avoids scaling the bitmap during the drag or on its final handoff frame.
Fast Refresh preserves refs while rerunning effects, so canceled paint requests must release their retained IDs or unseen diff regions stop rendering. Paint the final expanded body’s reserved boundary pixel because no following header owns that seam.
Reuse measured diff geometry when review controls open, keeping the interaction responsive on large changes. Preserve complete tab-track snapshots while exact content widths settle so new tabs and retained workspaces do not flash or truncate.
Keep diff headers and painted content on the same scroll clock while preserving native scrolling and independent per-file horizontal offsets.
Complete review, selection, typography, tree, and palette parity without returning line rendering to React.
Size tabs from rendered labels and switch to horizontal overflow once minimum click targets are reached, keeping the add-tab action adjacent until it must remain fixed.
Make Changes an always-expanded document with an optional desktop navigation tree, keep Git actions in their platform-owned headers, remove pinned and inline split shortcuts, and place review tabs beside Changes.
* fix(app): keep check rows the same height with and without "Add to chat"
A check row was minHeight 32 plus 8pt of vertical padding, but the xs
Button in its trailing slot is 28pt tall. The button stacked on the
padding rather than fitting inside the row, so a row grew to 44pt the
moment "Add to chat" appeared and the list visibly shifted as rows
gained the button.
Rows are single-line by construction, so the row now declares a fixed
height instead of a floor a child can outgrow. That height comes from
CONTROL_HEIGHTS, the same source the button sizes from, so the two
cannot drift apart later. GitLab pipeline rows share this style and get
the fix too.
* feat(app): summarize pull request checks and group them by status
The checks section listed every check inline under a bare "Checks"
header with three count pills. On a repo with a real matrix that meant
a wall of undifferentiated rows that pushed the activity timeline off
the pane, and the pills made the reader add up numbers to work out
whether the run was actually in trouble.
The section now leads with the answer — a headline that names the
outcome, a one-line count breakdown, and a ring showing the statuses in
proportion — then groups the rows by status with failures first, and
scrolls in place past roughly eight rows instead of growing without
bound. Groups collapse individually.
The derivation lives in checks-summary.ts as one pure function, so the
headline, the ring, and the group labels cannot disagree about the same
run. The rendering, the row, and the check identity move out of pane.tsx
into an owned module; the pane now wires it and nothing more.
Running checks also gain a timing. formatCheckRunDuration required both
a start and a completion, so anything still going showed nothing at all.
It now measures an unfinished run to now. The wire deliberately carries
formatted strings rather than raw timestamps — there is a test asserting
check timestamps are stripped — so the daemon does the measuring and the
client only decides how to say it: a bare "2m" on a running row would
read as "took 2m", so those rows say "running 2m".
* feat(app): add persistent workspace side panes
Keep the common review and file-browsing workflow beside the active agent without tearing down pane contents. Workspace tabs now share one compact chip language, assistant file links route into the retained right pane, and diff/file trees share stable geometry.
* fix(app): localize pull request empty state
* test(app): update explorer pane coverage
* fix(app): preserve explorer pane workflows
* test(app): make explorer tab selection idempotent
* fix(app): persist workspace review pins
* fix(app): preserve sidebar scrim drag hook
* fix(app): restore companion pane interactions
* test(app): follow selected sidebar surface
* perf(app): bound large diff row mounting
Keep the existing vertical and horizontal scroll owners so virtualization does not change gesture or scrolling behavior. Reserve exact offscreen row heights while mounting only the visible window, and retain the full renderer when wrapping or editing reviews requires dynamic geometry.
* perf(app): keep large diff reviews responsive
Opening an inline editor changed the whole review action object, reconciled large syntax-token trees, and disabled row virtualization. Keep the expensive token subtree stable and preserve deterministic row geometry while the editor is open.\n\nBound server-side highlighting for pathological single lines and remove the unused client highlighter so the actual diff pipeline owns the limit.
* test(app): check mounted diff typography
Row virtualization can unmount a fixed diff row after scrolling. Assert the configured font size against a currently mounted row so the test continues to verify rendered typography without depending on window position.
* test(app): stabilize Mermaid streaming coverage
* fix(app): validate persisted client state
Persisted replica data could satisfy TypeScript through an assertion while missing fields required by renderers. Treat every AsyncStorage value as untrusted input, clear values that fail strict schemas, and version the replica DTO so incompatible caches cannot hydrate.
* fix(app): preserve validated legacy state
* fix(app): preserve validated preferences
Preserve supported legacy location fields and every shipped theme while keeping unknown persisted fields rejected. Align browser test seeds with the current preference shape so strict validation exercises the intended provider.
* fix(app): migrate legacy agent preferences
Released clients stored host/location fields and per-provider thinkingOptionId together. Migrate those exact strict bytes into current preferences without admitting unknown fields.
* fix(app): preserve drafts with legacy reviews
Accept the exact strict shape released for workspace review attachments so migration can discard that obsolete attachment without clearing every persisted draft.
Restore the established tree rails while retaining file actions. Keep Escape local to generic editable fields so cancelling a file draft cannot interrupt an agent.
* perf(app): virtualize large diff rendering
Render bounded diff row groups through one vertical list and one horizontal surface. Bound retained list ranges when expansion grows the data set so large diffs paint promptly without sacrificing scrolling behavior.
* refactor(app): normalize working tree diff options
* fix(app): keep wide diff text reachable
Three systems were reporting the same states: a full and a muted status
tier in the theme, plus raw palette steps in StatusBadge and
getStatusDotColor. A PR icon was muted in the sidebar and full-strength
in the PR panel, and the Resolved badge used a theme-independent green
so it painted a dark block in light mode. The muted tier is deleted and
every status surface now reads the one token per signal.
Dots keep a band of their own. A dot is a few points of solid colour
with no shape to read and no label attached, and the running one pulses,
so at the status band's lightness it sat below the check icons and host
badge on its own row — backwards, since the dot is the row's state.
Chroma caps at 90% of gamut rather than 100% because at the edge green
reaches C=0.215 while blue manages 0.116 and the set stops reading as
one family; dark lightness caps at 0.72 because red desaturates to pink
above it.
Host badges needed a foreground variant of the identity table. That
table is tuned for a white letter on the colour as a fill; used as text
it cannot clear 4.5:1 on both themes at once, since light needs
luminance <= 0.183 and dark needs >= 0.212. The new set is generated on
the status family's exact lightness and chroma fraction, so a host name
and a CI check in the same meta row carry equal weight instead of the
brighter one winning the row.
Also: PR avatars had their own eight-hex palette and hash duplicating
identity-colors; workspace rows under a project group now indent to the
same rail as status-grouped rows; group headers are muted; and the
pulsing dot is faster and fades further.
Use mobile action sheets and long-press file menus while preserving provider context across sheet portals. Keep sidebar and file-pane glyphs on shared visual rails, avoid scrollbar layout shifts, and hide keyboard affordances where shortcuts are unavailable.
Sidebar polish round.
Menus. `dropdown-menu` and `context-menu` were two copies of one engine that
differed only in how the trigger fires — identical from `computePosition` down —
and neither could nest. Both are now thin wrappers over
`components/ui/menu/`, which owns anchoring, a pure page-stack navigation model,
and one presentation fork: flyout on pointer, pushed pages in a sheet on compact.
All 31 call sites are unchanged. Items got a rounded inset fill, a visible
separator, and `selected` (a check) split from `active` (a fill).
Sidebar display preferences. The flat wall of four sections becomes one row per
decision with its value, and per-feature toggles over what a workspace row shows:
host, pull request, checks, scripts. Each is independent — a ticked toggle that
draws nothing is a lie. `display-preferences/` hides that grouping lives in
zustand while title source and row items are app settings.
Timestamps. "Last activity" now ticks itself. The label rounds to the minute and
never shows seconds; past a week it becomes a date. The timer lives in the leaf
`<Text>`, at most one per resolution tier shared by every row, and a tick only
re-renders when the string actually changed — so a memoized row that legitimately
never re-renders still stays current.
Colour. Full status tier regenerated at 90% of gamut max (was 85), same rule as
the muted tier, lightness and hue untouched. File icons are pulled toward neutral
in OKLab behind `getFileIconSvg` — material-icon-theme picks its palette for a
tree where the icon is the only colour on the row, and ours is not. New/deleted
files use the forge square-plus/square-minus glyphs at muted weight instead of
tinted pills. Project icons round a little more; uploaded icons never do.
The project status badge is a knockout again: it fills with the colour of the row
behind it, so the ring reads as a gap in the icon rather than a white halo.
File rows. The always-visible kebab is gone from the file explorer and diff tree
in favour of right-click, which also reclaims the reserved action slot.
* perf(app): retain inactive workspace screens
App-wide routes cleared the active workspace selection, which removed the retained workspace deck from the React tree and made returning rebuild every panel. Keep mounted deck entries inactive across those routes and suspend inactive panel subscriptions so state is preserved without background work.
* perf(app): suspend remaining inactive queries
Retained composers and commit diff panels still subscribed through query hooks that did not receive panel activity. Propagate the existing activity gate so app-wide routes leave those hidden surfaces dormant.
* refactor(app): separate panel activity from connectivity
A hidden retained panel is still connected to its host. Keep connectivity truthful, pause work inside query and stream owners, and preserve terminal emulator identity while its stream is detached.
* test(app): target active retained controls
* fix(app): retain compact and terminal route content
* fix(app): hide inactive workspace overlays
* feat(app): expand command center actions
Keep command center navigation stable by revealing only clipped rows without smooth centering. Prioritize a concise default list while making workspace, Git, pane, and creation actions searchable through the existing contribution and action-policy paths.
* test(app): search for query-only command action
Add project is intentionally absent from the unfiltered command center. Exercise the overlay flow through search before selecting it.
* fix(workspaces): show isolation without checkout delay
* fix(workspaces): trust hydrated project capability
* fix(workspaces): resolve branch before creation
* fix(workspaces): honor invalidated checkout status
* fix(workspaces): preserve isolation while projects load
* fix(workspaces): keep provisional project routes usable
Route-derived projects do not know their worktree capability. Represent that uncertainty at the placement boundary so remembered worktree intent survives hydration without disabling the New Workspace screen indefinitely.
* fix: reject oversized checkout diffs
* fix(diff): keep large previews from disconnecting clients
The physical socket limit is an OOM backstop, not a frame-size contract. Bound structured diff construction incrementally and report oversized previews without closing the client's socket.
* fix(app): preserve oversized diff state
* fix(diff): stay within relay frame limit
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
"Open in browser" links for a self-hosted forge served on a non-standard
port (e.g. Forgejo/Gitea on :60443) dropped the port, producing
https://host/owner/repo/... instead of https://host:60443/owner/repo/...,
which 404s or hits the wrong service.
parseGitRemoteLocation discarded parsed.port (GitRemoteLocation had no
port field), and buildForgeBranchTreeUrl / buildForgeBlobUrl rebuilt the
origin from the portless host. Preserve the port on GitRemoteLocation and
reattach it in the web-URL builders, only for self-hosted http(s) origins
(an SSH port isn't the web port; a canonicalized cloud host uses the
default port). Host-identity matching (forge detection, cloud-host checks)
stays port-agnostic.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* fix(chat): load older timeline history consistently
Pagination depended on scroll events, so short or compacted initial pages could never request older history. Reevaluate edge visibility as layout and history change, and standardize loading indicators on the canonical app spinner.
* fix(chat): keep older history loading reliably
Use authoritative timeline cursor progress so merged rows cannot stall pagination. Wait for bottom anchoring before evaluating the history edge, and re-arm retries on a fresh upward gesture without automatic failure loops.
* feat(commits): distinguish workspace history from base commits
Keep every workspace commit visible while bounding base history to ten
context commits, and make push and base state readable in the commit rail.
* fix(commits): preserve history when base refs disappear
Fall back to recent HEAD history for stale saved bases, and reject truncated
git output instead of presenting an incomplete commit list.
* fix(commits): classify history against the current base
Use the furthest-ahead local or remote base for classification, and re-infer
base history when saved worktree metadata points to a deleted branch.
Open the complete working comparison in a desktop workspace tab while preserving inline sidebar diffs when the tab is closed.
Working and commit comparisons share one diff panel, and workspace layout is the sole owner of live tab persistence and identity.
Refs #1520
* feat(app): show recent commit history in explorer
* fix(app): keep recent commit history accurate
* fix(app): include merged commits in recent history
* perf(app): bound recent commit classification
* fix(app): refresh recent commits on Git updates
Module-level style composites could materialize the temporary adaptive theme before persisted settings loaded. Keep Unistyles reads in render and guard against eager module-scope access.
* refactor(forge): forge-neutral foundation (GitHub-only)
Decouple git-hosting from GitHub behind a neutral abstraction (issue #1616), GitHub-only for now; existing GitHub behaviour is unchanged.
- Forge manifest, neutral ForgeService contract, forge registry + resolver, and a client forge-module registry.
- GitHub code renamed to the neutral shape; PR/Issue attachment wording preserved.
- forge.search.response enums parse tolerantly (unknown kind/auth state degrade instead of breaking the client).
- createPullRequest reports typed CLI/auth errors instead of a generic message.
- forge-resolver host/remote caches are LRU-bounded.
- Forge host trust is explicit: only a known cloud host or a CLI-authenticated host is ever talked to; an unauthenticated GitHub Enterprise host fails resolution instead of routing to github.com.
- Docs: forge-providers guide, glossary and i18n forge-copy conventions, architecture and rpc-namespacing terminology.
- Vitest React Native mocks (unistyles, svg, linking, lucide) consolidated into shared aliased test-stubs.
* feat(forge): GitLab adapter, forge-aware UI, pipelines and approvals
GitLab adapter over the glab CLI on the neutral contracts: MR status, forge-aware UI, pipeline tree, and N-of-M approvals.
- threadIsResolved is part of the neutral timeline item.
- Pipeline load failures show an error instead of an empty section.
- Manual pipeline jobs render as pending.
- Fork/detached MR head pipelines are fetched by MR iid (glab ci get --merge-request).
* feat(forge): Gitea family adapter (Gitea, Forgejo, Codeberg)
One adapter over the tea CLI serving Gitea, Forgejo, and Codeberg on the neutral contracts.
- CI status aggregates commit statuses and Actions runs together.
- Gitea's terminal "warning" state maps to failure on server and client.
- Gitea Actions check details are reachable from the PR pane by workflowRunId.
* refactor(forge): localize compatibility handling
* test(forge): expect normalized GitLab facts
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* feat(app): add fileView preference for changes panel
* feat(app): add buildDiffTree util for changed-files tree
* feat(app): add changed-files tree directory row
* feat(app): changed-files tree view mode in changes panel (#117)
* feat(protocol): add checkout.commits.list RPC + commitsList feature flag
* feat(server): list branch commits ahead of base with on-remote flags
* feat(server): handle checkout.commits.list RPC and advertise capability
* feat(client): checkout.commits.list method + useCommitsQuery hook
* feat(app): per-commit inline view with local-vs-remote markers (#117)
* feat(protocol,server): per-commit file diff RPC (checkout.commits.file_diff)
* feat(app): open per-commit file diff on click (#117)
* refactor: surface baseRef + commits loading/error, drop dead depth field
* fix(app): flip commit local/remote dot — local hollow, remote filled
* feat(app): list/tree view for expanded commit file list (#117)
* fix(app): syntax-highlight per-commit file diff to match Changes view
* feat(app): draggable resize between commits and diff sections
* refactor(app): dedupe wrap-text helpers into diff-highlighted-text
* fix(app): clean up commits resize drag on unmount + a11y label
* fix(app): collapse commits section by default
* fix(app): render per-commit file diff with the shared Changes line renderer
* perf(app): memoize shared diff line row; drop redundant DiffLineView wrapper
* refactor(app): extract shared DiffFileBody; render commit file diff through it
* fix(app): hide inline-comment affordance in per-commit diffs (no reviewActions)
* feat(app): move commits to a resizable bottom drawer in the Changes panel
* feat(review): commitSha scoping for per-commit review drafts + attachment
* feat(app): per-commit inline comments wired through to the composer (#117)
* refactor(app): own commit file-diff open state in CommitFileList (drop reset effect)
* refactor(app): colocate diff-render cluster under git/diff-file-body/
* feat(app): add diff tab target kind (working/commit diff tabs)
* feat(app): useDiffFiles hook unifying working + commit diff targets
* feat(app): diff tab panel rendering working/commit diffs
* feat(app): open a commit diff tab on commit click; drop per-commit drawer/file list
* feat(app): open/scroll working diff tab on changed-file click
* fix(app): keep commit diff tabs ephemeral; align working diff whitespace
* feat(app): collapsible file sections in the diff tab, collapsed by default
* feat(app): make the on-remote commit dot more subtle
Dims the filled green remote dot (row + legend) to ~0.55 opacity so the
local-only ring stays the state that draws the eye.
* Reshape commit diffs around the existing Changes view
* Fix diff tab migration complexity after rebase
* Address diff tab review findings
* Clean up diff tab review interfaces
* Collapse commit diffs into the existing view
* Load commits when expanded
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* feat(app): refine tool call summaries and scrolling
Keep summary groups stable from the first tool call, preserve live activity feedback, and scroll expanded groups to the latest entry. Consolidate web scrollbar styling around the native browser mechanism.
* refactor(app): reuse tool call height constant
* fix(app): preserve tool call summary state
* fix(app): refresh web tool groups on expand
* fix(app): preserve grouped tool errors
* feat(app): expose archive for every workspace
Workspace actions now archive one workspace by id. The daemon remains responsible for backing-specific cleanup, including removing a managed worktree only after its final workspace is archived.
* refactor(app): clarify archive descriptor name
* fix(app): preserve workspace archive safety
* fix(app): allow archive without upstream
* refactor(app): remove stale archive callback
* test(app): cover workspace archive ownership
* feat(app): restore compact Changes diff controls
Keep the folder tree behind a view toggle while preserving the flat file list as the default.
Move secondary diff options into the overflow menu so the Changes toolbar stays compact.
* fix(app): mirror whitespace diff option copy
* fix(app): polish diff controls review follow-up
Group changed files in the Changes view into a collapsible directory
tree instead of a flat list, on every form factor — consistent with the
Files explorer, which already renders a directory tree on mobile.
Folders are collapsible and expanded by default, single-child directory
chains are compressed into one row (packages/app/src), and folder rows
show aggregate +/- stats.
Client-only, built entirely from the existing ParsedDiffFile paths:
- diff-tree.ts: pure build/compress/flatten of the directory hierarchy,
keyed by full uncompressed dir path so collapse state survives
compression changes as the diff mutates.
- diff-flat-items.ts: pure buildDiffFlatItems + sumHeightsBefore. The
function retains a flat mode (showTree=false) as a tested seam for a
possible future flat/tree toggle; sticky header indices are
file-headers-only, derived from the post-collapse list.
- panel-store: diffCollapsedFoldersByWorkspace slice (collapsed-set, so
folders default to expanded), persisted via version 12. The set is
reconciled against the current diff so folders that leave and reappear
default to expanded and the stored set stays bounded.
Presentation primitives (chevron, indent guides, indent constant) are
extracted into components/tree-primitives and shared with the Files
explorer, which drops its private copies (and a banned useUnistyles
call site). The two trees now render identical rows; only the
tree-building stays separate (server listings vs. diff paths).
The toolbar's expand-all stays file-diffs-only (renamed
allFileDiffsExpanded); a separate control collapses/expands all folders.
Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
* feat(schedules): per-run workspaces, isolation and archive controls
Scheduled agents never appeared in the sidebar: each schedule reused one
stamped workspace and archived the run's agent on completion. Now every run
mints its own workspace — the entity the sidebar shows — with per-schedule
controls for isolation (local or worktree) and whether to archive that
workspace when the run finishes.
The schedule form is rebuilt on a non-React form model (open/commands/close)
to end the flicker, edit-into-create contamination, and multi-host hydration
bugs that came from effect-choreographed shared state. That lands the reusable
foundations it needed: a form kit over a single control-geometry owner, a
data-access taxonomy (replica/snapshot/fetch) with real load-state semantics,
and lint/boundary guardrails so new screens inherit the paved road.
Also fixes desktop combobox popovers truncating options when the trigger is
narrower than the content. New schedule protocol fields are optional on both
create and update, so old and new clients/daemons stay compatible.
* test(app): complete mock themes for control-geometry tokens
switch and terminal-profile-edit-modal now style through
createControlGeometry, which reads theme.borderWidth[1], theme.opacity[50],
and theme.colors.borderAccent. The hand-rolled mock themes in these two
suites lacked those tokens, so the eager StyleSheet.create mock threw at
import (collection error), not on any assertion. Add the missing tokens.
* fix(schedules): address review findings on run cleanup, edit isolation, load error, sheet dismiss, and preference hydration
- Server: archive the run's workspace even when agent creation fails, so a
misconfigured provider no longer orphans a sidebar workspace/worktree.
- Edit form: keep a stored worktree isolation while worktree eligibility is
still resolving, instead of silently downgrading a saved schedule to local.
- Schedules screen: show the load error/retry UI when every host fails, rather
than spinning forever behind the loading branch.
- Form sheet: fire the parent onClose on native gesture/backdrop dismiss so the
sheet closes instead of re-opening.
- Form model: apply late-loading saved preferences to untouched create-mode
fields (never to edited schedules or user-modified fields).
* fix(schedules): review batch — reconnect resubscribe, worktree prune, Android dismiss, cadence and provider edit safety
- Push-router: re-send terminal and checkout-diff subscriptions after a
reconnect; the old per-hook effect resubscribed on isConnected and the
extracted router did not, so daemon restarts silently stopped terminals_changed
pushes (root cause of the terminal-activity e2e failures).
- Server: pass the run's source repo root when archiving worktree runs so the
worktree is unregistered (git worktree remove/prune), not just deleted; also
archive the workspace when agent creation fails even with archive-off (an
agentless workspace has nothing to inspect).
- Sheet: RN Modal onDismiss is iOS-only — notify dismiss once-per-close on the
Android native path too; replace the JSDOM component test with a real
Playwright dismiss spec and inline the trivial dismiss decision.
- Form model: editing an interval schedule no longer rewrites its cadence to
cron unless the cadence UI is touched (90-minute intervals have no cron
equivalent); switching projects clears stale provider/model state and gates
submit until the new host's snapshot resolves.
* chore(lint): drop custom lint wrapper and boundary script, plain oxlint
The import bans stay in .oxlintrc.json (oxlint-native no-restricted-imports).
The receiver-sensitive checks the custom script enforced are not worth a
parallel lint pipeline; if they come back it will be as oxlint rules.
* fix(schedules): crash-safe run cleanup, capability-gated run options, resilient mutations
- Persist the run's workspace/agent ids on the running-run record so a daemon
crash mid-run can be recovered: restart archives the interrupted run's
workspace under the same policy as normal cleanup (agentless always archives,
otherwise archiveOnFinish is respected).
- Hide Archive on finish (and omit archiveOnFinish/isolation from payloads) on
hosts without workspaceMultiplicity — an old daemon ignores the fields, so
offering the switch there would lie.
- Optimistic pause/resume/delete no longer throw when the schedules cache holds
a still-connecting entry alongside loaded data.
- Mode field is gated on provider mode options, not a selected model, so
model-less providers can pick their advertised modes.
* Merge workspaces across all hosts in the sidebar
Replace per-host sidebar sections with a single merged project list.
Projects that exist on multiple hosts (matched by projectKey) are
collapsed into one row. Host identity is surfaced via:
- A status-only footer pill showing online/offline host counts
- Hover cards with a mandatory host row on every workspace entry
- Subtitles showing the host name when a project spans >1 host
- A host filter in the grouping dropdown (not a sidebar switcher)
Order and group-mode stores migrated from per-server to global keys
with zustand persist migrations. New-workspace screen now shows a
merged project list with a host selector combobox.
Key files:
- workspace-structure.ts: merge algorithm by projectKey
- sidebar-view-store.ts / sidebar-order-store.ts: global state
- sidebar-workspace-list.tsx: context-based subtitle injection
- new-workspace-screen.tsx: host selector in footer row
* Add host filtering to the merged sidebar
The sidebar now merges workspaces from every connected host, so a single
global "active host" no longer fits. Host selection becomes explicit per
action via a host chooser, and agent history pages across all hosts at
once. Removes the active-host / active-server-id model and the
all-agents-list hook that fed it.
* Fix rebase fallout: translate archive host error, update stale workspace-key test
archiveWorkspacesOptimistically used a raw "Host is not connected" string
that the i18n completeness test forbids; route it through i18n.t with the
existing hostDisconnected key. The selectors memoization test asserted
un-prefixed workspace keys, but merged-sidebar keys are serverId-prefixed.
* Fix sidebar review fallout
* Address sidebar review followups
* Fix sidebar workspace identity migration
* Address sidebar review followups
* Handle partial host history failures
* Fix merged sidebar project removal and E2E drift
* Restore history routes and project removal shape
* Remove sidebar host context plumbing
* Tighten sidebar status and host filter state
* Keep new workspace project selection host-compatible
* Migrate sidebar view storage
* Streamline host selection flows
* Update all-host history on archive
* Fix empty project sidebar action
Code regression: the main merge dropped the empty-project New workspace child row while keeping the empty project persistence contract. Restore the row using the project host target so the sidebar stays aligned with the no-host-selection model.
* Fix Japanese shortcut translation key
Stale-base integration: main added the Japanese locale after this branch added cycleAgentMode to the English shortcut help map. Merging main exposed the missing ja key in CI typecheck and resources.test.
* Fix Playwright speech teardown race
* feat(app): add toolbar and loading states to the GitHub PR panel
Adds a toolbar under the PR title with a refresh button (refreshes git
and GitHub status + timeline in one tap), an always-visible View Pull
Request button, and a Merge control with a method dropdown that is
disabled-with-reason when the PR can't be merged yet.
The merge control reuses the existing git-actions policy verbatim via a
new buildPrPanelMergeActions selector fed into GitActionsSplitButton, so
no merge readiness/squash/auto-merge logic is duplicated.
Also replaces the empty-state flash with skeletons: a full-panel
skeleton while PR status loads and an activity skeleton while the
timeline loads, with empty states shown only when loaded-and-empty and
an error state with retry.
* refactor(app): tidy PR panel skeleton (i18n, shared pulse, index)
Addresses review feedback on the PR panel work:
- Localize the skeleton's "Checks" label via t() instead of hardcoding.
- Hoist the shared SkeletonPulse + useSkeletonPulse into activity-skeleton
so pane-skeleton reuses them instead of duplicating the pulse driver.
- Export PullRequestPaneError/Skeleton from the module index and import
them through it, matching the module's existing public surface.
* test(app): remove mock-based PR pane test
* fix(app): simplify PR panel toolbar
* fix(app): report PR panel retry failures