Commit graph halkyon/packages/halkyon_app/lib/halkyon_app.dart
Author SHA1 Message Date
31b9c17850
feat(app,learn,todo): the operator's "This instance" card, screen and badge (ADR 0021, step 1)
Whoever operates the instance approves, refuses and suspends accounts
and names other operators in the app (D21 to D24, D34):

- halkyon_app: InstanceOperatorRepository on
  ServerpodInstanceOperatorGateway (the module's instanceOperator
  endpoint; refusals as OperatorFailed: notFound, isOperator,
  lastOperator, notAllowed for 401 and 403, failed), in AppDependencies.
  OperatingCubit counts the accounts that wait: asked as the person's
  app opens, after every change, and every 5 minutes only while they
  operate the instance, so nobody else's app keeps asking.
  InstanceOperatorCard (or its tiles) and WaitingAccountsBadge show
  nothing at all for anyone else. InstanceOperatorScreen, laid out as
  Settings: the accounts that wait, each showing its four words to read
  out before Approve (D33), Refuse for new ones after asking; who
  operates, Take back after asking, the last always kept; Name an
  operator by handle; Find an account by the start of its handle, with
  Suspend after asking and Name as operator. English and German texts
  ("Admin" as the role word in German).
- Learn: the card right after the Sync card in Settings, which the
  cogs' count leads to as their dot leads to Sync; the count on the
  Settings cogs of Home and Profile.
- Todo: the tiles in the account sheet and the count on its button.
  Todo's server is a member, whose `operating()` is always null, so
  nothing shows there until it holds the register.

Tests: the gateway's mapping, the repository, both cubits with their
denials, the card, the badge and the screen; Learn's Settings shows the
card and the count to an operator and neither to anyone else.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:17:05 +01:00
7ac715cd97
feat(app): a wait screen for accounts that wait for the operator (ADR 0021, step 1)
An account signed in or up to on an instance whose operator approves
new accounts, or one suspended, gets no session but a wait: the device
keeps its pass, words and handle in the keyring per instance
(SecureAccountWaitStore, InstanceStorage.waitOf), the auth status is
Waiting, and SessionGate shows WaitScreen in its own app, so the start
screen's sheets go with it. Every sign-in flow hands over to it: the
password's sign-in, sign-up, reset and account reset, SimpleLogin's
sign-in, sign-up and mending; their cubits show no problem then.

The wait screen shows the handle, the four words the operator reads out
for the person to match, and that Halkyon never asks anyone to read them
out (D33); it asks where the wait stands as it opens, every 30 seconds,
as the app comes back and when asked. A new account can be deleted with
its pass, a suspended one can't (ADR 0017). Approved, it leads to the
usual sign-in, once more as before (D31); a pass the instance knows no
more ends the wait. The start screen says where new accounts wait for
approval, and offers no Create an account where the operator takes none;
both refusals (closed, too many waiting) have their words, en and de.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:17:05 +01:00
4e33133733
feat(base,app)!: GitHub signs nobody in any more (ADR 0020, D36)
Every account signs in with SimpleLogin or a password now, so GitHub
goes from the module, the shared app package, Learn and Todo, ahead of
ADR 0017's GITHUB_SIGN_IN_ENDS.

- halkyon_base: no `gitHubSignIn` endpoint, GitHub identity provider or
  GitHub refusals; no `githubClientId`/`githubClientSecret`, no
  `GITHUB_SIGN_IN_ENDS` and no `forgetGitHubAccounts`; the register
  loses `account-with-github`. `SignInWays.gitHub`, the basics'
  `gitHubClientId` and `gitHubSignInEnds`, and `PasswordRefusal`'s
  `signInAgain` and `noAddress` go. A first password takes a code
  mailed to the person's own address; an account without one sets none
  (`wrongCredentials`).
- Learn's server: `instance.info()` without `gitHubClientId`, the
  pruning without GitHub, and `minimumAppBuild` 24, as builds up to 23
  (v0.20.0) require `SignInWays.gitHub`: the server deploys with the
  release of build 24.
- halkyon_app: no GitHub button or problems on the start screen, no
  password offered after a GitHub sign-in, and `ServerpodAuthGateway`
  without the browser half only GitHub used (`(client, base:)`). The
  account section offers a first password only to an account with an
  address. `LoopbackWebAuth` and the apps' schemes stay, SimpleLogin's
  ways back.
