Roadmap
The journey from “useful module surface” to “stable API” happens incrementally, driven by real consumers.
Already landed
Section titled “Already landed”- Collection layer (src/games.js) —
gameIdentity,isPrefix,mergeGames, attributed-annotation merging. Built when the multi-source merge demand emerged. - Replay engine (src/replay.js) —
ReplayEngine, position hashing, FEN diffing primitives. - UCI engine client (src/engine/) — generic UCI protocol client + curated registry. Engine binaries are the consumer’s responsibility.
- FEN parser (src/fen.js) — grammar + semantic
validation (king count, pawn placement, castling backing, ep
consistency), normalization,
FenParseError. - Slice-based lifecycle (SPEC-lifecycle.md) —
reactive system over
cursor/tree/header/view/engineslices. Construction callbacks (onCursor/onTree/onHeader/onView/onEngine) +game.subscribe(slice, fn). Mutators declare slice impact statically; views are fresh per fire; re-entrant mutation throws. Replaces the prioronPositionChange/onChange/start()shape. - Engine integration bridge (SPEC-engine-integration.md)
—
publishEngineInfo(id, snapshot)on Game;attachEngine(wires UCI events → slice with leading+trailing throttling at 500ms, opt-inanalyze: trueto drive analysis from cursor);autoAnalyzestandalone for headless/shared-engine cases; UCI translation primitives (parseInfoLine,uciPvReplay,sanToUci,uciToSan,buildPositionCommand).
Order of operations (rough)
Section titled “Order of operations (rough)”| Step | What | Driven by |
|---|---|---|
| 1 | API audit — list every method on Game, decide which are public | first stable external consumer |
| 2 | Strip legacy fields — remove tournamentSlug from FIELD_SCHEMA and any other carry-over from earlier shapes | step 1 reveals these |
| 3 | Editor state primitives — refine the setComment / toggleNag / promoteVariation surface so Motif’s editor UIs have clean state operations to call | first consumer that wants editing UI |
| 4 | NAG kit as a stand-alone module under src/nag/ | clean extraction; KeySquares already wants this |
| 5 | Variant support — parameterize the position engine so non-standard chess works without forking the runtime. Pluggable legality engine (chess.js default; chessops / fairy-stockfish-rules / custom for variants) lives behind Motif’s GAME contract — consumers see no API change. | Rabbit’s variant work |
| 6 | PGN header editing — apps that load a PGN need to edit headers (Event, Date, players, result, etc.). PGN headers are chess data, same category as comments, so this belongs in Tabia. A prior setRecord(r) (whole-record replace) was removed because it had no consumers and silently allowed startFen mutation, which corrupts the tree. The right shape is targeted — e.g., setHeader(key, value) + removeHeader(key), with startFen excluded from settable keys since it’s structural and fixed at construction. Design the right surface when the first consumer needs it. | first app that wants header editing |
| 7 | Save-state tracking — “has this game been modified since last save?” Apps need it for unsaved-changes indicators, prompt-before-close, autosave triggers. A prior dirty flag + isDirty() was removed because it had no consumers, no markClean() to complete the round-trip, and every new mutation method had to remember to flip the flag (maintenance burden). The right shape includes both isDirty() AND markClean() (or markSaved()), possibly a dirty-changed event so consumers don’t poll, and consideration of whether to distinguish data mutations from preference toggles. Until then, apps can derive it themselves by flipping a local flag in their onChange callback. | first app that wants unsaved-changes UX |
The order is “extract when there’s a consumer,” not “extract on schedule.” Many steps can be partially deferred.
Keyboard binding is not Tabia’s concern — the canonical keymap and the binder both live in Motif, since they cross subsystems (Tabia actions + Rabbit board + Motif components) and involve DOM event handling that’s foreign to a data-only library.
Where rendering and UI live
Section titled “Where rendering and UI live”Tabia does not render boards or own UI components — that was
walked back when board.js was removed. Today:
- Board rendering → Rabbit (SVG, framework-free)
- UI components (MoveList, BranchPopover, Toolbar, FenArea, etc.) → Motif (Lit web components)
- Integration example →
PROMOTE/dev/(composes all three)
If something feels like it belongs in Tabia but it’s really UI, it belongs in Motif.
What success looks like
Section titled “What success looks like”- A new app imports
{ createGame, ReplayEngine }from Tabia, pairs with Rabbit for rendering and Motif for UI chrome, and gets a working interactive viewer in <100 lines of app code. - A prep-reader app uses Motif’s
<motif-move-list>wired to Tabia’sGame, getting a polished move list for free. - A future opening trainer imports
{ createGame }only (no UI), uses it for predict-the-move logic, and builds its own UI. - A future game-archive app imports the collection layer and gets duplicate detection + attributed-annotation merging without writing any of it.
Non-goals
Section titled “Non-goals”- Rendering. Tabia is data only. Renderers are sibling libraries.
- UI components. Motif’s territory.
- App shell. Modal scaffolding, tab systems, route management — consumer responsibilities.
- A general framework. Tabia provides building blocks. Each app provides its own scaffolding.