halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
# halkyon_app
|
|
|
|
|
|
|
|
|
|
What every Halkyon app (Learn, Todo, Train) is built around, on an instance's
|
|
|
|
|
`halkyon_base` module: the instances a device keeps an account on, signing in,
|
|
|
|
|
each person's data opened and closed, and syncing it. Taken from Halkyon Learn,
|
|
|
|
|
which it was written for, so it behaves as Learn's own did; the texts are in
|
|
|
|
|
British English, American English and German, and where they name the app,
|
|
|
|
|
they name yours.
|
|
|
|
|
|
|
|
|
|
Written as the apps are: Dart 3.13's primary constructors, Material from
|
|
|
|
|
`package:material_ui`, Lucide icons, blocs (`flutter_bloc`), LeanCode's lints.
|
|
|
|
|
|
|
|
|
|
## What it holds
|
|
|
|
|
|
|
|
|
|
- **Instances** (`lib/src/instance/`): `Instance` (an address as people type
|
|
|
|
|
it), `InstanceChoice` (those the device keeps an account on, one in use;
|
|
|
|
|
`FileInstanceStore`), `InstanceStorage` (each instance's people's folders and
|
|
|
|
|
session key), `InstanceChecker` (does an address answer, where does it keep
|
|
|
|
|
people's data; `ServerpodInstanceChecker` asks its `instance.basics()`),
|
|
|
|
|
`InstanceScope`. The screens: `FirstStartScreen` (the build's instance and
|
|
|
|
|
another, as tiles, `InstanceTile`), `InstanceStep` (an address, checked),
|
|
|
|
|
`showAccountSwitcher` and `AddAccountScreen`, and where an instance keeps
|
|
|
|
|
people's data, as a flag and words (`InstanceLocationBuilder`,
|
halkyon_app: data locations as precise as a country or a state
An operator's DATA_LOCATION and BACKUP_LOCATION may name a subdivision
(ISO 3166-2, US-CA, DE-BY) besides the EU or a country. The app adds
"(EU)" to a member country and the country to a subdivision ("Germany
(EU)", "California, United States", "Bavaria, Germany (EU)"), in
English and German, and shows the most specific flag there is: a
subdivision's own where the flag package has one, else its country's.
Subdivision names come from Unicode CLDR 48.2 (tool/subdivision_names.py),
bundled with their licence, which the app registers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 18:47:25 +02:00
|
|
|
`InstanceFlag`, `instanceLocationWords`): `EU`, a country or a subdivision
|
|
|
|
|
(ISO 3166-2, `US-CA`) as its operator says, "Germany (EU)" for a member of
|
|
|
|
|
the European Union (`europeanUnionMembers`) and "California, United States"
|
|
|
|
|
for a subdivision, with the most specific flag there is (a few subdivisions
|
refactor(halkyon_app): the account section, About, the language and countries' names, shared
What Learn and Todo each held now comes from halkyon_app:
- AccountTiles: whose account on which instance, switching, signing
out (asking first for an anonymous account, as it deletes it) and
deleting it (asking first), the app's own words passed in.
- showAppAbout: the app's name, version, icon and legalese as it gives
them, the source of what the instance runs (AppDependencies gains
sourceCodeUrl), the app's own parts, the licence list.
- resolveAppLocale: the first preferred language the app speaks, never
by country alone, British English otherwise.
- AssetCountryNames, the default of InstanceGate and InstanceScope:
Unicode CLDR 48.2's territory names in English and German
(tool/country_names.py, 7.6 KB), Hong Kong, Macao, Myanmar and
Palestine by their short names as Learn's world map has them, so
Todo says "Germany (EU)" where it said "DE (EU)".
Learn's texts read as before in every language; its About bloc and
gateway, its country names from the world map and the keys only the
moved code used are gone, as are Todo's copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 23:51:52 +02:00
|
|
|
have their own, such as Scotland; else the country's). Countries and
|
|
|
|
|
subdivisions are named by Unicode CLDR's names in English and German, which
|
|
|
|
|
come with the package (`AssetCountryNames`, `AssetSubdivisionNames`; the
|
|
|
|
|
package's `tool/country_names.py` and `tool/subdivision_names.py` write
|
|
|
|
|
them, from one release); tests give names of their own (`CountryNames`,
|
|
|
|
|
`SubdivisionNames`).
|
halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
- **Gates** (`lib/src/app/`): `InstanceGate` opens the app's dependencies on
|
|
|
|
|
the instance in use (`AppDependencies`, handed down by `AppScope`), anew on
|
|
|
|
|
a switch, the previous ones closed first; `SessionGate` keeps a person
|
|
|
|
|
signed in, signing in anonymously at a first start and asking how to start
|
|
|
|
|
once signed out (`StartScreen`); `PersonScope` opens each person's own
|
refactor(halkyon_app): the account section, About, the language and countries' names, shared
What Learn and Todo each held now comes from halkyon_app:
- AccountTiles: whose account on which instance, switching, signing
out (asking first for an anonymous account, as it deletes it) and
deleting it (asking first), the app's own words passed in.
- showAppAbout: the app's name, version, icon and legalese as it gives
them, the source of what the instance runs (AppDependencies gains
sourceCodeUrl), the app's own parts, the licence list.
- resolveAppLocale: the first preferred language the app speaks, never
by country alone, British English otherwise.
- AssetCountryNames, the default of InstanceGate and InstanceScope:
Unicode CLDR 48.2's territory names in English and German
(tool/country_names.py, 7.6 KB), Hong Kong, Macao, Myanmar and
Palestine by their short names as Learn's world map has them, so
Todo says "Germany (EU)" where it said "DE (EU)".
Learn's texts read as before in every language; its About bloc and
gateway, its country names from the world map and the keys only the
moved code used are gone, as are Todo's copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 23:51:52 +02:00
|
|
|
dependencies and closes them when the person goes. `resolveAppLocale`
|
|
|
|
|
picks the app's language as every Halkyon app does: the first preferred
|
|
|
|
|
language it speaks, never by country alone, British English otherwise.
|
|
|
|
|
- **About** (`showAppAbout`): the app's name (its frame's), its version,
|
|
|
|
|
icon and legalese where it gives them, where the source of what the
|
|
|
|
|
instance runs is (`InstanceSourceCode`, from `AppDependencies.sourceCodeUrl`;
|
|
|
|
|
both licences require offering it), the app's own parts (its credits), and
|
|
|
|
|
the licence list.
|
|
|
|
|
- **The account** (`AccountTiles`): whose account on which instance,
|
|
|
|
|
switching to the account on another, signing out (which of an anonymous
|
|
|
|
|
account deletes it, so asks first) and deleting it (after asking), for an
|
|
|
|
|
app's settings, in the app's words where only it can say them.
|
halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
- **Signing in** (`lib/src/auth/`): `AuthRepository` on an `AuthGateway`;
|
|
|
|
|
`ServerpodAuthGateway` signs in anonymously, with GitHub (the system
|
|
|
|
|
browser and a loopback address on Linux and Windows, `LoopbackWebAuth`) and
|
|
|
|
|
with a code by mail, through the instance's `halkyon_base`; `SessionBloc`,
|
|
|
|
|
`GitHubSignInBloc`, `EmailSignInBloc` (with `showEmailSignInSheet`) and
|
|
|
|
|
`SignInWaysCubit`; `SignedOutMark`, `SignInAddresses`, `PersonFolders`.
|
|
|
|
|
- **Sync** (`lib/src/sync/`): `SyncService` (once at start, shortly after
|
|
|
|
|
local changes, on resume and every few minutes, one sync at a time, counting
|
|
|
|
|
the changes not on the server yet, `PendingChangesStore`), `AppCompatibility`
|
|
|
|
|
and `compatibleSync` (the instance's minimum app build, the engine's table
|
|
|
|
|
check), `SyncStatusBloc`, `syncAndWait`, and the widgets: `SyncStatusCard`,
|
|
|
|
|
`AppMismatchBanner`, `SyncTroubleBadge`.
|
|
|
|
|
- **A client of `halkyon_base` alone** (`BaseClient`): to ask an instance
|
|
|
|
|
about itself, and to sign in as another person than the app's client holds.
|
|
|
|
|
- **Testing** (`package:halkyon_app/testing.dart`): fakes of every boundary
|
|
|
|
|
(`FakeAuthGateway`, `FakeInstanceChecker`, the stores in memory), a frame
|
|
|
|
|
(`testFrame`), `testApp`, and `TestAppDependencies`.
|
|
|
|
|
|
|
|
|
|
## Using it in an app
|
|
|
|
|
|
|
|
|
|
1. **The server** includes `halkyon_base` with the nickname `base` (its
|
|
|
|
|
README), so the app's generated client has `client.modules.base`.
|
2026-10-19 20:39:09 +02:00
|
|
|
2. **Depend on it** by path, within the repository's workspace, as on
|
|
|
|
|
`halkyon_ui`:
|
halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
|
|
|
|
|
```yaml
|
|
|
|
|
dependencies:
|
|
|
|
|
halkyon_app:
|
2026-10-19 20:39:09 +02:00
|
|
|
path: ../../../packages/halkyon_app
|
halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
```
|
|
|
|
|
|
|
|
|
|
3. **Texts**: add `HalkyonAppLocalizations.delegate` to every `MaterialApp`'s
|
|
|
|
|
delegates, beside `HalkyonUiLocalizations.delegate` and material_ui's
|
|
|
|
|
`GlobalMaterialLocalizations.delegates`.
|
|
|
|
|
4. **The frame** (`AppFrame`): the app's name and wordmark word, its icon, an
|
refactor(halkyon_app): the account section, About, the language and countries' names, shared
What Learn and Todo each held now comes from halkyon_app:
- AccountTiles: whose account on which instance, switching, signing
out (asking first for an anonymous account, as it deletes it) and
deleting it (asking first), the app's own words passed in.
- showAppAbout: the app's name, version, icon and legalese as it gives
them, the source of what the instance runs (AppDependencies gains
sourceCodeUrl), the app's own parts, the licence list.
- resolveAppLocale: the first preferred language the app speaks, never
by country alone, British English otherwise.
- AssetCountryNames, the default of InstanceGate and InstanceScope:
Unicode CLDR 48.2's territory names in English and German
(tool/country_names.py, 7.6 KB), Hong Kong, Macao, Myanmar and
Palestine by their short names as Learn's world map has them, so
Todo says "Germany (EU)" where it said "DE (EU)".
Learn's texts read as before in every language; its About bloc and
gateway, its country names from the world map and the keys only the
moved code used are gone, as are Todo's copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 23:51:52 +02:00
|
|
|
example address, its theme, delegates, languages (`resolveAppLocale` as
|
|
|
|
|
its resolution) and launch screen, and the three sentences only it can
|
|
|
|
|
say: what the first start's instance keeps of the person's, what a switch
|
|
|
|
|
to another account leaves behind, and where GitHub is added later.
|
halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
5. **The dependencies**: the app's composition root implements
|
refactor(halkyon_app): the account section, About, the language and countries' names, shared
What Learn and Todo each held now comes from halkyon_app:
- AccountTiles: whose account on which instance, switching, signing
out (asking first for an anonymous account, as it deletes it) and
deleting it (asking first), the app's own words passed in.
- showAppAbout: the app's name, version, icon and legalese as it gives
them, the source of what the instance runs (AppDependencies gains
sourceCodeUrl), the app's own parts, the licence list.
- resolveAppLocale: the first preferred language the app speaks, never
by country alone, British English otherwise.
- AssetCountryNames, the default of InstanceGate and InstanceScope:
Unicode CLDR 48.2's territory names in English and German
(tool/country_names.py, 7.6 KB), Hong Kong, Macao, Myanmar and
Palestine by their short names as Learn's world map has them, so
Todo says "Germany (EU)" where it said "DE (EU)".
Learn's texts read as before in every language; its About bloc and
gateway, its country names from the world map and the keys only the
moved code used are gone, as are Todo's copies.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 23:51:52 +02:00
|
|
|
`AppDependencies<P>` (`serverName`, `auth`, `signedOut`, `sourceCodeUrl`,
|
|
|
|
|
`openPerson`, `dispose`), its person's dependencies `Disposable`. Its `auth` is an
|
halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
`AuthRepository` on `ServerpodAuthGateway(client, base:
|
|
|
|
|
client.modules.base, callbackScheme: …, appName: …)`; the scheme is the
|
|
|
|
|
app's own (`app.halkyon.learn`), registered with each platform and in the
|
|
|
|
|
instance's GitHub OAuth app, whose loopback address on Linux and Windows is
|
|
|
|
|
`http://127.0.0.1/github-callback` (port 43823 unless given).
|
|
|
|
|
6. **The tree**:
|
|
|
|
|
|
|
|
|
|
```dart
|
|
|
|
|
InstanceGate(
|
|
|
|
|
frame: frame,
|
|
|
|
|
choice: choice,
|
|
|
|
|
checker: ServerpodInstanceChecker(app: 'todo'),
|
|
|
|
|
readLook: …, // the device's theme mode and language
|
|
|
|
|
open: Dependencies.create,
|
|
|
|
|
app: SessionGate(
|
|
|
|
|
look: (context) => …,
|
|
|
|
|
app: (person) => PersonApp(person: person), // with PersonScope inside
|
|
|
|
|
),
|
|
|
|
|
)
|
|
|
|
|
```
|
|
|
|
|
|
|
|
|
|
A person's widgets find their dependencies with
|
|
|
|
|
`PersonScope.of<PersonDependencies>(context)` and the app's with
|
|
|
|
|
`AppScope.of<Dependencies>(context)`.
|
halkyon_app: data locations as precise as a country or a state
An operator's DATA_LOCATION and BACKUP_LOCATION may name a subdivision
(ISO 3166-2, US-CA, DE-BY) besides the EU or a country. The app adds
"(EU)" to a member country and the country to a subdivision ("Germany
(EU)", "California, United States", "Bavaria, Germany (EU)"), in
English and German, and shows the most specific flag there is: a
subdivision's own where the flag package has one, else its country's.
Subdivision names come from Unicode CLDR 48.2 (tool/subdivision_names.py),
bundled with their licence, which the app registers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 18:47:25 +02:00
|
|
|
7. **Licences**: call `registerHalkyonAppLicenses()` once at startup (the
|
|
|
|
|
flags' and Unicode CLDR's notices), beside halkyon_ui's
|
|
|
|
|
`registerFontLicenses()`.
|
halkyon_app: instances, sign-in and sync plumbing for Halkyon's apps
What every Halkyon app's shell needs, taken from Halkyon Learn's and set
on halkyon_base's client: the instances a device keeps an account on,
the first start's tiles and where an instance keeps people's data, the
account switcher; signing in anonymously, with GitHub or an email code,
switching person and the start screen; the gates that build an app's
dependencies per instance and per person; and the sync service with its
status, Settings card and banner. Texts of its own in English and
German take the app's name; what only the app can say, it passes in.
Fakes for apps' tests are in testing.dart.
An instance's basics now name the app it serves (`app`, set by the
host's configuration), and an app accepts only an instance of its own.
SerialQueue and CoalescingRunner move into halkyon_core.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 15:18:49 +02:00
|
|
|
|
|
|
|
|
## Licence
|
|
|
|
|
|
|
|
|
|
GNU Affero General Public License 3.0 or later (`LICENSE`). The flags are
|
|
|
|
|
lipis' flag-icons, drawn by the `flag` package, under the MIT licence
|
halkyon_app: data locations as precise as a country or a state
An operator's DATA_LOCATION and BACKUP_LOCATION may name a subdivision
(ISO 3166-2, US-CA, DE-BY) besides the EU or a country. The app adds
"(EU)" to a member country and the country to a subdivision ("Germany
(EU)", "California, United States", "Bavaria, Germany (EU)"), in
English and German, and shows the most specific flag there is: a
subdivision's own where the flag package has one, else its country's.
Subdivision names come from Unicode CLDR 48.2 (tool/subdivision_names.py),
bundled with their licence, which the app registers.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-19 18:47:25 +02:00
|
|
|
(`assets/flags/LICENSE-flag-icons.txt`). The subdivisions' names are Unicode
|
|
|
|
|
CLDR's (release 48.2), under the Unicode License v3
|
|
|
|
|
(`assets/subdivisions/LICENSE-CLDR.txt`, `assets/subdivisions/ATTRIBUTION.md`).
|