Commit Graph
12 Commits
Author SHA1 Message Date
de69b2a2af fix(server): tone usage bars by how full they are (#2322)
A usage bar in Settings -> Usage stayed green all the way to 100%, so
the one moment the meter matters was the moment it said nothing.

The client already computes this. `window-bar.tsx` reads
`window.tone ?? deriveTone(usedPct)`, and `deriveTone` escalates past
70% and 90%. But `usage.ts` only assigns `window.tone` when a provider
sends one, so any provider that sends a tone opts out of the thresholds
entirely. Claude and Kimi hardcoded `tone: "ok"`, and Codex escalated to
`warning` past 70% but could never reach `danger`.

Providers now derive tone from the percentage they already have, through
one shared `toneFromUsedPct` whose thresholds match the client's.
Claude, Codex and Kimi windows all escalate correctly.

Balances had the same gap by a different route: `balance-bar.tsx` has no
`deriveTone` fallback, and `balanceToneFromRemaining` only escalates
once a balance is completely spent, so a credits bar at 99% was green.
Cursor and Grok know their limit, so they now tone by used percentage.
Codex credits report only a remaining balance with no limit, so they
keep the remaining-based tone, documented as the no-limit case.

MiniMax is unchanged: it maps a status its own API supplies rather than
hardcoding a tone.

Verified against the live Anthropic API with a session window at 83%,
which now reports `warning` where it previously reported `ok`.

Closes #2320

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-23 03:30:44 +08:00
Christoph LeiterandGitHub 9f5f5fce62 Fix terminal resize race (#2059) 2026-07-16 23:47:09 +02:00
64c819efeb fix(desktop): stop sandboxed preload from requiring a local module (#2111)
* fix(desktop): stop sandboxed preload from requiring a local module

The preload imported PASEO_BROWSER_PROFILE_PARTITION from the local module
./features/browser-profile.js. The preload runs in Electron's sandbox and is
tsc-compiled (not bundled), so that import emits require("./features/
browser-profile.js") at the top of preload.js. A sandboxed preload cannot
require a local module, so the require throws before
contextBridge.exposeInMainWorld runs. window.paseoDesktop is then undefined,
the app no longer detects itself as the desktop host, and it never starts the
built-in daemon (users had to run `paseo start` by hand).

The import was added in 0.1.108; 0.1.107 is clean, which matches the reports
that downgrading fixes it. See #2103.

Inline the one value the preload needs (the partition string) so it no longer
loads any local module, and keep browser-profile.ts as the source of truth for
the main process. Add preload-sandbox.test.ts, which parses preload.ts with the
TypeScript compiler API and asserts the only runtime module load is "electron"
(covering value imports, side-effect imports, re-exports, require, dynamic
import, and import-equals; type-only imports are ignored), plus a drift check
that the inlined literal matches the canonical constant.

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

* test(desktop): clearer preload drift-check failure message

Assert the inlined PASEO_BROWSER_PROFILE_PARTITION is present before comparing its value, so a missing/renamed constant reports "not found" instead of a misleading value-drift mismatch (Greptile feedback on #2111).

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 09:39:51 +02:00
6804882761 Document iOS/Android local dev setup and fix the mise Android toolchain (#2092)
* Pin android-sdk to 21.0 and unify Java on 21 for mise

The `.tool-versions` `android-sdk latest` pin had resolved to the
ancient `1.0` cmdline-tools bundle, whose sdkmanager (3.6.0) predates
the `emulator` package and fails with `Failed to find package
emulator`. Pin `android-sdk 21.0`, point the hardcoded install paths
in `.mise.toml` at it, and add `cmdline-tools/21.0/bin` to PATH so
sdkmanager and avdmanager resolve.

Also unify the Java version on 21: `.tool-versions` already pinned 21,
but `.mise.toml` overrode it to 17. A local `npm run android` Gradle
build succeeds on 21.

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

* Document iOS simulator and Android emulator local dev setup

Add local-dev docs for running the app against a worktree daemon:

- development.md: prerequisites for the ios-simulator preview service
  (Xcode, an iOS runtime, automatic CocoaPods install), the
  xcode-select/simctl troubleshooting fix, and a "Running the iOS app
  on a local simulator" section covering `npm run ios` and pointing
  the app at a worktree daemon via EXPO_PUBLIC_LOCAL_DAEMON.
- android.md: a "Prerequisites (local dev)" section (mise java 21 +
  android-sdk 21.0, sdkmanager components, AVD creation) and a
  "Running on an emulator against a worktree daemon" section covering
  REACT_NATIVE_PACKAGER_HOSTNAME and EXPO_PUBLIC_LOCAL_DAEMON=10.0.2.2
  for reaching the host daemon.

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

* Give full Intel x86_64 emulator setup commands in Android docs

Greptile flagged that the arm64-v8a -> x86_64 substitution for Intel
Macs was only a parenthetical. Provide a complete, copy-pasteable
x86_64 command block instead of asking readers to hand-edit the arm64
one.

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-16 00:44:17 +08:00
bac302626f feat(app): give each permission mode a distinct icon (#1980)
* feat(app): give each permission mode a distinct icon

The mode selector reused shield glyphs across modes: Auto mode showed a
question mark, Always Ask a checkmark, and the same icon appeared on
several modes (ShieldCheck on both Always Ask and Plan, ShieldAlert on
both Accept File Edits and Bypass). The result read as arbitrary rather
than as a scale of autonomy.

Assign every built-in provider mode a distinct icon from one shared
vocabulary, applied consistently across Claude, Codex, Copilot, and
OpenCode:

- Shield         guarded default (every action prompts)
- ShieldCheck    classifier/auto-reviewer vets prompts
- ShieldPlus     edit tools pre-approved, others still prompt
- ShieldEllipsis plan / read-only deliberation
- ShieldOff      fully permissive, no prompts

Register the new icons in the client MODE_ICONS map. Old clients
downgrade unknown icon names to ShieldCheck through the existing
customModeIcons compat gate, so the wire contract is unchanged.

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

* feat(app): order permission modes by autonomy

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
2026-07-13 11:45:42 +02:00
2658132384 fix(terminal): create PTYs at the client viewport size, not 80x24 (#2023)
New terminals were always spawned by the daemon at the hardcoded 80x24
default and only resized once the client's first resize arrived. On a
slow first mount that leaves a window -- or a stuck state -- where a new
terminal renders far smaller than its pane (e.g. vim in a corner).

Seed the PTY with the client's measured viewport size at creation time
so it is born at the right size and the race window is gone.

- protocol: optional `size` on create_terminal_request. Old daemons
  ignore it and keep 80x24; old clients send nothing. Fully backward
  compatible, no capability gate.
- client: a small last-measured-size cache keyed by serverId+cwd (every
  terminal in a workspace shares the pane) with a most-recent fallback;
  written on fit, read at create.
- server: thread rows/cols through the controller, terminal manager, and
  the worker-thread boundary into the existing size-aware createTerminal.

Tests: cache logic, protocol back-compat parse, controller forwarding.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-13 03:26:52 +08:00
c05e337cde fix(dev): drop unneeded cross-env from paseo.json dev scripts (#1637)
When running Paseo on the Paseo repo itself, the worktree setup step and the
paseo.json service scripts (daemon/app/desktop/ios-simulator) fail with
"cross-env: command not found" unless cross-env is installed globally. The
daemon runs paseo.json commands in a plain shell without the project's
node_modules/.bin on PATH (unlike npm run), so the bare cross-env (a local
devDependency) does not resolve.

cross-env only exists for Windows cmd compatibility, and every one of these
commands launches a unix ./scripts/*.sh, so it was not buying anything here.
Drop it and set the env vars inline. The cross-env dependency stays -- it is
still used by the npm run scripts where it is actually needed.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-12 11:40:39 +00:00
3259a69466 feat(app): folder tree in the Changes view (#1918)
Group changed files in the Changes view into a collapsible directory
tree instead of a flat list, on every form factor — consistent with the
Files explorer, which already renders a directory tree on mobile.
Folders are collapsible and expanded by default, single-child directory
chains are compressed into one row (packages/app/src), and folder rows
show aggregate +/- stats.

Client-only, built entirely from the existing ParsedDiffFile paths:

- diff-tree.ts: pure build/compress/flatten of the directory hierarchy,
  keyed by full uncompressed dir path so collapse state survives
  compression changes as the diff mutates.
- diff-flat-items.ts: pure buildDiffFlatItems + sumHeightsBefore. The
  function retains a flat mode (showTree=false) as a tested seam for a
  possible future flat/tree toggle; sticky header indices are
  file-headers-only, derived from the post-collapse list.
- panel-store: diffCollapsedFoldersByWorkspace slice (collapsed-set, so
  folders default to expanded), persisted via version 12. The set is
  reconciled against the current diff so folders that leave and reappear
  default to expanded and the stored set stays bounded.

Presentation primitives (chevron, indent guides, indent constant) are
extracted into components/tree-primitives and shared with the Files
explorer, which drops its private copies (and a banned useUnistyles
call site). The two trees now render identical rows; only the
tree-building stays separate (server listings vs. diff paths).

The toolbar's expand-all stays file-diffs-only (renamed
allFileDiffsExpanded); a separate control collapses/expands all folders.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-09 03:55:30 +08:00
807d0d6d69 fix(app): show New workspace action on non-git sidebar projects (#1857)
* fix(app): show New workspace action on non-git sidebar projects

The per-project "New workspace" affordance in the sidebar (the project-
header + button and the empty-project ghost row) was gated on
host.canCreateWorktree, i.e. projectKind === "git". Non-git projects
(non_git / directory) therefore showed no way to add a workspace, even
though a host with the workspaceMultiplicity capability can create
additional local workspaces for them.

Thread the per-host workspaceMultiplicity flag into the sidebar project
row model and show the affordance when canCreateWorktree ||
supportsWorkspaceMultiplicity, matching the gate already used by the
global "New workspace" button and the Cmd+N handler. Rename the internal
trailing-action kind new_worktree -> new_workspace (a client-only UI
model, not a wire type) to reflect that it now also covers non-git
workspaces.

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

* test(app): expect the per-row New workspace icon on non-git projects

The sidebar "+" now shows for non-git projects on a multiplicity-capable
host, so the Model B sidebar spec no longer asserts the non-git project
has zero new-worktree icons — it now expects the icon, like git projects.
Also refresh the now-stale "no new-worktree icon" comments in the
entry-points spec (the picker is still one valid entry point).

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

---------

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-02 21:15:10 +08:00
Christoph LeiterandGitHub 2b46caa20e feat(app): clearer interactive question card
Fixes #1642
2026-06-30 22:53:12 +02:00
daf7042cd2 Refresh open file tabs when revisited (#445) (#1699)
File previews read once and then froze: visited tabs stay mounted-but-hidden
(LRU in useMountedTabSet), so refetchOnMount never re-fired and the only way
to see fresh content was to close and reopen the tab.

Gate the file query's `enabled` on tab + app visibility so React Query
refetches on the disabled->enabled transition — and only when the cached read
is stale (keeping the existing 5s staleTime), so a quick flip away and back
doesn't re-read. Covers switching between tabs and backgrounding/reopening the
whole app to the same tab.

Adds a wiring test covering the stale-revisit refetch, the fresh-revisit
debounce, the app-foreground refetch, and the read-error path.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 20:16:52 +08:00
931ea97942 fix(app): reveal workspace-jump numbers only for the active modifier (#1580)
Holding the workspace-jump modifier reveals numbered badges in the sidebar for jumping to a workspace (Cmd+1..9). On the desktop app the badges also appeared while holding Option or Control, neither of which performs the jump — only Cmd does on macOS desktop (Ctrl on non-Mac, Alt on web).

The keydown/keyup handler flagged badge visibility for Alt on every runtime and for both Meta and Control on desktop — broader than the active workspace.navigate.index binding. Gate the badges to the single modifier that performs the jump on the current runtime, via a new getWorkspaceIndexJumpModifierKey helper. Adds unit tests for the three runtime cases.

Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-18 16:55:38 +00:00