Commit Graph

1332 Commits

Author SHA1 Message Date
Mohamed Boudra
cfa3b009e3 fix(app): prevent branch switcher title from overflowing its row
Wrap the branch icon and title in a flex row with minWidth 0 and
overflow hidden so long titles truncate instead of pushing the
switcher off-screen.
2026-04-19 14:26:51 +07:00
Mohamed Boudra
f411a2a383 feat(app): rank combobox options by word-boundary match quality
Introduce a shared scoreMatch utility that tiers matches by
word-boundary vs mid-word offset, and use it to rank combobox
options so e.g. "py" prefers "a/py" over "happy".
2026-04-19 14:26:51 +07:00
Mohamed Boudra
5a232d8cf1 fix(server,app): coalesce tool call stream events to prevent JS thread freeze
Pi's ACP binary streams 200+ redundant tool_call_update events per tool call,
bypassing the server coalescer and flooding the app with synchronous state
updates. Two-layer fix:

Server: extend AgentStreamCoalescer to handle tool_call items via last-write-wins
per callId (200ms window, up from 33ms). Terminal statuses flush immediately.

App: mergeToolCallDetail and appendAgentToolCall now return existing references
when fields are unchanged, preserving React.memo equality checks.
2026-04-19 14:26:50 +07:00
Mohamed Boudra
2238171955 fix(app): composer textarea shrinks back down on web
Measuring scrollHeight on the live textarea with an explicit height
set can only grow — it can never report a shorter content height.
The old `height: auto` toggle flickered across browsers.

