feat(ui): offer the backend picker when secret storage is unreachable - #5
Merged
Conversation
A machine with no Secret Service (headless Linux, container, CI) hit a dead end: the send failed, the error correctly said the keychain was unavailable, and nothing told the user that a setting exists to change it. Switchable backends only help if you can find the switch. An unreachable backend — as opposed to a missing entry — now carries a "Secret storage…" button to App Settings, on the send-failure card, the response footer, and the toasts raised by the Secrets panel and the Auth tab's save. Only that case gets it: a secret that simply isn't set still just says so, because there the fix is to set a value. `SecretResolver` returns Result<String, String>, so a send-time failure reaches the UI with its type long gone. Rather than thread a typed error through a headless trait used by every protocol, `secret_backend_unavailable` matches the message, and a test asserts a real Unavailable error is detected through both `to_string()` and the resolver's own String conversion — so rewording the Display can't silently remove the recovery route. Two things surfaced while building it: - The footer pasted the whole error into a 38px bar, so it clipped mid-word against whatever sat beside it (pre-existing; the new button made it obvious). `format::status_summary` takes the first line and caps it on a word break, char-boundary safe; the full text stays in the card and the hover tooltip. The right-hand group also gains a margin — overflow clips at the padding box, so padding on the left group couldn't create that gap. - Focusing the picker on arrival was tried and reverted: the settings page doesn't scroll-anchor, so focus landed on an off-screen control where a keypress would open an invisible dropdown. The button opens the page; scrolling to the section needs a ScrollHandle and addressable sections, which is a real change rather than a drive-by.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes the dead end left by the switchable-backends work: a machine with no Secret Service hit a
failed send, got a correct "OS keychain secret storage is unavailable" error, and had no indication
that a setting existed to change it. Switchable backends only help if you can find the switch.
What changed
An unreachable backend — as distinct from a missing entry — now offers a
Secret storage…button that opens App Settings:
Only that case gets it. A secret that simply isn't set still just says so, because there the fix is
to set a value, not to switch backend. That distinction is the
NotFoundvsUnavailablesplitadded earlier.
The stringly-typed bit, and why
posel_template::SecretResolverreturnsResult<String, String>, so by the time a send-timefailure reaches the UI its type is gone. Threading a typed error through a headless trait used by
every protocol is a bigger change than this warrants, so
secret_backend_unavailable(&str)matchesthe message — with a test that asserts a real
Unavailableerror is detected both throughto_string()and through the resolver's own String conversion. Reword the#[error]attribute andthat test fails, rather than the recovery route silently disappearing.
Two things found while building it
The footer clipped mid-word. It pasted the entire error into a 38px status bar, so it collided
with whatever sat to its right. Pre-existing — the new button just made it obvious.
format::status_summarynow takes the first line and caps it on a word break (char-boundary safe,tested with multi-byte input); the full text stays in the failure card and the hover tooltip. The
right-hand group also gains a margin: overflow clips at the padding box, so padding on the left
group couldn't produce that gap.
Failed · secret 'demo_token' could nSecret storage…Failed · secret 'demo_token' could…Secret storage…RetryFocusing the picker was tried and reverted. My original pitch said it would "jump straight to
the picker". It doesn't — it opens the page. Focusing the control without scrolling to it is worse
than not focusing: the settings page has no scroll anchor, so focus lands off-screen and a keypress
opens a dropdown the user can't see. Verified that empirically, then backed it out. Doing it
properly means giving the settings page a
ScrollHandleand splitting its single scroll child intoaddressable sections — a real change, not a drive-by, and not something to land unreviewed right
before a publish. Left as a follow-up with a note in the code.
Verification
cargo fmt --all --check,cargo clippy --workspace --all-targets -- -D warnings,cargo test --workspace→ 404 passed, 0 failed (+3)x86_64-pc-windows-gnu, clippy-D warnings) cleanbutton on both surfaces, clicked it and confirmed App Settings opens
envbackend shows no picker