Skip to content

Merge remote-tracking branch 'upstream/main' into 1.0.0-explore - #2350

Merged
wsp1911 merged 22 commits into
1.0.0-explorefrom
main
Aug 17, 2026
Merged

Merge remote-tracking branch 'upstream/main' into 1.0.0-explore#2350
wsp1911 merged 22 commits into
1.0.0-explorefrom
main

Conversation

@wsp1911

@wsp1911 wsp1911 commented Aug 17, 2026

Copy link
Copy Markdown
Collaborator

No description provided.

wgqqqqq and others added 22 commits August 17, 2026 15:58
The sidebar asked the user to pick a conversation *source* — Local or
Remote — before showing them anything. That is an implementation detail:
where a session runs is not how anyone looks for it, and the switcher cost
a permanent row of chrome to express it.

Conversations and workspaces now share one scroll. They are different kinds
of thing (a timeline and a set of places), so they stack rather than compete
for the same pane, and neither carries a label saying which machine it runs
on. The workspace section arrives through a `@BuilderParam` slot: the
sidebar has no business knowing about remote state, so the host wires it.

- Drop `ConversationSource` and `ConversationSourceSwitcher`.
  `AppRouteContract.conversationSource`/`routeForConversationSource` become
  `isRemoteRoute`/`remoteSurfaceDestination`, with `isRemoteRoute` stated as
  the complement of `isGeneralComposerRoute` so the two cannot drift apart.
  `remoteSurfaceDestination` resumes an in-flight remote session instead of
  dropping the user on a picker they did not ask for.
- Cap the conversation list at six rows behind an overflow row, so the
  workspace section stays visible on a phone without hunting for it. The
  shared scroll means the list can no longer take every pixel it wants.
- Fade rows out under the floating footer via a new `page_bg_fade` token
  (PAGE_BG at zero alpha, defined per theme). Without it a workspace row
  mid-scroll is visually sliced by the chat button sitting on top of it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The workspace section's "connect a desktop" row was wired to
`onSidebar.enterCode`, which only moves the visible surface to Remote.
Offline that resolves to `AppRoute.RemoteHome`, whose wide-layout detail
pane is `RemoteSurfaceHost` in placeholder mode: a header naming the
desktop the phone last held and two lines of status text, with no control
on it. So the row named the last-known machine on the right and then did
nothing, and pressing it again returned early on the `isRemoteRoute`
guard.

`RemoteSurfaceEntryPolicy` already states that reaching the remote surface
and asking to pair are different commands and must not share an entry
point. The row was on the wrong side of that line: give it
`onSidebar.connectDesktop`, which opens the connect sheet.

The section header's "+" shares the same event. Workspaces come from the
desktop and the phone cannot create one, so it is relabelled by the
connection it adds rather than a workspace it cannot make.

Also drop workspace entries with no path. `syncRemotePageSummary` seeds
the current workspace with the placeholder name 「未连接」 and an empty
path, and the projection turns any named current workspace into an entry.
On the Remote surface that never showed, because you only got there
connected; in the sidebar it is on screen from launch, so a reconnecting
phone with no workspace data yet listed a folder called 「未连接」 that
opens nothing.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The back arrow on the desktop picker and on the account page sat visibly
left of the circle it lives in. Measured on device: circle centre x=422.5,
chevron ink centre x=399.5 — about 7vp adrift.

The cause is SymbolGlyph sizing, not the surrounding layout. A chevron's
natural advance box is roughly half as wide as it is tall (~11.6vp at
fontSize 23, against 23.2vp of height). Forcing .width(26).height(26) does
not re-centre the glyph in the enlarged box; it draws left-anchored, so the
ink lands 26/2 - 11.6/2 ≈ 7vp left of where the box centre says it should.
Vertically it looked fine only because the natural height already matched
the forced one. Probing the two glyphs side by side with tinted backgrounds
confirmed it: the unsized chevron's box measured 37px wide and centred in
the circle, while a magnifier forced to the same 26vp filled its box and
stayed centred, because its ink is square.

Dropping the forced size lets the Stack/Button centre the natural box.
Re-measured after the change: ink centre x=422.5 against a circle centre of
422.5.

