Public Access
Merge main into the teams branch
Two conflicts, and both were two people counting the same things differently rather than disagreeing about what the code should do. PhoneShell's header comment. The branch made "the five hub screens" numberless, because TEAMS made it six and a number in that sentence had already gone stale once. Main corrected "three destinations" to "two" in the same sentence, because giving a shell the whole phone took the terminal out of the set the header is drawn on. Both are right and neither noticed the other: the header now stays on the hub's screens and on the two top-level destinations, which is Hosts and Keychain. The manual checks. Both sides appended a Phase 10 — main added the software keyboard and the phone's terminal surface as 10 and 11, the branch added Teams. Nothing about them overlaps, so the resolution is to keep all three in the order they were written and renumber Teams to Phase 12, its subsections and the one cross-reference inside 12.1 with it. Main's two phases keep the numbers they already carry in its history, since renumbering those would move headings somebody may already have linked to. Everything else merged without a conflict, and the two places worth checking afterwards both held: IsMoreSurface and the first case of PhoneShell.OnBackRequested each kept ShellScreen.Team alongside main's edits. Those two are one fact in two places, so a merge that dropped Team from either would have trapped the user on the teams screen with the MORE tab dark. Verified after resolving: solution builds with no errors and no new warnings, the Android head builds, and every suite passes — App 214, Layout 73, Api 162, Infrastructure 34, Contracts 25, Session 54. App gained the three shell-flow tests main brought with it. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
+131
-11
@@ -835,7 +835,127 @@ On the phone's host editor with several tags: the chips are at least 36 tall, sp
|
||||
lands between them rather than on the wrong tag, and the new-tag box and ADD sit on one row without either
|
||||
being squeezed to nothing.
|
||||
|
||||
## Phase 10 — Teams: the operations that span two accounts
|
||||
---
|
||||
|
||||
## Phase 10 — The software keyboard and the boxes that take secrets
|
||||
|
||||
Every check here needs a real Android device or emulator, and there is no headless equivalent of any of
|
||||
them: the software keyboard is an inset the platform reports, and a headless top level reports none.
|
||||
Worth running on two devices if you have them — one on Android 14 or earlier and one on Android 15 or
|
||||
later — because the interface is kept clear of the keyboard by a different mechanism on each. Before 15 the
|
||||
activity's `AdjustResize` has the platform shorten the window; from 15 the window is not resized at all and
|
||||
`PhoneShell` applies the reported inset itself. A build that only ever ran on one of the two will look
|
||||
correct and be half broken.
|
||||
|
||||
### 10.1 The vault passphrase box is treated as a password by the keyboard
|
||||
|
||||
Launch to the lock screen, tap the passphrase box, type a few characters.
|
||||
|
||||
**Pass:** dots on screen, and **no suggestion strip above the keyboard** — no completions, no previously
|
||||
typed words, no autocorrect. Then open any ordinary box on the phone (the host search, a snippet's name)
|
||||
and confirm the suggestions come back there.
|
||||
|
||||
**Failure means:** `TextInputOptions.ContentType` is missing — most likely a box was given `PasswordChar`
|
||||
directly instead of `Classes="... secret"`. `PasswordChar` is what the screen draws; the content type is
|
||||
what the keyboard is told, and only the second one keeps a passphrase out of the IME's learning
|
||||
dictionary. A box showing dots with a suggestion strip over it is the worst case, not a cosmetic one.
|
||||
|
||||
### 10.2 The keyboard does not cover the box being typed into
|
||||
|
||||
The same box: with the keyboard up, the passphrase box and the UNLOCK button under it are both visible.
|
||||
Repeat on each of the five boxes that take a secret — lock screen, both enrollment boxes, the connect
|
||||
password on HOSTS, and the connect password on FILES.
|
||||
|
||||
**Pass:** the box stays on screen when the keyboard opens, and the interface is shortened rather than slid
|
||||
— the header stays where it is rather than scrolling off the top.
|
||||
|
||||
**Failure means:** on Android 15 or later, the inset is no longer reaching `PhoneShell`. On 14 or earlier,
|
||||
`WindowSoftInputMode` has been dropped from the activity and the platform is panning the window instead of
|
||||
resizing it — which, for a window whose whole content is one native view, pans by nothing useful.
|
||||
|
||||
### 10.3 Nothing is stranded when the keyboard closes
|
||||
|
||||
Dismiss the keyboard with back or the down-chevron from each of those screens.
|
||||
|
||||
**Pass:** the interface fills the screen again immediately, with no band of empty canvas left along the
|
||||
bottom and no scroll position left part way down.
|
||||
|
||||
**Failure means:** the inset is being applied but not cleared — the closed state is not being read from the
|
||||
event, or the margin is only ever added to.
|
||||
|
||||
### 10.4 Rotating with the keyboard up
|
||||
|
||||
Focus a passphrase box, then turn the phone sideways.
|
||||
|
||||
**Pass:** the box is still visible and still focused, and the shell is intact — the activity handles the
|
||||
rotation rather than being recreated, and live shells survive it.
|
||||
|
||||
---
|
||||
|
||||
## Phase 11 — The phone's terminal surface
|
||||
|
||||
Every check here needs a real device for the reason Phase 10's do, plus one of its own: the interesting
|
||||
question on this screen is whether a native `WebView` composites above what Avalonia draws over it, and no
|
||||
headless surface has a native view to answer with. `docs/android-port.md` still records that as unverified;
|
||||
11.2 is the check that settles it.
|
||||
|
||||
### 11.1 A shell gets the screen · **the important one for chrome**
|
||||
|
||||
Open a shell from HOSTS.
|
||||
|
||||
**Pass:** the vault header, the session strip and the four-entry bottom bar are all gone. What is left is
|
||||
one bar — a back arrow, the session pills, a `+` — and then the terminal down to the accessory keys. Press
|
||||
back: all three come back, the tab is still in the strip and its dot is still green.
|
||||
|
||||
**Failure means:** one of the three rows is not bound on `IsShowingPages`, or the terminal is being reached
|
||||
by a route that leaves `Surface` on `Page`.
|
||||
|
||||
### 11.2 The connect menu is not drawn over the renderer · **the important one**
|
||||
|
||||
With a shell showing output, press `+`.
|
||||
|
||||
**Pass:** the terminal's rectangle goes to the canvas colour and the sheet sits over it whole — scrim, three
|
||||
rows and CANCEL, every one of them tappable, none of them sliced down the left edge. Tap the scrim: the
|
||||
terminal comes back with its scrollback intact and the shell still running.
|
||||
|
||||
**Failure means:** `IsTerminalShowing` is not being cleared by `IsConnectSheetOpen` — or, if the sheet is
|
||||
sliced *despite* the rectangle going blank, something else in that Panel is still showing. A sheet that
|
||||
draws over live terminal output is the Android answer to the compositing question, and it means every
|
||||
future sheet on this surface has to collapse the renderer too.
|
||||
|
||||
### 11.3 Back lowers the menu before it leaves the terminal
|
||||
|
||||
With the connect menu open, use the system back gesture.
|
||||
|
||||
**Pass:** the menu closes and the terminal is still showing. A second back leaves the terminal for the
|
||||
screen it was opened over.
|
||||
|
||||
**Failure means:** the guard in `PhoneShell.axaml.cs` is below the surface check rather than above it, and
|
||||
one gesture is spending two levels.
|
||||
|
||||
### 11.4 The two end buttons cannot be pushed off the bar
|
||||
|
||||
Open six or more shells.
|
||||
|
||||
**Pass:** the pills scroll under a fixed back arrow and a fixed `+`; neither ever leaves the screen, and
|
||||
scrolling the pills to either end does not move them.
|
||||
|
||||
**Failure means:** a control was put inside the `ScrollViewer` rather than beside it.
|
||||
|
||||
### 11.5 The text-size buttons are always reachable
|
||||
|
||||
With a shell open, scroll the accessory key row to the far left and the far right.
|
||||
|
||||
**Pass:** `A−` and `A+` stay pinned at the right-hand end throughout, separated from the keys by the
|
||||
hairline, and both are at least 38 tall. At the smallest and largest sizes the one that can do nothing is
|
||||
visibly disabled rather than silently inert.
|
||||
|
||||
**Failure means:** they have been folded into the scrolling row — which is the arrangement the connection
|
||||
line existed to avoid, and the reason it could be removed at all.
|
||||
|
||||
---
|
||||
|
||||
## Phase 12 — Teams: the operations that span two accounts
|
||||
|
||||
The server's own rules are covered by the endpoint suite: teams are renamed, an archive is refused while a
|
||||
vault is in the way, ownership changes hands, and every branch of the invitation claim is driven with
|
||||
@@ -850,7 +970,7 @@ second DodoSSH profile means a second machine, a second OS user, or the same mac
|
||||
Whichever account plays the invitee **must not have signed in to this deployment before** — most of what
|
||||
follows is about what happens the first time it does.
|
||||
|
||||
### 10.1 An invitation becomes a membership at the invitee's first sign-in · **the one worth the most care**
|
||||
### 12.1 An invitation becomes a membership at the invitee's first sign-in · **the one worth the most care**
|
||||
|
||||
1. Sign in as `alice`, make a team, and open its invitations.
|
||||
2. Invite `bob@example.com` as a Member. **Nothing is sent, and nothing should look as though it was** —
|
||||
@@ -866,12 +986,12 @@ follows is about what happens the first time it does.
|
||||
opens.
|
||||
|
||||
**Failure means:** step 5 failing with everything else passing is almost always the `email_verified` claim
|
||||
— go to 10.2 rather than reading the invitation code, because the server is doing exactly what it should.
|
||||
— go to 12.2 rather than reading the invitation code, because the server is doing exactly what it should.
|
||||
Step 6 opening the vault *without* Alice sharing a key would be the far more serious failure: nothing on
|
||||
the server can wrap a vault key, so an item that decrypts after a membership change alone means a key
|
||||
reached that machine by a route this architecture says does not exist.
|
||||
|
||||
### 10.2 An unverified address claims nothing, and the log is the only place that says so
|
||||
### 12.2 An unverified address claims nothing, and the log is the only place that says so
|
||||
|
||||
In Keycloak's admin console, clear **Email verified** on the invitee *before* their first DodoSSH sign-in.
|
||||
Invite that address, then sign in as them.
|
||||
@@ -888,7 +1008,7 @@ can walk into their team. Stop there. If it stays pending after verifying, the c
|
||||
**access** token — check the provider's mappers, and set `Oidc:EmailVerifiedClaim` if it sends the claim
|
||||
under some other name.
|
||||
|
||||
### 10.3 An invitation can be withdrawn until it is taken up
|
||||
### 12.3 An invitation can be withdrawn until it is taken up
|
||||
|
||||
Invite an address, then revoke it before anybody has signed in with it. Then sign in with that address.
|
||||
|
||||
@@ -901,7 +1021,7 @@ accepted one that could be unpicked here would be worse: it is a membership now,
|
||||
revokes their vault key grants and flags every team vault for rekey, which is not what "revoke invitation"
|
||||
should quietly do.
|
||||
|
||||
### 10.4 An address already in the team is refused; an address that merely has an account is not
|
||||
### 12.4 An address already in the team is refused; an address that merely has an account is not
|
||||
|
||||
With Bob in the team, invite `bob@example.com` to it again.
|
||||
|
||||
@@ -915,7 +1035,7 @@ hour on his next request rather than at a sign-in, so give it that long before d
|
||||
endpoint has become a way of asking the server which addresses have accounts on it, answerable by anybody
|
||||
willing to create a team first. See ADR 0009.
|
||||
|
||||
### 10.5 LAST ACTIVE is a real time, and a coarse one · **needs a couple of hours**
|
||||
### 12.5 LAST ACTIVE is a real time, and a coarse one · **needs a couple of hours**
|
||||
|
||||
Use one account and leave the other idle for two or three hours, then read the members table.
|
||||
|
||||
@@ -928,7 +1048,7 @@ user's own overlapping requests failing on a version that moved under them. A va
|
||||
means the refresh is not running on ordinary requests at all, which is the state that made this column
|
||||
impossible to offer honestly before.
|
||||
|
||||
### 10.6 Ownership changes hands in one act
|
||||
### 12.6 Ownership changes hands in one act
|
||||
|
||||
As the owner, transfer ownership to another active member, then read both rows.
|
||||
|
||||
@@ -942,7 +1062,7 @@ member, and to yourself.
|
||||
either one leaves a team that no client can administer back into shape. If your grants were revoked or the
|
||||
vaults are now flagged for rekey, the transfer is removing the outgoing owner rather than demoting them.
|
||||
|
||||
### 10.7 Archiving is refused while the team owns a vault
|
||||
### 12.7 Archiving is refused while the team owns a vault
|
||||
|
||||
With a team that owns at least one vault, try to archive it.
|
||||
|
||||
@@ -953,7 +1073,7 @@ in this product. The team is still in everybody's list afterwards and its vaults
|
||||
everybody holding a key — including the person who pressed it, quietly, and with nothing in the product
|
||||
able to put them back.
|
||||
|
||||
### 10.8 Archiving an empty team takes its memberships and its invitations with it · **needs two accounts**
|
||||
### 12.8 Archiving an empty team takes its memberships and its invitations with it · **needs two accounts**
|
||||
|
||||
Make a team that owns no vaults, add the second account to it, invite a third address, and archive it.
|
||||
|
||||
@@ -965,7 +1085,7 @@ invitations inside the same transaction exists to prevent, and it would happen w
|
||||
nobody is watching. Note that taking the freed slug is correct rather than a defect, and is also the reason
|
||||
an archived team is only restorable by an operator who checks that first.
|
||||
|
||||
### 10.9 Renaming a team, and the slug that does not move
|
||||
### 12.9 Renaming a team, and the slug that does not move
|
||||
|
||||
Rename a team and change its description.
|
||||
|
||||
|
||||
Reference in New Issue
Block a user