Skip to content

feat(tui): Add Monthly tab for model-by-month usage tracking#354

Open
kang-heewon wants to merge 5 commits intojunhoyeo:mainfrom
kang-heewon:add-monthly-tab
Open

feat(tui): Add Monthly tab for model-by-month usage tracking#354
kang-heewon wants to merge 5 commits intojunhoyeo:mainfrom
kang-heewon:add-monthly-tab

Conversation

@kang-heewon
Copy link
Copy Markdown

@kang-heewon kang-heewon commented Mar 23, 2026

Summary

  • TUI์— Monthly ํƒญ์„ ์ถ”๊ฐ€ํ•˜์—ฌ ์›” ร— ๋ชจ๋ธ ๊ต์ฐจ ํ…Œ์ด๋ธ” ํ‘œ์‹œ
  • ์ „์ฒด ๊ธฐ๊ฐ„ ๊ธฐ์ค€ ์›”ํ‰๊ท  ๋น„์šฉ/ํ† ํฐ ๊ณ„์‚ฐ (ํ…Œ์ด๋ธ” ํ•˜๋‹จ AVG/MO ํ–‰)
  • ์ •๋ ฌ(d/t/c), ์Šคํฌ๋กค, ๋ฐ˜์‘ํ˜• ์ปฌ๋Ÿผ ์ง€์›
  • tokscale monthly CLI โ†’ Monthly ํƒญ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ

Changes

  • MonthlyModelUsage struct ๋ฐ monthly_models/total_months ํ•„๋“œ ์ถ”๊ฐ€
  • aggregate_messages()์— monthly ์ง‘๊ณ„ ๋กœ์ง ์ถ”๊ฐ€ (GroupBy ์ง€์›)
  • Tab::Monthly variant ๋ฐ get_sorted_monthly() ํ•จ์ˆ˜ ์ถ”๊ฐ€
  • monthly.rs UI ๋ชจ๋“ˆ ์ƒ์„ฑ (daily.rs ํŒจํ„ด ๊ธฐ๋ฐ˜)
  • UI dispatcher/footer์— Monthly ํ†ตํ•ฉ
  • ๊ด€๋ จ ํ…Œ์ŠคํŠธ ์—…๋ฐ์ดํŠธ

Test plan

  • cargo clippy --package tokscale-cli ๊ฒฝ๊ณ  ์—†์Œ
  • cargo build --package tokscale-cli ์„ฑ๊ณต
  • cargo test --package tokscale-cli Tab ๊ด€๋ จ ํ…Œ์ŠคํŠธ ํ†ต๊ณผ
  • TUI ์‹คํ–‰ ์‹œ 6๊ฐœ ํƒญ ํ‘œ์‹œ ํ™•์ธ
  • Monthly ํƒญ์—์„œ ๋ฐ์ดํ„ฐ ๋ Œ๋”๋ง ํ™•์ธ

Notes

  • 3๊ฐœ ๊ธฐ์กด ํ…Œ์ŠคํŠธ(test_data_loader_*, test_handle_key_increase_decrease_refresh)๋Š” main ๋ธŒ๋žœ์น˜์—์„œ๋„ ์‹คํŒจํ•˜๋Š” ๊ธฐ์กด ์ด์Šˆ์ž…๋‹ˆ๋‹ค.

Summary by cubic

Adds a Monthly tab to the TUI for model-by-month usage tracking with sorting, scrolling, and an AVG/MO row. Monthly data is now aggregated during load, and tokscale monthly opens this tab.

  • New Features
    • Monthly aggregation added to DataLoader with MonthlyModelUsage, plus monthly_models and total_months.
    • New Tab::Monthly with Date/Cost/Tokens sorting, selection, scroll, and footer count.
    • New monthly UI: responsive columns (Month/Model/Provider/Client/Tokens/Cost), current month highlight, AVG/MO row, scrollbar, empty state.
    • CLI: tokscale monthly now redirects to the Monthly tab.

Written for commit 81c06eb. Summary will update on new commits.