Use a hidden off-DOM textarea that mirrors the composer's text-metric
styles and width, and read its scrollHeight on value/resize changes.
Gated by Metro file extension (.web / .native) so it's trivial to
rip out later.
2026-04-19 14:26:50 +07:00
Mohamed Boudra
fbc2371b95 feat(server): replace Pi ACP with direct SDK provider (#202)
* feat(server): replace Pi ACP integration with direct SDK provider

Replace the ACP-based Pi provider (which spawned a `pi-acp` subprocess
and talked JSON-RPC over stdio) with a direct in-process integration
using `@mariozechner/pi-coding-agent` as a library.

The new `PiDirectAgentClient` and `PiDirectAgentSession` use the Pi SDK
directly — creating sessions via `createAgentSession()`, managing models
via `ModelRegistry`, and mapping Pi's `AgentSessionEvent` stream to
Paseo's `AgentStreamEvent` types including:

- Thread/turn lifecycle events
- Assistant text and thinking/reasoning deltas
- Tool execution (bash, read, write, edit, find, grep, ls) with arg
  caching across start/update/end events
- Compaction events
- Session persistence and resume via `SessionManager`

Thinking levels flow through the existing `thinkingOptionId` /
`setThinkingOption()` infrastructure, not through features.

Includes 9 real e2e tests against a live Pi agent covering tool calls
(bash/read/write/edit), reasoning chunks, session persistence/resume,
model listing, and thinking option management.

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

* fix: update lockfile signatures and Nix hash

* refactor(server): improve Pi direct provider code quality

Address coding standards violations in pi-direct-agent.ts:

- Replace all `as Record<string, unknown>` / `as any` / `as unknown as`
  casts with properly typed boundaries using Pi SDK's exported tool
  input types (BashToolInput, ReadToolInput, EditToolInput, etc.)
- Introduce named interfaces at all boundaries: PiPromptPayload,
  ToolCallOutputSummary, PiModelReference, PiPersistenceMetadata
- Parse tool args once at boundary into typed discriminated union,
  then trust types internally — no more typeof probing in mappers
- Break all dense ternary chains into named steps / helper functions
- Import SDK types directly (RegisteredCommand, ResourceLoader, Skill)
  instead of casting through anonymous inline shapes
- Isolate the system prompt SDK escape hatch into a minimal boundary
  helper with a named interface

Behavior is preserved — all 9 real e2e tests still pass.

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

* fix: regenerate package-lock.json with resolved/integrity fields

The previous lockfile regeneration ran under an offline/cached npm
context and stripped resolved URLs and integrity hashes from existing
entries, which would break npm ci and Nix builds.

* fix: update Nix npmDepsHash for regenerated lockfile

* chore(server): bump @mariozechner/pi-coding-agent to ^0.67.68

* fix: update lockfile signatures and Nix hash

* fix(app): surface agent setter RPC errors as toasts

setAgentMode/Model/ThinkingOption/Feature errors from the daemon were
only logged to console, so failures like missing API keys silently
vanished. Add toast.error fallbacks at all call sites and hoist
ToastProvider above SessionProvider so session context can use it.

---------

Co-authored-by: Claude Opus 4.6 <noreply@anthropic.com>
Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-19 14:45:01 +08:00
Mohamed Boudra
c53d42acf8 chore(release): cut 0.1.60-rc.1 2026-04-18 20:56:49 +07:00
Mohamed Boudra
07714773cc Revert "chore(release): cut 0.2.0-rc.1"
This reverts commit 9fb0ab9b2d.
2026-04-18 20:53:24 +07:00
Mohamed Boudra
9fb0ab9b2d chore(release): cut 0.2.0-rc.1 2026-04-18 20:46:06 +07:00
Mohamed Boudra
7819019cea feat(server,app,website): prefix service URLs with project slug
Service hostnames now follow DNS convention with a project label so
independently opened projects no longer collide on routes like
`web.localhost`. Format becomes `{script}.{project}.localhost` for
default branches and `{script}.{branch}.{project}.localhost` for other
branches. The project slug comes from the GitHub repository name when a
remote exists, otherwise from the workspace directory basename.

Server changes:
- `buildScriptHostname` takes `{ projectSlug, branchName, scriptName }`
  and slugifies all labels at the boundary; `untitled` fallback only
  applies at hostname-label sites, never to shared `slugify`.
- `deriveProjectSlug(cwd)` and `parseGitHubRepoNameFromRemote` derive
  the slug; GitHub URL parsing now requires `host === "github.com"` to
  avoid embedded-path false matches.
- `ScriptRouteEntry` carries `projectSlug`; branch-rename handler reuses
  it instead of re-reading git in hot paths.
- Status projection derives slug once per call and uses route hostname
  when available; only falls back to building when no route exists.
- Spawn cleanup removes routes by `{ workspaceId, scriptName }` so
  cleanup after a branch rename doesn't leave a stale entry behind.

App and shared message tests updated to the new fixture shape; service
URLs continue to be opaque server-provided strings.

Landing-page service URL examples updated; allowlist docs unchanged.
2026-04-18 19:31:23 +07:00
Mohamed Boudra
23b20169ef test(app,server): align e2e specs with settings redesign, drop rg dep
- worktree-core test scans the server source via a pure Node walker
  instead of shelling out to `rg`, which is not installed on CI runners.
  Invariant and scope are unchanged.
- settings-host-page specs target the new detail-header label + rename
  affordance (settings-detail-header-title / host-page-label-edit-button)
  since the label card moved into the shared header as part of the
  modular settings refactor.
- settings-navigation compact tests assert the new two-tier Back
  semantics: root Back exits settings, detail Back returns to /settings.
2026-04-18 18:07:07 +07:00
Mohamed Boudra
4de4e90325 fix(app): close theme split escape hatches 2026-04-18 16:25:23 +07:00
Mohamed Boudra
9f6570ca3e Merge branch 'config-work' into dev 2026-04-18 16:08:57 +07:00
Mohamed Boudra
f483e62231 wip(app): keep keyboard shortcuts alive on /settings
Chrome gating currently disables shortcuts on non-workspace routes. Temporary
pathname check until chromeEnabled is split into separate workspace/global
concerns.
2026-04-18 16:08:43 +07:00
Mohamed Boudra
72e4476acc refactor(app): replace providers-snapshot invalidate with refetchIfStale
invalidate() flagged the query stale and kicked off a refetch unconditionally,
causing a loading flash every time the model selector opened. Expose
refetchIfStale() instead so callers only refetch when data is actually stale.
2026-04-18 16:03:13 +07:00
Mohamed Boudra
5d76cc8840 feat(app): gate return-key submit icon to new workspace composer
The new-workspace composer is a one-shot "create" submit, not a chat send, so
a return-key glyph reads more accurately there than an arrow. Add a submitIcon
prop on Composer/MessageInput (defaulting to "arrow") and opt NewWorkspace
into "return".
2026-04-18 16:03:08 +07:00
Mohamed Boudra
b0e0786419 fix(app): make cmd+o work regardless of sidebar visibility
The worktree.new keyboard handler was registered inside ProjectHeaderRow,
which only mounts when the sidebar is open. Register a single global handler
in the app layout keyed off the navigation-active workspace selection.
2026-04-18 16:03:01 +07:00
Mohamed Boudra
4d6a154f6e Fix Unistyles theme splits 2026-04-18 15:06:42 +07:00
Mohamed Boudra
5c3cc99d8c refactor(app): restructure settings with modular section navigation 2026-04-18 14:21:40 +07:00
Mohamed Boudra
14cc1cc83c feat: support creating worktrees from GitHub pull requests 2026-04-18 12:08:30 +07:00
Mohamed Boudra
59d9b2ebdd fix(app): bridge trigger and content in workspace hover card
Closes the hover card via a safe-zone hook that covers the trigger, the
content, and the rectangular bridge between them, so crossing the visual
gap no longer drops the hover. Replaces the event-based close flow whose
dropped pointerleave events left cards stuck open.
2026-04-18 10:58:17 +07:00
Mohamed Boudra
d8ff049aff fix(app): only open tooltip on keyboard focus
Modal close restores focus to the tooltip's trigger, which was firing
onFocus and re-opening the tooltip. Gate onFocus on input modality so
only keyboard-driven focus opens it, matching W3C tooltip behavior.
Also removes the key-based remount workarounds that existed solely to
sidestep this bug.
2026-04-18 10:10:38 +07:00
Mohamed Boudra
f08bff1b2d fix(app): centralize workspace diff stat rendering
The source-control button rendered diff stats inline with no formatting
while the sidebar used toLocaleString, so identical numbers looked
different. Route both through DiffStat and switch to compact notation
(e.g. 87,700 -> 87.7k) so large counts stay readable.
2026-04-18 09:56:14 +07:00
Mohamed Boudra
aac780ddf7 Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	nix/package.nix
2026-04-17 16:16:04 +07:00
Mohamed Boudra
49d5426059 fix(app): swap github icon for external link on hover in checks row 2026-04-17 15:54:40 +07:00
Mohamed Boudra
d6585f7c3a fix(server): harden worktree archive with parallel teardown
Fan out agent close + terminal kill concurrently with Promise.allSettled
so one failure never leaves the worktree half-dead. Await real PTY exit
with SIGHUP→SIGKILL escalation before rm to avoid EBUSY races. Tolerate
missing repoRoot and retry directory removal with backoff. Purge stale
client workspace layout/tab state after successful archive.
2026-04-17 15:50:48 +07:00
Mohamed Boudra
ed2dc08443 fix(app): hide attachment remove X until hover on desktop
Keep the button visible on native and compact breakpoints so touch users
can still remove attachments without hover.
2026-04-17 15:46:58 +07:00
Mohamed Boudra
07d10f55f9 fix(app): stop spawning providers per client by warming the snapshot store
The workspace screen was prefetching per-provider model lists via
`useProviderModels`, which fanned out N parallel
`list_provider_models_request`s that bypassed `ProviderSnapshotManager`
and spawned each provider fresh on every call (e.g. repeated copilot
ENOENT errors). The return value was never consumed.

Replace it with `useProvidersSnapshot(serverId, workspaceDirectory)` so
warmup goes through the shared, deduped, TTL-cached snapshot path that
consumers already read from.
2026-04-17 15:05:28 +07:00
Mohamed Boudra
13bfdd8cc1 fix(app): land on agent tab after creating a workspace
Workspace creation landed on the setup tab because openTab implicitly
focused the tab it opened, and the setup auto-open effect ran after
the setup dialog's navigation, stomping on the agent tab's focus.

Split the store API into explicit openTabFocused and openTabInBackground
so every caller declares intent. The setup auto-open effect now opens in
background and no longer steals focus; user-intent call sites (sidebar,
kebab menus, file explorer, terminal creation, prepareWorkspaceTab)
use openTabFocused and drop the redundant paired focusTab call.
2026-04-17 14:45:22 +07:00
Mohamed Boudra
9a8b01a738 fix(app): unstick iOS image picker and land reusable Maestro primitives
On iOS, tapping "+" → "Add image" in the Composer left an invisible
backdrop trapping touches after the native photo picker dismissed. Root
cause: the dropdown menu's onSelect fired while the RN Modal was still
completing UIKit dismissal, so PHPicker presented over a dismissing
Modal that never cleanly released.

DropdownMenu now defers the selected item's onSelect until Modal
onDismiss (UIKit-level completion) plus a short buffer on iOS, then
invokes it. Same DropdownMenu instance — no per-call opt-in.

Along the way, the Maestro repro surfaced a controlled-TextInput
character-drop race: fast inputText on a controlled input drops
characters mid-type. Direct-host and pair-link inputs are now
ref-backed uncontrolled inputs with stable testIDs.

New reusable E2E primitives under packages/app/maestro/:
- flows/land-in-chat.yaml — clearState + direct connect to
  127.0.0.1:6767, lands in the composer. Compose on this for any
  composer-level fixture.
- image-picker-repro.yaml — regression for the zombie-backdrop bug.

docs/MOBILE_TESTING.md gains the patterns: reach-the-composer via
land-in-chat, uncontrolled inputs for Maestro-typed fields, and the
dropdown → native-presenter dismissal timing on iOS.

Existing relay-pairing and relay-then-direct fixtures updated to use
stable testIDs instead of ambiguous text / point-based taps.
2026-04-17 14:07:15 +07:00
Mohamed Boudra
adf2f5921f fix(app): unblock host index redirect on native by dropping window.location read
`window` exists on React Native (aliased to `global`) but `window.location` is
undefined, so reading `window.location.pathname` threw and killed the redirect
effect — leaving the host index route stuck rendering null (blank screen).
Use the `pathname` from `usePathname()` directly, restoring the pre-refactor
behavior that worked on both web and native.
2026-04-17 11:42:35 +07:00
Mohamed Boudra
3838a8b969 feat(app): unify composer attachments with pill + lightbox redesign
Images and GitHub issues/PRs now live in one draft-store list (ComposerAttachment union) with cwd persisted on the draft. Paperclip becomes a + drop-up menu; queue button removed. Each pill uses a shared AttachmentPill wrapper with a corner-X that mirrors the auto-update toast spec. Pill body opens the attachment: GitHub via openExternalUrl, images via a new fullscreen AttachmentLightbox. GitHub picker auto-closes on select.
2026-04-17 11:36:08 +07:00
Mohamed Boudra
40f8a8414a chore(release): cut 0.1.59 2026-04-16 22:10:49 +07:00
Mohamed Boudra
981b94fa56 fix(app): balance composer right-side button spacing
Tightens transparent icon gaps to 4px and gives filled action buttons
(send, interrupt) a 4px left margin so they sit 8px from neighbors.
Shrinks context-window meter SVG from 20 to 16 to match the visual
weight of surrounding icons.
2026-04-16 20:30:16 +07:00
Mohamed Boudra
827092a246 fix(app): avoid infinite render loop in FilePanel zustand selector
The selector returned a freshly constructed authority object each call, so
useSyncExternalStore saw a new reference every render and force-rerendered,
producing a maximum update depth crash. Select the specific workspace
descriptor (stable reference) and derive the authority via useMemo.
2026-04-16 20:18:56 +07:00
Mohamed Boudra
6ffffc38a0 fix(app): update route test expectations for b64_ workspace ID prefix
The opaque-workspace-identifier refactor (e6044264) added a `b64_` prefix
to base64-encoded workspace IDs in route builders. Two app tests still
expected the unprefixed form and failed in CI.
2026-04-16 19:51:00 +07:00
Mohamed Boudra
a5582e0ee0 Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	packages/server/src/server/session.ts
2026-04-16 19:37:29 +07:00
Mohamed Boudra
727aa0cdac chore(release): cut 0.1.58 2026-04-16 19:17:37 +07:00
Mohamed Boudra
41fd45588f chore(release): cut 0.1.57 2026-04-16 16:16:30 +07:00
Mohamed Boudra
21742bbeb4 Merge remote-tracking branch 'origin/main' into dev
# Conflicts:
#	nix/package.nix
#	packages/app/src/components/adaptive-modal-sheet.tsx
#	packages/app/src/hooks/use-providers-snapshot.ts
#	packages/app/src/screens/workspace/workspace-screen.tsx
#	packages/server/src/server/agent/agent-management-mcp.ts
#	packages/server/src/server/agent/mcp-server.ts
#	packages/server/src/server/persistence-hooks.ts
#	packages/server/src/server/schedule/service.ts
#	packages/server/src/server/session.ts
2026-04-16 14:14:32 +07:00
Mohamed Boudra
11b407c804 fix(app): hide branch icon on non-git workspace headers
Gate the GitBranch icon behind isGitCheckout, and keep the header
skeleton visible until the checkout status query resolves so the icon
doesn't pop in and cause layout shift.
2026-04-16 13:14:00 +07:00
Mohamed Boudra
814098852c feat: provider model freshness TTL and diagnostic UI (#426)
* feat: add server-side TTL for provider snapshots and model list UI

Provider snapshots cached forever, causing newly available models
(e.g. OpenCode Go minimax, glm) to never appear in the picker.

Server: ProviderSnapshotManager now returns stale data immediately
and kicks off a background re-warm when the snapshot is older than
5 minutes. Injectable TTL/clock for testing.

App: Provider settings rows show model count and are tappable to
open a diagnostic sheet with a read-only model list (label + ID).

* feat: add per-provider refresh for models and diagnostic

* refactor: await refresh completion and clean up diagnostic sheet state

- ProviderSnapshotManager.refresh is now async; session.ts awaits it so
  the RPC ACK reflects real completion instead of just queuing work.
- Preserve models/modes/fetchedAt on entries during targeted refresh so
  the list no longer flashes empty mid-refresh.
- Show "Updated Xs ago" next to the Models count, plus loading and
  error states for the list body.
- Split resetSnapshotToLoading into full vs targeted branches.
- Flatten nested ternary in model list rendering into renderModelsBody.
- Drop redundant local refreshing state and cargo useMemo wrappers.

* fix(app): widen isImeComposingKeyboardEvent type to accept optional fields

TextInputKeyPressEventData has isComposing and keyCode as optional, so
Pick<KeyboardEvent, ...> was too narrow and broke typecheck on main.
2026-04-16 13:14:02 +08:00
Li Mu Zhi
5c6f175db5 fix: make code file preview text selectable on iOS (#447)
Add `selectable` prop to the code line Text component so users can
long-press to select and copy text on iOS. On web/desktop this is
already the default behavior via CSS user-select.

Markdown file preview has a similar issue but requires a different
fix (custom rules for react-native-markdown-display), left for a
follow-up.

Refs #238
Related #21

Co-authored-by: muzhi1991 <muzhi1991@limuzhideMac-mini-2.local>
2026-04-16 11:06:16 +08:00
amir
a1f3256923 style(app): theme native scrollbars across all web views (#399)
* style(app): theme native scrollbars in tool call detail views

Apply CSS scrollbar-color to all ScrollViews in ToolCallDetailsContent
and DiffViewer so the browser scrollbar matches the app's dark theme
instead of showing the default white native scrollbar.

* refactor: split useWebScrollbarStyle into .web.ts/.native.ts platform files

- Native shim returns undefined without calling useUnistyles
- Web variant uses properly typed WebScrollbarStyle interface instead of `as any`
- Add .d.ts for TypeScript module resolution

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-04-16 10:59:31 +08:00
Aaron Florey
1885d8602f feat(app): Render markdown files in file pane (#427)
Detect .md and .markdown files in the shared file pane and render them as markdown instead of syntax highlighted code. This makes README-style files easier to read without changing the existing preview flow for other text files or .mdx.

Keep the change focused to the app renderer by reusing the existing markdown stack and adding a narrow extension check with targeted coverage.
2026-04-16 10:33:49 +08:00
Rui Fan
ffbb2ffd06 fix: retry file explorer init when client reconnects after page refresh (#442)
hasInitializedRef was set to true before confirming the directory
listing request actually succeeded. On page refresh the WebSocket
client is still reconnecting, so requestDirectoryListing returned
early with "Host is not connected" and the ref stayed true —
preventing any retry once the client became available.

Fix: make requestDirectoryListing return Promise<boolean> and reset
hasInitializedRef on failure so the init effect re-runs automatically
when requestDirectoryListing is recreated after the client reconnects.

Fixes #441

Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 10:28:02 +08:00
Edvard Chen
45a5ba8637 Fix duplicate command args for generic ACP providers (#444)
* Fix duplicate command args for generic ACP providers

* Fix desktop IME enter handling
2026-04-16 10:23:05 +08:00
Mohamed Boudra
deca82653e chore(release): cut 0.1.57-rc.1 2026-04-15 21:02:07 +07:00
Mohamed Boudra
4e9d80d118 fix: prevent model name truncation in combobox item rows 2026-04-15 19:25:24 +07:00
Mohamed Boudra
e6044264e2 refactor: transition to opaque workspace identifiers 2026-04-15 17:59:09 +07:00
Mohamed Boudra
e635c6beca merge: integrate main into dev
Resolve 9 merge conflicts preserving both sides' intent:
- nix hash, bootstrap hostnames rename, websocket-server wiring
- session.ts handler dedup, snapshot fallback, worktree registration
- worktree-session git service + script/setup features
- sidebar-workspace-list setup navigation, session-context cleanup
- workspace-registry-bootstrap test kept (code still exists)
- checkout-git test additions from both sides

Post-merge fixes: isPaneFocused prop rename, server type rebuild.
2026-04-15 16:07:39 +07:00