Skip to main content
Home / Documentation / System Architecture & Foundation
Architecture

System Architecture & Foundation

Lichess Mobile GPL-3.0 base, 4-tier layer isolation, component boundaries, and SQLite local persistence.

Source in app repo: ARCHITECTURE.md
Source in app repo: CUT_PROPOSALS.md
App version: v1.4.0
Licence: GPL-3.0

Technical Architecture

Foundation reset: this application is built on a fork of lichess-org/mobile (GPL-3.0). The previous standalone Flutter implementation is archived at git tag legacy/pre-reset. It is reference material only — the product and domain knowledge lives in the Markdown specifications, not in the old code.

1. What this application is

A local-first chess repertoire + spaced-repetition trainer built on the Lichess Mobile application foundation. It is not "Lichess with modifications": Lichess Mobile provides the technical/UI foundation (board, theme, navigation, state management, persistence patterns); our product provides the domain (repertoire, review, SRS).

Lichess Mobile foundation (GPL-3.0 fork)
        |
        +-- application/product modules (Riverpod providers)
        |
        +-- OUR domain boundary (pure Dart: repertoire, review, SRS)
        |       |
        |       +-- Listudy-derived training semantics
        |       +-- chessrs-derived SRS/review semantics
        |
        +-- local persistence / import infrastructure (sqflite, dartchess PGN)

2. Layer boundaries

┌────────────────────────────────────────────────────────────┐
│ Presentation — Lichess Mobile shell (Flutter + Riverpod)    │
│   Review scene (primary) · Repertoire selector · Import ·  │
│   Settings (Lichess settings framework)                    │
└──────────────────────────┬─────────────────────────────────┘
                           │ talks only to application providers
┌──────────────────────────▼─────────────────────────────────┐
│ Application — Riverpod providers/services orchestrating    │
│   use-cases (import study, start review, submit move)      │
└──────────────────────────┬─────────────────────────────────┘
                           │ repository interfaces only
┌──────────────────────────▼─────────────────────────────────┐
│ DOMAIN (ours) — pure Dart, no Flutter/chessground imports   │
│   Study · Chapter · repertoire tree · RepertoireDecision   │
│   ReviewSession engine · ReviewState · Scheduler contract  │
└──────────────────────────┬─────────────────────────────────┘
                           │ adapters only
┌──────────────────────────▼─────────────────────────────────┐
│ Infrastructure — Lichess-derived:                          │
│   dartchess (rules, FEN, SAN, PGN) · chessground widgets   │
│   sqflite local DB · settings/preferences · file import   │
│   (no network on any critical path)                        │
└────────────────────────────────────────────────────────────┘

Boundary invariants

  1. Pure domain: our domain code (lib/src/domain/, 23 files) contains pure Dart logic and entities. Zero imports of package:flutter/... widgets, chessground, or sqflite — enforced by inspection and by the banned-API gate (scripts/gates/banned_apis_check.py). Testable without a widget tree. The module's public surface is the barrel domain/domain.dart.
  2. One chess representation: dartchess is the chess rules engine. Do not introduce a second chess library or reimplement move generation. Domain code may consume dartchess core types (Position, Move, San) through thin adapters; UI consumes chessground. Never create competing representations of moves/positions/games.
  3. Lichess UI is not our domain model: Lichess model/ classes for online features are presentation/application concerns of the foundation, not domain entities of our product. Our entities live in our domain module.
  4. Local-first critical path: user move → legality (dartchess) → repertoire lookup → SRS update → board update runs entirely in memory, locally. Network access is never on this path.
  5. Isolated integrations: Listudy-derived and chessrs-derived behavior live in isolated domain modules behind our contracts (see docs/INTEGRATION_MAP.md). No cross-cutting merges.
  6. Imported repertoires vs. online studies: Repertoires imported into ChessSRS (from PGN or Lichess) are pure, local-first snapshots stored in SQLite. They have no live socket connection and never auto-sync with remote changes. Remote Lichess study browsing (StudyScreen/StudyController) operates strictly in-memory over WebSockets for live viewing; it never writes to the local database.

3. Component responsibilities

Domain (ours)

Infrastructure (Lichess-derived)

Presentation (Lichess-derived)

