Commit Graph
2627 Commits
Author SHA1 Message Date
Jason c19c117009 feat(ui): set the aggregation default from the card's more menu
After the "start aggregation with this as default" button went away, the
aggregation preview showed the default-after-switch but had no control to
change it; the default could only be picked in the confirm dialog. Once
in aggregation mode it was an icon-only star next to remove.

Move "set as default" to the top of each card's more menu on the
aggregation tab, in both states, and leave add / remove as the only row
actions:

- In aggregation mode it takes effect at once, also for providers not
  added yet (the backend adds the new default to the list).
- In direct mode it only records the choice through set_proxy_route; the
  card moves to the default section and the confirm dialog preselects it.
- While routing it is listed but disabled with the reason: the default
  and the route target are one pointer, so changing it would switch the
  live route from a preview.

On Codex, when the default is an official account and the target is a
third-party provider, the item says the official subscription's models
won't be part of the aggregation. That was previously a silent side
effect of changing the default.

Mode-specific menu items come from the presentation builder
(CardPresentation.menuItems), like the row buttons.
2026-10-03 23:29:00 +08:00
Jason 3d23ddb3b5 refactor(ui): drop the direct and last-route chips from the route tab
The "used when direct" chip marked the provider the client falls back to
after leaving routing. It only helps on the direct tab, where "back to
direct" acts at once and doesn't name the provider. On the route tab
nothing acts on that provider, and with failover on the chip only showed
up when the provider happened to be outside the queue.

The "last routed" chip in the route preview said which provider "start
routing" would use, but the confirm dialog already preselects and shows
it.

Keep the direct chip on the direct tab only and remove the other one
together with its string.
2026-10-03 23:29:00 +08:00
Jason a374919868 feat(mode): set the proxy route without entering proxy mode
The route pointer (the aggregation default) could only change while the
app was already in routing or aggregation mode: switch_route_locked
rejects everything else. From direct mode the only way to choose it was
the confirm dialog shown when entering the mode.

Add set_route and the set_proxy_route command. In direct mode it only
records the pointer, to be used the next time the app enters routing or
aggregation; the mode, the direct pointer, the member list and the client
files stay untouched. Once in proxy mode it is a plain route switch.
Unknown providers and official subscriptions that can't be proxied are
rejected either way.
2026-10-03 23:29:00 +08:00
Jason 29513d9a54 refactor(ui): enter routing and aggregation only from the notice
While previewing the route or aggregation tab, every card carried a
"start routing here" / "start aggregation with this as default" button.
Both were shortcuts for the notice's button plus a provider choice, and
the aggregation one sat next to add / remove, which only edit the list.
On Codex it also had a hidden cost: an official account can only be the
default, never a member, so starting from any third-party card silently
dropped the official subscription out of the aggregation.

Remove both card buttons. The notice is the single entry, and the
confirm dialog now has the provider select for aggregation as well as
routing, preselecting the last route and falling back to the direct
provider. When an official account is eligible and another provider is
picked as the aggregation default, the dialog says its models won't be
part of the aggregation. The "also adds" note follows the picked default
instead of the last route.

Preview rows keep only list edits: none on the route tab, add / remove
on the aggregation tab. An official subscription that can't be routed
has no button left to hang the reason on, so it moves to the chip's
tooltip.
2026-10-03 23:29:00 +08:00
Jason 91a783c52a refactor(i18n): rename the Stack mode to Aggregation / 集約 in en and ja
Same reasoning as the Chinese rename: from the client's side the mode
behaves like an aggregator provider, so each language borrows the word
its own UI already uses for that provider category.

English uses "Aggregation", matching the "Aggregation" provider category
and the noun form of "Routing": Aggregation mode, aggregated models,
join the aggregation. Japanese uses 集約 (集約モード, 集約したモデル),
replacing the untranslated "Stack" that sat between 直接接続 and
ルーティング.

