Skip to content

feat(tui): add profile view toggle to Hourly tab#395

Open
christian-taillon wants to merge 19 commits intojunhoyeo:mainfrom
christian-taillon:feat/hourly-profile-view
Open

feat(tui): add profile view toggle to Hourly tab#395
christian-taillon wants to merge 19 commits intojunhoyeo:mainfrom
christian-taillon:feat/hourly-profile-view

Conversation

@christian-taillon
Copy link
Copy Markdown

@christian-taillon christian-taillon commented Apr 3, 2026

Summary

Adds a profile view toggle to the Hourly tab in Tokscale TUI, allowing users to switch between a detailed table view and an aggregated profile view showing usage patterns by time-of-day and day-of-week. Also includes several enhancements to the hourly report feature.

Changes

Core Feature: Profile View Toggle

  • Add HourlyViewMode enum (Table/Profile) with 'v' key to toggle views
  • Profile view displays:
    • Time-of-day breakdown (Morning/Daytime/Evening/Night)
    • Weekday breakdown (Mon-Sun)
    • Peak hour insight with tokens and cost
    • Visual bar charts scaled to terminal width

Profile View Enhancements

  • Dynamic bar widths that fill available horizontal space (20-80 chars)
  • Responsive layout adapts to terminal width
  • Period definitions:
    • Morning: 05:00-11:59
    • Daytime: 12:00-16:59
    • Evening: 17:00-21:59
    • Night: 22:00-04:59

Additional Hourly Report Improvements

  • Add Turn and Message count columns to hourly/daily views
  • Add Cost/1M column to models report table
  • Add cache hit ratio display with Cache× notation
  • Fix client/model sorting determinism
  • Add --kilo flag support for KiLo client

Testing

  • All 367 tests pass
  • Tested manually on various terminal widths
  • Profile view correctly shows usage patterns
  • Toggle between views works smoothly with 'v' key

How to Use

  1. Navigate to the Hourly tab in TUI
  2. Press 'v' to toggle between Table and Profile views
  3. Profile view shows aggregated usage patterns
  4. Press 'v' again to return to table view

Summary by cubic

Adds a profile view toggle to the TUI Hourly tab so you can switch between the table and an aggregated profile of usage by time-of-day and weekday. Also ships hour-level reporting across core, CLI, and TUI, plus cache efficiency and turn/message insights.

  • New Features

    • TUI Hourly: press 'v' to switch Table/Profile; profile shows time-of-day, weekday, and peak hour with bars that scale to terminal width.
    • Hourly report end-to-end: core hour buckets, CLI hourly subcommand with filters/JSON and a Source column, and a new TUI tab.
    • Cache efficiency: show "Cache×" = cache_read / (input + cache_write) in hourly, daily, and models tables.
    • Engagement metrics: add Turn and Msgs columns; detect turn boundaries from Claude Code logs.
    • Overview chart: press 'h' to toggle between daily and hourly granularity.
    • Models table: add Cost/1M for quick efficiency comparison.
  • Bug Fixes

    • Deterministic ordering for hourly clients and models.
    • Keep sort selection on the Hourly tab; only reset when switching tabs.
    • Rename "Cache%" to "Cache×" to reflect a multiplier, not a percentage.
    • CLI: add missing flags to hourly (--kilo, --crush, --home); make --light a no-op; fix report options.
    • Back-compat for cached messages: add serde default for is_turn_start.
    • Correct profile summary label to show hours (not days).

Written for commit 2802c9c. Summary will update on new commits.

@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Apr 3, 2026

@christian-taillon is attempting to deploy a commit to the Inevitable Team on Vercel.

A member of the Team first needs to authorize it.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

5 issues found across 18 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/tokscale-core/src/sessions/mod.rs">

<violation number="1" location="crates/tokscale-core/src/sessions/mod.rs:39">
P2: Adding a required field to `UnifiedMessage` breaks deserialization of previously cached data (bincode-serialized `CachedSourceStore` includes `Vec<UnifiedMessage>`). Without a default or schema bump, older cache files become unreadable after this change.</violation>
</file>

<file name="crates/tokscale-cli/src/tui/ui/hourly_profile.rs">

<violation number="1" location="crates/tokscale-cli/src/tui/ui/hourly_profile.rs:77">
P2: `hourly.len()` counts hourly buckets, but the UI labels it as days. This will overstate the day count (e.g., 48 hours shown as 48 days). Consider using unique date count or change the label to hours.</violation>
</file>

<file name="crates/tokscale-cli/src/tui/cache.rs">

<violation number="1" location="crates/tokscale-cli/src/tui/cache.rs:57">
P2: Cache schema was expanded but schema version was not bumped, allowing old cache files to be treated as fresh and suppressing reload of new hourly/count fields.</violation>
</file>

<file name="crates/tokscale-cli/src/tui/app.rs">

