Skip to content

feat : share a game via a link - #97

Open
yluom wants to merge 1 commit into
GuillaumeSD:mainfrom
yluom:feat/share-game-link
Open

feat : share a game via a link#97
yluom wants to merge 1 commit into
GuillaumeSD:mainfrom
yluom:feat/share-game-link

Conversation

@yluom

@yluom yluom commented Aug 21, 2026

Copy link
Copy Markdown

What

Adds a share button to the analysis toolbar. It copies a link that contains the whole game, so a game can be shared without saving it or uploading it anywhere.

Opening the link restores the game (and the board orientation) on the analysis page.

This picks up the idea of #86 and rebases it on current main.

How

  • src/lib/shareGame.ts builds and reads the link. The PGN is compressed with lz-string into a pgn query param.
  • Loading is handled in LoadGame, in the same useEffect that already handles gameId and lichessGameId, and reuses the existing orientation param.
  • The share param is validated before use: it must decompress and parse as a valid PGN, otherwise it is ignored. A broken or truncated link leaves the page in its normal empty state instead of throwing.
  • Clipboard feedback lives in a small useCopyToClipboard hook (the tooltip and icon confirm the copy for 2s). The existing Copy PGN button now uses it too, so both clipboard buttons behave the same way.

Why compress

lz-string's compressToEncodedURIComponent outputs URL-safe characters directly, so there is no percent-escaping on top of it. On a 61-ply game:

length
raw PGN 448
encodeURIComponent(pgn) 754
compressToEncodedURIComponent(pgn) 392

So a shared link is roughly half the size of one carrying a plain PGN, and it grows slowly since PGN text compresses well. No @types/lz-string is needed — lz-string ships its own typings.

Notes

  • No URL length cap. Even a long annotated game stays well under the limits of browsers and messaging apps, so a cap would only add a failure mode to explain.
  • Sharing uses getGameToSave(game, board), the same helper as the save button, so a game played on the board gets proper headers before being shared.

Tested

  • npm run lint (eslint + tsc --noEmit) and npm run build pass.
  • Manually: sharing a game produces a link that reloads the same game with the right orientation, and a deliberately corrupted ?pgn= param is ignored without breaking the page.

If the URL ever needs to be shorter

Compressing the PGN as text is the cheapest thing that works, and it keeps headers, comments and variations. For the record, here is what the alternatives cost. Sizes are for an 80-ply game; decode is timed in headless Chrome under Emulation.setCPUThrottlingRate at ×8, roughly a mid-tier phone. loadPgn alone costs 4.9 ms there, which is the floor any scheme has to pay.

option URL decode +
lz-string on the PGN — this PR 538 4.9 ms free: decoding costs exactly what loadPgn already costs. Keeps headers and comments longest URL
strip headers and move numbers, then compress ~unchanged ~2× shorter for a few lines, same format, no new dependency loses names/date/result unless re-added as separate params
native CompressionStream('deflate-raw') ~same ~unchanged drops the lz-string dependency async, and only ~5% better than lz-string once headers are stripped, so it buys little on its own
binary: from/to on 12 bits per ply 164 16.6 ms 3.4× shorter, ~1 frame, decoder only has to apply moves drops everything that is not a move; needs a versioned format
binary: index in the sorted legal-move list 76 39.3 ms 7× shorter, ~5.5 bits per ply — the practical minimum 8× the decode cost, and 306 ms for a 273-ply game on a low-end phone. Couples the link format to chess.js move generation, so an upgrade can silently break links already shared
short link backed by storage ~30 one request URL size stops depending on game length server state, retention and privacy, and links no longer work forever by construction

Two dead ends I checked: deflating either binary stream makes it bigger (it is already near-uniform), and base64url's fixed 33% overhead can only be cut by ~5% with a URL-safe base85 alphabet.

If this ever needs to shrink, from/to looks like the sweet spot — the legal-move index is the true minimum but has the worst ratio. Happy to do either as a follow-up; I kept this PR to the simple version on purpose.

Add a toolbar button that copies a link containing the current game, so a
game can be shared without saving it anywhere. The PGN is compressed with
lz-string into a `pgn` query param, which halves the link length compared
to a plain percent-encoded PGN.

Opening such a link loads the game through the existing url handling in
`LoadGame`, next to `gameId` and `lichessGameId`, and reuses the existing
`orientation` param.

Clipboard feedback is factored into a `useCopyToClipboard` hook so that the
share and the copy PGN buttons both acknowledge the copy.
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