eswat2 · sudoku prototypes · september 2026

Ten Sudokus

One puzzle, one Rust API, ten implementations across thirteen repositories. Eight are hand-built framework ports sharing a single component tree — and one of those eight is the original every other repo descends from. Two are single-file apps written entirely by Claude Code. This is what actually separates them.

the clue the rest were copied from · apr 2021
Stencil
proto-sudoku-wc → wc-sudoku
Web Componentssudoku-cc
Jurissudoku-juris
Preactsudoku-p4t
Reactsudoku-r3t
Solidsudoku-sld
Sveltesudoku-s4e
Vuesudoku-vue
Litsudoku-lit
ucesudoku-uce

Nine implementations fill one 3×3 box exactly. The tenth sits outside it, because it came first: the Stencil build predates every other repo and every one of them carries its component names — alert, cell, keys, tool-bar, tw-label.

The two in red are the new ones, styled the way Cell styles a selected square in the apps themselves, on the clrs.cc palette those apps actually ship.

the original, Apr 2021
hand-built framework port
generated by Claude Code

Not shown: sudoku-p4t-wc and sudoku-s4e-wc, custom-element variants of the Preact and Svelte builds. They are the closest cousins to sudoku-cc and are covered below.


01What all ten have in common

Less separates them than you would expect. Every one of the ten plays the same game against the same backend, and every one of them survives a reload.

Puzzle source
Every implementation fetches https://sudoku-rust-api.vercel.app/api/puzzle — your own Rust API. The response is an 81-character puzzle string and a ref field that is base64 of the 81 solution digits. Nobody solves anything; atob does the work.
Persistence
All ten keep game state in localStorage, and all ten namespace the key by project — proto-sudoku::data, sudoku-r3t::app-data, sudoku-cc:game-state, sudoku-juris:game-state. That is not tidiness: GitHub Pages puts every project on one shared eswat2.github.io origin, and an unqualified key had sudoku-cc adopting and deleting the Juris app's save.
Fallback
Every implementation carries a hard-coded puzzle so a dead API never blocks a game.
Hosting
Vercel, in all cases. How they get there is one of the sharper differences — see §04.

02Two families, one ancestor

the eight · one app, ported

A framework comparison

These are not eight Sudoku apps. They are one Sudoku app expressed eight ways, deliberately held constant so the framework is the only variable.

  • The same component tree, file for file: Alert, Alerts, Button, Cell, Eswat2Io, Header, Keys, Spinner, SudokuBoard, ToolBar, TwLabel, plus Alien and Fingerprint icons.
  • The same utils layer: bag, labels, store, tw-version, tw.
  • The same toolchain: pnpm, Vite, Tailwind with proto-tailwindcss-clrs, PostCSS, ky, bumpp, Prettier, cspell, only-allow.
  • The same npm scripts, down to bump, twx, cc and spell.

Only the state layer really moves: @stencil/store, Preact signals, Zustand, solid-js/store, a Svelte writable, Pinia, and nanostores for both Lit and uce.

The Stencil build came first — April 2021, five months before the next one. Its component filenames are the ones every later port kept, which makes it less a sibling than the template.

the two · one game, rebuilt

A no-build experiment

These are not ports of the eight. They rebuild the game from scratch under a constraint the others never had: one file, no build step.

  • sudoku-cc — a single SudokuBoard extends HTMLElement in open Shadow DOM. Zero dependencies. No package.json, no node_modules, nothing to install.
  • sudoku-juris — a port of sudoku-cc onto Juris 0.9.0, pulled from unpkg at runtime. Five components over one reactive store. Also no build.
  • Written by Claude Code, not by hand. Per the READMEs: roughly 200 iterations for sudoku-cc, all code generated, no manual edits.
  • They document themselves. sudoku_readme, sudoku_architecture and sudoku_specification in one; a blog post and a Juris-vs-Web-Components essay in the other.

Their point of comparison is each other — native Web Components against a tiny reactive framework — not the eight.


03Where the code actually diverges

Four differences matter more than the framework choice. Each changes how you'd read or edit the code.

  The eight framework ports sudoku-cc & sudoku-juris
