Skip to content

Deduplicate frontend date formatters into core/dates.js (Q-14) - #433

Merged
alexeygrigorev merged 4 commits into
mainfrom
refactor/q14-dates
Nov 12, 2026
Merged

alexeygrigorev merged 4 commits into
mainfrom
refactor/q14-dates

Conversation

@alexeygrigorev

@alexeygrigorev alexeygrigorev commented Nov 12, 2026 •

Copy link
Copy Markdown
Member

Audit Q-14 (hub intake b666fd44, parent 07609cba). This is a frontend-only refactor and should not change behavior.

What

frontend/src/core/dates.js (already the shared date module) now also exports:

  • formatDayMonth(value): replaces the two identical formatShortPlanningDate copies (surfaces/planning.js, surfaces/planning-newsletter.js). It reuses the existing DAY_MONTH formatter, which has the same Intl options. The name avoids a clash with core/workspace.js formatShortDate, which is left alone because its fallback differs.
  • formatIssueDate(value, { weekday }): replaces the copies in planning-newsletter-issue.js ("long") and work-detail/card-summary.js ("short").
  • formatUtcMinute(value) and formatEditedTimestamp(value): replace the identical formatEditedTimestamp copies in operations/newsletter-preview.js and operations/sponsor-docs.js, plus the two inline slice(0, 16).replace("T", " ") uses in card-summary.js.

Nullish-fallback decision

The two formatIssueDate copies differed only on invalid input: the issue page returned String(iso), the card summary returned String(iso || ""). The backend types publicationDate as string (backend/src/db/newsletterIssues.ts:96), but the frontend cannot rely on a type at runtime. So nothing is unified by assumption. The shared helper keeps the issue-page fallback String(value). The card summary call site passes issue.publicationDate || "", which gives the old output for every input (null, undefined, 0 and "" all become ""). This is shown by an oracle test, not argued. card.completedAt (Completed branch) and doc.lastEditedAt (behind its existing truthiness ternary) go through formatUtcMinute, which does no guarding, exactly like the old inline code.

Evidence

  • frontend/test/dates.test.mjs has table tests for each helper: month and year boundaries, leap day, long vs short weekday, timestamps read on their written UTC day (T23:59:59Z, +02:00), invalid, empty, null and undefined. Oracle tests also compare each helper with verbatim copies of the deleted functions across 15 input classes.
  • Local targeted run: dates plus 11 surface tests that cover the edited modules (planning-surface, newsletter-preview, sponsor-docs, sponsor-newsletters, work-detail-surface, …): 185/185 pass. The existing surface tests are unchanged. The dakit sync, border, colour and scale checks pass.
  • AC-1 grep: git grep -nE 'function (formatShortPlanningDate|formatIssueDate|formatEditedTimestamp|formatUtcMinute|formatDayMonth)|slice\(0, ?16\)\.replace\("T"' -- frontend/src matches only frontend/src/core/dates.js.

Backend allowlist (the only backend change)

sponsor-doc-page.js and newsletter-preview-page.js are served to anonymous visitors. They now import core/dates.js through operations/sponsor-docs.js and operations/newsletter-preview.js, so the module was added to isAnonymousSponsorDocAsset and isAnonymousNewsletterPreviewAsset. Without it those pages would 404 the module. dates.js has no imports, so nothing else enters either page's graph. backend/tests/anonymous-page-assets.test.ts walks all four anonymous pages and passes 5/5 locally.

CI history

  • ca1518b5: frontend-architecture line length (189 > 180) at planning-newsletter-issue.js (run 43266055910). Fixed in b71ea874 by moving the label into a const; markup unchanged.
  • b71ea874: anonymous-page-assets failed (run 43267874623). Fixed in bdd231c0.

No finance or newsletter-send changes. Full suites run on CI only (local host guard).

…tes.js

Table tests compare each helper with verbatim copies of the per-surface
formatters it replaces, across valid, timestamp, invalid, empty and
nullish inputs.
… ops surfaces

Removes the duplicate formatShortPlanningDate, formatIssueDate and
formatEditedTimestamp copies and the inline UTC-minute slices. The card
summary keeps its nullish fallback by passing publicationDate || "".
…imit

frontend-architecture.test.mjs failed on PR head ca1518b (run 43266055910):
the inlined weekday option pushed the line to 189 characters. Markup is
unchanged; the label is computed before the template.
…s.js

Both pages now import formatEditedTimestamp from core/dates.js;
anonymous-page-assets.test.ts failed on b71ea87 (run 43267874623)
because the module was not on their asset allowlists. dates.js has no
imports, so nothing else enters either page's graph.
@alexeygrigorev
alexeygrigorev merged commit f812255 into main Nov 12, 2026
4 checks passed
@alexeygrigorev
alexeygrigorev deleted the refactor/q14-dates branch November 12, 2026 17:44
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