Milestone v1.0: FamilySync MVP #1

Merged
luckberg merged 376 commits from gsd/v1.0-milestone into main 2026-06-10 17:39:19 -04:00
Showing only changes of commit ac0f8d282b - Show all commits
+14 -2
View File
@@ -350,7 +350,13 @@ export function EventForm() {
// D-06: validate recurrence bound inputs (T-06-06-input: client-side UX gate)
if (recurrence !== 'none') {
if (recurrenceBound === 'count' && recurrenceCount < 1) {
if (
recurrenceBound === 'count' &&
(!Number.isInteger(recurrenceCount) || recurrenceCount < 1)
) {
// WR-03: NaN < 1 is false, so a NaN count (from inputs like '' / '-' / 'e')
// previously bypassed this guard AND the payload spread, yielding an unbounded
// series. Require a finite integer ≥ 1 explicitly.
newErrors.recurrenceBound = 'Must be at least 1 occurrence'
} else if (recurrenceBound === 'until') {
// WR-02: a blank end date with bound='until' must be a validation error.
@@ -939,7 +945,13 @@ export function EventForm() {
min={1}
placeholder="e.g. 10"
value={recurrenceCount}
onChange={(e) => setRecurrenceCount(Number(e.target.value))}
onChange={(e) => {
// WR-03: parseInt + Number.isFinite guard. Number('') === 0 and
// Number('-'/'e') === NaN both previously slipped through; coerce any
// non-finite intermediate to 0 so the validate() guard catches it.
const n = parseInt(e.target.value, 10)
setRecurrenceCount(Number.isFinite(n) ? n : 0)
}}
style={{
...inputStyle,
...(errors.recurrenceBound ? { borderColor: 'var(--color-destructive)' } : {}),