4. Review state machine (preserved product semantics)

                ┌───────────────┐
                │ Select Scope  │  (all active / one study / one opening)
                └───────┬───────┘
                        ▼
                ┌───────────────┐
                │ Due Decision? ├─── No ──► calm idle state (option: Explore / Practice)
                └───────┬───────┘
                        │ Yes
                        ▼
                ┌───────────────┐
                │ Show Position │  (board oriented; move comments strictly hidden)
                └───────┬───────┘
                        ▼
                  User plays move
                        │
          ┌─────────────┴─────────────┐
          ▼                           ▼
    [Repertoire move]           [Other move]
          │                           │
   • record success (if SRS)   • record lapse (if SRS)
   • reveal move comment       • reveal expected move & comment
   • auto-traverse non-due     • keep board interactive for reguess
   • opponent auto-reply       • re-queue failed item
          │                           │
          └─────────────┬─────────────┘
                        ▼
                Next Due Decision      (until nothing due)

Key semantics (from PRODUCT/QUALITY + reference projects):

5. Identity & determinism

6. Licensing constraints (binding)

8. Module map

Sizes are hand-written Dart (generated *.freezed.dart / *.g.dart are gitignored and absent from a fresh clone). "Depends on" lists inward edges only, i.e. what the module imports.

Module Files / lines Role Entry points Depends on
lib/src/domain/ 23 / 3,153 Pure-Dart domain: repertoire tree, decisions, SRS state, review engine, scheduler contract, clock barrel domain.dart; ReviewSession, ReviewEngine, Scheduler, PositionKnowledgeState dartchess, meta, logging, uuid, crypto — no Flutter, chessground or sqflite
lib/src/review/ 2 / 2,182 Application layer: orchestrates sessions, persistence and SRS updates; Riverpod providers reviewServiceProvider, ReviewService.startSession / submitMove / retryMove / getDueSummary; ReviewController domain, persistence, study_preferences
lib/src/persistence/ 7 / 1,746 SQLite schema, StudyRepository contract and its sqflite implementation, JSON adapters, two one-shot data migrations study_repository.dart (contract), sqlite_study_repository.dart, srs_schema.dart domain, sqflite, dartchess
lib/src/db/ 3 / 410 Opens the one database, bundles the openings DB, secure storage database.dart (openDatabase, schema v15) sqflite, sqflite_common_ffi (desktop/tests)
lib/src/import/ 4 / 1,047 PGN import/export, Lichess study import, opening-name derivation importPgn / importPgnAsync → ImportResult; pgn_exporter.dart dartchess, domain
lib/src/network/ 5 / 2,736 Lichess HTTP, WebSocket, connectivity, server status — Lichess-derived http.dart (HttpClientFactory), socket.dart, server_status.dart http, cronet_http, cupertino_http, web_socket_channel
lib/src/model/ 111 / 22,504 Lichess-derived feature models (games, studies, auth, engine, users) model/**/*_providers.dart everything above
lib/src/view/ 76 / 23,784 Screens. review/ is ours; the rest is the inherited Lichess surface screen widgets application layer, widgets, model
lib/src/widgets/, styles/, design/ 65 / 12,827 Reusable widgets, tokens, and the design package — material_ui, chessground
lib/ total 329 / 73,557 lib/main.dart → lib/src/app.dart
test/ 188 / 51,543 1,598 test declarations see §13

Dependency direction is inward-only with three known exceptions: three screens import the persistence layer directly instead of going through an application provider — view/settings/srs_settings_screen.dart (db/database.dart), view/review/study_chapters_screen.dart (persistence/study_repository.dart) and view/analysis/analysis_hub_screen.dart (persistence/persistence.dart). This is tracked as item 14 in docs/tech-debt-inventory.md, not an intended shortcut. Five screens also import domain/ directly, which is allowed for value types but should not grow into orchestration.

9. Data flow