- Learn's Profile lists no GitHub; the texts go (English, German).
- Serverpod's `serverpod_auth_idp_github_account` stays, as its
  migrations make it; a row goes with its account, and ADR 0020's D36
  gives operators the SQL to delete those left.
- deploy/: compose.yaml and .env.example without GitHub. The READMEs,
  AGENTS.md files and ADR 0017 amended.

BREAKING CHANGE: builds up to 23 can't read the instance's answers
about an account's ways in; minimumAppBuild is 24.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 07:16:39 +01:00
1b79214764
feat(app): a reset of the keys without the phrase, its banner, and the keys' proof key for a password that signs in (ADR 0020, D31–D35)
All checks were successful
CI / check (push) Successful in 31m14s
CI / deploy (push) Has been skipped
SimpleLogin's sheet, its password and its recovery phrase both lost:
under the lost-password step's phrase field, Lost your recovery phrase
too? says what stays and what is lost and asks for new keys
(`keys.startReset`) with the sign-in of a moment ago, which the device
then drops. The next sign-in says when while it waits, and once due
offers new keys, the sign-up's own password and phrase steps
(`keys.completeReset`), or the password, which calls it off (D31).

Every app's home shows `KeysResetBanner` where "too old" shows, and the
account section's SimpleLogin row the same words (`KeysResetNotice`),
each with Call it off, the password's proof alone
(`keys.callOffResets`, D32), and, once due on a device that holds the
keys, Finish here, which wraps the same keys anew (D33, D35). Both show
only a reset of an account that signs in with SimpleLogin alone; ADR
0017's resets show in its own section, which now shares `AlertBox` with
them.

An account whose password signs in has no Forgot your password? there,
and is asked its password and a code once while its keys lack the proof
key, at a sign-in or on adding SimpleLogin (`password.setProofKey`,
D34). `PasswordUnlock` carries the proof key, which its forgetting
leaves, and the keys' password proves for a purpose given.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 23:24:44 +02:00
630965d920
feat(base)!: a handle at password sign-up (ADR 0020, D26, D28–D30)
Every new account gets a handle, and an account without one gets its
first as it sets a password. The handle is private until its person
goes public (D5).

- `passwordSignIn.signUp` takes a handle. `passwordSignIn.checkHandle`
  says whether a sign-up could take one, only while that sign-up's
  mailed code waits (`codeExpired` otherwise), counted per code request
  (30 an hour) and per network (60) (D29). Members ask the register.
- `password.set` takes the first handle of an account without one
  (D28); `SignInWays.handle` tells the app whether there is one.
- Each checks the handle before the code is used, so a refusal leaves
  the code for another try. An existing account at the address keeps
  its own handle where it has one, and the typed handle is still
  checked, so the answer says nothing about the address.
- `first_handle.dart` holds what SimpleLogin, the keys and passwords
  share: `refusalOfNewHandle`, `insertFirstProfile`, `hasHandle`.
