Skip to content

96e9567a: finish consolidating behaviour-divergent helpers (Berlin today, json/cleanItem, realDate docs) - #329

Merged
alexeygrigorev merged 3 commits into
mainfrom
fix/consolidate-divergent-helpers
Nov 4, 2026
Merged

alexeygrigorev merged 3 commits into
mainfrom
fix/consolidate-divergent-helpers

Conversation

@alexeygrigorev

Copy link
Copy Markdown
Member

Follow-up to #259 (the first 96e9567a consolidation pass, merged 2026-11-03). Re-inventoried all four helper families on current main: families (a) and (c) were already consolidated; families (b) and (d) still had true duplicates that the first pass missed, plus one undocumented divergence in (a). This PR finishes the job. No consumer-visible behavior changes.

Per-helper divergence decisions

Family Duplicate (before) Canonical Chosen behavior and rationale
(b) Berlin today backend/src/podcast/releaseSlots.ts:99 todayInTimezone(timezone, now) relativeDates.todayIn(tz, now) Identical en-CA YYYY-MM-DD output for all real zones; deleted the wrapper, call site uses todayIn directly.
(b) Berlin today backend/src/podcast/publication.ts:75 localDate(utcInstant, timezone) (formatToParts join) relativeDates.todayIn Kept the export and its instant-string signature (consumers pass scheduledAt strings); body is now a one-line delegation, so there is one formatter. DST parity pinned by tests.
(b) Berlin today backend/src/calendar/cardInvite.ts:558 private localDate(iso, timeZone) + inline en-CA formatter at :355 relativeDates.todayIn Byte-identical output (en-CA format of the same instant); both call sites of adoptEventDate and the stored-hold date now use the canonical helper.
(b) Berlin wall time backend/src/podcast/releaseSlots.ts zonedTimeToUtc(date, time, tz) + private timezoneOffsetMs relativeDates.civilToUtc(day, time, tz) Same signature, same DST-aware fixed-point semantics (both iterate an even number of offset probes, so nonexistent/ambiguous wall times resolve to the same instant); existing DST tests pin 17:00Z summer / 18:00Z winter.
(b) Berlin today cli/src/format.mjs berlinToday/todayIn kept deliberately CLI is a separate package that cannot import backend TS; first pass already added cli/test/berlin-today.test.mjs pinning behavioral parity. Not touched.
(c) intake auth — backend/src/routes/intakeAuth.ts Already consolidated by #259 (single credential cache, both emailDocuments and sponsorLeads consume it; tests/intake-auth.test.ts). Verified, nothing to do.
(d) json backend/src/router.ts:153 private jsonResponse + JSON_HEADERS http.jsonResponse Identical contract (JSON headers, string passthrough).
(d) json backend/src/routes/emailTemplates.ts json http.noStoreJson Identical headers (Content-Type + Cache-Control: no-store) and body.
(d) json backend/src/routes/apiTokens.ts:97 json http.json Identical contract; the module already imported noStoreJson/parseJsonObjectBody — this was a leftover.
(d) parseBody backend/src/routes/emailTemplates.ts parseBody(event) http.parseJsonObjectEventOrEmpty Behaviorally identical for every input: missing/empty → {}, array/primitive/invalid JSON → null, object passthrough.
(d) cleanItem backend/src/db/apiTokens.ts:24 generic cleanItem<T> db/cleanItem.cleanItem Identical PK/SK strip + null-when-absent.
(d) cleanItem backend/src/db/sessions.ts:19 cleanItem db/cleanItem.cleanItem Identical; aliased as cleanDbItem<Session> per the db/notifications.ts convention.
(d) json/cleanItem — kept docs/http.ts, documents/reviews.ts, routes/sponsorDocs.ts, sponsorContracts.ts, sponsorCommunications.ts, sponsorFinance.ts, documentExports.ts, newsletter/markdownPreview.ts, routes/company.ts, routes/offers.ts, db/cards.ts, db/tasks.ts, db/templates.ts, db/intake.ts their own helpers Deliberate variants already carrying "Kept separate" comments from #259 (docs CORS, no-referrer, nosniff, DENY framing, error-wrapping, throwing parseBody, canonical-shape assertions, GSI stripping). Untouched.
(a) realDate relativeDates.isCalendarDate vs sponsorCrm/model.realDate both kept, divergence documented Only divergence: years 0000-0099 — realDate's parse-based roundtrip accepts them, isCalendarDate's Date.UTC roundtrip rejects them. Kept: task-date resolution wants the stricter gate (no relative word may land on a year-0099 date) and records keep the looser parse gate. Documented at the helper and pinned by tests.

Tests

  • backend/tests/berlin-today.test.ts — new DST-edge parity test routing localDate/todayIn/berlinToday through one implementation.
  • backend/tests/podcast-release-slots.test.ts — DST conversion assertions now run against the canonical civilToUtc/todayIn.
  • backend/tests/real-date.test.ts — new test pinning the documented isCalendarDate/realDate year<0100 divergence.
  • backend/tests/shared-helpers.test.ts, api-tokens.test.ts, sponsor-docs.test.ts, card-calendar-invites.test.ts, intake-auth.test.ts — existing targeted tests prove the rewired consumers are behavior-identical.

All targeted tests pass (68 across the 8 files); tsc --noEmit clean. Full suite deliberately not run locally.

Task: 96e9567a

…iveDates (intake 96e9567a AC2)

Follow-up to the AC2 Berlin-today unification: releaseSlots.todayInTimezone,
podcast/publication.localDate, and the calendar cardInvite private localDate
plus an inline en-CA formatter all recomputed the civil date of an instant.
They now delegate to relativeDates.todayIn; releaseSlots.zonedTimeToUtc and
its private offset math now delegate to relativeDates.civilToUtc. No
consumer-visible behavior change (DST parity pinned by tests).
…rough shared helpers (intake 96e9567a AC4)

Follow-up to the AC4 shared-helper consolidation: five true duplicates of
http.json/http.noStoreJson/http.jsonResponse/http.parseJsonObjectEventOrEmpty
and db/cleanItem remained without a 'Kept separate' rationale. The router,
the email-templates and api-token routes, and the api-token and session db
modules now use the canonical helpers. Deliberate header/behavior variants
(docs CORS, no-referrer, nosniff, error-throwing parseBody) keep their
existing 'Kept separate' comments and are untouched.
…gence (intake 96e9567a AC1)

realDate (sponsor/finance records) accepts four-digit years 0000-0099 via
its parse-based roundtrip; relativeDates.isCalendarDate (task-date gate)
rejects them because its Date.UTC roundtrip maps those years onto
1900-1999. Keep both behaviors deliberately: task dates want the stricter
gate, and no consumer resolves a relative word onto a year-0099 date.
Divergence is now documented at the helper and pinned by tests.
@alexeygrigorev
alexeygrigorev merged commit beff0ea into main Nov 4, 2026
4 checks passed
@alexeygrigorev
alexeygrigorev deleted the fix/consolidate-divergent-helpers branch November 5, 2026 15:49
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