style(13-03): apply Prettier formatting across repo
Mechanical reformat — no logic changes. 398 files changed, 19125 insertions(+), 16457 deletions(-). Prettier 3.8.4 with .prettierrc (singleQuote:true, semi:true, tabWidth:2, trailingComma:all, printWidth:100). Isolated per D-13-08 for reviewability.
This commit is contained in:
@@ -47,7 +47,7 @@ outbox worker write-path, the events route, the PWA event form / auth-splash /
|
||||
push-prompt components, the calendar store, and supporting tests + CSS.
|
||||
|
||||
The code is heavily commented and carries a clear audit trail of prior fixes. The
|
||||
adversarial pass focused on the gaps *between* those documented fixes. The one
|
||||
adversarial pass focused on the gaps _between_ those documented fixes. The one
|
||||
Critical finding is a security-relevant injection vector in the RRULE `UNTIL`
|
||||
assembly (the route validates the date-window query params and write-body lengths,
|
||||
but `recurrenceUntil` is NOT validated as a date before being spliced into an RRULE
|
||||
@@ -67,9 +67,9 @@ The route validates `recurrenceUntil` only as `z.string().max(10).optional()`
|
||||
≤10-char string passes. The outbox worker then does:
|
||||
|
||||
```js
|
||||
const dateDigits = until.replace(/-/g, '')
|
||||
s += `;UNTIL=${dateDigits}` // all-day
|
||||
s += `;UNTIL=${dateDigits}T235959Z` // timed
|
||||
const dateDigits = until.replace(/-/g, '');
|
||||
s += `;UNTIL=${dateDigits}`; // all-day
|
||||
s += `;UNTIL=${dateDigits}T235959Z`; // timed
|
||||
```
|
||||
|
||||
`until.replace(/-/g,'')` strips hyphens but leaves every other character. A payload
|
||||
@@ -103,7 +103,7 @@ output is guaranteed digits-only and the injection vector closes.
|
||||
|
||||
**File:** `apps/api/src/routes/events.ts:199-203`
|
||||
**Issue:**
|
||||
The third `or()` branch selects *any* row with `dtstartDate` in `[start, end)`
|
||||
The third `or()` branch selects _any_ row with `dtstartDate` in `[start, end)`
|
||||
**without** gating on `hasRrule = 0`:
|
||||
|
||||
```js
|
||||
@@ -111,7 +111,7 @@ and(
|
||||
sql`${calendarEvents.dtstartDate} IS NOT NULL`,
|
||||
sql`${calendarEvents.dtstartDate} >= ${start}`,
|
||||
sql`${calendarEvents.dtstartDate} < ${end}`,
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
The timed branch above it explicitly gates `hasRrule = 0`, but this all-day branch
|
||||
@@ -124,13 +124,14 @@ real on the wire and any consumer that counts occurrences (or a future view that
|
||||
not dedup) sees doubles. Add the missing `hasRrule = 0` gate to the all-day branch.
|
||||
|
||||
**Fix:**
|
||||
|
||||
```js
|
||||
and(
|
||||
sql`${calendarEvents.hasRrule} = 0`,
|
||||
sql`${calendarEvents.dtstartDate} IS NOT NULL`,
|
||||
sql`${calendarEvents.dtstartDate} >= ${start}`,
|
||||
sql`${calendarEvents.dtstartDate} < ${end}`,
|
||||
)
|
||||
);
|
||||
```
|
||||
|
||||
### WR-02: EventForm silently creates an unbounded series when "On date" is selected but no date entered
|
||||
@@ -154,9 +155,10 @@ On date"). Treat a blank `recurrenceUntil` while `bound === 'until'` as a valida
|
||||
error.
|
||||
|
||||
**Fix:** Add to the `recurrence !== 'none'` block:
|
||||
|
||||
```js
|
||||
if (recurrenceBound === 'until' && !recurrenceUntil) {
|
||||
newErrors.recurrenceBound = 'Choose an end date'
|
||||
newErrors.recurrenceBound = 'Choose an end date';
|
||||
}
|
||||
```
|
||||
|
||||
@@ -173,6 +175,7 @@ yielding an unbounded series. The 0 case is caught by validation, but the NaN ca
|
||||
bypasses both the validation gate and the payload gate.
|
||||
|
||||
**Fix:** Sanitize on change and validate explicitly:
|
||||
|
||||
```js
|
||||
onChange={(e) => {
|
||||
const n = parseInt(e.target.value, 10)
|
||||
@@ -192,8 +195,8 @@ if (recurrenceBound === 'count' && (!Number.isInteger(recurrenceCount) || recurr
|
||||
`.toISOString().slice(0, 10)`. `eventDateTime.ts:64-65` and `EventForm.tsx:108-110`
|
||||
explicitly document this as forbidden ("NEVER use toISOString().slice(0,10) — that
|
||||
returns the UTC date, not the local date"). For a user west of UTC (the project's
|
||||
primary zones are Toronto/Detroit/New_York/Edmonton — all negative offsets) after
|
||||
~20:00 local, `todayIso()` returns *tomorrow's* date. This is the default
|
||||
primary zones are Toronto/Detroit/New*York/Edmonton — all negative offsets) after
|
||||
~20:00 local, `todayIso()` returns \_tomorrow's* date. This is the default
|
||||
`selectedDate` and seeds the initial fetch window — so a late-evening cold load can
|
||||
center the calendar on the wrong day and the EventForm create default (`todayIso()`
|
||||
at EventForm.tsx:153/158) pre-fills tomorrow. The fix already exists as the private
|
||||
@@ -201,6 +204,7 @@ at EventForm.tsx:153/158) pre-fills tomorrow. The fix already exists as the priv
|
||||
|
||||
**Fix:** Export `localDateISO` from `eventDateTime.ts` and use it in
|
||||
`initialCalendarRange()`/`todayIso()`:
|
||||
|
||||
```js
|
||||
function localDateISO(d: Date): string {
|
||||
return `${d.getFullYear()}-${pad2(d.getMonth()+1)}-${pad2(d.getDate())}`
|
||||
@@ -221,6 +225,7 @@ interstitial never arms, leaving the user on a hung query. The class uses a fixe
|
||||
should defensively also check `name`.
|
||||
|
||||
**Fix:**
|
||||
|
||||
```js
|
||||
function onGlobalError(error: unknown): void {
|
||||
if (error instanceof SessionExpiredError ||
|
||||
@@ -256,7 +261,7 @@ and let the next poll reconcile.
|
||||
**Issue:**
|
||||
`if (recurrenceUntil < startDate)` compares two strings lexicographically.
|
||||
`recurrenceUntil` comes from a `type="date"` input (`YYYY-MM-DD`) and `startDate` is
|
||||
also `YYYY-MM-DD`, so this works *today*. But it is silently coupled to both values
|
||||
also `YYYY-MM-DD`, so this works _today_. But it is silently coupled to both values
|
||||
always being zero-padded ISO dates. If `startDate` is ever blank (the IN-02 edit
|
||||
parse-failure path sets it to `''`), `recurrenceUntil < ''` is always `false`, so the
|
||||
bound-before-start guard is skipped exactly when the start is unknown. Low impact
|
||||
@@ -272,7 +277,7 @@ the implicit format coupling is fragile. Compare parsed dates or assert non-empt
|
||||
**Issue:**
|
||||
`new Date(clean)` for a string like `'2026-13-45'` returns an `Invalid Date`, caught
|
||||
and returned as `ok:false` — correct. But `new Date('2026-06')` (a partial date) is
|
||||
parsed as a *valid* UTC instant in V8, so a truncated/garbled cached value would parse
|
||||
parsed as a _valid_ UTC instant in V8, so a truncated/garbled cached value would parse
|
||||
"successfully" to an unintended day/time and be saved on edit without tripping the
|
||||
IN-02 blank-field guard. The function trusts `new Date()`'s permissive parsing.
|
||||
Tighten the accepted timed-format (e.g. require a `T` and `:` before calling
|
||||
@@ -287,7 +292,7 @@ Tighten the accepted timed-format (e.g. require a `T` and `:` before calling
|
||||
### IN-01: Dead/misleading interface doc comment in `client.ts` CalendarOccurrence
|
||||
|
||||
**File:** `apps/pwa/src/api/client.ts:107`
|
||||
**Issue:** The `id` field comment says ``` `${uid}::${dtstart_iso}` — stable identity ```
|
||||
**Issue:** The `id` field comment says `` `${uid}::${dtstart_iso}` — stable identity ``
|
||||
but the server (`expand.ts:101-104` `makeOccurrenceId`) now emits
|
||||
`ev-<sanitized-uid>-<epochMs>`. The `::`-format comment is stale and contradicts the
|
||||
actual wire contract (and the server-side comment that explains why `::` was
|
||||
|
||||
Reference in New Issue
Block a user