Board model A flat list of 81 cell objects with precomputed geometry — Maps for row, column and box, plus pick, locs, keys, solved. Peer highlighting is a lookup. Three parallel 9×9 number[][] arraysoriginalPuzzle (immutable clues, gates editability), currentPuzzle (live board), solutionPuzzle (answer key, may be null). 0 means empty. Peer highlighting is recomputed arithmetically across all 81 cells on every selection, and goes further than the ports' — it separates row, column and box, and picks out every cell already holding the selected number.
Entry & validation A Keys component in its own row under the board: select a cell and it lists the values its row, column and box don't already use, plus a delete key when the cell holds one of your entries. Validation is deferred and manual: a Check button sits in the toolbar from the moment a puzzle loads — the condition is list.length === 81 && !solved, the board's cell count rather than how many you have filled — and pressing it erases every wrong entry. Only the win is announced, as an Alert; a failed check says nothing. The same contextual keypad, rendered inline beside New Puzzle rather than as its own component — getValidNumbers eliminates peers exactly as computeKeys does. Validation is immediate: each placement is checked against the decoded solution as you make it, and a wrong one reverts itself after a 1s timeout. The same correction the ports apply — both erase what doesn't match — without a button and without the wait.
Rendering Whatever the framework does. Components return markup; the runtime reconciles. The Stencil build also renders into Shadow DOM (shadow: true), which makes it the closest structural relative of sudoku-cc. sudoku-cc replaces the entire shadowRoot.innerHTML on render, re-attaches every listener, then mutates cells by flat index (row * 9 + col). Any DOM reference held across a render is stale. sudoku-juris uses immutable grid copies to trigger re-render.
Styling Tailwind through a tw() helper, on the clrs palette. A TwLabel component prints the live Tailwind version in the toolbar. CSS inlined in template literals. In sudoku-cc it is duplicated across all three render paths — renderGrid, showLoadingState and showError, each assigning a fresh shadowRoot.innerHTML with its own <style> block — so a style change usually needs making more than once.
Accessibility Inconsistent across the set: a real <label> in Stencil, Preact, React, Solid and Vue; a <span> with role and tabindex in Svelte; plain <div>/<span> in Lit and uce. The keypad is a real <button> in all eight, so the keys can always be tabbed to — but only Svelte can also reach a cell from the keyboard (role="button", tabindex="0" and an actual onkeydown), which makes it the one build with a working path end to end. In the other seven the keypad is reachable and never populates. Documented as absent in both repos: no ARIA, no keyboard path, no focus management. Cells and keys alike are plain <div>s — .cell, .option-number, .clear-cell — so nothing in the game takes focus at all. The grid needs a pointing device.

04Build and deploy

This is the widest gap, and the one with the most day-to-day consequence.

the seven · build then push

Install with pnpm, build with Vite, then deploy by hand:

cd dist; ln -s ../.vercel; vc --prod

One target. The deployed artifact is a hashed bundle you can't diff against the repo, and shipping requires a working local toolchain.

stencil · publish then link

The odd one out. proto-sudoku-wc builds to an npm package and publishes it — currently 0.1.293. Then a second repo, wc-sudoku, deploys the actual site.

That site is 875 bytes: a <head> pointing at jsDelivr, pinned to the exact version, and a single <proto-sudoku /> in the body. Nothing else.

Shipping a change means publishing to npm, then bumping the version in two URLs and the <title>. Two repos, two registries, one page.

the two · just push

Pushing to main is the deploy. Vercel serves index.html statically; GitHub Pages serves the same file from root, with .nojekyll to keep Jekyll out of it.

Because nothing is built, the deployed bytes are exactly the repo's bytes — so curl -s <url> | md5 against md5 index.html is a valid check for whether production is current.

Two targets means index.html must work unmodified on both; anything origin-specific breaks one of them.


05Measured

Everything below was measured from fresh local builds — not estimated. Nine rows date from 5 September 2026; sudoku-juris was re-measured on the 7th, after its framework upgrade.

Bytes over the wire, gzipped

sudoku-cc
9.0 KB
sudoku-uce
19.5 KB
sudoku-sld
20.1 KB
sudoku-juris
20.5 KB
sudoku-p4t
22.2 KB
sudoku-lit
25.7 KB
sudoku-s4e
29.8 KB
sudoku-vue
40.0 KB
wc-sudoku
40.4 KB
sudoku-r3t
74.2 KB
Claude-generated, single file app code shipped from the repo framework or component, fetched from a CDN at load

Gzipped JS + CSS from each dist/, all rebuilt 5 Sep 2026. For the single-file apps this is the whole page. Two entries arrive from two origins and are shown split: sudoku-juris is 6.7 KB of app plus 13.7 KB of framework off unpkg, and wc-sudoku is a 0.4 KB page plus 39.9 KB of component off jsDelivr. The wc-sudoku figure is what the browser actually fetched on a cold load — Stencil lazy-loads, so it was measured from the network panel rather than from the package. The sudoku-juris row was re-measured 7 Sep 2026, when it moved from Juris 0.88.2 to 0.9.0 and switched to the minified build the package ships. 0.88.2 was 163.4 KB raw / 28.4 KB gzipped; 0.9.0's juris.js is 130.1 / 21.6, having split four subsystems this app never used into opt-in files rather than removing them; and the juris.mini.js actually loaded is 54.1 / 13.7. That last swap is worth more than the version bump, and it is easy to miss — juris.js and juris.mini.js are different files, and the repo had been serving the unminified one. Every other row still stands as measured on 5 Sep.

