feat(desktop): lead the session trace with an Astryx-native overview - #2289
Merged
Conversation
The metering ledger freezes the window each call ran under; the trace projection dropped it, so the Inspector could not set a prompt size against the ceiling it was actually metered against.
Pure view model for the trace panel's new summary sections: context budget from the latest metered main call, per-turn and per-session token/cache stats, the latest model, and the session's active span. Unknown stays unknown rather than reading as zero.
The 追踪 tab answered only "what did it cost" per turn. It now opens with the glance facts — context fullness against the window the last call was metered under, token and cache shape for the latest turn and the session, and the session's model/cost/span — with the causal timeline folded into a 原始记录 disclosure underneath. Unreported breakdowns render as unknown rather than zero, matching the ledger's own distinction.
…nents Review feedback: the first pass carried too much custom chrome — three hand-ruled sections, a two-column token grid and a display-size percent. The overview is now one ProgressBar (its own label + value) plus one MetadataList of session facts; the per-turn column is gone because the raw timeline already carries per-turn numbers. startedAt/contextWindow derivations with no remaining reader go with them.
…cord Review feedback. The coverage warning was the panel's loudest element while commenting on the least-read layer: it is now one quiet line inside the raw record it describes. The timeline itself stops printing uuids — a turn is named the way a reader counts (第 N 轮 / Turn N, its position in the session), stamped and metered on the header line, and each step carries a kind icon with its duration/cost right-aligned, so the record scans as evidence instead of wrapped prose.
A single-value bar answers "how full", which is the smaller half of the question a reader opens the Trace tab with; the half they act on is "full of what". The context bar is now the split the metering ledger actually carries — cache hit vs fresh prompt vs free headroom — with a three-column legend under it so counts and shares line up down the list. Only reported facts are drawn. When a provider reports no cache figure the prompt stays one band rather than becoming a zero cache hit, empty bands are dropped instead of rendering as slivers, and a prompt that overran its window still fills exactly one track (#1679). The overview also splits into the two sections it always was: one call's prompt above, the whole session's totals below. MetadataList's page-sized row rhythm is tightened to the legend's density so the two read as one panel in a 400px column.
… panel The overview had drifted into three type sizes with no rule behind them: a 12px section title over 12px legend rows, then 14px MetadataList facts, then a 14px turn heading in the raw record. Two neighbouring sections in two different sizes claim a hierarchy that does not exist, and the Astryx list also painted from the neutral defaults (#262626 / #737373) beside figures in product tokens. Rebuilt on the Astryx IDE template's properties panel, which is the closest reference this surface has: a level-3 heading per section, body-sized facts on the template's `metadataCompact` rhythm, and one supporting tier for what merely qualifies a figure. The context legend and the session facts now share a single three-column grid — name, figure, qualifier — so a cache rate or a retry count sits in its own column instead of being glued onto the number with a middot, and every figure in a section ends on the same edge. The panel palette seam is extended to the trace panel so its Astryx pieces stop painting neutral, and the workbar's default width goes 400 → 480 to give that grid room; a stored width still wins, so only a reader who never dragged the handle sees it.
… as a timeline Three problems in one layout, all of them the same mistake — putting things next to each other that a reader is never comparing. The session facts were laid out on the legend's shared right edge, which is what alignment is FOR when every figure is the same unit against the same window. A model name, a price and a timestamp compare with nothing, so that edge only opened a lane of white space in every row. They now sit beside their labels, with the qualifier trailing the figure; the context legend keeps its columns, where the shared edge is the comparison. The overview and the record were stacked with a fold between them, so the timeline always opened below nine facts it has nothing to do with, and its own trigger heading competed with the section headings above it. They are now two segmented views — 概览 / 时间轴 — the same shape the Astryx IDE template uses to segment Properties / History. A backend with no metered overview opens straight on the timeline, since then there is only one view to be in. And the record itself was hairline-separated blocks, which says "different rows" where the fact is "this order, and these steps belong to that turn". It is now an actual timeline: a node per turn (red when the turn failed), a rail carrying its steps, totals closing the header line.
Nine table rows, each given a label column and a value column, in a 480px panel: the labels crowded the left edge, the values ended wherever they ended, and the right half was empty. The layout was a symptom — the list had never been asked which of its numbers anyone reads. Asked now, per figure, "who reads this, to decide what": - Cost, duration and cache hit rate lead as headline stats. Those are the three a reader opens the tab for, and as figures rather than rows they give the column something worth filling. - The context bar stays: the only thing here that answers a question about now rather than about the past. - Input and output collapse into one ratio, and the cache read count goes — it is already stated as a rate above and drawn as a band in the bar. - Retries and compactions report by exception. Printing `0` twice taught the reader to skip the block that also carries the exceptions. - Model call count and last-activity are gone. The count had no decision attached; the timestamp is already on the session in the sidebar. - What is left — model, the in/out ratio, and any exception — is one wrapped meta line, not a table reserving a column per fact. The three-column grid stays exactly where alignment earns its keep: the context legend, where every figure is the same unit against the same window and the shared edge IS the comparison.
The tab switcher made the overview and the timeline rivals when they are the same question at two zoom levels; the timeline is back under the overview and the filter rides its heading line. Inside a turn the ranking is now explicit: the turn and its failure carry full ink, the measurement trails in supporting grey, and the steps sit a tier below. The failure is red text rather than a filled Badge, which out-shouted the label it qualified, and the rail's dot already flags the row from the margin. What a reader could not act on is gone: the per-step price (the turn above states the same money at the level a decision happens), the date on every turn stamp (a fact about the session, not the turn), and the `main` call kind and compaction checkpoint id that printed on rows where they said nothing.
The timeline was carrying five step icons, two filter controls, a wall-clock stamp per turn and up to four numbers a row — chrome about the record rather than the record. Gone, each because nothing was decided on it: - The filter, and the module behind it. A session's trace is a handful of turns on one screen; a search box over a list you can already see is a control that costs more attention than it saves, and 仅失败 now duplicates a red dot in the margin. - Every step icon. Five glyphs that restated a word printed next to them (`read`, `compaction`, `error`). - The wall-clock stamp on every turn. The order IS the timeline and the duration is beside it; the clock only mattered for correlating with a log the panel does not show. - One more font weight on the step line and on the failure marker, so the timeline ranks by ink alone: bold for the turn, colour for the exception, grey for the measurement. Three type sizes are left in the block — heading, turn, step — and two numbers a turn.
…ter a shape The panel was ranking with four sizes, and the smallest one carried six different roles — stat labels, the context readout, the meta line, the coverage notice, every step, every measurement. Six roles at one size is not a rank, it is small print. 12px is gone from this surface. Three sizes are left, one per rank: 20px for the figures a reader opened the tab for, 16px for the two section titles, 14px for every fact. Inside the 14px tier nothing is bold but the turn name; colour alone separates a label from a figure, a qualifier from what it qualifies, and an exception from either. The filter comes back in a form that is data before it is chrome: the failure count states how many turns failed and toggles to just those, so the line spends a word where a Switch spent a track, a label and a wrapped line. The query field keeps its own component and shrinks before it pushes. What the filter hides is now announced beside the rows it hid, not above the overview it has nothing to do with.
The overview ended in a run-on line — provider, model, input/output, reasoning, retries, compactions — six facts sharing one grey sentence because none of them had earned a place of its own. Asked what each one decides, only the first survived. The model leads the panel now, as the subject the figures are measured on rather than the tail of a sentence about them. Everything after it is gone: - Token totals and the reasoning split. Cost already prices those tokens, the context bar already sizes the prompt, and the hit rate already reports the cache; `248,800 / 740` mostly restated how many turns the session had. Exact counts belong to the run ledger, which is where an audit belongs. - Retry and compaction counts. Both are EVENTS the timeline below already lists — a retry as `×2` on the step that retried, a compaction as its own step. Counting them up here summarised a list on screen. `InspectorTokenStats` goes with them: the panel keeps one token figure, so the model exposes one, and the "a rate over nothing is unknown, not zero" rule (#1679) moves onto it intact.
The model was a 14px caption over 20px figures: it ranked below the numbers it governs, and alone at the top of the panel with no label it read as an orphan string. It is the section's title now, on the same head line every other block here uses — title on the left, the fact that qualifies it on the right. `glm-5.1` sits at the heading tier with `Z.AI` as its readout, exactly as `上下文窗口` sits beside `18,900 / 200,000 · 9.4%` and `时间轴` beside its failure count. Three blocks, one shape, and the panel now opens by naming what it is about instead of by whispering it. The overview's render condition collapses onto the same fact: the window and the cache rate are both read off the model calls, so a session with either necessarily has a model to name.
Every placement of it was wrong for the same reason. As a caption it ranked below the figures it introduced; as a title it was 16px floating over 20px. The size was never the bug — the line did not belong on this surface. The trace answers what a session DID and what it cost. Which model it was configured with is setup, and the app states it twice already: the composer names the model you are about to use, and the timeline in this very panel labels each model call with the model that call used. A single line picking the most recent one repeated the rows underneath it and, in a session that switched models mid-run, contradicted them. So the panel opens on its figures, with nothing above them. A label over a 20px number is exactly what a StatCell is, and its label is already at the right size for the job. `InspectorModelRef` goes with the line, and the overview's render condition simplifies to "the session is not empty" — cost and duration come from the run ledger's totals, so a backend that reports no per-call detail now keeps its two figures instead of losing the whole block.
The context bands were named after the code that produces them rather than after what a reader sees. `新增提示` was a literal reading of "fresh prompt" and left `缓存命中` without an opposite; `空闲` described the window as idle when what it has is room left. The three bands are now one vocabulary: 缓存命中 / 缓存未命中 / 剩余, with 已占用 for the single band a provider that reports no cache figure gets. The coverage notice was a chain of fragments built in the layout, with `下界` — a maths term — for its main claim. It is one sentence now, and the punctuation that joins it moved into the copy where the language lives: Chinese gets `:` and `、`, English gets `:` and `,`. Each fragment states its own count, which is also how English stops saying "1 turns". `费用不可得` became `费用未知`, which is the same fact in words someone says out loud.
Half the words on this panel came from nowhere a reader can see. The view model baked English literals (`compaction`, `error`, `permission`) into rows it hands to a Chinese UI, and passed the projection's raw identifiers straight through: `history_compact`, `allow`, `parked`, `tool_failed`. `×2` was the attempts counter's own notation, asking the reader to work out that one of two attempts was a retry. The projection records facts, so the naming moves to where naming belongs. A row now carries only identifiers that are genuinely the session's — a model id, a tool name — and the panel supplies the word for a row whose kind IS its identity. Every enum gets a table in the copy file, with the raw value falling through when nobody has named it yet, so a new call kind degrades to itself instead of to a blank. `本轮失败 · tool_failed` collapses into one phrase: the code already said what failed, it just said it in engineering vocabulary, and the label beside it said it again. Also here, from the same audit: `没有匹配的步骤——这个会话本身是有内容的` was explaining the panel's own state machine to the reader; English counts are singular when they are one.
Reviewed against Astryx's own incident-console template, whose inspector is the same shape as this one: a header, facts, and a timeline. One distance was doing three jobs. 16px separated the stat band from the context block, the overview from the timeline, AND one turn from the next — so proximity, the only grouping a panel without boxes has, said nothing. Blocks are 24px apart now, turns 16px, and rows 8px. The seam under the overview was a hairline plus two stacked 8px paddings nobody chose; 24px of air does the job a rule was doing badly. The rail was drawn per turn and stopped short of the next one, so the timeline was a column of stumps rather than a line, at 12% ink. It runs continuously now, from the first dot to the last, on --border-strong. It is the only strong vertical on the surface and it can afford to be seen. The context bar stops painting free space. The track IS the empty window, the band keeps its share of the flex so the used bands stay proportional, and 90% of the bar stops reading as a third data series next to the two that are actually data. The seams between bands go too: a 2px gap in an 8px bar is a quarter of it. The legend loses its share column. The bar is the share, the readout already states the one share that is not obvious, and two right-aligned number columns per row is what made three facts read as a spreadsheet. What is left — name left, number right — is the step row's own skeleton. Ink follows the same correction. Per-turn cost steps up to --foreground-secondary, because it is the column a reader scans and the whole right edge was one grey; the coverage notice steps down to muted with only its icon in warning ink, because a disclaimer was out-shouting the failures it comments on. Declined from the same review: List/ListItem (its label + description + endContent shape turns a single-baseline row into two lines), StatusDot and Token (the rail math is solved, and a filled chip out-shouts the label it qualifies), MetadataList (a fixed label column breaks the legend's shared right edge).
Astro-Han
force-pushed
the
feat/inspector-session-overview
branch
from
August 6, 2026 08:21
d09349a to
52fe254
Compare
…e truth An adversarial review of this branch found two claims the panel makes that it cannot keep, plus the residue of eighteen commits of redesign. The cache hit rate divided a session's cache reads by its prompts with no clamp, while the bar beside it clamped the same pair with a comment explaining why. The runtime's own Google mapping guards against a provider reporting more cache than prompt — and sets the cache figure anyway before it bails — so the pair reaches this panel and renders as `120.0%`. Both figures now clamp, per attempt, for the same stated reason. The filter searched the trace's identifiers while the panel had moved to rendering words: a reader who typed `第 7 轮`, `语义压缩` or `已允许` — every one of them on screen — got 没有匹配的记录, while `turn-7` and `semantic_compact`, on screen nowhere, matched. The filter now takes the copy and searches what the row draws, which is what its own doc comment claimed all along. `turn_failed` and `error`, two codes the projection really emits, get names; the call-kind tables are typed against the core union so the next kind fails the build instead of the reader. Cut with nothing lost: `segment.ratio` (the bar grows by tokens and the legend prints tokens), `InspectorStepRow.status`, `InspectorTurnRow.failureMessage`/`attributedToStepId`, a one-caller section wrapper whose only conditional was never false, and two classNames no stylesheet ever matched. The guards that hold the honesty invariants were tested only at their equality boundary, so deleting either one passed; they are now tested in the direction they exist for, and the `contextWindow` passthrough the whole context bar hangs from finally has a projection test.
Astro-Han
force-pushed
the
feat/inspector-session-overview
branch
from
August 6, 2026 08:22
52fe254 to
5dba80b
Compare
Astro-Han
marked this pull request as ready for review
August 6, 2026 08:33
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
Redesigns the session workbar's Trace (追踪) tab to lead with an Astryx-native overview instead of only per-turn pricing.
ProgressBar(its own label + value) — the latest metered main call's prompt size against the window it actually ran under (contextWindowis frozen in the metering ledger at call time and passed through the trace projection). Warning ≥70%, error ≥90%.MetadataList— model (registry display name), input / cache read (with hit rate), output (with reasoning), model calls (with retries), compactions, cost, duration, last activity. No per-turn column: per-turn numbers stay in the raw record.Collapsible, redesigned to read as evidence — turns are named the way a reader counts (第 N 轮/Turn N, never the uuid), stamped and metered on the header line, and each step carries a kind icon with duration/cost right-aligned. The coverage warning is demoted from a page-top banner to one quiet line inside this record, the layer it comments on.Out of scope on purpose: Claude's per-category context breakdown (system prompt / tools / messages) needs new runtime instrumentation and is left for a follow-up PR.
Validation
deriveInspectorOverviewModel,deriveInspectorPanelModel) with unit tests; targeted desktop suites (81 tests) green; core trace validation + runtime projection green.session-workbarPlaywright E2E (2 tests) passes; typecheck / lint / format:check / check-dead-css / check-copy / check-a11y all pass.Product/Session Workbar → Trace, fixture extended with cache/contextWindow metering): light, dark, compact viewport, empty state. Live-app (Electron + fake backend) capture confirms the honest timeline fallback.project-root/project-managementdesktop tests fail identically on unmodifiedmain(environmental, unrelated).Evidence
(Storybook screenshots — overview light/dark, raw record expanded — and one live-app capture attached below.)