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
{
"@@locale": "en",
"tryAgain": "Try again",
"instanceWelcome": "Welcome to {app}",
"@instanceWelcome": {
"description": "Screen readers hear it for the wordmark on the first start.",
"placeholders": {
"app": {
"type": "String"
}
}
},
"instanceBuiltInTagline": "Comes with the app",
"instanceAnother": "Another instance",
"instanceAnotherTagline": "Enter its address",
"instanceAddress": "Instance address",
"instanceAddressInvalid": "This isn't an address.",
fix(app)!: instances over plain HTTP are refused, but on this machine
What the app sends an instance (the proofs that sign in, the keys'
wrappings, every request a session carries) must not be read or changed on
the way, so an instance is reached over HTTPS, or over plain HTTP only on
this machine (localhost, 127.0.0.0/8, ::1), as Jellyfin servers are. An
address over plain HTTP elsewhere is refused before anything is sent there.
One kept from before is never opened: the app says why, and offers another
instance or removing it from the device, never dropping it silently.
Development builds still use their own instance over HTTP (an emulator
reaches the developer's machine as 10.0.2.2); releases never.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-21 14:35:21 +02:00
"instanceAddressInsecure": "{app} reaches instances over HTTPS only, so that nobody on the way reads or changes what it sends. Ask for the instance's HTTPS address.",
"@instanceAddressInsecure": {
"placeholders": {
"app": {
"type": "String"
}
}
},
"instanceInsecure": "{name} is reached over plain HTTP, where anyone on the way could read and change what {app} sends, so {app} no longer opens it. Its account stays on {name} and on this device. Use another instance, or remove it from this device.",
"@instanceInsecure": {
"placeholders": {
"name": {
"type": "String"
},
"app": {
"type": "String"
}
}
},
"instanceInsecureUseAnother": "Use another instance",
"instanceInsecureRemove": "Remove from this device",
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
"instanceAddressRequired": "Enter the address of an instance.",
"instanceContinue": "Continue",
"instanceUnreachable": "No instance of {app} answers at {name}.",
"@instanceUnreachable": {
"placeholders": {
"app": {
"type": "String"
},
"name": {
"type": "String"
}
}
},
"instanceUseBuiltIn": "Use {name}",
"@instanceUseBuiltIn": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"instanceOpenFailed": "Couldn't open the app on {name}.",
"@instanceOpenFailed": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"instanceDataAndBackupsIn": "Data and backups: {place}",
"@instanceDataAndBackupsIn": {
"placeholders": {
"place": {
"type": "String"
}
}
},
"instanceDataIn": "Data: {place}",
"@instanceDataIn": {
"placeholders": {
"place": {
"type": "String"
}
}
},
"instanceBackupsIn": "Backups: {place}",
"@instanceBackupsIn": {
"placeholders": {
"place": {
"type": "String"
}
}
},
"instanceNoBackups": "No backups",
"instanceEuropeanUnion": "European Union",
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
"instanceCountryInEu": "{country} (EU)",
"@instanceCountryInEu": {
"description": "A country of the European Union, where an instance keeps data: Germany (EU).",
"placeholders": {
"country": {
"type": "String"
}
}
},
"instanceSubdivisionOf": "{subdivision}, {country}",
"@instanceSubdivisionOf": {
"description": "A state or region with its country, where an instance keeps data: California, United States.",
"placeholders": {
"subdivision": {
"type": "String"
},
"country": {
"type": "String"
}
}
},
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
"accountsTitle": "Accounts",
"accountAdd": "Add account",
"accountAddSubtitle": "Choose the instance for the account to add.",
"accountSignedIn": "Signed in",
"accountRemoveTooltip": "Remove account",
"accountRemoveTitle": "Remove account?",
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 14:50:55 +02:00
"accountRemoveMessage": "Removes the account on {name} from this device, with all the device holds of it. What it synced stays on {name}: sign in there again to have it.",
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
"@accountRemoveMessage": {
"placeholders": {
"name": {
"type": "String"
}
}
},
"accountRemoveConfirm": "Remove",
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
"accountSignedInOn": "Signed in on {instance}",
"@accountSignedInOn": {
"placeholders": {
"instance": {
"type": "String"
}
}
},
"accountSwitch": "Switch account",
"accountSignOut": "Sign out",
"accountDelete": "Delete account",
"accountDeleteTitle": "Delete your account?",
2026-10-21 19:49:18 +02:00
"accountSignOutFailed": "Couldn't sign out on this device. Try again.",
"accountDeleteFailed": "Couldn't delete your account: the instance wasn't reached. Try again.",
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
"aboutSourceCode": "The source of what this instance runs:",
"aboutSourceUnreachable": "Couldn't reach this instance to ask where its source is.",
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
"sessionUnreachable": "Couldn't reach the server.",
"personDataFailed": "Couldn't open this device's data.",
"startSignedOut": "Signed out",
"startOn": "Start on {instance}:",
"@startOn": {
"placeholders": {
"instance": {
"type": "String"
}
}
},
"startOtherInstance": "Use another instance",
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 14:50:55 +02:00
"startNoWay": "{instance} offers no way to create an account.",
"@startNoWay": {
"description": "On the start screen, where the instance offers neither SimpleLogin nor a password (ADR 0020, D4): nobody can make an account there.",
"placeholders": {
"instance": {
"type": "String"
}
}
},
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-25 15:29:22 +01:00
"startSignUpsApproved": "New accounts on {instance} wait until its operator approves them.",
"@startSignUpsApproved": {
"description": "On the start screen, where new accounts wait for the operator's approval (Halkyon Learn's ADR 0021, D19).",
"placeholders": {
"instance": {
"type": "String"
}
}
},
"startSignUpsClosed": "{instance} takes no new accounts: sign in with yours.",
"@startSignUpsClosed": {
"description": "On the start screen, where the operator takes no new accounts (D19); Create an account is gone.",
"placeholders": {
"instance": {
"type": "String"
}
}
},
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
"syncTitle": "Sync",
"syncNow": "Sync now",
"syncStateSyncing": "Syncing…",
"syncStateSynced": "Everything synced",
"syncStateFailing": "Sync is failing",
"syncStateNotYet": "Not synced yet",
"syncStateMismatch": "This app can't sync with {server}",
"@syncStateMismatch": {
"placeholders": {
"server": {
"type": "String"
}
}
},
"syncMismatchDetail": "Update the app. If it is up to date, the server needs an update. What you do meanwhile stays on this device and syncs afterwards.",
"syncLastCompleted": "Last completed at {time}",
"@syncLastCompleted": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"syncLastFailed": "Last attempt failed at {time}",
"@syncLastFailed": {
"placeholders": {
"time": {
"type": "String"
}
}
},
"syncNoRunYet": "No sync has completed since the app started",
"syncPending": "{count, plural, =1{1 change waits to sync} other{{count} changes wait to sync}}",
"@syncPending": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"syncTroubleLabel": "Sync needs attention",
"syncMismatchBanner": "This app can't sync with {server} any more. Update it to keep syncing.",
"@syncMismatchBanner": {
"placeholders": {
"server": {
"type": "String"
}
}
},
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-20 15:42:45 +02:00
"syncMismatchMore": "More",
"startSignIn": "Sign in",
"startCreateAccount": "Create an account",
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-25 15:29:22 +01:00
"waitWaiting": "Your account waits until the operator of {instance} approves it.",
"@waitWaiting": {
"description": "On the wait screen of a new account (D31), under its handle.",
"placeholders": {
"instance": {
"type": "String"
}
}
},
"waitSuspended": "The operator of {instance} has suspended your account. Only they can lift it.",
"@waitSuspended": {
"description": "On the wait screen of a suspended account (D31).",
"placeholders": {
"instance": {
"type": "String"
}
}
},
"waitApproved": "The operator of {instance} has approved your account. Sign in once more, as you did before.",
"@waitApproved": {
"description": "On the wait screen once the account is approved (D31), above Sign in.",
"placeholders": {
"instance": {
"type": "String"
}
}
},
"waitEnded": "This sign-up has ended: the operator of {instance} refused it, or it waited too long.",
"@waitEnded": {
"description": "On the wait screen once the instance knows the wait no more: refused, past its 14 days, or its pass past its 30 (D31).",
"placeholders": {
"instance": {
"type": "String"
}
}
},
"waitBack": "Back to the start",
"@waitBack": {
"description": "The button of an ended wait: the start screen."
},
"waitMatchingCode": "Matching code",
"@waitMatchingCode": {
"description": "Above the four words the operator reads out for the person to match (D33)."
},
"waitMatchingNote": "When the operator of {instance} reads you four words, check that they are these. Never read them out yourself: Halkyon never asks anyone to.",
"@waitMatchingNote": {
"description": "Under the matching code (D33): the operator reads, the person confirms, so nobody posing as the operator learns the words.",
"placeholders": {
"instance": {
"type": "String"
}
}
},
"waitCheckNow": "Check now",
"@waitCheckNow": {
"description": "Asks the instance at once where the wait stands; it asks every 30 seconds by itself."
},
"waitUnreachable": "{instance} couldn't be reached. It's asked again every 30 seconds.",
"@waitUnreachable": {
"description": "On the wait screen, when asking where the wait stands failed.",
"placeholders": {
"instance": {
"type": "String"
}
}
},
"waitDelete": "Delete this account",
"@waitDelete": {
"description": "On the wait screen of a new account: deletes it with its pass (D31)."
},
"waitDeleteTitle": "Delete this account?",
"@waitDeleteTitle": {
"description": "The question before a waiting account is deleted."
},
"waitDeleteMessage": "It goes with everything it has, and nobody can sign in to it again.",
"@waitDeleteMessage": {
"description": "Under that question."
},
"waitDeleteFailed": "Couldn't delete the account. Try again.",
"@waitDeleteFailed": {
"description": "On the wait screen, when deleting the account failed."
},
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-20 15:42:45 +02:00
"passwordAddress": "Email address",
"passwordPassword": "Password",
"passwordNewPassword": "New password",
"passwordCurrent": "Current password",
"passwordRepeat": "New password again",
"passwordsDiffer": "The two passwords differ.",
"passwordShow": "Show password",
"passwordHide": "Hide password",
"passwordContinue": "Continue",
"passwordBack": "Back",
"passwordDone": "Done",
"passwordSave": "Save as a file",
"passwordQrCode": "QR code for your authenticator app",
"passwordMailCode": "Code from the email",
"passwordMailCodeSent": "We emailed a code to {address}. It's valid for 15 minutes.",
"@passwordMailCodeSent": {
"placeholders": {
"address": {
"type": "String"
}
}
},
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 18:09:04 +02:00
"passwordHandleIntro": "A code is on its way to {address}. Meanwhile, choose your handle: others find you by it. It stays private until you publish something or fill in your profile, and can change until your first publish.",
"@passwordHandleIntro": {
"placeholders": {
"address": {
"type": "String"
}
}
},
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-20 15:42:45 +02:00
"passwordResendCode": "Send a new code",
"passwordTotpCode": "Code from your authenticator app",
"passwordRecoveryCode": "Recovery code",
"passwordUseRecoveryCode": "Use a recovery code instead",
"passwordUseTotpCode": "Use your authenticator app instead",
"passwordUnlocking": "Unlocking your password on this device…",
"passwordMakingKeys": "Making your keys on this device…",
"passwordSignInTitle": "Sign in",
"passwordSignInAction": "Sign in",
"passwordSignInIntro": "With the email address and password of your account on {instance}.",
"@passwordSignInIntro": {
"placeholders": {
"instance": {
"type": "String"
}
}
},
"passwordSecondFactorIntro": "Enter the code your authenticator app shows for {instance}.",
"@passwordSecondFactorIntro": {
"placeholders": {
"instance": {
"type": "String"
}
}
},
"passwordRecoveryCodeIntro": "Enter one of the recovery codes you kept. Each signs in once.",
"passwordForgot": "Forgot your password?",
"passwordOtherPassword": "Change the address or password",
"passwordLostAuthenticator": "Lost your authenticator?",
"passwordLostAuthenticatorTitle": "Lost authenticator",
"passwordLostAuthenticatorIntro": "With a recovery code you sign in as usual. Without one, ask to reset your authenticators: it takes effect 72 hours later, and signing in with a code before then stops it.",
"passwordResetWithMail": "Reset with a code by email",
"passwordResetWithMailHint": "We email a code to {address}.",
"@passwordResetWithMailHint": {
"placeholders": {
"address": {
"type": "String"
}
}
},
"passwordResetWithPhrase": "Reset with your recovery phrase",
"passwordResetWithPhraseHint": "The 24 words you wrote down.",
"passwordSignInWithoutCode": "Sign in without a code",
"passwordSignInWithoutCodeHint": "Once a reset you asked for has taken effect.",
"passwordResetAction": "Reset",
"passwordAuthenticatorResetAt": "Your authenticators are reset on {date}. We emailed you: if it wasn't you, sign in with a code before then to stop it. After that, sign in with your password alone and set up a new authenticator.",
"@passwordAuthenticatorResetAt": {
"placeholders": {
"date": {
"type": "String"
}
}
},
"passwordUnfinishedIntro": "Your account was never finished. We emailed a code to {address}: with it, you sign in and set up an authenticator.",
"@passwordUnfinishedIntro": {
"placeholders": {
"address": {
"type": "String"
}
}
},
"passwordPhraseField": "Recovery phrase",
"passwordPhraseFieldHint": "The 24 words, with spaces between them",
"passwordPhraseFieldIntro": "Enter your recovery phrase: it proves the account is yours.",
"passwordAuthenticatorTitle": "Set up an authenticator",
"passwordAuthenticatorIntro": "Every sign-in takes a code from an authenticator app, such as Aegis, 2FAS or Proton Authenticator. Scan this code with it, or type in the key.",
"passwordAuthenticatorKey": "Key",
2026-11-03 22:07:35 +01:00
"passwordAuthenticatorKeyCopied": "Key copied. It's taken off the clipboard again in a minute.",
"@passwordAuthenticatorKeyCopied": {
"description": "After the authenticator's key was copied during its setup; the app empties the clipboard 60 seconds later."
},
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-20 15:42:45 +02:00
"passwordAuthenticatorOpen": "Open in your authenticator app",
"passwordAuthenticatorNoApp": "No app on this device opens it. Scan the code or type in the key.",
"passwordAuthenticatorCodeIntro": "Then enter the code it shows.",
2026-10-21 10:42:55 +02:00
"passwordAuthenticatorOwnerIntro": "To add it, confirm it is you: your password and a code from an authenticator you added before.",
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-20 15:42:45 +02:00
"passwordExistingCode": "Code from an authenticator you added before",
"passwordRecoveryCodesTitle": "Recovery codes",
"passwordRecoveryCodesIntro": "Each signs in once in place of a code from your authenticator, should you lose it. Keep them where only you find them: they're shown only now.",
"passwordCodesKept": "I've kept them",
"passwordPhraseTitle": "Your recovery phrase",
"passwordPhraseIntro": "These 24 words open your account's keys should you forget your password: without them, what's encrypted is lost. Write them down in order, and keep them where only you find them. They're shown only now.",
"passwordPhraseWrittenDown": "I've written it down",
"passwordPhraseCheckTitle": "Check your recovery phrase",
"passwordPhraseCheckIntro": "Type the words asked for, from what you wrote down.",
"passwordPhraseWord": "Word {number}",
"@passwordPhraseWord": {
"placeholders": {
"number": {
"type": "int"
}
}
},
"passwordPhraseWrong": "These aren't the words. Look again at what you wrote down.",
2026-10-29 22:34:02 +01:00
"passwordPhraseNotAWord": "Not a word a recovery phrase is made of",
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-20 15:42:45 +02:00
"passwordPhraseShowAgain": "Show the phrase again",
"passwordCreateTitle": "Create an account",
"passwordCreateIntro": "On {instance}, with your email address, a password, and an authenticator app for the code every sign-in takes.",
"@passwordCreateIntro": {
"placeholders": {
"instance": {
"type": "String"
}
}
},
"passwordSetUpTitle": "Set up a password",
"passwordSetUpIntro": "Sign in with your email address, a password and an authenticator app, on this device and others. Everything you have stays.",
"passwordChooseIntro": "Choose a password. It never leaves this device: the instance can't read it, and can't reset it without what it protects being lost.",
"passwordFinishResetTitle": "Finish the reset",
"passwordFinishResetIntro": "72 hours after you asked to reset your account, finish it here with a new code by email.",
"passwordFinishResetHavePhrase": "I have my recovery phrase",
"passwordFinishResetKeepsKeys": "Your account keeps its keys: nothing encrypted is lost.",
"passwordFinishResetNewKeys": "Without your recovery phrase, your account gets new keys: what was encrypted with the old ones is lost.",
"passwordRecoveryTitle": "Get back into your account",
"passwordRecoveryWithPhrase": "I have my recovery phrase",
"passwordRecoveryWithPhraseHint": "Set a new password at once. Everything stays.",
"passwordRecoveryWithPhraseIntro": "Your recovery phrase opens your account's keys, which your new password then protects. A code from your authenticator, or a recovery code, confirms it.",
"passwordRecoveryNoCodeHint": "Lost your authenticator as well? Reset your account 72 hours later with your recovery phrase: it keeps everything.",
"passwordRecoveryWithoutPhrase": "Reset my account",
"passwordRecoveryWithoutPhraseHint": "Takes effect 72 hours later. Without your recovery phrase, what's encrypted is lost.",
"passwordRecoveryStartIntro": "We email a code to your address. Your account can then be reset 72 hours later; signing in with your password before then stops it.",
"passwordRecoveryFinish": "Finish a reset",
"passwordRecoveryFinishHint": "72 hours after you asked for it.",
"passwordResetStarted": "Your account can be reset from {date}. We emailed you: if it wasn't you, sign in with your password before then to stop it.",
"@passwordResetStarted": {
"placeholders": {
"date": {
"type": "String"
}
}
},
"passwordSectionTitle": "Password and authenticator",
"passwordSectionFailed": "Couldn't read how your account signs in.",
"passwordSectionSetUp": "Set up a password",
"passwordSectionSetUpHint": "Sign in with your email address, a password and an authenticator app.",
"passwordSectionUnfinished": "Finish setting up: add an authenticator",
"passwordSectionUnfinishedHint": "Your password signs in once an authenticator is set up.",
"passwordSectionChange": "Change password",
"passwordSectionKeysMissing": "Sign in on this device with your password to change it.",
"passwordSectionNotConfirmed": "Not set up yet",
"passwordSectionRemove": "Remove {name}?",
"@passwordSectionRemove": {
"placeholders": {
"name": {
"type": "String"
}
}
},
2026-10-21 10:42:55 +02:00
"passwordSectionRemoveIntro": "Enter your password and a code from one of your authenticators to confirm.",
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-20 15:42:45 +02:00
"passwordSectionRemoveAction": "Remove",
"passwordSectionAddAuthenticator": "Add an authenticator",
"passwordSectionRecoveryCodes": "New recovery codes",
"passwordSectionRecoveryCodesHint": "The old ones stop working.",
"passwordSectionNewPhrase": "New recovery phrase",
"passwordSectionPhraseHint": "Your old phrase stops working.",
"passwordChangeTitle": "Change password",
"passwordChangeIntro": "Your other devices are signed out. Your keys stay: nothing is lost.",
"passwordChangeAction": "Change password",
"passwordChanged": "Password changed. Your other devices are signed out.",
2026-10-21 10:42:55 +02:00
"passwordNewCodesIntro": "Enter your password and a code from your authenticator app. Your old recovery codes stop working.",
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-20 15:42:45 +02:00
"passwordNewPhraseIntro": "Your new recovery phrase. Write the 24 words down in order: once you confirm it, your old phrase opens nothing any more.",
"passwordPhraseKept": "New recovery phrase set. The old one no longer works.",
"passwordWrongCredentials": "The address, the password or the code isn't right.",
"passwordTooManyAttempts": "Too many attempts. Try again in an hour.",
"passwordSetupIncomplete": "Your account was never finished: set up an authenticator.",
"passwordNotDue": "This takes effect on {date}.",
"@passwordNotDue": {
"placeholders": {
"date": {
"type": "String"
}
}
},
"passwordNotDueYet": "This hasn't taken effect yet.",
"passwordNoReset": "No reset waits for this address.",
"passwordExists": "This address's account has a password already. Sign in with it, or get back in if you've forgotten it.",
"passwordAddressTaken": "This address belongs to another account.",
"passwordNoPassword": "This account has no password.",
"passwordWrongCode": "This code isn't right. Enter the one your app shows now.",
"passwordLastAuthenticator": "Your last authenticator can't go: every sign-in needs one.",
"passwordTooManyAuthenticators": "Your account has as many authenticators as it may. Remove one first.",
"passwordUnknownAuthenticator": "This authenticator is gone.",
"passwordInvalidSetup": "The instance didn't accept what this device made. Update the app, or try again.",
"passwordChallengeExpired": "That took too long. Try again.",
"passwordUnavailable": "This instance offers no password sign-in.",
"passwordNotAnAddress": "This isn't an email address.",
"passwordTooManyCodes": "Too many codes asked for. Try again in an hour.",
"passwordWrongMailCode": "This code isn't right.",
"passwordTooManyMailTries": "Too many wrong codes. Ask for a new one.",
"passwordMailCodeExpired": "This code is no longer valid. Ask for a new one.",
"passwordMailFailed": "The instance couldn't send the email. Try again later.",
"passwordOtherAddressLinked": "Your account has another email address.",
"passwordNotAPhrase": "This isn't a recovery phrase: check every word and their number.",
"passwordPhraseDoesNotFit": "The address or the recovery phrase isn't right.",
"passwordUnusable": "This password can't be used: it's empty, or holds characters that aren't text.",
"passwordInstanceUnproven": "The instance couldn't prove it knows your password, so this device trusts nothing it says. Try again; if it happens again, the instance isn't the one it claims to be.",
"passwordKeysMissing": "Your account's keys aren't on this device. Sign out and in again with your password here.",
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-25 15:29:22 +01:00
"signUpsClosed": "This instance takes no new accounts.",
"@signUpsClosed": {
"description": "A sign-up refused: the operator takes no new accounts (D19)."
},
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-25 15:55:28 +01:00
"signUpsTooManyWaiting": "Too many new accounts already wait for approval. Try again later.",
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-25 15:29:22 +01:00
"@signUpsTooManyWaiting": {
"description": "A sign-up refused: ten new accounts wait for the operator's approval already (D19)."
},
2026-10-20 20:04:37 +02:00
"passwordUnsafeCosts": "The instance asked for your password to be checked in a way no account uses, so nothing was sent. If it happens again, the instance isn't the one it claims to be.",
2026-10-20 21:25:47 +02:00
"passwordFailed": "Couldn't reach the instance. Check the connection and try again.",
"passwordMinLength": "At least {count} characters",
"@passwordMinLength": {
"placeholders": {
"count": {
"type": "int"
}
}
},
"passwordTooShort": "A new password needs at least {count} characters: should the instance's database ever leak, a short one is guessed first.",
"@passwordTooShort": {
"placeholders": {
"count": {
"type": "int"
}
}
fix(auth): codes wait after ten wrong in a row, and the owner is told
Whoever knew the password could guess codes at 10 an hour forever,
unnoticed, each guess checked against every authenticator (the second
reading's M4). Every code an account's second factor refuses after a
right password (sign-in, a phrase's reset, a change) or from a signed-in
device (removing an authenticator, new recovery codes, the code from one
confirmed before) now counts, per account, from every network; after
every tenth in a row no code is taken (codesWait, saying until when), an
hour, then twice as long each time, a day at the most (longestCodeWait,
second_factor_waits.dart): at a day, ten guesses a day still have about a
1 % chance a year against one authenticator, so the first wait mails the
owner that someone knows their password. The instance keeps no address,
so sign-in and the phrase's reset now send the address the device typed
(and its language), used only for that mail after its hash is checked
against the account's. A code accepted clears it all.
Migration (module, Learn's and Todo's servers, Learn's device schema):
CREATE TABLE "second_factor_failures" ("id" bigserial PRIMARY KEY,
"authUserId" uuid NOT NULL, "failures" bigint NOT NULL, "waits"
bigint NOT NULL, "waitUntil" timestamp, "updatedAt" timestamp NOT
NULL) with a unique index on "authUserId": a new table, nothing
touched.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 21:47:38 +02:00
},
"passwordCodesWait": "Too many wrong codes in a row: no code is taken until {date}. If they weren't yours, someone knows your password: change it then.",
"@passwordCodesWait": {
"placeholders": {
"date": {
"type": "String"
}
}
2026-10-20 21:59:42 +02:00
},
fix(auth): a session's wrong codes end the session, not the account's codes
Wrong codes for removing an authenticator, new recovery codes or confirming
one counted toward the account's waits (the re-review's third finding): a
stolen session alone, without the password, could keep the owner's codes
waiting for up to a day at a time, renewed without end, holding them out
of signing in and of changing the password, the way to end that session,
and nobody was mailed. Those codes now count per session, where the session
is (the register's own, or a member's, which forwards the code): each try
counted before its code is checked (one statement, so tries sent at once
count each), a code accepted clearing the count, and the tenth wrong in a
row ending the session (sessionEnded; the device says it was signed out).
A try that checked no code (a refusal of something else) is taken back.
The account's waits now count only codes after a right password, and the
per-account ten tries an hour, which a session could fill as well, go.
Migration (module, Learn's and Todo's servers, Learn's device schema):
CREATE TABLE "session_code_failures" ("id" bigserial PRIMARY KEY,
"sessionId" uuid NOT NULL, "authUserId" uuid NOT NULL, "failures"
bigint NOT NULL, "updatedAt" timestamp NOT NULL) with a unique index on
"sessionId": a new table, nothing touched. Its rows go with a code
accepted, with the person, and hourly once their session is gone.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 22:53:20 +02:00
"passwordDeleteIntro": "Your account goes with everything in it, in every service of this instance. To confirm, type your password and a code from your authenticator app.",
fix(auth): every call with a code after the password takes the address
Whoever guessed codes decided whether the owner was mailed (the re-review's
fourth finding): the address a sign-in sent was optional, so a guesser left
it out and no mail went, and a wait begun without a mail used up the one
mail there was. Every call carrying a code after the password or the phrase
proved (signing in, the phrase's reset, a change of password, a new phrase,
deletion) now takes the account's address and the language of its mails,
and is refused (wrongCredentials, as anything wrong after the password)
before any code is checked when the address isn't the account's; every
wait mails the owner, not only the first. The app sends the address the
person typed, or for changes while signed in the one this device signed in
with, and says so where it doesn't know it (addressUnknown).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-20 23:08:25 +02:00
"passwordSessionEnded": "Too many wrong codes in a row from this device: it was signed out. Sign in again with your password and a code.",
2026-10-21 10:42:55 +02:00
"passwordAddressUnknown": "Enter your account's email address: the code goes there.",
"passwordDeleteIntroByMail": "Your account goes with everything in it, in every service of this instance. None of its authenticators works now, so a code by mail confirms it: type your password, then the code we email to your address.",
2026-10-20 23:32:49 +02:00
"passwordDeleteSendCode": "Email me a code",
2026-10-21 12:10:38 +02:00
"passwordAuthenticatorPasswordIntro": "To add it, confirm it is you with your password.",
"passwordWarningTitle": "Someone tried your password",
"passwordWarningBody": "On {date}, a device signed in to your account sent a wrong password or code ten times in a row, and was signed out. If that wasn't you, someone else may have that device or its session: change your password.",
"@passwordWarningBody": {
"placeholders": {
"date": {
"type": "String"
}
}
},
"passwordWarningDismiss": "Dismiss",
fix(auth): a reset waits while its owner is held out, and devices show it
The full reading's H1: an owner could be kept from calling a reset off, and
the reset then went through. Whoever knew the password and had the mailbox
asked an authenticators' reset and kept the codes waiting, so the owner's
sign-ins were refused until it took effect; whoever had the mailbox filled
the address's proofs ceiling from three networks and completed an account
reset. Now every hold-out of the owner after a reset was asked (a wait
that begins, an attempt a wait refuses, a sign-in or proof refused by the
address's ceiling) moves the account's resets not carried out to take
effect no sooner than 72 hours after it ends (pushOutResets). A reset
carried out (the password alone signing in until an authenticator is
confirmed) is never pushed, nor a lapsed one brought back.
Signed-in devices are told too: SignInWays carries when a waiting reset
of each kind takes effect, the account section shows them with Call it
off, which takes the owner's proof (password and code; password.
callOffResets, forwarded by members) and calls every reset off, as no wait
holds a signed-in owner's proof back. Texts in English and German.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-21 13:17:30 +02:00
"passwordWarningLater": "Remind me later",
"passwordResetsTitle": "A reset is waiting",
"passwordResetsAuthenticator": "Your authenticators will be removed on {date}: someone asked with your password. If that wasn't you, call it off and change your password.",
"@passwordResetsAuthenticator": {
"placeholders": {
"date": {
"type": "String"
}
}
},
"passwordResetsAccount": "Your account will get a new password and new keys on {date}, and what is encrypted under the old ones will be lost: someone asked with a code by mail. If that wasn't you, call it off.",
"@passwordResetsAccount": {
"placeholders": {
"date": {
"type": "String"
}
}
},
"passwordResetsCallOff": "Call it off",
"passwordCallOffTitle": "Call off the reset",
fix(auth): GitHub-only accounts go at GitHub's end; only the anonymous add an address
The full reading's M1 and L6: once GitHub IDs were forgotten at
GITHUB_SIGN_IN_ENDS, an account that had signed in with GitHub alone
counted as anonymous, so a stolen session and any mailbox gave it a first
password and authenticator; and such accounts, with no way in left, were
never pruned. Now:
- at the end, the daily run that forgets the GitHub IDs first deletes the
accounts that sign in with GitHub alone (no address, so never a
password), with everything they have, members told (deletePerson); the
app tells them for the three months before that their account goes then
unless they set up a password (passwordSectionGitHubEndsDeletes, en/de);
- password.set adds an address without GitHub only for someone anonymous
where the session lives (anonymousHere, which a member tells the
register, as it tells freshGitHubSignIn); anyone else is refused before
any code is used.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-21 13:23:40 +02:00
"passwordCallOffIntro": "Your password and a code call off every reset waiting.",
feat(app): signing in with SimpleLogin, its sheet and the start screen's offer (ADR 0020)
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 01:21:29 +02:00
"passwordSignedInBefore": "Only a device signed in since then can dismiss this warning: sign in again on this one, or change your password.",
"simpleLoginContinue": "Continue with SimpleLogin",
"simpleLoginNote": "SimpleLogin is Proton's sign-in, and a Proton account works there too. It learns that you use {instance}, which keeps only your SimpleLogin ID: your name suggests a handle once, and nothing else is kept.",
"@simpleLoginNote": {
"placeholders": {
"instance": {
"type": "String"
}
}
},
"simpleLoginTitle": "Sign in with SimpleLogin",
"simpleLoginAddTitle": "Add SimpleLogin",
"simpleLoginInBrowser": "Continue in your browser.",
"simpleLoginNewcomerTitle": "New here?",
"simpleLoginNewcomerIntro": "Your SimpleLogin account has no account on {instance} yet.",
"@simpleLoginNewcomerIntro": {
"placeholders": {
"instance": {
"type": "String"
}
}
},
"simpleLoginCreateHint": "Choose a handle and a password; you get a recovery phrase.",
"simpleLoginHaveOne": "I already have one",
"simpleLoginHaveOneHint": "Link it again with your handle and your recovery phrase.",
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 18:09:04 +02:00
"handleIntro": "Others find you by your handle. It stays private until you publish something or fill in your profile, and can change until your first publish.",
"handleLabel": "Handle",
"handleFree": "@{handle} is free",
"@handleFree": {
feat(app): signing in with SimpleLogin, its sheet and the start screen's offer (ADR 0020)
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 01:21:29 +02:00
"placeholders": {
"handle": {
"type": "String"
}
}
},
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 18:09:04 +02:00
"handleChecking": "Checking…",
feat(app): signing in with SimpleLogin, its sheet and the start screen's offer (ADR 0020)
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 01:21:29 +02:00
"simpleLoginPasswordIntro": "Choose a password: you type it when you sign in on another device. It never leaves this device: the instance can't read it, and can't reset it without what it protects being lost.",
"simpleLoginCreate": "Create the account",
"simpleLoginUnlockTitle": "Your password",
"simpleLoginUnlockIntroAs": "Signed in with SimpleLogin as @{handle}. Your password opens your keys on this device.",
"@simpleLoginUnlockIntroAs": {
"placeholders": {
"handle": {
"type": "String"
}
}
},
"simpleLoginUnlockIntro": "Signed in with SimpleLogin. Your password opens your keys on this device.",
"simpleLoginOpen": "Open",
"simpleLoginMendTitle": "Link your account",
"simpleLoginMendIntro": "Your SimpleLogin account isn't the one your account knows any more. Link them again with your handle and your recovery phrase: your other devices are then signed out.",
"simpleLoginMend": "Link",
"simpleLoginWrongPassword": "The password isn't right.",
"simpleLoginMendDoesNotFit": "The handle or the recovery phrase isn't right.",
feat(app): a lost password with the recovery phrase in SimpleLogin's sheet (ADR 0020, 7)
An account that signs in with SimpleLogin opens its keys with its
password. Where that is lost, the unlock step's "Forgot your password?"
opens one step: the recovery phrase, and a new password twice. There is
no address and no code: the SimpleLogin sign-in of a moment ago stands
for them (D20).
- The device opens the keys with the phrase (`keys.beginPhrase`) and
wraps them under the new password (`AccountCrypto.newKeysPassword`).
It then signs the phrase's proof bound to that password
(`signKeysResetProof`), which `keys.resetPassword` takes with the
sign-in. The keys stay, and every other session ends.
- A phrase that doesn't open the keys is refused on the device, and the
instance gets no proof to count. Two passwords that differ are
refused before anything is sent.
- The gateway's `beginPhrase` and `resetPassword` run on the account's
own session, not yet the device's. The repository's `resetPassword`
ties them together, and the cubit and sheet have their `forgot` step.
Three texts are new, in English and German.
- halkyon_base_server exports `isKeysPassword` for the interop test,
which checks against the register's checks, with real libsodium, what
the device sends.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-24 21:25:47 +02:00
"simpleLoginForgotTitle": "A new password",
"simpleLoginForgotIntro": "Your recovery phrase opens your keys, which your new password then protects. Everything stays; your other devices are signed out.",
"simpleLoginForgotDoesNotFit": "The recovery phrase isn't right.",
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)
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 22:37:17 +02:00
"simpleLoginResetWaiting": "New keys were asked for: from {date}, a SimpleLogin sign-in can set them up. Your password opening your keys calls it off.",
"@simpleLoginResetWaiting": {
"placeholders": {
"date": {
"type": "String"
}
}
},
"simpleLoginPhraseLost": "Lost your recovery phrase too?",
"simpleLoginResetTitle": "New keys",
"simpleLoginResetIntro": "Without your password or your recovery phrase, nobody can open your keys, not even {instance}. New ones can be set up 72 hours from now, when you sign in with SimpleLogin again. Your handle, your profile and what you published stay; what was encrypted under your old keys is lost, unless you finish on a device still signed in, which keeps them.",
"@simpleLoginResetIntro": {
"placeholders": {
"instance": {
"type": "String"
}
}
},
"simpleLoginResetIntroDevices": "Meanwhile your signed-in devices show the reset, and your password calls it off there, should someone else have asked for it.",
"simpleLoginResetAsk": "Ask for new keys",
"simpleLoginResetAskedTitle": "New keys asked for",
"simpleLoginResetAskedIntro": "From {date}, sign in with SimpleLogin again to set them up. Should you find your password or your recovery phrase before then, opening your keys with it calls the reset off.",
"@simpleLoginResetAskedIntro": {
"placeholders": {
"date": {
"type": "String"
}
}
},
"simpleLoginResetDueTitle": "Your new keys",
"simpleLoginResetDueIntro": "The new keys you asked for can be set up now. Or open your old keys with your password, which calls the reset off.",
"simpleLoginResetNewKeys": "Set up new keys",
"simpleLoginResetNewKeysHint": "A new password and recovery phrase. What was encrypted under your old keys is lost, unless you finish on a device still signed in.",
"simpleLoginResetHavePassword": "I have my password",
"simpleLoginResetHavePasswordHint": "Your keys open, and stay.",
"simpleLoginResetKeepIntro": "Choose a new password: your keys stay, under it and a new recovery phrase, so nothing is lost. Your other devices are signed out.",
"simpleLoginResetFinish": "Set up the keys",
"simpleLoginProofKeyTitle": "Your password and a code",
"simpleLoginProofKeyIntroAs": "Signed in with SimpleLogin as @{handle}. Your account also signs in with a password: type it and a code from your authenticator once, and your password opens your keys after SimpleLogin from then on.",
"@simpleLoginProofKeyIntroAs": {
"placeholders": {
"handle": {
"type": "String"
}
}
},
"simpleLoginProofKeyIntro": "Your account also signs in with a password: type it and a code from your authenticator once, and your password opens your keys after SimpleLogin from then on.",
"simpleLoginResetBanner": "New keys were asked for with your SimpleLogin account: from {date}, whoever has it can set them up. Unless that was you, call it off with your password.",
"@simpleLoginResetBanner": {
"placeholders": {
"date": {
"type": "String"
}
}
},
"simpleLoginResetBannerDue": "New keys were asked for with your SimpleLogin account, and whoever has it can set them up now. Unless that was you, call it off with your password.",
"simpleLoginResetBannerDueHere": "New keys were asked for with your SimpleLogin account and can be set up now. If that was you, finish here: your keys stay. If not, call it off with your password.",
"simpleLoginResetFinishHere": "Finish here",
"simpleLoginCallOffIntro": "Your password calls off the reset of your keys.",
"simpleLoginResetCalledOff": "Reset called off.",
"simpleLoginResetFinished": "New password and recovery phrase set.",
feat(app): signing in with SimpleLogin, its sheet and the start screen's offer (ADR 0020)
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 01:21:29 +02:00
"simpleLoginUnavailable": "This instance doesn't offer sign-in with SimpleLogin.",
"simpleLoginDenied": "SimpleLogin's sign-in was declined, so nothing was shared.",
"simpleLoginNoBrowser": "There's no browser to open SimpleLogin's page in.",
"simpleLoginStartAgain": "The sign-in took too long, or another one took its place. Begin it again.",
"simpleLoginRejected": "SimpleLogin didn't confirm the sign-in. Begin it again.",
"simpleLoginProviderUnavailable": "The instance can't reach SimpleLogin right now. Try again later.",
"simpleLoginTakenByAnotherPerson": "This SimpleLogin account belongs to another account here.",
"simpleLoginOtherIdentityLinked": "Your account has another SimpleLogin account already.",
"simpleLoginTooManyAttempts": "Too many sign-ins began from this network. Try again in an hour.",
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 18:09:04 +02:00
"handleInvalid": "A handle is 1 to 30 letters (a to z), digits or underscores.",
"handleReserved": "This handle is kept for the instance.",
"handleTaken": "Someone else has this handle.",
"handleTooManyChecks": "Too many handles were tried. Try again in an hour.",
2026-10-24 11:56:26 +02:00
"simpleLoginSectionTitle": "SimpleLogin",
"simpleLoginSignsYouIn": "Signs you in",
"simpleLoginAddHint": "Sign in with SimpleLogin, or with your Proton account.",
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-25 15:55:28 +01:00
"simpleLoginAdded": "SimpleLogin added.",
"operatorTitle": "This instance",
"@operatorTitle": {
"description": "The card and screen for whoever operates the instance (ADR 0021, D34)."
},
"operatorAccounts": "Accounts and operators",
"@operatorAccounts": {
"description": "The tile leading to the operator's screen."
},
"operatorWaitingCount": "{count, plural, =0{Nobody waits for approval} =1{1 account waits for approval} other{{count} accounts wait for approval}}",
"@operatorWaitingCount": {
"description": "How many accounts wait for the operator; also the badge's label.",
"placeholders": {
"count": {
"type": "int"
}
}
},
"operatorWaitingTitle": "Waiting",
"@operatorWaitingTitle": {
"description": "The card of accounts that wait for approval, new or suspended."
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"operatorOperatorsCount": "{count, plural, =1{1 operator} other{{count} operators}}",
"@operatorOperatorsCount": {
"description": "How many operate the instance, in the overview's row.",
"placeholders": {
"count": {
"type": "int"
}
}
},
"operatorAccountsTitle": "Accounts",
"@operatorAccountsTitle": {
"description": "The overview's row leading to every account, and that screen's title."
},
"operatorAccountsCount": "{count, plural, =1{1 account} other{{count} accounts}}",
"@operatorAccountsCount": {
"description": "How many accounts the instance has.",
"placeholders": {
"count": {
"type": "int"
}
}
},
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-25 15:55:28 +01:00
"operatorNobodyWaits": "Nobody waits.",
"@operatorNobodyWaits": {
"description": "The waiting card, empty."
},
"operatorNoHandle": "No handle yet",
"@operatorNoHandle": {
"description": "An account without a handle, where its handle would be."
},
"operatorNewSince": "New, waiting since {date}",
"@operatorNewSince": {
"description": "A new account that waits, and since when.",
"placeholders": {
"date": {
"type": "String"
}
}
},
"operatorSuspendedSince": "Suspended since {date}",
"@operatorSuspendedSince": {
"description": "A suspended account, and since when.",
"placeholders": {
"date": {
"type": "String"
}
}
},
"operatorApproveNote": "Read them these four words, and approve only once they confirm these are on their screen.",
"@operatorApproveNote": {
"description": "Before approving: the words prove it is their account that waits (D33)."
},
"operatorApprove": "Approve",
"@operatorApprove": {
"description": "Approves a waiting account."
},
"operatorRefuse": "Refuse",
"@operatorRefuse": {
"description": "Refuses a new account: deleted."
},
"operatorRefuseTitle": "Refuse this account?",
"@operatorRefuseTitle": {
"description": "The question before refusing."
},
"operatorRefuseMessage": "It is deleted with everything it has. They can sign up again.",
"@operatorRefuseMessage": {
"description": "What refusing does."
},
"operatorOperatorsTitle": "Operators",
"@operatorOperatorsTitle": {
"description": "The card of who operates the instance."
},
"operatorTakeBack": "Take back",
"@operatorTakeBack": {
"description": "Takes back the operator's role."
},
"operatorTakeBackTitle": "Take back the role?",
"@operatorTakeBackTitle": {
"description": "The question before taking the role back."
},
"operatorTakeBackMessage": "They keep their account, but no longer approve, refuse or suspend accounts here.",
"@operatorTakeBackMessage": {
"description": "What taking the role back does."
},
"operatorAppoint": "Name an operator",
"@operatorAppoint": {
"description": "Names an account an operator, by its handle."
},
"operatorAppointMessage": "They approve, refuse and suspend accounts here, and name operators, as you do.",
"@operatorAppointMessage": {
"description": "What an operator may do."
},
"operatorAppointConfirm": "Name",
"@operatorAppointConfirm": {
"description": "Confirms naming an operator."
},
"operatorHandle": "Handle",
"@operatorHandle": {
"description": "The field for an account's handle."
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"operatorHandleSuggested": "Only handles there are here are suggested.",
"@operatorHandleSuggested": {
"description": "Below a field suggesting handles as they are typed."
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-25 15:55:28 +01:00
},
"operatorFoundNone": "No account's handle starts like this.",
"@operatorFoundNone": {
"description": "A search that found nothing."
},
"operatorMadeOn": "Since {date}",
"@operatorMadeOn": {
"description": "When an account was made.",
"placeholders": {
"date": {
"type": "String"
}
}
},
"operatorIsOperator": "Operator",
"@operatorIsOperator": {
"description": "A tag: the account operates the instance."
},
"operatorIsWaiting": "Waiting",
"@operatorIsWaiting": {
"description": "A tag: the account waits for approval."
},
"operatorIsSuspended": "Suspended",
"@operatorIsSuspended": {
"description": "A tag: the account is suspended."
},
"operatorSuspend": "Suspend",
"@operatorSuspend": {
"description": "Suspends an account."
},
"operatorSuspendTitle": "Suspend this account?",
"@operatorSuspendTitle": {
"description": "The question before suspending."
},
"operatorSuspendMessage": "They are signed out everywhere and can't sign in until you approve them again. Nothing they have is deleted.",
"@operatorSuspendMessage": {
"description": "What suspending does."
},
"operatorMakeOperator": "Name as operator",
"@operatorMakeOperator": {
"description": "Names a found account an operator."
},
"operatorNotFound": "That account isn't there any more, or doesn't wait.",
"@operatorNotFound": {
"description": "Refused: no such account, or it doesn't wait."
},
"operatorIsOperatorProblem": "An operator can't be suspended. Take back their role first.",
"@operatorIsOperatorProblem": {
"description": "Refused: suspending an operator."
},
"operatorLastOperator": "The instance needs at least one operator.",
"@operatorLastOperator": {
"description": "Refused: taking back the last operator's role."
},
"operatorNotAllowed": "Only the instance's operators can do this.",
"@operatorNotAllowed": {
"description": "Refused: the person doesn't operate the instance."
},
"operatorFailed": "That didn't work. Try again.",
"@operatorFailed": {
"description": "Any other failure."
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
},
"operatorAssistantNotSet": "That assistant isn't set up here any more.",
"@operatorAssistantNotSet": {
"description": "The assistant asked for is no longer in deploy/assistants.env."
},
"operatorInvalidLimit": "A limit can't be below zero.",
"@operatorInvalidLimit": {
"description": "A limit below 0 was refused."
},
"assistantGrantsTitle": "Assistants",
"@assistantGrantsTitle": {
"description": "The operator's card of the assistants granted (Halkyon Learn's ADR 0021)."
},
"assistantGrantsNone": "Nobody has an assistant yet.",
"@assistantGrantsNone": {
"description": "No assistant is granted to anyone."
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"assistantSetCount": "{count, plural, =0{None set up} =1{1 assistant} other{{count} assistants}}",
"@assistantSetCount": {
"description": "How many assistants are set up, in the overview's row.",
"placeholders": {
"count": {
"type": "int"
}
}
},
"assistantPeopleCount": "{count, plural, =0{nobody granted} =1{1 person} other{{count} people}}",
"@assistantPeopleCount": {
"description": "How many people have an assistant, after how many are set up.",
"placeholders": {
"count": {
"type": "int"
}
}
},
"assistantSetUpTitle": "Set up here",
"@assistantSetUpTitle": {
"description": "The card of the assistants set up in deploy/assistants.env."
},
"assistantPeopleTitle": "People",
"@assistantPeopleTitle": {
"description": "The card, and the sheet section, of the people granted assistants."
},
"assistantGranteesFoundNone": "Nobody with an assistant has a handle starting like this.",
"@assistantGranteesFoundNone": {
"description": "No person granted one matches the start of a handle typed."
},
"assistantUnpriced": "No prices: counts its tokens only",
"@assistantUnpriced": {
"description": "An assistant set up without prices, which a budget counts in tokens alone."
},
"assistantEffort": "{effort, select, low{low} medium{medium} high{high} xhigh{extra high} max{max} other{{effort}}}",
"@assistantEffort": {
"description": "How much a model thinks before it answers, as a word in a sentence.",
"placeholders": {
"effort": {
"type": "String"
}
}
},
"assistantEffortName": "{effort, select, low{Low} medium{Medium} high{High} xhigh{Extra high} max{Max} other{{effort}}}",
"@assistantEffortName": {
"description": "How much a model thinks before it answers, as a name standing alone, as in a menu.",
"placeholders": {
"effort": {
"type": "String"
}
}
},
"assistantEffortSetUp": "Effort {effort}, up to {max}",
"@assistantEffortSetUp": {
"description": "An assistant's effort unless asked otherwise, and the highest a question may ask.",
"placeholders": {
"effort": {
"type": "String"
},
"max": {
"type": "String"
}
}
},
"assistantEffortFixed": "Effort {effort}",
"@assistantEffortFixed": {
"description": "An assistant's effort, the only one it takes.",
"placeholders": {
"effort": {
"type": "String"
}
}
},
"assistantNameUnset": "{name} (no longer set up)",
"@assistantNameUnset": {
"description": "An assistant granted that is no longer set up.",
"placeholders": {
"name": {
"type": "String"
}
}
},
"assistantNotAgreedCount": "{count, plural, =1{1 not agreed to yet} other{{count} not agreed to yet}}",
"@assistantNotAgreedCount": {
"description": "How many of a person's assistants they haven't agreed to yet.",
"placeholders": {
"count": {
"type": "int"
}
}
},
"assistantGrant": "Grant assistants",
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
"@assistantGrant": {
"description": "Opens the sheet granting an assistant to an account."
},
"assistantGrantSave": "Grant",
"@assistantGrantSave": {
"description": "The grant sheet's button."
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"assistantGrantChange": "Change assistants",
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
"@assistantGrantChange": {
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"description": "The sheet changing a person's assistants and budget, and a person's menu item."
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
},
"assistantGrantLimitHelper": "Empty for none. Days and months are UTC's.",
"@assistantGrantLimitHelper": {
"description": "Under the limit fields: the instance counts by UTC days and months (D42)."
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"assistantGrantBudget": "Budget, for all of them together",
"@assistantGrantBudget": {
"description": "The sheet's section of a person's budget."
},
"assistantGrantMoneyDaily": "Money a day",
"@assistantGrantMoneyDaily": {
"description": "A budget field."
},
"assistantGrantMoneyMonthly": "Money a month",
"@assistantGrantMoneyMonthly": {
"description": "A budget field."
},
"assistantGrantTokensDaily": "Tokens a day",
"@assistantGrantTokensDaily": {
"description": "A budget field."
},
"assistantGrantTokensMonthly": "Tokens a month",
"@assistantGrantTokensMonthly": {
"description": "A budget field."
},
"assistantGrantMoneyNote": "Money counts the assistants with prices, tokens count every one. Whichever runs out first stops them.",
"@assistantGrantMoneyNote": {
"description": "Below the budget fields."
},
"assistantGrantReplaces": "{count, plural, =1{Granting replaces the budget this person has:} other{Granting replaces the budgets these people have:}}",
"@assistantGrantReplaces": {
"description": "Before the people whose budget a grant replaces, each with theirs.",
"placeholders": {
"count": {
"type": "int"
}
}
},
"assistantGrantUnsetDropped": "Saving takes back what is no longer set up: {names}.",
"@assistantGrantUnsetDropped": {
"description": "Changing a person's assistants drops those no longer set up.",
"placeholders": {
"names": {
"type": "String"
}
}
},
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
"assistantGrantLimitInvalid": "Not a number this takes",
"@assistantGrantLimitInvalid": {
"description": "A limit typed that is no amount or number of tokens."
},
"assistantGrantTokens": "tokens",
"@assistantGrantTokens": {
"description": "After a limit in tokens, for an assistant without prices."
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"assistantGrantNote": "They agree once to what goes where before they ask one. Assistants they have already stay. You see how much they use, never what they ask.",
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
"@assistantGrantNote": {
"description": "In the grant sheet: consent (D40) and what the operator sees (D3)."
},
"assistantPerDay": "{amount} a day",
"@assistantPerDay": {
"description": "A daily limit.",
"placeholders": {
"amount": {
"type": "String"
}
}
},
"assistantPerMonth": "{amount} a month",
"@assistantPerMonth": {
"description": "A monthly limit.",
"placeholders": {
"amount": {
"type": "String"
}
}
},
"assistantNoLimits": "No limits",
"@assistantNoLimits": {
"description": "A grant without limits."
},
"assistantRevoke": "Take back",
"@assistantRevoke": {
"description": "A grant's menu item."
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"assistantRevokeAllTitle": "Take back every assistant?",
"@assistantRevokeAllTitle": {
"description": "Asks before taking every assistant back from a person."
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"assistantRevokeMessage": "They can no longer ask any of them, and their budget goes. What they used stays counted.",
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
"@assistantRevokeMessage": {
"description": "Under the question taking a grant back."
},
"assistantUseByMonth": "Use by month",
"@assistantUseByMonth": {
"description": "Opens everyone's figures (D3)."
},
"assistantUseTitle": "Use of assistants",
"@assistantUseTitle": {
"description": "The title of the figures' screen and sheet."
},
"assistantUseNone": "Nothing used in this month.",
"@assistantUseNone": {
"description": "A month without use."
},
"assistantUseKept": "Counted per UTC day, kept for this month and the two before. Never what was asked.",
"@assistantUseKept": {
"description": "Under the figures: what is counted and for how long (D41, D42)."
},
"assistantRequests": "{count, plural, =1{1 request} other{{count} requests}}",
"@assistantRequests": {
"description": "How many requests were counted.",
"placeholders": {
"count": {
"type": "int"
}
}
},
"assistantTokens": "{amount} tokens",
"@assistantTokens": {
"description": "How many tokens were counted, the number formatted.",
"placeholders": {
"amount": {
"type": "String"
}
}
},
"assistantsTitle": "Assistants",
"@assistantsTitle": {
"description": "Settings' card of the assistants a person may ask (Halkyon Learn's ADR 0021, point 9)."
},
"assistantModelAt": "{model} at {host}",
"@assistantModelAt": {
"description": "Which model answers, and the host that receives what is asked.",
"placeholders": {
"model": {
"type": "String"
},
"host": {
"type": "String"
}
}
},
"assistantLeftToday": "{left} of {limit} left today",
"@assistantLeftToday": {
"description": "What is left of a day's limit.",
"placeholders": {
"left": {
"type": "String"
},
"limit": {
"type": "String"
}
}
},
"assistantLeftThisMonth": "{left} of {limit} left this month",
"@assistantLeftThisMonth": {
"description": "What is left of a month's limit.",
"placeholders": {
"left": {
"type": "String"
},
"limit": {
"type": "String"
}
}
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"assistantYourBudget": "Your budget, for all of them",
"@assistantYourBudget": {
"description": "The person's budget for all their assistants."
},
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
"assistantAgree": "Read and agree",
"@assistantAgree": {
"description": "Opens the consent sheet of an assistant not agreed to yet."
},
"assistantYourUse": "Your use",
"@assistantYourUse": {
"description": "Opens the person's own figures."
},
"assistantPrivacyPage": "What goes where on {instance}",
"@assistantPrivacyPage": {
"description": "Opens the instance's privacy page (D39).",
"placeholders": {
"instance": {
"type": "String"
}
}
},
"assistantOwnTitle": "With your own key",
"@assistantOwnTitle": {
"description": "Above a person's own assistants (point 10)."
},
"assistantOwnAdd": "Add an assistant with your own key",
"@assistantOwnAdd": {
"description": "Opens the sheet adding an assistant with a personal key."
},
"assistantOwnNote": "The app asks your provider directly: this instance sees neither your key nor your questions, and counts nothing. The key is kept in your settings, encrypted on your devices.",
"@assistantOwnNote": {
"description": "What a personal key means (D5, D6)."
},
"assistantOwnEdit": "Your own key",
"@assistantOwnEdit": {
"description": "The personal key sheet's title."
},
"assistantOwnName": "Name",
"@assistantOwnName": {
"description": "The name the person gives their assistant."
},
"assistantOwnProtocol": "API",
"@assistantOwnProtocol": {
"description": "Above the choice of protocol."
},
"assistantOwnBaseUrl": "Address",
"@assistantOwnBaseUrl": {
"description": "The provider's address field."
},
"assistantOwnBaseUrlHelper": "Empty for the provider's own. Mistral, OpenRouter or a model of your own: its address with /v1.",
"@assistantOwnBaseUrlHelper": {
"description": "Under the address field: which services take which address."
},
"assistantOwnBaseUrlInvalid": "Only https, or http on your own network",
"@assistantOwnBaseUrlInvalid": {
"description": "An address the app refuses: keys and questions would travel in the clear."
},
"assistantOwnModel": "Model",
"@assistantOwnModel": {
"description": "The model's name at the provider."
},
"assistantOwnKey": "Key",
"@assistantOwnKey": {
"description": "The API key field."
},
"assistantOwnKeyHelper": "Empty for a model of your own that takes none.",
"@assistantOwnKeyHelper": {
"description": "Under the key field."
},
"assistantOwnImages": "Reads images",
"@assistantOwnImages": {
"description": "Whether the model reads images (D12)."
},
"assistantOwnSave": "Save",
"@assistantOwnSave": {
"description": "The personal key sheet's button."
},
"assistantOwnRemove": "Remove",
"@assistantOwnRemove": {
"description": "Removes an assistant with a personal key."
},
"assistantOwnRemoveTitle": "Remove {name}?",
"@assistantOwnRemoveTitle": {
"description": "Asks before removing an assistant with a personal key.",
"placeholders": {
"name": {
"type": "String"
}
}
},
"assistantOwnRemoveMessage": "Its key goes from your settings on every device.",
"@assistantOwnRemoveMessage": {
"description": "Under the question removing a personal key."
},
"assistantOwnRefused": "The key wasn't kept: this device can't encrypt your settings yet. Open your account's keys here first.",
"@assistantOwnRefused": {
"description": "A personal key refused, as the settings wouldn't be sealed."
},
"assistantOwnNotAllowed": "Whoever runs {instance} doesn't allow own keys here, so the app doesn't ask these.",
"@assistantOwnNotAllowed": {
"description": "Below the person's own assistants, where the operator doesn't allow them (ADR 0021, D6).",
"placeholders": {
"instance": {
"type": "String",
"example": "learn.halkyon.eu"
}
}
},
"assistantConsentTitle": "Before you ask {name}",
"@assistantConsentTitle": {
"description": "The consent sheet's title (D40).",
"placeholders": {
"name": {
"type": "String"
}
}
},
"assistantConsentModel": "It answers with the model {model} at {host}.",
"@assistantConsentModel": {
"description": "Who answers (D27).",
"placeholders": {
"model": {
"type": "String"
},
"host": {
"type": "String"
}
}
},
feat(learn): Explore by what a licence allows, a skeleton while the library loads, and an assistant's agreement per provider (ADR 0005, rule 12; ADR 0021, D66)
- Explore filters by what a studyset's licence lets a learner do: adapt
it (no NoDerivatives) and use it commercially (no NonCommercial),
which the instance reads from the release's licence
(ExploreQuery.adaptable, commercial) and the app the same way
(LicenseUse). An instance from before ignores both.
- While the studysets are read, the Library shows shimmering skeletons
of their cards rather than a spinner: halkyon_ui's new Shimmer and
SkeletonBox, still where the system asks for no animations.
- The scope's "Include private studysets" is offered only while the
person has a private studyset.
- An agreement to what goes where is per provider (protocol and host):
agreeing to one assistant agrees to every assistant of that provider,
those granted later too, and the agreement sheet says so. No
migration: it is written on every grant of the provider, so taking
one back keeps it for the others, and read per provider from any of
them.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 01:09:04 +01:00
"assistantConsentProvider": "It holds for every assistant at {host}.",
"@assistantConsentProvider": {
"description": "An agreement is to what goes where, so it covers every assistant of the provider (Halkyon Learn's ADR 0021, D66).",
"placeholders": {
"host": {
"type": "String"
}
}
},
feat(learn): a private studyset goes to an assistant only once agreed
ADR 0021, D9 and D17. Before a question sends a private studyset's
content to a remote model, through the parts chosen or a wider reach
with private studysets switched on, a warning names the studysets, the
provider's host and its terms (where checked: Anthropic, OpenAI,
Google, OpenRouter), and is asked once per studyset and assistant: the
agreement is kept in the person's sealed settings (schema 13,
privateAgreements, folded only where both rows hold it), shown in the
studyset's privacy dialog with Take back, and marked on the folded
button while it holds. Follow-ups and Try again ask again once taken
back, as the scope keeps where the parts came from (partsFrom). No
warning for an own assistant on this machine (loopback or localhost,
halkyon_llm's LlmEndpoint.onThisMachine); the local network may be
shared.
Texts that step 3 made untrue: the consent sheet now says what an
assistant looks up goes too and that conversations are kept encrypted
for the person's devices; the privacy dialogs no longer mention
showing a studyset to Claude; the privacy page says what is looked up
and when a private studyset goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 21:00:39 +01:00
"assistantConsentWhatGoes": "Only what you send goes there: your question, what you pick of what it is about, and what it looks up where you let it. The provider's own terms apply to it.",
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
"@assistantConsentWhatGoes": {
"description": "What goes to the provider (D27)."
},
"assistantConsentImages": "It reads the images you add.",
"@assistantConsentImages": {
"description": "For an assistant that reads images (D12)."
},
"assistantConsentInstance": "It passes through {instance} on its way, which keeps none of it.",
"@assistantConsentInstance": {
"description": "The instance sees it in passing and keeps nothing (D27).",
"placeholders": {
"instance": {
"type": "String"
}
}
},
"assistantConsentOperator": "Whoever runs {instance} sees how much you use it (requests, tokens, cost), never what you ask.",
"@assistantConsentOperator": {
"description": "The operator's figures (D3).",
"placeholders": {
"instance": {
"type": "String"
}
}
},
feat(learn): a private studyset goes to an assistant only once agreed
ADR 0021, D9 and D17. Before a question sends a private studyset's
content to a remote model, through the parts chosen or a wider reach
with private studysets switched on, a warning names the studysets, the
provider's host and its terms (where checked: Anthropic, OpenAI,
Google, OpenRouter), and is asked once per studyset and assistant: the
agreement is kept in the person's sealed settings (schema 13,
privateAgreements, folded only where both rows hold it), shown in the
studyset's privacy dialog with Take back, and marked on the folded
button while it holds. Follow-ups and Try again ask again once taken
back, as the scope keeps where the parts came from (partsFrom). No
warning for an own assistant on this machine (loopback or localhost,
halkyon_llm's LlmEndpoint.onThisMachine); the local network may be
shared.
Texts that step 3 made untrue: the consent sheet now says what an
assistant looks up goes too and that conversations are kept encrypted
for the person's devices; the privacy dialogs no longer mention
showing a studyset to Claude; the privacy page says what is looked up
and when a private studyset goes.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 21:00:39 +01:00
"assistantConsentConversations": "Your conversations are kept encrypted: only your devices can read them.",
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
"@assistantConsentConversations": {
"description": "Conversations are sealed on the devices (D13)."
},
"assistantConsentAgree": "Agree",
"@assistantConsentAgree": {
"description": "The consent sheet's button."
},
"assistantConsentAgreedOn": "You agreed on {date}.",
"@assistantConsentAgreedOn": {
"description": "An assistant agreed to, and when.",
"placeholders": {
"date": {
"type": "String"
}
}
},
"assistantProblemNotGranted": "This assistant isn't granted to you any more.",
"@assistantProblemNotGranted": {
"description": "notGranted."
},
"assistantProblemNotAgreed": "Agree to what goes where first.",
"@assistantProblemNotAgreed": {
"description": "notAgreed."
},
feat(app): the operator's overview of screens, grants to several people at once with one budget each, assistants by provider
Halkyon Learn's ADR 0021, D57 to D63, on halkyon_base's budgets and
effort:
- The operator's screen is an overview (Waiting, Operators, Accounts,
Assistants, each with its count) whose rows open screens of their own,
read 50 at a time as they scroll (OperatorListCubit, OperatorPage), so
hundreds of accounts push nothing else down (D62).
- A handle is picked among the accounts there are (AccountSuggestionField):
appointing an operator and granting take no other (D61).
- AssistantGrantsScreen replaces AssistantGrantsCard: the assistants set
up by provider, with their efforts and prices; the people granted, found
by their handle's start; one sheet granting several assistants, a
provider's at once, to several people, each with one budget in money and
tokens a day and a month, or changing one person's (D58, D59).
- Assistants are grouped by provider wherever they are listed, each under
its name with a dot in its colour (ProviderLabel, D60).
- AssistantsCard shows what is left of the person's budget for all their
assistants together; the months of the figures run oldest first (D57).
- AssistantChoice says the efforts a question may ask at and the ceiling
on output each asks for (D63); a person's own key gets none and keeps
4,096 tokens.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-26 16:05:49 +01:00
"assistantProblemInvalidEffort": "It doesn't think at this effort.",
"@assistantProblemInvalidEffort": {
"description": "A question asked an effort its assistant does not take."
},
feat(app,learn,todo): the assistants in Settings and on the operator's screen
ADR 0021, step 2's app half. halkyon_app asks the module's assistant
endpoints (ServerpodAssistantGateway, ServerpodAssistantOperatorGateway)
and a person's own provider directly (halkyon_llm), behind
AssistantRepository: an AssistantChoice is a GrantedAssistant, asked
through the instance, which counts it, or a PersonalAssistant with the
person's own key, which the instance never sees. assistant_messages.dart
turns a conversation into the endpoint's models and back.
AssistantsCard, for an app's settings: what is granted and left of its
limits, agreed to once on a sheet saying what goes where, the person's
use by UTC month, the instance's privacy page, and their own assistants
where the operator allows own keys (kept ones stay listed, unasked,
where it doesn't). AssistantGrantsCard on InstanceOperatorScreen: grants
by handle with their limits in the pricing's unit, changed and taken
back after asking, everyone's use by month; a found account's menu
grants too. Nothing shows where nothing is offered or can be read.
package:halkyon_app/assistant_models.dart holds the models in pure Dart.
Learn keeps own assistants in the person's settings (ownAssistants,
schema 12, SettingsOwnAssistants); SettingsRepository.update refuses
(OwnAssistantRefused) to write a key where the settings would not be
sealed (SettingsGateway.seals). Learn depends on halkyon_llm directly.
Todo wires the gateways; a member offers no assistant. No feature asks
an assistant yet (step 3).
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-25 18:59:41 +01:00
"assistantProblemLimitReached": "Its limit is reached until {time}.",
"@assistantProblemLimitReached": {
"description": "limitReached, with when it resets.",
"placeholders": {
"time": {
"type": "String"
}
}
},
"assistantProblemLimitReachedNow": "Its limit is reached for now.",
"@assistantProblemLimitReachedNow": {
"description": "Refused: a limit is reached, the instance not saying until when."
},
"assistantProblemImagesNotRead": "It reads no images.",
"@assistantProblemImagesNotRead": {
"description": "imagesNotRead."
},
"assistantProblemMalformed": "This question can't be sent.",
"@assistantProblemMalformed": {
"description": "malformed."
},
"assistantProblemTooLarge": "This question is too long, or holds too many or too large images.",
"@assistantProblemTooLarge": {
"description": "tooLarge."
},
"assistantProblemTooManyAtOnce": "Wait for the answers on their way first.",
"@assistantProblemTooManyAtOnce": {
"description": "tooManyAtOnce."
},
"assistantProblemProviderKey": "The provider refused the key.",
"@assistantProblemProviderKey": {
"description": "providerKey."
},
"assistantProblemProviderBusy": "The provider is busy. Try again in a moment.",
"@assistantProblemProviderBusy": {
"description": "providerBusy."
},
"assistantProblemProviderRefused": "The provider refused this question.",
"@assistantProblemProviderRefused": {
"description": "providerRefused."
},
"assistantProblemProviderFailed": "The provider failed. Try again.",
"@assistantProblemProviderFailed": {
"description": "providerFailed."
},
"assistantProblemNotAllowed": "Only people it is granted to can do this.",
"@assistantProblemNotAllowed": {
"description": "notAllowed."
},
"assistantProblemFailed": "That didn't work. Try again.",
"@assistantProblemFailed": {
"description": "failed."
feat(app): what's new after an update, drafted by the release tool (ADR 0030, D61)
halkyon_app's WhatsNew reads what each release changed from the app's
assets/whats_new/ (<version>.<language>.txt, English and German, a change
a line): after an update a sheet lists every release since the one the
device showed last, newest first, once; Settings shows the running
version's again, above About. Nothing on a fresh install.
tool/release.dart drafts the missing texts from the commits that count
after "Go on?" (German marked TRANSLATE:), opens them in the editor until
they fit (no mark left, F-Droid's 500 characters) and commits them with
the version. The English is the Forgejo notes' What's new, each language
F-Droid's changelog of the build, written at publishing and copied in by
tool/fdroid_repo.sh. Learn and Train bundle their texts, Learn's 0.22.0
and Train's 0.1.0 written to start.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-10-31 19:36:20 +01:00
},
"whatsNewTitle": "What's new",
"@whatsNewTitle": {
"description": "The sheet listing what changed in the app after an update, and its entry in Settings."
},
"whatsNewVersion": "Version {version}",
"@whatsNewVersion": {
"description": "Above a release's changes in What's new.",
feat(app): one Profile for every app, the person's own tab first, then each app's (ADR 0014)
The public profile was already one per person for every service of an
instance (the register, ADR 0016); its app side was Learn's alone. It
moves into halkyon_app: the model, the repository on the module's
endpoints, the form, the avatar drawn on the device and the sheet with
someone's profile. They take the Public prefix (PublicProfileRepository,
BasePublicProfileGateway), as Train's own ProfileRepository holds the
body's figures. AppDependencies gains profiles and files, the picker the
avatar is chosen with, which moves out of Learn too.
HalkyonProfile is every app's Profile screen: the person's own tab first,
the same in every app (the account and the ways they sign in, the app's
own cards, then the public profile), then the app's own tabs, folding into
the title. Learn adds its inbox and Statistics. Train adds Progress, Days
and, where nothing records, Plans, and so opens on the person's tab, no
longer on Progress. Todo, which has no Profile tab, opens it from a row at
the top of Browse, where Todoist keeps the account.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-02 09:44:38 +01:00
"placeholders": {
"version": {
"type": "String"
}
}
},
"profileIdentified": "Your progress is kept with your account.",
"profileProblemAvatarInvalid": "This picture can't be used: choose a PNG, JPEG, WebP or GIF image.",
"profileProblemBioTooLong": "At most 500 characters.",
"profileProblemDisplayNameTooLong": "At most 30 characters.",
"profileProblemHandleInvalid": "A handle is 1 to 30 letters (a to z), digits or underscores.",
"profileProblemHandleReserved": "This handle is kept for the instance.",
"profileProblemHandleTaken": "Someone else has this handle.",
"profileProblemLinkInvalid": "Links start with https:// or http://.",
"profileProblemPublished": "You have published: your handle can't change, nor your profile go.",
"profileProblemTooManyHandleChecks": "Too many handles were tried. Try again in an hour.",
"profileProblemTooManyLinkChecks": "The links were checked too often. Try again in an hour.",
"profileProblemTooManyLinks": "At most 4 links.",
"profileSheetNotFound": "Nobody on this instance has this handle.",
"profileSheetTitle": "Profile",
"profileSheetUnreachable": "The profile couldn’ t be loaded. Check your connection.",
"profileSignInWays": "Ways to sign in",
"profileTabProfile": "Profile",
"profileTitle": "Profile",
"profileWayEmail": "Email address",
"profileWayLinked": "Signs you in",
"publicProfileAddLink": "Add link",
"publicProfileAddress": "Others find you as @{address}",
"@publicProfileAddress": {
"placeholders": {
"address": {
"type": "String"
}
}
},
"publicProfileAddressPrivate": "Private until you fill in your profile or publish something; then others find you as @{address}.",
"@publicProfileAddressPrivate": {
"placeholders": {
"address": {
"type": "String"
}
}
},
"publicProfileBio": "Bio",
"publicProfileCheckLinks": "Check links",
"publicProfileChoosePicture": "Choose picture",
"publicProfileDisplayName": "Display name",
"publicProfileDisplayNameHint": "The name shown beside your handle",
"publicProfileFailed": "Couldn't reach the instance. Try again.",
"publicProfileHandle": "Handle",
"publicProfileHandleChangeTitle": "Change your handle",
"publicProfileHandleChecking": "Checking…",
"publicProfileHandleChoose": "Choose a handle",
"publicProfileHandleChooseTitle": "Choose your handle",
"publicProfileHandleConfirmChange": "Change",
"publicProfileHandleConfirmChoose": "Choose",
"publicProfileHandleDialogText": "Others find you by your handle. You can change it until you first publish a studyset.",
"publicProfileHandleFirst": "Choose your handle first: your profile is made with it, and saves as you write from then on.",
"publicProfileHandleFixed": "Fixed since you published: others refer to it.",
"publicProfileHandleFree": "@{handle} is free",
"@publicProfileHandleFree": {
"placeholders": {
"handle": {
"type": "String"
}
}
},
"publicProfileHandleHint": "Letters, digits and underscores, up to 30",
"publicProfileHandleYours": "This is your handle",
"publicProfileIntro": "How others see you once you publish: the handle they find you by, your name, a few words and links. It saves as you write, once you have chosen your handle.",
"publicProfileLinkHint": "https://…",
"publicProfileLinkVerified": "Links back to your profile",
"publicProfileLinks": "Links",
"publicProfileLinksChecked": "{count, plural, =1{Checked: {verified} of 1 link links back to your profile.} other{Checked: {verified} of {count} links link back to your profile.}}",
"@publicProfileLinksChecked": {
"placeholders": {
"count": {
"type": "int"
},
"verified": {
"type": "int"
}
}
},
"publicProfileLinksHint": "Your site or other accounts, up to 4.",
"publicProfilePage": "Your profile page: {url}",
"@publicProfilePage": {
"placeholders": {
"url": {
"type": "String"
}
}
},
"publicProfilePageCopied": "Address copied.",
"publicProfilePageCopy": "Copy the address",
"publicProfilePicture": "Picture",
"publicProfilePictureLater": "Choose your handle to add a picture.",
"publicProfilePictureType": "Images",
"publicProfileRemove": "Remove profile",
"publicProfileRemoveLink": "Remove link",
"publicProfileRemoveMessage": "Your display name, bio, links and picture are removed. Your handle stays yours and private until you fill in your profile again or publish something.",
"publicProfileRemovePicture": "Remove picture",
"publicProfileRemoveTitle": "Remove your public profile?",
"publicProfileRemoved": "Profile removed.",
"publicProfileRetry": "Retry",
"publicProfileSavedNow": "Saved",
"publicProfileSaving": "Saving…",
"publicProfileTitle": "Public profile",
"publicProfileUnreachable": "Your public profile can't be reached right now.",
"publicProfileVerifyHint": "A link gets a checkmark once its page links back to your profile page with rel=\"me\", as Mastodon checks links.",
"profileAccount": "Account",
fix(app,learn,meditate): a device without speech recognition says so, and how to get one
On GrapheneOS, which installs no recogniser, the microphone did nothing
and stayed listening: speech_to_text's initialize throws a
PlatformException (recognizerNotAvailable) there rather than answering
false, and DeviceSpeechInput let it through. A listen now answers how it
went (SpeechStart: listening, noRecognizer, unavailable) and never
throws; Learn's field and Meditate's dream say that no speech
recognition is installed and name two to install (FUTO Voice Input,
Google's speech services), in halkyon_app's texts. It also passes
androidNoBluetooth, as the apps declare no Bluetooth permission the
plugin would otherwise ask for.
Whats-New: Where the device has no speech recognition, the microphone says so and how to get one.
Whats-New-De: Wo das Gerät keine Spracherkennung hat, sagt das Mikrofon das und wie du eine bekommst.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Signed-off-by: Tizian 「ティツィアーン」 <raisondetredev@pm.me>
2026-11-04 20:45:55 +01:00
"profileSheetLinkVerified": "Links back to this profile",
"speechNoRecognizer": "This device has no speech recognition installed. Install one, such as FUTO Voice Input or Google's speech services, then try again.",
"@speechNoRecognizer": {"description": "Shown when the microphone is tapped on a device without any speech recognition app (as GrapheneOS without Google's services), with a hint how to get one."}
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
}