Skip to content

Fix iOS PathPicker closing/reopening while typing#78

Open
happier-bot wants to merge 1 commit intohappier-dev:devfrom
happier-bot:happier-bot/fix-mobile-path-picker-close-reopen
Open

Fix iOS PathPicker closing/reopening while typing#78
happier-bot wants to merge 1 commit intohappier-dev:devfrom
happier-bot:happier-bot/fix-mobile-path-picker-close-reopen

Conversation

@happier-bot
Copy link
Contributor

@happier-bot happier-bot commented Feb 22, 2026

Root cause

On iOS (containedModal presentation), the PathPicker screen was re-creating Stack.Screen header options on every keystroke because headerRight depended on input state (and a non-stable machine object identity). This could cause the modal to briefly dismiss/re-present, which remounted the screen and reset the TextInput value.

Fix

  • Keep header actions stable across keystrokes by:
    • reading the latest customPath from a ref inside the confirm handler
    • depending on machineHomeDir (a primitive) instead of the full machine object
    • removing the header-right disabled toggle (confirm is safe because we fall back to homeDir)

Tests

  • Added a unit test asserting headerRight remains stable across a keystroke-driven re-render.

Ran:

  • NODE_ENV=development yarn -s test:unit sources/__tests__/app/new/pick/path.presentation.test.ts sources/__tests__/app/new/pick/path.headerOptions.stability.test.tsx

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Fixed path confirmation button availability and improved home directory detection for path input fallback.
  • Tests

    • Added stability tests to ensure path picker functionality remains consistent across user interactions.

@coderabbitai
Copy link

coderabbitai bot commented Feb 22, 2026

Walkthrough

A new test file validates that header options remain stable across re-renders in PathPickerScreen, and the component implementation is refactored to use refs and derived state to maintain stable header actions and improve path resolution fallback handling.

Changes

Cohort / File(s) Summary
Path Picker Test
apps/ui/sources/__tests__/app/new/pick/path.headerOptions.stability.test.tsx
New test file that mocks React Native and routing modules to verify that headerRight function in PathPickerScreen remains stable across keystroke-driven re-renders.
Path Picker Component
apps/ui/sources/app/(app)/new/pick/path.tsx
Refactored component to use customPathRef for tracking current path value across renders, replaced machine-derived fallback logic with machineHomeDir state, simplified confirm button logic (always present), and added comments explaining header stability requirements.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately describes the main fix: stabilizing iOS PathPicker to prevent closing/reopening during typing, which is the core issue addressed.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

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

✨ Finishing Touches
  • 📝 Generate docstrings (stacked PR)
  • 📝 Generate docstrings (commit on current branch)
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

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.

@greptile-apps
Copy link
Contributor

greptile-apps bot commented Feb 22, 2026

Greptile Summary

Fixed iOS modal instability by stabilizing header options across keystrokes. The PathPicker modal was dismissing/reopening while typing because headerRight was being recreated on every render due to dependencies on customPath state and the machine object.

The fix uses a ref to capture the latest customPath value inside the confirm handler, depends on the primitive machineHomeDir instead of the full machine object, and removes the disabled toggle (since the fallback to homeDir makes confirm always safe).

A comprehensive unit test validates that headerRight maintains referential stability across re-renders.

Confidence Score: 5/5

  • This PR is safe to merge with no concerns
  • The fix correctly addresses a React Native navigation stability issue using established patterns (refs for latest values, primitive dependencies). The implementation is clean, well-documented with inline comments, and includes a comprehensive test that validates the fix. No logical errors, security issues, or unintended side effects detected.
  • No files require special attention

Important Files Changed

Filename Overview
apps/ui/sources/app/(app)/new/pick/path.tsx Stabilized header options by using ref for customPath and depending on machineHomeDir primitive instead of full machine object; removed disabled state from confirm button
apps/ui/sources/tests/app/new/pick/path.headerOptions.stability.test.tsx Added new unit test verifying headerRight remains referentially stable across keystroke-driven re-renders

Last reviewed commit: 0b2e5e8

Copy link

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

🧹 Nitpick comments (3)
apps/ui/sources/app/(app)/new/pick/path.tsx (1)

1-1: Inconsistent hook import style.

useState and useMemo are destructured from 'react', while useRef, useEffect, and useCallback are accessed via the React. namespace throughout the file. Consider making the style uniform — either destructure all used hooks on Line 1 or use React.* consistently.

Proposed fix
-import React, { useState, useMemo } from 'react';
+import React, { useState, useMemo, useRef, useEffect, useCallback } from 'react';

