Skip to content

Workspace architecture

The chess workspace organizes around three sibling libraries plus their consumers (apps + the local dev harness):

graph TD
M["Motif — design<br/>tokens · theme · components · assets"]
T["Tabia — data<br/>Game tree · ReplayEngine<br/>PGN parser · UCI client · merge"]
R["Rabbit — render<br/>pure SVG · variant-aware · themable"]
APP["chess apps<br/>(and PROMOTE/dev/ — the local harness)"]
M -->|visual| APP
T -->|chess state| APP
R -->|board pixels| APP
classDef lib fill:#2563eb,color:#fff,stroke:#93c5fd,stroke-width:2px;
class M,T,R lib;

No project depends on another at the source level. Coordination happens in the consuming app (or in PROMOTE/dev/, which is the canonical worked example).

Motif — visual identity, chess-aware UI components, assets

Section titled “Motif — visual identity, chess-aware UI components, assets”
  • Visual tokens (CSS custom properties): surface, text, shadow, radius, transition
  • Chess-aware tokens: win/loss colors, NAG palette, state gradients
  • Theme + board-style switcher (design/theme.js) — small persistence
    • class-toggle module, no chess logic
  • Chess-aware web components (components/) — MoveList, EnginePanel, FenArea, PgnArea, BranchPopover, ContextMenu, EcoOpeningBar, PlayerHeader, SanPreview, Toolbar. Built on Lit.
  • Static reference data — pieces/ (39 piece SVG sets, licensing audited in docs/PIECE-LICENSING.md), data/eco-epd.json, data/test-pgns/
  • Documented patterns (docs/RECIPES.md, docs/KEYBOARD.md)

CSS is consumable on its own. Web components import lit; the host app provides the lit module — typically by aliasing the bare specifier to its own installed copy (see PROMOTE/dev/vite.config.js for the harness pattern).

  • PGN parser + serializer (variant-agnostic)
  • createGame() — tree-shaped game state with variations, comments, NAGs, drawables
  • ReplayEngine — step/seek primitive with position hashing
  • Collection layer (games.js) — identity hashing, prefix detection, merge with attributed annotations
  • UCI engine client — generic, consumers provide the engine binary

Pure data. FEN / PGN / move-tree in and out. Pixels are someone else’s problem.

  • Pure SVG, framework-free
  • Variant geometry support (standard 8×8 + non-rectangular layouts)
  • Consumer-provided pieces (URL prefix, inline SVG, or loader fn)
  • CSS-variable themable
  • Driven imperatively via setPosition, setShapes, etc.

Knows nothing about chess rules. Position in, pixels out.

  • A framework. No router, no state-management opinion, no app shell.
  • A logic layer. Anything that knows positions, moves, or PGN belongs in Tabia.
  • A renderer. Boards are Rabbit’s job.
  • An app shell.
  • A renderer. Tabia did briefly wrap chessground in src/board.js; that was walked back. Tabia is data-only now — apps compose Tabia with a renderer at the app boundary.
  • A UI library. UI components live in Motif.
  • A game state engine. Rabbit renders whatever you tell it to render; it doesn’t validate moves or track variations.
  • A piece-set provider. Consumers supply pieces via the pieces option. (Motif hosts the canonical piece-set library for those who want one.)
  1. mkdir my-new-chess-app && cd $_ && git init
  2. Pull in Motif’s tokens — <link rel="stylesheet" href=".../Motif/design/tokens.css">
  3. Apply the motif class to <html>. Import theme.js if you want runtime theme switching.
  4. Import the slices of Tabia you need: createGame, ReplayEngine, mergeGames, etc.
  5. Mount Rabbit boards where you want pixels.
  6. Drop in Motif components (<motif-move-list>, <motif-engine-panel>, etc.); feed them Tabia state via attributes/properties.
  7. Write the app-shell glue that wires everything together.

The PROMOTE dev harness at ../../dev/ is the worked example — it composes all three sibling libraries in one page.

  • Not a published npm package set (yet). Local imports via relative paths until there’s a stable external consumer.
  • Not a framework. No router, no state-management opinion, no app shell. Bring your own.
  • Not a permanent boundary. As consumers materialize and APIs settle, packages will firm up. For now the boundary is conceptual.