Trailing chevrons elsewhere keep their forced width — there it acts as a
row gutter rather than as a centring box, and removing it would move the
rows rather than fix them.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The crooked back chevron fixed in b48f60d was not a one-off. Probing on
device showed SymbolGlyph draws its ink left-anchored inside any box the
caller forces wider than the glyph's natural advance box (vertically it
stays centred, so only the horizontal axis is affected). Most symbols
have a square advance box of fontSize x 1.013, but chevron_left and
chevron_right are only half as wide as they are tall, so every
`.width(N).height(N)` on a chevron shifted it left of where the
surrounding container promised to put it.

Two shapes of the same bug:

- Glyphs inside a fixed-size centring Stack or circular Button: the
  forced box overrode the container's centring and pulled the icon off
  centre by up to 4.5vp. Dropping the size lets the container centre the
  natural box. Measured on the settings close button: ink centre now
  0.3vp from the circle centre, was ~7vp.

- Trailing disclosure chevrons in list rows: the forced 16-18vp box left
  ~10vp of dead space between the arrow and the row's right padding, so
  the arrows never lined up with the card edge. The right gap on the
  model row now measures 18.6vp against the 18vp padding.

Collapse indicators that toggle between chevron_right and chevron_down
keep their fixed slot -- it is what stops the label jumping between the
two states -- but the slot moves to a wrapping Stack and the glyph inside
it goes unsized, matching the idiom already used in SubagentTaskCard.
Verified on device that the label x is identical in both states.

Leading icon slots in list rows are left as they are: because ink is
left-anchored, the forced width is what keeps icon and text left edges
aligned across rows of differing fontSize.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The previous bullet told authors to "give the glyph its visual size",
which is exactly what produced the off-centre chevrons fixed in
257a945. Replace it with the metrics measured on device, the reason
forcing a width moves a chevron left, the Stack-wrapper idiom for
collapse indicators, and the one case where a forced width is the right
call.
An activity group buffers a maximal run of thinking entries and settled
tools, then renders every thinking part above every tool it holds. A run
is only broken by a non-foldable entry such as assistant text, so a
silent chain of thought -> act -> thought collapses into one group and
the later thinking is hoisted above tools that had already run when the
model wrote it. On device that showed up as a single "已思考" sitting on
top of "已运行 4 个工具" for a message whose transcript alternates.

Open a new group whenever a thinking entry follows a tool in the same
run, and flush buffered tools before pushing a group so tools that ran
earlier cannot land below it. Consecutive thinking segments still merge
into one header.

Test plan:
- assembleHap + LocalTest for entry@default, no ERROR lines.
- On device (foldable inner screen, dark theme), the same message now
  renders 已思考 / 已运行 2 个工具 / 已思考 / 已运行 2 个工具 / 已思考
  where it previously rendered 已思考 / 已运行 4 个工具.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
ToolStatusList built its collapsed summary detail, tool operation labels,
Todo/Task fallbacks, and the failure suffix from Chinese string literals
while the rest of the component already used RemoteI18n. Move all of them
into the message table so the component has no user-visible literals left.

Test plan:
- assembleHap + LocalTest for entry@default, no ERROR lines.
- On device, an expanded tool group still reads
  "运行命令 · gh auth status 2>&1; gh api ..." and the collapsed row still
  reads "已运行 2 个工具"; no message keys leak into the UI.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
A transcript reads think -> act -> think -> act, but the app rendered one
「已思考」 stacked on top of 「已运行 N 个工具」: `ActivityGroup` merged every
thinking part in the group above every tool in it, hoisting later reasoning
over tools that had already finished when the model wrote it.

Reasoning and the tools it drove are one stretch of work, so they now fold
behind a single summary row instead of standing as two cards. `ToolStatusList`
takes the group's thinking parts and weaves them among its tool rows:
collapsed shows only 「已运行 N 个工具」, expanding reveals thinking and tools
in transcript order. A lone tool has no summary to unfold, so its reasoning
stays on screen rather than being lost.

`ActivityGroupPolicy` gains the placement rules — `mergeThinkingParts` keeps a
single header for an uninterrupted stretch of reasoning while pinning blocks on
either side of a tool call to where they happened, and `weaveRows` never puts
thinking above a summary row, which is the fold's own header.