- halkyon_app: one `HandleField` with its `HandleCheckCubit` (asked
  300 ms after typing pauses, only about the handle typed last; the
  instance's suggestion is free without asking) (D30). The password
  setup asks for the handle between the phrase check and the code, and
  a refusal at the code sends back to it. SimpleLogin's sign-up and
  keys steps use the same field and now check as it is typed.
- The module's README and ADR 0020 (D29, D30, the built state) amended.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 19:21:39 +02:00
5b9df73756
feat(app)!: no anonymous accounts on the device (ADR 0020, D4, D25)
Every account has a way in, so the apps make no anonymous account any
more. The start screen shows whenever nobody is signed in, a first
start included, and offers SimpleLogin, a password and GitHub for those
who have it, with no start without an account. An instance out of reach
is said so, with Try again, and one offering no way to create an account
too. An anonymous account's session left from before is deleted as the
app starts (D25), falling back to signing out on the device.

What only anonymous accounts needed goes: signing in anonymously, the
signed-out mark, adding GitHub to an account and switching to a GitHub
account from the Profile tab (with its Cancel, the only one), an
anonymous device's own keys, the warning that signing out deletes the
account, and the texts about keeping progress. Settings says on which
instance the account is signed in. Learn's publishing, reports and
suggestions and its learner count no longer ask whether the person has
an identity: everyone signed in has one.

The server still offers anonymous sign-in until the next commit, so
older apps keep working until v0.20.0 raises the minimum build.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 16:14:37 +02:00
fad12b4411
feat(app): adding SimpleLogin to an account (ADR 0020, D21, D23)
All checks were successful
CI / check (push) Successful in 32m34s
CI / deploy (push) Has been skipped
halkyon_app reads where the device's person stands with SimpleLogin
(nothing to show, linked, or missing) and shows it in SimpleLoginSection,
which each app places above the password part: Add SimpleLogin, once
added one more way in. GitHub is added to nobody where SimpleLogin is
offered, and Learn's Profile offers an anonymous person SimpleLogin
alone there (D23).

No cut-off day, reminder or banner (D24): with the developer the only
person on learn.halkyon.eu, the cut-off is the operator taking GitHub's
OAuth app out of the deploy's settings once that account has SimpleLogin.
ADR 0020 records it and drops step 5.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 13:00:28 +02:00
3a93dc4fd8
feat(app): signing in with SimpleLogin, its sheet and the start screen's offer (ADR 0020)
All checks were successful
CI / check (push) Successful in 31m49s
CI / deploy (push) Has been skipped
Where the instance offers SimpleLogin, the start screen offers it first,
saying that the instance keeps only the SimpleLogin ID, and opens a sheet
that waits for SimpleLogin's page in the browser (Cancel ends the wait),
then: for someone new, the choice between creating an account (the
suggested handle or another, checked with the instance, the password
twice, the recovery phrase shown once and three of its words typed back,
nothing reaching the instance before) and linking theirs again with its
handle and recovery phrase; for an account, its password opening its
keys; for an account of before, the keys made with the handle it lacks.
SimpleLoginCubit holds the steps, each refusal shown where it is mended
(a handle's on the handle, a sign-in to begin again on the browser's),
and closing the sheet stops waiting on the loopback.

The suggested handle is never checked: the instance suggests none that
anybody has, and every answer about a handle counts against its limit
(D5). The fake's handle rule is now the module's.

Learn and Todo build it with SimpleLogin's browser (halkyon_oidc_flutter,
now a direct dependency of both, with their names on the loopback's
page), their schemes and their loopback ports (43823, 43824), as the
instance's forwarding page has them. Their tests offer no SimpleLogin;
halkyon_app's test the sheet.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 09:17:50 +02:00
e87b156676
feat(app): signing in with SimpleLogin on the device, its data layer (ADR 0020)
SimpleLoginRepository signs in with SimpleLogin in the browser
(OidcBrowser: the app's scheme on Android, iOS and macOS, the app's port
on the loopback on Linux and Windows, D9), then makes the account's keys
and signs someone new up, opens an account's keys with the password, gives
an account of before its first keys, or links a new SimpleLogin account in
place of a lost one with the recovery phrase (D7), which opens the keys on
the device first: a phrase that doesn't fit sends no proof, and no
password is asked after. An anonymous person adding SimpleLogin keeps the
keys their device holds. Nothing is the device's until `enter`.

