Skip to content

Commit 3399817

Browse files
docs: update puzzle-page.md for the now-active puzzleDate
Documents puzzleDate's new roles: rendered as a human-readable date next to the title, and passed through as PuzzleContext.puzzleDate to the iframe. Removes the stale "accepted but not yet used" limitation bullet, notes that frontend now always resolves and sends a date (owns the date-in-URL/redirect-to-archive logic entirely), and adds an open question that DCR does not validate puzzleDate is a real, sensible calendar date beyond basic shape checking. Co-authored-by: Copilot App <223556219+Copilot@users.noreply.github.com>
1 parent 47ea8be commit 3399817

1 file changed

Lines changed: 38 additions & 26 deletions

File tree

dotcom-rendering/docs/puzzle-page.md

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -182,19 +182,19 @@ than requiring bespoke copy per field.
182182
(`src/types/puzzlePage.ts`, validated by `validateAsPuzzlePageType` in
183183
`src/model/validate.puzzlePage.ts`):
184184

185-
| Field | Type | Notes |
186-
| ---------------------------------- | ------------------------------------------------------ | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
187-
| `id` | `string` | Any stable identifier for the page instance. |
188-
| `slug` | `string` | Looked up in the `PuzzleConfig` registry; unknown slug → `404`. |
189-
| `webTitle` | `string` | Page `<title>` / share text. |
190-
| `config` | `ConfigType` | Same shape frontend sends for `/Article`, `/PuzzlesPage`, etc. Only checked for a `serverSideABTests: Record<string, string>` shape, content otherwise unused (no AB gate today). |
191-
| `nav` | `FENavType` | Same shape as other routes. |
192-
| `pageFooter` | `FooterType` | Same shape as other routes. |
193-
| `canonicalUrl` | `string` | Canonical link tag. |
194-
| `editionId` | `EditionId` (`'UK' \| 'US' \| 'AU' \| 'INT' \| 'EUR'`) | Validated against the known edition set. |
195-
| `instance.title` | `string` (required) | Rendered as the page `<h1>` and the iframe `title` attribute. |
196-
| `instance.puzzleDate` | `string?` (e.g. `"2026-09-11"`) | Which day's puzzle the reader wants to see. Accepted and validated as an optional string only. **Not yet wired into any rendering or the iframe URL** (see "Open questions"). Prep work for a future V1 calendar-navigation feature; unrelated to the removed crossword-only `date` display-string field. |
197-
| `instance.moreFromPuzzlesAndGames` | `PuzzleItem[]?` (from `src/types/puzzlesPage.ts`) | Rendered as a plain "More from Puzzles & games" list when present and non-empty. |
185+
| Field | Type | Notes |
186+
| ---------------------------------- | ------------------------------------------------------ | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
187+
| `id` | `string` | Any stable identifier for the page instance. |
188+
| `slug` | `string` | Looked up in the `PuzzleConfig` registry; unknown slug → `404`. |
189+
| `webTitle` | `string` | Page `<title>` / share text. |
190+
| `config` | `ConfigType` | Same shape frontend sends for `/Article`, `/PuzzlesPage`, etc. Only checked for a `serverSideABTests: Record<string, string>` shape, content otherwise unused (no AB gate today). |
191+
| `nav` | `FENavType` | Same shape as other routes. |
192+
| `pageFooter` | `FooterType` | Same shape as other routes. |
193+
| `canonicalUrl` | `string` | Canonical link tag. |
194+
| `editionId` | `EditionId` (`'UK' \| 'US' \| 'AU' \| 'INT' \| 'EUR'`) | Validated against the known edition set. |
195+
| `instance.title` | `string` (required) | Rendered as the page `<h1>` and the iframe `title` attribute. |
196+
| `instance.puzzleDate` | `string?` (e.g. `"2026-09-11"`) | Which day's puzzle the reader wants to see. Rendered as a human-readable date (e.g. "11 September 2026") next to the page title, and passed through unformatted as `PuzzleContext.puzzleDate` to the puzzle iframe (see below). `frontend` now always resolves and sends this for every request (its Puzzle Page URLs carry a date segment), though DCR still treats the field as optional and simply omits the display/context value when absent. |
197+
| `instance.moreFromPuzzlesAndGames` | `PuzzleItem[]?` (from `src/types/puzzlesPage.ts`) | Rendered as a plain "More from Puzzles & games" list when present and non-empty. |
198198

