Skip to content

settings: show the exact next-charge timestamp - #57

Merged
max-tet merged 2 commits into
mainfrom
fix/clayde/exact-next-charge-date
Sep 22, 2026
Merged

max-tet merged 2 commits into
mainfrom
fix/clayde/exact-next-charge-date

Conversation

@ClaydeCode

@ClaydeCode ClaydeCode commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Closes #56.

The Subscription card's active state rendered subscription.next_billing_date through the formatDateHumanize filter, so a paying subscriber read "Next charge in a month." It now reads "Next charge: 2026-07-01."

Per the comment on the issue, this is not a polish item: with a paid subscription the length of the current term and the date of the next charge have to be unambiguous, and a humanised interval is what creates the UWG exposure. The Next charge: wording is what freeshard-controller/docs/superpowers/specs/2026-04-24-paypal-billing-design.md specifies for the Active state.

What changed

  • src/views/Settings.vue — the next-charge line uses formatDate instead of formatDateHumanize.
  • src/lib/dates.js (new) — formatDate(value), the body of the existing formatDate filter, extracted so the unit suite can reach it. Same extraction pattern as src/lib/pricing.js.
  • src/main.js — the formatDate filter is registered from that function.
  • src/components/TextField.vue — one optional tooltip prop, for the hover described below.
  • tests/unit/dates.spec.js (new) — two cases on the formatter.

The date carries no clock time

The first version of this PR rendered 2026-07-01 13:30. Only the day matters for a charge, so the time is gone. That also disposes of a trap in the filter being reused: it formatted with hh, a 12-hour clock with no meridiem, which renders 13:30 as 01:30. With no clock component there is nothing left to be ambiguous about. The filter had no call sites before this PR, so nothing else on screen moved.

The other seven dates now show the exact date on hover

The card states seven further dates as intervals: delete_after in the trial and grace states, last_payment_failed_at, ended, the backup passphrase access time, and time_created / time_assigned / delete_after in the About overlay. None of them is a charge falling due, so none carries the billing-transparency requirement that motivates the change above, and they keep reading as intervals, which is the right default for "3 days ago".

But an interval is the wrong thing to read when the question is which day something happens, so hovering any of them now shows the calendar date. bootstrap-vue is already a dependency and b-tooltip is already used in Navbar.vue, so this is the v-b-tooltip directive and nothing new. The three About-overlay fields go through TextField, which takes its content as a string and had nowhere to hang a title, so it gained one optional tooltip prop; it is used only by this view and the prop is inert when absent.

Test evidence

The suite is a pure-unit one (tests/unit/, no @vue/test-utils in the repo), so the tests cover the formatter, not the template. The formatter test was broken on purpose to check it has teeth: putting HH:mm back into the format string fails both cases, 2 failed / 28 passed. Restored, 30 pass across 4 suites.

npx vue-cli-service lint is clean, and npm run build completes, which is what compiles the templates the tooltips were added to. Locally the build needs NODE_OPTIONS=--openssl-legacy-provider on Node 24; the serve script already carries that flag and build does not, which is a pre-existing wrinkle this PR does not touch.

Note on the issue body

The issue quotes next_charge_at at Settings.vue:70. On current main the field is subscription.next_billing_date and the line is 92 — the card was reworked after the issue was filed. The bug the issue describes is the same one; only the field name moved.

The subscription card rendered next_billing_date through
formatDateHumanize, so an active subscriber read "Next charge in a
month." With a paid subscription the length of the current term and the
date of the next charge have to be unambiguous; a humanised interval is
the kind of statement a competitor can attack under the UWG. The card
now reads "Next charge: 2026-07-01 13:30", which is also the wording the
PayPal billing design spec asks for.

next_billing_date is a timestamptz taken from PayPal's next_billing_time,
so rendering a time next to the date reflects the data rather than
padding it.

formatDate was the only absolute-date precedent in the codebase and had
no call sites. It formatted with hh, a 12-hour clock with no meridiem,
which renders 13:30 as 01:30 and would have carried the ambiguity
straight back into the billing line. Its body moves to src/lib/dates.js
on HH, where the unit suite can reach it, following the same extraction
as lib/pricing.js.
…ct date on hover

Max' call on the first version: for the next charge only the day matters,
not the hour. formatDate loses the time component, which also settles the
hh-versus-HH question the previous commit had to solve, since there is no
clock left to be ambiguous about. The billing line reads
"Next charge: 2026-07-01."

The card states seven other dates as intervals, from "in a month" to
"3 days ago". None of them is a charge falling due, so none needed the
same treatment, but an interval is still the wrong thing to read when the
question is which day something happens. Hovering now shows the calendar
date. bootstrap-vue is already a dependency and b-tooltip is already used
in the navbar, so this is the v-b-tooltip directive plus an optional prop
on TextField, whose three About-overlay fields take their content as a
string and had nowhere to hang a title.
@max-tet
max-tet merged commit 31879b5 into main Sep 22, 2026
2 checks passed
@max-tet
max-tet deleted the fix/clayde/exact-next-charge-date branch September 22, 2026 08:34
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.

fix(ui): Replace vague relative date format with exact billing timestamp in Settings.vue

2 participants