Flow A — import a PGN, then review it

  1. Pick a file (view/more/import_pgn_screen.dart, file_picker).
  2. Parse: ReviewController calls importPgnAsync / importPgn (import/pgn_importer.dart:198,358), which uses dartchess for legality, SAN and FEN. Multi-game PGN, RAV variations, comments, NAGs and starting FENs are handled; malformed input yields a structured ImportError and is reported, never silently dropped.
  3. Persist: StudyRepository.saveImportResult (persistence/study_repository.dart:21) writes srs_study, srs_chapter and one srs_decision row per recallable branch — the whole tree goes into srs_chapter.treeJson, and per-move writes never rewrite it.
  4. Start a session: ReviewController → ReviewService.startSession (review/review_service.dart:157) reads active studies, filters decisions by the chosen ReviewScope, and constructs a ReviewSession with the selected Scheduler and Clock.

Flow B — answering one move (the critical path)

  1. ReviewController forwards the board's move to ReviewService.submitMove (review_service.dart:270).
  2. The service takes a checkpoint() of the session and calls ReviewSession.submitMove (domain/review/review_session.dart:302) — pure Dart, no I/O: legality against dartchess, lookup in the repertoire tree, branch selection, and the SRS transition computed by the injected Scheduler.
  3. It returns a ReviewStepResult (correct / incorrect, the expected move, the comment to reveal, plus sideEffectStates for graph effects such as contagion and sibling updates).
  4. In SRS mode only, the service maps each updated state to a PositionKnowledgeState and writes it with the review event — incremental, per answer. In practice mode this step is skipped entirely and nothing is written.
  5. The controller updates the board and prompts the next due decision.

No step on this path performs network I/O. This is the invariant that makes review work offline, and it is why the scheduler and clock are injectable: tests drive the whole path deterministically with a fixed clock.

10. Data model

One SQLite database (Lichess schema v15, opened in db/database.dart). SRS tables are created by createSrsTables (persistence/srs_schema.dart):

Table Key columns Notes
srs_study id, title, createdAt, updatedAt, isActive, pgnHash pgnHash (indexed) deduplicates re-imports
srs_chapter id, studyId FK→study (cascade), sourceOrder, title, startingFen, treeJson, opening, orientation one row per chapter; the move tree lives in treeJson so a single move never rewrites the tree
srs_decision id, studyId, chapterId, nodeId, expectedMoves, canonicalStateId the scheduled unit: one row per branch the player must recall. expectedMoves holds multiple accepted moves; canonicalStateId links a node to a position's knowledge state
position_knowledge_state nextDueAt (indexed), … knowledge keyed by canonical position, so the same position reached by different move orders shares one schedule
srs_review_state nextDueAt (indexed), … per-decision SRS counters (first/last review, interval, lapses)
srs_review_event decisionId (indexed), whenTimestamp (indexed) append-only review log

Relationships: study 1→N chapters 1→N decisions; decisions reference a canonical position state; review events reference the decision they came from. Foreign keys cascade on study/chapter delete.

Position identity is the normalised four-field FEN (placement, side to move, castling rights, en-passant) — the "clean FEN" of §5, which is what makes cross-move-order knowledge sharing and transposition handling possible. A decision's canonical knowledge ID is sha1("<fenKey>|<every accepted move, sorted>"): the answer set is part of the identity, so two different questions asked at the same position keep separate schedules instead of being merged. (The earlier format used only the first accepted move and could not tell them apart — see the migration below.)

Two one-shot data migrations run after the schema upgrade and read/write rows, so they run outside the schema batch:

11. External dependencies and integration points

Integration Where Failure handling
Lichess HTTP API — lichess.org, lichess1.org (constants.dart, overridable via --dart-define) network/http.dart; HttpClientFactory prefers Cronet on Android, CupertinoClient on iOS/macOS, and falls back to dart:io HttpClient in a catch offline-first: nothing on the review path calls it; failures surface as errors in the Lichess-derived screens, and the factory degrades to the plain HTTP client
Lichess WebSocket — socket.lichess.org network/socket.dart study browsing is strictly in-memory and read-only; it never writes to SQLite (§2 invariant 6)
Lichess OAuth (PKCE) flutter_appauth, model/auth/auth_repository.dart app remains fully usable signed-out; only the Lichess-specific screens need it
Lichess explorer / tablebase explorer.lichess.org, tablebase.lichess.org advisory lookups; absence degrades to "no data", not an error state
Server availability network/server_status.dart derives ServerStatus from HTTP 503 = maintenance, 502 = down status is informational; the local review loop is unaffected. connectivity_plus gates network-only affordances
Chess engine model/engine/ — native Stockfish and Fairy-Stockfish via multistockfish, Leela via lc0 (both git-pinned, see docs/dependency-audit.md §4) advisory only, never the Review judge (decision D005); engine_failure.dart, engine_budget.dart and a slot model keep a missing or crashing engine from breaking review
Bundled assets assets/chess_openings.db (1.1 MB, copied out of the bundle on first use), assets/maia/, assets/images/stockfish/, positions.json, endgames.json no network needed; the openings DB is versioned by filename and replaced on mismatch

12. Design decisions and tradeoffs

The durable log is docs/decisions.md (D001–D017) and it is the authority; this is a map of the ones that shape the code most:

Inferred (from code, not written down): the schema stores the move tree as treeJson on the chapter row and writes decisions separately, which trades queryability of the tree for the guarantee that a move never rewrites the whole study — consistent with the incremental-persistence invariant in QUALITY.md. Marked as inferred because no document states the choice explicitly.

13. Known limitations and technical debt

Full detail lives elsewhere; this is the signpost.


Appendix: Subsystem Cuts & Upstream Discipline

Lichess Mobile Cut Proposals

Status: ACTIVE — Phase 1 F5+ staged cuts in progress.

This document is the authoritative record of what is removed, what is kept, and why. Update it before executing any cut. Future agents: read this entire file before touching anything.

The guiding rule:

Trim the Lichess product. Preserve the Lichess technical foundation. When uncertain: keep it. Delete less, verify more.


Execution status key


Owner decisions (recorded 2026-09-14)

These are the authoritative decisions from the owner. Do not override them.

# Decision Rationale
C1 Firebase [x] DONE Removed 2026-09-30 (Option B) — firebase_core, crashlytics, messaging dropped; app is 100% offline-first with zero cloud dependencies
C2 Notifications [x] DONE Removed 2026-09-30 — FCM path and Lichess push models removed; app has no server-push dependencies
C3 Auth/login [K] KEEP Login is optional but enables importing Lichess/chess.com studies; app is 100% functional offline without it
C4 Online play [x] DONE Removed 2026-09-15 — lobby, seeks, challenges, view/play, model/lobby, model/challenge
C5 Server games [x] DONE Removed 2026-09-15 — server game lifecycle, correspondence, GameScreen, ongoing games
C6 Puzzles tab [x] DONE Removed 2026-09-15 — tab, model/view dirs, deep links, quick action; shared FeedbackTile/SideToPlayPiece moved to widgets/feedback.dart
C7 Watch tab [x] DONE Removed (commit dd996ee74) — docs marked late in C6 commit
C8 Social [x] DONE Removed 2026-09-15 — removed Community/Players/Friends, Inbox from More tab & account menu, Home friends carousel, and message service poller
C9 Learn tab + coord training [x] DONE Removed 2026-09-14
C10 Blog/recap/announce [x] DONE Removed 2026-09-15 — models, services, home carousel, test
C11 Over-the-board game [x] DONE Removed 2026-09-14
C11 Chess clock tool [x] DONE Removed by owner request 2026-09-14
C12 Offline computer play [G] GREY Owner undecided; leave untouched until explicit approval
C13 Engine (Stockfish) [G] GREY May be useful as optional import advisor — undecided
C14 Opening explorer [K] KEEP Owner decision
C15 Analysis screen [G] GREY Undecided — shares widgets with study; leave untouched
C16 Board editor [K] KEEP Owner decision
C17 WebSocket [K] KEEP Owner decision (2026-09-16) — kept for study sync, cloud Stockfish evaluation, and future sync
C18 HTTP repos [K] KEEP Owner decision (2026-09-16) — base HTTP, auth, study import, opening explorer, and tablebase kept; dead online repos trimmed
UI-A Donate / patron links [x] DONE Removed (commit 1af79eab3)
UI-B "About Lichess" in More tab [x] DONE Removed (commit 1af79eab3)
UI-C "Lichess is a free…" message (LichessMessage widget) [x] DONE Removed (commit 1af79eab3)
UI-D "Welcome to the Lichess app" card [x] DONE Removed (commit 1af79eab3)
UI-E "Not all features available" text [x] DONE Removed (commit 1af79eab3)
UI-F App background theming [x] DONE Removed 2026-09-27 — see the note below

