Skip to content

Share Links (Game + Guess the Move)

Dice Chess Trainer supports two share flows:

  • Game position sharing from View/Train game playback.
  • Puzzle sharing from Practice mode (Guess the Move).

Both flows produce a public Open Graph landing page and then redirect to the PWA.


Click 🔗 Share in the top navigation bar.

EnvironmentBehaviour
Mobile / installed PWAOpens the native OS share sheet (navigator.share)
Desktop browserCopies the link to clipboard; a toast confirms success

The generated URL depends on current mode:

  • Game playback: /api/share/{game_id}?move={move_index}
  • Practice mode: /api/share/puzzle/{puzzle_id}

sequenceDiagram
    actor User
    participant PWA as Frontend PWA
    participant Share as /api/share/:gameId
    participant Games as /api/games/:gameId/preview.png

    User->>PWA: Click Share at move N
    PWA-->>User: /api/share/<gameId>?move=N

    User->>Share: Open link
    Share-->>User: HTML with og:* + JS redirect

    Note over User: Social crawler fetches preview
    User->>Games: GET /api/games/<gameId>/preview.png?move=N
    Games-->>User: board image

    User->>PWA: /?game=<gameId>&move=N
    PWA-->>User: game opened at move N
sequenceDiagram
    actor User
    participant PWA as Frontend PWA
    participant Share as /api/share/puzzle/:puzzleId
    participant Public as /api/v1/training-puzzles/:puzzleId/*

    User->>PWA: Click Share in Practice mode
    PWA-->>User: /api/share/puzzle/<puzzleId>

    User->>Share: Open link
    Share-->>User: HTML with og:* + JS redirect

    Note over User: Social crawler fetches preview
    User->>Public: GET /api/v1/training-puzzles/<id>/preview.png
    Public-->>User: PNG (or SVG fallback)

    User->>PWA: /?puzzle=<puzzleId>
    PWA->>Public: GET /api/v1/training-puzzles/<id>/public
    Public-->>PWA: puzzle payload
    PWA-->>User: isolated Guess the Move screen

App.svelte checks window.location.search for puzzle during onMount.

If present:

  • Render SharedPuzzleView directly.
  • Skip the main navigation shell.
  • Do not run normal game URL sync (game/move) effects.

This guarantees a single-purpose public screen.

SharedPuzzleView is intentionally limited:

  • Loads only one puzzle via ApiClient.getPublicPuzzle(puzzleId).
  • Renders board + dice + Reset / Check Answer.
  • Validates by board-part FEN equality (same strategy as practice mode).
  • If incorrect, replays solution moves visually.
  • No “next puzzle”, no bookmarks, no settings/admin navigation.

  • Returns minimal HTML with og:title, og:description, og:image, twitter:card.
  • Redirects to /?game={game_id}&move={move}.
  • og:image points to /api/games/{game_id}/preview.png.
  • Returns minimal HTML with puzzle-specific OG metadata.
  • Redirects to /?puzzle={puzzle_id}.
  • og:image points to /api/v1/training-puzzles/{puzzle_id}/preview.png.
  • When linked puzzle has game_id, metadata includes players and ratings.

  • Endpoint: /api/games/{game_id}/preview.png
  • Source: gameMoveHistoryStateMap[move] (fen, dice, clocks)
  • Render path: fen_to_svg(...) -> cairosvg.svg2png(...)
  • Endpoint: /api/v1/training-puzzles/{puzzle_id}/preview.png
  • Source: TrainingPuzzle.normalized_initial_fen + puzzle dice + optional game metadata
  • Render path: High-quality python-chess SVG board composited into a standard layout with player metadata and unicode dice symbols -> cairosvg.svg2png(...)
  • Fallback: if cairo is missing at runtime, endpoint returns image/svg+xml

TopNavigationBar.svelte decides share type at click time:

  • If practice mode is active and currentPuzzle.id exists -> share puzzle URL.
  • Otherwise -> share game URL with current move index.

No API request is needed to build the URL.


  • frontend-pwa/src/App.svelte
  • frontend-pwa/src/views/SharedPuzzleView.svelte
  • frontend-pwa/src/components/TopNavigationBar.svelte
  • frontend-pwa/src/lib/api/client.ts
  • backend-api/src/dicechess_trainer/routers/share.py
  • backend-api/src/dicechess_trainer/routers/training.py