Skip to content

feat: dashboard rework with eleven ranged pages and honest pricing - #51

Closed
KurutoDenzeru wants to merge 22 commits into
mainfrom
feat/dashboard-rework
Closed

KurutoDenzeru wants to merge 22 commits into
mainfrom
feat/dashboard-rework

Conversation

@KurutoDenzeru

Copy link
Copy Markdown
Owner

Description

Rebuilds the dashboard into a navigable page set and reworks the pricing ledger so every reported number traces to a recorded value.

The dashboard was one vertical scroll of components. It is now a sidebar shell with hash routing and eleven range-aware pages: Overview, Models, Providers, Costs, Requests, Errors, Traces, Tools, Frustration, Projects, Gain. Range picker in the header, 1–6 hotkeys, g + letter jumps.

The pricing ledger blended a default price into totals when it could not match a model id, so a single unknown model silently priced the whole account at Sonnet rates. A model id now resolves to its canonical vendor and provider, and three numbers are reported separately and never blended: measured (vendor-reported), API-equivalent (public rates), and cache savings. API-equivalent totals state coverage (N/M models priced) instead of hiding it. On this machine the API-equivalent total moved from $339.63 to $86.84 with 39/52 models priced and the 13 unpriced models named.

Related Issue

None. This branch started as a port of the omp-stats dashboard page set.

Type of Change

  • 🐛 Bug fix (non-breaking change that fixes an issue)
  • ✨ New feature (non-breaking change that adds functionality)
  • 📝 Documentation update
  • 🎨 Style/UI update
  • 💥 Breaking change (fix or feature that would cause existing functionality to change)
  • ♻️ Refactor (no functional changes)
  • 🧪 Test update
  • 🔧 Configuration change

Changes Made

  • Sidebar shell, hash routing, range picker, and the eleven pages above.
  • Six ranges: 1h, 24h (default), 7d, 30d, 90d, all. A short range filters recent rows by exact timestamp and buckets them (5m inside the hour, 1h inside the day); a day range reads the payload's day tables. One payload serves every range.
  • Session traces end to end: GET /sessions, /session/trace, /session/entry parse span timelines from the pi/omp transcripts (turns, model calls, tools, subagent tracks, markers, per-tool stats) with mtime and ETag caching. The Traces page renders a searchable session list, a per-track waterfall, and a span drawer with the raw transcript row. Every span comes from a recorded timestamp.
  • Pricing: resolve a model to its real vendor instead of falling back to a default; report measured, API-equivalent, and cache savings as three numbers; state coverage and name the unpriced models.
  • Usage rows now carry time to first token, provider, and working directory, so latency and project pages read recorded data rather than nothing.
  • Requests gains a per-row TTFT column plus TTFT and median-elapsed strip stats. Overview gains Avg elapsed and TTFT, and its activity chart switches between tokens, requests, errors, and cost. Providers gets the sortable totals table, a stacked burn chart, and a peak-hour histogram. Errors groups failures by their recorded reason.
  • Dashboard freeze fix: rangeView returned a fresh object each render, so every memo below it missed on each click and re-aggregated every day table on each expand, tab, and drawer. The view is now memoized on its inputs. The Models detail panel also mounted a recharts chart per expanded row; it now draws its day bars with plain elements.
  • windowPartial counted any full recent list as a truncated window, so the default 24h range showed a false "lower bound" note on five pages. A window is truncated only when the list is full and every row in it falls inside the window.
  • usage.db migrations guard against a truncated re-parse: a rebuild that keeps under half the rows restores the pre-migration backup rather than publishing the loss.

Screenshots/Recordings

Before After
One vertical scroll of cards Sidebar shell with 11 routed, range-aware pages

Visual verification during development was done against the omp-stats instance at 127.0.0.1:3847 on dark and light themes. Not captured here.

Testing

  • I have tested this locally
  • I have added/updated unit tests
  • I have added/updated integration tests

bun run verify (frozen lockfile + build + test) is what CI runs: 62 files, 445 tests, all passing.

New coverage: test/shared/session-trace.test.ts (parser: omp rows, pi fallbacks, unterminated calls, child-track linking, pricing, directory shape) and dashboard/app/test/ for pages, routing, traces, theme, and settings. dashboard/app/test/interaction-cost.test.tsx spies on the aggregation and fails if a second fold runs inside a click, which is the regression guard for the freeze above.

Also verified: a served instance returning 200 with a /data.json carrying every field; the cap note appearing on 30d/90d/all and absent on 1h/24h.

Checklist

  • My code follows the project's coding standards
  • I have performed a self-review of my code
  • I have commented my code where necessary
  • I have updated the documentation accordingly
  • My changes generate no new warnings or errors
  • I have checked for accessibility compliance
  • I have verified responsive design (if applicable)

