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

View file

@ -164,6 +164,24 @@
}
}
},
"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"
}
}
},
"syncTitle": "Sync",
"syncNow": "Sync now",
"syncStateSyncing": "Syncing…",
@ -216,6 +234,88 @@
"syncMismatchMore": "More",
"startSignIn": "Sign in",
"startCreateAccount": "Create an account",
"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."
},
"passwordAddress": "Email address",
"passwordPassword": "Password",
"passwordNewPassword": "New password",
@ -437,6 +537,14 @@
"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.",
"signUpsClosed": "This instance takes no new accounts.",
"@signUpsClosed": {
"description": "A sign-up refused: the operator takes no new accounts (D19)."
},
"signUpsTooManyWaiting": "As many new accounts wait for approval as may. Try again later.",
"@signUpsTooManyWaiting": {
"description": "A sign-up refused: ten new accounts wait for the operator's approval already (D19)."
},
"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.",
"passwordFailed": "Couldn't reach the instance. Check the connection and try again.",
"passwordMinLength": "At least {count} characters",