- Add MonthlyModelUsage struct with month, model, provider, client, tokens, cost, message_count
- Add monthly_models and total_months fields to UsageData
- Implement monthly aggregation in aggregate_messages() with GroupBy support
- Sort monthly_models by month desc, cost desc, model asc
- Add Monthly variant to Tab enum (between Daily and Stats)
- Update Tab::all(), as_str(), short_name(), next(), prev()
- Add get_sorted_monthly() with Date/Cost/Tokens sorting
- Add Monthly branch to get_current_list_len()
- Add monthly module stub to ui/mod.rs
- Import MonthlyModelUsage in app.rs
- Full Month ร— Model table with responsive columns
- Provider/Client columns in full width mode
- Sort indicator (โ–ฒ/โ–ผ) for Date/Cost/Tokens
- Current month highlight (Yellow + Bold)
- Monthly average row at bottom (AVG/MO)
- Scrollbar and selection/striped styling
- Empty state message
- Add Monthly count label to footer (n entries)
- Change tokscale monthly CLI to open Monthly tab instead of Daily
- Update test_tab_all: expect 6 tabs with Monthly
- Update test_tab_next/prev: include Monthly navigation
- Update test_tab_as_str/short_name: add Monthly assertions
- Update test_handle_key_tab_switch/backtab_switch: full tab cycle
@vercel
Copy link
Copy Markdown
Contributor

vercel bot commented Mar 23, 2026

@kang-heewon 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.

2 issues found across 7 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-cli/src/tui/cache.rs">

<violation number="1" location="crates/tokscale-cli/src/tui/cache.rs:335">
P2: Monthly usage data is never serialized/deserialized in the cache; the new fields are always reset to empty/zero, so cached loads render an empty Monthly tab.</violation>
</file>

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

<violation number="1" location="crates/tokscale-cli/src/tui/ui/monthly.rs:259">
P3: Scrollbar is drawn over the full inner block (`area.inner(...)`) even when the AVG/MO footer row is reserved, so it overlaps/mismatches the footer; it should use `table_area` for the scrollbar height.</violation>
</file>

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

models: u.models.into_iter().map(|m| m.into()).collect(),
agents: u.agents.into_iter().map(|a| a.into()).collect(),
daily: daily?,
monthly_models: Vec::new(),
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 23, 2026

Choose a reason for hiding this comment

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

P2: Monthly usage data is never serialized/deserialized in the cache; the new fields are always reset to empty/zero, so cached loads render an empty Monthly tab.

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 335:

<comment>Monthly usage data is never serialized/deserialized in the cache; the new fields are always reset to empty/zero, so cached loads render an empty Monthly tab.</comment>

<file context>
@@ -332,6 +332,8 @@ impl TryFrom<CachedUsageData> for UsageData {
             models: u.models.into_iter().map(|m| m.into()).collect(),
             agents: u.agents.into_iter().map(|a| a.into()).collect(),
             daily: daily?,
+            monthly_models: Vec::new(),
+            total_months: 0,
             graph: graph.transpose()?,
</file context>
Fix with Cubic


frame.render_stateful_widget(
scrollbar,
area.inner(Margin {
Copy link
Copy Markdown
Contributor

@cubic-dev-ai cubic-dev-ai bot Mar 23, 2026

Choose a reason for hiding this comment

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

P3: Scrollbar is drawn over the full inner block (area.inner(...)) even when the AVG/MO footer row is reserved, so it overlaps/mismatches the footer; it should use table_area for the scrollbar height.

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/ui/monthly.rs, line 259:

<comment>Scrollbar is drawn over the full inner block (`area.inner(...)`) even when the AVG/MO footer row is reserved, so it overlaps/mismatches the footer; it should use `table_area` for the scrollbar height.</comment>

<file context>
@@ -0,0 +1,282 @@
+
+        frame.render_stateful_widget(
+            scrollbar,
+            area.inner(Margin {
+                horizontal: 0,
+                vertical: 1,
</file context>
Suggested change
area.inner(Margin {
table_area.inner(Margin {
horizontal: 0,
vertical: 1,
}),
Fix with Cubic

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.

1 participant