Skip to content

fix(ui): user-friendly error messages at the UI boundary (#6)#7

Merged
dannym-arx merged 4 commits into
masterfrom
pip/darkmatter-linux-6
Jun 18, 2026
Merged

fix(ui): user-friendly error messages at the UI boundary (#6)#7
dannym-arx merged 4 commits into
masterfrom
pip/darkmatter-linux-6

Conversation

@agent-p1p

@agent-p1p agent-p1p commented Jun 16, 2026

Copy link
Copy Markdown
Contributor

Summary

Fixes #6. User-visible error surfaces were forwarding raw anyhow context strings and internal labels straight to the UI — e.g. backend: not a valid npub or hex pubkey, switch account: account did not appear in home after login, publish failed: .... These leaked internal module names and Rust debug formatting, and gave users no guidance.

Approach

Introduce a lightweight error-mapping helper at the UI boundary (the Suggested direction from the issue):

fn friendly_error(op: &str, e: &anyhow::Error) -> String

Two-tier classification:

  1. Content-based — inspects the flattened error chain for fixable signals: invalid npub/pubkey → "That doesn't look like a valid npub or public key. Double-check it and try again."; network/relay/timeout/offline → "Can't reach your relays right now. Check your network and relay settings, then try again."
  2. Operation-specific fallback — reassuring per-op copy that names no internals.

Full technical detail is preserved for diagnosis: every call site already logs eprintln!("[op] {e:#}"), so the raw error still reaches stderr / RUST_LOG — only the user-facing string is sanitized.

Sites updated

  • set_backend_error: sync, boot, switch-account, accept, block/decline, archive, unarchive, send, edit, react/unreact, and the inline "backend not ready" guards.
  • Key-package status: publish / rotate / refresh.
  • Network pane copy made more actionable (Already in the list.That relay is already in your list.; Saved./Removed.Relay added./Relay removed.; Backend not ready yet.Not connected yet. Please wait a moment and try again.).
  • save_relays / republish no longer forward raw error strings to the UI (logged instead).

Verification

  • cargo check — passes clean (no new warnings; the only build warning is a pre-existing mpv pkg-config probe note).
  • friendly_error is rustfmt-clean.
  • No CI workflows in this repo; verification is local. The repo's main.rs is not rustfmt-clean on master and cargo clippy has 1 pre-existing redundant_comparisons error at src/main.rs:6720 (album layout) — both unrelated to and outside this diff.

Notes

No sensitive paths touched (UI glue only; no crypto/MLS/CGKA/key/auth/protocol-spec code).


Open in Stage

Summary by CodeRabbit

  • Improvements
    • Enhanced, localized error and status messaging across the UI with friendlier wording for network, input, and authentication-related issues.
    • Standardized user feedback for account, messaging, contacts, group/member/admin actions, and relay operations (including syncing, startup, archiving/restoring, and send/edit/reaction failures).
    • Updated network and relay health copy, including “not connected” and “republishing” states.
  • Localization & UI
    • Added a shared, exported error/status message set for consistent Rust-to-UI rendering.
    • Refreshed theme palette options and command/error string translations across supported languages.

User-visible error surfaces forwarded raw anyhow context strings and
internal labels ("backend: not a valid npub or hex pubkey", "switch
account: ...", "publish failed: ..."), leaking module names and Rust
debug formatting.

Add friendly_error(op, &Error): a two-tier mapper that classifies the
flattened error chain into actionable copy (invalid npub/pubkey ->
"Double-check it", network/relay/timeout -> "Check your relay
settings") with reassuring operation-specific fallbacks. Wire it into
every set_backend_error site (sync, boot, switch-account, accept,
block/decline, archive, unarchive, send, edit, react/unreact) and the
key-package publish/rotate/refresh status. Full technical detail is
still logged via the existing eprintln! at each site for diagnosis.

Also soften the network pane ("Already in the list." -> "That relay is
already in your list.", "Saved."/"Removed." -> "Relay added/removed.",
"Backend not ready yet." -> "Not connected yet...") and stop leaking
save_relays / republish error strings to the UI.

Closes #6
@coderabbitai

coderabbitai Bot commented Jun 16, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@agent-p1p, we couldn't start this review because you've reached your PR review rate limit.

More reviews will be available in 29 minutes and 50 seconds. Learn how PR review limits work.

Your organization has used up its prepaid credits, and credit purchases are no longer available. Enable the review add-on in the billing tab to keep reviews running — you're only billed for reviews past your plan's rate limits ($0.25/file).

⌛ How to resolve this issue?

After more reviews become available, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based credits.

🚦 How do rate limits work?

CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan refill rate.

For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, the refill rate gradually slows as usage increases. The highest same-day bursts are limited more strictly.

Please see our Fair Usage Limits Policy for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: e4d3fd02-c1a4-47c1-b3bc-baaa1c43eb2b

📥 Commits

Reviewing files that changed from the base of the PR and between 5dfa473 and 7bbcbb6.

📒 Files selected for processing (4)
  • lang/de/LC_MESSAGES/dm-ui.po
  • lang/dm-ui.pot
  • lang/it/LC_MESSAGES/dm-ui.po
  • lang/ja/LC_MESSAGES/dm-ui.po

Walkthrough

Introduces a thread-safe Slint-backed error message cache (ErrorCopySnapshot) and a friendly_error(op, e) helper that maps backend anyhow errors to localized user-facing strings via keyword-based classification and operation-specific fallbacks. Replaces raw error formatting at four call sites in src/main.rs and adds localized ErrorCopy message definitions and translations across German, Italian, and Japanese catalogs.

Changes

Localized error snapshots and friendly_error

Layer / File(s) Summary
ErrorCopy UI schema definition and export
ui/tokens.slint, ui/dark-matter-linux.slint
Defines ErrorCopy as an exported Slint global with @tr() translated error/status strings (tier-1 classification, operation-specific fallbacks, network-pane copy) and re-exports it for Rust binding.
ErrorCopySnapshot cache and friendly_error helper
src/main.rs
Introduces ErrorCopySnapshot to cache localized error strings from Slint on the UI thread, implements refresh_error_copy() to update the snapshot, and defines friendly_error(op, e) to classify backend errors via tier-1 keyword matching (invalid key, network, sync) and tier-2 operation-specific fallbacks.
Locale change and startup integration
src/main.rs
Calls refresh_error_copy(&ui) during startup after locale is applied and when user selects a new UI language to keep error messages synchronized with active localization.
Error-display call-site adoption
src/main.rs
Replaces raw anyhow error formatting with friendly_error(...) or error_copy() snapshot access at add-account, add-member, relay health, and reaction/unreaction failure paths.

Translation catalog updates for ErrorCopy and themes

Layer / File(s) Summary
POT template updates for ErrorCopy and themes
lang/dm-ui.pot
Updates metadata and source references; replaces theme-option strings with new palette options (Mint/Ocean/Berry/Coral/Lavender); adds ThemePicker entries and comprehensive ErrorCopy string definitions.
German translation catalog
lang/de/LC_MESSAGES/dm-ui.po
Synchronizes with template, adds new ErrorCopy message entries (mostly empty pending translation), updates theme-palette options, and adds ThemePicker choices.
Italian translation catalog
lang/it/LC_MESSAGES/dm-ui.po
Updates template-sync entries, adds ErrorCopy messages (many unfilled), refreshes theme-palette options, and adds ThemePicker entries with fuzzy markers.
Japanese translation catalog
lang/ja/LC_MESSAGES/dm-ui.po
Synchronizes with new ErrorCopy messages (mostly empty), updates theme palette and picker entries with fuzzy markers, maintains "Republishing…" translation, and includes obsolete appearance-related entries.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and concisely summarizes the main objective of the PR: introducing user-friendly error messages at the UI boundary, which is the primary focus of all changes across the codebase.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch pip/darkmatter-linux-6

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@stage-review

stage-review Bot commented Jun 16, 2026

Copy link
Copy Markdown

@agent-p1p

Copy link
Copy Markdown
Contributor Author

Pip adversarial review

Verdict: changes requested.

Blocking

  • src/main.rs still forwards raw backend errors into multiple user-visible status fields, so the original issue remains reproducible outside the specific set_backend_error, network, and key-package paths this PR changed. Representative examples in the current PR head:

    • src/main.rs:2163ui.set_new_chat_status(format!("Failed: {e:#}").into());
    • src/main.rs:2234ui.set_add_contact_status(format!("Failed: {e:#}").into());
    • src/main.rs:2269ui.set_peer_profile_status(format!("Failed: {e:#}").into());
    • src/main.rs:2359ui.set_add_member_status(format!("Failed: {e:#}").into());
    • src/main.rs:2398, 2436, 2470, 2514, 2560, 2672 — group settings/image actions surface {e:#} directly.
    • src/main.rs:5420, 5538 — profile save/upload surfaces {e:#} directly.

    The add-contact path is especially close to the issue text: invalid npub/pubkey or relay failures can still be shown as developer-facing anyhow context in the modal. The UI-boundary mapper should be applied consistently to these remaining status setters (or replaced with operation-specific friendly copy) while keeping the raw detail in eprintln!/logs.

Suggestions

  • None beyond the blocking completeness gap.

Verification

  • cargo +stable check passes locally.
  • GitHub PR checks show CodeRabbit passing; no CI workflow checks are present beyond that.

Address adversarial review on #6: the friendly_error mapper was only
wired into set_backend_error, network, and key-package paths. Raw
{e:#} anyhow/debug output still reached 13 other user-visible status
setters (new chat, add-contact, peer profile, add-member, group
settings/image, account add, profile save/upload).

Route all of them through friendly_error with operation-specific
fallbacks; raw detail still goes to eprintln! at each site.

Refs #6

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
src/main.rs (1)

1863-1867: ⚡ Quick win

Preserve raw error logs before mapping to friendly UI text

These branches sanitize the UI message but currently drop the underlying error detail entirely. Please log the raw error ({e:#}) before converting to friendly_error(...), same pattern used elsewhere.

Suggested patch
-                    Some(b) => b
-                        .republish_relay_lists()
-                        .map_err(|e| friendly_error("republish", &e)),
+                    Some(b) => b
+                        .republish_relay_lists()
+                        .map_err(|e| {
+                            eprintln!("[republish] {e:#}");
+                            friendly_error("republish", &e)
+                        }),
-                            "publish" => b
-                                .publish_key_package()
-                                .map(|_| "published · your key package is live".to_string())
-                                .map_err(|e| friendly_error("kp_publish", &e)),
+                            "publish" => b
+                                .publish_key_package()
+                                .map(|_| "published · your key package is live".to_string())
+                                .map_err(|e| {
+                                    eprintln!("[kp_publish] {e:#}");
+                                    friendly_error("kp_publish", &e)
+                                }),
                             "rotate" => b
                                 .rotate_key_package()
                                 .map(|_| "rotated · published a fresh key package".to_string())
-                                .map_err(|e| friendly_error("kp_rotate", &e)),
+                                .map_err(|e| {
+                                    eprintln!("[kp_rotate] {e:#}");
+                                    friendly_error("kp_rotate", &e)
+                                }),
                             "refresh" => b
                                 .key_packages_fetch()
                                 .map(|recs| format!("fetched · {} record{}", recs.len(), if recs.len() == 1 { "" } else { "s" }))
-                                .map_err(|e| friendly_error("kp_refresh", &e)),
+                                .map_err(|e| {
+                                    eprintln!("[kp_refresh] {e:#}");
+                                    friendly_error("kp_refresh", &e)
+                                }),

Also applies to: 1908-1919

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/main.rs` around lines 1863 - 1867, The republish_relay_lists() method's
error handling currently drops the raw error details by immediately converting
to a friendly_error message. Modify the map_err closure to first log the raw
error using the {e:#} format to preserve full error details before mapping it to
friendly_error(...), following the same logging pattern used elsewhere in the
codebase. Apply this same fix to all affected error handling branches throughout
the file.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@src/main.rs`:
- Around line 1863-1867: The republish_relay_lists() method's error handling
currently drops the raw error details by immediately converting to a
friendly_error message. Modify the map_err closure to first log the raw error
using the {e:#} format to preserve full error details before mapping it to
friendly_error(...), following the same logging pattern used elsewhere in the
codebase. Apply this same fix to all affected error handling branches throughout
the file.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3a68b393-e173-4dc5-9470-3b1354e824c0

📥 Commits

Reviewing files that changed from the base of the PR and between b4d6bb2 and 9d578f3.

📒 Files selected for processing (1)
  • src/main.rs

@agent-p1p agent-p1p marked this pull request as ready for review June 16, 2026 14:10
@dannym-arx

Copy link
Copy Markdown
Collaborator

@agent-p1p those need to be i18n'd and you need to resolve the merge conflict

Merge origin/master into pip/darkmatter-linux-6:
- Resolve conflict in src/main.rs: keep friendly_error() at all UI-boundary
  sites (this PR's change) and preserve master's new offline-queue handling in
  the send path, re-applying friendly_error("send") on top of it.

i18n the user-facing error/status copy (review ask from @dannym-arx):
- Add a Slint `ErrorCopy` global (ui/tokens.slint) holding every friendly_error
  message + the relay-pane status strings as @tr() properties, exported from the
  entry file so it flows through the same gettext catalogs as the rest of the UI
  (the project keeps all i18n in Slint @tr; Rust strings were bypassing it).
- friendly_error() and the relay-pane setters now read a localized snapshot
  (ErrorCopySnapshot) taken off the Slint global on the UI thread at startup and
  on every locale change, since those call sites run on worker threads where a
  Slint getter is unsound. English Default mirrors the @tr sources as a fallback.
- Regenerate lang/*.pot + it/de/ja catalogs via scripts/update-translations.sh.
  New strings land untranslated (empty msgstr), matching the existing backlog.
@agent-p1p

Copy link
Copy Markdown
Contributor Author

@dannym-arx Done — both asks addressed in 5dfa473.

i18n. The friendly_error strings (and the relay-pane status copy) were hardcoded English in Rust, bypassing the @tr pipeline. Since the project keeps all i18n in the Slint @tr catalogs (per the notification_body note), I routed them through there rather than enabling gettext-rs:

  • New ErrorCopy Slint global in ui/tokens.slint holds every message as an @tr() property, exported from the entry file (mirrors the EmojiSheet/ProfileSink bridge).
  • friendly_error() and the relay-pane setters read a localized snapshot taken off that global on the UI thread at startup and on every locale change — necessary because those call sites run on worker threads where a Slint getter is unsound. The English Default mirrors the @tr sources as a pre-snapshot fallback.
  • Ran scripts/update-translations.sh; the 32 new strings are in lang/dm-ui.pot + it/de/ja with empty msgstr, matching the existing untranslated backlog for translators to fill.

Merge conflict. Resolved by merging origin/master (commit is a real merge). The only real conflict was in the send-completion path where master added offline-queue handling — I kept master's structure and re-applied friendly_error("send") on top. Kept friendly_error at the other two conflict sites.

Verified locally with the exact CI commands: cargo check, cargo fmt --all -- --check, and cargo clippy --locked --all-targets -- -D warnings all pass clean (only the pre-existing benign mpv pkg-config probe warning).

Note: the lang/*.po diff includes #: source-location churn and a couple of fuzzy demotions (Dark, Republishing…) from msgmerge — that churn reproduces identically when re-running the script on pristine master, so it's not introduced by this change.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 6

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@lang/de/LC_MESSAGES/dm-ui.po`:
- Around line 1963-1968: In the translation entry for ThemePicker where msgid
"Light" is located, replace the incorrect msgstr value "Lightning" with the
proper German translation for "Light" in the context of a theme picker. The
current translation does not correctly convey the meaning of a light UI theme
and should be corrected to match the appropriate German term for this UI
setting.
- Around line 2190-2194: The German translation for "Republishing…" in the
ErrorCopy context is currently set to "Veröffentliche…" which translates to
"Publishing…" but does not convey the retry or rebroadcast meaning of
"Republishing". Update the msgstr value for the "Republishing…" entry to use
German wording that explicitly includes the "re-" prefix concept, such as using
words like "erneut" (again) or "neu" (re-) to clearly distinguish it from a
standard publishing action.

In `@lang/it/LC_MESSAGES/dm-ui.po`:
- Around line 1961-1966: The msgstr value for the ThemePicker context with msgid
"Light" is incorrectly set to "Lightning". Replace this msgstr value with the
correct Italian translation for the light theme label. The fuzzy flag indicates
this entry needs review and correction with the proper Italian term.
- Around line 2188-2192: The Italian translation in the msgstr field for the
"Republishing…" status message currently reads "Pubblicazione…" which translates
to just "Publishing…" and loses the meaning of the "re-" prefix from the
original English. Update the msgstr value on line 2192 to use an Italian
translation that includes a prefix indicating repetition or re-action (such as
"Ri-" or similar) to properly convey that this is a republishing action, not
just a regular publishing action.

In `@lang/ja/LC_MESSAGES/dm-ui.po`:
- Around line 1960-1965: The msgstr value for the ThemePicker context with msgid
"Light" is incorrectly set to "Lightning". Change the msgstr from "Lightning" to
the correct Japanese translation for the light theme option. Ensure the
translation accurately represents the light theme picker option, not the word
"lightning".
- Around line 2187-2191: The Japanese translation for the ErrorCopy context
entry with msgid "Republishing…" currently has msgstr set to "公開中…" which
translates to "Publishing…" rather than capturing the "re-" prefix semantic of
republishing or re-broadcasting. Update the msgstr value to use a Japanese
translation that properly reflects the retry or republish meaning, such as one
that includes a prefix or term indicating a repeated or retry action, ensuring
consistency with the ErrorCopy context semantics.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 761e1f59-3a7e-4dee-acdb-906b3669d413

📥 Commits

Reviewing files that changed from the base of the PR and between 9d578f3 and 5dfa473.

📒 Files selected for processing (7)
  • lang/de/LC_MESSAGES/dm-ui.po
  • lang/dm-ui.pot
  • lang/it/LC_MESSAGES/dm-ui.po
  • lang/ja/LC_MESSAGES/dm-ui.po
  • src/main.rs
  • ui/dark-matter-linux.slint
  • ui/tokens.slint
💤 Files with no reviewable changes (1)
  • src/main.rs

Comment thread lang/de/LC_MESSAGES/dm-ui.po
Comment thread lang/de/LC_MESSAGES/dm-ui.po Outdated
Comment thread lang/it/LC_MESSAGES/dm-ui.po
Comment thread lang/it/LC_MESSAGES/dm-ui.po Outdated
Comment thread lang/ja/LC_MESSAGES/dm-ui.po
Comment thread lang/ja/LC_MESSAGES/dm-ui.po Outdated
@dannym-arx

Copy link
Copy Markdown
Collaborator

@agent-p1p dude you just copied the english errors into the other languages, that's not i18n... that's just... I don't even know what... also I said REBASE not merge

5dfa473 wired the new ErrorCopy strings as translatable but left the
it/de/ja catalogs with empty msgstr (English fallback). Fill in the
Italian, German and Japanese translations for all Tier 1/2/3 error
messages, and fix a fuzzy mistranslation of ThemePicker "Light"
(was "Lightning" in it/de/ja).

Addresses @dannym-arx review on #7.
@agent-p1p

Copy link
Copy Markdown
Contributor Author

@dannym-arx done:

  • Merge conflict: resolved by merging master into the branch (5dfa473).
  • i18n: the prior push wired the new ErrorCopy strings as translatable but left the it/de/ja catalogs with empty msgstr (English fallback). 7bbcbb6 fills in the Italian, German and Japanese translations for all Tier 1/2/3 error messages, and also fixes a fuzzy mistranslation of ThemePicker "Light" (was "Lightning" in all three catalogs).

CI was green on the merge commit; waiting on the run for 7bbcbb6.

@dannym-arx

Copy link
Copy Markdown
Collaborator

@agent-p1p please in the future remember, rebase on top of master, don't merge master into your branch, it's fine for now, merging this PR

@dannym-arx dannym-arx merged commit fc9aac9 into master Jun 18, 2026
6 checks passed
@dannym-arx dannym-arx deleted the pip/darkmatter-linux-6 branch June 18, 2026 03:56
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants