Commit Graph

1358 Commits

Author SHA1 Message Date
Mohamed Boudra
ac45f23213 Fix mobile sidebar regressions and workspace e2e 2026-04-20 16:38:12 +07:00
Mohamed Boudra
a6423f7f0a Improve workspace navigation and render isolation 2026-04-20 16:14:22 +07:00
Mohamed Boudra
403fb03c97 feat(app): retain workspace screens on sidebar switch 2026-04-20 12:55:58 +07:00
Mohamed Boudra
f8411d121e Separate explorer checkout from panel state 2026-04-20 12:40:39 +07:00
Mohamed Boudra
9ea75afd37 Fix welcome screen unistyles leak 2026-04-20 12:40:30 +07:00
Mohamed Boudra
8600f183b1 feat(sidebar): restore PR state color, swap badge order, brighten checks on hover
- PR icon color reflects merge state (open/merged/closed) via the
  existing getWorkspacePrIconColor helper.
- PR badge is rendered before the failed-checks badge in the workspace
  row.
- Hovering the PR badge swaps the GitPullRequest icon for ExternalLink
  in place (same size, no layout shift) and removes the trailing arrow.
- Hovering the checks row in the workspace hover card brightens the
  icon and "Checks" label to foreground.
2026-04-20 10:57:27 +07:00
Mohamed Boudra
002946d4dd fix(sidebar): prevent text selection when dragging project/workspace rows
The drag handle was attached only to the left sub-view of each row, so
grabbing the right half initiated no drag, and pointerdown on the branch
or project title raced the browser's text-selection start before
dnd-kit's activation threshold — often producing a text selection
instead of a drag.

Move dragHandleProps to the outer row wrapper, add userSelect: none to
the row styles, and tighten PointerSensor activation distance.
2026-04-20 10:49:46 +07:00
Mohamed Boudra
fb02bc8795 fix(workspace): collapse header loading skeleton to a single bar
The header now shows only one row of text, so drop the second skeleton
and widen the primary bar to match.
2026-04-20 10:46:23 +07:00
Mohamed Boudra
53baa16bee refactor(checkout-diff): rely solely on subscription for data
Drop the one-shot getCheckoutDiff query and use skipToken; the
subscribe_checkout_diff flow populates the cache on subscribe response
and updates. Derive isLoading from the presence of cached payload and
remove the now-unused isError/error fields from git-diff-pane.
2026-04-20 10:45:59 +07:00
Mohamed Boudra
67eddbd6a9 perf(explorer-sidebar): memoize animation context value
Stabilize animateToOpen/animateToClose with useCallback and wrap the
provider value in useMemo so consumers don't re-render on every parent
re-render.
2026-04-20 10:45:55 +07:00
Mohamed Boudra
044546ae0c perf(explorer-sidebar): skip PR queries when sidebar is closed
Thread isOpen down to SidebarContent so usePrPaneData stays disabled
until the user actually opens the sidebar.
2026-04-20 10:45:51 +07:00
Mohamed Boudra
bd748b6edc perf(workspace): gate side effects on stack route focus
Pass useIsFocused through as isRouteFocused rather than short-circuiting
the whole tree. Terminals query/subscription, checkout diff, providers
snapshot, keybindings, tab reconciliation, focused-agent writes, and
sidebar rendering all no-op when the route is in the stack but not
focused, so a hidden workspace screen no longer drives background work.
2026-04-20 10:45:48 +07:00
Mohamed Boudra
427664dfa7 fix(app): drop getId from workspace stack route
Expo Router maps getId to React Navigation getId, which reorders an
already-mounted workspace screen on Android native-stack/Fabric. Keep
workspace identity/retention outside the route-level API and leave a
comment warning against re-adding it.
2026-04-20 10:45:02 +07:00
Mohamed Boudra
a0169bf7f4 feat(app): export isDev platform flag and enable runtime metrics in dev
Adds a shared isDev constant alongside isWeb/isNative and wires daemon
clients to collect runtime metrics (10s interval) only in development.
2026-04-20 10:44:57 +07:00
Mohamed Boudra
afe0eaab5b Set APP_VARIANT=development in app startup scripts 2026-04-20 09:07:34 +07:00
Mohamed Boudra
64065061c5 chore(release): cut 0.1.60-rc.2 2026-04-19 23:21:12 +07:00
Mohamed Boudra
75f83b0b9b Merge refactor/session-store-identity into main 2026-04-19 22:36:10 +07:00
Mohamed Boudra
badec6f587 Merge orchestrate/notif-routing-presence into main 2026-04-19 22:35:37 +07:00
Mohamed Boudra
5a2708653c feat(server,app): redesign git/GitHub refresh architecture with server authority
Centralizes refresh orchestration in WorkspaceGitService and GitHubService:
server coalesces, deduplicates, and throttles git/GitHub reads; clients peek
snapshots (cold reads fall through to a one-shot fetch) and never drive
refreshes. Introduces a two-TTL model (consumer vs internal), force-emit
bypass so fingerprint-matched refreshes still propagate, notifyGitMutation
wiring at ~13 action handlers, adaptive GitHub polling based on check state,
and checkout_status_update / prStatus push-to-cache wire messages. Fixes the
stale diff-stat bug and the inverted base-branch diff direction, and flips
next-action affordances promptly after commit / PR merge.
2026-04-19 22:35:26 +07:00
Mohamed Boudra
06b3fc2ec6 fix(server,app,desktop): route in-app notifications by presence cascade
Replaces the per-client stale/visibility policy with one shared
presence cascade so a backgrounded Firefox tab can no longer steal
in-app notifications from an active Electron desktop window.

