Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
f073b2c
docs: add multi-phase plan for generic Game page
andresilva-guardian Sep 7, 2026
0d2eccd
feat(game-page): add GameConfig registry, FEGamePageType, and AB gate
andresilva-guardian Sep 7, 2026
e64e1fe
feat(game-page): add GameLayout, GamePage, and iframe/component regis…
andresilva-guardian Sep 7, 2026
8f67895
feat(game-page): add handler, render, and POST /GamePage route
andresilva-guardian Sep 7, 2026
4de9def
test(game-page): add fixtures and tests for handler/gate/registry
andresilva-guardian Sep 7, 2026
47b4e51
docs: finalize Game page plan with manual validation steps and fronte…
andresilva-guardian Sep 7, 2026
f4c21ef
Remove game-page-experiment AB gate (routes will be mapped in a separ…
andresilva-guardian Sep 7, 2026
bd8560e
Fix GameLayout visual parity: PDF link and section label link
andresilva-guardian Sep 7, 2026
cc86529
docs: add docs/game-page.md as the real reference doc for Game Page
andresilva-guardian Sep 8, 2026
c7110cd
docs(game-page): list local access command for each of the 12 slugs
andresilva-guardian Sep 8, 2026
ab6cb4d
fix: dev server root sendFile 404s when repo path contains a dot-segm…
andresilva-guardian Sep 8, 2026
3fc8b78
Remove crossword from Game Page: simplify GameConfig registry + FEGam…
andresilva-guardian Sep 10, 2026
5692b3f
Remove crossword rendering code from GameLayout
andresilva-guardian Sep 10, 2026
354e3e1
Remove crossword fixtures/test cases for Game Page
andresilva-guardian Sep 10, 2026
bb0f5ba
docs(game-page): update for the iframe-only scope reduction
andresilva-guardian Sep 10, 2026
3c35ae2
Rename Game Page -> Puzzle Page throughout (PR review: terminology)
andresilva-guardian Sep 10, 2026
80d6981
Remove non-V0 puzzles from the registry (PR review feedback)
andresilva-guardian Sep 10, 2026
be85602
Move Puzzle Page validation into its own file (PR review feedback)
andresilva-guardian Sep 10, 2026
8cd7f6d
Pass user ID to puzzle iframe + postMessage + reload on login (PR rev…
andresilva-guardian Sep 10, 2026
3bd0204
docs: consolidate into a single docs/puzzle-page.md reference
andresilva-guardian Sep 10, 2026
9b4404e
Add optional puzzleDate field to FEPuzzlePageType (PR review)
andresilva-guardian Sep 11, 2026
1ba5a53
docs(puzzle-page): reflect puzzleDate, frontend gating, and new publi…
andresilva-guardian Sep 11, 2026
3a800b6
docs+fixtures: reflect frontend's new top-level puzzle-page URL shape
andresilva-guardian Sep 11, 2026
b60a73d
Replace userId-only iframe context with combined guardian-puzzle-context
andresilva-guardian Sep 14, 2026
975c1e6
docs(puzzle-page): reflect combined guardian-puzzle-context mechanism
andresilva-guardian Sep 14, 2026
cfaa3b3
Add curated per-puzzle SEO description to PuzzleConfig registry
andresilva-guardian Sep 14, 2026
872b415
Wire PuzzleConfig.description into render.puzzlePage.web.tsx + OG/Twi…
andresilva-guardian Sep 14, 2026
067215b
docs(puzzle-page): document SEO description/OG/Twitter wiring + futur…
andresilva-guardian Sep 14, 2026
4af65b6
Add optional image field to PuzzleConfig for share/preview images
andresilva-guardian Sep 14, 2026
4797dd7
Wire optional PuzzleConfig.image into og:image/twitter:image + test c…
andresilva-guardian Sep 14, 2026
72ddf81
docs(puzzle-page): document optional PuzzleConfig.image field
andresilva-guardian Sep 14, 2026
1e07c76
Add cumulative v1/v2 AB test entries for Puzzles & Games rollout
andresilva-guardian Sep 14, 2026
5130cc5
Add cumulative isPuzzlesHubV1Enabled/isPuzzlesHubV2Enabled gate helpers
andresilva-guardian Sep 14, 2026
ebc184d
Gate the "More from Puzzles & Games" rail behind isPuzzlesHubV1Enabled
andresilva-guardian Sep 14, 2026
5c59c20
docs(puzzle-page): document 3-tier v0/v1/v2 rollout gating structure
andresilva-guardian Sep 14, 2026
53c842c
Remove em dashes across branch per new house style rule
andresilva-guardian Sep 14, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
82 changes: 82 additions & 0 deletions ab-testing/config/abTests.ts
Original file line number Diff line number Diff line change
Expand Up @@ -157,6 +157,26 @@ const ABTests: ABTest[] = [
groups: ["control", "variant"],
shouldForceMetricsCollection: false,
},
/**
* Puzzles & Games rollout, tier v0 (the master switch).
*
* Gates the baseline Puzzles & Games experience: the new Puzzles Hub
* page, and the 6 V0 puzzle pages (sudoku easy/medium/hard/killer,
* word-wheel, wordiply). At v0, there is no archive, no calendar, no
* progress indicators, no sign-in-to-track-progress prompt, no "more
* from puzzles" rail, and the hub's sub-nav has no links yet.
*
* This is the master switch for the whole Puzzles & Games experience:
* turning it off (or down to 0%) hides everything: the hub, the V0
* puzzle pages, and (by the cumulative design below) every later tier
* too, since v1/v2 only take effect when this is also enabled.
*
* See `puzzles-new-hub-v1`/`puzzles-new-hub-v2` below for the later
* rollout tiers, and `src/lib/puzzlesHubVersionExperiment.ts` /
* `src/lib/puzzlesHubExperiment.ts` in dotcom-rendering for the
* corresponding cumulative gate-check helpers
* (`isPuzzlesHubEnabled`/`isPuzzlesHubV1Enabled`/`isPuzzlesHubV2Enabled`).
*/
{
name: "puzzles-new-hub",
description: "Rollout of the new Puzzles Hub experience",
Expand All @@ -169,6 +189,68 @@ const ABTests: ABTest[] = [
groups: ["control", "variant"],
shouldForceMetricsCollection: false,
},
/**
* Puzzles & Games rollout, tier v1 (w/c 12 Oct launch).
*
* Only takes effect when `puzzles-new-hub` (v0) is ALSO enabled for the
* reader. This test does nothing on its own, by design, so the
* rollout can never end up in an inconsistent state (e.g. v1 features
* showing while the v0 baseline they build on is switched off).
*
* On top of v0, this tier activates: the full hub sub-nav links (to
* /word-games, /logic-puzzles, /trivia-and-quizzes), a
* sign-in-to-track-progress message, a calendar/archive view for
* crosswords/logic-puzzles/word-games (not Wordiply, which has no
* archive), progress indicators (Available/Completed), the "More from
* Puzzles & Games" related-content rail, newsletter signup, and
* changes to the existing crossword page (print CTA repositioning, a
* "play other puzzles" container).
*
* To roll back from v1 to v0 without a deploy: flip this test's
* `audienceSize` to `0 / 100` (or `status` to `"OFF"`) while leaving
* `puzzles-new-hub` untouched.
*/
{
name: "puzzles-new-hub-v1",
description:
"Rollout of the v1 Puzzles & Games features (w/c 12 Oct), on top of the puzzles-new-hub v0 baseline",
owners: ["puzzles.team@guardian.co.uk"],
status: "ON",
expirationDate: "2026-12-31",
type: "server",
audienceSize: 0 / 100,
audienceSpace: "A",
groups: ["control", "variant"],
shouldForceMetricsCollection: false,
},
/**
* Puzzles & Games rollout, tier v2 (future, no launch date confirmed
* yet as of this writing).
*
* Only takes effect when BOTH `puzzles-new-hub` (v0) AND
* `puzzles-new-hub-v1` are ALSO enabled for the reader, same
* cumulative-by-design principle as v1 above, applied one tier further.
*
* On top of v0+v1, this tier activates: the On the Ball and Film Reveal
* iframe games (Trivia and Quizzes group), a "Most played" container,
* EventKit-driven navigation, migrating existing crossword pages onto
* the new Puzzle Page template, and search-engine mobile app nudges.
*
* Kept at 0% until that work begins; there is nothing to roll back yet.
*/
{
name: "puzzles-new-hub-v2",
description:
"Rollout of the v2 Puzzles & Games features (no date confirmed yet), on top of the puzzles-new-hub/puzzles-new-hub-v1 baseline",
owners: ["puzzles.team@guardian.co.uk"],
status: "ON",
expirationDate: "2026-12-31",
type: "server",
audienceSize: 0 / 100,
audienceSpace: "A",
groups: ["control", "variant"],
shouldForceMetricsCollection: false,
},
{
name: "commercial-prebid-transaction-ids",
description:
Expand Down
431 changes: 431 additions & 0 deletions dotcom-rendering/docs/puzzle-page.md

Large diffs are not rendered by default.

116 changes: 116 additions & 0 deletions dotcom-rendering/fixtures/manual/puzzlePage.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
import type { PuzzleConfig } from '../../src/model/puzzles/puzzleConfigs';
import { puzzleConfigs } from '../../src/model/puzzles/puzzleConfigs';
import type { FEPuzzlePageType } from '../../src/types/puzzlePage';
import type { PuzzleItem } from '../../src/types/puzzlesPage';
import { Standard } from '../generated/fe-articles/Standard';

const sampleMoreFromPuzzlesAndGames: PuzzleItem[] = [
{
id: 'sudoku-easy-daily',
title: 'Sudoku easy',
type: 'sudoku-easy',
set: 'all',
cardVariant: 'compact',
cadence: 'Daily',
slug: 'sudoku-easy',
},
{
id: 'word-wheel-daily',
title: 'Word wheel',
type: 'word-wheel',
set: 'all',
cardVariant: 'compact',
cadence: 'Daily',
slug: 'word-wheel',
},
];

/**
* Illustrative canonical URL matching `frontend`'s public URL shape for
* puzzle pages (top-level, mirroring crosswords, distinct from the
* `/puzzles-and-games` hub), purely a fixture value, not something DCR
* validates or enforces the shape of.
*/
const canonicalUrlForSlug = (slug: string): string => {
const sudokuMatch = /^sudoku-(.+)$/.exec(slug);
if (sudokuMatch) {
return `https://www.theguardian.com/sudoku/${sudokuMatch[1]}`;
}
return `https://www.theguardian.com/${slug}`;
};

/**
* Builds a `FEPuzzlePageType` fixture for the given `slug`, defaulting to a
* generic instance for that slug's `PuzzleConfig`. Pass `overrides` to
* customise individual fields (deep-merged only one level for `instance`).
*/
export const createPuzzlePage = (
slug: string,
overrides: Partial<FEPuzzlePageType> = {},
): FEPuzzlePageType => {
const puzzleConfig = puzzleConfigs[slug];

if (!puzzleConfig) {
throw new Error(`Unknown puzzle slug in fixture: ${slug}`);
}

return {
id: `puzzle-page-${slug}`,
slug,
webTitle: `${slug} | The Guardian`,
config: {
...Standard.config,
contentType: 'Game',
// DCR no longer gates /PuzzlePage on any AB test participation
// (the former 'game-page-experiment' gate was removed; routes
// will be mapped/exposed via a different project instead), so
// this is left empty rather than implying any particular value
// is required.
serverSideABTests: {},
},
nav: Standard.nav,
pageFooter: Standard.pageFooter,
canonicalUrl: canonicalUrlForSlug(slug),
editionId: Standard.editionId,
instance: {
title: `${slug} puzzle`,
puzzleDate: '2026-09-11',
moreFromPuzzlesAndGames: sampleMoreFromPuzzlesAndGames,
},
...overrides,
};
};

/** One fixture per supported slug, for local dev preview and tests. */
export const puzzlePageFixtures: Record<string, FEPuzzlePageType> = Object.keys(
puzzleConfigs,
).reduce<Record<string, FEPuzzlePageType>>((acc, slug) => {
acc[slug] = createPuzzlePage(slug);
return acc;
}, {});

/**
* A fixture-only illustrative preview/share image URL. None of the real
* `puzzleConfigs` registry entries have a real image configured yet (see
* docs/puzzle-page.md) - this exists purely so both the with-image and
* without-image branches of Puzzle Page's OG/Twitter metadata have fixture
* and test coverage, without inventing a placeholder image for the real
* registry itself.
*/
export const samplePuzzleImageUrl =
'https://i.guim.co.uk/img/media/fixture-only-example/puzzle-preview.jpg?width=1200&height=630&quality=85';

/**
* Returns a copy of `slug`'s real `PuzzleConfig` with `image` set to
* `samplePuzzleImageUrl` - a fixture-only variant for exercising the
* with-image branch (the real registry entry itself is left untouched).
*/
export const createPuzzleConfigWithImage = (slug: string): PuzzleConfig => {
const puzzleConfig = puzzleConfigs[slug];

if (!puzzleConfig) {
throw new Error(`Unknown puzzle slug in fixture: ${slug}`);
}

return { ...puzzleConfig, image: samplePuzzleImageUrl };
};
Loading
Loading