Skip to content

ui: extract shared sign-policy option row and fix doubled card border (#403)#418

Merged
kwsantiago merged 1 commit into
mainfrom
sign-policy-option-row
Jul 16, 2026
Merged

ui: extract shared sign-policy option row and fix doubled card border (#403)#418
kwsantiago merged 1 commit into
mainfrom
sign-policy-option-row

Conversation

@wksantiago

@wksantiago wksantiago commented Jul 15, 2026

Copy link
Copy Markdown
Contributor

Summary by CodeRabbit

  • New Features

    • Added consistent sign-policy selection rows with radio buttons, titles, descriptions, and selection styling.
    • Standardized sign-policy choices across onboarding and sign-policy settings screens.
    • Added configurable card borders for greater UI flexibility.
  • UI Improvements

    • Improved visual consistency and accessibility for policy selection controls.

@coderabbitai

coderabbitai Bot commented Jul 15, 2026

Copy link
Copy Markdown

Review Change Stack

Warning

Review limit reached

@kwsantiago, you've reached your PR review limit, so we couldn't start this review.

Next review available in: 13 minutes

Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available.
You're only billed for reviews past your plan's rate limits ($0.25/file).

How can I continue?

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 reviews.

How do review limits work?

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

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, additional reviews become available more gradually as earlier reviews age out of the rolling window.

Please refer docs for additional details.

Review details
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 0596eabb-aa60-4bc3-bd29-3e7b00efc01d

📥 Commits

Reviewing files that changed from the base of the PR and between 1a97874 and 84f2b8d.

📒 Files selected for processing (4)
  • app/src/main/kotlin/io/privkey/keep/OnboardingScreen.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/SignPolicyScreen.kt
  • app/src/main/kotlin/io/privkey/keep/ui/components/KeepCard.kt
  • app/src/main/kotlin/io/privkey/keep/ui/components/SignPolicyOptionRow.kt

Walkthrough

Sign-policy cards are consolidated into a shared SignPolicyOptionRow composable. Both onboarding and NIP-55 screens use it, while KeepCard now accepts an explicit border configuration.

Changes

Sign-policy UI consolidation

Layer / File(s) Summary
Shared policy option component
app/src/main/kotlin/io/privkey/keep/ui/components/SignPolicyOptionRow.kt, app/src/main/kotlin/io/privkey/keep/ui/components/KeepCard.kt
Adds a selectable policy row with radio-button semantics and makes card borders configurable.
Onboarding integration
app/src/main/kotlin/io/privkey/keep/OnboardingScreen.kt
Replaces the local policy-option composable with SignPolicyOptionRow while preserving selection state updates.
NIP-55 integration
app/src/main/kotlin/io/privkey/keep/nip55/SignPolicyScreen.kt
Uses the shared policy row and retains asynchronous global policy persistence.

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

Sequence Diagram(s)

sequenceDiagram
  participant User
  participant OnboardingScreen
  participant SignPolicyScreen
  participant SignPolicyOptionRow
  participant KeepCard
  User->>OnboardingScreen: select policy
  OnboardingScreen->>SignPolicyOptionRow: provide selected policy state
  User->>SignPolicyScreen: select policy
  SignPolicyScreen->>SignPolicyOptionRow: provide selected policy state
  SignPolicyOptionRow->>KeepCard: render selectable bordered card
Loading

Possibly related issues

Possibly related PRs

Suggested reviewers: kwsantiago

Poem

A bunny hops through cards so bright,
One shared row makes choices right.
Borders gleam and radios sing,
Onboarding blooms with NIP-55 spring.
Hop, hop—clean UI takes flight!

🚥 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 accurately summarizes the main changes: extracting a shared sign-policy option row and adjusting card borders.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
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 unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch sign-policy-option-row

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.

@wksantiago wksantiago self-assigned this Jul 15, 2026
@wksantiago wksantiago linked an issue Jul 15, 2026 that may be closed by this pull request

@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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/src/main/kotlin/io/privkey/keep/nip55/SignPolicyScreen.kt (1)

63-71: 🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Prevent race conditions when persisting the global policy.

Launching multiple uncoordinated coroutines on Dispatchers.IO when the user rapidly taps different options can lead to a race condition. The asynchronous writes might execute out of order, resulting in the persisted global policy being out of sync with the UI's selectedPolicy.

To guarantee data integrity, cancel the previous save job before starting a new one.

🔒 Proposed fix using job cancellation

First, declare a reference to track the active job at the top of the composable:

+import kotlinx.coroutines.Job
...
     var selectedPolicy by remember { mutableStateOf(SignPolicy.MANUAL) }
     var userInteracted by remember { mutableStateOf(false) }
     val coroutineScope = rememberCoroutineScope()
+    var saveJob by remember { mutableStateOf<Job?>(null) }

Then, update the onSelected callback to cancel any pending write:

                     onSelected = {
                         userInteracted = true
                         selectedPolicy = policy
+                        saveJob?.cancel()
-                        coroutineScope.launch {
+                        saveJob = coroutineScope.launch {
                             withContext(Dispatchers.IO) {
                                 signPolicyStore.setGlobalPolicy(policy)
                             }
                         }
                     }
🤖 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 `@app/src/main/kotlin/io/privkey/keep/nip55/SignPolicyScreen.kt` around lines
63 - 71, Track the active persistence coroutine in SignPolicyScreen and cancel
it before launching a new save in the onSelected callback. Update the job
reference to the newly launched coroutine while preserving the existing
IO-backed signPolicyStore.setGlobalPolicy(policy) operation, ensuring rapid
selections persist in order.
🧹 Nitpick comments (2)
app/src/main/kotlin/io/privkey/keep/ui/components/SignPolicyOptionRow.kt (1)

29-35: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Clip the ripple effect to match the card's rounded corners.

Applying Modifier.selectable outside the Card means its ripple effect (indication) is drawn on the rectangular layout bounds, ignoring the card's rounded corners. To fix this visual glitch, clip the modifier to the card's shape before applying the selectable behavior.

♻️ Proposed fix
+import androidx.compose.material3.CardDefaults
+import androidx.compose.ui.draw.clip
...
     KeepCard(
-        modifier = modifier.selectable(
+        modifier = modifier
+            .clip(CardDefaults.shape)
+            .selectable(
             selected = isSelected,
             onClick = onSelected,
             role = Role.RadioButton
         ),
         border = BorderStroke(Dimens.cardBorderWidth, borderColor)
🤖 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 `@app/src/main/kotlin/io/privkey/keep/ui/components/SignPolicyOptionRow.kt`
around lines 29 - 35, Update the modifier chain on KeepCard in
SignPolicyOptionRow so the card shape clips its bounds before selectable applies
the ripple indication. Reuse the card’s existing rounded shape for the clip,
preserving the current selection, click, and border behavior.
app/src/main/kotlin/io/privkey/keep/OnboardingScreen.kt (1)

105-114: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Add selectableGroup() for proper accessibility grouping.

Both screens display a list of radio-button options, but the parent layouts lack Modifier.selectableGroup(). Without this modifier, screen readers will not correctly announce the options as part of a single interactive radio group (e.g., "1 of 3").

Wrap the SignPolicy.entries.forEach loops in a Column with Modifier.selectableGroup() to ensure proper accessibility semantics.

  • app/src/main/kotlin/io/privkey/keep/OnboardingScreen.kt#L105-L114: Wrap the list in a Column(Modifier.selectableGroup(), verticalArrangement = Arrangement.spacedBy(Dimens.space16))
  • app/src/main/kotlin/io/privkey/keep/nip55/SignPolicyScreen.kt#L59-L73: Wrap the list in a Column(Modifier.selectableGroup(), verticalArrangement = Arrangement.spacedBy(12.dp))
🤖 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 `@app/src/main/kotlin/io/privkey/keep/OnboardingScreen.kt` around lines 105 -
114, Wrap the SignPolicy.entries option list in OnboardingScreen.kt (lines
105-114) with a Column using Modifier.selectableGroup() and
Arrangement.spacedBy(Dimens.space16). Apply the same change to
SignPolicyScreen.kt (lines 59-73), using Arrangement.spacedBy(12.dp), while
preserving each SignPolicyOptionRow and its selection behavior.
🤖 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.

Outside diff comments:
In `@app/src/main/kotlin/io/privkey/keep/nip55/SignPolicyScreen.kt`:
- Around line 63-71: Track the active persistence coroutine in SignPolicyScreen
and cancel it before launching a new save in the onSelected callback. Update the
job reference to the newly launched coroutine while preserving the existing
IO-backed signPolicyStore.setGlobalPolicy(policy) operation, ensuring rapid
selections persist in order.

---

Nitpick comments:
In `@app/src/main/kotlin/io/privkey/keep/OnboardingScreen.kt`:
- Around line 105-114: Wrap the SignPolicy.entries option list in
OnboardingScreen.kt (lines 105-114) with a Column using
Modifier.selectableGroup() and Arrangement.spacedBy(Dimens.space16). Apply the
same change to SignPolicyScreen.kt (lines 59-73), using
Arrangement.spacedBy(12.dp), while preserving each SignPolicyOptionRow and its
selection behavior.

In `@app/src/main/kotlin/io/privkey/keep/ui/components/SignPolicyOptionRow.kt`:
- Around line 29-35: Update the modifier chain on KeepCard in
SignPolicyOptionRow so the card shape clips its bounds before selectable applies
the ripple indication. Reuse the card’s existing rounded shape for the clip,
preserving the current selection, click, and border behavior.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 3b9d844e-6d54-41f5-8f25-d000e6fb2cb7

📥 Commits

Reviewing files that changed from the base of the PR and between 1fa66e9 and 1a97874.

📒 Files selected for processing (4)
  • app/src/main/kotlin/io/privkey/keep/OnboardingScreen.kt
  • app/src/main/kotlin/io/privkey/keep/nip55/SignPolicyScreen.kt
  • app/src/main/kotlin/io/privkey/keep/ui/components/KeepCard.kt
  • app/src/main/kotlin/io/privkey/keep/ui/components/SignPolicyOptionRow.kt

@wksantiago wksantiago requested a review from kwsantiago July 15, 2026 17:20
@kwsantiago kwsantiago force-pushed the sign-policy-option-row branch from 1a97874 to 84f2b8d Compare July 16, 2026 02:39
@kwsantiago kwsantiago merged commit 4626e08 into main Jul 16, 2026
4 checks passed
@kwsantiago kwsantiago deleted the sign-policy-option-row branch July 16, 2026 02: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.

Extract shared sign-policy option row and fix doubled card border

2 participants