@@ -53,9 +53,22 @@ contains exactly 6 slugs, all rendered via the generic sandboxed
5353
5454Codeword, futoshiki, suguru, and the trivia/quizzes puzzles (on-the-ball,
5555film-reveal) were removed from the registry for V0 and may return later.
56- All AmuseLabs-hosted entries share one URL template
57- (` https://tg.amuselabs.com/guardian/date-picker?set=guardian-{slug}&embed=1&idx=1 ` ),
58- differing only by the ` {slug} ` substitution.
56+ ** Each entry's ` iframe.url ` is its own complete, independently-written URL,
57+ there is deliberately no shared URL template or ` {slug} ` -style
58+ substitution mechanism.** There used to be one: every AmuseLabs-hosted
59+ entry's URL was built from a shared template
60+ (` https://tg.amuselabs.com/guardian/date-picker?set=guardian-{slug}&embed=1&idx=1 ` )
61+ by substituting DCR's own ` slug ` in for AmuseLabs' ` set=guardian-* ` query
62+ param. That was a real, live bug: nothing guarantees a provider's own
63+ naming convention matches our internal slug, and it already silently
64+ didn't for ` sudoku-killer ` (its real, confirmed AmuseLabs ` set ` is
65+ ` killer-sudoku-medium ` , not ` sudoku-killer ` , a different word order plus
66+ an unexplained "-medium" suffix that is genuinely part of the real,
67+ working identifier). The fix (confirmed against the native Android/iOS
68+ apps' own real, working AmuseLabs integration) was to remove the shared
69+ template entirely, not just patch that one instance: every entry now
70+ specifies its own complete, independent, hardcoded URL, so a future
71+ change to one entry can never silently or accidentally affect another.
5972
6073### Hitting it locally
6174
@@ -117,16 +130,23 @@ Both steps are config-only. The layout does not need any changes for a new
117130iframe-based slug:
118131
1191321 . Add a new key to ` src/model/puzzles/puzzleConfigs.ts ` 's ` puzzleConfigs `
120- record (` slug ` , ` puzzleGroup ` , ` iframe: { provider, urlTemplate } ` ,
133+ record (` slug ` , ` puzzleGroup ` , ` iframe: { provider, url } ` ,
121134 ` shareEnabled ` , ` printEnabled ` , ` hasArchive ` , ` title ` , ` description ` ,
122135 optional ` image ` ). If it's another AmuseLabs-hosted puzzle, reuse the
123- ` amuseLabsPuzzle(slug, puzzleGroup, title, description) ` helper (note:
124- this helper doesn't take ` image ` , set it afterwards on the returned
125- object if/when a real image is available for that puzzle).
126- ` validatePuzzleConfigs ` runs once at module load and throws immediately
127- if the entry is malformed (mismatched ` slug ` , unknown ` puzzleGroup ` ,
128- empty ` iframe.provider ` /` iframe.urlTemplate ` , empty ` title ` /
129- ` description ` , or a present-but-empty ` image ` ).
136+ ` amuseLabsPuzzle(slug, puzzleGroup, title, description, url) ` helper
137+ (note: this helper doesn't take ` image ` , set it afterwards on the
138+ returned object if/when a real image is available for that puzzle).
139+ ** ` iframe.url ` must be that puzzle's own complete, explicit iframe URL,
140+ confirmed against the actual provider (or a source that has itself
141+ confirmed it against the provider, e.g. the native apps' own working
142+ integration), not derived from ` slug ` or copied from another entry.**
143+ Never assume a provider's own naming convention matches our internal
144+ slug: the killer-sudoku incident above is a direct, confirmed example
145+ of that assumption silently being wrong. ` validatePuzzleConfigs ` runs
146+ once at module load and throws immediately if the entry is malformed
147+ (mismatched ` slug ` , unknown ` puzzleGroup ` , missing ` iframe.provider ` ,
148+ a missing/empty/non-absolute ` iframe.url ` , empty ` title ` /` description ` ,
149+ or a present-but-empty ` image ` ).
130150 ** Write real, curated copy for ` title ` /` description ` ** , sourced from
131151 the product team's SEO spreadsheet for that puzzle (see "SEO" below for
132152 the exact ` {date} ` templating mechanism). It becomes the page's
@@ -317,7 +337,25 @@ interface PuzzleContext {
317337 ` frontend ` 's own documentation for how it resolves and redirects on the
318338 date-in-URL structure.
319339
320- The iframe reloads automatically whenever either half of the context
340+ ** A separate, plain ` uid=<userId> ` query parameter is also appended
341+ alongside ` guardian-puzzle-context ` ** , only when the reader is signed in
342+ (omitted entirely, not sent as ` uid=null ` or empty, when signed out). This
343+ is a genuinely different, independently-confirmed mechanism from
344+ ` guardian-puzzle-context ` above: the native (Android/iOS) apps' own real,
345+ working AmuseLabs integration appends ` &uid=<value> ` as a plain query
346+ parameter when the user is authenticated ("If the user is authenticated,
347+ we add &uid=<puzzleId >"), and DCR adopted the same query parameter
348+ name/pattern once confirmed. It is additive, not a replacement,
349+ ` guardian-puzzle-context ` still carries dark mode and puzzle date, for
350+ which there is no separately-confirmed mechanism yet. ` uid ` 's value is
351+ sourced identically to ` guardian-puzzle-context.userId `
352+ (` idToken.claims.legacy_identity_id ` ); the native apps call their
353+ equivalent value a "puzzleId", but there is no independent confirmation
354+ that identifier format matches ours, only that this exact query parameter
355+ name/pattern is what they use for their own equivalent value (see "Open
356+ questions" below).
357+
358+ The iframe reloads automatically whenever any part of the context or ` uid `
321359changes while the reader is already on the page: sign in, sign out,
322360switching accounts, or the reader's OS switching light/dark theme. The
323361component subscribes to both auth state changes
@@ -332,18 +370,30 @@ fires again after every such reload too.
332370
333371## Open questions / known limitations
334372
335- - ** The ` PuzzleContextMessage ` shape needs confirming with
336- AmuseLabs/Wordiply.**
373+ - ** The ` PuzzleContextMessage ` shape (` guardian-puzzle-context ` ) is still
374+ unconfirmed with AmuseLabs/Wordiply; ` uid ` specifically is now
375+ confirmed.**
337376 `{ type: 'guardian-puzzle-context', context: { userId: string | null,
338- darkMode: boolean } }` and the ` ?guardian-puzzle-context=<JSON >` query
339- parameter are DCR's proposal, documented in code
340- (` src/components/PuzzleIframe.island.tsx ` ), but neither has been confirmed
341- against what AmuseLabs or Wordiply actually expect to receive, including
342- whether ` legacy_identity_id ` (rather than the OIDC ` sub ` claim) is the
343- right identifier format for them, and whether either provider's iframe
344- even supports a dark-mode signal in the first place (see the dark-mode
345- bullet below). This needs external coordination before relying on it for
346- anything beyond best-effort personalisation.
377+ darkMode: boolean, puzzleDate: string | null } }` and the
378+ ` ?guardian-puzzle-context=<JSON> ` query parameter remain DCR's own
379+ proposal, documented in code (` src/components/PuzzleIframe.island.tsx ` ),
380+ neither has been confirmed against what AmuseLabs or Wordiply actually
381+ expect to receive, including whether ` legacy_identity_id ` (rather than
382+ the OIDC ` sub ` claim) is the right identifier format for the ` userId `
383+ field within it, and whether either provider's iframe even supports a
384+ dark-mode signal in the first place (see the dark-mode bullet below).
385+ The separate, plain ` uid=<userId> ` query parameter (see "User/context
386+ info passed to the puzzle iframe" above), by contrast, ** is** confirmed:
387+ sourced from the native (Android/iOS) apps' own real, working AmuseLabs
388+ integration, which uses this exact query parameter name/pattern for
389+ their own equivalent identity value. That confirmation does not extend
390+ to the identifier _ format_ : the native apps call their value a
391+ "puzzleId", and there is no independent confirmation that
392+ ` legacy_identity_id ` is the same format as whatever they send, only that
393+ the ` uid ` query parameter itself, and the pattern of "include only when
394+ signed in, omit entirely when signed out", is confirmed correct. This
395+ needs external coordination before relying on ` guardian-puzzle-context `
396+ for anything beyond best-effort personalisation.
347397- ** The auth-state-change subscription is a new mechanism in this
348398 codebase.** ` subscribeToAuthStateChange() ` uses the underlying
349399 ` @guardian/identity-auth ` client's own public ` authStateManager.subscribe `
0 commit comments