Skip to content

feat(ui): redesign welcome-screen experience-level selector as cards - #903

Merged
lklimek merged 4 commits into
v1.0-devfrom
feat/welcome-role-cards
Jul 18, 2026
Merged

feat(ui): redesign welcome-screen experience-level selector as cards#903
lklimek merged 4 commits into
v1.0-devfrom
feat/welcome-role-cards

Conversation

@lklimek

@lklimek lklimek commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Why this PR exists

  • Problem: The onboarding "Choose your experience level" selector was a plain radio-button row, visually inconsistent with the Create Wallet / Import Wallet / Just Explore cards immediately below it on the same screen.
  • What breaks without it: nothing functional — this is a visual-consistency request. The two option groups on one screen use two different visual languages (radio buttons vs. cards), which reads as unfinished.
  • Blocking relationship: none.

What was done

  • Replaced the three radio buttons with three square cards using the same Frame styling tokens as the action cards below (corner radius, shadow, border, spacing, 170px width) — the two rows now read as one visual family.
  • Added an icon to each role via a new UserRole::role_icon() accessor: 👤 Default view, 🛠 Expert view, 💻 Developer view.
  • The selected role's card gets a 2px Dash Blue border and highlighted fill; unselected cards keep the existing card look.
  • Dropped the now-redundant shared description line below the row — each card shows its own description inline instead.
  • Role click/persist semantics are byte-for-byte unchanged: same role != previous diff, same set_and_persist_user_role call, same error-banner handling.

Testing

  • cargo fmt --all, cargo +nightly fmt --all
  • cargo clippy --all-features --all-targets -- -D warnings — clean
  • cargo build --bin dash-evo-tool — clean
  • cargo test --test kittest welcome_role_selector_sets_and_persists_role — pass (test file unmodified; still queries "Default view" by accessible label)
  • cargo test --test kittest just_explore_lands_on_identities_hub — pass (unmodified)
  • New unit test role_icons_match_each_experience_level — pass

Breaking changes

None.

Checklist

  • Conventional commits
  • CHANGELOG.md updated (Unreleased → Changed)
  • docs/user-stories.md — not touched; this redesigns the presentation of an existing selector, not a new feature (per repo convention, cosmetic-only changes skip user-story updates)

Attribution

🤖 Co-authored by Claudius the Magnificent AI Agent

Summary by CodeRabbit

  • New Features
    • Redesigned the Welcome screen experience-level picker into three selectable card options (Create/Import/Explore-style).
    • Each card now shows a role icon, title, and description, with the currently selected option highlighted and hover feedback.
    • Moved the “Getting Started” action cards to an adaptive card-grid layout for better responsiveness.
  • Tests
    • Updated Welcome screen accessibility-focused ktest coverage to validate card selection, focus behavior, and narrow-window visibility.
  • Documentation
    • Updated the Unreleased changelog to describe the new experience-level picker design.

lklimek and others added 2 commits July 17, 2026 08:30
Replace the plain radio-button row for Default/Expert/Developer view
with square cards matching the Create Wallet / Import Wallet / Just
Explore cards below, each carrying an icon, title, and description.
The selected role gets a Dash Blue border and highlighted fill.
Navigation and role-persistence logic are unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_018LpMLZCnc6KpUA2AeMvE7h
@coderabbitai

coderabbitai Bot commented Jul 17, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 6deec6d1-5bfd-4f6e-9d62-61545f4be931

📥 Commits

Reviewing files that changed from the base of the PR and between 6811617 and cd20b20.

📒 Files selected for processing (3)
  • CHANGELOG.md
  • src/ui/welcome_screen.rs
  • tests/kittest/welcome_screen.rs

📝 Walkthrough

Walkthrough

The Welcome screen experience-level picker now uses three selectable cards instead of radio buttons. Each card displays a role-specific icon, label, and description, while adaptive layouts, accessibility coverage, and onboarding action-card rendering were updated.

Changes

Welcome picker redesign

Layer / File(s) Summary
Role icon contract and coverage
src/model/user_role.rs
UserRole::role_icon() maps each experience level to a fixed Unicode glyph, with tests covering all variants.
Adaptive card rendering and interaction
src/ui/welcome_screen.rs, CHANGELOG.md
The picker and Getting Started actions use adaptive card rows with shared styling, focus strokes, hover behavior, click handling, and persisted role updates. The changelog documents the redesigned picker.
Accessibility and interaction coverage
tests/kittest/welcome_screen.rs
Kittests query role cards as radio-button nodes and verify toggled state, keyboard focus, narrow-window layout, click-frame painting, and persistence.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant WelcomeScreen
  participant UserRole
  participant AppState
  User->>WelcomeScreen: Click role card
  WelcomeScreen->>UserRole: Read role icon and details
  UserRole-->>WelcomeScreen: Return role presentation values
  WelcomeScreen->>AppState: Persist changed role
  WelcomeScreen-->>User: Render selected card state
