feat(tui): DateBreakdownPanel improvements, shared components, and branding - #19
Merged
Conversation
borderStyle causes OpenTUI crash when used in dynamically rendered components (inside Show). Removed border, kept all other features: - Source grouping (OPENCODE, CLAUDE, etc.) - Per-model breakdown with cost and tokens - Input/Output/Cache token display
- Remove unused Setter import from StatsView - Remove unused onDateSelect prop from StatsViewProps - Remove unused palette prop from DateBreakdownPanelProps - Add cacheWrite token display (CacheW) in breakdown - Simplify footer (remove redundant total, already in header)
- Create ModelRow component for unified model display with colored dot, name, percentage, and token breakdown - Create TokenBreakdown component for consistent In/Out/CR/CW display - Refactor OverviewView and StatsView to use shared components - Ensure Stats breakdown panel uses exact same styles as Overview's model-by-cost
…l is active The issue was that getCellStyle was called once per cell inside a <For> callback, returning a plain object. When colorPalette changed, SolidJS didn't track the dependency because the callback was cached by <For>. Fix: Replace getCellStyle with inline reactive function calls (isSelected, getCellColor) that are evaluated directly in JSX attributes, ensuring proper reactivity tracking.
- Change 'Token Usage Tracker' to 'tokscale' - Make header title clickable to open https://github.com/junhoyeo/tokscale - Support macOS, Linux, and Windows for opening URLs
…te parsing - Extract DateBreakdownPanel component to separate file (72 lines) - Move SOURCE_COLORS to utils/colors.ts with SourceType typing - Add getSourceColor() helper function - Fix date parsing timezone drift with T00:00:00 suffix - Add model sorting by total tokens in DateBreakdownPanel - Remove debug code (debugInfo signal, debug UI, comments) - Update component barrel exports StatsView.tsx reduced from 316 to 225 lines.
junhoyeo
added a commit
to haunchen/tokscale
that referenced
this pull request
Jun 17, 2026
…arser tests Map the raw junhoyeo#19 responseModel `gemini-3-flash-a` onto the priced `gemini-3-flash-preview` so Antigravity CLI cost no longer resolves to 0. Add alias-resolution, junhoyeo#9/junhoyeo#10==junhoyeo#3 field-mapping invariant, and malformed-protobuf bounds tests. Constraint: must not weaken the junhoyeo#707 brand-token fuzzy-match guard in lookup.rs Confidence: high Scope-risk: narrow
junhoyeo
added a commit
that referenced
this pull request
Jun 17, 2026
#713) * feat(sessions): read Antigravity CLI usage from local SQLite databases The Antigravity CLI (the terminal agent that stores its data under `~/.gemini/antigravity-cli/`) was never counted. tokscale only knew two Gemini-family sources: the Gemini CLI (scans `~/.gemini/tmp/*.{json,jsonl}`) and Antigravity (pulls usage from a running IDE language server over RPC and caches it under the config dir). The Antigravity CLI fell into neither bucket, so its on-disk usage was invisible — `tokscale antigravity sync` found the filesystem candidates but cached zero because its only artifact path still requires a live language-server RPC connection. This adds Antigravity CLI as a first-class local scan source so its usage updates automatically like every other file-based source — no RPC, no `antigravity sync`. A new `antigravity-cli` client globs `~/.gemini/antigravity-cli/conversations/*.db` (honoring `GEMINI_CLI_HOME`) and a new parser reads each conversation database directly. Each `gen_metadata` row is one generation encoded as the same `GeneratorMetadata` protobuf the IDE returns over `GetCascadeTrajectoryGeneratorMetadata`. The repository has no `.proto`/prost decoder (the IDE path receives JSON because the language server does the proto-to-JSON conversion), so the parser ships a tiny dependency-free wire-format reader and pulls only the fields it needs. The field numbers were reverse-engineered from real databases and cross-checked across 6 sessions / 140 turns: `chatModel.#19` is the response model, `usage.#5`/`#9`/`#10` are cacheRead/output/thinking (verified by the invariant `#9 + #10 == #3`, the stored total output), `#11` is the responseId used for dedup, and input combines the fixed system-prompt count `#1` with the newly-processed input `#2`. The session timestamp and workspace come from `trajectory_metadata_blob`. Adding the new `ClientId` variant fans out to the usual registration points: the scanner gains a `*.db` glob arm (which naturally rejects `.db-wal`/`.db-shm` sidecars), both local-parse dispatch paths gain a branch, and the CLI `ClientFilter`, client labels, TUI picker, and frontend source maps gain entries. The deprecated per-client boolean flags intentionally do not, since `antigravity-cli` is reachable only via the canonical `--client antigravity-cli`. Closes #712. * fix(sessions): handle file:// authority/UNC paths and test Antigravity CLI wiring Addresses the cubic review on #713. `file_uri_to_path` previously stripped `file://` and only special-cased the leading slash before a Windows drive letter, so a non-empty authority (`file://host/share/...`, the UNC form) lost its host and collapsed into a bare path. It now treats an empty-authority remainder as before (`/C:/x` → `C:/x`, `/home/x` kept) and reconstructs a non-empty authority as a UNC path (`host/share/x` → `//host/share/x`) so `normalize_workspace_key` preserves the `//` prefix. A unit test covers the Windows-drive, POSIX, UNC, and percent-encoded-CJK cases. The new `AntigravityCli` client wiring is now asserted in `test_client_as_str`, `test_client_key`, and `test_client_from_key` (display name "Antigravity CLI", hotkey `f`, and the reverse hotkey mapping). * style: rustfmt antigravity_cli.rs * fix(antigravity-cli): add gemini-3-flash-a pricing alias and harden parser tests Map the raw #19 responseModel `gemini-3-flash-a` onto the priced `gemini-3-flash-preview` so Antigravity CLI cost no longer resolves to 0. Add alias-resolution, #9/#10==#3 field-mapping invariant, and malformed-protobuf bounds tests. Constraint: must not weaken the #707 brand-token fuzzy-match guard in lookup.rs Confidence: high Scope-risk: narrow --------- Co-authored-by: Junho Yeo <i@junho.io>
pinion05
added a commit
to pinion05/tokscale
that referenced
this pull request
Jun 23, 2026
junhoyeo#713) * feat(sessions): read Antigravity CLI usage from local SQLite databases The Antigravity CLI (the terminal agent that stores its data under `~/.gemini/antigravity-cli/`) was never counted. tokscale only knew two Gemini-family sources: the Gemini CLI (scans `~/.gemini/tmp/*.{json,jsonl}`) and Antigravity (pulls usage from a running IDE language server over RPC and caches it under the config dir). The Antigravity CLI fell into neither bucket, so its on-disk usage was invisible — `tokscale antigravity sync` found the filesystem candidates but cached zero because its only artifact path still requires a live language-server RPC connection. This adds Antigravity CLI as a first-class local scan source so its usage updates automatically like every other file-based source — no RPC, no `antigravity sync`. A new `antigravity-cli` client globs `~/.gemini/antigravity-cli/conversations/*.db` (honoring `GEMINI_CLI_HOME`) and a new parser reads each conversation database directly. Each `gen_metadata` row is one generation encoded as the same `GeneratorMetadata` protobuf the IDE returns over `GetCascadeTrajectoryGeneratorMetadata`. The repository has no `.proto`/prost decoder (the IDE path receives JSON because the language server does the proto-to-JSON conversion), so the parser ships a tiny dependency-free wire-format reader and pulls only the fields it needs. The field numbers were reverse-engineered from real databases and cross-checked across 6 sessions / 140 turns: `chatModel.junhoyeo#19` is the response model, `usage.junhoyeo#5`/`junhoyeo#9`/`junhoyeo#10` are cacheRead/output/thinking (verified by the invariant `junhoyeo#9 + junhoyeo#10 == junhoyeo#3`, the stored total output), `junhoyeo#11` is the responseId used for dedup, and input combines the fixed system-prompt count `#1` with the newly-processed input `junhoyeo#2`. The session timestamp and workspace come from `trajectory_metadata_blob`. Adding the new `ClientId` variant fans out to the usual registration points: the scanner gains a `*.db` glob arm (which naturally rejects `.db-wal`/`.db-shm` sidecars), both local-parse dispatch paths gain a branch, and the CLI `ClientFilter`, client labels, TUI picker, and frontend source maps gain entries. The deprecated per-client boolean flags intentionally do not, since `antigravity-cli` is reachable only via the canonical `--client antigravity-cli`. Closes junhoyeo#712. * fix(sessions): handle file:// authority/UNC paths and test Antigravity CLI wiring Addresses the cubic review on junhoyeo#713. `file_uri_to_path` previously stripped `file://` and only special-cased the leading slash before a Windows drive letter, so a non-empty authority (`file://host/share/...`, the UNC form) lost its host and collapsed into a bare path. It now treats an empty-authority remainder as before (`/C:/x` → `C:/x`, `/home/x` kept) and reconstructs a non-empty authority as a UNC path (`host/share/x` → `//host/share/x`) so `normalize_workspace_key` preserves the `//` prefix. A unit test covers the Windows-drive, POSIX, UNC, and percent-encoded-CJK cases. The new `AntigravityCli` client wiring is now asserted in `test_client_as_str`, `test_client_key`, and `test_client_from_key` (display name "Antigravity CLI", hotkey `f`, and the reverse hotkey mapping). * style: rustfmt antigravity_cli.rs * fix(antigravity-cli): add gemini-3-flash-a pricing alias and harden parser tests Map the raw junhoyeo#19 responseModel `gemini-3-flash-a` onto the priced `gemini-3-flash-preview` so Antigravity CLI cost no longer resolves to 0. Add alias-resolution, junhoyeo#9/junhoyeo#10==junhoyeo#3 field-mapping invariant, and malformed-protobuf bounds tests. Constraint: must not weaken the junhoyeo#707 brand-token fuzzy-match guard in lookup.rs Confidence: high Scope-risk: narrow --------- Co-authored-by: Junho Yeo <i@junho.io>
t1000040
referenced
this pull request
in tmobi-internal/tokscale
Jun 30, 2026
junhoyeo#713) * feat(sessions): read Antigravity CLI usage from local SQLite databases The Antigravity CLI (the terminal agent that stores its data under `~/.gemini/antigravity-cli/`) was never counted. tokscale only knew two Gemini-family sources: the Gemini CLI (scans `~/.gemini/tmp/*.{json,jsonl}`) and Antigravity (pulls usage from a running IDE language server over RPC and caches it under the config dir). The Antigravity CLI fell into neither bucket, so its on-disk usage was invisible — `tokscale antigravity sync` found the filesystem candidates but cached zero because its only artifact path still requires a live language-server RPC connection. This adds Antigravity CLI as a first-class local scan source so its usage updates automatically like every other file-based source — no RPC, no `antigravity sync`. A new `antigravity-cli` client globs `~/.gemini/antigravity-cli/conversations/*.db` (honoring `GEMINI_CLI_HOME`) and a new parser reads each conversation database directly. Each `gen_metadata` row is one generation encoded as the same `GeneratorMetadata` protobuf the IDE returns over `GetCascadeTrajectoryGeneratorMetadata`. The repository has no `.proto`/prost decoder (the IDE path receives JSON because the language server does the proto-to-JSON conversion), so the parser ships a tiny dependency-free wire-format reader and pulls only the fields it needs. The field numbers were reverse-engineered from real databases and cross-checked across 6 sessions / 140 turns: `chatModel.#19` is the response model, `usage.#5`/`#9`/`#10` are cacheRead/output/thinking (verified by the invariant `#9 + #10 == #3`, the stored total output), `#11` is the responseId used for dedup, and input combines the fixed system-prompt count `#1` with the newly-processed input `#2`. The session timestamp and workspace come from `trajectory_metadata_blob`. Adding the new `ClientId` variant fans out to the usual registration points: the scanner gains a `*.db` glob arm (which naturally rejects `.db-wal`/`.db-shm` sidecars), both local-parse dispatch paths gain a branch, and the CLI `ClientFilter`, client labels, TUI picker, and frontend source maps gain entries. The deprecated per-client boolean flags intentionally do not, since `antigravity-cli` is reachable only via the canonical `--client antigravity-cli`. Closes junhoyeo#712. * fix(sessions): handle file:// authority/UNC paths and test Antigravity CLI wiring Addresses the cubic review on junhoyeo#713. `file_uri_to_path` previously stripped `file://` and only special-cased the leading slash before a Windows drive letter, so a non-empty authority (`file://host/share/...`, the UNC form) lost its host and collapsed into a bare path. It now treats an empty-authority remainder as before (`/C:/x` → `C:/x`, `/home/x` kept) and reconstructs a non-empty authority as a UNC path (`host/share/x` → `//host/share/x`) so `normalize_workspace_key` preserves the `//` prefix. A unit test covers the Windows-drive, POSIX, UNC, and percent-encoded-CJK cases. The new `AntigravityCli` client wiring is now asserted in `test_client_as_str`, `test_client_key`, and `test_client_from_key` (display name "Antigravity CLI", hotkey `f`, and the reverse hotkey mapping). * style: rustfmt antigravity_cli.rs * fix(antigravity-cli): add gemini-3-flash-a pricing alias and harden parser tests Map the raw #19 responseModel `gemini-3-flash-a` onto the priced `gemini-3-flash-preview` so Antigravity CLI cost no longer resolves to 0. Add alias-resolution, #9/#10==#3 field-mapping invariant, and malformed-protobuf bounds tests. Constraint: must not weaken the junhoyeo#707 brand-token fuzzy-match guard in lookup.rs Confidence: high Scope-risk: narrow --------- Co-authored-by: Junho Yeo <i@junho.io>
haunchen
added a commit
to haunchen/tokscale
that referenced
this pull request
Aug 5, 2026
… went unidentified `SessionModels::sole_model` served rows carrying neither `junhoyeo#19` nor `junhoyeo#21` whenever the file named exactly one model. That counted named ids, not models: a conversation where one row carries a display label no row ever identifies ran at least two models while naming only one, and an unlabelled row there could be either. It inherited the single named id and billed a model switch under the wrong model. Gate the fallback on every label in the file resolving to a machine id, so those rows stay `unknown` — the same rule the label path already followed, now applied to the unlabelled one. Also mark `junhoyeo#21` optional in the field table. It was present on every row observed so far, but the parser has never required it and the `sole_model` path exists precisely for rows that carry neither field.
junhoyeo
pushed a commit
that referenced
this pull request
Aug 6, 2026
…1051) parse_gen_metadata read the model from chatModel.#19 and fell back to the literal string "unknown", which inferred_provider_from_model cannot place, producing unpriceable antigravity/unknown rows. Antigravity CLI drops a whole group of chatModel fields (1, 2, 8, 10, 16, 19) on some continuation and tool turns, but #21 — the model display label — survives on every one, and sibling rows carry the machine id next to the identical label. Turns are now attributed by resolving that label against labels seen elsewhere in the same database. Rejected: fall back to the conversation's sole model whenever a row lacks a label | a conversation containing a model switch would bill the wrong model instead of staying unknown (cubic P1, fixed in 73d5937 by withholding the fallback when any label is unresolved) Confidence: high Scope-risk: narrow Directive: post-#1053 these rows no longer abort a submission — they are excluded silently, so a regression here is invisible rather than loud Not-tested: a database where the same display label maps to two different machine ids
junhoyeo
added a commit
that referenced
this pull request
Aug 8, 2026
…lay labels in SessionModels (#1059) * Refactor label resolution logic in antigravity_cli.rs * Fix dereferencing in antigravity_cli.rs * test(antigravity_cli): pin the aliased-label recovery and satisfy rustfmt The alias comparison landed with a 122-column line and a whitespace-only line, so `cargo fmt --all -- --check` failed the Lint gate; no build leg ran on the branch to catch it because the first-time-contributor workflow gate held the test jobs. Adds the regression test the change was missing. Two rows spell the same priced model `gemini-pro-default` and `gemini-pro-agent` under one display label, and a third row carries the label with no `#19`. Without the fix that third row resolves to `unknown` and `submit` aborts; the test asserts `gemini-3.1-pro`. Verified red-green: reverting the canonical comparison fails it with `left: "unknown"`. Also switches the fully-qualified `crate::pricing::aliases::` call to the `pricing::aliases::` form already used elsewhere in this file, and corrects the `by_display` doc comment, which still described the pre-change invariant. Constraint: display labels must never become pricing keys, so the fix compares alias targets rather than trusting the label Rejected: storing the canonical id in by_display | changes the contract that the map returns an observed #19 machine id Confidence: high Scope-risk: narrow Not-tested: three-way labels where two ids share an alias and a third does not --------- Co-authored-by: Junho Yeo <i@junho.io>
junhoyeo
pushed a commit
that referenced
this pull request
Aug 18, 2026
…1131) * fix(antigravity-cli): resolve routing-label rows from display labels Antigravity CLI writes the generic routing label `gemini-default` as `chatModel.responseModel` (#19) whenever the router picked the model, leaving the concrete tier only in the sibling `#21` display label (e.g. "Gemini 3.5 Flash (Low)"). The parser preserved the label verbatim, so those rows were excluded from `tokscale submit` as unpriced despite carrying a tier-identifying display label. Across real databases this silently dropped ~218M tokens / $74 of Gemini usage. Resolve such rows in three steps: use a concrete `#19` as before; otherwise recover the machine id from a sibling row sharing the display label (the existing `SessionModels` mechanism); otherwise map the display label itself via a verified lookup table. A routing label is never treated as evidence of a concrete model, so it no longer poisons the `by_display` ambiguity check when a real sibling id exists. Rows that still resolve to nothing keep `gemini-default` verbatim, so the submission-time exclusion (`is_generic_routing_label` in lib.rs) continues to apply rather than guessing. Display labels are server-supplied and could be renamed or localized, so only labels verified against real user databases are mapped; anything unknown returns None and the routing label is preserved. Fixes #1116. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> * fix(antigravity-cli): map "Gemini 3.5 Flash (Low)" to gemini-3.5-flash-extra-low Align the display-label mapping with the existing alias table (`model_placeholder_m187` → `gemini-3.5-flash-extra-low` in `aliases.rs:85`) so IDE and CLI Low-tier usage resolves to the same model id. The previous value (`gemini-3.5-flash`) split the tier across two model rows. Prices are identical today, but per-tier rows would diverge — the exact scenario `aliases.rs:76-79` was written to guard against. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Summary
This PR improves the TUI DateBreakdownPanel, extracts shared components for consistency, fixes reactivity bugs, updates branding, and refactors for better code organization and type safety.
Changes
🐛 Bug Fixes
borderStyle="round"which crashed OpenTUI on dynamically rendered componentsgetCellStylereturned a plain object inside<For>callback, which SolidJS didn't track reactivelyisSelected()andgetCellColor()functions called directly in JSX for proper reactivityT00:00:00suffix to date strings to prevent off-by-one day/month bugs in negative UTC timezones♻️ Refactoring
ModelRowcomponent: Unified model display with colored dot, name, percentage, and token breakdownTokenBreakdowncomponent: Consistent In/Out/CR/CW display across viewsDateBreakdownPanelto separate file: Moved from StatsView.tsx to its own file (72 lines)SOURCE_COLORSto colors.ts: Centralized source color definitions with type-safeRecord<SourceType, string>getSourceColor()helper: Clean API for getting source colors with fallbackonlyNonZero,showCostInline,shortLabelsoptions for simpler APIdebugInfosignal, debug UI display, and debug comments from StatsView✨ Features
open), Linux (xdg-open), and Windows (start)🔒 Type Safety Improvements
SOURCE_COLORSnow typed asRecord<SourceType, string>for compile-time exhaustivenessgetSourceColor()acceptsSourceType | stringfor flexibility while maintaining type safetyFiles Changed
packages/cli/src/tui/components/Header.tsx- Branding and GitHub linkpackages/cli/src/tui/components/StatsView.tsx- Reduced 316→225 lines, debug removed, shared componentspackages/cli/src/tui/components/DateBreakdownPanel.tsx- New file (extracted component)packages/cli/src/tui/components/OverviewView.tsx- Use shared ModelRow componentpackages/cli/src/tui/components/ModelRow.tsx- New shared componentpackages/cli/src/tui/components/TokenBreakdown.tsx- New shared componentpackages/cli/src/tui/components/index.ts- Export new componentspackages/cli/src/tui/utils/colors.ts- Added SOURCE_COLORS and getSourceColor()Testing
npx tsc --noEmit)