Commit Graph
2700 Commits
Author SHA1 Message Date
Jason ee66be2222 ci(release): build macOS arches in parallel and add manual dry runs
- macOS used one runner that compiled aarch64 and x86_64 one after the
  other inside `tauri build --target universal-apple-darwin`, and every
  notarization retry recompiled both. Now `macos-binary` compiles each
  arch on its own runner with --no-bundle, and `macos-release` merges
  them with lipo and runs `tauri bundle --target universal-apple-darwin
  --bundles app`. Only that bundle/sign/notarize step is retried. The
  styled DMG from create-dmg, the zip and the updater tar.gz are made as
  before. `macos-binary` carries the release environment, so a release
  is still approved once at the start; `macos-release` must not, or it
  would wait for a second approval halfway.
- The Tauri signing key preparation moves into a composite action shared
  by the Linux/Windows matrix and `macos-release`. Its output matches
  the old inline script for every key format tried.
- Windows x64 builds only the MSI. Tauri also built an NSIS setup.exe
  that was never uploaded; the portable zip still comes from the exe.
- latest.json is generated inside publish-release from the downloaded
  signatures, before the release is created, so a missing platform
  fails without leaving a half-published release. The
  assemble-latest-json job is gone.
- workflow_dispatch runs the full build, signing and notarization as a
  dry run, with assets kept as workflow artifacts. Only a tag push
  publishes, even when a dispatch is started on a tag; the concurrency
  group includes the event name so a dry run cannot cancel a release in
  progress; and `/` in branch names becomes `-` in asset file names.
- Quote the $GITHUB_OUTPUT redirects that shellcheck flagged.
2026-10-05 12:18:17 +08:00
Jason e95c5e68d0 ci: path-filter pushes to main too, with a weekly full run
Pushes to main ran every job on the theory that they keep the caches
warm, but a cache whose key hits exactly is never saved again, so those
full runs refreshed nothing. Pushes now skip unaffected jobs like pull
requests do.

A scheduled run every Monday (03:41 Beijing) and workflow_dispatch run
everything, rebuilding caches after eviction: the filter step only runs
for pull_request and push, and the `|| 'true'` fallback on its outputs
turns a skipped filter into "both areas changed". The concurrency group
now includes the event name so the scheduled run and a push to main do
not cancel each other.
2026-10-05 12:17:58 +08:00
Jason b544bc27c6 ci: run backend tests under cargo-nextest and cache deps with rust-cache
- Backend tests run with `cargo nextest run --profile ci`: one process
  per test, a 180s per-test timeout (src-tauri/.config/nextest.toml),
  and no fail-fast. The WSL2 nightly uses it too, still one test at a
  time and with the same selection as the old `--skip database::`, so a
  hung test is killed instead of eating the whole job.
- Integration tests shared one fixed test home, and each process wiped
  it on startup. Under nextest they ran concurrently and failed en
  masse, so tests/support.rs now picks a directory per
  NEXTEST_TEST_GLOBAL_SLOT. Plain `cargo test` keeps the old directory.
- Swatinem/rust-cache replaces the hand-rolled actions/cache step. It
  caches dependencies only, not the cc-switch crate that is rebuilt
  every run, and keys on OS, rustc version and Cargo.lock. The WSL2 CI
  job and the nightly share the `backend` key but only restore it, so
  their lib-only builds never save over the full Windows cache.
- Quote the $GITHUB_OUTPUT redirect that shellcheck flagged.
2026-10-05 12:17:50 +08:00
Jason ef633b5789 test: fix the two clippy lints in test code
`cargo clippy --all-targets -- -D warnings` failed on two tests: an
`op_ref` comparison against `&data_url` in transform_codex_chat and a
`type_complexity` row tuple in usage_stats. Compare by value and name
the row type. CI's clippy does not lint test targets, so this only
unblocks the stricter local run.
2026-10-05 12:17:33 +08:00
Jason c62eab2ba1 build(release): compile with 16 codegen units
With codegen-units = 1, LLVM optimizes the large main crate on a single
thread, and that dominates release build time. 16 units make the main
crate compile about 64% faster for a binary about 17% larger, measured
locally. Applies to every release target; thin LTO and opt-level "s"
are unchanged.
2026-10-05 12:17:24 +08:00
Jason f344af0256 ci: install the Rust toolchain pinned in rust-toolchain.toml
dtolnay/rust-toolchain@stable installed the latest stable, then rustup
switched to the 1.95 channel from rust-toolchain.toml on the first cargo
call, so every job downloaded two toolchains. Read the channel from
rust-toolchain.toml and pass it as `toolchain` in the CI backend and
WSL2 jobs, the release build and the WSL2 nightly, keeping the file as
the single source of the compiler version.
2026-10-05 11:13:26 +08:00
Jason f9e2ebbb9f ci(release): fail fast on notarization rejections and missing updater signatures
- macOS: tee each build attempt to a log, and stop on HTTP 401/403 from
  Apple instead of retrying. An expired developer agreement cannot be
  fixed by a retry; for v4.0.0 the loop rebuilt both architectures twice
  more and ran into the job timeout.