<violation number="1" location="crates/tokscale-cli/src/tui/app.rs:418">
P2: apply_tab_sort_defaults sets Date sorting only for the Hourly tab and never resets cost sorting when leaving it. Because the function runs on every tab switch, visiting Hourly leaves `sort_field` as Date for Models/Agents/Daily, which changes those tabs’ ordering away from the intended cost sort (the comment says “other tabs keep cost sort”).</violation>
</file>

<file name="crates/tokscale-cli/src/main.rs">

<violation number="1" location="crates/tokscale-cli/src/main.rs:2083">
P2: The new Hourly subcommand defines `--light` and passes `json || light` into `run_hourly_report`, but the function ignores `_light_or_json` and the handler always runs the CLI report. This makes `--light` ineffective and prevents `tokscale hourly` from falling back to the TUI tab when available.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

agents: Vec<CachedAgentUsage>,
daily: Vec<CachedDailyUsage>,
#[serde(default)]
hourly: Vec<CachedHourlyUsage>,
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2: Cache schema was expanded but schema version was not bumped, allowing old cache files to be treated as fresh and suppressing reload of new hourly/count fields.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At crates/tokscale-cli/src/tui/cache.rs, line 57:

<comment>Cache schema was expanded but schema version was not bumped, allowing old cache files to be treated as fresh and suppressing reload of new hourly/count fields.</comment>

