Domain Model & Schemas
The domain vocabulary, entity hierarchy, position identity keys, and SQLite database schema.
Domain Model
Core entities
Study
A user-owned collection imported from one source. Contains one or more Chapters.
Suggested fields:
- id
- title
- source metadata
- created/updated timestamps
- chapters
Chapter
A logical unit from the source PGN, commonly a game or named study chapter.
Suggested fields:
- id
- studyId
- title
- source order
- root position/node
- metadata
PositionNode
A node in the repertoire tree representing a chess position and its move relationships.
Suggested concepts:
- id
- position key / FEN-equivalent state
- parent node
- incoming move
- child nodes
- source chapter references
- annotations/comments if preserved
A tree node should not be assumed to be a review item.
RepertoireDecision
A decision the user may be asked to recall.
Conceptually:
position + user-side expected repertoire move(s) + review identity
Multiple study branches may refer to the same chess position. Do not merge them automatically unless the product explicitly defines compatible repertoire semantics.
ReviewState
Current SRS state for one review item.
Must support at least:
- first review
- last review
- next due
- repetitions / recall history summary
- difficulty/stability data needed by scheduler
ReviewEvent
Immutable-ish historical record of a review outcome. Keep enough information to reconstruct/debug scheduling behaviour.
Opening
A future cross-study classification/grouping concept. It should be orthogonal to Study and Chapter.
Relationships
Study
-> many Chapters
Chapter
-> root PositionNode
PositionNode
-> child PositionNodes
PositionNode
-> zero or more RepertoireDecisions
RepertoireDecision
-> one ReviewState
ReviewState
-> many ReviewEvents
Important distinction
A PGN move tree is content.
A RepertoireDecision is a learning unit.
A ReviewState is scheduling data.
Keep these concepts separate.
Position identity
A position identity must be deterministic and must account for all chess state relevant to legal moves and repetition semantics. FEN is a useful interchange representation; an internal normalized key may be better for persistence.
Do not identify a position only by its piece placement if side-to-move, castling rights, or en-passant state can affect legal moves.
Repertoire branches
A position may have multiple valid repertoire continuations because the PGN contains branches. Review should know which move(s) are accepted for the active study/scope.
Do not infer that every legal chess move is a repertoire answer.
Future sync identity
Entities that may sync across devices need stable IDs generated independently of the local database implementation. Do not rely on auto-increment database IDs as cross-device identity.
Repertoire Integration Mapping
Integration Map — Listudy & chessrs Extraction Plan
Status: Phase 0 map. No integration happens until the Lichess Mobile foundation is stable (Phase 1) and the vertical slice ships (Phase 2/3).
The reset directive is explicit: do not merge repositories. Extract behavior and proven solutions into isolated modules inside our domain boundary.
Lichess Mobile (UI/app foundation)
|
+-- application/product modules
|
+-- OUR domain boundary (contracts below)
| |
| +-- Listudy-derived training semantics (module: repertoire training)
| +-- chessrs-derived SRS semantics (module: srs scheduling)
|
+-- local persistence/import infrastructure (sqflite, PGN via dartchess)
License constraints (hard)
| Source | License | Rule |
|---|---|---|
| lichess-org/mobile | GPL-3.0 | We are a fork: entire app is GPL-3.0. Preserve LICENSE, COPYING.md, notices. |
| ZackMurry/chessrs | GPL-3.0 | GPL-compatible: direct code reuse permitted if desired; still prefer clean reimplementation in Dart. Attribute. |
| ArneVogel/listudy | AGPL-3.0 | AGPL ≠ GPL interchangeable. Do not copy code. Study behavior, reimplement independently. |
Consequence: from chessrs, small, attributed code adaptation is legally possible; from listudy, only behavioral inspiration. Default posture for both: reimplement against our contracts, cite the source file in the doc comment.
From Listudy (Phase 4) — behavioral extractions
Source: assets/js/study.js, assets/js/modules/tree_utils.js,
modules/tree_from_pgn.js, study_controller.ex (concept only).
| Concept | Source | Our module | Notes |
|---|---|---|---|
| Training loop: validate user move against tree children; wrong → undo board, mark error, retrain all sibling replies to 0 | study.js handle_move |
review session engine | our RepertoireDecision updates instead of node values |
Opponent reply auto-play with weighted-random + anti-repetition (tree_size_weighted_random_move, value bump) |
study.js ai_move |
review session engine | keeps review varied across sessions |
| Per-node training value 0–5; value 5 = fully trained; auto-skip fully trained prefix moves (key moves) | tree_utils.js update_node_value, study.js start_training |
ReviewState/SRS design input | maps to "auto-traverse non-due material" |
Chapters = PGN games; FEN header support; chapter selector |
study.js setup_chapter_select |
repertoire module | matches our Study→Chapter model |
| Trees cached client-side keyed by PGN hash (regenerate on change) | study.js setup_trees |
import pipeline | hashing invalidation pattern for local DB |
Move comments + PGN arrow/circle shapes (csl/cal) rendered on board |
study.js get_pgn_shapes |
repertoire UI (optional) | only where useful; secondary |
| Hint arrows gated by training value (new/2×/5×/always) | study.js display_arrows |
review settings (optional) | post-beta |
Progress per chapter (tree_progress) |
tree_utils.js |
review stats (minimal) | only if it serves Review UX |
Explicitly NOT taken: Elixir backend, users/favorites/comments socially, tactics/books/blog SEO pages, achievements & combos, Stockfish web worker, chessclicker.
From chessrs (Phase 5) — behavioral extractions
Source: backend/src/main/kotlin/com/zackmurry/chessrs/ (entity, service),
frontend/src/pages/practice/ + store/boardSlice.ts.
| Concept | Source | Our module | Notes |
|---|---|---|---|
Review item = position + move: fenBefore, san, uci, isWhite |
entity/Move.kt |
RepertoireDecision | confirms our decision-unit model |
SRS interval ladder: ease × scaling^numReviews (minutes), user-tunable ease/scaling |
service/SpacedRepetitionService.kt |
second Scheduler impl |
complements our SimpleScheduler ladder |
Fields: lastReviewed, numReviews, due, timeCreated |
entity/Move.kt |
ReviewState | subset of our state (we add lapses/stability for FSRS-upgrade path) |
| Review queue prefetching: fetch N due items, refill when 1 remains | PracticeMainPanel.tsx |
review session engine | avoids per-item latency in UI |
| Wrong answer → show correct move on the board, pause ~3s, reset; correct → advance | PracticeMainPanel.tsx, boardSlice |
review feedback UX | aligns with our quiet-feedback principle |
| Per-move board perspective (isWhite flips board) | PracticeMainPanel.tsx |
review scene | board orientation from repertoire side |
cleanFen normalized position identity |
Move.kt |
position keys | matches QUALITY.md position identity invariant |
opening string on moves for grouping |
Move.kt, OpeningService.kt |
Opening cross-study dimension | Phase 4/5+ |
Explicitly NOT taken: Kotlin/Spring backend, GraphQL API, PostgreSQL/JDBC, OAuth/Lichess auth, accounts, engine service.
Integration sequence
- Phase 2 (slice) uses OUR contracts only (already encode most chessrs queue/feedback behavior — the old ReviewEngine's semantics survive as spec).
- Phase 4 (Listudy): opponent auto-reply + variation-aware training inside the review engine; chapter/FEN handling verified against import tests.
- Phase 5 (chessrs): alternate scheduler impl + queue prefetch tuning,
behind the
Schedulercontract, selected in settings.
Each integration is one isolated module + tests; no cross-cutting rewrites.