Lichess mobile app
This report summarizes scanner evidence for lichess-org/mobile, a Dart project with 2,637 GitHub stars. SynthScan examined 937,853 lines of code across 7897 source files, recording 831 pattern matches distributed across 11 syntactic categories. The legacy adjusted pattern signal is 0.7 (Low pattern signal); it is not an authorship estimate.
Use the findings to ask whether maintainers can explain intent, verify dependencies, simplify unnecessary scaffolding, and focus review effort. Risk Payload v1 adds ownership, supply-chain, over-engineering, and review-effort vectors when available. These are prompts for human verification, not probabilities, security findings, or measured maintenance costs.
Use each vector to guide manual verification of the underlying findings.
Payload schema: 1.0.0 · Engine: 2.0.0 · Scoring: heuristic-density-v1 · Scan date: 2026-10-31.
Can maintainers explain intent, assumptions, and trade-offs? 796 finding(s).
Are imports declared and package/API names verified? 7 finding(s).
Are wrappers, guards, and repeated structures necessary? 28 finding(s).
Which changes need focused human review? 831 finding(s).
This chart tracks the legacy adjusted pattern score across scan runs. Changes can reflect source changes, scan coverage, or detector/scoring versions; they do not measure AI adoption or prove that a team refactored code. Compare runs only when their scoring versions are compatible. The dashed line tracks the raw pattern-hit count.
Groups findings by configured severity weight. Severity is not a confidence estimate, security rating, or proof that a package is missing. Verify imports against the project's manifests and inspect each finding in its local design context.
This horizontal bar chart shows where configured pattern scores accumulate across directories. Treat the bars as locations to inspect, not as evidence of authorship or code quality. Use the file-level findings to decide whether a directory needs a focused ownership, dependency, complexity, or review check.
The scanner identified 831 distinct pattern matches across 11 syntactic categories. Each entry below represents a discrete location in the source code where the engine recorded a detector match to inspect in context. Expand any category row to inspect the individual file paths, line numbers, code snippets, and the lexical context (CODE, COMMENT, or STRING) in which each match was detected.
Reading the findings table: The Severity column indicates the configured finding weight (CRITICAL / HIGH / MEDIUM / LOW). The Context column identifies whether the match occurred inside executable code, an inline comment, or a string literal — legacy comment-context matches receive a ×1.5 score weight. The ⚡ bolt icon marks clustered matches: three or more patterns within a 10-line window, each receiving an additional ×1.5 density multiplier under the legacy scoring model.
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | test/network/fake_websocket_channel.dart | 101 | } | COMMENT |
| LOW | test/network/fake_websocket_channel.dart | 201 | /// Makes a write to the sink throw whenever this returns true, as one whose peer is already | COMMENT |
| LOW | test/model/engine/fake_engine.dart | 41 | COMMENT | |
| LOW | test/model/engine/fake_engine.dart | 661 | /// | COMMENT |
| LOW | test/view/explorer/opening_explorer_screen_test.dart | 81 | COMMENT | |
| LOW | test/view/broadcast/broadcast_game_screen_test.dart | 181 | }, | COMMENT |
| LOW | …Widgets/Daily Puzzle Widget/Views/ChessBoardView.swift | 1 | import ChessgroundAssets | COMMENT |
| LOW | ios/Runner/SharePlugin.swift | 1 | import Flutter | COMMENT |
| LOW | ios/Runner/LiveActivityPlugin.swift | 21 | COMMENT | |
| LOW | ios/Runner/LiveActivityPlugin.swift | 41 | /// itself when their `staleDate` passes. | COMMENT |
| LOW | linux/flutter/generated_plugin_registrant.h | 1 | // | COMMENT |
| LOW | scripts/gen_practice.dart | 1 | // Generates the practice content embedded in the app, by crawling lichess.org/practice and writing | COMMENT |
| LOW | scripts/gen_practice.dart | 121 | /// lichess.org judges a practice goal on its own evaluation, which for most users is a cloud eval | COMMENT |
| LOW | scripts/gen_maia_book.dart | 1 | // Generates the opening book the Maia opponents play from, by crawling the Lichess opening | COMMENT |
| LOW | …pts/practice_engine_check/goal_reachability_check.dart | 1 | // Replays every engine practice chapter against the engine the app plays them on, and judges each | COMMENT |
| LOW | …pts/practice_engine_check/goal_reachability_check.dart | 21 | // | COMMENT |
| LOW | scripts/practice_engine_check/build_engine.sh | 1 | #!/usr/bin/env bash | COMMENT |
| LOW | scripts/practice_engine_check/uci_bridge.cpp | 1 | // A plain UCI binary around the engine the app plays practice on. | COMMENT |
| LOW | .github/workflows/draft_github_release.yml | 41 | # distribution: temurin | COMMENT |
| LOW | .github/workflows/draft_github_release.yml | 61 | # echo "storeFile=upload-keystore.jks" >> key.properties | COMMENT |
| LOW | lib/firebase_options.dart | 1 | // File generated by FlutterFire CLI. | COMMENT |
| LOW | lib/l10n/l10n.dart | 61 | COMMENT | |
| LOW | lib/l10n/l10n.dart | 81 | /// Please make sure to update your pubspec.yaml to include the following | COMMENT |
| LOW | lib/l10n/l10n.dart | 101 | /// First, open your project’s ios/Runner.xcworkspace Xcode workspace file. | COMMENT |
| LOW | lib/l10n/l10n.dart | 201 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 221 | String get mobileAccountPreferencesHelp; | COMMENT |
| LOW | lib/l10n/l10n.dart | 241 | /// No description provided for @mobileAppLogs. | COMMENT |
| LOW | lib/l10n/l10n.dart | 261 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 281 | String get mobileBronsteinDelay; | COMMENT |
| LOW | lib/l10n/l10n.dart | 301 | /// No description provided for @mobileChapterName. | COMMENT |
| LOW | lib/l10n/l10n.dart | 321 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 341 | String mobileCodeLengthMessage(String param); | COMMENT |
| LOW | lib/l10n/l10n.dart | 361 | /// No description provided for @mobileCustomizeHomeTip. | COMMENT |
| LOW | lib/l10n/l10n.dart | 381 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 401 | String get mobileDisplayModeDetailed; | COMMENT |
| LOW | lib/l10n/l10n.dart | 421 | /// No description provided for @mobileFilters. | COMMENT |
| LOW | lib/l10n/l10n.dart | 441 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 461 | String mobileGoodEvening(String param); | COMMENT |
| LOW | lib/l10n/l10n.dart | 481 | /// No description provided for @mobileHomeTab. | COMMENT |
| LOW | lib/l10n/l10n.dart | 501 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 521 | String get mobileMoveOnReleaseSubtitle; | COMMENT |
| LOW | lib/l10n/l10n.dart | 541 | /// No description provided for @mobileNextMistake. | COMMENT |
| LOW | lib/l10n/l10n.dart | 561 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 581 | String get mobileOverTheBoard; | COMMENT |
| LOW | lib/l10n/l10n.dart | 601 | /// No description provided for @mobilePerfShortBlitz. | COMMENT |
| LOW | lib/l10n/l10n.dart | 621 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 641 | String get mobilePerfShortFromPosition; | COMMENT |
| LOW | lib/l10n/l10n.dart | 661 | /// No description provided for @mobilePerfShortRacingKings. | COMMENT |
| LOW | lib/l10n/l10n.dart | 681 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 701 | String get mobilePositionLeft; | COMMENT |
| LOW | lib/l10n/l10n.dart | 721 | /// No description provided for @mobilePreviousPage. | COMMENT |
| LOW | lib/l10n/l10n.dart | 741 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 761 | String get mobilePuzzleThemesSubtitle; | COMMENT |
| LOW | lib/l10n/l10n.dart | 781 | /// No description provided for @mobileRemoveBookmark. | COMMENT |
| LOW | lib/l10n/l10n.dart | 801 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 821 | String get mobileSettingsDraggedPieceTarget; | COMMENT |
| LOW | lib/l10n/l10n.dart | 841 | /// No description provided for @mobileSettingsDrawnShapeColorHelp. | COMMENT |
| LOW | lib/l10n/l10n.dart | 861 | /// In en, this message translates to: | COMMENT |
| LOW | lib/l10n/l10n.dart | 881 | String get mobileSettingsPickAnImageBlur; | COMMENT |
| LOW | lib/l10n/l10n.dart | 901 | /// No description provided for @mobileSettingsPickAnImageSwipeToDisplay. | COMMENT |
| 717 more matches not shown… | ||||
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM⚡ | lib/src/widgets/user.dart | 339 | // ── reds / warm (~0–25°) ────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 344 | // ── yellow-greens (~65–130°) ────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 348 | // ── teals / cyans (~170–200°) ───────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 353 | // ── blue-greys (desaturated ~200°) ──────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 356 | // ── blues (~215°) ───────────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 359 | // ── indigos (~230–245°) ─────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 364 | // ── deep purples (~265–275°) ────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 368 | // ── purples (~285–295°) ─────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 371 | // ── pinks (~330–350°) ───────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 376 | // ── reds / warm (~0–25°) ────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 381 | // ── yellow-greens (~65–130°) ────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 385 | // ── teals / cyans (~170–200°) ───────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 390 | // ── blue-greys (desaturated ~200°) ──────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 393 | // ── blues (~215°) ───────────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 396 | // ── indigos (~230–245°) ─────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 401 | // ── deep purples (~265–275°) ────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 405 | // ── purples (~285–295°) ─────────────────────────────────────────── | COMMENT |
| MEDIUM⚡ | lib/src/widgets/user.dart | 408 | // ── pinks (~330–350°) ───────────────────────────────────────────── | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | test/mock_server_responses.dart | 0 | { "id": "$testuserid", "username": "$testuserid", "createdat": 1290415680000, "seenat": 1290415680000, "title": "gm", "p | STRING |
| HIGH | test/network/aggregator_test.dart | 0 | { "id": "$testuserid", "username": "$testuserid", "createdat": 1290415680000, "seenat": 1290415680000, "title": "gm", "p | STRING |
| HIGH | test/model/user/user_repository_test.dart | 0 | { "id": "$testuserid", "username": "$testuserid", "createdat": 1290415680000, "seenat": 1290415680000, "title": "gm", "p | STRING |
| HIGH | test/model/puzzle/puzzle_repository_test.dart | 0 | {"puzzles":[{"game":{"id":"prlkcqov","perf":{"key":"rapid","name":"rapid"},"rated":true,"players":[{"userid":"silverjo", | STRING |
| HIGH | test/view/puzzle/puzzle_screen_test.dart | 0 | {"puzzles":[{"game":{"id":"prlkcqov","perf":{"key":"rapid","name":"rapid"},"rated":true,"players":[{"userid":"silverjo", | STRING |
| HIGH | test/service/puzzle_service_test.dart | 0 | {"puzzles":[{"game":{"id":"prlkcqov","perf":{"key":"rapid","name":"rapid"},"rated":true,"players":[{"userid":"silverjo", | STRING |
| HIGH | test/model/game/game_repository_test.dart | 0 | {"id":"qvchcotc","rated":false,"variant":"standard","speed":"blitz","perf":"blitz","createdat":1673443822389,"lastmoveat | STRING |
| HIGH | test/view/analysis/analysis_screen_test.dart | 0 | {"id":"qvchcotc","rated":false,"variant":"standard","speed":"blitz","perf":"blitz","createdat":1673443822389,"lastmoveat | STRING |
| HIGH | test/view/engine/test_engine_app.dart | 0 | {"id":"qvchcotc","rated":false,"variant":"standard","speed":"blitz","perf":"blitz","createdat":1673443822389,"lastmoveat | STRING |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | lib/src/tab_navigation.dart | 400 | /// The layout that the main tab scaffold gives to the routes of a tab navigator, read by | COMMENT |
| MEDIUM | lib/src/tab_scaffold.dart | 17 | /// Main scaffold that provides the bottom navigation bar and tab switching view. | COMMENT |
| MEDIUM | lib/src/view/study/study_screen.dart | 43 | /// The scaffold is the same while the study is loading and once it is loaded: the iOS 26 pinned bar | COMMENT |
| MEDIUM | lib/src/view/study/study_screen.dart | 44 | /// buttons of the route are lost if its scaffold is replaced. | COMMENT |
| MEDIUM | lib/src/view/analysis/analysis_screen.dart | 116 | // The same tree in every state, so that the scaffold and its app bar are updated, not replaced, | COMMENT |
| MEDIUM | lib/src/view/analysis/retro_screen.dart | 58 | // The same tree in every state, so that the scaffold and its app bar are updated, not replaced, | COMMENT |
| MEDIUM | lib/src/view/analysis/analysis_settings_screen.dart | 32 | // The same tree in every state, so that the scaffold and its app bar are updated, not replaced, | COMMENT |
| MEDIUM | lib/src/view/broadcast/broadcast_round_screen.dart | 494 | // Only what the scaffold needs, so that game updates don't rebuild the whole screen. | COMMENT |
| MEDIUM | lib/src/widgets/platform.dart | 119 | /// image or color theme (transparent scaffold), it is the surface color with the same opacity, so | COMMENT |
| MEDIUM | lib/src/widgets/platform.dart | 299 | // This is the case if this scaffold is built inside a root tab where the main scaffold holds | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW⚡ | test/network/fake_websocket_channel.dart | 247 | // TODO: implement transform | COMMENT |
| LOW⚡ | test/network/fake_websocket_channel.dart | 253 | // TODO: implement transformSink | COMMENT |
| LOW⚡ | test/network/fake_websocket_channel.dart | 259 | // TODO: implement transformStream | COMMENT |
| LOW⚡ | test/network/fake_websocket_channel.dart | 265 | // TODO: implement cast | COMMENT |
| LOW⚡ | test/network/fake_websocket_channel.dart | 271 | // TODO: implement changeSink | COMMENT |
| LOW⚡ | test/network/fake_websocket_channel.dart | 277 | // TODO: implement changeStream | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | test/view/user/user_screen_test.dart | 46 | expect(find.text('John Doe'), findsOneWidget); | CODE |
| LOW | test/view/user/user_screen_test.dart | 982 | "realName": "John Doe" | STRING |
| LOW | test/view/user/search_screen_test.dart | 53 | expect(find.text('John Doe'), findsOneWidget); | CODE |
| LOW | test/view/user/search_screen_test.dart | 101 | "name": "John Doe" | CODE |
| LOW | test/widgets/rich_link_text_test.dart | 131 | expect(email.emailAddress, 'user@example.com'); | CODE |
| LOW | test/widgets/rich_link_text_test.dart | 138 | expect(email.emailAddress, 'user@example.com'); | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| HIGH | scripts/generate_icon.py | 85 | static const String? _kFontPkg = null; | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | pubspec.yaml | 103 | # included with your application, so that you can use the icons in | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | .github/workflows/draft_github_release.yml | 12 | # Creating a release and its tag writes to the repository. | COMMENT |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| MEDIUM | scripts/update_openings_db.py | 16 | print(f"Error: {path_to_chess_openings} is not a valid directory.") | CODE |
| Severity | File | Line | Snippet | Context |
|---|---|---|---|---|
| LOW | lib/src/view/play/challenge_odd_bots_screen.dart | 105 | //special bots have a shorter range of time controls, to prevent an error of the slider we need to check if the time | COMMENT |