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>
This commit is contained in:
Tizian 「ティツィアーン」 2026-10-25 15:55:28 +01:00
commit 31b9c17850
Signed by: raisondetredev
GPG key ID: 07F6FCF405488B64

View file

@ -216,7 +216,7 @@
"passwordInstanceUnproven": "Die Instanz konnte nicht beweisen, dass sie dein Passwort kennt, daher vertraut dieses Gerät ihr nicht. Versuch es noch einmal; passiert es wieder, ist die Instanz nicht die, die sie zu sein behauptet.",
"passwordKeysMissing": "Die Schlüssel deines Kontos sind nicht auf diesem Gerät. Melde dich hier ab und mit deinem Passwort wieder an.",
"signUpsClosed": "Diese Instanz nimmt keine neuen Konten an.",
"signUpsTooManyWaiting": "Es warten schon so viele neue Konten auf ihre Freigabe, wie dürfen. Versuch es später noch einmal.",
"signUpsTooManyWaiting": "Es warten schon zu viele neue Konten auf ihre Freigabe. Versuch es später noch einmal.",
"passwordUnsafeCosts": "Die Instanz wollte dein Passwort auf eine Weise prüfen lassen, die kein Konto nutzt, daher wurde nichts gesendet. Passiert es wieder, ist die Instanz nicht die, die sie zu sein behauptet.",
"passwordFailed": "Die Instanz war nicht erreichbar. Prüf die Verbindung und versuch es noch einmal.",
"passwordMinLength": "Mindestens {count} Zeichen",
@ -309,5 +309,41 @@
"simpleLoginSectionTitle": "SimpleLogin",
"simpleLoginSignsYouIn": "Meldet dich an",
"simpleLoginAddHint": "Melde dich mit SimpleLogin an, oder mit deinem Proton-Konto.",
"simpleLoginAdded": "SimpleLogin hinzugefügt."
"simpleLoginAdded": "SimpleLogin hinzugefügt.",
"operatorTitle": "Diese Instanz",
"operatorAccounts": "Konten und Admins",
"operatorWaitingCount": "{count, plural, =0{Niemand wartet auf Freigabe} =1{1 Konto wartet auf Freigabe} other{{count} Konten warten auf Freigabe}}",
"operatorWaitingTitle": "Wartend",
"operatorNobodyWaits": "Niemand wartet.",
"operatorNoHandle": "Noch kein Handle",
"operatorNewSince": "Neu, wartet seit {date}",
"operatorSuspendedSince": "Gesperrt seit {date}",
"operatorApproveNote": "Lies diese vier Wörter vor, und gib das Konto erst frei, wenn dir bestätigt wird, dass genau diese auf dem Bildschirm stehen.",
"operatorApprove": "Freigeben",
"operatorRefuse": "Ablehnen",
"operatorRefuseTitle": "Dieses Konto ablehnen?",
"operatorRefuseMessage": "Es wird mit allem gelöscht, was darin ist. Eine neue Anmeldung bleibt möglich.",
"operatorOperatorsTitle": "Admins",
"operatorTakeBack": "Entziehen",
"operatorTakeBackTitle": "Die Rolle entziehen?",
"operatorTakeBackMessage": "Das Konto bleibt, kann hier aber keine Konten mehr freigeben, ablehnen oder sperren.",
"operatorAppoint": "Admin ernennen",
"operatorAppointMessage": "Admins geben hier Konten frei, lehnen sie ab, sperren sie und ernennen Admins, so wie du.",
"operatorAppointConfirm": "Ernennen",
"operatorHandle": "Handle",
"operatorFindTitle": "Ein Konto finden",
"operatorFoundNone": "Kein Handle beginnt so.",
"operatorMadeOn": "Seit {date}",
"operatorIsOperator": "Admin",
"operatorIsWaiting": "Wartend",
"operatorIsSuspended": "Gesperrt",
"operatorSuspend": "Sperren",
"operatorSuspendTitle": "Dieses Konto sperren?",
"operatorSuspendMessage": "Es wird überall abgemeldet und kann sich erst wieder anmelden, wenn du es freigibst. Nichts darin wird gelöscht.",
"operatorMakeOperator": "Zum Admin ernennen",
"operatorNotFound": "Dieses Konto gibt es nicht mehr, oder es wartet nicht.",
"operatorIsOperatorProblem": "Admins können nicht gesperrt werden. Entzieh zuerst die Rolle.",
"operatorLastOperator": "Die Instanz braucht mindestens einen Admin.",
"operatorNotAllowed": "Das können nur die Admins der Instanz.",
"operatorFailed": "Das hat nicht geklappt. Versuch es noch einmal."
}