<file context>
@@ -53,6 +53,8 @@ struct CachedUsageData {
     agents: Vec<CachedAgentUsage>,
     daily: Vec<CachedDailyUsage>,
+    #[serde(default)]
+    hourly: Vec<CachedHourlyUsage>,
     graph: Option<CachedGraphData>,
     total_tokens: u64,
</file context>
Fix with Cubic

crhan and others added 13 commits April 3, 2026 10:00
…oggle

Support hour-granularity token consumption tracking:
- Core: HourlyUsage/HourlyReport structs, get_hourly_report() with local
  timezone support (derives hour slot from UnifiedMessage.timestamp)
- CLI: `tokscale hourly` subcommand with full client/date filters,
  table and JSON output, Source column showing which tool was used
- TUI: dedicated Hourly tab (sort, scroll, striped rows, current-hour
  highlight) mirroring Daily tab patterns
- Overview: press 'h' to toggle bar chart between daily/hourly granularity

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
- Add "Cache%" column to hourly table showing cache hit multiplier
- Calculate cache efficiency as cache_read / (input + cache_write)
- Display "∞" for infinite ratio (cache reads, zero paid input)
- Display "—" for no cache activity
- Update cache calculation logic in app state
- Enhance hourly and daily UI models with ratio formatting

Co-Authored-By: Claude Haiku 4.5 <noreply@anthropic.com>
Distinguish genuine human input from API message count (Msgs).
Detect user→assistant boundaries in Claude Code JSONL, filtering out
tool_result and system messages that also use type:"user".

Turn/Msgs ratio reveals agent depth per interaction.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Propagate turn_count and message_count through TUI data structs,
cache layer, and renderers (both narrow and full-width layouts).
Also add turnCount/messageCount to daily JSON export.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
…rly tab

The Hourly subcommand was missing the --kilo CLI arg (added upstream in
junhoyeo#353) and omitted kilo from its ClientFlags initializer. Add both.

Also fix the kilocode help text in the Hourly command (was "Show only Kilo
usage", should match other commands: "Show only KiloCode usage").

Update TUI tab tests to reflect the new Hourly tab inserted between Daily
and Stats: test_tab_all now expects 6 tabs, and tab_next/tab_prev/backtab
key-switch tests include the Hourly→Stats and Stats→Hourly transitions.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…stence on Hourly tab

Two bugs identified in code review:

1. lib.rs: HourlyUsage.clients and .models were populated via HashSet
   iteration, producing non-deterministic ordering in JSON output and
   display. Sort both vecs before returning.

2. tui/app.rs: set_sort() called reset_selection() which forced
   sort_field back to Date/Descending whenever the user pressed c/t on
   the Hourly tab. Extract the per-tab sort default into
   apply_tab_sort_defaults() and call it only from tab-switch handlers
   (Tab, BackTab, Left, Right), not from reset_selection. This lets
   Cost and Tokens sorting persist while the user is on the Hourly tab.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Display cost per million tokens alongside absolute cost in all models
report table variants (model-only, client,model, client,provider,model).
Helps compare model efficiency at a glance — higher token usage with
lower Cost/1M means better cache utilization or a cheaper model.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
- Add HourlyViewMode enum (Table/Profile) with 'v' key to toggle
- Create hourly_profile.rs with time-of-day and weekday breakdowns
- Add aggregation helpers: aggregate_by_period, aggregate_by_weekday, find_peak_hour
- Update footer to show [v:profile] hint on Hourly tab
- Add unit tests for HourlyViewMode toggle behavior
Remove total_cost fields from PeriodBucket and WeekdayBucket
that were calculated but never displayed in profile view.
- Wide terminals (>100 chars): 30-char bars
- Medium terminals (80-100 chars): 24-char bars
- Narrow terminals (<80 chars): 18-char bars
- Consistent bar width for both period and weekday sections
Calculate bar width dynamically based on terminal width:
- Bar width = inner.width - 36 (overhead for labels, etc.)
- Clamp between 20-80 chars for reasonable display
- Uses most of the available horizontal space
@christian-taillon christian-taillon force-pushed the feat/hourly-profile-view branch from b5a1b6f to e81a82b Compare April 3, 2026 17:02
@christian-taillon
Copy link
Copy Markdown
Author

Dependency Note

This PR builds on top of #359 (feat: add hourly usage report). That PR should be merged first.

I've tested #359 extensively and experienced no bugs - any issues I reported earlier were false positives. The hourly tab foundation is solid and ready to merge.


Testing Summary for #359:

  • ✅ Hourly tab displays correctly
  • ✅ Data loads and aggregates properly
  • ✅ Sorting and navigation work as expected
  • ✅ Cache persistence works across sessions
  • ✅ No crashes or memory issues observed

Older cached message entries without is_turn_start will now deserialize
safely with the field defaulting to false, ensuring backward compatibility
with existing caches.
The summary line was incorrectly showing the count of hourly data points
as days. Changed to hours to accurately reflect the data being displayed.
…ht handling

- Add missing --crush flag to Hourly command definition and handler
- Add missing use_env_roots field to ReportOptions in run_hourly_report
- Remove unused _light_or_json parameter from run_hourly_report
- Ignore --light flag in Hourly command (was misleading, now a no-op)

The --light flag for Hourly was being passed but never used, creating
misleading behavior. It now behaves consistently with the actual implementation.
@christian-taillon
Copy link
Copy Markdown
Author

Review Outcome Summary (cubic findings)

All findings from @cubic have been assessed and addressed:

Fixed

  • Cache compatibility: Added #[serde(default)] to is_turn_start field in UnifiedMessage for backward-compatible deserialization (crates/tokscale-core/src/sessions/mod.rs)
  • Hourly profile label: Corrected summary label from "days" to "hours" (crates/tokscale-cli/src/tui/ui/hourly_profile.rs)
  • Hourly CLI cleanup: Removed unused _light_or_json parameter and clarified --light as a no-op; also fixed pre-existing bugs: added missing crush flag and use_env_roots: true to Hourly command (crates/tokscale-cli/src/main.rs)

False Positive

  • TUI JSON cache schema-version: The JSON cache uses #[serde(default)] for newly added fields, making schema evolution backward-compatible without explicit version handling.

Already Fixed

  • Hourly sort persistence: Already addressed in commit c4d8ef0; no regression found.

Validation

cargo test -p tokscale-core && cargo test -p tokscale-cli

Result: 470 passed (core), 74 passed (cli)

All changes are minimal and scoped to the reported findings.

@cubic-dev-ai
Copy link
Copy Markdown
Contributor

cubic-dev-ai bot commented Apr 3, 2026

Review Outcome Summary (cubic findings)

All findings from @cubic have been assessed and addressed:

Fixed

...

@christian-taillon The fixes for cache compatibility, labels, and the CLI signature look solid and address the concerns accurately.

Regarding the TUI JSON cache versioning: I suggested versioning because #[serde(default)] only protects you during field additions. If you eventually rename a field or change a type, serde will simply silently fill the new field with a default (like 0 or false) while ignoring the old key, which can lead to confusing "empty" states in the UI until the user manually refreshes. However, for adding hourly data and the is_turn_start flag, your current approach is perfectly fine for this scope.

On sort persistence: I see the logic in apply_tab_sort_defaults now. By moving the sort reset into the tab-switching handlers and out of reset_selection, you've ensured that the user's chosen sort (e.g., sorting the Hourly tab by Cost) persists even as they scroll or interact. I likely missed that this was already being addressed in the same batch of changes.

One small detail in crates/tokscale-cli/src/main.rs: adding the missing --crush flag and setting use_env_roots: true brings the hourly command into parity with the daily and models reports, which is great for consistency.

These updates effectively resolve the issues I flagged.

For more details on how I handle memory and learnings, you can check out the Cubic documentation on learnings.

Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 4 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="crates/tokscale-cli/src/main.rs">

<violation number="1" location="crates/tokscale-cli/src/main.rs:2359">
P1: Hourly report ignores `--home` because report options are hardcoded to `home_dir: None` and env roots.</violation>
</file>

Reply with feedback, questions, or to request a fix. Tag @cubic-dev-ai to re-run a review.

@christian-taillon
Copy link
Copy Markdown
Author

Good catch! The --home flag was indeed being ignored in the Hourly report. The fix has been applied in commit 2802c9c to match how Monthly handles the --home flag - accepting home_dir as a parameter, computing use_env_roots from it, and passing both to ReportOptions.

@christian-taillon
Copy link
Copy Markdown
Author

Maintainers, I want to be clear, I built off #395 which I think is a good feature.

Happy to do a round or two of rebasing for other PRs - I do reccomend @crhan's PR be merged. Happy to do another round of conflict resolution - I do intend to use this feature long term so please let me know if you'd like changes pre-merge.

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