Tell the phone's keyboard these are secrets, and get it off the box
ci / api image (push) Successful in 21s
ci / build and test (push) Successful in 1m22s
ci / android head (push) Failing after 5s

Five boxes on this head take a secret and every one of them was drawing dots
and saying nothing. `PasswordChar` is a screen property: Windows has no opinion
about what is being typed into a text box, so the desktop head needs nothing
more. Android's software keyboard has an opinion, and left at its default it
read a vault passphrase as prose — completions offered in the suggestion strip
above the box, and the passphrase itself learned into the IME's dictionary.
Dots on screen with a word bar over them is the worst of both: hidden from the
person typing it and offered to the room. `TextInputOptions.ContentType` is the
property the Android backend maps onto `InputType`, and it is what turns both
off. Both attributes now live in one `TextBox.secret` class rather than being
repeated per box, because they are two halves of one fact and the next box
added would have got one of them.

The keyboard also went on covering whichever box had raised it. That is in
`PhoneShell` rather than on each screen: everything the phone draws is inside
its one root panel, so a bottom margin shortens all eleven screens at once, and
a screen added later cannot forget to handle something it never had to know
about.

Two mechanisms, and it matters that neither is a backstop for the other. Before
Android 15 the activity now declares `AdjustResize` and the platform shortens
the window itself; left unspecified Android chooses, and what it chooses for a
window whose entire content is one native view — which is what an Avalonia
surface is — is to pan, sliding the window by however much it thinks the
focused native view needs and leaving the box exactly where it was. That was
the bug. From Android 15 the attribute is ignored, edge-to-edge being enforced
and the window no longer resized for the keyboard at all, and the reported
inset is what there is. Each is dead where the other applies — where the window
resizes, the inset arrives already consumed and measures zero — which is why
the margin comes from the inset alone. Both added together would strand the
interface an entire keyboard above the keyboard.

Scrolling the box back into view keys off the size change rather than off
either mechanism. `ScrollViewer` already brings a newly focused child into
view; what it cannot know is that the visible region shrank after the focus,
and both ways of losing that region end in the same resize.

None of it is reachable by a test. The software keyboard is an inset the
platform reports and a headless top level reports none, so phase 10 of
`docs/manual-checks.md` is the whole of the verification — including the note
to run it on one device each side of Android 15, since a build exercised on
only one of the two will look correct and be half broken.
This commit is contained in:
2026-08-03 13:55:08 +02:00
parent 16e0051e89
commit 35387b1c9d
10 changed files with 225 additions and 8 deletions
+55
View File
@@ -834,3 +834,58 @@ nothing else about them changes. Neither host is queued for push.
On the phone's host editor with several tags: the chips are at least 36 tall, spaced enough that a miss
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 — 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.