Also drops `collapsedSummaryDetail`, which nothing called, and the four
`chat.tools*Count` message keys it was the only reader of.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
feat(harmonyos): replace the sidebar source switcher with workspaces
Add a settings page showing historical model call statistics (requests,
tokens, estimated cost) broken down by model, provider group, and
endpoint, with a token usage trend chart.

- services-core: pure aggregation module with unit tests
- core: attribution resolver (config + models.dev catalog) and
  get_statistics on the token usage service
- desktop: get_token_usage_statistics Tauri command
- web-ui: UsageStatisticsConfig settings tab + i18n (en/zh-CN/zh-TW)
- services-core: remove price estimation; UsageStatisticsEntry now carries
  cacheHitRate (cached / reported input); UsageStatistics gains
  totalCacheReportedInputTokens for the overall hit rate
- core: resolver no longer resolves prices
- web-ui: summary cards show requests/tokens/cached tokens/hit rate; tables
  show model/group/endpoint, requests, tokens, cache hit rate; fix table
  header layout (min column widths + no-wrap) that squeezed Chinese labels;
  drop cost columns and footnote
- i18n: update en/zh-CN/zh-TW copy
…splay

- move cache hit rate out of the distribution tables into a dedicated
  per-model card with a ratio bar
- display hit rates truncated to two decimals (never rounded up); only a
  true 100% hit (cached == reported input, exact in the backend) shows as
  100%
- trend chart now spans the full row; distribution tables revert to
  name / requests / tokens
The image understanding tool calls AIClient directly, bypassing the round
executor, so its consumption never reached the token usage store and
multimodal models were missing from the usage statistics page. Persist the
response usage through a global TokenUsageService reference installed by
the desktop runtime.
Dynamic var() string building (--bf-appearance-token-${name}) is invisible to
the theme color audit, which flagged an unresolved required CSS var and broke
the CI Frontend Build job. Replace every token reference with a literal var()
so the audit resolves them statically.
The appearance contract audit (part of build:web) requires every
data-bf-component to match a registered descriptor id and every data-bf-part
to be a string literal declared on the same node as its component. Align the
usage statistics page: component id 'usage-statistics-config', literal parts
on root/filters/summary/distributions/modelHitRate/trendPanel/empty, and drop
appearance attributes from internal chart components.
Filter persisted usage records against exact half-open query bounds
instead of including every record from matching daily files.

- Resolve today, week, and month boundaries in the caller's time zone
- Align trend buckets and labels with the same local calendar
- Preserve UTC behavior when older clients omit the time zone
- Cover boundary timestamps, local midnight, and DST transitions
Resolve model and provider breakdowns from model_config_id instead of
API protocol formats or ambiguous model-name catalog fallback.

- Aggregate model statistics with stable configuration IDs
- Group provider usage by provider_instance_id and display config names
- Isolate deleted or missing configurations with explicit status
- Show supplier names in model distribution and cache hit rate panels
- Route browser time-zone detection through shared i18n helpers
- Add backend and UI regression coverage
Filter usage records before aggregation so every statistics panel
reflects the selected provider or model query consistently.

- Match providers by configured display name instead of API protocol
- Match configured and effective model names
- Support deleted configurations through effective model fallback
- Add debounced filter controls and localized empty states
- Keep requests without filter fields backward compatible
Mark token usage statistics as workspace-agnostic because usage is
recorded by the current BitFun runtime rather than routed to the SSH
workspace host.

Add regression coverage to preserve this host-scoped behavior.
Route usage statistics through the shared transport so Desktop,
Peer Device, and Server/Web read data from the active host.

- Share request parsing across product surfaces
- Add CLI peer and Server RPC support
- Negotiate peer capability and reject unsupported older hosts
- Clarify that usage data is stored separately on each device
Move dashboard request parsing and time-range resolution into
bitfun-services-core so Core remains a compatibility wrapper.

Add a dedicated token-usage-statistics feature for IANA time-zone
aggregation instead of expanding local-storage with chrono-tz.
Preserve UTC queries for local-storage-only consumers and update
the feature governance checks and focused tests.
@wsp1911
wsp1911 merged commit 1c9d564 into 1.0.0-explore Aug 17, 2026
13 of 17 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants