User bubbles previously used surface2, which blended into the surrounding
chat surface. Add an `accentSubtle` theme token (accent darkened, slightly
desaturated, low alpha) and use it for the bubble fill so user messages
read as distinct without shouting.
Fixes#986
Adds Lezer-based highlighting to fenced code blocks in assistant messages,
with a hover/tap copy button. Colors move into the theme as
`theme.colors.syntax`, so the chat blocks, file preview, and diff viewer
share one StyleSheet path — token color updates flow through the Unistyles
native ShadowRegistry with no React re-renders on the chat hot path.
* fix(mcp): add missing schedule tools (update, logs, run-once)
Add three schedule MCP tools to close the parity gap between
CLI (`paseo schedule update/logs/run-once`) and MCP surface.
The WebSocket RPC handlers and ScheduleService methods already
exist (added in PR #694), but the MCP tools were never registered.
Tools added:
- update_schedule: update interval, prompt, name, maxRuns, etc.
- schedule_logs: retrieve run history for a schedule
- run_once_schedule: trigger immediate one-time execution
Includes 8 unit tests covering all three tools.
Fixes#1031
* Fix schedule MCP update handling
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
The submit_review fastlane lane identified the just-uploaded build by
CFBundleVersion alone. In this project the production iOS profile resets
the build number per marketing version, so every release sits at build 2
and Spaceship::ConnectAPI::Build.all returns the whole TestFlight history.
The .find call picked an arbitrary match, which is how 0.1.76's binary got
attached to the 0.1.75 App Store version slot and submitted for review
under the wrong label.
The release workflow now forwards app_version and app_build_version from
the build_ios outputs into the fastlane step as env vars, and the Fastfile
filters builds by marketing version plus build number to pin the exact
binary. The standalone resubmit workflow takes the same identifiers as
optional workflow_dispatch inputs; with both unset it falls back to "most
recently uploaded iOS build" so manual reruns still work.
Pinning fastlane in the Gemfile to lock the Spaceship behavior this lane
depends on.
* Open chat file links in a side pane on cmd-click
* Add hover tooltips to file links showing cmd-click hint
* Collapse file-link open path to a single disposition seam
Replace the parallel "open in side" callback that was threaded
alongside the normal open callback at every layer between the chat
click handler and the workspace router. One callback now carries a
`OpenFileDisposition` ("main" | "side") so the modifier-key decision
lives at the leaf and the routing decision lives at the workspace
screen, with no branching in between.
* Give assistant file links a home
* fix(app): require long-press for sidebar dnd on mobile
* fix(app): right-edge open gesture for explorer sidebar
* fix(app): left-edge open gesture for workspace sidebar
* fix(app): allow gesture children to scroll on web
* fix(app): lock viewport zoom on compact web
* fix(app): don't submit on enter in compact web composer
---------
Co-authored-by: jon <nikuscs@gmail.com>
`inheritUserEnvironment` set the daemon's PATH to NixOS user/system
profiles only, missing `~/.nix-profile/bin` and
`~/.local/state/nix/profile/bin` where home-manager installs per-user
CLIs. Result: agents like claude/opencode installed via home-manager
were invisible to the daemon and provider checks failed with
"Provider 'X' is not available. Please ensure the CLI is installed."
Prepend both home-manager profile dirs to PATH when running as a real
user (cfg.user != "paseo"), and update the option doc accordingly.
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
* fix mobile sidebar web interactions
* fix(app): restore button role on sidebar project row
---------
Co-authored-by: Mohamed Boudra <boudra.moha@gmail.com>
* nix: share npmDeps FOD between paseo and paseo-desktop
The daemon and desktop derivations share package-lock.json, so their
npmDeps FODs contain byte-identical content. But Nix names the FOD
store path with the consuming pname prefix, so paseo-<v>-npm-deps and
paseo-desktop-<v>-npm-deps resolve to different store paths despite
identical bytes. Building both runs prefetch-npm-deps twice in
parallel; on a clean store this downloads the entire registry
(~1-2 GB of tarballs) twice over the wire.
Wire paseo through as a callPackage arg of the desktop drv and
inherit (paseo) npmDeps. One FOD, one fetch, one store path.
Override path becomes paseo.override { npmDepsHash = "..."; } — the
desktop drv picks up the overridden npmDeps transitively. Downstream
flakes that previously did paseo-desktop.override { npmDepsHash }
need to either drop the desktop-side override (recommended) or pass
the overridden daemon through as paseo-desktop.override { paseo }.
* nix: trace daemon runtime closure with @vercel/nft
The daemon Nix installPhase used to `cp -a node_modules $out/lib/paseo/`,
shipping every package in the hoisted root — Expo, React Native, Metro,
Electron, ML stacks, ~1700 third-party deps the daemon never `require()`s
at runtime. Result: ~2.6 GB store path for a daemon whose actual runtime
closure is a tiny fraction of that.
Replace it with static module-graph tracing via @vercel/nft (the same
library Vercel and Next.js use for serverless bundling). The new
installPhase runs `node scripts/trace-daemon.mjs`, which:
1. Calls nodeFileTrace on three entry points — cli/dist/index.js,
server/dist/scripts/supervisor-entrypoint.js, and the forked
server/dist/server/terminal/terminal-worker-process.js. The worker
needs to be a separate entry because nft does not follow fork()
process boundaries.
2. Unions the trace result with an explicit list of non-JS runtime
inputs nft does not detect: shell-integration assets read via
readFileSync, the Silero VAD ONNX model loaded by the sherpa
provider, .env.example, the CLI shebang script, and node-pty's
compiled prebuilt binary for the host platform.
3. Ignores cross-platform native variants we explicitly do not ship
(sherpa-onnx-*, @mariozechner/clipboard-*) and node-fetch's
optional `encoding` peer.
installPhase consumes the file list, copies each entry into
$out/lib/paseo/ preserving its repo-relative path, and wraps two bin
entries (paseo, paseo-server) via makeWrapper.
Verified end-to-end:
- daemon $out drops from ~2.6 GB to ~131 MB (≈95% reduction)
- paseo --version, paseo --help work from the new $out
- paseo-server boots, HTTP server reaches "Server listening" within
~40 ms, no missing-module errors in the bootstrap log
- speech features degrade gracefully when sherpa-onnx-* / model files
are absent (the documented behaviour for the Nix build, unchanged)
@vercel/nft added to root devDependencies (1.5.0); used only at
build time by the Nix derivation.
* fix(nix): gitignore result
* fix(nix): refresh npm deps hash post-rebase
The conflict resolution during rebase kept the trace-daemon hash, but
the merged package-lock.json combines upstream's lockfile updates with
the @vercel/nft addition, so the hash needed to be recomputed.
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
---------
Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Lists previously had a static marginVertical that stacked with
paragraph.marginBottom, producing 20px gaps and oversized whitespace
around paragraphs sandwiched between lists.
Spacing is now context-aware via the render rule: list -> list keeps
the normal paragraph gap (back-to-back lists read as continuous
content) while p <-> list transitions get a slightly larger gap (lists
read as their own section). Static margins can't satisfy both because
RN doesn't collapse margins, so the bullet_list / ordered_list rule
inspects the next sibling type and picks marginBottom accordingly.
Also exclude package-lock.json from the format pre-commit hook glob —
oxfmt internally ignores lockfiles but exits with an error when given
no remaining targets, breaking lockfile-only commits.
Claude CLI's built-in slash commands (/voice, /usage, "Unknown command:
...") run client-side with no model turn — they arrive as a result with
output_tokens: 0 and the user-visible text carried in result.result, but
no preceding assistant_message in the stream. Surface that text as an
assistant_message so the turn doesn't end silently.
Also unify Codex skill-command text blocks to always carry the
"$skill-name" prefix in the text payload, matching the no-args path
which already did.
- Centralize horizontal alignment: StreamItemWrapper / contentWrapper own
the chat rail (paddingHorizontal: 8). Stream items stop setting their
own outer horizontal padding. Copy icons get negative margins so the
visible icon sits flush with the rail edges.
- Unify the inline working indicator and assistant turn footer into a
shared slot (minHeight: 24, paddingBottom: 24). No layout shift on
streaming -> idle, and both gain breathing room above the composer
and the next user bubble.
- Equalize the gap on both sides of an assistant message adjacent to a
tool call (20px each). Drop assistantMessageStylesheet.containerSpacing,
which was a redundant marginBottom that only fired in one direction
and duplicated getGapBetween's responsibility.
- Scale up the chevron via transform (size stays 12, viewBox padding no
longer dominates), iconBadge dimensions unchanged. Tool icon and
chevron now read as similar visual weight.
- Shared STREAM_METADATA_FONT_SIZE (13) for user message timestamp,
assistant footer duration/timestamp, and the live elapsed timer.
- formatDuration: one rule for static and live displays - 0-10s as
X.Xs, 10-60s as Xs, minutes/hours integers only. Drops the `mode`
parameter.
- formatMessageTimestamp: explicitly carry the resolved hourCycle so
the runtime applies an OS-level 24h preference when the platform
exposes it (e.g. AppleICUForce24HourTime on macOS).
The user message bubble had hover on a `Pressable` wrapping the bubble,
with the timestamp and copy button as siblings outside it — moving the
cursor onto them lost hover. Wrapping both in a single Pressable then
flickered, because `TurnCopyButton` is itself a Pressable and nested
Pressables fight over hover state.
Rewrite `UserMessage` to match the workspace row pattern: hover-tracking
on a plain outer `View` with `onPointerEnter`/`onPointerLeave`, nested
Pressables freely inside. Pointer events are non-bubbling DOM events,
so descendants can't disrupt the outer hover.
Rewrite `docs/hover.md` with the workspace row as THE canonical pattern,
not one option among several. Names the three failure modes that come
from skipping it — including "nested Pressables fight over hover" — and
explicitly bounds when `Pressable.onHoverIn`/`onHoverOut` is acceptable
(self-styling only).
Update `constants/platform.ts` so its hover guidance points at the doc
instead of recommending the broken pattern.
When the user invokes /paseo-advisor with another skill reference
(/unslop, /unslop-risk, $diagnose, etc.), the orchestrator should
instruct the advisor to load and invoke that skill against the current
task before doing anything else.
Thread an optional logger through runGitCommand and the CheckoutContext
helpers so workspace-git-service can emit trace-level traces around each
git invocation. Behavior is unchanged when no logger is supplied.
Add a daemon setting `autoArchiveAfterMerge` (default off). When on, the
daemon's existing GitHub poll triggers archive once the PR is merged,
but only if the worktree is clean (not dirty, not ahead of origin).
Skips silently otherwise.
* Auto-attach pasted GitHub PR/issue URLs in the composer
Paste a GitHub PR or issue link into any composer and it shows up
as an attachment pill within a debounce tick when the URL matches
the workspace's remote. On the new-workspace screen, an attached PR
also surfaces an inline "Check out PR #N?" hint next to the picker;
one click promotes it to the checkout target through the same
syncPickerPrAttachment flow the dropdown uses.
Unifies the new-workspace picker and composer attachment picker on
one shared GitHub search hook and cache key, so both consumers (and
the auto-attach lookup) share the React Query cache.
* Fix oxfmt formatting in new-workspace-screen import
* Add project rename so duplicates get distinguishable names
Two checkouts of the same remote collapse into a single project record
(same projectId), which left users unable to tell duplicates apart in
the UI. customName lives alongside the derived displayName as an
override that reconciliation never touches, so renames persist across
git-remote changes. The rename UI lives in project settings.
Closes#987 (rename half).
* Fix COMPAT(projectCustomName) version to v0.1.76
* Update useProjects shape test for new projectCustomName key
* Use sendCorrelatedSessionRequest for renameProject
Match the pattern used by checkoutPrMerge and other newer RPCs
instead of hand-rolling the select callback.
* Use project.rename.{request,response} dotted RPC names
Match the convention from docs/rpc-namespacing.md (added in 75a6f8277).
Schemas, type exports, daemon client, session handler, and tests all
move to the namespaced names. No back-compat shim needed since the RPC
hasn't shipped.