Skip to content

fix(ui): enforce future-only planned start date + widen Configure Purchase Term field#1258

Open
cristim wants to merge 3 commits into
mainfrom
fix/qa437-purchase-ui-polish
Open

fix(ui): enforce future-only planned start date + widen Configure Purchase Term field#1258
cristim wants to merge 3 commits into
mainfrom
fix/qa437-purchase-ui-polish

Conversation

@cristim

@cristim cristim commented Jun 19, 2026

Copy link
Copy Markdown
Member

Summary

Two small Purchase-flow UI polish fixes from the QA sheet, shipped together.

Bug 1 (QA 5.6) - Add Purchases start-date picker allowed past dates

Closes #1249

Root cause: the <input type="date" id="add-purchases-start-date"> in the existing-plan "Add Purchases" modal (frontend/src/plans.ts) had no min attribute, so the browser date-picker let users select dates in the past, which is contradictory for scheduling future purchases.

Fix: after the default value (tomorrow) is assigned, set startDateInput.min to today's ISO date. Past dates are now blocked in the picker while today remains selectable (a same-day start is legitimate). The default value stays tomorrow.

Bug 2 (QA 5.3) - Configure Purchase modal Term field too narrow to read

Closes #1250

Root cause: the Term <select> in the Configure Purchase modal carries class .purchase-row-term (set in recommendations.ts), but no CSS rule existed for it anywhere, so the browser rendered it at content width and clipped both the selected value and the dropdown arrow.

Fix: add .purchase-row-term { min-width: 5rem; } to frontend/src/styles/modals.css, next to the sibling .purchase-modal-* rules. CSS-only; recommendations.ts is untouched (owned by another PR).

Testing

  • Bug 1: regression test added in frontend/src/__tests__/plans-range-validation.test.ts (new describe block "Add Purchases modal: start-date picker enforces future-only dates (QA 5.6)"). Asserts input.min === today's ISO date after the modal opens, plus that the default value stays tomorrow. Confirmed FAIL pre-fix (received ""), PASS post-fix. Full file: 44 pass / 0 fail.
  • npx tsc --noEmit: no errors.
  • Bug 2: CSS-only, not unit-testable in jsdom - needs visual verification in a real browser (Configure Purchase modal Term dropdown should no longer clip the value/arrow).

Note

Stacked on #1254 (pre-commit repair); retarget to main when #1254 merges.

Summary by CodeRabbit

  • Bug Fixes
    • The Add Planned Purchases modal now enforces a minimum date constraint on the start-date picker field to prevent users from selecting any dates prior to today's date.
    • Resolved visual clipping issues affecting the display of term values in Configure Purchase rows through improved styling and layout adjustments for better readability.

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

Warning

Review limit reached

@cristim, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 4 minutes and 53 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 7d63f851-a8e7-4ba5-bf0f-e5dc1b3e30b8

📥 Commits

Reviewing files that changed from the base of the PR and between 451a70f and 43c3954.

📒 Files selected for processing (3)
  • frontend/src/__tests__/plans-range-validation.test.ts
  • frontend/src/plans.ts
  • frontend/src/styles/modals.css
📝 Walkthrough

Walkthrough

Two independent bug fixes: openAddPurchasesModal in plans.ts now sets the min attribute on the start-date input to today's ISO date, with a corresponding regression test added. Separately, a .purchase-row-term { min-width: 5rem } rule is added to modals.css to prevent the Configure Purchase term field from being clipped.

Changes

Add Purchases start-date min constraint (QA 5.6)

Layer / File(s) Summary
Start-date min attribute and regression test
frontend/src/plans.ts, frontend/src/__tests__/plans-range-validation.test.ts
openAddPurchasesModal sets startDateInput.min to today's YYYY-MM-DD; tests assert min equals today and default value remains tomorrow.

Configure Purchase term field width (QA 5.3)

Layer / File(s) Summary
.purchase-row-term min-width rule
frontend/src/styles/modals.css
Adds .purchase-row-term { min-width: 5rem } adjacent to other purchase-modal CSS rules to prevent term value and dropdown arrow from being clipped.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~4 minutes