C8 Social note: Friends list, inbox, player search, and relations navigation entries were removed from the More tab and Account menu; the Home screen friends carousel was removed; the background message service was silenced; and the relation repository, following_user model, and follow/block actions were pruned in Step 15. Base user/account models remain for C3 (auth).

UI-F App background note: The setting was removed because it could not work, not because it was judged unimportant. Two independent facts, each verified in the code:

  1. Its only consumer was unreachable. makeAppTheme in lib/src/theme.dart was defined and never called — app.dart builds the Diagram theme through srsThemeData(srsColors). The background fed _makeBackgroundImageTheme, a translucent-surface theme whose whole purpose was letting a background show through, and that in turn was reachable only from makeAppTheme. The preference was stored and read by nothing that ran. Separately, scaffoldBackgroundColor: c.ground is opaque in theme_bridge.dart, so even the dead path could not have shown through.
  2. The Diagram palette is specified against a fixed ground. design/docs/02-tokens.md tabulates "Contrast on ground (light / dark)" and tokens.dart states "Every value passes 4.5:1 against ground and surface in its own theme". ground is #F1F3F4 / #0D0F13, defined as "App background". A user-chosen image behind ink text invalidates the one guarantee the token system rests on, and nothing tests it against an arbitrary background.

Making the feature work was therefore not a small fix: it meant either dropping that contrast guarantee or building image-luminance sampling to pick light or dark per picture, which is feature creep the feature policy forbids by default. Leaving a picker that visibly selects a background and changes nothing was the worst of the three options, so it went.

Reversal, if the owner disagrees: do not set scaffoldBackgroundColor in theme_bridge.dart and restore FullScreenBackground in buildScreenRoute. The deleted code is in this branch's parent commits.

Removed with it: lib/src/widgets/background.dart, lib/src/view/settings/background_theme_choice_screen.dart, the BackgroundColor enum, the BackgroundImage sealed class and its JSON converter, setBackground, the isForcedDarkMode forced-dark escape hatch in app.dart, and the usingCustomBackground branch in game_summary_table.dart. lib/src/theme.dart survives at 50 lines holding only CustomTheme, which two explorer views read through context.lichessTheme.


Safe execution order

Each step is ONE logical change, ONE commit. Gate: fvm flutter analyze (0 issues) + fvm flutter test (all pass) + fvm flutter run -d linux (app launches, manual spot-check) before proceeding to the next step.

NEVER batch multiple steps into one commit.

Step 1  [x]  C9  — Learn tab + coordinate training                  DONE
Step 2  [x]  UI  — Lichess branding strings from home + more screens DONE
Step 3  [x]  C7  — Watch tab (TV / tournaments / broadcasts)  DONE (dd996ee74)
Step 4  [x]  C6  — Puzzles tab                                DONE
Step 5  [x]  C11 — Over-the-board game & Clock tool             DONE
Step 6  [x]  C10 — Blog / recap / announce (home carousels + model) DONE
Step 7  [x]  C4  — Online play: lobby / seek / challenges           DONE
Step 8  [x]  C5  — Server game lifecycle + correspondence       DONE
Step 9  [x]  C8  — Social navigation & Home carousel & Message poller DONE
Step 10 [K]  C17 — WebSocket (KEPT by owner decision for study sync / cloud eval)
Step 11 [K]  C18 — HTTP network & core repositories (KEPT for auth / study / explorer)
Step 12 [x]  Tab reduction: clean 2-tab shell (Review + More) — Home tab removed DONE
Step 13 [x]  C8  — Dead social views & message models (b8664182d)      DONE
Step 14 [x]  User— Dead leaderboard & online bot screens/providers (b0ad26284) DONE
Step 15 [x]  C8  — Dead relation repository, following_user & follow/block actions DONE
Step 16 [x]  C4  — Dead playban dialog, notification model & account service monitoring DONE
Step 17 [x]  UI/Widgets — Dead orphaned widgets, views & assets (UserContextMenu, ServerOutageDisplay, ExpandedSection, SideIndicator, TextBadge, BrightnessNotifier) DONE
Step 18 [x]  C5/C6 — Dead Android system gestures exclusion utility & method channel handler DONE

