Commit Graph
200 Commits
Author SHA1 Message Date
800bd2bef1 feat: add host-local custom project icons (#2335)
* feat(projects): add local icons and colors

* refactor(projects): simplify appearance handling

* fix(projects): serialize appearance saves

* test(app): update project appearance expectations

* test: fix project appearance CI expectations

* refactor(projects): organize appearance code

* fix(server): adopt main's reconcile model in project appearance updates

* fix(projects): preserve host-local appearance targets

* fix(desktop): restore Linux development startup

Ubuntu can block Electron's unprivileged user-namespace fallback while the npm-installed sandbox helper is not SUID. Disable the Chromium process sandbox only in the Linux development runner so packaged desktop policy remains unchanged.

* feat(projects): add custom project icons

Keep URL acquisition in the client so the host daemon only validates uploaded bytes. Project name and icon changes share one adaptive edit sheet.

* refactor(settings): scope projects by host

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-08-01 01:34:42 +08:00
Mohamed BoudraandGitHub de44c8cd66 Show New Workspace isolation controls immediately (#2702)
* 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.
2026-07-31 17:50:35 +02:00
35f985baff Switch reasoning, mode, plan, and fast from the Command Center (#2274)
* Switch reasoning, mode, plan, and fast from the Command Center

Extend the Command Center (Cmd-K) with selectors for a focused agent's
Thinking (reasoning effort), access Mode, Plan mode, and Fast mode,
mirroring the model switcher. Typing surfaces tiered "<Setting> › <Value>"
breadcrumb rows with icons and a check on the current value, for both a
running agent and a new draft tab. Rows only appear while typing, so the
default palette is unchanged.

- Thinking and Mode are multi-option selects; Plan mode and Fast mode are
  On/Off toggles.
- Plan mode is dual-natured: a permission mode for Claude/Copilot/OpenCode
  (excluded from the Mode list, given its own toggle) and the plan_mode
  feature for Codex. Fast mode drives the fast_mode feature (model-gated).
- All logic lives in command-center/agent-setting-contributions.ts as pure,
  unit-tested builders, with a small icon helper and a hook that registers
  the running-agent selectors; the draft registers from its composer tab.
  No changes to the command palette component and no protocol changes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>

* Unify Command Center agent controls

* refactor: clarify agent control ownership

* refactor(app): tighten command center agent controls

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-07-31 17:37:04 +02:00
Mohamed BoudraandGitHub 99eca19a76 Keep selected projects when switching hosts (#2700)
* fix(app): preserve project selection across hosts

Rendered project identity and persisted equivalence were conflated after duplicate-project handling. Keep them separate so host changes resolve selectable equivalents deterministically without resetting legacy sidebar identity.

* fix(app): restore selected clone after host round trip

Keep manual origin and current destination placements separately so switching back restores the chosen checkout while archive gaps preserve the active host selection.

* fix(app): key status icons by project view

* fix(app): preserve origin during archive gaps
2026-07-31 15:42:08 +02:00
Mohamed BoudraandGitHub 8e983c5d27 Stop transient file conflict callouts (#2694)
* fix(app): prevent transient file conflict callouts

Watcher versions are candidates until a matching disk read settles them. The editor now classifies the settled bytes once, preserving dirty buffers and keeping reload content paired with its revision.

* fix(app): preserve external BOM changes
2026-07-31 19:03:11 +08:00
91da0bac4d Choose which orchestration skills get installed (#2680)
* feat(settings): choose which orchestration skills to install

The skills row installed the whole bundle or nothing. A settings control
next to it now opens a sheet where you keep every bundled skill or pick a
subset, and saving converges ~/.agents, ~/.claude, and ~/.codex to it.

"All skills" reads the bundle directory instead of a hardcoded catalog, so
a skill added in a later release installs itself without another code
change. Retired bundle names stay in a separate cleanup list so old copies
on disk are still removed but never become selectable.

The selection persists under Electron user data, separate from install
state, so uninstalling does not erase the choice — a later install brings
back exactly the chosen set. Installs with no saved selection read as
"all", which is what they already had.

* Restore skill selection after failed save

* fix(settings): don't save a skill selection that never reached disk

A failed save wrote the preference, converged, then wrote the old preference
back. That leaves a window where a selection the user was told did not save
is the one on disk, and a crash inside it makes the window permanent.
Converge first and persist only on success — nothing to roll back, and a
failed save now leaves disk and preference exactly as Cancel would.

Resolving the bundle path when the command handlers are wired also broke
daemon-manager's tests: the packaged branch reads process.resourcesPath,
which is undefined outside Electron. Resolve targets per command instead —
registering handlers should not touch the environment.

* fix(settings): make skills convergence single-writer and confirm removals

Startup auto-update and the settings commands were separate writers over the
same skill directories. Either could apply a plan computed from a selection the
other had already replaced — narrow the selection while startup is repairing
drift and the repair puts the removed skill back. There is now one controller
that owns scan, apply, and persist, and every entry point queues through it, so
concurrent callers see a consistent result instead of an interleaved one.

Save also had no recovery once it had converged: if the selection could not be
committed afterwards, disk was changed while the preference still said the old
thing, and the next startup silently undid the change. Save now puts the
managed directories back to what the committed selection describes before it
reports the failure, so a failed Save lands where Cancel would.

Deselecting a skill deletes its directory in all three agent homes, including
files the user put inside it. That is now confirmed like every other
destructive action, naming the skills and the directories. Adding skills or
saving an unchanged selection still saves without a prompt, and a skill the
host has not installed yet deletes nothing so it does not prompt either.

* fix(settings): confirm removal for skills installed in only some agent homes

The removal confirmation read an `add` op as "not on disk yet". An `add` op
means "missing from at least one target", so a skill sitting in ~/.claude but
not the other two looked absent, and deselecting it deleted that directory —
with anything the user had put inside it — without asking.

The status snapshot now carries the installed skill names directly, taken from
union presence across the three agent homes, and the confirmation reads that.
`ops` keeps its one meaning.

A host that predates the field cannot say what is on disk, so the app bridge
assumes the saved selection is installed. Asking about a directory that turns
out to be absent is harmless; the reverse is not.

* fix(settings): warn about every skill directory a save deletes

The confirmation asked "was this selected before and is it selected now",
which is a different question from the one the host answers. The host
converges to the desired set and deletes every managed directory outside it,
so a skill on disk that the saved preference never mentioned — reinstalled by
hand, restored by a sync, left behind by drift — was deleted with no warning,
user files included.

The predicate now mirrors the host rule: everything installed that the draft
will not keep, regardless of what was saved before. It moved out of the sheet
into its own module so the rule has one owner and can be tested directly.
Saving with nothing to delete still does not prompt.

* fix(settings): a failed save keeps files the user added inside a skill

Save converges before it commits, and convergence deletes whole skill
directories. When the commit then failed, the recovery re-installed the
committed selection — which rebuilds the bundled files and nothing else, so
notes, hooks, and anything else the user had put inside a deselected skill
were gone for good. Cancel does not do that, and a failed save is supposed to
land where Cancel would.

Save now runs as a transaction. Before converging it stages a copy of every
managed skill directory across the three agent homes, next to the tree so the
copy stays on one filesystem. Committing drops the staging; failing anywhere
puts each directory back exactly as it was, deletes the ones the candidate had
created, and then drops the staging. Staging left by a process that died
mid-save is cleaned up by the next one.

The controller only sees begin/commit/rollback; the filesystem work lives in
skills-transaction.ts.

* fix(settings): make skill removal confirmation authoritative

* fix(settings): verify skill convergence before commit

* fix(settings): preserve concurrent skill edits on rollback

* fix(settings): preserve staged skill metadata

* fix(settings): stage skill deletions atomically

* fix(settings): stage removals per filesystem

* fix(settings): restore staged skill symlinks

* fix(settings): canonicalize skill transaction roots

* fix(settings): keep automatic skill updates non-destructive

* fix(settings): report committed skill saves accurately

* fix(settings): keep skill updates non-destructive

* fix(settings): route skill removals through selection

* fix(settings): close skill selection edge cases

* fix(settings): preserve skill data during maintenance

* fix(settings): hide unavailable skill install

* fix(desktop): keep install policy at command boundary

* fix(desktop): restore staged skill directories atomically

* test(app): retry transient skills sandbox cleanup

* fix(desktop): recover externally deleted skill stages

* fix(desktop): preserve conflicting skill rollback data

* fix(desktop): generalize skill rollback conflicts

* fix(desktop): preserve skill rollback collisions

* fix(desktop): recover quarantined skill backups

* fix(app): inset adaptive sheet content on compact

The compact Choose skills sheet rendered edge to edge: the All skills card
touched both screen edges, the section label was glued to it, and the skill
list ran to the edges too. Desktop was correct.

AdaptiveModalSheet expressed its content inset through
BottomSheetScrollView.contentContainerStyle. That is a library prop, not the
style prop Unistyles registers, and gorhom's scroller was never rewritten by
the Babel plugin, so on web the emitted class was never applied and the
padding and gap resolved to nothing. The desktop card uses a real ScrollView,
which Unistyles does track, which is why only compact was affected.

The inset now lives on a View the sheet owns, rendered by every presentation,
so one definition covers compact and desktop and a scroller only ever gets
theme-free layout. The caller-facing prop is contentStyle, since it no longer
lands on a scroll content container.

Choose skills drops its ad-hoc label for SettingsSection, so the label carries
the app's section rhythm instead of sitting equidistant between two cards.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>

* fix(desktop): prevent skill rollback data loss

Preserve externally replaced directory trees during recovery and reject managed child symlinks before synchronization can write outside a skill directory.

---------

Co-authored-by: Claude Opus 5 <noreply@anthropic.com>
2026-07-31 12:28:32 +02:00
Mohamed BoudraandGitHub b6f1274f47 Clarify changed and deleted files in the file viewer (#2670)
* fix(app): distinguish changed and deleted files

Successful reads now reconcile transient missing events without letting stale
cached data mask a deletion. File notices expose only actions valid for the
current buffer state.

* fix(app): preserve file state ordering

* fix(app): allow retrying file checks

* fix(app): clear unchanged file conflicts

* fix(app): keep file conflict recovery consistent

Own disk observation and read ordering in one model so stale reads and failed subscriptions cannot produce contradictory actions.

* fix(app): allow retrying file preview errors

Preserve daemon read errors and expose retry for initial and non-editable file loads.
2026-07-30 22:50:58 +02:00
Mohamed BoudraandGitHub 20e46e3e3f Restore project grouping across hosts (#2565)
* fix(projects): restore cross-host grouping

Random host-local project IDs replaced the remote-derived value that had also served as grouping identity. Persist a separate opaque group key and let normal boot reconciliation backfill older records without a migration.

* fix(projects): keep ambiguous clones separate

A shared remote can identify a project across hosts, but it cannot distinguish two independent clones on one host. Treat those keys as ambiguous so host-local project IDs remain separate.

* fix(projects): preserve host-local project identity

* fix(projects): strengthen grouping identity

* test(app): update project host shape contract

* fix(projects): preserve selected host identity

* fix(projects): close grouped routing gaps

* fix(projects): stabilize legacy subproject grouping

* fix(projects): preserve selected project identity

* fix(projects): isolate legacy host identity

* fix(projects): preserve symlinked bootstrap roots

* fix(projects): reconcile grouped custom names

* fix(projects): stabilize settings and path identity

* fix(projects): normalize default remote ports

* fix(projects): harden host and remote identity

* fix(projects): preserve host-scoped actions

* fix(projects): preserve remote path semantics

* fix(projects): model SCP remote semantics

* fix(projects): keep settings on routed host

* fix(projects): preserve SSH URL path semantics

* fix(projects): separate identity from presentation

* test(projects): use grouped identity in UI specs

* fix(projects): harden grouped project routing

* test(projects): expect framed settings route

* fix(projects): preserve exact placement identity

* fix(projects): harden grouping identity and routing

* fix(projects): restore project key as grouping identity

Random host-local project IDs had replaced the persisted cross-host value carried by projectKey. Keep projectId for routing while reconciliation owns and persists projectKey for grouping.

* fix(projects): prevent project key collisions

Preserve platform path semantics and generic remote transports in cross-host keys, and compare legacy checkout roots using filesystem equivalence before materializing ownership.

* test(projects): assert host-scoped project keys

* fix(projects): preserve project key compatibility

Resolve legacy workspace project references at the daemon boundary while keeping projectKey as the cross-host identity. Complete generic SSH normalization without collapsing distinct repositories.

* fix(projects): harden project reference identity

* fix(projects): retain archived reference errors

* fix(projects): scope legacy paths to hosts

* test(app): model persisted project keys

* fix(projects): retain absolute scp users

* fix(projects): resolve legacy mutation aliases

* fix(projects): preserve conservative identity boundaries

* fix(projects): preserve legacy placement identity

* test(projects): assert legacy placement identity

* test(projects): expect canonical Windows keys

* fix(projects): separate bootstrap identity fields

* fix(projects): scope legacy paths by host

* fix(projects): stabilize opaque project identity

* fix(projects): preserve remaining opaque identities

* fix(projects): classify forward-slash UNC roots

* fix(projects): preserve opaque selection keys

* fix(projects): frame host-local project keys

* fix(projects): canonicalize framed identities

* test(projects): isolate repeated remote fixtures

* fix(projects): keep project key as UI identity

* fix(projects): reconcile hydrated project references

* fix(projects): preserve archived project identity

* refactor(projects): group projects from project records, not workspaces

Cross-host grouping had been built by hanging a projectKey off every
workspace descriptor. That scoped the key to the wrong entity: the app
then had to reconstruct project identity per workspace in six modules,
which forced projectKey fallbacks into RPC arguments and a server-side
resolver that turned grouping keys back into project ids.

A workspace has a projectId. The key belongs to the project, and
grouping is a display concern, so the daemon now publishes projects
over project.list plus the existing project.update channel and the app
groups them in one place. Projects are addressed by host-local
projectId everywhere, including the settings route.

Projects persisted without a key are backfilled by ordinary boot
reconciliation, with no migration. Old daemons advertise no projectList
capability and simply do not group.

* test(projects): seed projects alongside workspaces in fixtures

Sidebar fixtures seeded workspaces without their projects, which the
daemon cannot produce now that projects arrive on their own channel, so
nothing rendered. Adds a seeding helper that supplies both.

Also drops the provisioning test for accepting a project key where a
project id belongs; that resolver was removed deliberately.

* fix(projects): keep derived project identity stable

Four gaps left by shrinking the key derivation:

Local keys used a bare path resolve, so one directory reached by a
different spelling produced a different key and reset grouped identity.
Windows CI caught this with the 8.3 and long-name forms of one temp
path. Canonicalize through the shared realpath-aware normalization.

Subproject display names lost their selected-path input, so every
subproject of a monorepo rendered under the same remote-derived name.

Reconciliation only ever backfilled a missing key, so a project whose
remote changed stayed grouped with hosts on the old remote.

Project settings kept a disconnected host selected and rendered a dead
end with no way back to an editable host.

* fix(projects): read scheme remotes as URLs, not scp form

An `ssh://user@host:22/org/repo` remote matched the scp-form pattern
first, because `[^@]+` happily eats `ssh://git`, so the port landed in
the path and the project keyed as `remote:host/22/org/repo`. Two
spellings of one remote therefore never grouped.

Try scp form only when there is no scheme, and drop protocol-default
ports so an explicitly written `:22` matches the same remote without it.
2026-07-29 23:36:31 +02:00
Mohamed BoudraandGitHub acb8506d1f Revert the timeline optimistic and pagination rework (#2596)
* Add npm release track invariant and beta dist-tag procedure

* Revert "Keep older chat history and image previews stable (#2490)"

The timeline optimistic/pagination rework needs more work than main releases can wait for. Reverted together with #2484; both are reapplied on integration/timeline-optimistic-rework.

* Revert "Stop completed turns from appearing stuck (#2484)"

See the #2490 revert. #2490 built on this submission-authority change, so the two revert and reapply as a pair.
2026-07-29 13:35:43 +02:00
Mohamed BoudraandGitHub 504b687f89 Keep older chat history and image previews stable (#2490)
* fix(app): keep timeline history and previews stable

Treat persisted timeline replicas as display-only so authoritative pagination always comes from the daemon. Require renewed user intent between older-history pages unless the viewport genuinely remains at the history edge.

Model assistant image acquisition as loading, loaded, or failed so recreating a preview URL cannot be rendered as an error.

* fix(app): keep file image previews current

* fix(app): stabilize history pagination edges

* fix(app): recover transient timeline loads

* fix(app): retain assistant image previews

* Stabilize history pagination lifecycle

* Stabilize history settlement and image previews

* Retry file images after reconnect

* Keep active previews and pagination requests alive

* Replace image hook tests with typed ports

* Integrate timeline hydration with submission authority

* Harden pre-hydration timeline reconciliation

* Make rewind E2E use real scroll intent

* Preserve live timeline state through hydration

* Keep mounted image attachments retained

* Protect preview persistence and lifecycle hydration

* Preserve idless live assistant continuations

* Preserve timeline rows across delayed hydration

* test(app): cover real assistant image files

* Preserve assistant tool ordering during hydration
2026-07-28 22:29:03 +02:00
f8dd0fc2e0 Switch projects from New Workspace with ⌘P/Ctrl+P (#2110)
* feat(app): add ⌘P shortcut to switch project on New Workspace screen

Opens the existing project picker with its search focused so the project
can be switched from the keyboard (type + Enter) instead of clicking the
badge and then the project.

Wires a new "workspace.project.pick" action through the standard keyboard
pipeline (binding -> route passthrough -> dispatcher), handled by a
screen-scoped handler on the New Workspace screen that is only registered
while the screen is mounted and there are projects to pick. Because
preventDefault only fires when a handler handles the key, ⌘P/Ctrl+P still
triggers native print everywhere else. Adds a Settings -> Shortcuts help
row (rebindable) and the "Switch project" label across all locales.

* test(app): cover project picker shortcut

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-07-28 00:32:41 +02:00
Mohamed BoudraandGitHub fa1198c2be Stop completed turns from appearing stuck (#2484)
* fix(app): stop completed turns from appearing stuck

Turn activity was inferred from a user-message row flag, so a stale duplicate row could keep the working footer active after the turn ended. Track submission lifecycle separately and route every send path through the shared submission flow.

* fix(app): guard pending message identity

* test(app): measure submission layout independent of scroll

* fix(app): keep submitted messages consistent through reconnects

Track each in-flight send until its own RPC establishes acceptance, and keep canonical timeline placement authoritative. Restore legacy cached rewind IDs during cache deserialization.

* fix(app): close canonical submission races

* test(app): cover canonical submission races in browser

* fix(app): keep replacement submissions authoritative

Keep current pending rows out of legacy cache migration and leave ambiguous terminal lifecycle events to the daemon snapshot. This prevents fabricated rewind identities and stale completion events from marking replacement turns idle.

* fix(app): keep submitted messages stable across sync

Give submission transport and canonical timeline ingestion separate authority. Preserve every unresolved local send across replacement, reconcile provider identity once, and bridge RPC acceptance to authoritative running state without deriving lifecycle from timeline rows.

* fix(app): settle submissions in either acknowledgement order

Complete submission transactions when RPC and provider acknowledgement arrive in either order. Cache only transaction-owned local rows as transient data, preserve canonical ID-less prompts, and invalidate ambiguous legacy display caches instead of inventing provider identity.

* fix(app): keep agent visible during history handoff

Running and terminal updates could clear create continuity before the initial authoritative timeline arrived, leaving a streaming agent behind the loading screen. End the handoff only when authoritative history is applied.

* fix(app): settle attachment-only submissions

Canonical providers can acknowledge image-only prompts with empty text. Reconcile those events by client identity without rendering a blank canonical row.

* fix(agent): settle out-of-band message submissions

Accepted commands that do not allocate a foreground turn previously had no canonical user acknowledgement. Record the command before its handler runs so submission state and reconnect history converge through the normal timeline producer.

* fix(app): settle out-of-band submissions compatibly

* fix(app): preserve canonical prompt order

* test(app): keep workspace status check timing-independent

The workspace-status scenario asserted footer settlement before initial agent creation had necessarily completed. The dedicated draft-handoff coverage owns that lifecycle contract.
2026-07-27 21:27:10 +02:00
Mohamed BoudraandGitHub b97d6d13f3 Load complete chat history when reaching the top (#2481)
* 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.
2026-07-27 18:21:07 +08:00
Mohamed BoudraandGitHub 42ee5a5949 Restore archived agents from History (#2316)
* fix(app): restore archived agents from History

History navigation lost the explicit archived-agent intent during tab reconciliation, and workspace recovery stopped after restoring the workspace. Preserve the selected tab and recover its provider session as one action while serializing provider resume with timeline hydration.

* fix(app): preserve archived agent recovery invariants

* fix(server): preserve timeline hydration call shape

* fix(app): retain agent tabs through lookup

* fix(server): upgrade in-flight timeline broadcast
2026-07-22 15:03:09 +02:00
Mohamed Boudra a4d11cda2b fix(app): keep hidden chats current through brief switches 2026-07-21 20:25:50 +02:00
Mohamed BoudraandGitHub 2cb1b041dd Start new workspaces from pasted pull requests (#2290)
* fix(workspaces): start pasted pull requests from their branch

Recognized pull request links now select their branch directly instead of
waiting for a separate confirmation. Ensure single-branch clones also record
the fetched branch so the new worktree can track it.

* fix(workspaces): keep pasted PR checkout target-safe

* fix(workspaces): make pasted PR resolution deterministic

* fix(workspaces): preserve explicit checkout selection

A recognized pull request selects itself only when its attachment is added. Once the user chooses a branch, later composer edits must not derive checkout state from existing attachments.

* fix(composer): ignore stale pull request lookups

A lookup may finish after the workspace target changes. Only apply its attachment and selection event when the host, working directory, client, and remote still match the target that started it.

* fix(workspaces): make checkout choices authoritative

Arm automatic pull request selection only when a new PR link is detected. An explicit picker choice cancels that pending selection, existing draft attachments never infer checkout state, and target changes suppress carried-over lookups.

* fix(workspaces): stabilize pull request auto-selection

Keep the first PR selected when one edit contains several links, and accept a completed lookup when only the transport client changed for the same host and checkout.

* fix(composer): release removed pull request lookups

Release each auto-attach invocation when its composer state is abandoned, while continuing to discard any stale response.
2026-07-21 15:20:05 +02:00
Mohamed Boudra afeb4d18f8 fix(app): keep reload splash continuous
Treat desktop settings evaluation as part of daemon startup so restored app chrome cannot render between bootstrap phases.
2026-07-21 13:44:23 +02:00
Mohamed BoudraandGitHub 4bda2dfea9 Edit workspace files directly on web (#2270)
* feat(files): edit workspace files on web

Keep source buffers synchronized with host file changes and require an explicit overwrite or reload when revisions diverge.

* feat(panels): surface and protect modified tabs

Expose tooltip and modification state through the generic panel boundary so tabs can show stable metadata and guard every close route consistently.

* fix(tests): use portable fake timeout handle

* fix(files): harden editor conflict handling

Preserve modified panel state across tab eviction, use precise revisions for optimistic writes, coalesce concurrent file watchers, and localize the editor interface.

* fix(files): close editor concurrency gaps

Coalesce clean reloads, preserve subscriber identities and file permissions, suspend pending saves during close confirmation, and carry precise revisions through file reads.

* test(files): expect read revision metadata
2026-07-21 04:41:19 +08:00
Mohamed BoudraandGitHub 0cfb9b6b94 Keep sidebar pins visible while reopening (#2210)
* fix(app): keep sidebar pins visible while reopening

Hidden sidebars stop consuming live workspace updates but retain their last rendered entries so the opening animation never exposes an empty list.

* test(app): reuse sidebar pin flow
2026-07-18 22:26:31 +02:00
Mohamed BoudraandGitHub 7917532716 Keep focused agent timelines live and catch up instantly (#2196)
* fix(app): keep focused agent timelines live

Window focus was conflated with app visibility, so switching OS windows could remove the selected timeline subscription. Separate those signals, grace every visibility-driven removal, and cover retained history until authoritative catch-up completes.

* fix(app): keep timeline catch-up failures non-blocking

* fix(app): surface timeline catch-up failures

* fix(app): preserve file preview refocus

* fix(app): preserve optimistic catch-up flow
2026-07-18 16:30:59 +02:00
Mohamed BoudraandGitHub 39cb3dbb9c Treat every added folder as an independent project (#2098)
* fix(projects): detect when projects become Git repositories

Project identity was coupled to Git placement, while non-Git roots were dropped by session-scoped observation. Keep identity tied to the selected root and observe Git transitions daemon-wide so empty projects update without rehoming workspaces.

* fix(projects): preserve metadata across Git read failures

Refresh archived workspace facts before persistence and treat only confirmed non-repositories as non-Git so transient failures cannot rewrite stored project metadata.

* fix(projects): close project update races

* fix(projects): refresh checkout metadata when reopening folders

Persist worktree ownership separately from workspace kind and gate exact-root project creation on stable host identity. Refresh active and archived records so missed Git transitions cannot return stale checkout descriptors.

* test(projects): accept Windows aliases for repaired roots

* test(server): retry transient Windows cleanup locks

* fix(projects): propagate project metadata refreshes

Project-only Git transitions now fan out workspace updates for legacy clients. Explicit project creation refreshes stored kind, and equivalent cwd spellings reuse existing workspace records.

* fix(projects): refresh worktree source project kind

* fix(projects): preserve exact-folder runtime isolation

* fix(projects): preserve exact cwd across worktree lifecycle

* fix(projects): harden exact-folder worktree flows

* fix(projects): derive exact cwd from matched path identity

* fix(projects): preserve nested worktree lifecycle

* refactor(projects): centralize workspace placement

* test(e2e): verify isolated server ports

* fix(projects): preserve placement reshape compatibility

* fix(projects): validate created worktree placement

* fix(projects): preserve placement through workspace lifecycle

Archive and recovery were rediscovering or guessing checkout roots instead of consuming the persisted workspace placement. Make the record authoritative for exact cwd, backing worktree, and source repository, and classify stale paths before Git reconciliation.

* fix(worktrees): preserve source checkout root

Convert Git's common administrative directory back to the source checkout root when legacy archive placement is reconstructed.

* fix(worktrees): compare filesystem identities

Resolve discovered worktrees and teardown locations through the shared realpath-aware matcher so Windows short and long path spellings cannot split placement identity.

* fix(worktrees): centralize path containment

Route worktree ownership, listing, resolution, and deletion through the realpath-aware containment primitive so Windows path aliases cannot be filtered by an earlier raw prefix check.

* fix(worktrees): remove duplicate ownership discovery

* test(e2e): isolate daemon restart ownership

* fix(worktrees): make lifecycle operations transactional

* fix(workspaces): share git watches by cwd

* test(worktrees): make teardown proof portable

* fix(sync): integrate project updates with directory owner

* fix(workspaces): close reconciliation edge cases

* refactor(sync): remove duplicate project reconciliation

Keep workspace and project deltas behind the host directory transaction, with owner-boundary coverage for snapshot replay and queued full reconciliation.

* fix(sync): buffer project updates before hydration

Start the workspace transaction with the online connection epoch so project broadcasts cannot publish a partial directory before the authoritative snapshot commits.
2026-07-17 19:30:17 +02:00
Mohamed BoudraandGitHub 1977d330ed Reduce workspace, agent, and chat sync traffic (#2028)
* feat(sync): keep live data scoped and current

Only viewed chats receive live timeline rows, while directory state now uses one subscribed bootstrap followed by ordered deltas. Legacy clients and daemons retain their existing behavior through centralized compatibility gates.

* fix(sync): preserve selective delivery boundaries

Keep selective timeline capability app-owned, union viewed sets across shared sockets, and reconcile directory side effects from accepted state. Visibility and archive suppression now follow their existing authoritative boundaries.

* test(app): align browser fixtures with runtime contracts

* fix(sync): preserve mixed-client delivery guarantees

* fix(sync): finish paged history before advancing

* fix(sync): replay live deltas after refresh failures

* test(server): model socket capability lookup

* test(app): use platform shortcut for split-pane coverage

* fix(app): keep hidden timelines dormant

* test(app): normalize split-pane shortcut setup

* fix(app): preserve sync state across retries

* fix(app): preserve background sync state across races

* fix directory bootstrap reconciliation edge cases

* fix selective timeline compatibility races

* fix superseded directory sync races

* fix(sync): centralize directory replica ordering

Own agent and workspace refresh transactions in HostRuntime so reconnect epochs, buffered updates, and lifecycle invalidation share one ordering boundary. Route timeline metadata and mixed-capability stream delivery through their authoritative runtime sources.

* fix(app): keep timeline requests runtime-scoped

* fix(app): close directory bootstrap races
2026-07-17 17:03:34 +02:00
Mohamed BoudraandGitHub 5da6548aff Improve the archived workspace restore flow (#2002)
* fix(app): restore missing workspaces from History

Workspace and agent archival are separate lifecycles. Carry restore intent from History so closed agents can reopen archived workspaces without changing ordinary navigation.

* fix(app): wait for workspace hydration before restore

* fix(app): preserve History agent unarchive

* fix(app): reopen archived History agents outside active directory

History entries are not merged into the active session maps. Carry the row archive state through explicit restore intent so live workspaces still reopen without broadening ordinary navigation.

* fix(app): ignore stale History archive state for active agents

A cached History row can outlive a successful reopen. When the workspace exists, live session state now wins so an active agent is not interrupted by another refresh.

* fix(app): reopen every selected archived History agent

History entries without workspace IDs returned before restoration, and a second archived agent sharing an in-flight workspace restore was dropped. Preserve both agent-only and deferred reopen intent.

* fix(app): require explicit workspace recovery

* fix: preserve workspace recovery contracts

* fix: preserve workspace recovery compatibility

* fix: preserve recovered workspace session state

* fix: preserve terminal navigation timing

* fix(server): preserve successful workspace recovery

* fix: preserve workspace recovery intent
2026-07-16 17:22:37 +02:00
Mohamed BoudraandGitHub 6aba0370ae Make remote daemon update failures actionable (#2120)
* fix(settings): show daemon update failures

React Native web ignores Alert.alert, so failed remote updates reset to an enabled button without any user-visible explanation. Keep updater progress and failure as explicit rendered state, including the daemon's error, and cover the real browser-to-daemon failure path.

* test(settings): harden daemon update regression

* fix(settings): disable Desktop-managed daemon updates

Desktop-bundled daemons advertised npm self-update even though the install-origin checks always rejected it. Report Desktop ownership to clients, render actionable guidance, and refuse stale requests before touching npm.
2026-07-16 21:04:31 +08:00
Mohamed BoudraandGitHub f4509fe044 Open files in more installed editors (#2119)
* feat(desktop): open files in more installed editors

Detect bundled app commands as well as PATH launchers and preserve file positions through editor-specific launches.

* fix(desktop): preserve editor target compatibility

Keep existing file-manager preference ids, retain project context for file launches, and recognize Windows 64-bit IDE commands.
2026-07-16 14:39:55 +02:00
Mohamed Boudra 943d03ad99 fix(projects): separate registration from workspace setup
Adding or cloning a project now registers it and opens workspace setup
instead of creating a workspace implicitly. Keep Add Project independently
mounted from Search so closing one cannot control the other.
2026-07-15 23:13:55 +02:00
Mohamed BoudraandGitHub dfe3330ef8 Add a keyboard-driven project setup flow (#2097)
* feat(projects): add keyboard-driven project setup

* test(app): update add project loading assertion

* fix(projects): harden GitHub project setup
2026-07-15 21:16:10 +02:00
Mohamed BoudraandGitHub 90e0a0e353 Find and open workspaces from the command center (#2096)
* feat(app): add workspaces to command center

* fix(app): scale down command center typography

* fix(app): avoid workspace theme subscription

* fix(app): theme command center bottom sheet

* fix(app): tighten command center workspace results
2026-07-15 20:40:24 +02:00
Mohamed BoudraandGitHub 50ed0d0ab1 Fix desktop layout and window controls at half-screen width (#1983)
* fix(app): preserve desktop layout in half-screen windows

Treat native window controls as physical corner obstructions instead of deriving clearance from compact layout state. Keep 751px windows in desktop mode while preserving usable content when persisted sidebars are wide.

* fix(app): retain sidebar width across window resizes

* fix(app): yield app sidebar to narrow settings

* test(app): wait for mobile sidebar motion to settle

* fix(app): yield navigation to narrow workspace explorer

* fix(app): mark sidebar sizing helpers as worklets

* fix(app): hide app navigation when space is constrained
2026-07-14 23:01:14 +02:00
Mohamed BoudraandGitHub 1f5283f5a3 Surface cross-workspace subagents in their workspaces (#2061)
* fix(workspaces): show provider subagent activity

Provider subagent lifecycle lives outside managed agent snapshots, so workspace aggregation dropped it after the parent turn finished. Count running provider subagents against the delegation root and emit workspace updates when their status changes.

* fix(workspaces): surface cross-workspace subagents
2026-07-14 14:22:14 +02:00
Matt CowgerandGitHub 9ea58aae0c fix(server): stop OpenCode daemon crash on session close, fix #2014 (#2027)
* fix(server): patch OpenCode SDK unhandled rejection on stream abort

The SDK's SSE abort handler calls reader.cancel() without handling
its rejection. When Paseo aborts the event stream during normal
session close, that promise can reject and crash the daemon with an
unhandled rejection outside any Paseo-owned code path.

Patches both copies the SDK ships (dist/gen and dist/v2/gen, the
latter being what Paseo actually imports). Extends
postinstall-patches.mjs to support patch-package running from a
non-root cwd, since @opencode-ai/sdk lives in packages/server's own
node_modules rather than the hoisted root.

* fix(server): stop assuming OpenCode's default agent is "build"

OpenCode users can rename or delete any agent, including the built-in
"build"/"plan" defaults. Paseo was injecting a hardcoded "build"
agent in several places whenever no mode was explicitly requested:

- The protocol manifest declared defaultModeId: "build" for opencode,
  which AgentManager.normalizeConfig applied to every session
  (including internal metadata-generation sessions) before any
  provider-level logic ran.
- OpenCode's own normalizeOpenCodeModeId defaulted an empty/"default"
  modeId to "build" rather than omitting the agent field.
- Unattended child creates (e.g. subagents spawned by an unattended
  parent) forced modeId: "build" to express unattendedness, even
  though that's already carried by the auto_accept feature.

Now an unset mode stays unset end-to-end: the "agent" field is
omitted from OpenCode prompt/command calls entirely, letting OpenCode
fall back to its own configured default agent instead of Paseo
guessing one that may not exist.

* fix(server): validate agent create mode on the WebSocket session path

resolveAndValidateCreateAgentMode already rejects modes unknown to a
provider's discovered mode list, but it was only wired into the MCP
create path. App-created agents (create_agent_request) skipped it
entirely, so a client's remembered mode preference — which can go
stale when a user renames or deletes OpenCode agents — sailed through
to the provider and failed mid-turn with an opaque error instead of a
clear rejection at creation time.

resolveSessionCreateAgent now calls
providerSnapshotManager.resolveCreateConfig, matching the MCP path,
so an invalid mode now throws 'Invalid mode ... Available modes: ...'
immediately on create.

* chore: address review feedback on create-agent and postinstall script

- Document the cleanup-ordering constraint in resolveSessionCreateAgent:
  mode validation runs after buildSessionConfig (cwd isn't known until
  that completes), so a thrown validation error leaves any
  worktree/workspace buildSessionConfig created for the caller to clean
  up. session.ts already handles this for the worktree path; the
  directory-only workspace path has a pre-existing gap, not introduced
  by this validation.
- Log spawn errors from patch-package in postinstall-patches.mjs
  (e.g. ENOENT if it's missing from PATH) instead of failing silently.

* fix(server): don't fabricate OpenCode modes when discovery finds none

fetchModesFromClient and mergeOpenCodeModes fell back to
DEFAULT_MODES ([build, plan]) whenever OpenCode discovery returned
nothing — e.g. for a freshly-created worktree whose OpenCode server
hasn't loaded the project config yet. That fabricated list let
create-time mode validation accept a stale 'plan' preference that the
provider then rejects at prompt time (Agent not found: 'plan').

Return an empty list instead: OpenCode users can rename or delete any
agent, so any hardcoded fallback risks validating a mode that doesn't
exist. DEFAULT_MODES is retained only for description enrichment and
sort ordering.

* fix(app): reconcile stale selected mode against discovered modes on create

The mode picker displays modeOptions[0] when the stored modeId isn't
in the discovered list (e.g. a globally-remembered 'plan' that a
workspace's OpenCode config no longer defines), but the create request
still submitted the raw stored modeId. Result: UI showed 'Build' while
the request sent 'plan', which the daemon then rejected.

Reconcile the selected mode against the discovered mode ids when
building the create config (both the workspace draft composer and the
workspace-setup dialog), so the submitted mode matches what the picker
shows — falling back to the first available mode when the stored one is
absent. Mirrors the existing resolveEffectiveModel reconciliation for
models.

* test(server): fix opencode mode tests after fallback change + rebase

- Replace 'available modes include build and plan' (which relied on the
  removed [build, plan] discovery-failure fallback) with two tests:
  one asserting discovered agents map to modes, one asserting empty
  discovery yields no fabricated modes.
- Restore messageId fields in the four provider-subagent timeline
  assertions. These were accidentally dropped during rebase conflict
  resolution; the #2013 subagent code legitimately emits messageId, so
  the expectations must include it.

* test(app): fix e2e seed helper using invalid opencode mode

createIdleAgent seeded an OpenCode agent with modeId
'bypassPermissions' — a Claude mode OpenCode never had. This worked
before only because the session create path silently coerced unknown
modes to 'build'. Now that create-time mode validation rejects modes
the provider doesn't define, use 'build' + auto_accept (OpenCode's
unattended full-access equivalent), matching the rewind-flow helper.

Fixes the e2e failures across archive-tab, command-center-host,
settings-toggle-tab-regression, workspace-agent-tab-rename,
workspace-pane-remount, workspace-navigation-regression, and
worktree-restore specs, which all seed via this helper.
2026-07-14 19:00:53 +08:00
Mohamed BoudraandGitHub 13d6ad598e Keep New Workspace prompts when switching projects or hosts (#2036)
* fix(app): preserve New Workspace prompts across target changes

The New Workspace draft was keyed by the selected host and project, so changing either target replaced the visible prompt with another draft scope. Treat the screen as one persistent draft surface and migrate the newest active legacy draft.

* test(app): express draft persistence as user actions

* fix(app): clear stale picker PR context

* fix(app): persist picker attachment ownership

* fix(app): keep workspace target context safe
2026-07-13 15:36:48 +02:00
Mohamed BoudraandGitHub a788a0f843 Keep agent stream controls clickable under the scroll button (#2007)
* fix(app): keep stream controls clickable under scroll button

The full-width animated overlay won browser hit testing even outside the circular control. Keep positioning on a pass-through container and animate only the button-sized child.

* test(app): preserve scroll hit-test diagnostics

* test(app): align scroll hit-test fixture deterministically

* test(server): include OpenCode message IDs in subagent assertions
2026-07-13 04:30:02 +08:00
66445adc07 Show provider subagents in the subagents track (#2013)
* Add provider-owned subagent stream contract

* OpenCode provider: detect child sessions, attach adopted children to the hosting server, synthesize external turns

- Emit internal provider_child_session_detected / provider_session_deleted stream events from the global event stream (before the no-active-turn gate), including BFS hydration of existing descendants on subscribe
- Track child sessions in a registry mapping child session id -> hosting server url; resumeSession attaches via acquireExisting() so adopted children share the parent's server generation and keep receiving live events
- Synthesize external turns for adopted (externally driven) sessions so timeline, tool calls, and permission requests stream without a Paseo-initiated prompt
- Honor launchContext.env in resumeSession (acquireDedicated parity with createSession)
- Stop folding child tool parts into the parent's sub_agent action log; the parent row stays as a pointer

* Render provider subagents in workspace timelines

* fix provider subagent timeline reliability

* fix provider subagent lifecycle edge cases

* fix provider subagent event fidelity

* Fix provider subagent history fidelity

* Fix restored provider subagent state

* Fix provider subagent visibility edges

* Restore provider subagent timelines

* Page provider subagent history

* Forward subagent questions reliably

* fix(subagents): preserve OpenCode child context

* fix(subagents): harden provider timeline routing

* fix(subagents): preserve provider page continuity

* fix(subagents): retain live child prompts

---------

Co-authored-by: Omer <639682+omercnet@users.noreply.github.com>
2026-07-13 03:13:00 +08:00
Mohamed BoudraandGitHub 03dcda7c41 Archive any workspace from the app (#2003)
* 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
2026-07-11 18:22:31 +02:00
Mohamed BoudraandGitHub 61a9117a6b Restore fuzzy project folder search and desktop browsing (#1968)
* fix(projects): restore folder search and browsing

* fix(projects): preserve fuzzy search edge cases

* test(projects): move picker cleanup into fixture

* fix(projects): preserve absolute path descendants

* test(server): compare canonical search paths

* fix(projects): anchor root path suggestions

* fix(projects): anchor Windows root suggestions

* fix(projects): anchor directory search paths

* test(server): canonicalize directory search root

* fix(app): anchor rooted basename suggestions

* refactor(search): unify directory suggestions

Use one parameterized daemon search engine for project paths and workspace entries. Keep local recommendations as a small client overlay and remove unnecessary response metadata.

* fix(search): preserve filesystem root semantics

Accept absolute queries through configured root aliases while keeping traversal and output canonical. Keep blank suffix browsing matcher-independent and avoid metadata-keyed listing caches where Windows cannot invalidate them reliably.
2026-07-10 17:52:55 +08:00
Mohamed BoudraandGitHub 4fb70668f2 Manage the built-in daemon from one place in settings (#1938)
* feat(settings): consolidate daemon management into the host page

The app-level Daemon section and the localhost host page rendered the
same daemon card in two places. The host page is now the single home:
the Host sidebar group leads with Overview, and a desktop-only "Enable
built-in daemon" option in the host switcher recovers a removed
localhost connection. Old settings/daemon deep links redirect
(COMPAT-tagged).

* fix(settings): hide daemon recovery option when the server id probe fails

Review feedback: !query.isLoading treated an errored IPC probe as
resolved, so the recovery affordance could show without knowing the
local server id. Replace the resolved-boolean hook with a single
discriminated state hook (loading | error | resolved) — errors hide the
affordance and fall back to the settings root in the redirect. Also
collapses the two query subscriptions per caller into one.

* fix(settings): keep daemon recovery reachable when no hosts remain

Review feedback: removing localhost as the only host takes the settings
sidebar down its no-host branch, which never mounts the host picker —
so the "Enable built-in daemon" option was unreachable in the most
common recovery case. The sidebar now owns the single hook call, passes
it to the picker, and renders the same action as a sidebar row next to
"Add host" when no hosts exist.

* test(e2e): update settings host header to Overview

Test drift: the host section's sidebar/header label was intentionally
renamed to "Overview"; the spec still asserted the old "Host" title.
Also assert the retired app-level Daemon row stays absent.
2026-07-08 10:20:30 +02:00
Mohamed BoudraandGitHub d9b65e269f Scheduled runs each get their own workspace in the sidebar (#1934)
* 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.
2026-07-07 22:13:42 +02:00
Mohamed BoudraandGitHub bf5e3b47e7 Add a Schedules screen to manage recurring agents (#1246)
* feat: add Schedules screen to manage scheduled agents

Adds a Schedules section in the sidebar header (below Sessions) for viewing
and managing new-agent schedules: recurring jobs that spawn an agent on a
cron or interval cadence.

- Borderless table showing provider icon, model, cadence, next run, and
  status, with row actions to edit, pause/resume, run now, and delete.
- Create/edit sheet that reuses the agent provider/model/mode/working-
  directory pickers, plus an interval/cron cadence editor with presets,
  validation, and a UTC preview.
- Responsive: hover-revealed row actions on desktop, an always-visible
  action menu on mobile.
- Data layer over the existing schedule RPCs (list/create/update/pause/
  resume/delete/run-once) with optimistic pause, resume, and delete.

Scope is limited to new-agent schedules for now.

* Make the schedule form controls visually consistent

Merge the provider and model pickers into the single nested selector the
draft screen uses, render it as a full-width field, mute the placeholder,
proportion the cadence segmented controls, unify the cron preset chips,
and route the sheet scroll through the themed scrollbar.

* Address schedules review feedback

* Translate schedule hook client fallback errors

* Make schedules global and project scoped

* Stabilize project settings transport E2E

* Reset schedule model on cross-host project change

* Reject malformed cron step expressions

* Tighten schedules project query freshness

* refactor(schedules): clean up review feedback

* test(schedules): extract fake host setup

* refactor(schedules): share cron expression parsing

Use one structural cron parser for app previews and daemon cadence validation so the accepted grammar cannot drift between surfaces.

Remove the unused host-scoped schedules redirect while keeping the global schedules route.
2026-06-30 22:58:49 +02:00
Christoph LeiterandGitHub 2b46caa20e feat(app): clearer interactive question card
Fixes #1642
2026-06-30 22:53:12 +02:00
Mohamed BoudraandGitHub cb486c3a5a Speed up app Playwright CI (#1830)
* ci(playwright): shard app e2e in CI

Run the app Playwright suite across isolated CI shards and keep restarted E2E daemons on the same speech-disabled setup path as global setup.

* test(app): share disabled speech e2e env
2026-06-30 12:37:47 +02:00
Mohamed BoudraandGitHub e9431517a0 Show host in search results and filter sidebar by multiple hosts (#1825)
* feat(app): show host in command center and multi-select host filter

Command-center agent results now show which host the workspace lives on,
shown only when more than one host is connected (matching the sidebar).

The sidebar display-preferences filter becomes multi-select — pick any
number of hosts instead of one-or-all — and its host rows now carry a
status dot on the left like the other host pickers. Persisted filter
state migrates from the old single host to a host list.

* test(app): extract host-filter and command-center e2e DSL helpers

Move the mechanical menu/search interactions out of the new spec bodies
into focused helpers so the tests read as user intent.

* fix(app): drop the icon from the host filter "All hosts" item

* fix(app): make multi-host e2e seeding order-independent

The offline-host seed relied on Playwright running the test's init script
after the fixture's registry reset — an ordering Playwright does not
guarantee. Write the full registry and set the fixture's disable-once flag,
then reload, so the multi-host seed survives regardless of script order.

Also tag the pre-v2 hostFilter migration reader with COMPAT so the cleanup
sweep can find it.
2026-06-30 09:40:24 +02:00
Mohamed BoudraandGitHub 7c6152663e Show found desktop updates after manual checks (#1815)
* fix(desktop): report found app updates during manual checks

Manual checks could reuse cached update state while the renderer discarded pending update details. Keep found update versions visible while downloads prepare, and reserve install affordances for ready updates.

* test(desktop): cover manual update retry after errors

* fix(desktop): keep ready updates ready on recheck
2026-06-30 08:12:15 +02:00
Mohamed Boudra ad803005c8 feat(app): show the connection in use on host switcher rows
The old marker only ever tagged the local host as "Local" based on its
server id. Each row now shows the connection actually in use, inline
after the host name — the TCP or relay endpoint (default :443/:80
stripped), or "Local" for socket/pipe transports.
2026-06-29 23:11:29 +02:00
Mohamed BoudraandGitHub 85acaceb16 Fork assistant turns into new drafts (#1788)
* feat(chat): fork assistant turns into new drafts

Builds chat-history attachments on the daemon and routes them into draft composers so users can continue an assistant turn in an existing workspace tab or New Workspace.

* fix(chat): clean up fork draft handling

* fix(chat): address fork draft review feedback

* fix(server): build fork context from bounded timeline

* fix(app): restore assistant turn footer in streams

* fix(app): preserve fork draft setup

* fix(app): tighten assistant fork footers

* fix(app): lock active fork draft handoff

* fix(app): preserve attachment-only draft retries

* fix(app): unify composer attachment scopes

* fix(app): align chat history attachment labels

* fix(app): centralize attachment pill content

* fix(app): trim fork context for workspace naming
2026-06-29 18:29:59 +02:00
Mohamed BoudraandGitHub b613bea9f6 Default client RPC waits to 60 seconds (#1789)
* fix(client): wait longer for session responses

* fix(client): default RPC waits to sixty seconds

* fix(app): preserve detached stream scroll on delayed history

Code drift: longer client RPC waits let delayed timeline responses arrive after a user scroll-away, so web stream anchoring must not reattach on transient scroll-top resets.

Restore the 15s connect deadline and leave app initialization slack above the default 60s session RPC wait.

* fix(client): keep helper waits within caller deadlines

Review fix: the 60s default session RPC wait leaked into wait previews and waitForAgentUpsert helper fetches. Bound those helper RPCs to the caller deadline or a short best-effort preview timeout, and allow small scroll ranges to reattach at bottom.

* fix(client): respect caller timeout budgets

* fix(cli): keep diagnostic probes responsive

* Refactor daemon client request options

* Preserve daemon client legacy overloads
2026-06-29 17:23:18 +02:00
Mohamed BoudraandGitHub 4968969c87 Preserve chat scroll-away after delayed history 2026-06-28 19:10:12 +07:00
Mohamed BoudraandGitHub 65f1143e6a Attach dropped files in every composer (#1750)
* fix(app): attach dropped files in composer

Composer parents had to expose attachment callbacks, so New Workspace only wired image drops and JSON files never became file attachments. Centralize dropped-file ingestion in Composer so all composer surfaces share the same image and file path.

* fix(app): handle desktop dropped attachment paths
2026-06-27 09:03:04 +08:00
Mohamed BoudraandGitHub 3fbd82664d Make New Workspace an app-wide screen (#1746)
* fix(app): make new workspace global

New Workspace now opens as an app-wide screen. Host and project context can still preselect the form through query params, but the route itself no longer lives under a host.

* fix(app): simplify new workspace selection
2026-06-26 15:50:02 +08:00
Mohamed BoudraandGitHub a49c658d1f Merge sidebar workspaces across all connected hosts (#1538)
* 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
2026-06-26 12:16:49 +08:00