Commit Graph

3108 Commits

Author SHA1 Message Date
Mohamed Boudra
92be6c0cba Replace skills auto-sync with explicit install/update/uninstall (#797)
* Replace skills auto-sync with explicit install/update/uninstall

Skills no longer sync silently on every desktop launch. Settings now
shows Install (fresh), Update (with a confirm dialog listing per-skill
add/update/delete ops), or Uninstall depending on what's on disk.

A manifest at ~/.agents/skills/.paseo-manifest.json tracks Paseo-owned
skills so uninstall removes only what we wrote, never user content.
Existing users migrate transparently — first read synthesises a
manifest from on-disk hashes, so an unchanged install flips silently
to up-to-date.

Also restructures the integrations layer: skills/ and cli-install/
each own their files behind a designed index.ts. The shared
integrations-manager.ts is gone.

* Rewrite skills install flow with disk-truth model

Drops the manifest sidecar, migration logic, and the
agentsDirHasSkillContent heuristic that misread user-authored skills
as Paseo content. Replaces them with a hardcoded PASEO_SKILL_NAMES
list (current bundle + paseo-chat for retired-skill cleanup); the
disk is the source of truth for what's installed.

Renames the SkillsState "fresh" → "not-installed" everywhere
(server, IPC parser, hook test mocks). Install and update share an
applySkills body. Errors propagate instead of being swallowed.

Fixes the case where Uninstall left state at "drift" because the
user had personal skills under ~/.agents/skills/.

* Stop symlinking claude skills, write real files

If the user has ~/.claude/skills symlinked to ~/.agents/skills, the
per-skill symlink path resolved through the parent symlink and fs.rm'd
the agents-side files we'd just written. Mirror real files to claude
the same way as codex; agents stays the canonical source-of-truth dir.

* Make Uninstall button visible (outline, not ghost)
2026-05-07 18:41:13 +08:00
Ethan Greenfeld
4f0b264886 Fix ACP terminal shell command spawning (#793) 2026-05-07 16:24:30 +08:00
Mohamed Boudra
bad304c2d3 Detect GitHub issue/PR URL in composer search
Pasting a GitHub issue or pull request URL into the composer's
issue/PR picker now searches by the number from the URL instead
of treating the full URL as a keyword that returns no matches.
2026-05-07 15:23:45 +07:00
Mohamed Boudra
0785ee31f0 Match model description in combobox search
Search now matches across model label, id, provider, and description
with multi-token fuzzy matching, so "kimi zen" finds the OpenCode Zen
Kimi model.
2026-05-07 15:23:16 +07:00
Mohamed Boudra
faf664d803 Fix desktop CLI passthrough tty handling (#791)
* Fix desktop CLI passthrough tty handling

* Fix desktop daemon manager test drift

* Update daemon stop test after main merge

* test(cli): isolate npx cache per CLI test
2026-05-07 15:14:18 +08:00
Mohamed Boudra
6fb2fe2283 Fix pane shortcuts in editable fields 2026-05-07 13:28:42 +07:00
Mohamed Boudra
cdce9a1235 Add spacing after Codex goal status 2026-05-07 13:08:45 +07:00
Mohamed Boudra
285d4edf23 fix(server): harden findExecutable + remove gratuitous realpath from spawn paths (#787)
* Phase 0: baseline tests for probeExecutable

* Phase 1: invert self node env handling

* Phase 2: refactor probeExecutable to execCommand

* Phase 3: push POSIX executable lookup to system which

* Fix probe test spawn-failure fixture

* Guard system which executable lookup
2026-05-07 14:03:15 +08:00
Mohamed Boudra
5c86956fef Render Codex image thread items as path markdown (#785) 2026-05-07 13:19:31 +08:00
Mohamed Boudra
80bdb4d45b Use tree-kill for daemon shutdown (#788)
* Use tree-kill for daemon shutdown

* fix: update lockfile signatures and Nix hash

* Use tree-kill for daemon shutdown

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-05-07 13:18:39 +08:00
Mohamed Boudra
7558bd7947 Fix dev override host bootstrap (#789) 2026-05-07 13:08:10 +08:00
Mohamed Boudra
16944da45b Fix MCP terminal capture scrollback 2026-05-07 11:59:13 +07:00
Mohamed Boudra
55ec13f153 fix(cli): honor PASEO_PASSWORD env var (fix #776) (#782)
* fix(cli): honor PASEO_PASSWORD env var (fix #776)

The CLI only extracted passwords from `tcp://host?password=` URIs, so
`PASEO_PASSWORD=xxxx paseo ls` connected without auth and was rejected
by the daemon with "Password required". Fall back to the env var when
the host (TCP or otherwise) carries no password.

* docs: document PASEO_PASSWORD env var as a CLI auth source

Pairs with the CLI fix in this branch — the "Connecting with a password"
section now lists both the tcp URI query and the env-var fallback, with
their precedence. Also clarify the dual role (daemon vs CLI) of
PASEO_PASSWORD in the env var reference.
2026-05-06 23:22:22 +08:00
Mohamed Boudra
c36f4cfee4 Vendor ACP provider catalog 2026-05-06 20:00:40 +08:00
Mohamed Boudra
ac375f152d Wrap desktop IPC in shared mutation/query hooks (fix #761) (#765)
* Wrap desktop IPC calls in shared hooks

* Unslop desktop IPC hook callsites

* Redesign desktop IPC hooks around domains
2026-05-06 19:58:17 +08:00
Zitao Xiong
24bdd3c8ad fix(relay): use TLS for any port-443 relay endpoint (#774)
* fix(relay): use TLS for any port-443 relay endpoint

`shouldUseTlsForDefaultHostedRelay` only returned true for
`relay.paseo.sh:443`. Self-hosted relays on port 443 connected via
`ws://` instead of `wss://`, causing HTTP 400 from the TLS server.

Port-based detection: port 443 → wss, everything else → ws.

* Test port-based hosted relay TLS detection

Cover the new behavior: any port-443 endpoint (hosted Paseo or
self-hosted) and IPv6 hosts return true, non-443 ports and malformed
input return false.

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-05-06 19:56:32 +08:00
Mohamed Boudra
7e8187ddc7 fix: never rename worktree branches from create_agent path
Auto-naming should only run during worktree creation (mnemonic on create,
async LLM rename moments after). Creating a new agent inside an existing,
properly-named branch must not trigger a rename.
2026-05-06 18:33:36 +07:00
Mohamed Boudra
2e45650f22 Allow self-hosted relays to opt into wss:// (#767)
* Add relay TLS schema fields

* Wire relay TLS into websocket URLs

* Propagate relay TLS from pairing offers

* Expose relay TLS opt-in

* Mark relay TLS fallback for removal

* Preserve explicit relay TLS false

* Align relay TLS defaults outside config loader
2026-05-06 17:25:16 +08:00
Mohamed Boudra
6c319d05dd feat(codex): wire /goal slash command with mid-turn support (#711)
Adds optional `tryHandleOutOfBand` on AgentSession so providers can
handle slash commands as side effects without allocating a turn or
tripping the activeForegroundTurnId gate. Codex provider implements it
for /goal (set/pause/resume/clear), version-gated to codex >= 0.128.0
with `--enable goals`. Mid-turn /goal pause now works alongside a live
turn against the same thread.

E2E test against real codex 0.128.0 covers set + mid-turn pause + clear.
2026-05-06 16:50:02 +08:00
Mohamed Boudra
9eafab5a50 Fix index startup navigation test mock
The cold workspace URL navigation fix changed the / startup path for a matching persisted workspace. Index no longer renders a workspace Redirect there; it calls navigateToWorkspace so the centralized workspace navigation path owns the transition, then keeps the startup splash visible while that imperative navigation runs.

Update the test mock to expose navigateToWorkspace and assert that behavior directly. This keeps the test aligned with the route-shape fix and prevents CI failures on branches rebased over main.
2026-05-06 15:46:01 +07:00
github-actions[bot]
efff5b8454 fix: update lockfile signatures and Nix hash 2026-05-06 08:23:22 +00:00
Mohamed Boudra
2c2ded7492 Upgrade Electron to 41.2.0 2026-05-06 15:22:12 +07:00
Mohamed Boudra
7932e38d0d Fix browser reload focus and devtools handling
Cmd+R was scoped through a stale Electron-side active browser value. Opening or focusing a browser pane set activeBrowserPaneId, but switching to an agent tab only updated workspace layout state. Electron still remembered the old browser and the app menu reloaded that hidden/previous browser instead of the app.

Make workspace layout the authority. Derive the active browser from focusedPaneId plus the focused pane's focusedTabId, and sync browserId|null to Electron from one WorkspaceScreen effect. BrowserPane no longer marks itself active from URL-bar or webview focus handlers, so call sites do not have to remember to clear Electron state when focus moves to an agent, terminal, or another pane.

Electron now treats the synced value as the workspace-active browser, not an independently observed focus source. Menu reload uses that workspace-derived browser when present; otherwise it reloads the app window. Direct Cmd+R events that originate inside a browser guest still reload that guest through the guest before-input-event path.

Browser webviews now own their guest context menu in the main process. Add copy/paste/select-all and a development Inspect Element action for attached browser guest WebContents.

DevTools opening is routed through the main process by browserId. Docked-right DevTools did not become visible for browser guest contents in practice, even though inspect mode activated, so the final behavior opens detached DevTools for both the toolbar button and Inspect Element. The renderer logs bridge/result failures, and main logs registration/open/inspect outcomes for future debugging.

Regression coverage is at the workspace layout derivation layer: focused browser id follows the focused pane's active browser tab and becomes null when the active tab is not a browser. Verified with targeted layout-store tests, navigation E2E, settings E2E, npm run typecheck, and npm run lint.
2026-05-06 15:15:15 +07:00
Mohamed Boudra
5e5fc97798 Fix cold workspace URL sidebar navigation
Directly opening a workspace URL created a different React Navigation state shape than entering from /. Expo Router parsed the URL as a workspace layout route with a nested index child whose route.path and params pointed at the original workspace. Later sidebar navigation used router.dismissTo for another workspace. React Navigation's POP_TO reused the existing workspace route by name and updated the parent params, but preserved the nested child state. The result was a hybrid state: parent workspace params for B, child index path for A. Expo Router serialized the focused child path, so the address bar stayed on A and sidebar clicks looked like no-ops.

The fix is to stop using workspace/[workspaceId]/_layout.tsx as the actual screen. That layout did not render a Slot, so it only added an extra navigation level for Expo Router to preserve. Move the workspace UI, bootstrap boundary, open-intent consumption, and retained WorkspaceDeck into the leaf index route, making /h/:serverId/workspace/:workspaceId one real rendered route.

Keep workspace switches on navigateToWorkspace/router.dismissTo. This is not another replace workaround: replace previously created duplicate mounted workspace shells. The route-shape fix removes the stale nested child state that made dismissTo fail after cold deep links.

Also route archive/new-agent and notification flows through the centralized workspace navigation path so they do not bypass the same route contract.

Regression coverage: cold-load workspace A, click workspace B in the sidebar, and assert the URL changes to B. Verified with workspace-navigation-regression.spec.ts, settings-navigation.spec.ts, npm run typecheck, and npm run lint.
2026-05-06 15:13:53 +07:00
Mohamed Boudra
868479c493 Fix workspace navigation regression on web (#772)
Centralize workspace navigation behind a small route-derived API so the workspace shell reads active selection from the current Expo Router route instead of a separately synchronized store.

Remove the stale layout-driven active-workspace write that regressed web after 02e5b564.
2026-05-06 13:22:31 +08:00
Mohamed Boudra
0eacc98e17 website: add /cloud design partners page and shared shell
- New /cloud page with signup form (email, name, company, role, message)
  posting to a Discord webhook via a TanStack Start server function on
  the Cloudflare Worker. Webhook URL read from DISCORD_WEBHOOK_URL secret.
- Extract SiteShell, SiteFooter, FAQItem from landing-page.
- Migrate /privacy, /download, /blog, /changelog, /cloud to SiteShell so
  they share header + footer with consistent width.
- Add /cloud to header and footer nav.
- Make /docs sidebar full-width; bump footer text from xs to sm.
- Add Cloudflare Workers types and ignore .dev.vars.
2026-05-06 10:45:14 +07:00
Mohamed Boudra
ea4d6bb7ce Extract cwd resolution into testable function 2026-05-06 08:55:11 +07:00
Mohamed Boudra
40543128f7 Fix Codex import default mode 2026-05-05 22:49:55 +07:00
Mohamed Boudra
eac392e366 docs: tighten skills blurb and add hr before star chart 2026-05-05 22:22:02 +07:00
Mohamed Boudra
5396811c58 docs: tighten skills blurb 2026-05-05 22:19:55 +07:00
Mohamed Boudra
11383fa356 docs: rename to Skills and reframe with capability blurb 2026-05-05 22:19:41 +07:00
Mohamed Boudra
322bb1d962 docs: replace skills code block with bullet list 2026-05-05 22:17:21 +07:00
Mohamed Boudra
2402620d2f docs: add community section with paseo-relay link 2026-05-05 22:15:48 +07:00
Mohamed Boudra
3ac3b71ad3 docs: add Community section with paseo-relay link 2026-05-05 22:14:49 +07:00
Mohamed Boudra
02e5b56408 Fix duplicate workspace shell navigation 2026-05-05 21:55:57 +07:00
Mohamed Boudra
60531b5e1b refactor: long-tail type-aware sweep (T3.c) (#759)
* refactor: long-tail type-aware sweep (T3.c)

Cluster T3.c — 15 files, 24 errors cleared.

Rules fixed:
- no-useless-default-assignment: 6 → 0 (mcp-server.ts — Zod .default() makes destructuring defaults redundant)
- no-unnecessary-type-assertion: 1 → 0 (workspace-registry-model.ts — remove redundant ! on string[])
- no-unsafe-enum-comparison: 3 → 0 (use-push-token-registration.ts, use-image-attachment-picker.ts — use PermissionStatus enum values)
- no-redundant-type-constituents: 6 → 0 (workspace-setup.ts, tool-call-detail-state.ts, workspace-draft-agent-tab.tsx, stream.test.ts, desktop-settings-commands.ts — unknown | T → unknown)
- no-unnecessary-type-parameters: 4 → 0 (indexeddb-attachment-store.test.ts, use-archive-agent.ts, class-mocks.ts, agent-stream-coalescer.ts — narrow interface / remove unused generics)
- no-unnecessary-type-conversion: 4 → 0 (session-context.tsx — remove String() on already-string values; class-mocks.ts — remove String(prop) after symbol guard)
- no-implied-eval: 1 → 0 (agent-stream-coalescer.test.ts — narrow timer interface to function-only, eliminating string overload)

Deferred (need caller updates or extra files beyond 15-file cap):
- no-unnecessary-type-parameters: host.ts:onResized/onDragDropEvent, electron/events.ts (callers pass typed handlers; removing generic breaks contravariance without cast)
- no-base-to-string: ~21 errors, 18 files
- restrict-template-expressions: ~11 errors, 9 files
- await-thenable: ~7 errors, 4 files
- no-unnecessary-type-conversion: ~9 remaining errors, 9 files

* fix(server): restore MCP tool handler defaults that the linter mis-flagged

The `no-useless-default-assignment` rule fires because Zod's `.default()`
chain makes the TypeScript type non-optional, so the linter treats the
destructuring defaults as redundant. But the MCP framework invokes
handlers with raw (untransformed) params — Zod defaults are not applied
before the call. The destructuring defaults are the runtime safety net.

Reverts the three handler changes from T3.c:
- send_prompt: background = false, notifyOnFinish = false
- list_agents: includeArchived = false, sinceHours = 48, limit = 50
- get_terminal_lines: stripAnsi = true

The no-useless-default-assignment errors on these lines remain and are
deferred to a future cluster that addresses the MCP framework integration.
2026-05-05 22:38:55 +08:00
Mohamed Boudra
2415858d8f feat(website): clarify download page with Server section and copy buttons
Splits the old "Web & CLI" block into separate Web and Server sections so
new users can find the headless install flow without reading the FAQ.
Each section gets a one-line subtitle, and command rows reuse the
CodeBlock component (extracted from CommandDialog) with a new compact
size variant. Also adds Homebrew and Nix install rows, and stacks rows
vertically on small screens.
2026-05-05 21:31:10 +07:00
Somasundaram Ayyappan
9f01b4b7a2 fix: include untracked files in checkout shortstat (#608) (#762)
Count lines in untracked files when computing branch diffstat.
Adds file size limit, empty file handling, and CRLF normalization.
2026-05-05 21:10:54 +08:00
github-actions[bot]
425aa20747 fix: update lockfile signatures and Nix hash 2026-05-05 13:03:21 +00:00
Mohamed Boudra
685a3d0ee2 refactor(server,app): lift return types and parse at boundaries (T2 typeaware production sweep) (#758)
* refactor(server,app): lift return types and parse at boundaries (T2 typeaware production sweep)

* fix(server): align acp-agent test assertions with pino call shape
2026-05-05 19:58:56 +07:00
Mohamed Boudra
53e1be4da6 refactor(server/tests): solve class-stub mock pattern (T1.e) (#757)
Introduce createStub<T> (Proxy-based) and asInternals<T> (single-cast wrapper)
in test-utils/class-mocks.ts to replace all as unknown as casts in server test
files that construct fakes of private-field classes or access class internals.

- class-mocks.ts: one justified as unknown as T cast inside createStub; Proxy
  throws on any unstubbed method call, adding real runtime safety vs bare cast
- session-stubs.ts: all 19 as unknown as casts replaced with createStub/asInternals
- websocket-server.notifications.test.ts + relay-reconnect.test.ts: createStub
  for HTTPServer / pino.Logger / AgentManager stubs; asInternals for internals access
- snapshot-mutation-ownership.test.ts: createStub for SessionOptions field stubs
- session.workspace-git-watch.test.ts: createStub for all SessionOptions fields;
  asInternals for SessionInternals access
- session.workspace-resolution-invariants.test.ts: createStub + asInternals
- acp-agent.test.ts, claude-agent.redesign.test.ts, codex-app-server-agent.test.ts,
  acp-wrapper-smoke.test.ts: asInternals for all internal-access casts

Errors cleared: ~80 as unknown as casts removed across 11 files.
Lint: 0 errors on all 11 files. Typecheck: green.
2026-05-05 19:58:56 +07:00
Mohamed Boudra
eb6d19969d refactor: explicit returns + void floating promises (T3.a typeaware sweep) (#756)
Cluster T3.a — mechanical fixes for consistent-return and no-floating-promises.

Files touched (15):
- server/shared/tool-call-display.ts — default throw in buildCanonicalDetailDisplay switch
- server/agent/prompt-attachments.ts — default throw in renderPromptAttachmentAsText switch
- server/workspace-directory.ts — default throw in getSortValue switch
- server/agent/mcp-server.ts — default throw in mcpCreateWorktreeInput switch
- server/terminal/terminal-session-controller.ts — default return in dispatch switch
- server/daemon-e2e/agent-configs.ts — default return false in isProviderAvailable switch
- server/agent/agent-manager.ts — void dispatchSessionEvent fire-and-forget call
- server/agent/mcp-shared.ts — void 4 notify() floating calls
- app/agent-status-bar.utils.ts — default throw in getStatusSelectorHint switch
- app/keyboard-shortcuts.ts — default throw in resolvePayload switch
- app/resolve-agent-form.ts — default throw in resolveAgentForm switch
- app/ui/dropdown-menu.tsx — void .then() + return undefined for promise/always-return
- app/use-agent-form-state.ts — void refreshSnapshot() floating call
- cli/worktree/create.ts — default throw in toDaemonCreateInput switch
- cli/onboard.ts — return process.exit(1) to satisfy consistent-return in catch

Deferred (outside 15-file cap or in EXCLUDE list):
- server/worktree-session.ts — getSortValue (same pattern, cap reached)
- Multiple files with other errors (unbound-method, unsafe-type-assertion) are separate clusters
2026-05-05 19:58:56 +07:00
Mohamed Boudra
df5c59263e refactor(typeaware): no-unnecessary-type-conversion + unbound-method sweep (T3.b partial) (#754)
* refactor: clear no-unnecessary-type-conversion + unbound-method (T3.b partial)

Fixes 15 files within the hard cap:

unbound-method (interface method shorthand → property function syntax):
- pane-context.tsx: PaneContextValue + PaneFocusContextValue (fixes agent-panel, browser-panel, draft-panel)
- provider-runner.ts: ProviderTurnRunner interface
- process-tree.ts: process.kill.bind(process)

no-unnecessary-type-conversion (remove redundant Boolean()/String() wraps):
- agent-timeline-store.ts, worktree-session.ts, pairing-qr.ts
- sherpa-realtime-session.ts, sherpa-stt.ts (String() → direct call chain)
- workspace-execution.ts, confirm-dialog.ts, use-archive-agent.ts
- use-agent-history.ts, left-sidebar.tsx, autocomplete.tsx, dropdown-menu.tsx

Deferred (9 errors across 7 files, all single-line Boolean(hovered) removals):
project-picker-modal.tsx, agent-list.tsx, branch-switcher.tsx,
file-explorer-pane.tsx, workspace-open-in-editor-button.tsx,
browser-pane.electron.tsx, sortable-inline-list.web.tsx

* fix(app): preserve Boolean coercion fallback in archive state lookups

readPendingState(...)[key] and (pendingQuery.data ?? {})[key] return
boolean | undefined at runtime — noUncheckedIndexedAccess is off so
TypeScript types this as boolean but the actual value can be undefined.
Add ?? false to maintain the explicit boolean return contract.
2026-05-05 19:58:56 +07:00
Mohamed Boudra
059d543c97 refactor(server/tests): clear 146 type-aware lint errors in session tests (T1.a) (#753)
Replace `as unknown as SessionOptions["X"]` call-site casts with typed stub
helpers from a new `test-utils/session-stubs.ts` module. Unsafe assertions
are now contained to that single infrastructure file, keeping all call sites
in the two fully-fixed test files clean.

Files fully fixed (typeAware: true → 0 errors):
- session.workspaces.test.ts: 119 → 0
- session.test.ts: 27 → 0

Helpers added in session-stubs.ts:
- asSessionLogger / asAgentManager / asAgentStorage / asDownloadTokenStore /
  asPushTokenStore / asChatService / asScheduleService / asLoopService /
  asCheckoutDiffManager / asDaemonConfigStore / asTerminalManager /
  asGitHubService / asWorkspaceGitService / asScriptRouteStore /
  asWorkspaceScriptRuntimeStore
- asSessionInternals<T> for private session access
- filterByType / findByType message helpers
- createProviderSnapshotManagerStub returning typed spy refs
2026-05-05 19:58:56 +07:00
Mohamed Boudra
eb16829f51 refactor(server/tests): replace unsafe type assertions with Reflect + Zod (T1.b partial) (#752)
Cluster T1.b — first wave of no-unsafe-type-assertion fixes across agent
provider and MCP test files. Clears all type-aware lint errors in 6 files;
remaining 4 files (class-mock patterns) deferred to T1.b2.
2026-05-05 19:58:56 +07:00
Mohamed Boudra
b1d3867da9 refactor(server): parse daemon/ws/wire test responses with Zod (T1.c typeaware sweep) (#750)
Fixes all reachable typescript-eslint(no-unsafe-type-assertion) errors
in the four in-scope test files by:
- Adding Zod schemas (WireEnvelopeSchema, BinaryFrameSchema,
  RuntimeMetricsLogSchema) and helpers (parseSentEnvelope, parseSentFrame,
  assertStr) to parse wire frames instead of casting JSON.parse() results
- Replacing `JSON.parse(x) as { ... }` with Zod-validated helpers
- Replacing `mock.sent[N] as string` / `String(mock.sent[N])` with assertStr()
- Replacing `frame as Uint8Array` with direct type annotation (Buffer extends Uint8Array)
- Replacing `session.args.onMessage as (() => void) | undefined` with typeof
  function guard and direct invocation
- Replacing `metricsCall![0] as RuntimeMetricsLog` with RuntimeMetricsLogSchema.parse()

Remaining `as unknown as SomeClass` patterns in constructor mocks and
`client as unknown as { ... }` internal-access patterns are out of scope:
they require extracting interfaces from concrete classes (AgentManager,
AgentStorage, FileBackedChatService, etc.) with private members, or
refactoring the client API to expose internal methods — neither is safe
to do in a test-only sweep.
2026-05-05 19:58:56 +07:00
Mohamed Boudra
006a79ed01 refactor(app/relay): parse test responses with Zod (T1.d typeaware sweep) (#749)
Replace unsafe type assertions with Zod schema parsing:
- app/utils/project-config-form.test.ts: Use PaseoConfigRawSchema.parse()
  instead of 'as unknown as PaseoConfigRaw' (~10 errors fixed)
- server/daemon-e2e/relay-transport.e2e.test.ts: Use ConnectionOfferSchema
  and WSOutboundMessageSchema.parse() instead of inline type casts (~8 errors fixed)

Deferred to follow-up cluster (architectural decisions needed):
- host-runtime.test.ts: FakeDaemonClient mock needs proper interface
  implementation (requires source file changes)
- cloudflare-adapter.test.ts: WebSocket/DurableObjectState mocks need
  design review (external Cloudflare types)

Target rule: typescript-eslint(no-unsafe-type-assertion)
Errors cleared: 18/76 in cluster (61 deferred)
2026-05-05 19:58:56 +07:00
Mohamed Boudra
0d80394180 test(app/e2e): sessions-screen empty state (Cluster G7) (#742)
* test(app/e2e): cover sessions-screen empty state (Cluster G7)

Adds one E2E test — opens Sessions on a fresh workspace with no agents
and asserts the "No sessions yet" placeholder renders. Uses `withWorkspace`
fixture (no agent seeding) so the empty branch runs for the first time.

Also exports `expectSessionsEmptyState` helper from archive-tab helpers
for reuse in future session-related specs.

* fix(app/e2e): run sessions-empty test before archive-tab agents are created

The sessions screen shows global agent history for the daemon. Running
the empty-state test last meant the reconciliation tests had already
created 6 agents, so "No sessions yet" never rendered.

Moving the describe block first ensures it runs on a clean daemon
(workers:1, fullyParallel:false, archive-tab.spec.ts is first alphabetically).

* refactor(app/e2e): guard sessions-empty ordering + dedup selector constant

Addresses reviewer feedback on the ordering fragility:

- Add NOTE comment above Sessions screen empty state describe explaining
  why it must remain first in the file (daemon history is global; the
  reconciliation tests below call createIdleAgent which would pollute it).

- Add a fast-fail guard in expectSessionsEmptyState: asserts 0 agent rows
  with a 5s timeout so a future maintainer sees an immediately actionable
  failure message rather than a mysterious "No sessions yet" timeout.

- Extract AGENT_ROW_SELECTOR constant to eliminate the duplicated
  [data-testid^="agent-row-"] string shared by getSessionRowByTitle and
  the new guard.

* fix(app/e2e): move sessions-empty to 00-prefixed file to survive new specs

agent-stream-ui.spec.ts (merged in #743) sorts before archive-tab.spec.ts
and creates agents, breaking the empty-state test. Any future a*-*.spec.ts
has the same risk.

Fix: move the test to 00-sessions-empty.spec.ts — digit prefix sorts
before all alpha-named specs, making the ordering constraint explicit at
the filesystem level.

Also adds a beforeAll daemon probe that fails fast with a clear message
if any pre-existing agents are found, covering both ordering violations
and stale daemon state from a previous run.

Supporting changes:
- Add fetchAgentHistory to ArchiveTabDaemonClient interface (typed as
  Array<{ id: string }> — enough for the count check)
- Remove the test and NOTE comment from archive-tab.spec.ts
- Update expectSessionsEmptyState guard comment to reference the new file
2026-05-05 19:58:56 +07:00
Mohamed Boudra
9721dadbe9 test(app/e2e): picker keyboard interaction tests (Cluster G8) (#744)
* test(app/e2e): add picker keyboard-interaction tests (Cluster G8)

Cover branch-picker keyboard contract: open via Space, navigate with
ArrowDown/ArrowUp, select with Enter, close with Escape.

Adds six helpers to helpers/new-workspace.ts:
openBranchPicker, selectPickerOptionByKeyboard, closeBranchPicker,
expectPickerOpen, expectPickerClosed, expectPickerSelected.

Also moves delayBrowserAgentCreatedStatus and its private helpers out of
new-workspace.spec.ts into the helpers module where they belong.

* fix(app/e2e): address picker keyboard test review feedback

- Add { timeout: 30_000 } to expectPickerClosed (FadeOut animation safety)
- Simplify selectPickerOptionByKeyboard: ArrowDown → Enter (remove redundant ArrowUp)
- Migrate expectStartingRefPickerTriggerPr trigger selector from testID to ARIA role

* fix(app/e2e): fix picker keyboard test — open via click not Space

RN Web Pressable renders as <div role="button"> which does not fire
onPress from a programmatic Space key event. Switch openBranchPicker
to trigger.click() so the picker reliably opens in CI headless Chrome.

Keyboard behaviour (ArrowDown + Enter, Escape) is still exercised by
selectPickerOptionByKeyboard and closeBranchPicker respectively.
2026-05-05 19:58:56 +07:00
Mohamed Boudra
9bd1407ca6 test(app/e2e): mobile sidebar open/close transition (Cluster G6) (#746)
* test(app/e2e): sidebar query pause + mobile open/close transition (Cluster G6)

G6.1: assert fetch_workspaces_request stops being sent when desktop sidebar
is closed (CDP WebSocket frame counting, no store injection).

G6.2: assert mobile sidebar panel animates in/out at 390×844 viewport via
toBeInViewport on translateX-animated element.

Adds installWorkspaceFetchMonitor, expectWorkspaceListSubscribed,
closeSidebar, openMobileAgentSidebar, closeMobileAgentSidebar,
expectMobileAgentSidebarVisible, and expectMobileAgentSidebarHidden helpers.

* test(app/e2e): mobile sidebar open/close transition (Cluster G6)

G6.1 (query-pause perf invariant) reclassified as a unit-test follow-up
— counting internal RPC frames in E2E is banned per updated roadmap.

G6.2: asserts mobile sidebar panel animates in/out at 390×844 viewport
via toBeInViewport on the translateX-animated sidebar-sessions element.

Adds openMobileAgentSidebar, closeMobileAgentSidebar,
expectMobileAgentSidebarVisible, and expectMobileAgentSidebarHidden helpers.
2026-05-05 19:58:55 +07:00