Steps beyond 12 (C12 offline computer, C13 engine) are blocked on owner GREY decisions and are not started until explicit approval.


C1/C2 decided 2026-09-29 — keep, and what "keep" actually means here

Owner decision: keep Firebase, because notifications need it. Recorded here because the decision and its consequences are easy to conflate, and only one of the two is a decision.

What is actually running. The FCM path is live and started at launch (lib/src/app.dart:123). It requests permission, listens for token refresh, and registers on connectivity regain. It is real code doing real work.

What it delivers is not SRS reminders. CUT_PROPOSALS.md C2 was written as "may be needed for SRS review reminders (like Anki)". That feature does not exist. There is no zonedSchedule and no topic subscription anywhere in lib/; the only display calls initialize the plugin (lib/src/init.dart:71). What the plumbing delivers today is Lichess's own push, registered against Lichess:

// lib/src/model/notifications/notification_service.dart:475
client.post(Uri(path: '/mobile/register/firebase/$token'))

That request also returns early when no user is signed in (:471), so the whole notification path is gated behind sign-in — which is why the unreachable account menu (fixed in PR #84) was suppressing notifications as well as study import.

The consequence that is not a decision. Keeping Firebase keeps lib/firebase_options.dart pointed at Lichess's project — lichessv2, org.lichess.mobileV2. So:

lib/firebase_options.dart is generated, so this is a console-and-regeneration task, not a code edit. It stays open because it is a consequence of the decision, not a separate decision.

What would change the answer. Building the Anki-style daily review reminder that C2 was written for would need a backend we own, and at that point owning the Firebase project stops being optional. That is the decision to revisit first if notifications are ever built for real.


Detailed cut plans


Step 2 — Lichess branding strings (UI-A through UI-E)

Why: These are Lichess product strings that appear in the ChessSRS UI. They confuse users and misrepresent the product.

What to remove (surgical edits, no file deletions):

Shared dependencies that must remain: LichessMessage widget class in widgets/misc.dart — other callers may exist; remove references not the widget itself. AboutScreen — check if any other caller exists before deciding whether to delete the screen.

Risk: LOW — pure UI text/widget removal, no state or routing changes.

Tests to update: test/app_test.dart — if it asserts the welcome message text, update accordingly.


Step 3 — C7: Watch tab

Why: TV, tournaments, broadcasts, and streamers are Lichess server content with no relevance to repertoire training.

Files — FEATURE-SPECIFIC (delete):

Files — SHARED (edit only):

Shared dependencies to check before deleting:

Risk: MEDIUM — home screen references tournament widgets; must clean those before deleting model files.


Step 4 — C6: Puzzles tab

Why: Tactics training is an explicit non-goal of ChessSRS.

Files — FEATURE-SPECIFIC (delete):

Files — SHARED (edit only):

Risk: MEDIUM — puzzle tab is a whole tab; check home screen home_widgets enum for puzzle references.


Step 5 — C11: Over-the-board game & Clock tool — COMPLETED

Why: Pass-and-play local two-player chess and standalone physical clock simulator are not relevant to local-first SRS.

Status: Completed. Removed view/over_the_board, model/over_the_board, over_the_board_game.dart, OverTheBoardGameResultDialog, OTB navigation entries in play_menu.dart, board_editor_screen.dart, and analysis_actions.dart. Removed standalone clock tool view/clock, model/clock/clock_tool_*, clock_tool_controller_test.dart, and More tab entry. All quality gates (./verify) passed with 0 warnings.

Files — FEATURE-SPECIFIC (deleted):

Files — SHARED (edited):

Game clock stays (lib/src/model/clock/chess_clock.dart & test/model/clock/chess_clock_test.dart — kept; used by in-game countdown clocks).


Step 6 — C10: Blog / recap / announce

Why: Server-pushed content feeds with no relevance to local-first SRS.

Files — FEATURE-SPECIFIC (delete):

Files — SHARED (edit only):

Risk: MEDIUM — home_tab_screen has multiple blog reference sites; methodical line-by-line removal required.


Steps 7–9 — C4 / C5 / C8: Online play, server games, social entries

These are larger cuts with more cross-cutting references. Detailed plans will be written immediately before execution. Key constraint: C3 (auth) stays, so the auth model and HTTP client infrastructure is preserved.

High-level scope:


Steps 10–11 — C17 / C18: WebSocket + HTTP repositories

Execute only after C4–C9 are done and confirmed to have no remaining socket or HTTP consumers outside auth and study-import paths.


What is explicitly NOT cut

Amended by D016: "not cut" here means these directories are not deleted wholesale; it does not mean their visual contents (Lichess colours, Material-styled components, Lichess-specific widgets) are frozen. The visual identity in design/docs/ supersedes the Lichess-styled parts of styles/ and widgets/. Structural/non-visual code in these directories (layout math, platform adapters unrelated to look) may still be kept and reused where it doesn't conflict with the new design.


Completed cuts

Step Feature Commit Date Tests before → after
1 / C9 Learn tab + coordinate training f74627873 2026-09-14 1570 → 1564
2 / UI Lichess branding (donate, about, LichessMessage, welcome card) 1af79eab3 2026-09-14 1564 → 1564
3 / C7 Watch tab (TV / tournaments / broadcasts) dd996ee74 2026-09-15 —
4 / C6 Puzzles tab (model/view, nav, deep links, quick action) fb4847be2 2026-09-15 1242 passing, analyze 0, linux build ok
5 / C11 Over-the-board game & Clock tool 4d1c4b211 & 1b7e18a03 2026-09-14 —
6 / C10 Blog / recap / announce (models, services, home carousel) 156d760fa 2026-09-15 1242 → 1235 passing, analyze 0, linux build ok
7 / C4 Online play: lobby / seek / challenges c2094c070 2026-09-15 1235 → 1182 passing, analyze 0, linux build ok
8 / C5 Server game lifecycle + correspondence bc7b4dcc4 2026-09-15 1182 → 1084 passing, analyze 0, linux build ok
9 / C8 Social navigation entries from More tab 790bb99c2 2026-09-15 1182 passing, analyze 0, linux build ok
12 / Home Home tab + home widgets/prefs (clean 2-tab shell: Review + More) 29c3db19e 2026-09-16 1188 → 1158 passing, analyze 0, linux build ok
13 / C8-Views Dead social views & models (messages, conversations, friend/player screens) b8664182d 2026-09-18 1267 → 1252 passing, analyze 0, linux build ok
14 / User-Views Dead leaderboard & online bot screens, models, and providers b0ad26284 2026-09-18 1252 → 1248 passing, analyze 0, linux build ok
15 / C8-Relation Dead relation repository, following_user model, and follow/block actions db6409c31 2026-09-24 1248 → 1246 passing, analyze 0, linux build ok
16 / C4-Playban Dead playban dialog, notification model & account service monitoring c3e91b183 2026-09-24 1246 passing, analyze 0, linux build ok
17 / UI-Residue Dead orphaned widgets, views & assets (UserContextMenu, ServerOutageDisplay, ExpandedSection, SideIndicator, TextBadge, BrightnessNotifier) 8f0e8f7d3 2026-09-24 1246 passing, analyze 0, linux build ok
18 / Gestures Dead Android system gestures exclusion utility & method channel handler bd4fa6c97 2026-09-24 1246 passing, analyze 0, linux build ok
19 / Android-Widgets Dead Android broadcast widget (BroadcastWidgetProvider, layouts, preview drawables, strings, receiver) 3ed89a5b7 2026-09-30 analyze 0
20 / Home-Widget-Plugin Drop home_widget plugin dependency, board preference listener, AGP 9 workarounds f7ff59df9 2026-09-30 analyze 0
21 / iOS-Widgets Dead iOS LichessWidgets extension (1830 LOC Swift, targets, schemes, FeedKit/flutter-chessground SPM deps) b8777bef8 2026-09-30 pbxproj syntax valid
22 / Firebase-FCM Strip Firebase, Crashlytics, FCM push models & local notifications (Option B) pending 2026-09-30 analyze 0