Skip to content

Add monthly view to TUI#133

Open
Sewer56 wants to merge 1 commit intoPiebald-AI:mainfrom
Sewer56:feature/monthly-view-toggle
Open

Add monthly view to TUI#133
Sewer56 wants to merge 1 commit intoPiebald-AI:mainfrom
Sewer56:feature/monthly-view-toggle

Conversation

@Sewer56
Copy link
Contributor

@Sewer56 Sewer56 commented Mar 15, 2026

Changes

Press m in the TUI to toggle between daily and monthly stats. The monthly view rolls up all your daily usage into per-month totals, making it easier to see longer-term trends.

image

Notes

This works, but it's not perfect.
The changes could be a bit cleaner; but that'd require a larger refactor.
I wanted a quick view personally; and felt like throwing away the code would be a waste, so here it is.

Use it, or do anything else with it :p

Refactor needed: Notably src/tui.rs has grown too large (~500 lines of changes in this PR alone). It should be split into smaller modules (e.g., separate files for rendering, state management, input handling).

Summary by CodeRabbit

  • New Features

    • Added daily and monthly aggregate view modes for the TUI statistics display
    • Enhanced date filtering with improved pattern matching for date inputs
    • Added upload progress visualization and tracking
  • Tests

    • Expanded test coverage for aggregation functionality and date filtering

Allow the stats table to switch between daily and monthly periods with a hotkey so broader usage trends are easier to inspect without leaving the dashboard.
@coderabbitai
Copy link

coderabbitai bot commented Mar 15, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5854ef36-2536-480d-b56d-a99bdc904698

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • ✅ Review completed - (🔄 Check again to review again)
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
📝 Coding Plan
  • Generate coding plan for human review comments

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@Sewer56 Sewer56 marked this pull request as ready for review March 15, 2026 16:17
Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🧹 Nitpick comments (1)
src/tui.rs (1)

110-120: Consider caching monthly aggregates per refresh.

get_aggregate_stats() rebuilds the rolled-up BTreeMap every call. In monthly mode this helper is hit by navigation, date jump, and draw paths, so one keypress can re-aggregate the same daily_stats several times.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tui.rs` around lines 110 - 120, get_aggregate_stats currently recomputes
the monthly rollup every call; to fix, add a cache that stores the result of
aggregate_daily_stats_by_month(&view.daily_stats) and only rebuild it when
daily_stats changes (e.g., on a refresh/update). Concretely: add a
cached_monthly field (Option<...>) to the owner of AnalyzerStatsView or
AnalyzerStatsView itself, set/invalidate that cache whenever daily_stats is
updated/refreshed, update get_aggregate_stats to return
AggregateStatsData::Owned from the cached_monthly when
AggregateViewMode::Monthly (computing and storing it on first use if absent),
and ensure cache invalidation happens on the same code paths that mutate or
reload view.daily_stats so navigation/draw calls reuse the cached monthly
BTreeMap.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@src/tui.rs`:
- Around line 1205-1207: The clamp and metadata must be computed from the
actually rendered subset rather than the full map: change the code so
get_aggregate_stats/aggregate_stats.as_map() is invoked on the same
filtered/reordered collection used to render the table (the rendered_rows /
rendered_aggregate_map), then call clamp_table_selection(table_state,
rendered_aggregate_map.len() + 2); also update the routines that derive best-row
indices, totals labels and model metadata (the code paths that currently read
from the unfiltered order) to read from that same rendered_aggregate_map so
reversed/filtered views and search jumps show correct max markers, selected row
and Total (...m) label.

In `@src/tui/logic.rs`:
- Around line 84-96: The single-number branch currently treats any parsed number
>31 as year and 1..=12 as month but lets 13..=31 fall-through to the substring
match, causing "/20" or "/25" to match year substrings; update the logic in the
parts[0] parse block so that: if number > 31 return day_year == number; else if
(13..=31).contains(&number) return a comparison against the day-of-month field
(use the existing day-of-month variable or parse it from day); else if
(1..=12).contains(&number) return day_month == number; otherwise fall back to
return day.contains(trimmed); ensure parts[0], number, day_year, day_month and
the day substring match are the referenced symbols to locate where to change.

---

Nitpick comments:
In `@src/tui.rs`:
- Around line 110-120: get_aggregate_stats currently recomputes the monthly
rollup every call; to fix, add a cache that stores the result of
aggregate_daily_stats_by_month(&view.daily_stats) and only rebuild it when
daily_stats changes (e.g., on a refresh/update). Concretely: add a
cached_monthly field (Option<...>) to the owner of AnalyzerStatsView or
AnalyzerStatsView itself, set/invalidate that cache whenever daily_stats is
updated/refreshed, update get_aggregate_stats to return
AggregateStatsData::Owned from the cached_monthly when
AggregateViewMode::Monthly (computing and storing it on first use if absent),
and ensure cache invalidation happens on the same code paths that mutate or
reload view.daily_stats so navigation/draw calls reuse the cached monthly
BTreeMap.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 61154f04-2114-48a6-87a9-bc659339419e

📥 Commits

Reviewing files that changed from the base of the PR and between 6610d71 and 1db144d.

📒 Files selected for processing (3)
  • src/tui.rs
  • src/tui/logic.rs
  • src/tui/tests.rs

Comment on lines +84 to 96
// Single number - prefer month matching, but allow year-only lookups too.
if let Ok(number) = parts[0].parse::<u32>() {
if number > 31 {
return day_year == number;
}

if (1..=12).contains(&number) {
return day_month == number;
}
}

// Otherwise match if the date contains this string
return day.contains(trimmed);
Copy link

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Handle single-number day jumps explicitly.

Line 84 treats a lone number as month-or-year, but values 13..=31 fall through to the substring match on Line 96. On 20xx data, /20 or /25 will match the year portion of many rows instead of the day-of-month, so jump-to-date lands on the wrong entry.

💡 Proposed fix
     if parts.len() == 1 {
         // Single number - prefer month matching, but allow year-only lookups too.
         if let Ok(number) = parts[0].parse::<u32>() {
             if number > 31 {
                 return day_year == number;
             }

             if (1..=12).contains(&number) {
                 return day_month == number;
             }
+
+            return day_number
+                .map(|actual_day| actual_day == number)
+                .unwrap_or(false);
         }

         // Otherwise match if the date contains this string
         return day.contains(trimmed);
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@src/tui/logic.rs` around lines 84 - 96, The single-number branch currently
treats any parsed number >31 as year and 1..=12 as month but lets 13..=31
fall-through to the substring match, causing "/20" or "/25" to match year
substrings; update the logic in the parts[0] parse block so that: if number > 31
return day_year == number; else if (13..=31).contains(&number) return a
comparison against the day-of-month field (use the existing day-of-month
variable or parse it from day); else if (1..=12).contains(&number) return
day_month == number; otherwise fall back to return day.contains(trimmed); ensure
parts[0], number, day_year, day_month and the day substring match are the
referenced symbols to locate where to change.

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