On a fresh iOS install, the PIN-entry screen shown during FTUE rendered
the input field as an effectively invisible rectangle inside the
Card. Users couldn’t see they needed to tap there to enter the PIN
to join the cluster — the screen looked like just a title, a
description, and a Connect button (which was disabled until 4 digits
were typed, completing the dead end).
composeApp/.../startup/PinEntryScreen.kt rendered an
OutlinedTextField with three properties that combined to make the
field invisible against the iOS Material light theme:
label. Without a label, an empty OutlinedTextField
shows literally nothing inside the rectangle — no floating label,
no in-field hint.placeholder. Same effect; nothing tells the user the
field expects input.unfocusedContainerColor =
surfaceVariant.copy(alpha = 0.15f)) and no border-color
override. The default OutlinedTextField unfocused border uses
colorScheme.outlineVariant, which is intentionally subtle. On
iOS’ Material light scheme that pale grey vanishes against the
white Card surface, leaving nothing visible at all.The bug was described as iOS-specific in the report, but the same composable serves desktop and Android — every target was missing the affordance; iOS just exposed it most starkly because of the lighter default surface.
composeApp/.../startup/PinEntryScreen.kt — added
label = { Text("PIN") } (the discoverability affordance: shows
inside the field when empty, floats up when focused), set
unfocusedBorderColor = MaterialTheme.colorScheme.outline (replaces
outlineVariant so the border is always visible regardless of
theme/background contrast), and focusedBorderColor =
MaterialTheme.colorScheme.primary for the standard “yes, you’re
typing here” feedback. Container alpha left as-is.composeApp/src/desktopTest/.../screenshot/Scenarios.kt — three new
Roborazzi screenshot scenarios (ftue__pin-entry__light,
ftue__pin-entry__dark, ftue__pin-entry__light__error) that
render the real PinEntryScreen composable. Compile-time guard +
visual-diff artifact for any future visibility regression.@BeforeEach setup, no delay, no real filesystem — pure
setContent { ... } calls per the workflow rule shipped in
Sautner-Studio-LLC/krill-agents#3.OutlinedTextField without a label AND without a placeholder
is almost always a bug. Either is a usability affordance; both
missing is a hidden field. If someone really wants a label-less
field (rare — it’s typically a single-cell PIN box, not a form
field), they should at minimum override unfocusedBorderColor to
something with adequate contrast against the parent surface.outlineVariant. Material 3 deliberately
subordinates outlineVariant to other foreground content; on light
themes it can be near-invisible against surface /
surfaceContainer. Form fields that are the primary call-to-action
on a screen (like FTUE) should set unfocusedBorderColor =
colorScheme.outline (one notch up in contrast) at minimum.PinEntryScreen here; do the same
for any new FTUE-tier screen.OutlinedTextField {
without a label = argument would have caught this, but the
false-positive rate would be high (legitimate uses exist) and the
population of FTUE screens is small. Relying on the screenshot
scenarios + the prevention rule above instead.