Skip to content

Feat/dashboard detail and combine - #2

Merged
78wesley merged 7 commits into
masterfrom
feat/dashboard-detail-and-combine
Sep 19, 2026
Merged

78wesley merged 7 commits into
masterfrom
feat/dashboard-detail-and-combine

Conversation

@78wesley

Copy link
Copy Markdown
Owner

No description provided.

78wesley and others added 7 commits September 19, 2026 20:21
Walking one billing period at a time is O(gap): a daily subscription running
since 2015 is ~4000 steps from today, and callers ask for a whole month of
payments, re-walking from the period start each time. Rendering the dashboard
for 40 such subscriptions took 2.5s.

Skip most of the cadence in one jump, then step the remainder. Day- and
week-based cadences are plain timedeltas and always jump; month-family anchors
on day 29-31 keep stepping, because month-end clamping makes that sequence
non-arithmetic (Jan 31 -> Feb 28 -> Mar 28) and a single jump would land
elsewhere. The month jump deliberately under-counts (31 days/month) so it can
never overshoot the reference date.

Same dashboard: 0.038s. Parametrised tests pin the jump to the step-by-step
result across cadences, leap days and month-end anchors.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A price change is recorded by closing the running period and opening a new one
the next day, but every period restarted the billing cycle on its own start
date — so changing a price silently moved the billing day, and in the month of
the change the subscription was billed twice (once by each period).

Anchor each period at the start of the contiguous run it belongs to: back-to-
back periods are one continuous subscription and keep their original day, while
a gap (cancelled, then resubscribed) does start a new cycle on the new date.
The period covering a billing date still supplies its price.

Fixes both the doubled charge and the wrong billed/still-due split on the
dashboard, and the same dates on the detail page's next-payment list.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Charts were tall and mute: a 640x220 viewBox scaled to ~420px on a wide screen,
and a value was only readable from the SVG title attribute.

- Every mark (bar, point, breakdown row) carries data-tip-* and sits in a <g>
  with a transparent full-height hit area, so the whole column is hoverable.
  CHART_JS renders a floating card on hover and pins it on click or tap, which
  is the only way to read a value on a touch screen. Marks are focusable and
  labelled, so the keyboard reaches them too.
- Default viewBox height 220 -> 150 (the height is really an aspect ratio),
  with tighter padding and one fewer gridline: ~30% shorter at the same width.
- bar/line charts take tip_labels and notes, so a tooltip can say "January 2026
  / EUR 141.56 / 4 subscriptions" while the axis stays at "Jan".
- tab_nav() and disclosure(): shadcn-styled tabs (plain links) and a native
  <details> show/hide, both JS-free.
- plural() moves into fmt so the routes stop redefining it.
- badge_cls gained the success/warning kinds the dashboard was already asking
  for; they silently fell through to the solid default variant.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The dashboard answered "what did this year cost" but not "what do I owe now".
Add a card that walks real billing dates rather than prorated cost, so it shows
money as it lands on the card: this month's total, last month's, the delta, the
payments still to come this month, and a toggle revealing those already billed.

Reorder around it — "Right now" (month card + run-rate) now leads the page and
the year selector sits below with everything it governs, including the two
year-scoped tiles that used to sit in the run-rate row.

The monthly chart gained tooltips: full month name, exact amount, and how many
subscriptions were active that month.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Seven cards stacked on one page meant scrolling past the price history to reach
the audit log. Give it a summary header — name, status, category, current price,
next payment, actions — over four tabs: Overview (details, cost per period, next
payments), Spend (monthly chart and lifetime figures), Periods (price history
and the add form), History (audit trail).

Tabs are plain ?tab= links, so they deep-link, survive a reload, and let the
period POSTs redirect back to the tab they came from. The History tab and the
add-period form only render for roles that may use them, and an unknown or
no-longer-permitted tab falls back to Overview.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…ongs to

Importing newer prices for a subscription whose current period is still
open-ended creates a second subscription with the same name, and there was no
way to fix that short of retyping the periods by hand.

combine_subscriptions() folds one into the other: periods are re-pointed rather
than copied, so who added each price and when survives, and an open-ended period
is closed the day before the next one starts — add_period's price-change rule,
applied across the merged timeline so it works whichever side is open. Genuine
overlaps between bounded periods are refused and nothing is written;
plan_combine() decides that before any change is made.

Import no longer leaves the duplicate unexplained: it still creates the row
(guessing would be worse) but the result page names the clash and offers
Combine, spelling out what will move and which period gets closed. The detail
page offers the same for any same-named leftovers.

Combining needs subscriptions.edit + subscriptions.delete, is refused across
teams (a super admin in cross-team view can see both rows), and audits both
sides.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
@78wesley
78wesley merged commit 816c7a8 into master Sep 19, 2026
1 check passed
@78wesley
78wesley deleted the feat/dashboard-detail-and-combine branch September 19, 2026 18:29
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