Additional Notes

Lint reports warnings that predate this branch (react-hooks/exhaustive-deps on models, unicorn/no-array-sort, nested cell components); no new classes.

Labels applied: bug, enhancement, documentation, tests. There is no style label on this repo, so the UI part is covered by enhancement — say the word and I will create a style/UI label.

Two limits worth stating. Providers' "Subscription windows" and "Window utilization" sections are honest notes rather than empty tables: each needs per-account plan, quota, reset, and rate-limit data that the transcripts do not carry. And tokens-per-second is not reported, because dividing tokens by elapsed wall clock would conflate tool time with model speed.

@KurutoDenzeru KurutoDenzeru added bug Something isn't working enhancement New feature or request documentation Improvements or additions to documentation tests Test update labels Oct 8, 2026
@KurutoDenzeru KurutoDenzeru self-assigned this Oct 8, 2026
@KurutoDenzeru

Copy link
Copy Markdown
Owner Author

Dashboard rework: payload, tokens-per-second, and the last freeze

Four fixes on top of 47fb48a, each measured before and after on the same machine, same data, same browser.

  • /data.json is now gzipped and carries an ETag the client replays. 797 KB → 141 KB on the wire, and an unchanged poll returns 304 with 0 bytes instead of re-downloading the whole report every 5 seconds.
  • The client no longer builds a second 800 KB JSON string per poll to detect change; it compares a four-field stamp.
  • Tokens/s was inflated by roughly the request count. The Models column divided a model's total output by the mean duration of a single request: 20 tokens over a 1050 ms mean printed 19.0 instead of 9.5. The divisor is now total generating time (mean duration × run count). Against OMP's live values: step-5-preview 16.4 (OMP 15.1), deepseek-v4.1-flash 61.1 (OMP 61.3), TTFT 6.2s (OMP 6.0s).
  • Two gateways of one model merged into one row. The fold keyed on the display label alone, so cline/cline-free/mimo-v2.6-flash and opencode-zen/mimo-v2.6-flash collapsed into a single row and cost landed on the wrong provider. The key now carries the serving segment.
  • The last freeze. renderExpanded passed a fresh arrow on every render, so TanStack re-derived its expanded-row model on each parent render, including the one an expand itself triggers. Under a 6× CPU throttle, measured with Profiler and a longtask observer: expanding a Models row went from 3076 ms across seven long tasks to 43 ms with none. Every range switch now reports zero long tasks.

Verification: npx tsc -b clean, bun run test 446/446, and the expanded panel confirmed open in a real browser with the detail content rendering (screenshot in this thread). The tokens-per-second test fails when the fix is reverted, which I confirmed by reverting it and re-running.

Note on the residual tokens/s gap versus OMP: OMP counts cache-read and cache-write in throughput; tersio counts output only. TTFT matches to the decimal.

@KurutoDenzeru

Copy link
Copy Markdown
Owner Author

Superseded by #53, which is the same work carried to its conclusion. Closed rather than merged.

feat/dashboard-rework is not an ancestor of feat/omp-stats-dashboard, so 64 files are unique to this branch — the dash/, pages/, settings/, and app/ component trees, the @tanstack/react-table dependency, and /data.json. All of it is the architecture #53 replaced: components/agents/ and components/charts/ now hold the pages and the page kit, charts/table.tsx is a native sorting and paging table that needs no dependency, and api/agent serves per-view payloads where /data.json carried one whole report.

The behaviour fixes behind the architecture all survive, each reimplemented against omp's own database rather than Tersio's ledger:

  • The reparse guard that restores a pre-migration backup when a rebuild keeps under half the rows: extensions/shared/usage-store.ts.
  • Tokens per second over total generating time, not the mean duration of one request: extensions/shared/agent-stats.ts sums output_tokens * 1000 / duration and divides by the count of timed rows.
  • One row per model per provider, so two gateways of one model no longer merge: the byModel group carries the provider in its key.
  • Time to first token, provider, and working directory on request rows: read from omp's messages, which carries all three.
  • The interaction-cost guard against re-aggregating on a fold: the client polls api/agent every 30 seconds while visible, and the server memoizes the aggregate for 5 seconds keyed on both database file stamps.

Two things here were deliberately dropped rather than replaced: the Usage page and its ledger-only surface, and the gain and frustration pages that read a store the reference writes from. Tersio's own ledger stays a product through tersio usage, tersio reset, and tersio dashboard --export.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

bug Something isn't working documentation Improvements or additions to documentation enhancement New feature or request tests Test update

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant