feat(icons): every icon fades from cobalt into its app's backdrop; Learn shows its own inside the app; Train's dumbbell (ADR 0031, D5 to D7)
Each icon now ends in the colour its app's screens wash down from: Learn cobalt into purple, Todo through violet into strawberry (Learn's former gradient), Train unchanged into carrot, Meditate's to come through pacific into fern (Todo's former). Train's glyph is Lucide's dumbbell, Meditate's will be the sprout. Learn shows its graduation cap atop the rail, on the launch screen and on halkyon_app's screens, as Todo and Train show theirs; the kingfisher leaves the app and stays the F-Droid repository's icon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
|
|
@ -4,7 +4,7 @@ What holds for every app and package of this repository (how work is done here,
|
|||
|
||||
A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS schedule. Content is rich text (`appflowy_editor` documents) everywhere. Targets: Linux, Windows, macOS, Android, iOS. There is no web build.
|
||||
|
||||
- **Packages** (`apps/learn/`): `halkyon_learn_server` (endpoints, models, migrations), `halkyon_learn_client` (generated, never edited), `halkyon_learn_flutter` (the app, ID `app.halkyon.learn`), `halkyon_learn_curator` (the dictionaries' converter, below), and `tool/` with the scripts that write the app's assets (maps, Anki fixtures; run them from `apps/learn`; its icon, Lucide's graduation cap since ADR 0031, is the root's `tool/app_icons.py`'s, the kingfisher staying the emblem atop the rail and on the launch screen, `assets/icon/halkyon_icon.png`). What every Halkyon server has comes from the Serverpod module `halkyon_base` (ADR 0014, decided 2026-10-19; `packages/halkyon_base_server` and `packages/halkyon_base_client`, which Learn's packages take by path): signing in, the account's ways in and its deletion, the pruning, and the instance's basics. The server sets it up for Learn before it starts (`HalkyonBase.configure(pod, learnBase(accounts: learnAccounts()))` in `server.dart`; `lib/src/auth/learn_base.dart`: Learn holds the instance's register of accounts (ADR 0016), `learnAccounts` reading the members from `INSTANCE_MEMBERS` and refusing to start with `ACCOUNT_REGISTER_URL`; the app it serves, `learn`, which its instances name in their basics and the app checks (never change it), the app's name in sign-in mails, this project's source, `minimumAppBuild`, Learn's own deletion as a hook), its endpoints are `client.modules.base` in the app, its tables (`email_identity`, `email_code_request`) are the module's, and tests that delete people or ask `instance.info()` set it up as the server does (`useLearnBase(sessionBuilder)`). What every Halkyon app's shell has comes from `halkyon_app` on the module's client (ADR 0014, `packages/halkyon_app`; with `halkyon_core`'s `SerialQueue` and `CoalescingRunner`): the instances and the first start, signing in and switching person, the gates (`InstanceGate`, `SessionGate`, `PersonScope`, `AppScope`), the sync service and where it stands, with texts of its own (`HalkyonAppLocalizations`, in `appLocalizationsDelegates`) that name the app from `learnFrame`. Change what they hold there, not here.
|
||||
- **Packages** (`apps/learn/`): `halkyon_learn_server` (endpoints, models, migrations), `halkyon_learn_client` (generated, never edited), `halkyon_learn_flutter` (the app, ID `app.halkyon.learn`), `halkyon_learn_curator` (the dictionaries' converter, below), and `tool/` with the scripts that write the app's assets (maps, Anki fixtures; run them from `apps/learn`; its icon, Lucide's graduation cap since ADR 0031, is the root's `tool/app_icons.py`'s, `assets/icon/halkyon_learn_icon.png`, also atop the rail, on the launch screen and on halkyon_app's screens (`learnIcon`) since 2 Nov 2026, as Todo's and Train's show theirs, ADR 0031, D6). What every Halkyon server has comes from the Serverpod module `halkyon_base` (ADR 0014, decided 2026-10-19; `packages/halkyon_base_server` and `packages/halkyon_base_client`, which Learn's packages take by path): signing in, the account's ways in and its deletion, the pruning, and the instance's basics. The server sets it up for Learn before it starts (`HalkyonBase.configure(pod, learnBase(accounts: learnAccounts()))` in `server.dart`; `lib/src/auth/learn_base.dart`: Learn holds the instance's register of accounts (ADR 0016), `learnAccounts` reading the members from `INSTANCE_MEMBERS` and refusing to start with `ACCOUNT_REGISTER_URL`; the app it serves, `learn`, which its instances name in their basics and the app checks (never change it), the app's name in sign-in mails, this project's source, `minimumAppBuild`, Learn's own deletion as a hook), its endpoints are `client.modules.base` in the app, its tables (`email_identity`, `email_code_request`) are the module's, and tests that delete people or ask `instance.info()` set it up as the server does (`useLearnBase(sessionBuilder)`). What every Halkyon app's shell has comes from `halkyon_app` on the module's client (ADR 0014, `packages/halkyon_app`; with `halkyon_core`'s `SerialQueue` and `CoalescingRunner`): the instances and the first start, signing in and switching person, the gates (`InstanceGate`, `SessionGate`, `PersonScope`, `AppScope`), the sync service and where it stands, with texts of its own (`HalkyonAppLocalizations`, in `appLocalizationsDelegates`) that name the app from `learnFrame`. Change what they hold there, not here.
|
||||
- **Instances**: anyone may run one. The app takes the server URL from `--dart-define=SERVER_URL=…`, so never hard-code a host.
|
||||
|
||||
## Working on Learn
|
||||
|
|
@ -24,7 +24,7 @@ A spaced-repetition learning app: studysets of flashcards, reviewed on an FSRS s
|
|||
- **One graph per context.** The dependency graph belongs to one instance and one signed-in person; when either changes, a `KeyedSubtree` disposes the whole subtree and rebuilds it. Why: nothing from one person's session (blocs, streams, caches) can leak into another's.
|
||||
- **App-wide vs. per person.** `Dependencies` holds what outlives a person (the client, auth). Everything of the signed-in person (their local database, its sync, their repositories) is `PersonDependencies`, opened by halkyon_app's `PersonScope` inside `SessionGate`'s keyed subtree (`Dependencies.openPerson`) and disposed with it; widgets reach it through `PersonDependencies.of(context)`.
|
||||
- **The device's own settings** (appearance, language, citation style, the PDF sidebar's share of the room as last dragged; `core/device_settings/`) belong to the device, not to a person: kept in `<app support>/device_settings.json`, read in `Dependencies.create()` so the first frame already has them, and provided by `DeviceSettingsScope` around halkyon_app's `SessionGate` (`LearnSessions`, `lib/src/app/`), whose plain app takes its look from it (theme mode and language, `DeviceSettings.look`), as `PersonApp` does; halkyon_app's `InstanceGate` reads the same two before any instance opens (`readLook`). What should follow the person to their other devices goes into their synced `PersonSettings` instead.
|
||||
- **Every person has an app of their own** (`PersonApp`, `lib/src/app/`), which `SessionGate` builds inside the keyed subtree: a `MaterialApp.router` with its own `GoRouter`, and `PersonScope` around the router's navigator (`MaterialApp.builder`). So every screen and dialog is inside the person's scope and reads their repositories from `PersonScope.of(context)` wherever it is built, and a change of person disposes the router with every screen on it and starts again at home. Until someone is signed in, `SessionGate` shows a plain `MaterialApp`: the launch screen, then the start screen. **The gates are halkyon_app's** (ADR 0014, shared by Halkyon's apps): `InstanceGate`, `SessionGate`, `PersonScope`, `AppScope`; Learn gives them `learnFrame` (`lib/src/app/learn_frame.dart`: its name and word, kingfisher, theme, languages, launch screen, and the sentence only it can say, the first start's subtitle, in its own words), its dependencies, `ServerpodInstanceChecker(app: 'learn')` and its `CountryNames` (`main.dart`).
|
||||
- **Every person has an app of their own** (`PersonApp`, `lib/src/app/`), which `SessionGate` builds inside the keyed subtree: a `MaterialApp.router` with its own `GoRouter`, and `PersonScope` around the router's navigator (`MaterialApp.builder`). So every screen and dialog is inside the person's scope and reads their repositories from `PersonScope.of(context)` wherever it is built, and a change of person disposes the router with every screen on it and starts again at home. Until someone is signed in, `SessionGate` shows a plain `MaterialApp`: the launch screen, then the start screen. **The gates are halkyon_app's** (ADR 0014, shared by Halkyon's apps): `InstanceGate`, `SessionGate`, `PersonScope`, `AppScope`; Learn gives them `learnFrame` (`lib/src/app/learn_frame.dart`: its name and word, icon, theme, languages, launch screen, and the sentence only it can say, the first start's subtitle, in its own words), its dependencies, `ServerpodInstanceChecker(app: 'learn')` and its `CountryNames` (`main.dart`).
|
||||
- **Platform plugins sit behind small interfaces in `Dependencies`**, with fakes in tests: picking files (`FilePicker`, `file_selector`), saving files the app made, such as exports (`FileSaver`: `file_selector`'s dialog on the desktop, `file_picker`'s on Android and iOS, where `file_selector` has none; halkyon_app's `writePrivately` writes them the own user's alone, on macOS into the chosen file itself, which the sandbox's `files.user-selected.read-write` entitlement allows: keep it, or nothing saves there), playing sounds (`SoundPlayer`, `audioplayers`), playing YouTube videos (`YoutubeVideos`, `youtube_player_iframe` where a web view exists, else the browser), reading PDFs (`PdfEngine`, pdfrx on PDFium, which `flutter test` can't load: widget tests use `FakePdfEngine`, whose PDF bytes say their pages and chapters), cutting Japanese on the device (`JapaneseAnalyser`: MeCab, which needs the IPADIC the app downloads, on an isolate of its own), reading Anki packages on another isolate (`AnkiPackageReader`; a widget test's fake clock can't wait for one), zipping an export with its files on another isolate (`ExportArchiver`, for the same reason), and the Pomodoro timer's side of the system (`PomodoroPlatform`, `FakePomodoroPlatform` in tests).
|
||||
- **Material's `Card` widget clashes with the domain's `Card`**: files that need both import `material_ui` with `hide Card`.
|
||||
- **State: `flutter_bloc`, uniformly.** Blocs fold repository streams into immutable states with value equality, are provided as close to their consumer as possible, and log through one global `BlocObserver`. Why: the data layer is stream-shaped, event → state machines test without widgets, and one pattern everywhere is itself the point.
|
||||
|
|
|
|||
|
Before Width: | Height: | Size: 4.8 KiB After Width: | Height: | Size: 4.7 KiB |
|
Before Width: | Height: | Size: 2.9 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 6.2 KiB After Width: | Height: | Size: 6 KiB |
|
Before Width: | Height: | Size: 10 KiB After Width: | Height: | Size: 9.2 KiB |
|
Before Width: | Height: | Size: 14 KiB After Width: | Height: | Size: 13 KiB |
|
Before Width: | Height: | Size: 1.9 KiB After Width: | Height: | Size: 1.9 KiB |
|
Before Width: | Height: | Size: 1.2 KiB After Width: | Height: | Size: 1.2 KiB |
|
Before Width: | Height: | Size: 2.6 KiB After Width: | Height: | Size: 2.5 KiB |
|
Before Width: | Height: | Size: 4 KiB After Width: | Height: | Size: 4 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 5.1 KiB |
|
Before Width: | Height: | Size: 124 KiB |
|
Before Width: | Height: | Size: 27 KiB After Width: | Height: | Size: 26 KiB |
|
Before Width: | Height: | Size: 32 KiB After Width: | Height: | Size: 31 KiB |
|
Before Width: | Height: | Size: 380 B After Width: | Height: | Size: 379 B |
|
Before Width: | Height: | Size: 941 B After Width: | Height: | Size: 894 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.5 KiB |
|
Before Width: | Height: | Size: 646 B After Width: | Height: | Size: 635 B |
|
Before Width: | Height: | Size: 1.5 KiB After Width: | Height: | Size: 1.4 KiB |
|
Before Width: | Height: | Size: 2.3 KiB After Width: | Height: | Size: 2.2 KiB |