- Treat a missing macOS .tar.gz.sig, Windows MSI or MSI signature
  (either arch), AppImage or AppImage signature as an error. They used to
  print a warning, and latest.json then silently left that platform out,
  so its users would get no update.
- assemble-latest-json downloads only the *.sig files it reads and fails
  if any of the six platforms lacks a signature.
2026-10-05 11:13:19 +08:00
Jason e4960bbaf0 test(wsl): run the WSL2 nightly as a lib suite that can pass on 9P
The nightly has not passed in weeks: 37 tests failed on the
\\wsl.localhost share before the run hung. They fall into a few causes
that come from the 9P filesystem, not from product bugs:

- Windows cannot lock SQLite files there, so every on-disk SQLite
  fixture fails with "database is locked". Skip `database::` by name and
  let fixture tests return early via config::sqlite_unsupported_in_temp_dir.
  The account recovery test now uses an in-memory DB that it reuses
  across the simulated restart.
- Integration tests all create the app database, so run --lib only.
  Users keep that database in the Windows home unless they override the
  app config dir.
- The share exposes no file identity, so parent timelines are not cached.
  The five cache tests now check reuse only where parent_cache_supported.
- Remote symlinks are not followed. The catalog escape test asserts None
  when the link resolves, and otherwise that the result is unreadable.

With one test binary left, the timeout drops from 90 to 30 minutes.
2026-10-05 11:13:08 +08:00
Jason d115fddb07 fix(codex): skip Codex state DBs on WSL paths Windows cannot lock
When the Codex config dir points into a WSL distro (\\wsl$ or
\\wsl.localhost), Windows reaches state_*.sqlite through the 9P
redirector, which has no byte-range locks. Every open waits out the 5s
busy timeout and fails with "database is locked", so the history
migration never wrote its completion marker and retried on each start,
and every session scan stalled loading thread titles.

Skip such DBs in the history migration, its restore path and the
session title loader, logging one warning. The migration counts them as
zero rows and completes; titles fall back to the session index.
is_wsl_path moves from claude_mcp into config so both can share it.
2026-10-05 11:12:52 +08:00
Jason 43077f5539 fix(usage): stop parented Codex sync from deadlocking on a poisoned cache
When the replay cache mutex was poisoned, sync_single_codex_file went
through `if let Ok(caches) = replay_caches().lock() { .. } else { .. }`.
Under Rust 2021 the scrutinee temporary lives until the end of the else
branch, so the PoisonError still held the guard while
resolve_parent_signatures locked the same mutex again, and the thread
hung. The WSL2 nightly hit this after an unrelated test panicked while
holding the lock.

Copy the cache lookup out before releasing the lock, and have
clear_codex_replay_caches recover from and clear the poison. A
regression test poisons the lock and times out after 30s on the old code.
2026-10-05 11:12:34 +08:00
Jason 259d807f0d feat(settings): add a help tip explaining the Claude Code onboarding skip
Rewrite the unused skipClaudeOnboardingDescription in all four locales to
say what the toggle actually does (writes or removes hasCompletedOnboarding
in ~/.claude.json) and wire it to the row's help popover.
2026-10-05 09:58:28 +08:00
Jason f6387b862b style(quota): put the reset-credits chevron in the countdown slot
When the reset-credits dropdown sits on its own card row, the chevron now
takes the countdown slot's icon position, lining up under the clock of the
row above; text right edges stay aligned. Merged rows are unchanged.
2026-10-05 09:58:28 +08:00
Jason df08bb15c3 fix(grokbuild): use preset apiKeyUrl for the get-API-key link
The Grok Build form passed the preset's websiteUrl to the API key link,
so sponsor presets opened the plain homepage instead of their referral
signup URL. Prefer the selected preset's apiKeyUrl, matching
useApiKeyLink, without changing the saved websiteUrl.
2026-10-05 09:58:28 +08:00
Jason 636e161658 feat(opencode): mark reasoning models so OpenCode offers thinking variants
OpenCode only generates thinking variants (ctrl+t) for models whose
capabilities.reasoning is true, and falls back to models.dev only when
the provider key matches a models.dev provider. CC Switch writes custom
provider keys, so preset and fetched models never got any variants.

- Fill `reasoning: true` when a fetched model is known to reason (preset
  row or models.dev); never write false, which would shadow models.dev.
- Mark reasoning-capable models in OpenCode presets, cross-checked with
  models.dev; skip non-reasoning, router (`auto`) and mixed entries, and
  LongCat 2.0 whose preset turns thinking off.
- Let OpenCode derive the variants itself instead of hardcoding effort
  tables that drift between OpenCode releases.
- Remove the unused getPresetModelDefaults helper.
2026-10-05 09:58:28 +08:00
Allen Xu a33c156e5f fix(shell): match the sidebar's bottom divider to the one above it (#7874)
The bottom bar's border-t ran edge to edge and sat flush against the last
global item, so a selected Usage row touched the line. Draw it as an inset
divider with 6px above and below, like the divider above the global items,
and drop the extra gap between Apps and Settings on the collapsed rail.
2026-10-05 09:47:15 +08:00
Lu Chong 30318ac7dc fix(proxy): report zero-usage response.incomplete as api_error, not max_tokens (#7868)
A Responses-API upstream can reject a request before processing it and
still terminate the SSE stream with response.incomplete +
incomplete_details.reason=max_output_tokens while reporting zero input
and output usage (observed with the ChatGPT Codex backend when it
refuses a tool schema, e.g. a `pattern` regex with nested quantifiers).
The converter mapped that terminal to stop_reason "max_tokens", so
clients reported an output-token limit that was never hit.

Genuine max-output-token truncation always reports non-zero usage. When
an incomplete terminal reports zero input/output usage and produced no
content, emit an api_error event carrying the rejection details instead
of a fabricated max_tokens stop reason.

Covered emission paths:
- the empty-output fast path and the compatible-gateway single-JSON
  path, both via responses_json_to_anthropic_sse
- the regular terminal handling in the streaming generator

Real truncation (non-zero usage) keeps stop_reason max_tokens, and the
non-stream codex_oauth aggregation propagates the error event as a
proxy error via anthropic_sse_to_message_value.
2026-10-04 23:50:40 +08:00
Allen Xu 55ef871fc7 perf(shell): animate the sidebar edge on the compositor instead of width (#7860)
Lay out the sidebar and content area at their final widths once, then draw the moving edge with a cover panel that slides on a 200 ms transform animation, so the toggle runs at the display's refresh rate instead of WKWebView's ~60fps main-thread rate. Interrupting a toggle continues from the current edge; window-resize auto-collapse and prefers-reduced-motion skip the animation. Drop the width transition and the content-width pinning from #7845. <main> gets an opaque bg-app and isolate so its z-indexed descendants (sticky matrix headers, FullScreenPanel) stay under the cover while collapsing.
2026-10-04 23:02:35 +08:00
Allen Xu cddb3b68ac fix(sessions): read Gemini CLI JSONL chat files (#7866)
Newer Gemini CLI releases record chats as tmp/<hash>/chats/session-*.jsonl and migrate legacy .json files to .jsonl on resume, so the Sessions page showed no Gemini sessions and Gemini usage sync imported nothing from them.

- Replay JSONL like Gemini CLI's loadConversationRecord (in-place upsert by id, $set and $set.messages checkpoints, $rewindTo), with an id index so replay stays linear
- Use the shared parser for the session list, message loader, full-content refs and usage sync
- Hide a legacy .json that sits beside its migrated .jsonl and delete both together, removing the legacy file first
- Skip CLI-injected <session_context>/<hook_context> and slash commands when deriving titles, and mark injected context in the message view

Fixes #7861
2026-10-04 23:02:22 +08:00
Jason a189980f35 docs(release): shrink and center v4.0 release note screenshots
Render the screenshots at 720px (560px for the cropped Codex model
picker) and center them, matching the published release body.
2026-10-04 20:12:48 +08:00
Jason 0d0dd0a548 docs(readme): use English 88API banner in en/ja/de READMEs 2026-10-04 17:38:51 +08:00
Jason 412579f9ad chore(release): v4.0.0 v4.0.0 2026-10-04 17:06:16 +08:00
Jason 8b977f2715 fix(quota): keep balances one quiet color until they run out
Balances under 10% of their total were drawn darker and bolder (and the
tray title added "running low"), so a few cards showed the same kind of
number in different shades with nothing saying why. Balances now stay
gray and only turn red at zero, on the card, in the expanded bar and in
the tray. The left percentage is still computed from the total, so bar
length and which lines get picked are unchanged. Per-tier percentages
keep their under-10% emphasis.
2026-10-04 16:54:30 +08:00
Jason 2153c1ccf0 feat(quota): show reset countdowns again instead of hiding them on hover
The v7 quota redesign moved reset times into the native hover title, which
takes over a second to appear and gives no hint it is there, so in practice
they were gone.

- Provider cards: each quota row now ends with a fixed-width "⏱ 2h30m" slot
  (empty when that row has no reset, so rows stay aligned). A merged row
  shows its earliest reset; the hover text still lists every tier. The
  column only widens (136 → 194px) when some tier has a reset time.
- Expanded quota bars write "Resets in 2h30m" after the value.
- Auth center account rows get the same countdown slot.
- Countdowns are computed at render time from resetsAt instead of being
  baked into the line when built, so they no longer go stale between
  queries.
- Copilot no longer overwrites the hover detail, which dropped its reset.
2026-10-04 16:48:27 +08:00
Jason 6d6bbe6747 docs(release): add v4.0 release notes in Chinese, English and Japanese
Add the v4.0 release notes and the screenshots they reference
(main page, Apps page, Aggregation, Codex model picker, session reader
and usage, in Chinese and English) under assets/release-notes/v4.0.0.
2026-10-04 16:25:20 +08:00
Jason bfd28b2f9f feat(presets): add Tu-zi (兔子API) presets across nine apps
Tu-zi (api.tu-zi.com) is a new-api based aggregator. It is not a
sponsor, so the presets sit in the non-sponsor block of each file.

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi. Endpoints follow the vendor
  wiki (wiki.tu-zi.com): Anthropic- and Gemini-style clients use the bare
  host; Codex, Grok Build and the OpenCode Anthropic SDK use /v1.
- Claude Code uses ANTHROPIC_API_KEY, as the vendor's own CC Switch
  guide tells users to switch from ANTHROPIC_AUTH_TOKEN.
- Claude Desktop uses the bare host although the vendor's Desktop guide
  shows /v1: Desktop appends /v1/messages itself, and /v1/v1/messages
  returns 404 on this gateway while /v1/messages reaches auth.
- Multi-model apps list Claude Opus 5, Sonnet 5 and Haiku 4.5. The
  public pricing catalog marks Opus/Sonnet 5.5 with a "claude code"
  endpoint type rather than "anthropic", so they are left out of the
  SDK-based clients. Codex defaults to gpt-5.6-sol, Gemini to
  gemini-3.8-flash, Grok Build to its default grok-4.5.
- Endpoint candidates add the vendor's Guangzhou (api.ourzhishi.top),
  Los Angeles (api.sydney-ai.com) and Cloudflare (apicdn.tu-zi.com)
  relays, which front the same backend, in the five apps whose presets
  support candidates.
- Display name is localized through providerForm.presets.tuzi:
  "兔子API" in zh / zh-TW, "Tu-zi" in en / ja. Search aliases add
  兔子 / 兔子api / tuzi.
- 128x128 raster icon from the API site's logo.png, plus metadata.
- Claude preset rows in the selector test go from 72 to 73.
2026-10-04 14:57:57 +08:00
Jason 7d5ed45e5e feat(presets): add 88API sponsor presets across nine apps
88API (88api.ai), a multi-model API platform run by a Hong Kong company,
joins as a sponsor, placed directly below FluxA in the sponsor order
(preset files and the four README sponsor tables stay in sync; Gemini
and Grok Build have no FluxA preset, so it follows SubRouter there).

- Presets for Claude Code, Claude Desktop, Codex, Gemini CLI, Grok
  Build, OpenCode, OpenClaw, Hermes and Pi on api.88api.ai, with
  88api.ai as a second endpoint candidate (same new-api deployment, and
  the host the vendor's own setup scripts default to). Anthropic- and
  Gemini-style clients use the bare host; Codex, Grok Build and the
  OpenCode Anthropic SDK use /v1.
- Model lineup is Claude Opus 5.5, Sonnet 5.5, Haiku 4.5 and Fable 5.1,
  defaulting to Opus 5.5. Claude Desktop maps the Sonnet/Opus/Haiku role
  routes to those models via mappedRoutes. Codex defaults to
  gpt-5.6-sol (vendor config script), Gemini to gemini-3.8-flash.
- Adds Claude Sonnet 5.5 to the Pi model catalog (1M context, 128K
  output, image input, reasoning; per models.dev) so the Pi preset can
  reference it.
- No OpenClaw cost fields: prices vary by the vendor's channel groups.
- Referral link only in apiKeyUrl and README hrefs.
- 128x128 raster icon (square-padded from the vendor logo), metadata
  entry, four-locale partnerPromotion copy and a 1200px banner.
- Claude preset rows in the selector test go from 71 to 72.
2026-10-04 14:41:48 +08:00
Jason ac2a0bbc7e feat(quota): list when each saved ChatGPT limit reset expires
With more than one saved reset, the reset row opens a dropdown listing
every expiry date (same-day ones grouped, the ones expiring within three
days emphasized):

- Auth Center: the whole reset row is the trigger.
- Provider card: only the "Resets N" segment of the merged second row is
  the trigger; the rest of the column still refreshes on click, and the
  refresh icon only shows while hovering the refresh area.
2026-10-04 12:58:31 +08:00
Jason a7d3825f57 feat(quota): show refresh feedback when clicking the card quota
Clicking the quota on a provider card already re-queried it, but the
only feedback was a brief dim, so it looked like nothing happened.

- Hovering or focusing the column reveals a small refresh icon left of
  the first row (the column is right-aligned, so text never shifts).
- A click spins it until the query settles, for at least 600 ms, then
  shows a check on success or a red cross on failure for one second.
  Background polling and focus refetches still only dim, so several
  cards never spin at once.
- Failure means the column turned into "quota unavailable" or the
  refetch itself errored. The second case covers transient failures
  inside the keep-last-good window, where the old quota stays on screen
  and a check would be a lie; UsageFooter now returns the refetch
  promise instead of discarding it. A retry that fails again on an
  already failed column also gets the cross.
- After a click the icon stays hidden until the pointer leaves or focus
  moves, so it does not pop back right after the check fades. The
  button uses aria-disabled instead of disabled while busy, since
  Chromium may not deliver mouseleave to a disabled button.
2026-10-04 12:58:31 +08:00
Jason c0e093a26c feat(subscription): show saved ChatGPT limit resets
Codex now lets Go/Plus/Pro/Business subscribers bank rate-limit resets
and redeem them later. Query wham/rate-limit-reset-credits alongside
wham/usage (in parallel, best-effort: any failure just leaves the count
out and never fails the quota itself) and count the resets that are
still available and unexpired, the same way CodexBar does, instead of
trusting available_count.

The count shows wherever ChatGPT quota does, since both the Codex CLI
and the managed-account paths go through query_codex_quota:

- Provider card: joins the weekly tier on the second row
  ("Wk 64% · Resets 1"), reusing the existing merged-row rule.
- Expanded quota and Auth Center: its own row, with the earliest
  expiry written where the bar would be.
- Hidden at zero; emphasized when the earliest reset expires within
  three days. Resets that expire after the query are dropped at render.

SubscriptionQuota gains an optional resetCredits field (serde default),
so cached snapshots from older builds still deserialize.
2026-10-04 12:58:31 +08:00
Owlbay ebd9357ceb feat(usage): paginate the request log, provider, model and pricing tables
Extract the request-log pager into a shared TablePagination and add useClientPagination for tables that load all rows at once. Pages hold 20 rows, reset when the range or filters change, and clamp when a refresh returns fewer rows.

Reapplies c932436dc, reverted in 6d077f2a1.
2026-10-04 12:58:31 +08:00
Jason c42feedd30 fix(gemini): drop the redundant custom preset
The Gemini preset list ended with a "自定义" entry from the original
Gemini integration. The v7 picker already renders its own built-in
custom row, and presetGroup files category "custom" under third-party,
so the add dialog showed two custom options, the extra one without an
icon.

The built-in row starts from GEMINI_DEFAULT_CONFIG, which holds the same
empty base URL and gemini-3.6-flash model, so nothing is lost. The entry
was last in the array, so the index-based `gemini-${index}` ids of the
other presets do not shift.
2026-10-04 12:58:31 +08:00
Jason 9c7878e67e feat(presets): add official icons for DMXAPI, BaiLing, TheRouter, Together AI and Astron
These presets either had no icon or, for Together AI in Hermes,
referenced a `together` key that did not exist, so the picker fell back
to the first letter without any error.

- TheRouter: the official logo-black.svg inlined with currentColor
  strokes, so it follows the theme like other monochrome icons.
- Together AI: the LobeHub together-color SVG, inlined.
- DMXAPI, BaiLing: the 256px frame from each official favicon.ico,
  added as URL imports since both are gradients.
- Astron (iFlytek MaaS): the official favicon; only 32px is published.

Each icon gets a metadata entry, and every preset of these providers
across all apps now sets `icon`. All 738 presets now resolve an icon.
2026-10-04 12:58:31 +08:00
Jason 515526b247 fix(providers): mark API Key as required where saving checks it
The v7 pass gave the name and endpoint fields the required asterisk, but
ApiKeySection never forwarded `required` to ApiKeyInput, so the API Key
field was never marked even though saving warns when it is empty.

ApiKeySection now takes an opt-in `required` and drops it for official,
cloud_provider and disabled fields. Only the forms whose save path checks
the key pass it: Claude Code, Codex (also used by Grok Build), Gemini and
Claude Desktop. Additive apps don't validate the key and stay unmarked.

ApiKeyInput now sets only aria-required, like EndpointField: a native
`required` would let browser validation block the submit before the
"save anyway?" confirmation for an empty key could appear.
2026-10-04 12:58:31 +08:00
Jason 4baa08400f docs(presets): update stale ordering and primePartner comments for v7
v7 sorts presets by display name with no pinning, and no longer reads
primePartner. The sponsor-block comments still claimed file order equals
in-app display order, which would mislead anyone adding a preset.
2026-10-04 12:58:31 +08:00
Jason 7f86dbe299 fix(providers): show the preset's promotion under the API Key field again
The v7 visual pass dropped the partner promotion box together with the
promoted ordering. Ordering stays by name, but the promotion text comes
back as the API Key field's hint (caption, secondary text, no box), shown
only when the "Get API Key" link is shown, as before.

ApiKeyInput gains a generic `hint` prop for text below the input.
2026-10-04 12:58:31 +08:00
Jason 3e60be14b9 refactor(stack): describe aggregated models by upstream id and window
The model picker's description column repeated the provider name that
the display name already carries, in two languages, on every row:
"经 CC Switch 路由到 X (Routed by CC Switch to X)".

Replace it with the upstream model id and its context window, e.g.
"kimi-k3 · 256K". The display name can be user-chosen and the published
id carries the reserved prefix, so this is the only place the real
upstream id shows up. Claude 1M models show 1M rather than the row's
global window; an unknown window leaves just the model id.

The reserved "ccs-" id prefix is unchanged.
2026-10-04 12:58:31 +08:00
Allen Xu 372b169896 perf(shell): keep sidebar toggle from re-rendering and reflowing the page (#7845)
Move the collapsed state and the Cmd/Ctrl+\ shortcut from App into Sidebar so a toggle only re-renders the sidebar. During the 200 ms width transition, pin the content area's children to the collapsed-sidebar width so ResizeObserver-driven content (usage chart, virtual lists, tab indicators) lays out once at the end instead of every frame; skipped under prefers-reduced-motion. useSlidingIndicator no longer sets state when the measured rect is unchanged.
2026-10-04 10:33:48 +08:00
Owlbay b8b3c6722a fix(sessions): follow-ups from the session reader review (#7836)
* perf(sessions): defer the find highlight term like the hit search

The highlight term followed every keystroke, re-parsing the Markdown of
the whole page in long sessions. It now uses the same deferred value as
the hit computation.

* fix(sessions): include legacy OpenCode part files in the cache fingerprint

Legacy storage keeps message bodies under storage/part/{messageID}/ and
streaming only rewrites those files, so the fingerprint of the message
directory alone served stale transcripts.

* fix(sessions): show rejected Codex apply_patch calls as failed

apply_patch outputs like "apply_patch verification failed: ..." or
"patch rejected by user" carry no exit code and were shown as
successes. They are now errors and report no added/removed counts,
since nothing was written. Patch counting also stops at *** End Patch
and no longer counts '-' lines inside added files.
2026-10-04 10:14:12 +08:00
Jason ba8c1cd611 test(sessions): JSON-escape the sidecar path spliced into Claude transcript lines
The test replaced a marker in already-serialized JSON with the raw sidecar
path; on Windows its backslashes became invalid escapes, the line failed to
parse and the transcript came back one message short.
2026-10-04 00:05:20 +08:00
Jason 3abbdf28a1 fix(sessions): don't evict loaded images whose waiters haven't taken them
With every cached image in use, a newly loaded entry (refs 0) was the only
evictable one and got revoked before its waiting hook acquired it, leaving
the image stuck on the loading placeholder; two loads finishing together
could also evict each other. Count pending waiters per image and skip
images that still have one when evicting.
2026-10-04 00:05:19 +08:00
Jason f5a4bdc977 fix(proxy): keep holding late-argument calls until a non-delta event arrives
When several function calls emit empty end events before their delayed
argument deltas, the first delta used to flush every held event, sending
the other calls on without arguments. Argument deltas from any call (held
or not) are now forwarded without releasing anything; held end events are
filled in and released together on the next non-delta event.
2026-10-04 00:05:19 +08:00
Jason d7c4da570c test(presets): expect 71 Claude preset rows after Command Code joined 2026-10-03 23:32:02 +08:00
Jason be746ea804 fix(claude-desktop): stop the routing service when leaving a mapping card
Switching Claude Desktop from a model-mapping card back to a direct card
left the local routing service running until some later check. The main
switch, tray click and project apply now record whether the outgoing card
was a mapping card and, if it was and nothing else uses the service, stop
it. Switches that never involved a mapping card leave the service alone,
so a service the user started by hand in settings keeps running.
2026-10-03 23:29:01 +08:00
Jason 6fd176ba12 fix(usage): deepen the top heatmap level in light mode
The heatmap mixed --chart-1 (#0891b2) at 30/50/75/100%, so the darkest
cell was a mid-tone and the whole map read pale (3.68:1 against the card).
Mixing a darker cyan at every level fixed the top but washed out the low
levels.

Move the ramp into --heat-1..4 tokens defined per theme. Light keeps the
lower levels on chart-1 (32%, 62%, solid) and switches only the top level
to #0e7490, same hue one step darker (5.36:1). Dark keeps its previous
ramp, since brighter already means more there. Charts and quota meters
still use --chart-1.
2026-10-03 23:29:01 +08:00
Jason a43d3b2f9d fix(theme): align the light accent with Tailwind orange-500
The light --action-bg was #f97315, one unit off the #f97316 the old
add-provider button used via bg-orange-500. Use #f97316 so the accent
matches the pre-redesign orange exactly, recompute the light --ring HSL
from it, and update the design-system doc to the same value.
2026-10-03 23:29:01 +08:00
Jason 63ea818496 fix(usage): default the trend chart to tokens and list it first
Match the heatmap on the same page, which already puts Tokens first
and selects it by default.
2026-10-03 23:29:01 +08:00
funkpopo 4b6e3297f6 fix(sidebar): keep sidebar tooltips from covering adjacent buttons (#7839)
Open the bottom-bar tooltips upward and the expanded collapse-button tooltip to the left, so the Apps tooltip no longer covers the Settings button. Add an optional disableHoverableContent prop to HoverTip (closes on trigger leave and ignores pointer events) and enable it for all sidebar tooltips.
2026-10-03 23:29:01 +08:00
funkpopo 044ff96a08 fix(ui): truncate long provider names in mode status bar (#7837)
Keep the provider name from overlapping the failover switch and route settings button. The lead text still truncates first; the value only truncates when it alone exceeds the row, and a title shows the full text on hover.
2026-10-03 23:29:01 +08:00
Jason 9f6a758ce5 fix(shell): drop the sidebar brand divider on macOS
The divider under the brand row sat about 11px below the page header's
bottom border. Two near-parallel lines that miss each other read as a
misalignment, and the divider only existed in the expanded macOS layout.
Separate the brand from the navigation with spacing instead.
2026-10-03 23:29:01 +08:00
Jason ef071c1148 docs(usage): correct the Grok access token lifetime in the module doc
The access token lasts about six hours, not seven days, and the Grok CLI
renews it from the stored refresh_token on its next run. The module doc
now matches the refresh-pending handling from add36d6df.
2026-10-03 23:29:01 +08:00