199199
### User/context info passed to the puzzle iframe
200200

@@ -203,18 +203,20 @@ about the current reader to the puzzle provider two ways:
203203

204204
- As a single JSON-encoded `guardian-puzzle-context` query parameter on the
205205
iframe `src` (e.g.
206-
`?set=guardian-sudoku-easy&embed=1&idx=1&guardian-puzzle-context=%7B%22userId%22%3Anull%2C%22darkMode%22%3Afalse%7D`,
207-
which decodes to `{"userId":null,"darkMode":false}`), present from the
208-
iframe's very first request. Unlike the parameter's previous `userId`-only
209-
form, this is always included: the context shape always carries both
210-
fields, so there's no "nothing to add" case to omit it for.
206+
`?set=guardian-sudoku-easy&embed=1&idx=1&guardian-puzzle-context=%7B%22userId%22%3Anull%2C%22darkMode%22%3Afalse%2C%22puzzleDate%22%3Anull%7D`,
207+
which decodes to `{"userId":null,"darkMode":false,"puzzleDate":null}`),
208+
present from the iframe's very first request. Unlike the parameter's
209+
previous `userId`-only form, this is always included: the context shape
210+
always carries all three fields, so there's no "nothing to add" case to
211+
omit it for.
211212
- Via `window.postMessage({ type: 'guardian-puzzle-context', context }, '*')`
212213
(the `PuzzleContextMessage` shape), sent to the iframe once it has loaded.
213214

214215
```ts
215216
interface PuzzleContext {
216217
userId: string | null;
217218
darkMode: boolean;
219+
puzzleDate: string | null;
218220
}
219221
```
220222

@@ -240,6 +242,16 @@ interface PuzzleContext {
240242
When `darkModeAvailable` is `false`, `darkMode` is always `false` and the
241243
media query isn't even consulted.
242244

245+
- **`puzzleDate`** is `instance.puzzleDate` passed straight through
246+
unformatted (the raw `YYYY-MM-DD` string, not the "11 September 2026"
247+
display text rendered next to the title), so third-party providers get
248+
the machine-readable form. `null` when `instance.puzzleDate` is absent.
249+
DCR does not parse the Puzzle Page URL or own the date-in-path/
250+
redirect-to-archive logic itself: it purely receives whatever date
251+
`frontend` resolved and sent in the request payload, and passes it on. See
252+
`frontend`'s own documentation for how it resolves and redirects on the
253+
date-in-URL structure.
254+
243255
The iframe reloads automatically whenever either half of the context
244256
changes while the reader is already on the page: sign in, sign out,
245257
switching accounts, or the reader's OS switching light/dark theme. The
@@ -308,14 +320,14 @@ darkMode: boolean } }` and the `?guardian-puzzle-context=<JSON>` query
308320
- **Responsive/mobile layout has not been explicitly verified** for Puzzle
309321
Page or the puzzle iframes themselves (which are entirely provider-
310322
controlled content).
311-
- **`instance.puzzleDate` is accepted but not yet used for anything.** It is
312-
validated as an optional string and otherwise ignored. DCR always shows
313-
whichever puzzle the resolved `slug`'s provider iframe URL happens to
314-
serve "live" today, regardless of `puzzleDate`. Wiring this into the
315-
actual iframe URL (so a specific past date's puzzle is shown) is deferred
316-
to V1, pending investigation into whether/how each provider's iframe URL
317-
scheme (AmuseLabs, Wordiply) supports requesting a specific historical
318-
date at all.
323+
- **DCR does not validate that `puzzleDate` is a real, sensible calendar
324+
date.** Beyond the existing shape check (a non-empty string), nothing in
325+
DCR confirms `puzzleDate` is an actual calendar date (e.g. rejecting a
326+
nonexistent `"2026-02-30"`) or a sensible one (e.g. rejecting a wildly
327+
out-of-range date). Deeper, format-level validation of the date-in-URL
328+
value is `frontend`'s responsibility at the route level (per its own
329+
task); true calendar/business-logic validity (e.g. "did this puzzle
330+
actually exist on this date") is not validated anywhere in the stack yet.
319331
- **DCR's `/PuzzlePage` endpoint itself still has no route-level access
320332
control** (unchanged from before). `frontend`'s existing
321333
`PuzzlesHubExperiment`/`puzzles-new-hub` AB test gate decides whether a

0 commit comments

Comments
 (0)