Commit Graph
104 Commits
Author SHA1 Message Date
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 Boudra e0e50c9a8e fix(app): preserve persisted theme styles after startup
Module-level style composites could materialize the temporary adaptive theme before persisted settings loaded. Keep Unistyles reads in render and guard against eager module-scope access.
2026-07-18 17:45:02 +02:00
Mohamed BoudraandGitHub a622860a3e Keep workspace focus mode scoped and easy to exit (#2151)
* fix(workspace): keep focus mode scoped and easy to exit

Route focus mode through the active workspace so persisted state cannot hide chrome on settings or other screens. Add a visible exit control and keep desktop window chrome aligned and visually quiet.

* fix(app): clarify muted chrome semantics
2026-07-16 23:46:24 +02:00
Mohamed BoudraandGitHub 328361667f Keep browser sign-ins across tabs and restarts (#2089)
* feat(desktop): share persistent browser data across tabs

Keep browser sign-ins and site storage across tabs and app restarts, with a Settings action to clear the shared profile.

* fix(settings): prevent duplicate browser data clears

* fix(desktop): complete browser data cleanup

Clear legacy per-tab profiles after upgrades and localize the browser data settings in every supported language.

* fix(desktop): close browser profile cleanup races
2026-07-16 00:40:24 +08: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 1b8af2e58e Refine tool call summaries and scrolling (#2069)
* feat(app): refine tool call summaries and scrolling

Keep summary groups stable from the first tool call, preserve live activity feedback, and scroll expanded groups to the latest entry. Consolidate web scrollbar styling around the native browser mechanism.

* refactor(app): reuse tool call height constant

* fix(app): preserve tool call summary state

* fix(app): refresh web tool groups on expand

* fix(app): preserve grouped tool errors
2026-07-14 21:16:29 +02:00
Mohamed BoudraandGitHub 5e2a8b6633 Make help easier to find and cloned workspaces open reliably (#2045)
* feat(app): add sidebar help and support menu

Give users a single place to run diagnostics, find shortcuts, and reach the support channels. Share one global diagnostic sheet between Settings and the sidebar so both entry points collect the same report.

* fix(app): keep cloned workspace navigation atomic

A newly merged clone flow still used the previous navigation signature and opened its draft tab separately. Route the cloned workspace and its draft target through the unified navigation command so attention selection cannot replace the intended tab.

* test(app): harden sidebar help coverage

Accept both the Discord invite URL and its canonical redirect, assert placement through real element geometry, and remove an unnecessary diagnostic-host callback.

* test(app): cover support redirect variants

Keep the help-menu E2E stable across prerelease versions and the valid redirect forms returned by Discord and GitHub.
2026-07-14 00:02:53 +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 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 Boudra 0d1c8db87d fix(settings): align host switcher with sidebar rows
The host picker reused the composer trigger's content-width pill, so its
label sat 6px left of the section rows and the chevron hugged the label
instead of the trailing edge. Add a `block` mode to ComboboxTrigger that
fills the width and uses the sidebar-row gap, and size the status dot to
the icon footprint so the label and chevron line up with the rows below.
2026-06-29 12:16:20 +02: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
Mohamed BoudraandGitHub 26f169866f Add app diagnostic report (#1728)
* feat(diagnostics): add app diagnostic report

* fix(diagnostics): guard app diagnostic runs

* fix(diagnostics): include websocket runtime metrics

* fix(diagnostics): type websocket metric snapshot

* fix(diagnostics): include daemon shell env

* fix(loop): make stop cancellation test deterministic
2026-06-25 23:20:52 +08:00
Alcimério RangelandGitHub 9960772fe9 feat: add Brazilian Portuguese locale (#1653)
* Add Brazilian Portuguese locale

* Add missing pt-BR translations after main merge

* Test zh-CN translation fallback coverage
2026-06-25 17:36:04 +08:00
acea7f3d24 feat: live multi-provider quota panel (#1278)
* feat: live provider quota panel (Claude + Codex)

Adds Claude and Codex plan usage to the context window percentage circle tooltip, querying their respective APIs directly using existing CLI auth tokens.

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

* fix: address review feedback - restore quota trigger, guard NaN date

- Re-add triggerFetch() on agent idle/error in agent status subscription
- Guard formatResetsAtLabel against NaN from malformed API date strings

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

* fix: resolve composer conflict and stub subscribe in websocket tests

* feat(quota): add cursor, copilot, zai, grok, and kimi quota UI and improve type safety

* feat(quota): make Claude and Codex pluggable, and map additional quota/credits metrics

* security(quota): fix shell injection vulnerability by migrating exec to execFile

* revert: restore original scripts/dev.ps1 and packages/desktop/scripts/dev.ps1

* Fix i18n resource test expectation

* Fix quota tooltip empty-provider state

* Preserve zero values in Grok quota

* Fix empty quota fetch state

* Ignore quota frames in relay reconnect tests

* Persist refreshed Codex tokens to source auth file

* fix(quota): read Claude OAuth credentials from the macOS login Keychain

On macOS, Claude Code stores its OAuth credential in the login Keychain
(generic-password item, service "Claude Code-credentials"), not in
~/.claude/.credentials.json — that file usually does not exist there, so the
Claude provider's existsSync check failed and macOS users silently got no
Claude quota. Read it via the macOS `security` CLI (its ACL only trusts
/usr/bin/security to decrypt; a native Keychain read would prompt), and stay
read-only there since Claude Code owns the refresh/persist. Linux and Windows
keep using ~/.claude/.credentials.json unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>

* Fix quota keychain timeout and local host reconciliation

* Fix terminal notification test quota stub

* Ignore quota frames in terminal notification tests

* fix(quota): bypass Claude token refresh on macOS Keychain-backed logins

* fix(dev): update local dev daemon port to 6768 on Windows scripts/dev.ps1

* fix(dev): bypass Unix dev-daemon.sh on Windows in dev.ps1

* fix(client): handle and dispatch provider_quota event in DaemonClient

* fix(desktop): fix PowerShell quote stripping in desktop dev.ps1 config seeding

* fix(desktop): execute config update via temp file to avoid PowerShell quoting bugs

* fix(desktop): fix concurrently command invocation on Windows in dev.ps1

* fix(desktop): resolve path escaping and environment setting syntax errors in dev.ps1 for Windows

* Add on-demand provider usage views

* Fix Cursor usage billing dates

* Move provider usage renderer coverage to e2e

* Use provider manifest for quota fetchers

* Add provider usage fetch timeouts

* Reshape provider usage fetchers

---------

Co-authored-by: ABorakati <ABorakati@users.noreply.github.com>
Co-authored-by: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
Co-authored-by: lumingjun <lumingjun@bytedance.com>
2026-06-20 12:19:50 +08:00
Mohamed Boudra 36a136d811 Add shared trigger components and horizontal desktop workspace arguments
- Add DropdownTrigger and ComboboxTrigger with an overridable chevron slot and a 1 px translateY caret nudge for consistent vertical alignment.

- Convert text-based dropdown/combobox triggers across model selector, agent controls, mode control, settings, and new workspace to use the shared components.

- Lay out New Workspace arguments horizontally on desktop without labels while keeping the mobile vertical stack; align the project icon left edge with the title.
2026-06-16 14:56:07 +07:00
Mohamed Boudra 426547bfcc Fix composer send shortcut conflicts 2026-06-12 13:29:51 +07:00
Mohamed Boudra fa800ae78e Add configurable terminal profiles 2026-06-12 11:47:00 +07:00
Mohamed Boudra d3a6bf80dd fix(app): clean up i18n settings labels 2026-06-11 13:48:06 +07:00
chyendongnhanh338andGitHub 08ebe5e7e1 Complete client UI i18n migration
Adds client-side i18n support, language settings, translated UI copy, and locale parity coverage.
2026-06-11 14:15:35 +08:00
Mohamed Boudra 201f427f62 fix(app): keep settings sidebar scrollable 2026-06-10 17:02:58 +07:00
Mohamed Boudra eca0a5bf67 Let manual update checks bypass rollout
Automatic desktop update checks still respect rollout admission. Manual checks carry an explicit intent through the app and desktop updater service, and the up-to-date state now shows when the last check completed.
2026-06-09 15:47:30 +07:00
b5192e577a Stop local daemon when removing localhost (#1297)
* fix(app): stop local daemon when removing localhost

* fix(app): handle daemon removal rollback

* fix(app): return listen address in desktop e2e mock

* fix(app): refresh daemon status after registration failure

* fix(app): unslop daemon status error coverage

* fix(app): preserve daemon toggle error context

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-06-04 22:30:40 +08:00
Mohamed Boudra b0c4cc99ed Fix settings host picker navigation 2026-06-04 09:37:14 +07:00
Mohamed BoudraandGitHub 369adced52 Archive merged PR workspaces from settings (#1313)
* Add workspace settings for merged PR cleanup

* Fix worktree branch ahead status

* Handle PR worktree upstream status

* Remove stale checkout tracking field

* Update settings E2E host section slugs

* Treat unknown upstream as unsafe for auto-archive
2026-06-03 23:17:08 +08:00
Mohamed BoudraandGitHub a1c8e1a1f9 Add appearance settings for theme, fonts, and syntax highlighting (#1236)
* Add appearance settings for theme, fonts, and syntax highlighting

New Appearance section in Settings to configure the app theme, UI and
mono font family + size, and a syntax-highlighting theme chosen
independently of light/dark. Font family fields default to empty and
show the active system stack as the placeholder; a live split-diff
preview reflects the choices as they change. The theme picker moves
here from General.

Preferences are client-only (AsyncStorage) and applied at runtime by
patching every registered Unistyles theme via
UnistylesRuntime.updateTheme, so existing StyleSheet consumers repaint
with no per-component preference reads and no useUnistyles. No
daemon/protocol change.

* Expand syntax themes and move the preview under the chooser

Replace the small fixed syntax-theme set with eight popular themes — GitHub,
Catppuccin, Dracula, Tokyo Night, One, Nord, Gruvbox, Solarized — built from a
compact per-theme role palette. The app theme and syntax theme stay separate;
the only coupling is the light/dark axis: a theme with both variants uses its
light palette on a light app and its dark palette on a dark app, while the code
frame (gutter, line numbers, background) keeps following the app theme. Default
is now GitHub (GitHub keeps its existing hand-tuned maps for an exact match).

Move the live preview directly under the syntax-theme chooser, drop the
"preview.ts" filename header, and use a clearer code snippet for it.

* Update settings e2e for the moved theme picker and Appearance section

The theme picker moved from General to the new Appearance section, so the
General-content assertion no longer finds "Theme" — point it at "Default send"
(which stays in General) and add Appearance to the e2e section map plus an
expectAppearanceContent check, and exercise the Appearance section in the
sidebar-navigation test.

* Render the appearance preview as a unified diff

Switch the live preview from side-by-side columns to a single unified diff:
unchanged context lines plus "-"/"+" rows for the change, matching how diffs
read elsewhere in the app. Same snippet, mono font, syntax colors, and live
code-font draft behavior.

* Fix code font size change shrinking all UI text

applyAppearance scaled the live (already-patched) theme ramp instead of the
authored FONT_SIZE ramp, so it was not idempotent: every appearance change
re-scaled the current sizes, compounding the whole-UI font scale. With any
non-default UI size, changing the code size — or any setting — shrank (or grew)
all app text, and it persisted into the theme. Build the ramp from the canonical
FONT_SIZE ramp each apply (code size still set absolutely), so applies are
idempotent and a code-size change never touches the UI ramp.

* Apply the interface font across the app on web

react-native-web stamps a default font onto every text element, so the interface
font only reached the few components that set a font explicitly. Inject one rule
that points all text at a CSS variable (updated live as the setting changes),
with high specificity (1,2,0) so it deterministically beats RN-web's base font
and Unistyles' generated classes — no stylesheet-order fragility. Code, diff, and
monospace surfaces carry a `data-pmono` marker (and their subtrees are excluded),
so they keep their code font.

Web-only: React Native has no global font cascade, so native still applies the UI
font only where components opt in. Inline `code` within chat prose is a known
minor gap (its render paths aren't tagged yet).

* Keep preview code in the code font

* Apply code font settings to terminals

* Fix settings host picker import after rebase

* Format terminal webview bundle
2026-05-31 16:09:01 +08:00
Mohamed BoudraandGitHub 0e3eb9cf75 Flatten settings sidebar with a host picker (#1241)
* Flatten settings sidebar with a host picker

Settings had two implicit tiers: app-level sections as flat sidebar rows,
and per-host settings hidden behind a drill-in to one monolithic host page.
This flattens both into a single sidebar with an "App" group and a "Host"
group, where the host's settings are top-level rows scoped by a host picker.

- Split the monolithic HostPage into four section pages: Connections,
  Orchestration (was the unnamed inject-tools + system-prompt block),
  Providers, and Daemon (status, updates, restart, remove).
- Host picker reuses the canonical <Combobox> sidebar host switcher; defaults
  to the local host so a single-local-host user never thinks about "the daemon".
- Host sections become real sub-routes (/settings/hosts/[id]/[section]); the
  old /settings/hosts/[id] redirects to the connections section, so existing
  deep links (pairing, workspace, open-project) keep working.
- "Open providers" from the project screen now lands on the Providers section.

* Fix e2e host-settings navigation for the flat sidebar

The flat-settings refactor moved host settings onto separate section pages,
but three e2e helpers still expected the old monolithic host page:

- openDesktopSettings: the daemon-lifecycle card now lives on the Daemon
  section — navigate there before asserting it.
- acp catalog: "Add provider" now lives on the Providers section — open that
  section before opening the add-provider modal.
- openAddHostFlow: "Add host" is now an item inside the host picker (Combobox),
  not a standalone button — open the picker first.

Test-only; no product changes. Fixes the 8 playwright failures on this branch.
2026-05-31 12:55:08 +08:00
Mohamed BoudraandGitHub 69d36c76fe Show connected host daemon versions on the About page (#1220)
* Show connected host daemon versions on the About page

* Update About E2E assertion for the App version label
2026-05-29 14:51:53 +08:00
Mohamed Boudra 5f11f602fb feat(app): add community links and sidebar home button 2026-05-22 17:40:42 +07:00
edb0ba888a Make terminal scrollback configurable (#1021)
* Make terminal scrollback configurable

* Fix terminal scrollback lint violation

* Fix terminal runtime test environment split

* Fix terminal scrollback updates

---------

Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-05-17 19:58:35 +08:00
4570e65ce8 fix(app): respect iPad safe area in settings sidebar (#922)
Co-authored-by: zbl <zbl@zbl-M4Pro.local>
2026-05-12 07:27:49 +00:00
Mohamed BoudraandGitHub ca503c1560 feat(app): grouped project settings with docs links + scrollbar fix (#837)
* feat(app): group project settings into Worktree / Scripts / Metadata

Adds SettingsGroup component (title + info tooltip + trailing slot) and
restructures the project settings screen into three categorized groups
with explanatory tooltips. Metadata prompt labels drop the "prompt"
suffix now that they live under a "Metadata generation" group, and
placeholders read as example instructions a real user would write.
Applies useWebScrollbarStyle to the settings ScrollViews.

* Add ExternalLink component and docs links to project settings
2026-05-09 19:31:19 +08:00
Mohamed Boudra 67c93dba49 Reorder settings sidebar: Projects after General 2026-05-07 18:27:11 +07:00
Mohamed BoudraandGitHub 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 02e5b56408 Fix duplicate workspace shell navigation 2026-05-05 21:55:57 +07:00
Mohamed Boudra f58cfe9008 Open script service URLs in-app or external
Adds a Service URLs setting (ask / in-Paseo / external browser) that
controls where URLs from running scripts open on desktop. First click
prompts with a "don't ask again" checkbox; in-Paseo opens a workspace
browser tab. Closing a browser tab now clears its session partition.
2026-05-03 15:14:28 +07:00
Mohamed Boudra 219050a7ab feat: project settings page with paseo.json editor
Adds a Projects settings flow for inspecting and editing per-project
paseo.json (worktree setup/teardown, scripts, terminals) directly from the
app. Reads/writes go through new daemon RPCs with revision-based optimistic
concurrency, and the screen always keeps its header and host picker visible
so a hung host doesn't trap the user.
2026-04-26 11:37:47 +07:00
Mohamed Boudra ae3e88e7e1 feat(desktop): split daemon and release settings to main process
Move daemon management and release channel settings out of the renderer's
AsyncStorage and into a desktop-owned settings store persisted by the main
process. Adds a "Keep daemon running after quit" option, a daemon lifecycle
section in settings, and a one-time migration of legacy renderer-owned
desktop settings.
2026-04-25 13:59:07 +07:00
Mohamed BoudraGitHubgithub-actions[bot] <github-actions[bot]@users.noreply.github.com>
446dd9cfe6 chore: drive linter to zero, promote warn -> error in CI (#545)
* chore(lint): use Set for LOG_FORMATS membership check

* chore(lint): convert type aliases to interfaces (autofix)

- Remove no-use-before-define rule (conflicts with unistyles ordering)
- Add typescript/consistent-type-definitions: interface
- Run oxlint --fix: 606 type->interface conversions across 268 files

Typecheck green. Warnings: 5432 -> 3787 (-1645).

* chore(lint): clean up relay package warnings

* chore(lint): clean up cli package warnings

* chore(lint): flatten nested ternaries in server (no-nested-ternary)

* chore(lint): escape entities and hoist inline objects in website

* chore(lint): hoist inline arrays in app (jsx-no-new-array-as-prop)

* fix(types): restore Record assignability after type->interface autofix

* chore(lint): hoist inline arrays in app (jsx-no-new-array-as-prop)

* chore(lint): clean up desktop and highlight packages

* chore(lint): hoist inline arrays in app (jsx-no-new-array-as-prop)

* chore(lint): hoist inline arrays in app (jsx-no-new-array-as-prop)

* chore(lint): format keyboard-shortcuts-section

* chore(lint): rename shadowed bindings in server (no-shadow)

Rename inner bindings that shadow outer imports or function params:
- Promise executor `resolve` -> `resolvePromise` (shadowed path `resolve`)
- Method params `options` -> `input`/`target`/`update`/`runOptions`/`opts`/`killOptions`
- Misc loop/destructure renames for `workspaceId`, `scriptNames`, `path`, `query`, `taskNotificationItem`

Mechanical change only; no behavior change.

* fix(types): add index signatures for Record assignability

* chore(lint): extract nested callbacks in server (max-nested-callbacks)

* chore(lint): hoist inline callbacks in app (jsx-no-new-function-as-prop)

Work in progress: 30 of 369 warnings fixed across 23 files.

* chore(lint): hoist inline callbacks in app (jsx-no-new-function-as-prop)

* chore(lint): reduce cyclomatic complexity in server

* chore(lint): hoist inline callbacks in app components (jsx-no-new-function-as-prop)

* chore(lint): parallelize safe await-in-loop in server

* chore(lint): hoist inline callbacks in app (jsx-no-new-function-as-prop)

Finish eliminating jsx-no-new-function-as-prop warnings in
agent-status-bar.tsx and complete refactor of git-diff-pane.tsx
by extracting per-item components and using stable useCallback
handlers.

* chore(lint): parallelize more safe await-in-loop in server

* fix(server): restore microtask ordering in message dispatch and stream event handler

The complexity refactor added async dispatcher chains that inserted extra
microtasks before message handlers ran, and wrapped the stream event switch
in an await that fired between emitState and dispatchStream. Two tests
regressed on both counts. Route to the matching dispatcher synchronously
and skip the await when the stream handler has no async work.

* chore(lint): hoist/memoize inline arrays and objects in app JSX

* chore(lint): parallelize safe awaits in server (no-await-in-loop)

Converts three sequential `for ... await` loops to `Promise.all`:
- OpenCode: configure MCP servers in parallel
- Sherpa: download model files and ensure multiple models concurrently
- Speech runtime: check required model files across models in parallel

* chore(lint): clear website warnings to zero

Resolves all 61 oxlint warnings in packages/website/ by hoisting inline
callbacks/JSX, extracting sub-components to satisfy jsx-max-depth, using
stable data-derived keys, adding explicit button types, flattening nested
ternaries, and removing unused code.

* chore(lint): parallelize more safe awaits in server (no-await-in-loop)

- Workspace reconciliation: archive missing workspaces and orphaned
  projects concurrently instead of sequentially.
- Directory suggestions: resolve child directory candidates in parallel
  before filtering.

* chore(lint): allow css imports in import/no-unassigned-import, hoist host page styles

* chore(lint): hoist dictation-controls inline arrays

* fix(server): restore microtask ordering in session message dispatch

Converts per-group dispatchers to Promise<void> | undefined. The complexity
refactor broke two tests by inserting extra microtasks between each
dispatcher's await. The new pattern routes synchronously via a nullish-
coalescing chain and awaits only the matching dispatcher's promise.

* chore(lint): flatten nested ternaries in button/volume-meter/autocomplete

* chore(lint): narrow explicit any in misc server files

* chore(lint): flatten nested ternaries in host-runtime/host-page/providers

* chore(lint): clear desktop warnings to zero

Convert polling loops to recursive helpers to avoid no-await-in-loop,
and switch the ws import to the named export so the WebSocket type is
referenced directly.

* chore(lint): remove useless constructors in server tests

* chore(lint): hoist pair-scan inline arrays and objects

Extracts corner style arrays and barcode scanner settings to module
scope, memoizes insets-dependent body/helper text styles.

* chore(lint): hoist inline style arrays in question-form-card

* chore(lint): hoist/memoize inline styles in plan-card

Extracts markdown rule style arrays into dedicated MarkdownInlineText,
MarkdownListItemContent, and MarkdownParagraph subcomponents that
memoize their own style arrays. Memoizes PlanCard container/title/
description styles.

* chore(lint): narrow explicit any in server relay/loader/logger-likes

* chore(lint): hoist SheetBackground combined style

* chore(lint): hoist inline styles in screen-header

* chore(lint): hoist inline styles/objects in menu-header

* chore(lint): hoist top-level inline arrays in diff-viewer

* chore(lint): narrow explicit any in codex-app-server-agent thread items

* chore(lint): reduce max-depth in claude-sdk-behavior test

* chore(lint): memoize inline styles in command-center

* chore(lint): extract helper to reduce max-depth in agent-response-loop

* chore(lint): flatten nested conditions in pi-direct-agent history

* chore(lint): reduce max-depth in claude-agent query pump

* chore(lint): clear cli warnings to zero

Refactor polling loops into recursive helpers to satisfy
no-await-in-loop, fix WebSocket named import, and correct
an absolute-path import in tests/tmp.

* chore(lint): memoize inline styles in context-menu

* chore(lint): narrow explicit any in claude-agent/openai stt

* chore(lint): reduce max-depth in opencode-agent foreground loop

* chore(lint): memoize inline styles in explorer-sidebar

* chore(lint): memoize mobile sidebar styles in explorer-sidebar

* chore(lint): memoize inline styles in agent-list

* chore(lint): reduce relay test no-await-in-loop warnings

Refactor while-loops and for-retry loops in e2e.test.ts and
live-relay.e2e.test.ts into recursive poll/attempt helpers.
Remaining 8 warnings in encrypted-channel.ts concern the custom
Transport interface (on-handler slots, not DOM EventTarget) and a
sequential send loop; both reflect deliberate runtime contracts.

* chore(lint): fix typecheck errors from hoisting refactors

- pair-scan: type BARCODE_SCANNER_SETTINGS as BarcodeSettings
- explorer-sidebar: add missing desktopSidebarStyle useMemo
- sidebar-workspace-list: coerce null dotColor to transparent
- menu-header, e2e.test: formatting

* chore(lint): memoize inline styles in workspace-screen

* chore(lint): memoize inline styles in workspace-desktop-tabs-row

* chore(lint): hoist constant style arrays in desktop-updates-section

* chore(lint): memoize inline styles in combobox

* chore(lint): extract SplitGroupChild to memoize inline styles

* chore(lint): memoize inline styles in dropdown-menu

* chore(lint): memoize inline styles in workspace-hover-card

* chore(lint): memoize inline styles in autocomplete, message-input, composer

* chore(lint): remove explicit any in server (129 warnings)

- session.ts: catch(error: any) -> catch(error) with Error coercion at use
- daemon-client transport: any -> unknown in listener types
- sherpa/onnx loaders: introduce structural native types
- pocket-tts-onnx: typed ONNX session inputs/outputs/tensors
- tests: any -> unknown + named stub types

* chore(lint): hoist inline arrays and objects in app (145 warnings)

- Hoist static style arrays/objects to module-level consts
- Memoize dynamic ones with useMemo and correct deps
- jsx-no-new-array-as-prop: 145 -> 43
- jsx-no-new-object-as-prop: 64 -> 21

* chore(lint): memoize inline styles in _layout

Fixes 3 react-perf/jsx-no-new-object-as-prop warnings in root layout by
memoizing the stack screen options, the agent screen override, and the
GestureHandlerRootView root style.

* chore(lint): memoize inline styles and objects in stream/status panes

Clears 10 react-perf/jsx-no-new warnings across welcome-screen,
file-explorer-pane, terminal-pane, agent-stream-view, and
agent-status-bar by extracting per-item subcomponents, hoisting constant
style tuples, and memoizing derived arrays/objects.

* chore(lint): memoize inline style objects in list/pane components

Clears 6 react-perf/jsx-no-new-object-as-prop warnings across
synced-loader, stream-strategy-web, sortable-inline-list, file-pane, and
both draggable-list platform variants by memoizing or hoisting their
inline style objects.

* chore(lint): hoist test fixture arrays/objects

Clears 14 react-perf/jsx-no-new-* warnings across app test files by
hoisting constant fixtures to module scope or wrapping them in helper
factories so they no longer appear as inline JSX prop values.

* chore(lint): memoize inline styles in message components

Clears 5 react-perf/jsx-no-new-* warnings in message.tsx by memoizing
image source objects, extracting an AssistantMessageBlockContainer for
per-block spacing, and moving todo list item rendering into a
TodoListItemRow subcomponent so each row memoizes its own style arrays.
The remaining 8 warnings in react-native-markdown-display render rules
are inherent to that library's rule API.

* chore(lint): remove unused vitest imports in server tests

* chore(lint): remove unused helpers and vars in server tests

* chore(lint): add explicit returns in then() callbacks in server

* chore(lint): fix no-shadow and no-nested-ternary in server

* chore(lint): flatten nested blocks to satisfy max-depth in server

* chore(lint): extract helpers to satisfy max-depth in server

* chore(lint): parallelize Claude persisted agents lookup

Replace sequential parseClaudeSessionDescriptor loop with Promise.all
to fix no-await-in-loop warning.

* chore(lint): parallelize Linux watch directory traversal

Switch BFS to level-by-level Promise.all over readdir calls, replacing
the sequential pop/await loop that triggered no-await-in-loop.

* chore(lint): parallelize workspace registry bootstrap upserts

Collect per-workspace upsert inputs in a sync pass, then run registry
writes via Promise.all to eliminate no-await-in-loop warnings.

* chore(lint): parallelize test daemon cleanup rm calls

Run the paseoHomeRoot/staticDir removals concurrently in close() and
the startup catch block to fix no-await-in-loop.

* chore(lint): mechanical cleanup in app (unused, shadow, nested-ternary)

Recovers in-flight edits from the app mechanical agent that couldn't be
pushed due to concurrent tree contention. Removes unused helpers and
locals, flattens nested ternaries, narrows a few props where unused.

* chore(lint): parallelize pending permission approvals

approvePendingPermissions now filters and records handled IDs in a
sync pass, then awaits all allowPermission calls concurrently instead
of looping awaits.

* chore(lint): parallelize rebase head-name lookup

Try both rebase backends (rebase-merge, rebase-apply) concurrently
via Promise.all and return the first non-null result.

* chore(lint): no-unused-vars in app

* chore(lint): parallelize worktree terminal bootstrap

Start each bootstrap terminal concurrently via Promise.all. Since every
spec creates an independent terminal and returns a standalone result,
parallelization preserves order while removing two no-await-in-loop
warnings.

* chore(lint): parallelize worktree bootstrap test cleanup and script spawn

Run terminal cleanup across managers and spawnWorkspaceScript for api
and web concurrently to eliminate no-await-in-loop warnings while
preserving semantics.

* chore(lint): prefer-add-event-listener in app

* chore(lint): parallelize script-health-monitor afterEach and spawns

Close all stub TCP servers concurrently and spawn typecheck/api
scripts via Promise.all to drop two no-await-in-loop warnings.

* chore(lint): jsx-no-useless-fragment in app

* chore(lint): parallelize executable PATH probing

* chore(lint): parallelize client and relay transport test cleanup

* chore(lint): parallelize agent storage record file probing

* chore(lint): no-array-index-key in app

* chore(lint): parallelize bootstrap provider availability cleanup

* chore(lint): parallelize Codex rollout file search

* chore(lint): parallelize dictation wav fixture search

* chore(lint): parallelize Claude session id fixture test

* chore(lint): parallelize script health monitor server cleanup

* chore(lint): parallelize Codex skills directory scan

* chore(lint): promise/always-return in app

* chore(lint): no-shadow in use-dictation, hoist icons in splash screen

* chore(lint): no-shadow in composer callbacks

* chore(format): apply oxfmt to 3 server files

* chore(lint): no-explicit-any in stt-manager/chat-mentions tests

* chore(lint): no-shadow in small app files

Rename inner shadowing identifiers in:
- add-host-modal, agent-status-bar, sidebar-workspace-list, terminal-pane
- workspace-setup-dialog, session-context, use-is-local-daemon
- setup-panel, new-workspace-screen, workspace-desktop-tabs-row

* chore(lint): no-explicit-any in claude-agent.test

* chore(lint): no-shadow in small app test/e2e files

* chore(lint): no-explicit-any in workspace-git-service.test

* chore(lint): no-shadow in app test hoisted blocks

* chore(lint): no-explicit-any in websocket-server.runtime-metrics.test

* chore(lint): no-explicit-any in acp-agent.test

* chore(lint): no-shadow in app composer/message-input/e2e helpers

* chore(lint): no-explicit-any in session.workspace-git-watch.test

* chore(lint): no-explicit-any in websocket-server.notifications.test

* chore(lint): no-shadow in sidebar-workspace-list.test, use-pr-pane-data.test

* chore(lint): no-explicit-any in websocket-server.relay-reconnect.test

* chore(lint): no-explicit-any in codex-app-server-agent.test

* chore(lint): no-shadow in composer.test, host-runtime.test, new-workspace-screen.test

* chore(lint): no-explicit-any in codex-app-server-agent.features.test

* chore(lint): hoist jsx-as-prop in app components batch 1

Memoize inline JSX icons passed as props across add-host-modal,
branch-switcher, composer, pair-link-modal, pr-pane, and
sidebar-workspace-list to satisfy react-perf/jsx-no-jsx-as-prop.

* chore(lint): no-explicit-any in session.test

* chore(lint): hoist jsx-as-prop in app components batch 2

Memoize inline JSX passed as props across agent-list, agent-status-bar,
file-explorer-pane, git-actions-split-button, and message to satisfy
react-perf/jsx-no-jsx-as-prop.

* chore(lint): hoist jsx-as-prop in app components batch 3

Memoize inline JSX passed as props across combined-model-selector,
draggable-list.native, provider-diagnostic-sheet, and
workspace-setup-dialog to satisfy react-perf/jsx-no-jsx-as-prop.

* chore(lint): hoist jsx-as-prop in desktop components

Memoize inline JSX passed as props across desktop-permissions-section,
desktop-updates-section, integrations-section, and pair-device-section
to satisfy react-perf/jsx-no-jsx-as-prop.

* chore(lint): no-explicit-any in mcp-server.test

* chore(lint): hoist jsx-as-prop in workspace/new-workspace screens

* chore(lint): hoist jsx-as-prop in remaining screens

* chore(lint): no-explicit-any in session.workspaces.test

* chore(lint): no-explicit-any in snapshot-mutation-ownership.test

* chore(lint): no-explicit-any in dictation-stream-manager.test

* chore(lint): no-explicit-any in relay-transport.e2e.test

* chore(lint): no-explicit-any in worktree-session.test

* chore(lint): no-explicit-any in model-resolver.test

* chore(lint): no-explicit-any in sherpa-parakeet-stt.test

* chore(lint): no-explicit-any in speech-download.e2e.test

* chore(lint): no-explicit-any in script-health-monitor.test

* chore(lint): no-explicit-any in schedule/service.test

* chore(lint): no-explicit-any in persistence-hooks.test

* chore(lint): no-explicit-any in editor-targets.test

* chore(lint): no-explicit-any in send-while-running-stuck-test-utils.test

* chore(lint): no-explicit-any in agent-storage.test

* chore(lint): no-nested-ternary in app batch 1 (18 files)

* chore(lint): no-explicit-any in generate-sherpa-tts-matrix

* chore(lint): no-explicit-any in claude-agent

* chore(lint): no-explicit-any in websocket-server

* chore(lint): no-explicit-any in process-conversation

* chore(lint): no-explicit-any in daemon-client

* chore(lint): no-explicit-any in codex-app-server-agent

* chore(lint): no-nested-ternary in app batch 2 (26 files)

* chore(format): apply oxfmt to server test files

* chore(lint): parallelize agent archiving in test-mcp-inject

* chore(lint): no-explicit-any in small app files (batch 1)

* chore(lint): no-explicit-any in small app files (batch 2)

* chore(lint): no-explicit-any in app (batch 3)

Covers use-web-scrollbar, stream-strategy, dictation-stream-sender test,
terminal-perf helper, checkout-git-actions-store test, and
web-desktop-scrollbar.

* chore(lint): no-explicit-any in app (batch 4)

Covers composer, message-input, tooltip, workspace-desktop-tabs-row,
and the e2e helpers (app, node-ws-factory, terminal-probes).

* chore(lint): no-explicit-any in polyfills/crypto.ts

* chore(lint): no-explicit-any in components/sidebar-workspace-list.tsx

* chore(lint): prefer-array-find over filter().at/pop

Replace filter(pred).at(-1)/pop() patterns with findLast(pred) and
filter(pred)[0] with find(pred) across server and app.

* chore(lint): no-explicit-any in components/message.tsx

* chore(lint): no-explicit-any in components/plan-card.tsx

* chore(lint): no-map-spread replace with Object.assign

Replace { ...obj, override } inside map callbacks with Object.assign({}, obj, { override })
to satisfy oxc/no-map-spread. Preserves copy-on-write semantics.

* chore(lint): no-extraneous-class in test mocks

Add dispose() stubs to xterm addon mocks to satisfy typescript-eslint/no-extraneous-class, and convert static-only Notification mocks to plain objects.

* chore(lint): no-named-as-default use named imports for ws and openai

* chore(lint): jsx-no-new-array-as-prop hoist composed style in volume-display

Memoize the style array so it is not created inline on every render.

* chore(lint): prefer-add-event-listener use Object.assign for non-DOM handlers

These transports (Transport interface, StreamableHTTPServerTransport) use
plain on<event> properties rather than EventTarget, so addEventListener is
not an option. Using Object.assign to set the handlers avoids the lint
false-positive without changing behavior.

* chore(lint): no-multiple-resolved null pendingResolve after settling

Replace boolean-settled guards with a nullable captured resolve/reject.
Each promise callback captures the resolver, nulls it on first use, and
calls it. This preserves the existing single-resolution semantics while
making the no-multiple-resolved rule happy.

* chore(lint): reduce structural complexity in server

Extract nested-callback cleanup helpers in worktree-bootstrap.test. Extract Codex model definition builder to drop list-models complexity below 20.

* chore(lint): extract helpers in agent-activity and session-store-hooks.test

Split tool-call update handling in groupActivities into helpers to clear max-depth. Hoist useWorkspaceFields selector in the hooks test to drop callback nesting.

* chore(lint): rules-of-hooks and exhaustive-deps in agent-stream-view

Move useMemo calls for permission card styles above the early return
so hooks run unconditionally. Add missing dependencies to the inline
path press handler and the stream render callback.

* chore(lint): rules-of-hooks in diff-scroll via optional context hook

Replace try/catch around useExplorerSidebarAnimation with a new
useExplorerSidebarAnimationOptional hook that returns null when the
provider is absent.

* chore(lint): rules-of-hooks and exhaustive-deps in sidebar-workspace-list

Replace conditional useMemo calls with plain inline arrays, capture
creatingWorkspaceTimeoutsRef.current in the effect body before cleanup,
and depend on the full input object in armTimers.

* chore(lint): rules-of-hooks in split-container

Hoist useWorkspaceLayoutStore and useMemo calls above the pane-kind
early return so hooks always run in the same order.

* chore(lint): rules-of-hooks in context-menu and dropdown-menu

Inline the resolvedWidthStyle object inside the content useMemo and move
the early return below the hook so it is always invoked in the same order.

* chore(lint): rules-of-hooks in web-desktop-scrollbar

Move thumbRegionStyle and handleStyle useMemo calls above the visibility
early return.

* chore(lint): reduce complexity in types/stream

Split reduceStreamUpdate timeline branch into per-case helpers (tool call and compaction) to clear complexity and max-depth warnings.

* chore(lint): reduce complexity in diff-highlighter

Extract metadata detection, path extraction, hunk parsing, and content-line push into helpers to drop parseDiff complexity below 20.

* chore(lint): exhaustive-deps in composer and use-attachment-preview-url

Memoize githubSearchItems so the picker callbacks don't see a new array
every render, hoist realtimeVoiceButtonStyle above rightContent useMemo
that depends on it, and add missing style dependencies. Tighten
use-attachment-preview-url to read the attachment via a ref while keying
off stable field primitives.

* chore(lint): reduce complexity in tool-call-detail-state

Extract per-detail helpers for hasMeaningfulToolCallDetail to clear complexity warning.

* chore(lint): reduce complexity in agent-grouping

Split groupAgents into partition, project-activity map, active-project, and inactive-date helpers.

* chore(lint): reduce max-depth in voice-runtime

Extract retireFinishedGroup helper to flatten processPlaybackQueue nesting.

* chore(lint): reduce max-depth in use-branch-switcher

Extract maybeRestoreStashForBranch callback to flatten handleBranchSelect nesting.

* chore(lint): exhaustive-deps in components batch

Address exhaustive-deps warnings across file-explorer-pane, file-pane,
git-diff-pane, message, provider-diagnostic-sheet, stream-strategy-web,
terminal-emulator, terminal-pane, and volume-meter. Memoize values that
otherwise changed every render, read non-reactive values through refs
when the effect intentionally tracks a different key, and add missing
dependencies where they were genuinely absent.

* chore(lint): reduce complexity/max-depth in draft-store

Extract collectQueuedMessageAttachmentIds and collectStreamUserImageIds helpers from runAttachmentGc. Extract buildMigratedDraftRecord helper for migratePersistedState.

* chore(lint): reduce complexity in tooltip and workspace-scripts-button

Extract resolveActualSide and resolveAlignedCoordinate helpers in tooltip. Extract resolveScriptIconColor in workspace-scripts-button.

* chore(lint): exhaustive-deps in contexts and hooks batch

Fixes exhaustive-deps warnings across app contexts and hooks by adding
missing deps, stabilizing derived arrays with useMemo, capturing ref values
at effect entry, and using ref pattern where deps were intentionally omitted.

* chore(lint): final hook-rules in workspace-screen, editor-button, stores, e2e

Memoizes derived arrays (availableEditors, terminals) to stabilize
useMemo deps, adds missing deps (normalizedServerId, normalizedWorkspaceId,
explorerToggleStyle, workspaceIdsKey, eventName), and renames the Playwright
fixture callback parameter from `use` to `provide` so eslint-plugin-react-hooks
doesn't conflate it with React's use() hook.

* chore(lint): reduce complexity/max-nested-callbacks in workspace-tabs-store

Extract migrate function body into migrateWorkspaceTabsState top-level
helper with per-loop sub-helpers (migrateUiTabsForKey, mergeExplicitTabOrder,
mergeLegacyTabOrder, migrateFocusedTabIds) and replace the inline IIFE in
ensureTab with buildNextTabsForEnsure helper.

* chore(lint): reduce complexity in session-store and panel-store

Extract isSessionServerInfoUnchanged helper from updateSessionServerInfo
in session-store. In panel-store split migrate body into per-version
migration helpers (migratePanelV2Explorer, migratePanelV3Explorer,
migratePanelExplorerTabByCheckout, migratePanelDesktopFocusMode) and
a top-level migratePanelState function.

* chore(lint): reduce complexity in keyboard-shortcuts and use-keyboard-shortcuts

Split resolveKeyboardShortcut into resolveInitialChordStep and
resolveAdvancingChordStep helpers with a shared buildMatchFromBinding
factory. Split handleAction's large switch into handleDispatchOnlyAction,
handlePayloadAction, handleSettingsToggle, and handleCommandCenterToggle
helpers, with hasPayloadKey type guard replacing inline payload checks.

* chore(lint): reduce complexity in audio recorder web hooks

Extract assertMicrophoneEnvironment helper from useAudioRecorder start
callback. Split useDictationAudioSource stop callback with
disconnectDictationAudioGraph, stopMediaRecorderIfActive,
finalizeRecorderStoppedPromise, and safeDisconnectNode helpers.

* chore(lint): reduce complexity in use-git-actions

* chore(lint): reduce complexity in use-pr-pane-data

* chore(lint): reduce complexity in use-agent-autocomplete

* chore(lint): reduce complexity in use-agent-screen-state-machine

* chore(lint): reduce complexity in session-stream-reducers

* chore(lint): reduce complexity in desktop-updates-section

* chore(lint): reduce complexity in pair-device-section

* chore(lint): reduce complexity in update-callout-source

* chore(lint): reduce complexity in provider-diagnostic-sheet

* chore(lint): disable no-await-in-loop (sequential-by-necessity cases)

Most remaining no-await-in-loop warnings were in legitimate sequential
patterns: polling loops with sleep, streaming/pagination cursors, shared
mutable state, ordered side effects (audio playback, port allocation).
Parallelizing these would change observable behavior. Rather than force
restructures that add complexity without benefit, disable the rule.

* chore(lint): reduce complexity in setup-panel

Extract helpers (resolveAutoExpandIndex, resolveSetupStatusLabel,
resolveCommandLog, buildCommandRowState) and lift the standalone log
view and top-level error banner into dedicated sub-components to drop
SetupPanel below the complexity limit.

* chore(lint): reduce complexity in AssistantMarkdownImage

Extract error-text resolution into a helper so the image component
drops below the cyclomatic complexity threshold.

* chore(lint): reduce complexity in ProjectLeadingVisual

Extract the status-variant dispatch into a ProjectLeadingVisualStatus
sub-component so the outer function stays below the cyclomatic
complexity limit.

* chore(lint): extract ChatAgentContent agent-building helper

* chore(lint): reduce complexity in ChatAgentContent

Extract the chat-agent selector (selectChatAgentState +
resolveChatAgentFromSession), the agent-shape constructor
(buildChatAgentFromState), and the not-found/error/boot view
dispatch (renderChatAgentNonReadyView) into helpers so both the
selector callback and the component function drop below the
cyclomatic complexity limit.

* chore(lint): extract subcomponents to reduce JSX nesting depth

* chore(lint): reduce complexity in app components (status-bar, explorer, setup-dialog, tool-call-details)

* chore(lint): reduce complexity in app hooks/runtime/stores/misc components

* chore(lint): long-tail cleanup (no-async-endpoint-handlers, unicorn rules, unescaped-entities, unassigned-import allowlist)

* chore(lint): simplify agent useMemo deps to match helper signature

* chore(lint): rename ChatService.postMessage to dispatchMessage

* chore(lint): server cleanup (no-shadow, no-useless-*, no-unmodified-loop, control-regex, require-yield)

* chore(lint): app cleanup (jsx-max-depth, jsx-no-new-*-as-prop, complexity, misleading-regex, no-useless-*)

* chore(lint): e2e cleanup (no-unmodified-loop-condition, max-nested-callbacks)

* chore(lint): workspace-screen complexity + no-empty-pattern cleanup

* chore(lint): use Array.from for defensive snapshot iteration

* chore(lint): final mechanical tail (control-regex, max-depth, useless-expressions, examples cleanup)

* chore(lint): reduce agent-stream-view complexity

* chore(lint): reduce resolveAgentModelSelection complexity

* chore(lint): reduce globalSetup complexity

* chore(format): oxfmt draft-store single-line signature

* chore(lint): reduce GitDiffPane complexity

* chore(lint): reduce MessageInput complexity

* chore(lint): reduce Combobox complexity

* fix(server): bound listLinuxWatchDirectories readdir concurrency

Wraps the per-level readdir traversal in listLinuxWatchDirectories with a
module-level p-limit (default 16, tunable via PASEO_LINUX_WATCH_READDIR_CONCURRENCY).
On broad repos, the previous Promise.all over an entire BFS level could issue
an unbounded number of concurrent readdir calls. Behavior is otherwise
identical: still traverses all non-.git directories and swallows per-directory
readdir failures.

* chore(lint): promote oxlint warnings to errors

- package.json: lint/lint:fix now pass --deny-warnings so any warning fails
- .github/workflows/ci.yml: add lint job running npm run lint
- .oxlintrc.json: disable no-empty-pattern for e2e/fixtures.ts
  (Playwright requires the empty object destructure as the first arg)
- packages/app/e2e/fixtures.ts: restore async ({}, provide) signature
- packages/app/e2e/global-setup.ts: oxfmt single-line signature

* fix(test): relay-transport ws mock exports WebSocket named binding

Commit 38efad7d switched relay-transport.ts from default to named import
of ws.WebSocket, but the test mock still only exported default. Updated
vi.mock("ws") to return both default and named WebSocket bindings so the
mocked module matches the new import shape. Classification: test drifted
with production code; test now exports what the production import needs.

* chore(lint): make oxlint error at config level; add Lefthook pre-commit

Replaces --deny-warnings workaround with semantic severity at the config
level, and adds whole-repo pre-commit gates via Lefthook.

- .oxlintrc.json: all enabled categories (correctness, suspicious, perf)
  and intentional rule overrides now use "error" instead of "warn".
  Removed duplicate "require-await" key (last-wins "off" preserved).
- package.json: lint scripts are plain oxlint / oxlint --fix. Adds
  lefthook devDep and "prepare": "lefthook install --force" so hooks
  install on npm install even where core.hooksPath is set globally.
- lefthook.yml: pre-commit runs format:check, lint, typecheck in
  parallel. Whole-repo gates (no glob filters). Block-only, no
  auto-formatting.

* fix: update lockfile signatures and Nix hash

* chore(lint): pre-commit runs formatter in write mode, then lint+typecheck

Switches the pre-commit hook from format:check to format (write) with
stage_fixed: true, so the formatter rewrites files and re-stages them
into the commit. Lint and typecheck then run in parallel against the
formatted tree.

Sequencing uses Lefthook 2.x jobs with a nested group: top-level jobs
run sequentially, so the format job completes before the checks group
starts; within checks, lint and typecheck run in parallel.

CI continues to use format:check, so unformatted code pushed with
--no-verify still fails on the server.

* chore(lint): revert pre-commit to format:check (no stage_fixed)

stage_fixed: true with a whole-repo formatter (oxfmt .) can silently
re-stage the full reformatted file when a developer used git add -p
to stage only a hunk, losing their hunk-level intent. Safer to just
block the commit on unformatted code and let the dev run
`npm run format` themselves.

Reverts to the simple parallel check form: all three commands run
concurrently and are read-only.

* chore(ts7): tsgo compatibility across the monorepo

Preserves tsc 5.9.3 behavior while making TypeScript 7 / tsgo
(@typescript/native-preview 7.0.0-dev.20260423.1) green
package-by-package.

tsconfig:
- server/tsconfig.server.json: drop removed `baseUrl`; rewrite
  `@server/*` paths entry to be relative to the tsconfig.
- website/tsconfig.json: drop removed `baseUrl`; rewrite
  `~/*` paths entry to be relative.
- expo-two-way-audio/tsconfig.json: replace inherited
  moduleResolution=node10 with `bundler`, set `module: esnext`
  (required by `bundler`). Matches Metro/Expo runtime behavior
  and avoids forcing `.js` extensions in source.

source:
- app/e2e/helpers/terminal-probes.ts: WebSocket subclass `send`
  now types its argument as `Parameters<WebSocket["send"]>[0]`
  so the subclass signature tracks the platform type across
  tsc 5.9 and TS 7 libdom.
- relay/src/crypto.ts: tweetnacl setPRNG callback allocates a
  fresh Uint8Array for getRandomValues (owning ArrayBuffer) and
  copies into the caller-provided view. TS 7 libdom requires
  ArrayBufferView<ArrayBuffer> (not ArrayBufferLike).

All 8 packages green under both tsc --noEmit and tsgo --noEmit.

* fix(app): update os-notifications test mocks for addEventListener

Commit 46731a51 switched attachWebClickHandler from notification.onclick
to notification.addEventListener("click", ...) to satisfy the
prefer-add-event-listener lint rule. The test mocks still asserted
the old onclick property, so two specs failed on CI with
"notification.addEventListener is not a function". Update the three
MockNotification classes to record click listeners through
addEventListener and drive them via clickListeners[0] in the
assertions.

* chore: switch typecheck from tsc to tsgo (@typescript/native-preview)

Installs @typescript/native-preview 7.0.0-dev.20260423.1 as a root
devDependency and swaps every per-workspace `typecheck` script from
`tsc --noEmit` to `tsgo --noEmit`. Build scripts still use `tsc`, so
emit behavior is unchanged. Monorepo typecheck drops from ~28s to
~4.7s wall clock (~6x faster).

expo-two-way-audio previously ran `tsc` without --noEmit for its
typecheck step. tsgo is stricter about rootDir on emit, so the
typecheck script explicitly passes --noEmit here too.

* fix: update lockfile signatures and Nix hash

* fix(app): commit *.css module declaration for CI typecheck

expo generates expo-env.d.ts (which references expo/types where
*.css is declared) locally, but the file is gitignored. CI does a
fresh checkout + install and doesn't run expo, so the declaration
is missing and tsgo rejects the side-effect import
'@xterm/xterm/css/xterm.css' with TS2882. Add a committed
packages/app/global.d.ts with 'declare module "*.css"' so typecheck
is self-contained.

tsc tolerated the missing declaration more loosely; tsgo is stricter.

---------

Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
2026-04-24 12:26:10 +08:00
Mohamed Boudra b6afc66131 fix(app): avoid settings header offset 2026-04-22 16:19:31 +07:00
Mohamed Boudra 6b210df471 fix(app): stabilize startup connection routing 2026-04-22 13:11:41 +07:00
Mohamed Boudra 92e701be8f style: adjust typography and standardize ui text 2026-04-20 21:31:59 +07:00
Mohamed Boudra dfc814147a feat: add beta release channel 2026-04-20 20:38:56 +07:00
Mohamed Boudra 5c3cc99d8c refactor(app): restructure settings with modular section navigation 2026-04-18 14:21:40 +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 BoudraandGitHub 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
Mohamed Boudra e3962e1753 Merge origin/main into dev (second round)
Resolve 21 conflicts from latest main (provider profiles, Windows fixes, 0.1.55-rc.1).
Fix all type errors and syntax issues from merge artifacts.
2026-04-14 04:07:36 +07:00
Mohamed Boudra 8b1fbb7b86 Merge branch 'main' into dev
Resolve 38 conflicts preserving both sides' work:
- Server: DB-based registries (dev) + WorkspaceGitService/DaemonConfigStore (main)
- App: script proxy/voice MCP (dev) + pin/hide tabs/bootstrap boundary (main)
- Shared: WorkspaceScriptPayload (dev) + git/GitHub runtime schemas (main)
- Deleted workspace-registry-bootstrap.ts (dev's DB approach supersedes)
2026-04-14 03:05:45 +07:00
Mohamed BoudraandGitHub 6f28027687 feat: provider profiles — custom provider definitions (#290)
* feat: add provider profiles for custom provider definitions

Users can define custom providers in config.json that appear as first-class
entries alongside built-ins. A provider can override a built-in (custom binary,
env, models) or create a new one by extending a base via `extends`. Generic
ACP transport supported via `extends: "acp"`. Providers can be hidden with
`enabled: false`. Hardcoded models merge with runtime-fetched ones.

- Config schema with Zod validation, auto-migration from old format
- Dynamic provider registry replaces static provider lists
- GenericACPAgentClient for user-defined ACP providers
- Snapshot entries carry label/description/defaultModeId over the wire
- MCP tools accept dynamic provider IDs
- App derives provider definitions from snapshot with static fallback
- CLI `provider ls` calls daemon with label column
- Schedule/session rehydration validates providers against registry

* fix: accept any provider status in CLI provider ls test

The test now connects to a real daemon where providers may be loading
or unavailable, not just the static "available" fallback.

* ci: re-trigger CI checks

* style: fix checkout-git.ts formatting to match CI Biome version

* fix: relax provider ls test assertions for daemon-backed responses

The daemon snapshot may not include all 5 built-in providers in CI
(some require external binaries). Assert at least the core 3
(claude, codex, opencode) instead of all 5.

* fix app combobox dropdown positioning flash

* refactor: stop merging models in provider registry, use override models directly

Override models now replace instead of merge with base provider models.
Also add icon/colorTier fallback from definition modes in fetchModes.

* refactor: make provider definitions fully dynamic from server snapshots

Remove static AGENT_PROVIDER_DEFINITIONS fallbacks from the client — providers,
modes, icons, and color tiers now flow entirely from runtime snapshots. Add icon
and colorTier to AgentMode schema so the server can advertise mode visuals
directly. Fix setAgentMode to persist modeId in agent config so the selected
mode survives session reload. Simplify model merging so profile models replace
runtime models instead of prepending.

* docs: add ad-hoc daemon testing guide
2026-04-13 19:42:01 +07:00
Mohamed Boudra 0f7fa55a0f Centralize platform gating and fix iPad/tablet support
Add `packages/app/src/constants/platform.ts` with canonical gates
(isWeb, isNative, getIsElectron) and refactor all ~100 ad-hoc
Platform.OS checks across 69 files to use shared imports.

Fix sidebar hover crash on native (onPointerEnter → onHoverIn),
fix tooltip to use breakpoint instead of platform detection, make
sidebar action buttons always visible on native where hover doesn't
work, and document the platform gating decision matrix in CLAUDE.md.
2026-04-13 13:24:00 +07:00