Covers the locale files, the tray's en/ja labels and their test
assertions, and the English halves of six backend errors. Log messages,
code identifiers and storage keys are unchanged.
2026-10-03 23:29:00 +08:00
Jason 21cbc3a8b4 refactor(i18n): rename the Stack mode to 聚合 in Chinese
From the client's side the Stack mode does what an aggregator provider
does: one endpoint, models from several providers, each request routed
by the model it names. Calling it 聚合 lets users reuse a concept they
already know instead of learning what 叠加 stacks onto.

Labels and tabs read 聚合, sentences 聚合模式 / 聚合的模型, and verb uses
become 加入聚合. zh-TW follows; en/ja keep "Stack". Code identifiers,
storage keys and logs are unchanged. Unrelated uses of 叠加 (the
Volcengine discount copy, two code comments, a CSS comment) are kept.
2026-10-03 23:29:00 +08:00
Jason 8a9fdfad75 fix(sessions): summarize Hermes JSONL sessions by their last message too
The last-message summary only covered the state.db scan; transcripts merged
from ~/.hermes/sessions/*.jsonl still showed the first user message. Pick the
last non-empty user/assistant message from the tail and keep the first user
message as the title fallback only, matching the SQLite rows.
2026-10-03 23:29:00 +08:00
Jason b23c9d5b35 fix(sessions): expand a tool output's tail when the search also hits past the preview
The auto-expand only asked whether the first preview lines contained the
query, so an output matching both inside and after the preview kept its tail
folded while the reader counted both hits and the second one was unreachable.
Expand the tail whenever it has a hit of its own.
2026-10-03 23:29:00 +08:00
Jason 55fa8e852e fix(proxy): roll back the saved listen address when the restart fails to bind
When the new address or port could not be bound, update_config had already
stopped the old server and written the new config, so the service stayed down
and the next save returned Ok(false) because nothing was running: the UI
reported success while clients still pointed at the old port. On a failed
restart write the previous config back (global four fields or the legacy full
row) and start the server on it again, so a failed save changes nothing.
2026-10-03 23:29:00 +08:00
Jason edc8c2b966 fix(proxy): save global routing settings without clobbering per-app retries and timeouts
update_global_proxy_config was routed through ProxyService::update_config, whose
legacy DAO reads the claude row and writes all seven fields into every app row,
so one click on "save and restart" copied Claude's retries and timeouts onto
Codex, Gemini and Grok Build. Add ProxyService::update_global_config that only
writes the four global fields through the global DAO, and share the
restart-or-apply step with the legacy path.
2026-10-03 23:29:00 +08:00
Jason 92e6f3b975 fix(settings): move the data-dir baseline after a successful save
The settings page no longer unmounts after saving, but the saved
app-config-dir baseline was only captured on mount. After saving a new
data directory and choosing "restart later" the save button stayed,
resetting to the old value could not be saved, and every later save
reported "restart required" again. Keep the baseline in state and commit
it to the just-saved value once the override is persisted.
2026-10-03 23:29:00 +08:00
Jason 485a3b69de fix(apps): surface installation conflicts found by diagnose
"Diagnose installation conflicts" only stored its result in component
state: no toast when conflicts were found, rows stayed collapsed, and the
"N other installs" entry came from the page-load snapshot that nothing
refreshed, so a conflict that appeared after opening the page had no
visible effect. Write the probe reports back into installReports (both
the manual diagnose and the post-upgrade one), auto-expand rows with
diagnosed conflicts and toast the count.
2026-10-03 23:29:00 +08:00
Jason e7b6f2b927 fix(ui): stop form drawers from closing on an overlay click
SheetContent let Radix dismiss on any outside pointer-down, so a stray
click on the dimmed area threw away the MCP / prompt / Pi resource /
route-settings draft without asking. Match DialogContent: ignore outside
clicks by default, and let read-only drawers (request detail, data
sources) opt back in with dismissOnOutsideClick.
2026-10-03 23:29:00 +08:00
Jason fd4463001e fix(prompts): drop the always-failing undo on Pi imports
The imported entry matches AGENTS.md byte for byte, so Pi treats it as
the enabled prompt and delete_pi_prompt refuses to remove it; the undo
on the import toast could only ever report "cannot delete the enabled
prompt". Show the import toast without an undo.
2026-10-03 23:29:00 +08:00
Jason 3795a3fe34 fix(mcp): keep hidden apps' flags when a batch paste overwrites a server
The drawer's apps state covers every MCP app with hidden ones set to
false, and buildEntry spread it over the existing server's apps, so
overwriting from a batch paste turned the server off in apps hidden from
the sidebar and removed it from their config. Only let the visible
checkboxes override the stored flags.
2026-10-03 23:29:00 +08:00
Jason 3239b36672 fix(tray): rebuild the menu when the stack roster or failover queue changes
The v7 tray lists only stack members and queue members in order, but
set_proxy_stack_member, add/remove_from_failover_queue and
update_providers_sort_order never rebuilt it, so the tray kept showing a
provider that had just been removed from the stack, and clicking it
re-added it as the default. Refresh the tray from those commands, the
same as set_auto_failover_enabled already does.
2026-10-03 23:28:59 +08:00
Jason 0dd97698ce fix(proxy): make "save and restart service" restart the service
update_global_proxy_config only ran the UPDATE on proxy_config, so with
the service running a new listen address / port took effect at the next
app launch while attached clients kept pointing at the old port, and the
logging switch didn't apply live. Route the listen fields and the logging
flag through ProxyService::update_config (restart on change, runtime
apply otherwise), resync attached routes and rewrite Claude Desktop's
model-mapping gateway profile after a restart. Drop the duplicate toast in
RoutingSection; the mutation already reports success and failure.
2026-10-03 23:28:59 +08:00
Jason b600ce2eff fix(sessions): show Hermes's last message in the list, not the first
Every other provider fills the session summary with the last message and
the list labels it "Last:", but the Hermes scanner put the first user
message there. Query the last displayable user / assistant row per
session for the summary and keep the first user message only as the
title fallback.
2026-10-03 23:28:59 +08:00
Jason 4ca7f47ab0 fix(sessions): expand tool output that contains a find hit
Find counted matches inside collapsed tool output and highlighted them,
but the group's open state ignored the query, so the active hit was
invisible. Treat a hit in the output like a hit in a long message: open
the output, and show all lines when the hit is past the preview.
2026-10-03 23:28:59 +08:00
Jason 7422c24bcd fix(usage): keep pricing reachable when there is no usage yet
The empty state replaced the whole dashboard, including the tabs, so a
fresh install had no way into model pricing / models.dev sync until some
usage existed. Add a "configure pricing" entry to the empty state that
opens the tabs with pricing selected, without drawing the all-zero
overview.
2026-10-03 23:28:59 +08:00
Jason 0523ec9de7 fix(providers): explain why Codex official cards can't join the queue
The failover view offered "add to queue" on Codex official account cards;
the backend rejects them (they route with the client's own login) and the
add mutation had no onError, so nothing happened. Mirror
provider_supports_failover in the UI, dim the card with the reason, and
toast failures from the queue add / remove mutations.
2026-10-03 23:28:59 +08:00
Jason fe9593a722 fix(providers): compute failover priorities from the full queue
The failover view derived P-numbers, up/down bounds and the "routing"
badge from the index in the provider list it was handed, which is the
search-filtered list. Filtering out P1 relabelled the rest and let the
last visible card move down. Number by the full queue, drop stale ids
against the unfiltered providers in ProviderList, and mark the provider
the backend recorded as the route (falling back to the queue head) rather
than whichever card happens to be first.
2026-10-03 23:28:59 +08:00
Jason e447b3d0b6 fix(mcp): don't save a masked secret as empty after renaming its key
The JSON tab shows env / header values as a placeholder and swaps the
original value back in by key when parsing. Renaming a key (or adding
one) while keeping the placeholder found no original and silently stored
"". Report it as a parse error naming the key instead.
2026-10-03 23:28:59 +08:00
Jason d6666977a3 fix(skills): make the skills.sh retry button actually refetch
After a failed first page the retry handler only reset the page offset,
which was already 0, so the query key never changed and nothing was
requested. Refetch explicitly on the first page; a failed load-more still
restarts from page one.
2026-10-03 23:28:59 +08:00
Jason 05d70dc32f fix(ui): lift dropdown menus above full-screen panels
DropdownMenuContent sat at z-50 while FullScreenPanel is z-60, so the
account menu in the auth center opened from a provider form rendered
behind the panel. Put dropdowns on the same layer as Popover and Select
(z-100).
2026-10-03 23:28:59 +08:00
Jason 4fd53be9e6 fix(providers): disable delete on the provider in use again
The v7 card menu lost the guard the old ProviderActions had: the current
provider's delete item was enabled, the backend rejected it and the
confirm dialog stayed open with an unhandled rejection. Mark the direct
provider (and the routed one while routing / stacking) with a delete
reason in every switch-mode view and on Claude Desktop, and close the
confirm dialog even when the delete fails.
2026-10-03 23:28:59 +08:00
Jason 081fdd4d7a fix(skills): never touch OpenClaw's skills dir during sync
OpenClaw doesn't support Skills, so SkillApps::is_enabled_for always
returns false for it, yet sync_to_app still walked ~/.openclaw/skills and
deleted any real directory whose name matched a managed skill. The
provider-switch sync hit this on every switch; the new "resync now"
button made it a one-click action. Skip OpenClaw alongside Claude Desktop
and Pi in both sync_to_app and resync_all_apps.
2026-10-03 23:28:59 +08:00
Jason 20c4ff86c6 fix(ui): close provider panels when navigating away from the app page
The add / edit / usage-script panels now only cover the content area, so
the sidebar stays clickable while one is open. Their appId followed
activeApp, so switching apps with the edit panel open saved the Claude
provider into Codex. Close the three panels from selectApp / openPage /
openSettings.
2026-10-03 23:28:59 +08:00
Jason a39af812e1 fix(usage): show load errors in the request detail panel
The panel only checked whether data was present, so a failed query fell
through to "Request not found". That is how the ambiguous-column error
in get_request_detail looked like a missing feature.

Show a danger notice with the error message and a retry button when the
query fails, and keep "Request not found" for a lookup that returned
nothing.
2026-10-03 23:28:59 +08:00
Jason 5be4451dcd fix(usage): qualify columns in the request detail query
get_request_detail joins proxy_request_logs with providers but left
created_at and a dozen other columns without a table alias. Both tables
have created_at, so SQLite rejected the statement with "ambiguous column
name" and every lookup failed, whatever the row.

The bug dates back to the session-log import work but stayed hidden:
RequestDetailPanel existed without being mounted anywhere until the v7
redesign wired row clicks to it. Released versions never ran this query.

Add a regression test covering a routed row, a session-log row and a
missing id; the function had no test before.
2026-10-03 23:28:59 +08:00
Owlbay ba914a8360 Revert "feat(sessions): start project groups collapsed and remember expanded ones"
This reverts commit 302ce1143b.
2026-10-03 23:28:59 +08:00
Owlbay 4370d14dc8 feat(sessions): start project groups collapsed and remember expanded ones
Grouping by project now opens with every project collapsed and persists
the projects the user expands (new expandedProjects key; the old
collapsedProjects list had the opposite meaning and is ignored). All
groups open while a search is active so matches are never hidden.
2026-10-03 23:28:59 +08:00
Owlbay cf2e6a717d feat(usage): redesign the dashboard with an all-time heatmap and day tiles
- Add an "All" range. Ranges up to 30 days keep the bar chart; "All"
  shows a 53-week daily heatmap. Both default to Tokens and show tokens,
  requests and cost (USD) on hover.
- Rebuild the metric cards: large Token and cost cards plus a six-cell
  strip, replacing the "more metrics" toggle.
- Show today / this week / this month / 7-day average / streak tiles
  under the chart for every range.
- Default the dashboard to the last 24 hours.
- Drop the 5s and 10s auto-refresh options (write events already
  invalidate queries; saved values fall back to 30s), keep the previous
  data while a new range loads, and treat results as fresh for 15s.
2026-10-03 23:28:59 +08:00
Owlbay c932436dc6 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.
2026-10-03 23:28:59 +08:00
Owlbay b8c600a28c perf(usage): keep dashboard queries off the main thread and cheapen dedup
- Run the usage read commands (summary, trends, provider/model stats,
  request logs and detail) through spawn_blocking; as sync commands they
  ran on the Tauri main thread and froze the window during wide scans.
- Pick the session/proxy dedup strategy per time window: skip it when
  either side is empty, and when proxy rows are the minority find the
  duplicate session rows once (rowid NOT IN) instead of a correlated
  EXISTS per row. Same results as before; a full scan on a session-heavy
  database drops from ~0.19s to ~0.05s.
- Cache the request-log total per filter until the connection sees any
  write (total_changes), the live end time picks up new rows, or 60s pass.
2026-10-03 23:28:59 +08:00
Jason c212cdea7c style(ui): format checkbox className call
Prettier wants the two-argument cn() call on one line; format:check
failed on this file after #7816.
2026-10-03 23:28:59 +08:00
千羽and千羽 57b1eff1d4 feat(ui): v8 — theme color, unified controls, motion, virtual lists (#7816)
Unify the visual and interaction rules on top of the v7 redesign.

- Theme color #F97315 for --action-* and --ring; dark mode uses #EA580C
  with white text, toned-down mode colors and translucent soft fills
- Buttons mapped to three sizes (28 / 32 / 36); inputs, selects and
  search fields share fieldClass; self-drawn checkbox and radio
- HoverTip replaces title on about 100 icon buttons; it stays closed
  when a closing menu returns focus to its trigger
- Install tailwindcss-animate so menu, popover, dialog and tooltip
  enter/exit animations take effect; sliding segment thumb and tab
  underline
- Remove the command palette and the header prompt entry; provider card
  primary actions become icon buttons
- Virtualize the session list and the Skills discovery list; optimistic
  toggles for MCP and Skills with a 300ms delayed disabled state
- Toasts drawn unstyled with status-colored icons and de-duplicated
- Windows: hide the system title bar and use in-app window controls;
  macOS: brand row under the traffic lights
- Focus rings only for keyboard users
- Skills discovery and the prompt list show the last result from a
  local cache and refresh in the background
- Drop the trailing ellipsis from 36 dialog-opening labels
- Provider list fills the main area

Co-authored-by: 千羽 <qyinter@users.noreply.github.com>
2026-10-03 23:28:59 +08:00
Jason 37c8da1b87 feat(usage): show estimated speed for requests imported from session logs
Session-log requests now show a speed marked with "≈" in the request
log, the request detail and the provider table, computed from the
duration the importers estimate from log timestamps. The estimate
includes the wait for the first token, so it is kept apart from the
exact routed speed: a provider row falls back to it only when it has no
routed data, and the help text says the figure reads low.

Requests under 200 output tokens get no estimate, because the first-token
wait dominates there: measured against long replies, 200-300 token
requests read about a quarter low and 100-200 about 40% low. A higher
bar left most Codex rows empty, since only a quarter of Codex requests
exceed 500 output tokens.

The detail panel shows "Duration (estimated)" for these rows and says
why a short reply has no estimate. Help text is updated in all four
locales.
2026-10-03 23:28:59 +08:00
Jason 7e9b99bcbf feat(usage): estimate request duration from Claude Code and Codex session logs
Requests imported from session logs had no timing, so speed was only
available through the routing service. The importers now derive a
duration from log timestamps and store it in latency_ms (first_token_ms
stays NULL, 0 still means "no timing"). The duration includes the wait
for the first token, so the resulting speed reads lower than the routed
figure; provider stats sum it separately (est_speed_*) and never mix it
into the exact speed. Only rows with at least 200 output tokens and a
duration of at least one second count.

Claude Code: a reply ends at its latest block timestamp. Its start is
found by walking parentUuid up from the first block (apiBlockIndex 0),
skipping attachment rows, to the user message or tool result. The
previous line cannot be used: results of parallel tool calls are written
between blocks of the same reply. The start usually sits before the byte
cursor, so each incremental read looks back 256 KiB for chain and block
info without importing it. A reply seen in two syncs gets its duration
extended when the later blocks arrive.

Codex: rollout layouts differ by version. A token_usage_record marks the
end of the response when present; otherwise token_count does, unless it
was written only after the tool outputs, in which case the last output
item is used. The start is the latest boundary before the first output
item. Output items that old versions wrote after their own token_count
are ignored, and task_started / turn_aborted clear the state so an
interrupted request does not lend its start to the next one.

Rows imported earlier keep latency 0; nothing is backfilled.
2026-10-03 23:28:59 +08:00
Jason b0b6559a9b fix(usage): complete Claude session-log rows when a reply's last block arrives
Claude Code writes subagent transcripts block by block: every block but
the last carries the output-token count as it stood at that moment
(often single digits) and no stop_reason. When a sync landed between two
blocks, the reply was imported with that interim count, and the final
block was later dropped by the request_id dedup, so its output tokens
and output cost stayed under-counted for good. On one machine 293 of
24,928 subagent replies were affected, about 1.6M output tokens.

A reply that is already stored is now refreshed when a later block
reports more output tokens: tokens and costs are rewritten from the
newer block. If the completed usage matches a row the routing service
logged for the same request, the earlier session-log row was a duplicate
and is removed instead. Only session-log detail rows are touched, and a
refreshed row counts as imported so the UI reloads.

Rows that were already imported short are not backfilled; the cursor has
moved past them.
2026-10-03 23:28:59 +08:00
Jason d76a1cda33 feat(usage): enable models.dev pricing sync by default
New installs now start with automatic models.dev pricing sync on, and a
pricing file that omits the flag reads as enabled. Existing installs are
left alone: model-pricing.json has carried an explicit autoSyncEnabled
since v3.20.0, so a stored false stays false.
2026-10-03 23:28:59 +08:00
Jason 600b00cd86 fix(usage): give numeric columns the spare width in the request log
Provider and model took 54% of the table, which left the five numeric
columns at exactly their content width however wide the window was. Cut
the pair to 40% (18/22, model names run longer) and pin time and app to
their content width so the remainder goes to the numeric columns. At the
default 1000px window there is no spare width and nothing changes.
2026-10-03 23:28:58 +08:00
Jason 7e1d447d40 feat(usage): show app filter chips as icons only
Drop the app names from the usage filter row and show all seven apps as
icon chips; the name moves to the tooltip and aria-label. With icons the
row always fits, so the width-based "More" overflow menu and its
per-app request counts go away, along with the summary-by-app query that
only fed them. Remove the two unused locale keys.
2026-10-03 23:28:58 +08:00
Jason f625f5663a feat(usage): default the usage dashboard to today
The dashboard opened on the last 7 days; open on today (local midnight
to now) instead. The trend chart already switches to hourly buckets for
ranges of a day or less.
2026-10-03 23:28:58 +08:00
Jason d59cac2975 fix(usage): share one quota cache between tray and card, add token-refresh-pending state
The tray refreshed a CLI-login official subscription through the usage-script
path, which stored the result in the script cache and emitted a "script"
event. The provider card reads the subscription query instead, so a tray
refresh never reached the card and the two could disagree until the card's
next poll. The tray now has a Subscription source for these providers: it
refreshes through get_subscription_quota and reads only the subscription
cache, the same one the card writes.

xAI OAuth providers keep the script path, and no longer fall back to the
app-wide CLI subscription cache when their own snapshot is missing.

Claude's access token expires every few hours and Claude Code renews it on
its next run. An expired access token with a live refresh token is now
reported as CredentialStatus::RefreshPending ("token refresh pending")
instead of "sign-in expired", on the card, in the accounts center and in the
tray. A missing or expired refresh token, or a 401/403 on a locally valid
token, still reports an expired sign-in.
2026-10-03 23:28:58 +08:00
Owlbay c2269df7a2 fix(ui): keep icon tiles and the env warning readable in dark mode
The v7 pass left a few surfaces on a fixed white background while their
contents still followed the theme:

- Provider card icon tile: monochrome (currentColor) logos turned white on
  white and initials fallbacks rendered as dark blobs. Pin the tile's text
  to a dark neutral and give the fallback a transparent background; keep
  multi-character initials on one line.
- Env conflict banner items: bg-white with theme text hid the variable
  name in dark mode. Use the bg-surface token.
- Checkboxes: replace bg-white with bg-surface.
2026-10-03 23:28:58 +08:00
Jason c55a36c0bf feat(ui): rebuild the accounts center and Hermes memory pages
Accounts center: each service is now one card with a help tip, "Add
account" and a ⋯ menu ("Delete all accounts…" from two accounts up).
Account rows show the default and re-login badges, the sign-in date, how
many providers use the account, the remaining quota with a refresh time,
and a ⋯ menu for set default, sign in again and delete. Waiting, loading,
load-failure and login-failure states are drawn in place. Deleting still
confirms first and names the affected providers. Provider forms keep
their account picker.

Hermes memory: the save button moves to the page header and is the only
solid button. The file tabs mark unsaved edits with a dot and keep drafts
when switching. The enable row shows the real file path, or that Hermes
skips the file while it is off; the editor is a plain monospace area; the
meter shows characters remaining and warns when over the limit. When
changes take effect is said in the toasts, and "Adjust limit in Hermes
Web UI" asks before starting the Web UI.
2026-10-03 23:28:58 +08:00
Jason c1ead8d0cc fix(usage): skip burst windows in provider speed totals
Main now hides per-request speed when the generation window is under
100ms. Apply the same rule to the provider speed totals so the table and
the request log agree, and update the preset row count now that the ETok
presets are gone.
2026-10-03 23:28:58 +08:00
Jason 0b1af534fa feat(ui): apply the v7 design review
Give each visual signal one meaning across pages.

Modes: only the mode being viewed gets the raised fill; the active mode
keeps its colour and dot. The Stack preview starts Stack from a row
("Start Stack with this as default…"), like the routing preview, so the
dialog no longer picks a default. The mode help moves next to the tabs,
the routing status drops the local address, the two-step explanation and
the "advanced feature" checkbox leave the dialogs, and added cards in
additive apps are white; grey now only marks the default provider.

Navigation: page-level switches use underline tabs (new PageTabs, also
used by Usage), with a smaller second level for OpenClaw config, Hermes
memory and Skills. Segmented controls stay for modes and in-page filters.
Hermes memory keeps unsaved edits when switching files.

Danger: irreversible confirmations use the red button; recoverable ones
(back to direct, remove from app, uninstall Skills, rebuild Codex usage,
delete env vars) no longer use red.

Pages:
- MCP/Skills matrix: one square checkbox shape for every state, and the
  column header highlights and shows the app name on hover or focus.
- Skills: drop the update banner; "N updatable" in the header filters
  the table and offers "Update all"; the unmanaged banner keeps only
  Ignore; the installed count lives on the tab only.
- Add provider: "All" is split by category, names sorted within each;
  versions split into plan and region, shown as two segmented rows when
  they form a grid and as a dropdown otherwise.
- Usage: the trend chart shows the selected metric only; today's log
  rows show the time only and the provider column gets the space.
- Sessions: the reader merges into the page header.
- Accounts: rows say how many providers use them, and removing an
  account confirms first and names the affected providers.
- Apps: small neutral switches for "Show"; Settings says "Check for new
  versions of your apps at launch".

Also add the provider empty state, a one-time "new layout" notice for
existing users, and a 1040px max width for card lists on wide windows.
2026-10-03 23:28:58 +08:00
Jason b7a5ef2885 feat(ui): follow the spec for preset versions, tray problems and skills storage
Presets: name the Tencent family "Tencent Cloud" (腾讯云), since it also
covers pay-as-you-go plans in some apps, and let a family list its
versions in design order (Kimi by region, Tencent Lite before Pro).
Families without an order keep the preset file order.

Tray: a failed tray switch no longer expires after ten minutes. It stays
until the app switches successfully or its page is opened in the main
window, reported through the new tray_app_page_seen command.

Skills storage and sync: turn the sheet into the spec's dialog with a
real three-way sync method (auto, symlink only, copy only; auto was
shown as symlink before), radio buttons for where the main copy lives,
the real CC Switch skills path with an Open button, and a visible line
explaining why "Move and switch" is unavailable.
2026-10-03 23:28:58 +08:00