Commit Graph
3 Commits
Author SHA1 Message Date
jaap-jan b931a06998 Repaint the phone's chrome, radii and accent to the v5 vocabulary 2026-08-08 15:22:04 +02:00
jaap-jan dc1ebf6afa Let the recovery code be copied, and give the phone a clipboard to copy to
ci / build and test (push) Canceled after 0s
ci / android head (push) Canceled after 0s
ci / api image (push) Canceled after 0s
Both screens had made the code selectable and both said why: a person who cannot
get it out of the box photographs the screen, and a screenshot is a worse home
for it than a clipboard. This finishes that argument. Selecting 64 characters of
letter-spaced monospace with a thumb is the version of "possible" people give up
on halfway — and on the phone the screen blocks screenshots, so the honest
remaining options were retyping it or losing it.

It is the one secret this application deliberately offers to a clipboard, and the
contrast with the keychain's copy is the whole argument rather than an
inconsistency. There, copying the private half is refused outright, because
installing a key means pasting the public one and the private one has no business
leaving the vault. Here there is no better route: the code exists for one screen,
is stored nowhere, and has to reach a password manager. The clipboard is the
intended destination rather than a way round the design.

The sentence afterwards matters as much as the copy, and is asserted: a clipboard
is a staging post, this screen is the only place the code exists, and the next
thing copied replaces it. Somebody who copies and does nothing has not saved it.

The phone had no clipboard delegate at all — the desktop passed one and this head
passed null — so COPY PUBLIC KEY on the keychain answered "this machine has no
clipboard" on a device that plainly has one. Nothing about that was platform
shaped: Android has a clipboard and Avalonia surfaces it through the same
TopLevel. Wiring it fixes that copy too.

The test fixture built its shell without a clipboard, which modelled the bug
rather than the product, so it has one now and the public-key test asserts what
lands there instead of the refusal. The refusal keeps its own test, on a shell
built without one, because the view model reads the delegate's absence rather
than an empty result — and because a button that silently does nothing on this
screen is worse than one that refuses.
2026-08-05 18:14:34 +02:00
jaap-jan 7a3a521c59 Give the phone the rest of its screens, and a way in
ci / build and test (push) Failing after 2s
ci / android head (push) Failing after 1s
All seven screens of the design, plus the two it does not draw because it starts at an
enrolled phone: naming a server, and choosing a passphrase.

The five states docs/android-port.md worried about losing at 360dp are all here and none
of them softened. The changed-key refusal is a full-screen panel rather than a bottom
sheet, because a sheet is swipe-to-dismiss by convention and that screen must have no way
forward. The recovery code raises FLAG_SECURE for its own state and lowers it afterwards,
so the sentence about screenshots is true rather than decorative. The delete
confirmations keep their counts and replace the row in place.

Signing in works, and the seam it needed is worth more than the implementation:
IAuthorizationCallback now sits between OidcClient and the loopback listener, so the two
heads differ in where the response arrives and in nothing else. PKCE, the state check,
discovery, the token exchange and the key binding stay one implementation — a second OIDC
client would be a second place for a security bug to live. The phone registers a
private-use scheme with the system rather than binding a loopback port, which on a shared
device any other app can do first.

The accessory key row needed TerminalWorkspace.SendInputAsync: ordinary typing goes from
the renderer straight down the socket, and there was no way in for the keys a software
keyboard does not have. Ctrl latches, because one thumb cannot chord, and the latch is
drawn — a modifier that is on and does not look on is how somebody sends ^L to a database
prompt believing they typed an l.

597 client tests green, including two new ones for the input path and one for the
terminal surface command. Nothing has run on a device.
2026-07-31 21:43:11 +02:00