The two that load at runtime

Two of the ten deliver a nearly empty page that pulls its real payload from a public CDN. They arrived at that shape four years apart and for different reasons — and they are the only bars worth splitting open.

Piece Origin Raw Gzipped
sudoku-juris
App code — the whole index.html the repo 33.1 KB 6.7 KB
Juris 0.9.0 runtime — juris.mini.js unpkg.com 54.1 KB 13.7 KB
Total over the wire two origins 87.2 KB 20.5 KB
wc-sudoku
The wrapper page — index.html the repo 0.9 KB 0.4 KB
proto-sudoku-wc 0.1.293 — 5 files cdn.jsdelivr.net 109.8 KB 39.9 KB
Total over the wire two origins 110.6 KB 40.4 KB
sudoku-cc, for comparison the repo, zero dependencies 45.9 KB 9.0 KB

The Juris result cuts both ways. The app code really is smaller than the Web Components version — 6.7 KB against 9.0 KB gzipped, about 26% less for the same game. It just costs 13.7 KB of framework to get there, so the page a visitor actually downloads is 2.3× the size of sudoku-cc.

wc-sudoku pushes the same trade to its limit: the deployed repo holds almost nothing, and 99% of the payload is the published component. The upside the Juris app doesn't get is that the CDN URL is pinned to an exact version, so what ships is immutable and rollback is a one-line edit.

Narrowed to sudoku-juris's inline <script> alone — excluding <head>, page chrome and the CDN tag — the app is 32.2 KB raw / 6.2 KB gzipped across 846 non-blank lines. Neither CDN tag carries an integrity hash, and either app goes down if its CDN does; the Juris repo lists both as known gaps in its own CLAUDE.md.

Repo Stack State Files Lines Commits Since
proto-sudoku-wc Stencil 4 → npm → jsDelivr @stencil/store 23 1,046 2,503 Apr 2021
sudoku-cc Web Components instance fields 1 1,295 17 Jun 2025
sudoku-juris Juris 0.9.0 (CDN) Juris store 1 1,020 4 Aug 2025
sudoku-p4t Preact + Vite @preact/signals 24 1,014 1,481 Jan 2023
sudoku-r3t React 19 + Vite Zustand + persist 21 735 236 Apr 2026
sudoku-sld Solid + Vite solid-js/store 25 938 1,899 Sep 2021
sudoku-s4e Svelte + Vite writable 23 800 2,260 Dec 2021
sudoku-vue Vue 3 + Vite Pinia 22 778 229 Apr 2026
sudoku-lit Lit 3 + Vite nanostores 22 942 1,268 Feb 2023
sudoku-uce uce 1.18 + Vite nanostores 22 1,058 1,265 Feb 2023

Lines are not comparable across the two families and shouldn't be read as one column. For the eight framework builds it counts only component and util source under src/ — excluding CSS, index.html and config. For the two single-file apps it is the entire index.html: markup, CSS and script together. The commit counts tell their own story — years of dependency bumps on one side, a handful of generated commits on the other. proto-sudoku-wc's 2,503 don't include the further 1,218 on wc-sudoku, the deploy repo that carries the 875-byte page.

The custom-element variants land where you'd expect: sudoku-p4t-wc at 29.0 KB gzipped over 24 files, sudoku-s4e-wc at 36.1 KB over 23. Both pay roughly 6 KB for the custom-element wrapper. With those two, and the wc-sudoku deploy shell, the family runs to thirteen repositories.


06What each family is for

the eight

A controlled experiment in frameworks

Holding the app, the components, the utils, the toolchain and even the npm scripts constant is the whole value. Change one variable — the reactivity model — and the diff between any two repos is a genuine framework comparison rather than a comparison of two people's taste. That only works because there was a template to hold constant, which is what the Stencil build has been since 2021. The long commit histories are the maintenance cost, made visible.

the two

A controlled experiment in generation

Different variable entirely: not which framework, but what an agent produces under a hard constraint — one file, no build, no dependencies — and then whether a tiny reactive framework improves on the native-Web-Components version of the same thing. The generated architecture docs and the Juris-vs-Web-Components essay are part of the output, not notes about it.

Which is why sudoku-cc shipping the smallest payload of the ten isn't really a win over React — it's the constraint doing its job. The more interesting result is at the other end: sudoku-juris hands its lead straight back to a CDN framework, though far less of one than it looks: on Juris 0.9.0's minified build it ships 2.3× sudoku-cc and beats six of the eight framework apps outright. wc-sudoku reached the same thin-page-plus-CDN shape from the opposite direction four years earlier and lands 19.9 KB heavier. Two answers to where does the code live, arrived at independently, that finish nowhere near each other once both are actually minified.