Then replace React.useRef, React.useEffect, React.useCallback, and React.useMemo (line 118) with the direct names.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/sources/app/`(app)/new/pick/path.tsx at line 1, The file mixes import
styles: solve by updating the import line to destructure all hooks used (add
useRef, useEffect, useCallback alongside useState and useMemo) and then replace
React.useRef, React.useEffect, React.useCallback and the React.useMemo usage
with the direct names useRef, useEffect, useCallback, and useMemo respectively
so all hook calls use the same destructured import style; locate usages of
React.useRef, React.useEffect, React.useCallback and the React.useMemo call
around the component (e.g., where useState/useMemo are used) and update them to
the direct identifiers.
apps/ui/sources/__tests__/app/new/pick/path.headerOptions.stability.test.tsx (2)

49-49: Untyped any in mock fixtures.

Lines 49 and 58 use any without justification. Per coding guidelines, broad as any casts are forbidden except in boundary fixtures with a one-line rationale. These are mock fixtures, so they qualify for the exception, but consider adding a brief comment or narrowing the types.

Example narrowing
-        setParams: (params: any) => ({ type: 'SET_PARAMS', payload: { params } }),
+        setParams: (params: Record<string, unknown>) => ({ type: 'SET_PARAMS', payload: { params } }),
-    return {
-        useUnistyles: () => ({ theme }),
-        StyleSheet: { create: (input: any) => (typeof input === 'function' ? input(theme) : input) },
-    };
+    return {
+        useUnistyles: () => ({ theme }),
+        StyleSheet: { create: (input: unknown) => (typeof input === 'function' ? (input as (t: typeof theme) => unknown)(theme) : input) },
+    };

Also applies to: 58-58

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/sources/__tests__/app/new/pick/path.headerOptions.stability.test.tsx`
at line 49, The mock fixture uses an untyped any for setParams (and the other
mock at line 58); either narrow the type to the real params shape (e.g., a
Params interface or unknown) on the setParams function signature, or if you
deliberately allow a broad cast in this test fixture, add a one-line
justification comment directly above the mock (e.g., "// test fixture:
simplified broad cast allowed") and keep the cast limited to the fixture only;
update the setParams declaration (and the other mock) accordingly so reviewers
can locate and verify the change.

3-3: react-test-renderer is deprecated in React 19 — the entire test suite (20 tests) uses it, and @testing-library is not yet adopted in the project.

The deprecation is soft (logs warnings in React 19, no removal yet), so a migration is a project-wide decision rather than a per-file concern. This test's use of react-test-renderer is consistent with the existing codebase pattern and does not require immediate changes.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@apps/ui/sources/__tests__/app/new/pick/path.headerOptions.stability.test.tsx`
at line 3, This test imports react-test-renderer via "import renderer, { act }
from 'react-test-renderer';" which is known to be soft-deprecated in React 19;
do not change the import or test logic here—leave the usage as-is to stay
consistent with the existing suite, but add a short inline comment above the
import noting the React 19 deprecation and that migration to `@testing-library` is
a planned project-wide effort (add a TODO/ticket reference) so reviewers know
this file was considered and intentionally left unchanged.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Nitpick comments:
In
`@apps/ui/sources/__tests__/app/new/pick/path.headerOptions.stability.test.tsx`:
- Line 49: The mock fixture uses an untyped any for setParams (and the other
mock at line 58); either narrow the type to the real params shape (e.g., a
Params interface or unknown) on the setParams function signature, or if you
deliberately allow a broad cast in this test fixture, add a one-line
justification comment directly above the mock (e.g., "// test fixture:
simplified broad cast allowed") and keep the cast limited to the fixture only;
update the setParams declaration (and the other mock) accordingly so reviewers
can locate and verify the change.
- Line 3: This test imports react-test-renderer via "import renderer, { act }
from 'react-test-renderer';" which is known to be soft-deprecated in React 19;
do not change the import or test logic here—leave the usage as-is to stay
consistent with the existing suite, but add a short inline comment above the
import noting the React 19 deprecation and that migration to `@testing-library` is
a planned project-wide effort (add a TODO/ticket reference) so reviewers know
this file was considered and intentionally left unchanged.

In `@apps/ui/sources/app/`(app)/new/pick/path.tsx:
- Line 1: The file mixes import styles: solve by updating the import line to
destructure all hooks used (add useRef, useEffect, useCallback alongside
useState and useMemo) and then replace React.useRef, React.useEffect,
React.useCallback and the React.useMemo usage with the direct names useRef,
useEffect, useCallback, and useMemo respectively so all hook calls use the same
destructured import style; locate usages of React.useRef, React.useEffect,
React.useCallback and the React.useMemo call around the component (e.g., where
useState/useMemo are used) and update them to the direct identifiers.

leeroybrun added a commit that referenced this pull request Mar 2, 2026
Ports and hardens the bot-generated PR changes against current dev worktree.

- #60: clarify default model label + prune Claude model suggestions
- #64: centralize change_title aliases + normalize across UI/CLI providers
- #71: document skipping capability probe on Codex ACP resume
- #78: stabilize iOS PathPicker header options while typing
- #83: remove synthetic stop+explain follow-up; unify stop button copy/i18n
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.

1 participant