SimpleLoginGateway calls halkyon_base's `simpleLogin` and `keys`
endpoints (ServerpodSimpleLoginGateway), with a fake for tests, as does
FakeOidcBrowser. The calls made on clients of their own (signed out, or as
a session not yet the device's) move from the password gateway into
BaseCalls, which both share.

Fixes BaseProtocol, the client of halkyon_base alone, which didn't read
halkyon_oidc's classes: every refusal of SimpleLogin's sign-in on a
signed-out call would have reached the app as a failure. OidcBrowser keeps
its loopback private, so that a fake can implement it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 09:17:50 +02:00
3cb2000927
feat(oidc): halkyon_oidc_flutter, the device's half of signing in with OpenID Connect (ADR 0023)
OidcBrowser opens the page of an attempt the server began (OidcStart),
waits until the browser comes back by an OidcReturn (the app's scheme,
a verified https address, or the loopback) and returns the provider's
code (OidcCode), its refusal (OidcDenied), or nothing when the person
closed the browser. It checks the returned state against the attempt's,
as the server never compares it; a wrong state, neither one code nor
one refusal, no browser or a failed way back are OidcCallbackExceptions.

halkyon_app's LoopbackWebAuth moves in with its tests, relicensed from
AGPL v3 to BSD-3-Clause by its author, the developer; GitHub's sign-in
keeps using it from there until it ends. The package is BSD-3-Clause,
publish_to: none, as the module (ADR 0023, D2, D3).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 09:17:49 +02:00
905bb9abf2
feat(app): content sealed with data keys, and an anonymous person's device keys (ADR 0019)
`ContentCipher` makes a person's data keys, sealed to their account and
signed by it, opens them, and seals and opens a row's content with them for
the row it is stored in (`SodiumContentCipher`, on the isolate it is used
on, as rows are read and written one by one; `FakeContentCipher` for
tests). A sealed payload holds the signature, then the ciphertext.

`AccountCrypto.newDeviceKeys` makes the key pairs an anonymous person's
device encrypts their private studysets with (ADR 0017, point 9, not built
before); setting a password keeps them as the account's
(`keepingKeys` with a new phrase), so nothing they made private is lost.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 14:13:08 +02:00
19aabb0266
fix(app): GitHub's refusals say why, and no hint adds GitHub where passwords are offered
Where an instance offers password sign-in, GitHub is added to nobody (ADR
0017, D2), yet the start screen still said "then add GitHub" after a GitHub
account nobody has, and every refusal but an unknown account read
"Couldn't sign in with GitHub." (the end-to-end test of 21 Oct).

- The gateway tells from one read of the basics whether GitHub signs in and
  whether it is added to people (`gitHubOffer`, in place of
  `gitHubClientId`); the GitHub bloc keeps it (`Ready.linking`) for the
  profile, with why an attempt failed (`Ready.problem`).
- The instance's `usePassword`, `deprecated` and `ended` become problems of
  their own (`gitHubSignInProblemOf`), worded by `gitHubProblemText` and on
  the start screen; an anonymous person refused a link is told the GitHub
  account is nobody's and offered the password instead.
- Where passwords are offered, the start screen offers an account to create
  in place of the app's hint on adding GitHub.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 14:13:06 +02:00
999877d95c
fix(app): nothing secret is copied
The reading of the full reading's fixes found that copied recovery codes
and authenticator keys still reached clipboard histories (Windows, KDE's
Klipper, Gboard, Samsung's) and cloud clipboards, as no Flutter engine
marks a copy as sensitive, and that on Android an app in the background
can't empty the clipboard again, so the minute's clearing often never
happened.

As the developer decided: recovery codes are saved, like the phrase; an
authenticator's key is scanned as a QR code, opened in the authenticator
app, or typed from the screen. copySecret and its texts go.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 14:13:00 +02:00
f3c3f7fb51
fix(app): secrets are kept out of screenshots and the recent apps
The recovery phrase, recovery codes and an authenticator's QR code and
key now show inside a SecretScreen: on Android it holds FLAG_SECURE while
it shows (through a channel each app's MainActivity answers, counting
holds, as several may show at once; ScreenGuard.kt), so screenshots,
screen recordings and the recents view don't get them; on iOS, which has
no such flag, it covers them while the app isn't in front, so the app
switcher's picture doesn't hold them (Dart-side, not checked on a device).
Desktops do neither: a window loses focus whenever its person turns to a
password manager. Todo's debug APK builds with it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 14:12:58 +02:00
a0b7e2bf18
fix(app): the phrase is never copied, codes leave the clipboard a minute later
The recovery phrase opens the account's keys; clipboard histories and
cloud clipboard sync would keep it beyond the app's reach. It is saved as
a file or written down. Recovery codes and an authenticator's key may be
copied, and the clipboard is emptied 60 s later if it still holds them;
they are no longer selectable, as the system's own copy wouldn't empty it.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 14:12:55 +02:00
1d4320370b
fix(auth): a code by mail serves only what it was asked for
Codes by mail were tied to nothing but their address (the re-review's
seventh finding): a hostile instance could answer that a setup never
finished, have the person type a code into it, relay it to start the real
instance's account reset, and 72 hours later relay a second to finish it
with its own password and keys; the mail said "to sign in" whatever the
code was for. A code is now asked for a purpose (signUp, finishSetup,
authenticatorReset, accountReset, deletion), only that purpose takes it (any
other is refused as no longer valid, leaving the code as it was), and its
mail says what it would do ("to reset your account on learn.halkyon.eu: a
new password and new keys, and what was encrypted with the old ones is
lost") and to type it only in the app on that instance, nowhere else. The
app asks for each code with the purpose of its step.

Migration (module, Learn's and Todo's servers, Learn's device schema):
ALTER TABLE "email_code_request" ADD COLUMN "purpose" text; nullable,
so the requests of the last 15 minutes before it stay, taken by nothing
and forgotten within the hour.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 14:12:48 +02:00
ae57cbe864
feat(auth)!: codes by mail no longer sign in
ADR 0017, step 3 (D4: nobody signs in with email codes yet, so the
password replaces them outright). `emailCode.verifyCode`,
`emailSignIn.signIn` and the switch token go, with the register's
`verifySignInCode` and `completeEmailSwitch` and their operations
between services. `emailCode.sendCode` stays, now without a session
(signing up and getting back in have none), as what proves an address
for a password's setup, a sign-up, a setup never finished and the
resets; `accountRecovery.sendCode`, which did that for the resets,
goes. The `switchChallengeId` column stays: Serverpod's challenges
still link a completion token, and older servers read it during a
deploy.

The apps: halkyon_app's email sign-in (its bloc, sheet, gateway calls
and the start screen's Continue with email) goes; Learn's profile and
Todo's account sheet show `AccountSecuritySection` (a password set up
by someone anonymous or signing in with GitHub, whose end it names;
the authenticators; changing the password; new recovery codes and
phrase), which reads anew after every change the repository makes.

BREAKING CHANGE: Learn's `minimumAppBuild` is 22: builds up to 21 sign
in with the endpoints that went. Todo was never released and stays at
build 1.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 14:12:41 +02:00
735dfa892a
test(app): the password screens, signed out and signed in
ADR 0017, step 3: widget tests driving the sheets as a person would,
on halkyon_app's fakes: signing in with a code (a wrong one refused
alike), an account never finished signing in with a code by mail and
setting up its authenticator (QR code, key, recovery codes saved), a
lost authenticator's reset, creating an account (the phrase shown and
three of its words checked before anything reaches the instance), the
recovery phrase getting back in, and the account section (a password
offered to someone anonymous; an authenticator added only with a code
from one before; the password changed).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 14:12:41 +02:00
a4819c3d39
feat(app): sign in with a password and TOTP on the device
ADR 0017, step 3: halkyon_app's side, which Learn and Todo take.

- The account's cryptography on the device (`SodiumAccountCrypto`):
  halkyon_keys and SRP's client half, each step on an isolate of its
  own. sodium hands out results as views of native memory its
  finalizers free once the isolate that made them ends, so everything
  crossing back is copied first: a view read freed memory and crashed
  the process.
- `PasswordGateway` on the module's endpoints (steps without a session
  through a client holding none; an authenticator set up with a session
  granted but not yet the device's), and `PasswordRepository` running
  the flows: signing in (M2 checked, the keys opened before anything is
  trusted), signing up, a password for someone signed in, an account
  never finished, the authenticators, recovery codes, changing the
  password, a new recovery phrase, and getting back in.
- The unlocked keys in the system's keyring per instance and person
  (`SecureAccountKeyStore`), gone once another person signs in or
  nobody, and with the instance's removal.
- The screens: the start screen's Sign in and Create an account (and
  when GitHub sign-in ends), the sign-in sheet (codes, recovery codes,
  an account never finished, a lost authenticator), the account's setup
  (the recovery phrase shown once, three random words typed back before
  anything reaches the instance; the authenticator's QR code, drawn
  with pub.dev's qr; the recovery codes), getting back in, and the
  account section (`AccountSecuritySection`).
- `FileSaver` moves from Learn into halkyon_app, for the phrase and the
  codes in Todo too; `AppDependencies` names it and `passwords`.
- halkyon_keys rebuilds account keys from the secret keys a device kept;
  the key proof's message is in halkyon_base_client, checked against
  the register's.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-23 14:12:41 +02:00
87500b7c6b
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-22 18:11:08 +02:00
96f2d14618
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:50:26 +02:00
e23cc77f7c
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:31:40 +02:00