Server (`agent-attention-policy.ts`): exports `PRESENCE_THRESHOLD_MS`
(3 min) and `computeNotificationPlan({ allStates, agentId, reason,
nowMs })` returning `{ inAppRecipientIndex, shouldPush }`. Cascade:
focus suppression requires presence; otherwise the most-recent
present client wins (lower-index tiebreak); push fires only when no
client is present, and never on `error`. `broadcastAgentAttention`
calls the policy once per event.

Electron (`daemon-manager.ts`, `idle.ts`): registers
`desktop_get_system_idle_time` returning `powerMonitor
.getSystemIdleTime() * 1000`. Renderer wrapper
`getDesktopSystemIdleTimeMs()` is best-effort: rejected IPC, null,
NaN, Infinity, or negative values return null without throwing.

Hook (`use-client-activity.ts`): Electron-only effect polls every 5s
and updates `lastActivityAtRef` only forward, so OS-wide activity
keeps a backgrounded Electron window present while in-window events
still update immediately.

`ClientHeartbeatMessageSchema` is unchanged; old/new clients and
daemons remain wire-compatible.

Tests: 52 focused tests across policy unit, dispatch, e2e, hook,
desktop wrapper, and permissions. Phase-3 dispatch test +
Phase-5 stale-poll test verified by mutation (breaking the focus
check / the `>` guard makes the exact regression fail).
2026-04-19 21:48:39 +07:00
Mohamed Boudra
bd1fb311ea refactor(app): reshape session store reads around narrow hook surface
Eliminate sidebar re-render cascades by fixing both sides of the
subscription contract:

- Session-store writes preserve identity: setWorkspaces, mergeWorkspaces,
  removeWorkspace, and patchWorkspaceScripts return the previous Map/entry
  reference when content is unchanged.
- HostRuntimeController.updateSnapshot only notifies when a patched field
  actually changed; idle probe ticks no longer replace the snapshot.
- New canonical hook surface in stores/session-store-hooks.ts
  (useWorkspace, useWorkspaceFields, useWorkspaceStructure,
  useWorkspaceKeys, useResolveWorkspaceIdByCwd,
  useWorkspaceStatusesForBadges, useWorkspaceExecutionAuthority,
  useRecommendedProjectPaths, useHasWorkspaces). These are the only
  place to subscribe to workspaces.
- 15 greedy subscribers migrated to the hook surface. The sidebar is now
  driven by useWorkspaceStructure for identity/ordering; each row
  hydrates its own descriptor via useWorkspaceFields, so a workspace
  update re-renders only its row. Archive-redirect callbacks switched to
  event-time useSessionStore.getState().
- Deleted dead code: use-session-directory.ts, workspace-fetch-debug.ts,
  and the unused SidebarProjectEntry aggregates (activeCount,
  totalWorkspaces, aggregated statusBucket).

No user-visible behavior change. Targeted tests green (79 tests across
host-runtime, session-store, session-store-hooks, sidebar list, sidebar
row model, sidebar shortcuts, workspace source of truth).
2026-04-19 20:19:45 +07:00
Mohamed Boudra
8991d34f78 fix(app): allow collapsing the parent folder of a selected file (#500)
The file explorer had a useEffect that re-enforced expansion of every
ancestor of `selectedEntryPath` whenever `expandedPaths` changed. As a
result, the moment a user collapsed the parent folder of a file they
had just opened, the effect re-added that folder to the expanded set
and the folder appeared stuck open.

Selection and expansion are orthogonal: clicking a file should not
dictate that its parent folder stays open forever. There is also no
current caller that sets `selectedEntryPath` from outside the tree, so
the reveal-on-external-selection use case the effect was designed for
is unused. Delete the effect and its two now-dead helpers. If a future
"Reveal in Explorer" action is introduced, it can expand ancestors at
the call site.

Adds an E2E regression test that reproduces the original scenario:
expand a folder, open an image, collapse the parent folder, assert
children are hidden. Also asserts an unrelated sibling folder still
expands after the image is open, guarding against any future regression
that makes the bug global.
2026-04-19 16:49:09 +08:00
Mohamed Boudra
d9d6509880 fix(test): resolve Metro .web.ts variants in vitest
message-input.tsx imports ./composer-height-mirror, which only exists
as .web.ts / .native.ts / .d.ts. Metro picks the right variant at build
time, but vitest was using Vite's default extension list and failing to
resolve the import — breaking app tests in CI.

Prepend `.web.*` variants to `resolve.extensions` in both the root and
app vitest configs so test runs (which already alias react-native →
react-native-web) pick the web implementation, matching what Metro
does for the web bundle.
2026-04-19 15:11:37 +07:00
Mohamed Boudra
e373c33108 feat(app,server): add pull request pane to the workspace sidebar
Introduce a PR tab in the explorer sidebar that shows status checks,
reviews, and the merged timeline for the current branch's pull
request. Extend checkout PR status with number, draft flag, repo
identity, and per-check workflow/duration; add a new
pull_request_timeline request/response pair backed by a GraphQL
query; enrich the workspace git snapshot to carry the fields through
to the client; and extend the checkout-git-actions store so timeline
queries are invalidated alongside status when mutations land.
2026-04-19 14:26:52 +07:00
Mohamed Boudra
d3393b434a feat(app,server): rank new-workspace picker by recency with debounced search
Plumb updatedAt through GitHub issue/PR search and committerDate through
branch suggestions so the new-workspace picker interleaves branches and
PRs sorted by last activity. Debounce the search query so remote GitHub
searches are driven by what the user typed rather than a static list.
2026-04-19 14:26:51 +07:00
Mohamed Boudra
43bbf8cb12 feat(app): collapse workspace header button labels on narrow rows
Measure the header row width and hide text labels on the Open,
Scripts, and git action split buttons below 700px so the header
fits without wrapping on split-pane layouts.
2026-04-19 14:26:51 +07:00
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