From 34e6b21416f1a87a082282e7c21da7dc09ec747b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 10:40:43 +0000 Subject: [PATCH 1/6] Initial plan From ba0ea51e4d763ae5e6bcb11e1290599d75206740 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 10:43:25 +0000 Subject: [PATCH 2/6] spec: add OpenSpec artifacts for fix-passcode-keypad-missing-digit Co-authored-by: cakesoft-shashank <50690016+cakesoft-shashank@users.noreply.github.com> --- .../.openspec.yaml | 2 + .../design.md | 50 +++++++++++++++++++ .../proposal.md | 31 ++++++++++++ .../specs/passcode-keypad-rendering/spec.md | 20 ++++++++ .../tasks.md | 12 +++++ 5 files changed, 115 insertions(+) create mode 100644 openspec/changes/fix-passcode-keypad-missing-digit/.openspec.yaml create mode 100644 openspec/changes/fix-passcode-keypad-missing-digit/design.md create mode 100644 openspec/changes/fix-passcode-keypad-missing-digit/proposal.md create mode 100644 openspec/changes/fix-passcode-keypad-missing-digit/specs/passcode-keypad-rendering/spec.md create mode 100644 openspec/changes/fix-passcode-keypad-missing-digit/tasks.md diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/.openspec.yaml b/openspec/changes/fix-passcode-keypad-missing-digit/.openspec.yaml new file mode 100644 index 000000000..81cd71fe0 --- /dev/null +++ b/openspec/changes/fix-passcode-keypad-missing-digit/.openspec.yaml @@ -0,0 +1,2 @@ +schema: spec-driven +created: 2026-05-11 diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/design.md b/openspec/changes/fix-passcode-keypad-missing-digit/design.md new file mode 100644 index 000000000..20fb1b1b7 --- /dev/null +++ b/openspec/changes/fix-passcode-keypad-missing-digit/design.md @@ -0,0 +1,50 @@ +## Context + +The login screen and passcode creation flow both render the shared `KeyPadView` / `KeyPadButton` components from `src/components/AppNumPad/`. The reported failure is intermittent: one numeric label can render late or disappear even though the keypad layout remains visible and tappable. This points to a presentation-layer issue inside the shared keypad button rather than Redux, saga, Realm, MMKV, PSBT, or hardware signer flows. + +## Goals / Non-Goals + +**Goals:** +- Make keypad digit labels render consistently on first paint in shared passcode flows. +- Keep the fix isolated to the shared keypad presentation components. +- Add focused tests that verify the shared keypad renders all digits. + +**Non-Goals:** +- Changing any Redux slice or saga; none are involved in this UI-only fix. +- Changing passcode validation, biometric authentication, or navigation. +- Adding Realm schema changes, MMKV keys, or migrations; none are needed. + +## Decisions + +1. **Stabilize the shared digit label rendering in `KeyPadButton`.** + The keypad is reused by login and passcode creation, so fixing the shared button prevents duplicate screen-level work. The implementation should prefer the most direct React Native text rendering path for the digit label and keep the existing touch/animation behavior intact. + + - Alternative considered: patching layout in `Login.tsx` only. Rejected because `CreatePin.tsx` uses the same shared keypad and could retain the bug. + - Alternative considered: changing passcode state timing or throttling. Rejected because the screenshot shows a display problem before any input interaction. + +2. **Cover the regression with a focused component test.** + A keypad-level test can assert that digits `0` through `9` are present without coupling the test to login business logic. + +3. **Avoid store and persistence changes.** + Redux slices/sagas involved: none. Realm schema changes: none. MMKV additions: none. Migration in `src/store/migrations.ts`: not required. + +## Risks / Trade-offs + +- **[Risk]** Replacing the label rendering path could slightly change keypad typography. + **Mitigation:** Keep the existing font size, line height, and alignment so the visual change stays minimal. + +- **[Risk]** Tests may need mocks for shared animated/themed wrappers. + **Mitigation:** Reuse the repository’s current Jest setup and keep the assertion focused on rendered digit labels. + +## Migration Plan + +No data migration or rollout sequencing is required. The change is a local UI rendering fix that can be rolled back by reverting the shared keypad component if needed. + +## Open Questions + +- None at this time. + +## Affected Files + +- Modified: `src/components/AppNumPad/KeyPadButton.tsx` +- Modified or added test: keypad-focused test file under the existing Jest test structure diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/proposal.md b/openspec/changes/fix-passcode-keypad-missing-digit/proposal.md new file mode 100644 index 000000000..11e223711 --- /dev/null +++ b/openspec/changes/fix-passcode-keypad-missing-digit/proposal.md @@ -0,0 +1,31 @@ +## Why + +The passcode keypad on the login screen intermittently renders with a missing digit, which blocks or delays passcode entry for returning users. This needs to be fixed now because the issue affects a core unlock flow on both mainnet and testnet environments. + +## What Changes + +- Stabilize the passcode keypad digit rendering on the login and passcode creation flows so all digits remain visible as soon as the keypad appears. +- Add focused validation around the keypad digit labels to guard against regressions in the shared keypad component. +- Keep the change limited to keypad presentation behavior without altering passcode verification, storage, or signer flows. + +## Capabilities + +### New Capabilities +- `passcode-keypad-rendering`: Ensures the shared passcode keypad consistently displays all numeric keys in authentication flows. + +### Modified Capabilities +- None. + +## Impact + +- Affected code: `src/components/AppNumPad/*`, login/passcode screens that render the shared keypad, and focused tests covering keypad labels. +- APIs/dependencies: No external API or dependency changes. +- Hardware signer compatibility: No impact; this change only affects local passcode UI rendering. +- Subscription gating: No subscription tier impact. +- Security/privacy impact: No key material, storage, or network behavior changes; the update is limited to local UI rendering. + +## Non-goals + +- Changing passcode length, passcode validation, or biometric authentication behavior. +- Changing wallet, vault, signer, or network connectivity flows. +- Introducing new theming, navigation, or subscription behavior unrelated to keypad visibility. diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/specs/passcode-keypad-rendering/spec.md b/openspec/changes/fix-passcode-keypad-missing-digit/specs/passcode-keypad-rendering/spec.md new file mode 100644 index 000000000..17362835e --- /dev/null +++ b/openspec/changes/fix-passcode-keypad-missing-digit/specs/passcode-keypad-rendering/spec.md @@ -0,0 +1,20 @@ +## ADDED Requirements + +### Requirement: Shared passcode keypad renders all numeric keys +The system SHALL render all numeric keys from `0` through `9` when the shared passcode keypad is shown in authentication flows that use `KeyPadView`. + +#### Scenario: Login keypad loads with every digit visible +- **GIVEN** the user opens the login screen on mainnet or testnet +- **WHEN** the shared passcode keypad is rendered +- **THEN** numeric keys `1` through `9` and `0` MUST all be visible without waiting for a delayed re-render + +#### Scenario: Passcode creation keypad reuses the same stable rendering +- **GIVEN** the user opens the passcode creation flow +- **WHEN** the shared passcode keypad is rendered +- **THEN** numeric keys `1` through `9` and `0` MUST all be visible in the initial keypad layout + +#### Scenario: Rendering fix does not change passcode controls +- **GIVEN** an authentication flow renders the shared passcode keypad +- **WHEN** the keypad is displayed +- **THEN** the delete control MUST remain available +- **AND** the keypad MUST continue to emit the same numeric key values when pressed diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/tasks.md b/openspec/changes/fix-passcode-keypad-missing-digit/tasks.md new file mode 100644 index 000000000..4e251ffe1 --- /dev/null +++ b/openspec/changes/fix-passcode-keypad-missing-digit/tasks.md @@ -0,0 +1,12 @@ +## 1. UI Components + +- [ ] 1.1 Inspect the shared keypad button rendering path and apply the smallest fix that keeps all numeric labels visible on initial render. + +## 2. Tests + +- [ ] 2.1 Add or update a focused Jest test that verifies the shared keypad renders digits `0` through `9`. + +## 3. Validation + +- [ ] 3.1 Install project dependencies and run focused validation for the keypad component and related linting. +- [ ] 3.2 Capture a screenshot or equivalent UI evidence showing the keypad digits render after the fix. From 6b09d21d7df758babf50d374e141bdc1d90cc128 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 10:53:28 +0000 Subject: [PATCH 3/6] fix: stabilize passcode keypad digit rendering Agent-Logs-Url: https://github.com/KeeperCommunity/bitcoin-keeper/sessions/5519a8b0-c4ab-4e94-936a-32a5a89d8e41 --- .../tasks.md | 8 ++-- src/components/AppNumPad/KeyPadButton.tsx | 14 +++--- tests/components/KeyPadView.test.tsx | 45 +++++++++++++++++++ 3 files changed, 58 insertions(+), 9 deletions(-) create mode 100644 tests/components/KeyPadView.test.tsx diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/tasks.md b/openspec/changes/fix-passcode-keypad-missing-digit/tasks.md index 4e251ffe1..c7e2dcab8 100644 --- a/openspec/changes/fix-passcode-keypad-missing-digit/tasks.md +++ b/openspec/changes/fix-passcode-keypad-missing-digit/tasks.md @@ -1,12 +1,12 @@ ## 1. UI Components -- [ ] 1.1 Inspect the shared keypad button rendering path and apply the smallest fix that keeps all numeric labels visible on initial render. +- [x] 1.1 Inspect the shared keypad button rendering path and apply the smallest fix that keeps all numeric labels visible on initial render. ## 2. Tests -- [ ] 2.1 Add or update a focused Jest test that verifies the shared keypad renders digits `0` through `9`. +- [x] 2.1 Add or update a focused Jest test that verifies the shared keypad renders digits `0` through `9`. ## 3. Validation -- [ ] 3.1 Install project dependencies and run focused validation for the keypad component and related linting. -- [ ] 3.2 Capture a screenshot or equivalent UI evidence showing the keypad digits render after the fix. +- [x] 3.1 Install project dependencies and run focused validation for the keypad component and related linting. +- [x] 3.2 Capture a screenshot or equivalent UI evidence showing the keypad digits render after the fix. diff --git a/src/components/AppNumPad/KeyPadButton.tsx b/src/components/AppNumPad/KeyPadButton.tsx index decf9e0b2..1a9d2e046 100644 --- a/src/components/AppNumPad/KeyPadButton.tsx +++ b/src/components/AppNumPad/KeyPadButton.tsx @@ -1,6 +1,5 @@ -import { StyleSheet, TouchableOpacity, Animated } from 'react-native'; +import { StyleSheet, TouchableOpacity, Animated, Text as NativeText } from 'react-native'; import React, { useState } from 'react'; -import Text from 'src/components/KeeperText'; import ScaleSpring from '../Animations/ScaleSpring'; import ThemedColor from '../ThemedColor/ThemedColor'; @@ -11,7 +10,7 @@ export interface Props { bubbleEffect?: boolean; } -const KeyPadButton: React.FC = ({ title, onPressNumber, keyColor, bubbleEffect }: Props) => { +function KeyPadButton({ title, onPressNumber, keyColor, bubbleEffect }: Props) { const [pressed, setPressed] = useState(false); const keyPad_colors = ThemedColor({ name: 'keyPad_colors' }); @@ -46,12 +45,16 @@ const KeyPadButton: React.FC = ({ title, onPressNumber, keyColor, bubbleE /> )} - + {title} - + ); +} + +KeyPadButton.defaultProps = { + bubbleEffect: false, }; const styles = StyleSheet.create({ @@ -64,6 +67,7 @@ const styles = StyleSheet.create({ keyPadElementText: { fontSize: 25, lineHeight: 30, + textAlign: 'center', zIndex: 1, opacity: 1, }, diff --git a/tests/components/KeyPadView.test.tsx b/tests/components/KeyPadView.test.tsx new file mode 100644 index 000000000..cbbdbc845 --- /dev/null +++ b/tests/components/KeyPadView.test.tsx @@ -0,0 +1,45 @@ +import React from 'react'; +import { fireEvent, render } from '@testing-library/react-native'; +import KeyPadView from 'src/components/AppNumPad/KeyPadView'; + +jest.mock('src/components/Animations/ScaleSpring', () => ({ children }: React.PropsWithChildren) => children); +jest.mock('src/components/ThemedColor/ThemedColor', () => jest.fn(() => '#ffffff')); + +describe('KeyPadView', () => { + it('renders digits 0 through 9', () => { + const { getByText, getByTestId } = render( + } + /> + ); + + ['1', '2', '3', '4', '5', '6', '7', '8', '9', '0'].forEach((digit) => { + expect(getByText(digit)).toBeTruthy(); + }); + expect(getByTestId('btn_clear')).toBeTruthy(); + }); + + it('emits numeric values and delete presses', () => { + const onPressNumber = jest.fn(); + const onDeletePressed = jest.fn(); + const { getByTestId } = render( + } + /> + ); + + fireEvent.press(getByTestId('key_1')); + fireEvent.press(getByTestId('key_0')); + fireEvent.press(getByTestId('btn_clear')); + + expect(onPressNumber).toHaveBeenNthCalledWith(1, '1'); + expect(onPressNumber).toHaveBeenNthCalledWith(2, '0'); + expect(onDeletePressed).toHaveBeenCalledTimes(1); + }); +}); From 13024a5fd262bbb457d0de0091b5f112157030dc Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 10:53:33 +0000 Subject: [PATCH 4/6] chore: archive fix-passcode-keypad-missing-digit --- .../.openspec.yaml | 0 .../design.md | 0 .../proposal.md | 0 .../specs/passcode-keypad-rendering/spec.md | 0 .../tasks.md | 0 .../specs/passcode-keypad-rendering/spec.md | 24 +++++++++++++++++++ 6 files changed, 24 insertions(+) rename openspec/changes/{fix-passcode-keypad-missing-digit => archive/2026-05-11-fix-passcode-keypad-missing-digit}/.openspec.yaml (100%) rename openspec/changes/{fix-passcode-keypad-missing-digit => archive/2026-05-11-fix-passcode-keypad-missing-digit}/design.md (100%) rename openspec/changes/{fix-passcode-keypad-missing-digit => archive/2026-05-11-fix-passcode-keypad-missing-digit}/proposal.md (100%) rename openspec/changes/{fix-passcode-keypad-missing-digit => archive/2026-05-11-fix-passcode-keypad-missing-digit}/specs/passcode-keypad-rendering/spec.md (100%) rename openspec/changes/{fix-passcode-keypad-missing-digit => archive/2026-05-11-fix-passcode-keypad-missing-digit}/tasks.md (100%) create mode 100644 openspec/specs/passcode-keypad-rendering/spec.md diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/.openspec.yaml b/openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/.openspec.yaml similarity index 100% rename from openspec/changes/fix-passcode-keypad-missing-digit/.openspec.yaml rename to openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/.openspec.yaml diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/design.md b/openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/design.md similarity index 100% rename from openspec/changes/fix-passcode-keypad-missing-digit/design.md rename to openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/design.md diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/proposal.md b/openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/proposal.md similarity index 100% rename from openspec/changes/fix-passcode-keypad-missing-digit/proposal.md rename to openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/proposal.md diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/specs/passcode-keypad-rendering/spec.md b/openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/specs/passcode-keypad-rendering/spec.md similarity index 100% rename from openspec/changes/fix-passcode-keypad-missing-digit/specs/passcode-keypad-rendering/spec.md rename to openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/specs/passcode-keypad-rendering/spec.md diff --git a/openspec/changes/fix-passcode-keypad-missing-digit/tasks.md b/openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/tasks.md similarity index 100% rename from openspec/changes/fix-passcode-keypad-missing-digit/tasks.md rename to openspec/changes/archive/2026-05-11-fix-passcode-keypad-missing-digit/tasks.md diff --git a/openspec/specs/passcode-keypad-rendering/spec.md b/openspec/specs/passcode-keypad-rendering/spec.md new file mode 100644 index 000000000..8253e41a8 --- /dev/null +++ b/openspec/specs/passcode-keypad-rendering/spec.md @@ -0,0 +1,24 @@ +# passcode-keypad-rendering Specification + +## Purpose +TBD - created by archiving change fix-passcode-keypad-missing-digit. Update Purpose after archive. +## Requirements +### Requirement: Shared passcode keypad renders all numeric keys +The system SHALL render all numeric keys from `0` through `9` when the shared passcode keypad is shown in authentication flows that use `KeyPadView`. + +#### Scenario: Login keypad loads with every digit visible +- **GIVEN** the user opens the login screen on mainnet or testnet +- **WHEN** the shared passcode keypad is rendered +- **THEN** numeric keys `1` through `9` and `0` MUST all be visible without waiting for a delayed re-render + +#### Scenario: Passcode creation keypad reuses the same stable rendering +- **GIVEN** the user opens the passcode creation flow +- **WHEN** the shared passcode keypad is rendered +- **THEN** numeric keys `1` through `9` and `0` MUST all be visible in the initial keypad layout + +#### Scenario: Rendering fix does not change passcode controls +- **GIVEN** an authentication flow renders the shared passcode keypad +- **WHEN** the keypad is displayed +- **THEN** the delete control MUST remain available +- **AND** the keypad MUST continue to emit the same numeric key values when pressed + From 0cf419407f34b6a004ea9e84a1762d4f81fd14a2 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 10:56:16 +0000 Subject: [PATCH 5/6] fix: address keypad review feedback Agent-Logs-Url: https://github.com/KeeperCommunity/bitcoin-keeper/sessions/5519a8b0-c4ab-4e94-936a-32a5a89d8e41 --- openspec/specs/passcode-keypad-rendering/spec.md | 3 +-- src/components/AppNumPad/KeyPadButton.tsx | 6 +----- tests/components/KeyPadView.test.tsx | 5 +++-- 3 files changed, 5 insertions(+), 9 deletions(-) diff --git a/openspec/specs/passcode-keypad-rendering/spec.md b/openspec/specs/passcode-keypad-rendering/spec.md index 8253e41a8..8f237d794 100644 --- a/openspec/specs/passcode-keypad-rendering/spec.md +++ b/openspec/specs/passcode-keypad-rendering/spec.md @@ -1,7 +1,7 @@ # passcode-keypad-rendering Specification ## Purpose -TBD - created by archiving change fix-passcode-keypad-missing-digit. Update Purpose after archive. +Define the shared passcode keypad requirement so authentication flows always render visible numeric keys `0` through `9` and retain the existing delete control behavior. ## Requirements ### Requirement: Shared passcode keypad renders all numeric keys The system SHALL render all numeric keys from `0` through `9` when the shared passcode keypad is shown in authentication flows that use `KeyPadView`. @@ -21,4 +21,3 @@ The system SHALL render all numeric keys from `0` through `9` when the shared pa - **WHEN** the keypad is displayed - **THEN** the delete control MUST remain available - **AND** the keypad MUST continue to emit the same numeric key values when pressed - diff --git a/src/components/AppNumPad/KeyPadButton.tsx b/src/components/AppNumPad/KeyPadButton.tsx index 1a9d2e046..516cd0858 100644 --- a/src/components/AppNumPad/KeyPadButton.tsx +++ b/src/components/AppNumPad/KeyPadButton.tsx @@ -7,7 +7,7 @@ export interface Props { title: string; onPressNumber: (value: string) => void; keyColor: string; - bubbleEffect?: boolean; + bubbleEffect: boolean; } function KeyPadButton({ title, onPressNumber, keyColor, bubbleEffect }: Props) { @@ -53,10 +53,6 @@ function KeyPadButton({ title, onPressNumber, keyColor, bubbleEffect }: Props) { ); } -KeyPadButton.defaultProps = { - bubbleEffect: false, -}; - const styles = StyleSheet.create({ keyPadElementTouchable: { flex: 1, diff --git a/tests/components/KeyPadView.test.tsx b/tests/components/KeyPadView.test.tsx index cbbdbc845..f33d65c53 100644 --- a/tests/components/KeyPadView.test.tsx +++ b/tests/components/KeyPadView.test.tsx @@ -1,5 +1,6 @@ import React from 'react'; import { fireEvent, render } from '@testing-library/react-native'; +import { View } from 'react-native'; import KeyPadView from 'src/components/AppNumPad/KeyPadView'; jest.mock('src/components/Animations/ScaleSpring', () => ({ children }: React.PropsWithChildren) => children); @@ -12,7 +13,7 @@ describe('KeyPadView', () => { onPressNumber={jest.fn()} onDeletePressed={jest.fn()} bubbleEffect - ClearIcon={} + ClearIcon={} /> ); @@ -30,7 +31,7 @@ describe('KeyPadView', () => { onPressNumber={onPressNumber} onDeletePressed={onDeletePressed} bubbleEffect - ClearIcon={} + ClearIcon={} /> ); From 183512e8a9a4a26f1813aab08f4d33d9cbb909f5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Mon, 11 May 2026 10:57:46 +0000 Subject: [PATCH 6/6] fix: keep keypad button prop backward compatible Agent-Logs-Url: https://github.com/KeeperCommunity/bitcoin-keeper/sessions/5519a8b0-c4ab-4e94-936a-32a5a89d8e41 --- src/components/AppNumPad/KeyPadButton.tsx | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/src/components/AppNumPad/KeyPadButton.tsx b/src/components/AppNumPad/KeyPadButton.tsx index 516cd0858..828a277d0 100644 --- a/src/components/AppNumPad/KeyPadButton.tsx +++ b/src/components/AppNumPad/KeyPadButton.tsx @@ -7,10 +7,11 @@ export interface Props { title: string; onPressNumber: (value: string) => void; keyColor: string; - bubbleEffect: boolean; + // eslint-disable-next-line react/require-default-props + bubbleEffect?: boolean; } -function KeyPadButton({ title, onPressNumber, keyColor, bubbleEffect }: Props) { +function KeyPadButton({ title, onPressNumber, keyColor, bubbleEffect = false }: Props) { const [pressed, setPressed] = useState(false); const keyPad_colors = ThemedColor({ name: 'keyPad_colors' });