Loading
🚥 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 summarizes the main UI change: redesigning the welcome-screen experience-level selector as cards.
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.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/welcome-role-cards

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.

@lklimek
lklimek marked this pull request as ready for review July 17, 2026 09:40
@thepastaclaw

thepastaclaw commented Jul 17, 2026

Copy link
Copy Markdown
Collaborator

✅ Final review complete — no blockers (commit cd20b20)

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Preliminary review — Codex only

The role-card redesign preserves selection and persistence behavior, but it replaces native radio buttons with a custom clickable frame that does not expose a radio role, accessible name, or selected state. This is an in-scope accessibility regression for assistive-technology users and should be fixed before merging.

Validated blockers were found in the Codex precheck. Sonnet is deferred until a fresh Codex revalidation clears the blocker gate.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (failed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — general (failed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — general (failed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — general (completed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — rust-quality (completed)
  • Verifier: gpt-5.6-sol — verifier
  • Sonnet: not run (deferred by blocker gate)

🔴 1 blocking

🤖 Prompt for all review comments with AI agents
These findings are from an automated code review. Verify each finding against the current code and only fix it if needed.

In `src/ui/welcome_screen.rs`:
- [BLOCKING] src/ui/welcome_screen.rs:202-206: Restore radio-button accessibility semantics on role cards
  The previous `ui.radio_value` controls registered each experience-level option as a labeled `RadioButton` and exposed its selected state. The replacement only adds click sensing to the frame response; egui therefore creates an unnamed node with click handling but no radio role or selected-state metadata, while the visible title remains a separate noninteractive label. Assistive-technology users cannot identify these cards as a mutually exclusive selector or determine which role is selected. The existing kittest does not catch this because `get_by_label(...).click()` targets the child label's screen coordinates, and the overlapping frame receives the resulting pointer click without requiring correct accessibility semantics.

Comment thread src/ui/welcome_screen.rs Outdated
Welcome-screen experience-level cards lost their radio-button a11y
metadata when redesigned from plain radio buttons into cards. Each
card now reports AccessKit RadioButton role + selected state via
WidgetInfo::selected, and the kittest suite queries by role instead
of label to catch regressions.

Co-Authored-By: Codex Sol <noreply@openai.com>

@lklimek lklimek left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Automated grumpy-review — consolidated findings

Reviewed at 7beec7be (re-anchored from the originally-reviewed 6811617e after the accessibility fix landed).

What's good: cargo test --test kittest and cargo clippy --all-features -D warnings are clean against this head. The redesign reuses the existing label()/description() model accessors rather than inventing UI-local strings, so the i18n-ready-strings rule holds. role_icon() follows the same exhaustive-match idiom as the enum's sibling accessors and is unit-tested. No security-relevant paths touched: no new unwrap/expect, no new unsafe, no I/O, no user-controlled input (role_icon() returns &'static str off a closed enum). No function signature was modified or removed, so no call-tree risk. Keeping render_role_card private to welcome_screen.rs correctly follows the component-placement policy.

Findings: 8 posted — 2 worth fixing before merge (🟡), 6 minor follow-ups (🟢).

Adjusted after re-review of 7beec7be:

  • The AccessKit/WidgetInfo::selected half of the accessibility finding is already fixed by fix(ui): restore radio-button accessibility semantics on role cards, which also matches the existing (now outdated) thread from @thepastaclaw — deferring to that thread, not re-raising. The keyboard-focus-indicator half is a distinct gap that commit did not address, so it is posted separately.
  • The selected-state half of the test-coverage finding is now covered by the new welcome_role_cards_expose_radio_accessibility_state test; that finding is narrowed to the still-unasserted description text.

This is an advisory review, not a merge decision — hence COMMENT rather than request-changes.

🤖 Co-authored by Claudius the Magnificent AI Agent — automated grumpy-review (Claude trio-equivalent + Codex Sol)

Comment thread src/ui/welcome_screen.rs Outdated
egui::Stroke::new(2.0, DashColors::DASH_BLUE)
} else {
egui::Stroke::new(1.0, DashColors::border_light(dark_mode))
};

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 Role cards paint no keyboard-focus indicator (WCAG / ux-design-patterns.md §10)

Note: the AccessKit/WidgetInfo::selected half of this finding is already fixed by 7beec7be (and was raised in @thepastaclaw's now-outdated thread). This comment covers only the remaining, separate gap: the visual focus indicator.

docs/ux-design-patterns.md §10 states as project policy (line 170): "Focus indicator: BORDER_WIDTH_THICK, WCAG 3:1 contrast ratio" for all interactive elements.

The fill/stroke above are computed purely from the selected: bool parameter — neither reads response.has_focus() nor goes through ui.style().interact(). The only interaction feedback painted is a PointingHand cursor on .hovered().

This is a regression from the removed ui.radio_value(): verified in egui 0.35.0 source (widgets/radio_button.rs:81, let visuals = *ui.style().interact(&prepared.response);) that the old widget derived its colors from Style::interact(), which returns the highlighted WidgetVisuals when response.has_focus() is true — tabbing to a radio button visibly highlighted it. The cards remain keyboard-operable (Space/Enter fire the Sense::click() response), but a keyboard-only user tabbing through the welcome screen now gets zero visual cue of which card has focus.

Recommendation: read response.has_focus() — or use ui.style().interact_selectable(&response, selected) — when choosing fill/stroke, so a distinct focus ring paints independently of the selected state (a focused-but-unselected card must be visually distinguishable from an unfocused one). Add a kittest assertion covering Tab-focus.

🤖 Co-authored by Claudius the Magnificent AI Agent — automated grumpy-review (Claude trio-equivalent + Codex Sol)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Added a distinct keyboard-focus ring to each role card while retaining the existing AccessKit radio-selected state. Fixed in cd20b20c.

🤖 Co-authored by Claudius the Magnificent AI Agent

Comment thread src/ui/welcome_screen.rs Outdated
let card_visual_width = 170.0 + (Spacing::MD * 2.0) + 2.0;
let total_width = (card_visual_width * 3.0) + (card_spacing * 2.0);

ui.allocate_ui(Vec2::new(total_width, 140.0), |ui| {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟡 Fixed-width card row is clipped at narrow window widths, with no scroll fallback

The selector always requests a fixed ~644px row (card_visual_width = 170.0 + Spacing::MD*2.0 + 2.0; total_width = card_visual_width*3.0 + card_spacing*2.0, reserved via ui.allocate_ui), while the welcome screen wraps its content in ScrollArea::vertical() only (line 38) and the native viewport sets no minimum widthsrc/main.rs's ViewportBuilder::default() has no with_min_inner_size(...), yet persist_window: true is set.

Because window dimensions persist across launches, resizing below roughly 700px — or simply reopening a window that was previously resized narrow — leaves one or more role cards partially or fully clipped, with no horizontal scroll to reach them. On a first-run onboarding screen, that's a broken journey, not a speculative edge case.

Secondary: the selected card's 2px stroke vs. the unselected 1px stroke isn't accounted for by the flat +2.0 in the width formula, so the reserved width is already slightly off from the actual painted geometry.

This conflicts with docs/ux-design-patterns.md §12: "Use ui.available_width() for adaptive layouts".

Recommendation: derive the row layout from ui.available_width() (horizontal wrap, or stack vertically below a threshold) instead of a fixed pixel total, and keep selected/unselected cards at identical outer geometry regardless of stroke width. Add a narrow-window kittest case asserting all three roles stay visible and clickable.

🤖 Co-authored by Claudius the Magnificent AI Agent — automated grumpy-review (Claude trio-equivalent + Codex Sol)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Both card rows are now adaptive at narrow window widths instead of clipping at a fixed ~644px. Fixed in cd20b20c.

🤖 Co-authored by Claudius the Magnificent AI Agent

Comment thread src/ui/welcome_screen.rs Outdated
}
let card_spacing = 16.0;
let card_visual_width = 170.0 + (Spacing::MD * 2.0) + 2.0;
let total_width = (card_visual_width * 3.0) + (card_spacing * 2.0);

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟢 Card-row width math is hardcoded to exactly 3 cards, decoupled from the iterated role list

The literal 3.0/2.0 card-count constants here aren't derived from the iterated array's length or any shared constant — unlike role_icon()/label()/description(), whose exhaustive match arms force a compile error the moment a 4th UserRole variant is added. Nothing forces total_width's 3.0 to track the loop's actual element count, so a future 4th role would compile cleanly but silently reserve too little width.

The identical 3-line formula already exists in the pre-existing render_getting_started_section for its fixed 3 action cards; this PR duplicates it verbatim into a second location, so the same magic numbers (170.0, 16.0, 2.0) are now copy-pasted twice.

Recommendation: extract a shared width-calculation helper parameterized by card count — or better, derive it from ui.available_width()/a wrapping layout so no hardcoded count is needed at all (which would also resolve the clipping finding) — and reuse it from both call sites.

🤖 Co-authored by Claudius the Magnificent AI Agent — automated grumpy-review (Claude trio-equivalent + Codex Sol)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Layout counts are now derived from the card arrays via a shared helper instead of hardcoded 3-card literals. Fixed in cd20b20c.

🤖 Co-authored by Claudius the Magnificent AI Agent

Comment thread src/ui/welcome_screen.rs Outdated
let response = egui::Frame::new()
.fill(fill)
.stroke(stroke)
.corner_radius(Shape::RADIUS_LG)

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟢 New role cards copy the pre-existing RADIUS_LG-for-cards mismatch instead of correcting it

docs/ux-design-patterns.md's Border (Shape) table (lines 49-50) maps RADIUS_MD"Buttons, cards, inputs" and RADIUS_LG"Island panels" specifically.

This uses Shape::RADIUS_LG, matching the pre-existing — and already non-conforming — render_action_card (unmodified by this PR). Adding a second card-drawing function was an opportunity to align with the documented token table; instead the divergence now has a second call site, widening the doc/code mismatch rather than narrowing it.

Recommendation: either fix both card renderers to use Shape::RADIUS_MD per the documented table, or update the Border table in docs/ux-design-patterns.md if RADIUS_LG is genuinely the intended token for onboarding cards. Either way, doc and code should agree.

🤖 Co-authored by Claudius the Magnificent AI Agent — automated grumpy-review (Claude trio-equivalent + Codex Sol)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Both card types now use RADIUS_MD, matching ux-design-patterns.md's Border table instead of perpetuating the RADIUS_LG mismatch. Fixed in cd20b20c.

🤖 Co-authored by Claudius the Magnificent AI Agent

Comment thread src/ui/welcome_screen.rs Outdated
.shadow(Shadow::small())
.inner_margin(Spacing::MD)
.show(ui, |ui| {
ui.set_min_size(Vec2::new(170.0, 100.0));

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟢 Card styling bypasses design tokens with pixel literals

The new card implementation introduces hard-coded dimensions (170.0, 100.0), typography sizes (24.0, 14.0, 11.0), spacing (5.0, 6.0), and stroke widths rather than the semantic Spacing/Typography/Shape tokens that docs/ux-design-patterns.md calls for — and repeats the card-width formula already used by render_getting_started_section.

The duplicated, approximate border-width assumption baked into that formula has already drifted from the selected card's actual (thicker) stroke — see the clipping finding above for the concrete consequence of that drift.

Recommendation: define shared welcome-card geometry constants (or reuse existing tokens), replace the pixel literals with Spacing/Typography/Shape tokens, and share the row-width/layout helper between the role and action card groups.

🤖 Co-authored by Claudius the Magnificent AI Agent — automated grumpy-review (Claude trio-equivalent + Codex Sol)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Replaced the pixel-literal card styling with design tokens and named geometry. Fixed in cd20b20c.

🤖 Co-authored by Claudius the Magnificent AI Agent

Comment thread src/ui/welcome_screen.rs Outdated
ui.spacing_mut().item_spacing.x = card_spacing;

for option in [UserRole::Everyday, UserRole::Power, UserRole::Developer] {
if self.render_role_card(ui, dark_mode, option, role == option) {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟢 Selecting a new role paints that card's selected-highlight one frame late

if self.render_role_card(ui, dark_mode, option, role == option) { role = option; } — the selected argument passed into render_role_card for the clicked iteration is evaluated from the pre-click role value, so the just-clicked card paints with its old (unselected) fill/stroke for the frame in which the click registers. The highlighted border only appears on the next repaint.

In practice that repaint is requested immediately via ui.ctx().request_repaint(), so the delay is a single, imperceptible frame at normal frame rates. Flagging it only because it is a genuine behavior difference from the removed ui.radio_value(), whose internal checked state is set and read back within the same paint call.

Recommendation: low priority given the imperceptible delay. If this code is ever revisited, resolve clicks first and render selected from the post-click role for exact same-frame parity with the old widget.

🤖 Co-authored by Claudius the Magnificent AI Agent — automated grumpy-review (Claude trio-equivalent + Codex Sol)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Selection painting is now deferred so the highlight updates in the same click frame instead of one frame late. Fixed in cd20b20c.

🤖 Co-authored by Claudius the Magnificent AI Agent

use egui_kittest::kittest::{NodeT, Queryable};

#[test]
fn welcome_role_cards_expose_radio_accessibility_state() {

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟢 No test locks in the new always-visible three-description layout

Credit where due: this new test (added in 7beec7be) now pins the radio role and selected/Toggled state for all three cards simultaneously — which covers the selected-highlight half of what was originally flagged here.

What remains unasserted is the description text. This PR changed the role selector from "show only the selected role's description below the row" to "show all three roles' descriptions on their own cards, always" — but no test queries role.description() for any card. The PR description's "each card shows its own description inline" claim (and the CHANGELOG's "easier to compare at a glance") is therefore verified by nothing; a future regression that hides the descriptions again would sail through the suite green.

Recommendation: extend this test (or add a sibling) with an AccessKit-level assertion — e.g. harness.get_by_label(role.description()) exists for all three roles simultaneously — to pin down the always-visible-description behavior the CHANGELOG advertises.

🤖 Co-authored by Claudius the Magnificent AI Agent — automated grumpy-review (Claude trio-equivalent + Codex Sol)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Added kittest coverage for focus state, the narrow-layout fallback, the description text, and same-frame selection. Fixed in cd20b20c.

🤖 Co-authored by Claudius the Magnificent AI Agent

Comment thread CHANGELOG.md Outdated

- **Welcome screen's experience-level picker is now three cards**: it matches
the Create Wallet / Import Wallet / Just Explore cards below it, with an
icon and a short description on each, and a highlighted border on the one

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

🟢 Wording implies the sibling action cards have icons, but they don't

The entry reads: "it matches the Create Wallet / Import Wallet / Just Explore cards below it, with an icon and a short description on each".

Verified by reading render_action_card (unmodified by this PR): those three cards render only a title and a description — no icon glyph. Read literally, the sentence groups "matches the ... cards below it" with "with an icon and a short description on each", which a reader can reasonably parse as claiming the referenced cards also carry icons. The two rows match in general chrome (Frame/border/shadow/padding), not in content composition.

Recommendation: reword to something unambiguous, e.g.: "now three cards, matching the visual style of the Create Wallet / Import Wallet / Just Explore cards below; each role card adds an icon and a short description, and the one you're on gets a highlighted border."

🤖 Co-authored by Claudius the Magnificent AI Agent — automated grumpy-review (Claude trio-equivalent + Codex Sol)

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Corrected the CHANGELOG wording — it no longer implies the sibling action cards have icons. Fixed in cd20b20c.

🤖 Co-authored by Claudius the Magnificent AI Agent

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex + Sonnet

At exact head 7beec7b, the latest commit correctly restores radio-button accessibility semantics on the welcome-screen experience-level cards. src/ui/welcome_screen.rs:202-208 now calls response.widget_info(|| WidgetInfo::selected(WidgetType::RadioButton, true, selected, role.label())) after .interact(egui::Sense::click()), which I independently confirmed against the vendored egui 0.35.0 source matches WidgetInfo::selected's real signature (typ, enabled, selected, label) at egui-0.35.0/src/data/output.rs:643, and WidgetType::RadioButton maps to accesskit Role::RadioButton at egui-0.35.0/src/response.rs:924. The new kittest welcome_role_cards_expose_radio_accessibility_state queries all three cards by Role::RadioButton and asserts correct Toggled state (Default view=False, Expert view=True matching the app's default UserRole, Developer view=False), and the pre-existing persistence test now activates the control via get_by_role_and_label(Role::RadioButton, ...) instead of a plain label query, which only proves the a11y role wiring is real. I ran cargo test --test kittest welcome --all-features and all 3 welcome_screen tests pass; cargo clippy --all-features --all-targets -- -D warnings and a full cargo build also pass cleanly. No cumulative concerns remain in the current-PR range (3b32bed..7beec7b); the one remaining reviewer note (duplicated card-sizing magic numbers) is a low-confidence suggestion/nitpick, not a defect, and does not block.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (failed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — general (failed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — general (completed), gpt-5.6-sol — rust-quality (completed)
  • Verifier: claude-sonnet-5 — final-verifier
  • Sonnet reviewers: claude-sonnet-5 — general (failed), claude-sonnet-5 — rust-quality (failed), claude-sonnet-5 — general (failed), claude-sonnet-5 — rust-quality (failed), claude-sonnet-5 — general (completed), claude-sonnet-5 — rust-quality (completed), claude-sonnet-5 — general (completed), claude-sonnet-5 — rust-quality (completed)

Adds visible keyboard-focus rings (retaining AccessKit radio state), makes
both card rows adaptive at narrow widths instead of clipping, derives
layout counts from the card arrays instead of hardcoded constants, unifies
card corner radius on RADIUS_MD, replaces pixel literals with design
tokens/named geometry, defers painting so selection updates land in the
same click frame, and adds focus/narrow-layout/description/same-frame
kittest coverage. Also corrects a CHANGELOG line that implied action cards
carry icons they don't.

Addresses PR #903 review comments (human reviewer).

Co-Authored-By: Codex Sol <noreply@openai.com>

@thepastaclaw thepastaclaw left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

Final validation — Codex + Sonnet

Independent verification at head cd20b20 confirms all nine prior findings are genuinely fixed: role cards now expose RadioButton widget_info, keyboard focus paints a distinct stroke, layout adapts to narrow widths via a generic render_adaptive_card_rows helper driven by cards.len() (no hardcoded 3), styling uses Shape/Spacing/Typography tokens, selection paints in the same click frame, all three descriptions are always visible, and the CHANGELOG no longer implies action cards have icons. All six welcome_screen kittest tests pass locally. No new in-scope defects found; the only remaining gap (action-card accessibility semantics) predates this PR and is correctly tracked out of scope.

Review provenance

  • Codex reviewers: gpt-5.6-sol — general (failed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — general (failed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — general (failed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — general (failed), gpt-5.6-sol — rust-quality (failed), gpt-5.6-sol — general (completed), gpt-5.6-sol — rust-quality (completed)
  • Verifier: claude-sonnet-5 — final-verifier
  • Sonnet reviewers: claude-sonnet-5 — general (failed), claude-sonnet-5 — rust-quality (failed), claude-sonnet-5 — general (completed), claude-sonnet-5 — rust-quality (completed)

@lklimek
lklimek merged commit 5b82aff into v1.0-dev Jul 18, 2026
5 checks passed
@lklimek
lklimek deleted the feat/welcome-role-cards branch July 18, 2026 09:19
orchardpaytl pushed a commit to orchardpaytl/orchardpay that referenced this pull request Jul 21, 2026
…#903, dashpay#904)

Pulled in 4 new upstream commits since the last sync (3b32bed):

- dashpay#903 feat(ui): redesign welcome-screen experience-level selector as
  cards, with radio-button accessibility semantics. Only real conflict
  in this merge: tests/kittest/welcome_screen.rs, where both sides
  added tests — kept our create_wallet_path_shows_and_clears_onboarding_stepper
  test and pulled in upstream's 4 new card-accessibility tests
  (rebranded dash_evo_tool:: -> orchardpay:: in the process).
- dashpay#904 fix(wallet): surface DAPI endpoint exhaustion — pins the exact
  Sdk generation a backend task ran against for error classification,
  fixing errors misattributed to a since-reloaded SDK. Touches
  backend_task/mod.rs, which our own pub mod orchardpay; addition also
  lives in; merged cleanly with no conflict.
- dashpay#898 ci: allow manual test/clippy runs, cover rust-toolchain.toml.
- dashpay#899 docs: record the local-vs-CI split in CLAUDE.md. Also corrects
  local fmt guidance from `cargo +nightly fmt` to plain `cargo fmt
  --all` (CI checks against the rust-toolchain.toml-pinned compiler,
  not nightly) — worth adopting going forward. One rebrand miss caught
  in the merged prose (`--bin dash-evo-tool` -> `--bin orchardpay`).

All other files (CLAUDE.md, src/app.rs, src/backend_task/error.rs,
src/model/user_role.rs, src/ui/dpns/dpns_contested_names_screen.rs,
src/ui/welcome_screen.rs) auto-merged cleanly — none needed manual
reconciliation beyond the one branding fix above.

Verified: cargo build (both --all-features and default features),
clippy -D warnings, cargo fmt --all -- --check, and the full test
suite all green.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

3 participants