Suggested labels

priority/p2, urgency/this-sprint

Poem

🐇 A date picker once wandered too far in the past,
So I set a min boundary to make the rule last.
And a term field too narrow? A quick 5rem grow —
No clipping, no squinting, now values all show!
Small fixes, big smiles — this bunny hops on! 🌸

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes both main changes: enforcing future-only start dates and widening the Configure Purchase Term field.
Linked Issues check ✅ Passed The PR fully addresses both linked issues: setting min attribute to today's date for start-date input (#1249) and adding min-width CSS rule for Term field (#1250).
Out of Scope Changes check ✅ Passed All changes align with the two linked issues. The test additions, the single line in plans.ts, and the CSS rule are all scoped to the identified bugs.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/qa437-purchase-ui-polish

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

@cristim cristim added triaged Item has been triaged type/bug Defect priority/p3 Polish / idea / may never ship severity/low Minor harm urgency/this-quarter Within the quarter impact/few Limited audience effort/xs Trivial / one-liner labels Jun 19, 2026
@cristim

cristim commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
✅ 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.

@cristim cristim changed the base branch from fix/1222-precommit-repair to main June 19, 2026 14:51
@cristim

cristim commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

cristim added 2 commits June 19, 2026 23:56
#1249)

Set `startDateInput.min` to today's ISO date after the default value
(tomorrow) is assigned so the browser date-picker rejects past dates
while still allowing today as a valid same-day start.

Regression test added in plans-range-validation.test.ts: asserts
`input.min === today ISO` after `openAddPurchasesModal` returns (FAIL
pre-fix, PASS post-fix, 44 tests green).
…pping (closes #1250)

The Term select carries class `.purchase-row-term` but no CSS rule
existed for it, so the browser rendered it at content width and clipped
the selected value and dropdown arrow. Add `min-width: 5rem` alongside
the other `.purchase-modal-*` rules in modals.css.

CSS-only change; visual verification required (not unit-testable in
jsdom).
@cristim cristim force-pushed the fix/qa437-purchase-ui-polish branch from 308f47c to 80df799 Compare June 19, 2026 22:02
@cristim

cristim commented Jun 19, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai review

@coderabbitai

coderabbitai Bot commented Jun 19, 2026

Copy link
Copy Markdown
Contributor

Rate Limit Exceeded

@cristim have exceeded the limit for the number of chat messages per hour. Please wait 2 minutes and 5 seconds before sending another message.

@cristim

cristim commented Jun 20, 2026

Copy link
Copy Markdown
Member Author

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 20, 2026

Copy link
Copy Markdown
Contributor
✅ Action performed

Full review finished.

… day

`new Date().toISOString().split('T')[0]` returns the UTC calendar date, not
the user's local one. For any user west of UTC after their local-evening
crossover, the prior `min = toISOString().split('T')[0]` would resolve to
tomorrow's UTC date and grey out today in the picker, directly contradicting
the PR's own claim that "today remains selectable (a same-day start is
legitimate)" (QA 5.6).

Extract a `toLocalDateInputValue(Date) -> YYYY-MM-DD` helper that builds the
ISO string from local year/month/day components (mirrors the local-midnight
pattern already used by `isPlanOverdue` above), and use it for both `value`
(default tomorrow) and `min` (today) in `openAddPurchasesModal`.

Regression test updated in `plans-range-validation.test.ts`: the prior
assertion used the same UTC function as production code, so it would have
gone green even with the bug present. The replacement asserts both `min` and
`value` against the local-component derivation, and adds an explicit
"local calendar day, not UTC" case that documents the failure shape.
@cristim

cristim commented Jun 26, 2026

Copy link
Copy Markdown
Member Author

Adversarial review pass — in-scope fix pushed, two follow-ups filed

In-scope fix (just pushed as 43c395440) — UTC vs local-calendar bug in the new min / default value.

Both lines added by this PR used new Date().toISOString().split('T')[0], which returns the UTC calendar day. For any user west of UTC after their local-evening crossover (e.g. US Pacific time after ~5pm local), min resolves to tomorrow's UTC date and the picker would grey out today — directly contradicting the PR body's promise that "today remains selectable (a same-day start is legitimate)" (QA 5.6).

The fix introduces a toLocalDateInputValue(Date) -> YYYY-MM-DD helper that builds the string from local year/month/day components (mirrors the local-midnight pattern isPlanOverdue already uses just above), and applies it to both value and min. The regression test in plans-range-validation.test.ts had the same shape as the production bug — new Date().toISOString().split('T')[0] on both sides — so it would have stayed green with the bug present. It now asserts against a local-component derivation, plus an explicit "local calendar day, not UTC" case documenting the failure shape.

tsc --noEmit clean. plans-range-validation.test.ts: 45 passed (was 42 + 3 new). plans.test.ts: 173 passed combined.

Out-of-scope follow-ups (filed as separate issues per the playbook):

  • Enforce future-only purchase start_date server-side (defense in depth for #1249) #1286 — server-side enforcement of start_date >= today. The title of this PR says "enforce future-only" but the <input type="date" min=...> attribute is only a UA hint (Chrome blocks submit, Firefox/Safari accept; devtools / a direct POST /plans/{id}/purchases bypasses it). parseCreatePurchasesRequest (internal/api/handler_plans.go:327-343) only validates the date format. The scheduler treats a past ScheduledDate as due-now (internal/purchase/manager.go:529), so a bypass would queue the executions for the next tick. P2 / severity/medium / effort/s.
  • Sweep toISOString().split('T')[0] -> toLocalDateInputValue across frontend date inputs #1287 — broader sweep of the same toISOString().split('T')[0] shape across frontend/src/history.ts (4 call sites) and frontend/src/apikeys.ts (1 call site). P3 / severity/low / effort/s.

Kept those out of this PR to honour its effort/xs scope and not delay the QA 5.6 / QA 5.3 polish.

CI UNSTABLE: the three failing checks on this PR (Lint Code, Integration Tests, Security Scanning) are preexisting main-wide failures unrelated to a CSS + date-min change:

  • Lint Code: 2875 Go-side golangci-lint issues (errcheck/godot/misspell/unparam/...) in internal/api/, internal/server/, internal/email/ etc. — none in this PR's files (all three changed files are frontend-only).
  • Integration Tests: role "testuser" does not exist Postgres bootstrap failure (CI infra).
  • Security Scanning: npm audit reporting pre-existing transitive vulns in webpack/svgo/ws/serialize-javascript.

@coderabbitai full review

@coderabbitai

coderabbitai Bot commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

@cristim Thanks for the thorough adversarial pass — the local-calendar fix makes sense, and keeping the server-side validation and broader toISOString().split('T')[0] sweep in follow-up issues #1286 and #1287 is a good scope split for this PR.

I’ll run a full review with the updated local-date helper/test coverage in mind.


✏️ Learnings added
Learnt from: cristim
URL: https://github.com/LeanerCloud/CUDly/pull/1258

Timestamp: 2026-06-26T13:56:27.300Z
Learning: In this repository's frontend date-input flows such as `frontend/src/plans.ts`, using `new Date().toISOString().split('T')[0]` is incorrect for local calendar semantics because it derives the UTC day and can disable/select the wrong local date for users west of UTC. Use a local-component helper that formats `YYYY-MM-DD` from local year/month/day for `<input type="date">` `value` and `min`.

Note: Learnings are effective only in the context of similar code segments. To apply general coding guidelines, please configure review instructions. You can manage existing learnings in the app.

✅ Action performed

Full review finished.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

effort/xs Trivial / one-liner impact/few Limited audience priority/p3 Polish / idea / may never ship severity/low Minor harm triaged Item has been triaged type/bug Defect urgency/this-quarter Within the quarter

Projects

None yet

1 participant