Skip to content

Fix buzz view/next rate display bugs (#257, #259, #260)#282

Merged
narthur merged 7 commits into
mainfrom
fix/view-next-rate-bugs
Jun 6, 2026
Merged

Fix buzz view/next rate display bugs (#257, #259, #260)#282
narthur merged 7 commits into
mainfrom
fix/view-next-rate-bugs

Conversation

@narthur
Copy link
Copy Markdown
Collaborator

@narthur narthur commented Jun 4, 2026

Three small, independent fixes to buzz view / buzz next, one commit each.

#260 — round goal rate to a readable precision

buzz view dumped the API's full-precision rate (0.21317778888888886 hours / day). A new formatRateValue helper rounds to 4 decimals, trims trailing zeros, and avoids scientific notation so large whole-number rates (e.g. 100000) stay readable.

#259 — show current rate alongside end rate

The Rate line only showed the goal's end rate (the road's final segment). Now captures the API's rcur and, when it differs from the end rate, shows both:

Rate:        0 hours / day (current), 0.21 (end)

Flat roads (current == end) still show a single rate, no redundant split.

#257buzz next skips overdue goals

buzz next would surface an already-overdue goal and print OVERDUE instead of a countdown. New filterOutOverdue helper (applied after the existing completed-goal filter) makes next point at the soonest goal that still has time left.

Tests

  • New rounding cases in TestFormatRate
  • TestReviewModelViewWithCurrentAndEndRate + TestReviewModelViewWithEqualCurrentAndEndRate
  • TestFilterOutOverdue

All pass; gofmt/go vet clean. Each commit builds independently. (The pre-existing TestParseDuration overflow failure is unrelated — it's open bug #271.)

Closes #257
Closes #259
Closes #260

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features

    • App now captures legacy/current rate values from the API and displays them alongside target rates when different.
  • Bug Fixes

    • Overdue goals are excluded from the "Next" view so only active upcoming deadlines appear.
    • Rate numbers render as clean fixed decimals (no scientific notation, normalizes -0).
    • Next goal timing is consistently computed to avoid flapping between views.
  • Tests

    • Added unit and view tests for overdue filtering and rate-display scenarios.

narthur and others added 3 commits June 3, 2026 21:02
The Beeminder API returns rates at full float precision (e.g.
0.21317778888888886), which `buzz view` dumped verbatim via %g. Add a
formatRateValue helper that rounds to 4 decimal places, trims trailing
zeros, and avoids scientific notation so large whole-number rates stay
readable.

Closes #260

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`buzz view`/`review` showed only the goal's end rate (the final road
segment). Capture the API's `rcur` (current rate) and, when it differs
from the end rate, display both — e.g. "0 hours / day (current), 0.21
(end)". Flat roads where the rates match still show a single rate.

Closes #259

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
`buzz next` surfaced the most urgent goal even when it was already
overdue, printing "OVERDUE" instead of a countdown. Add filterOutOverdue
and apply it (after the completed-goal filter) so `next` points at the
soonest goal that still has time left.

Closes #257

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings June 4, 2026 02:03
@github-actions
Copy link
Copy Markdown

github-actions Bot commented Jun 4, 2026

🚀 Pre-release Build Ready

Test builds are ready! Install directly using the bin command:

# Install the pre-release
bin install https://github.com/PinePeakDigital/buzz/releases/tag/pr-282-latest buzz-pr-282
# Run the pre-release
buzz-pr-282
# Uninstall the pre-release
bin remove buzz-pr-282

Direct Download Links

Or download binaries directly from the pre-release page:

💡 No GitHub login required for downloads!

🗑️ This pre-release will be automatically deleted when the PR is closed.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

This PR addresses three UX/logic bugs in buzz view / buzz next related to rate display precision, showing current vs end rate, and skipping already-overdue goals when selecting the “next” goal.

Changes:

  • Add rate formatting to round to a readable precision and avoid scientific notation in buzz view.
  • Display both current rate (rcur) and end rate (rate) when they differ.
  • Filter out overdue goals in buzz next so it points at the soonest goal that still has time remaining.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
review.go Adds formatRateValue and updates rate rendering to support readable rounding and current vs end rate display.
review_test.go Adds tests for rate rounding and for current/end rate display behavior.
next.go Applies an overdue-goal filter in buzz next after completed-goal filtering.
beeminder.go Extends Goal with rcur and adds filterOutOverdue helper.
beeminder_test.go Adds unit test coverage for filterOutOverdue.

Comment thread review.go
Comment thread review.go
Comment thread beeminder.go
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository: PinePeakDigital/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: a7b44ea8-2f0a-488b-a772-dccdaec36fbe

📥 Commits

Reviewing files that changed from the base of the PR and between f45b0f4 and 73d8b62.

📒 Files selected for processing (3)
  • beeminder_test.go
  • next.go
  • review_test.go

📝 Walkthrough

Walkthrough

Adds legacy Rcur to Goal and a filterOutOverdue(goals, now) helper; displayNextGoal snapshots time and uses the filter; rate formatting now rounds/fixes "-0" and view output can show current + end rates when they differ; tests cover filtering and rate-display cases.

Changes

Overdue filtering and rate display improvements

Layer / File(s) Summary
Goal model with current rate and overdue filtering
beeminder.go, beeminder_test.go
Goal adds Rcur *float64 and imports time; filterOutOverdue(goals []Goal, now time.Time) filters out goals with Losedate < now; test TestFilterOutOverdue validates behavior and ordering.
Exclude overdue goals from next display
next.go
displayNextGoal snapshots now := time.Now(), calls filterOutOverdue(goals, now) after removing completed goals, and formats the selected goal with FormatGoalDueDateAt(nextGoal, now).
Rate formatting and dual-rate display
review.go, review_test.go
Adds math/strconv imports, introduces formatRateValue with capped decimal precision and non-scientific output; formatRate/formatGoalDetails use it to render current and end rates (showing both when different); tests updated/added for rounding, zero, and legacy Rcur cases.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • PinePeakDigital/buzz#153: Both PRs modify review.go rate rendering to expose current-rate information in goal view output.
🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 60.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title clearly identifies the three specific issues being fixed (#257, #259, #260) and describes the main change as 'Fix buzz view/next rate display bugs', which directly aligns with the changeset.
Linked Issues check ✅ Passed All three linked issue objectives are met: #257 adds filterOutOverdue to skip overdue goals in 'buzz next', #259 shows both current and end rates in 'buzz view' when they differ, and #260 implements formatRateValue to round rates to readable precision without scientific notation.
Out of Scope Changes check ✅ Passed All changes are in scope: beeminder.go adds Rcur field and updateCurrentRate logic, next.go applies the overdue filter with consistent time snapshot, review.go implements formatRateValue and updates rate display logic, and all tests directly validate these changes.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/view-next-rate-bugs

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

Copy link
Copy Markdown
Contributor

@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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
next.go (1)

62-76: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use a single now snapshot for filtering and rendering.

Line 65 filters with one time.Now() call, but Line 76 formats the same goal with a later one. A goal that's about to expire can still slip through here and print OVERDUE, which is the bug this change is trying to eliminate.

Proposed fix
 func displayNextGoal() error {
 	_, _, goals, err := loadConfigAndGoals()
 	if err != nil {
 		return err
 	}
+	now := time.Now()

 	// Skip goals that have already reached their end value — they have no
 	// remaining work, so surfacing them as "next" would mislead the user into
 	// acting on a completed goal.
 	goals = filterOutEndValueReached(goals)

 	// Skip overdue goals: "next" should point at the soonest goal that still
 	// has time left, not one that's already past its deadline (which would
 	// render as OVERDUE rather than a countdown).
-	goals = filterOutOverdue(goals, time.Now())
+	goals = filterOutOverdue(goals, now)

 	// If no goals, return error
 	if len(goals) == 0 {
 		return fmt.Errorf("no goals found")
 	}

 	// Get the first goal (most urgent)
 	nextGoal := goals[0]

 	// Format the output: "goalslug baremin timeframe"
-	timeframe := FormatGoalDueDate(nextGoal)
+	timeframe := FormatGoalDueDateAt(nextGoal, now)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@next.go` around lines 62 - 76, Take a single timestamp snapshot (e.g., now :=
time.Now()) and use it for both the overdue filtering and the output formatting
so the same instant is used everywhere; replace the direct time.Now() call in
filterOutOverdue(goals, time.Now()) with the snapshot and likewise ensure
FormatGoalDueDate uses that same reference time (either by changing its
signature to FormatGoalDueDate(goal, refTime) or adding a small helper like
FormatGoalDueDateAt(goal, refTime)), then use that helper when formatting
nextGoal so a goal cannot become OVERDUE between the filterOutOverdue and
FormatGoalDueDate calls.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@review.go`:
- Around line 335-344: The comparison uses raw floats (goal.Rcur vs goal.Rate)
which can differ but format to the same displayed value; change the branch to
compare the formatted/displayed rate strings instead (use the same formatting
helpers used for rendering, e.g., call formatRateValue or formatRate for
*goal.Rcur and *goal.Rate with goal.Runits/goal.Gunits and compare those
strings) and only render both "current" and "end" when the formatted strings
differ; update the logic around rateStr, formatRate, formatRateValue, goal.Rcur
and goal.Rate accordingly.
- Around line 250-253: The formatted rate can end up as "-0" when a small
negative value rounds to zero in formatRateValue; after computing rounded (using
rateDisplayDecimals), normalize negative zero to positive zero before calling
strconv.FormatFloat—e.g. detect if rounded equals 0 (or math.Signbit(rounded) &&
rounded == 0) and set rounded = 0.0 so the output never prints "-0".

---

Outside diff comments:
In `@next.go`:
- Around line 62-76: Take a single timestamp snapshot (e.g., now := time.Now())
and use it for both the overdue filtering and the output formatting so the same
instant is used everywhere; replace the direct time.Now() call in
filterOutOverdue(goals, time.Now()) with the snapshot and likewise ensure
FormatGoalDueDate uses that same reference time (either by changing its
signature to FormatGoalDueDate(goal, refTime) or adding a small helper like
FormatGoalDueDateAt(goal, refTime)), then use that helper when formatting
nextGoal so a goal cannot become OVERDUE between the filterOutOverdue and
FormatGoalDueDate calls.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PinePeakDigital/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 252e114b-2792-433d-9bf1-5c3497ff3bcd

📥 Commits

Reviewing files that changed from the base of the PR and between 94b207c and bce3c45.

📒 Files selected for processing (5)
  • beeminder.go
  • beeminder_test.go
  • next.go
  • review.go
  • review_test.go

Comment thread review.go
Comment thread review.go
narthur and others added 2 commits June 3, 2026 21:13
The live Beeminder goal endpoint returns the current rate as `currate`,
not `rcur` (the field name only `rcur`/`ravg` appeared in an older API
dump). As written, the current/end rate split would never trigger
because `Rcur` was always nil. Map `currate`, fall back to `rcur` via a
CurrentRate() helper for any payload that still uses the old name, and
verify against the live API:

    Rate:        0.1623 hours / day (current), 0.1104 (end)

Follow-up to #259.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Compare formatted rate strings, not raw floats, when deciding the
  current/end split, so values that round equal don't render split
- Normalize a small negative rate that rounds to zero to "0" (no "-0")
- Use math.Pow10 for base-10 scaling in formatRateValue
- Compare Unix timestamps directly in filterOutOverdue

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@narthur
Copy link
Copy Markdown
Collaborator Author

narthur commented Jun 4, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 4, 2026

✅ Action performed

Review finished.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

@narthur
Copy link
Copy Markdown
Collaborator Author

narthur commented Jun 5, 2026

@coderabbitai full review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Jun 5, 2026

✅ Action performed

Full review finished.

Copy link
Copy Markdown
Contributor

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
next.go (1)

65-76: ⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Use one reference timestamp for filtering and rendering.

Line 65 and Line 76 use different “now” values. A near-deadline goal can pass the overdue filter and still render as OVERDUE immediately after.

Proposed fix
 func displayNextGoal() error {
 	_, _, goals, err := loadConfigAndGoals()
 	if err != nil {
 		return err
 	}
@@
-	goals = filterOutOverdue(goals, time.Now())
+	now := time.Now()
+	goals = filterOutOverdue(goals, now)
@@
-	timeframe := FormatGoalDueDate(nextGoal)
+	timeframe := FormatGoalDueDateAt(nextGoal, now)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@next.go` around lines 65 - 76, The code uses two different time.Now() calls
(in filterOutOverdue(goals, time.Now()) and FormatGoalDueDate(nextGoal)) causing
racey overdue detection; capture a single reference timestamp (e.g., now :=
time.Now()) and pass that same now into filterOutOverdue and into the rendering
call (update FormatGoalDueDate to accept a time parameter or add a helper
FormatGoalDueDateAt) so filterOutOverdue(goals, now) and
FormatGoalDueDate(nextGoal, now) use the identical instant when selecting and
formatting nextGoal.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@next.go`:
- Around line 65-76: The code uses two different time.Now() calls (in
filterOutOverdue(goals, time.Now()) and FormatGoalDueDate(nextGoal)) causing
racey overdue detection; capture a single reference timestamp (e.g., now :=
time.Now()) and pass that same now into filterOutOverdue and into the rendering
call (update FormatGoalDueDate to accept a time parameter or add a helper
FormatGoalDueDateAt) so filterOutOverdue(goals, now) and
FormatGoalDueDate(nextGoal, now) use the identical instant when selecting and
formatting nextGoal.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Repository: PinePeakDigital/coderabbit/.coderabbit.yaml

Review profile: ASSERTIVE

Plan: Pro

Run ID: 5959f698-eafd-40cb-ad05-a61f561febad

📥 Commits

Reviewing files that changed from the base of the PR and between 94b207c and f45b0f4.

📒 Files selected for processing (5)
  • beeminder.go
  • beeminder_test.go
  • next.go
  • review.go
  • review_test.go

narthur and others added 2 commits June 5, 2026 13:41
filterOutOverdue and the rendered countdown previously each called
time.Now() independently, so a goal could pass the overdue filter and
then render as OVERDUE moments later. Capture now once and thread it
through both via the existing FormatGoalDueDateAt helper.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Review-loop test-coverage findings (auto-applied, low-risk test-only):
- formatRateValue's negative-zero normalization (-0 -> "0") was load-bearing
  but unexercised; add a -0.00001 case to TestFormatRate.
- filterOutOverdue's strict "<" boundary (a goal due exactly at now is kept)
  was untested; add a due-now goal to TestFilterOutOverdue.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@narthur narthur merged commit db76921 into main Jun 6, 2026
5 checks passed
@narthur narthur deleted the fix/view-next-rate-bugs branch June 6, 2026 17:50
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.

buzz view sometimes shows a ton of decimal points for rate buzz view shows end rate and not current rate buzz next should not output overdue goals

2 participants