Skip to content

feat: onboarding spotlight flows, navigation refactor & test coverage - #86

Merged
alichherawalla merged 11 commits into
mainfrom
feat/onboarding-spotlight-tests-and-flow4-fix
Mar 1, 2026
Merged

alichherawalla merged 11 commits into
mainfrom
feat/onboarding-spotlight-tests-and-flow4-fix

Conversation

@alichherawalla

Copy link
Copy Markdown
Collaborator

Summary

This PR delivers the complete onboarding spotlight system with reactive spotlights, flattens the app's navigation architecture, and adds comprehensive test coverage (175 new tests across 11 suites).

Key changes:

  • Navigation refactor: Flatten all sub-stack navigators (ChatsStack, ProjectsStack, ModelsStack, SettingsStack) into the RootStack. Tabs now point directly to screen components, eliminating nested navigation complexity and enabling SpotlightTour to work across all screens.

  • Expanded onboarding flows: Add spotlight steps 11–16 covering model picker highlight (Flow 2), voice input hint chaining (Flow 3), and full image generation guidance (Flow 4: load model → new chat → draw → settings). Includes a reactive spotlight system with shownSpotlights tracking for one-shot contextual hints.

  • Comprehensive tests: 11 new test suites spanning unit tests (tooltip content, step press handling, chat screen chaining, reactive conditions, flow validation), integration tests (full flow lifecycle, cross-flow interactions, reset behavior), and RNTL render-based tests (spotlight rendering on HomeScreen, ChatsListScreen, ChatScreen, ModelSettingsScreen, ProjectEditScreen).

  • CI & docs: Dynamic test count badge via CI, test summary table in README, updated codebase guide, cleaned up TODO.

Type of Change

  • New feature (non-breaking change that adds functionality)
  • Refactor (code change that neither fixes a bug nor adds a feature)

Screenshots / Screen Recordings

N/A — spotlight system is overlay-based and tested via automated tests. Navigation refactor is structural with no visual change.

Checklist

General

  • My code follows the project's coding style and conventions
  • I have performed a self-review of my code
  • I have added/updated comments where the logic isn't self-evident
  • My changes generate no new warnings or errors

Testing

  • Existing tests pass locally (npm test)
  • I have added tests that prove my fix is effective or my feature works

React Native Specific

  • No hardcoded pixel values — uses SPACING / TYPOGRAPHY constants from the theme
  • Styles use useThemedStyles pattern (not inline or static StyleSheet.create)
  • No unnecessary re-renders introduced

Related Issues

Follow-up: Flow 4 image model download spotlight fix will be addressed in a separate PR.

Additional Notes

  • MaybeAttachStep wrapper prevents ChatInput remounting during spotlight step chains
  • Conditional AttachStep mounting (one at a time) prevents waypoint dots/lines artifacts
  • Cross-tab navigation timing adjusted from 600ms → 800ms to allow sheet-close + tab-switch animation to complete
  • triedImageGen completion now requires actual image generation, not just download

github-actions Bot and others added 6 commits February 26, 2026 09:26
…uide

- Remove completed items from TODO (document upload, PDF support, image
  gen fix, resolved test issues) — only future work remains
- Add Testing section to README with test commands and codecov badge
- Add scripts/test-count.sh to dynamically count tests across all
  platforms (Jest, Android JUnit, iOS XCTest, Maestro E2E)
- Add test:count npm script
- Update CODEBASE_GUIDE directory structure to reflect refactored
  components (ChatInput/, ChatMessage/, checklist/, onboarding/) and
  screens (HomeScreen/, ChatScreen/, ModelsScreen/) into subdirectories
- Add @react-native-documents/picker and viewer to platform services table

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Update CI workflow to count tests across all platforms (Jest, Android
  JUnit, iOS XCTest) and publish total via schneegans/dynamic-badges-action
- Add CI status badge and dynamic test count badge to README Testing section
- Remove hardcoded test count, replace with shields.io endpoint badge
  updated automatically on every push to main
- Badge requires GIST_SECRET and TEST_BADGE_GIST_ID to be configured
  in repo settings

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Move all sub-screens (Chat, ProjectDetail, ProjectEdit, ModelSettings,
VoiceSettings, DeviceInfo, StorageSettings, SecuritySettings) from
nested tab stacks into the RootStack. Tabs now point directly to their
screen components instead of wrapping sub-stack navigators.

Key changes:
- Remove ChatsStack, ProjectsStack, ModelsStack, SettingsStack navigators
- Merge all screen params into RootStackParamList, simplify MainTabParamList
- Use CompositeNavigationProp for tab-hosted screens navigating to RootStack
- Fix SettingsScreen reset onboarding: getParent()?.dispatch() (Tab→RootStack)
- Add useFocusEffect to ModelsScreen to reset detail view on tab blur
- Collapse (not unmount) ModelsScreen chrome during detail to keep AttachSteps mounted
- Add back buttons to ChatScreen's NoModelScreen and LoadingScreen
- Move SpotlightTourProvider from MainTabs to RootStack level so all screens
  (including Chat, ProjectEdit, ModelSettings) can use useSpotlightTour()
- Update test mocks for flattened navigation structure

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Add 6 new spotlight steps (indices 11-16) and reactive spotlight system:
- Flow 2: step 11 highlights first model in picker sheet (animated border)
- Flow 3: step 12 chains to voice input hint after ChatInput spotlight
- Flow 4: steps 13-16 guide through image gen (load model, new chat, draw, settings)

Key changes:
- Add shownSpotlights store for one-shot reactive spotlight tracking
- Conditional AttachStep mounting (one at a time) to prevent waypoint dots/lines
- MaybeAttachStep wrapper avoids ChatInput remounting during step chains
- Pulsating border in ModelPickerSheet (AttachStep can't work inside Modal)
- triedImageGen completion now requires actual image generation, not just download
- 32 new unit tests covering all flows, reactive conditions, and reset behavior

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
Adds 11 test suites covering all 6 onboarding flows across 3 testing layers:

Unit tests:
- spotlightTooltips: tooltip content matches spec for all 17 steps
- handleStepPress: pending spotlight queuing, navigation, timing delays
- chatScreenSpotlight: step 3→12 chaining, chainingRef guard, AttachStep mapping
- reactiveSpotlightConditions: boolean guards for reactive steps 13-16
- onboardingFlows: step count, shape, render function validation

Integration tests:
- spotlightFlowIntegration: full lifecycle of all 6 flows, cross-flow
  interactions, reset behavior, step-to-flow mapping

RNTL render-based integration tests:
- HomeScreenSpotlight: handleStepPress renders with correct goTo calls,
  reactive imageLoad spotlight (step 13), 800ms/600ms timing
- ChatsListScreenSpotlight: reactive imageNewChat spotlight (step 14)
- ChatScreenSpotlight: pending step consumption, step 3→12 chain,
  reactive imageDraw (step 15) and imageSettings (step 16)
- ModelSettingsScreenSpotlight: pending step 6 consumption on mount
- ProjectEditScreenSpotlight: pending step 8 consumption on mount

Also fixes cross-tab navigation timing (600ms → 800ms) in handleStepPress
to allow sheet-close + tab-switch animation to complete before spotlight.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Summary of Changes

Hello, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed!

This pull request significantly enhances the new user experience by introducing a guided onboarding process with interactive spotlights and a streamlined navigation system. The refactoring simplifies the app's routing, making it more robust and easier to manage, especially for cross-screen interactions. The extensive new test suite ensures the reliability and correctness of these new features and the refactored components, providing a solid foundation for future development.

Highlights

  • Onboarding Spotlight Flows: Implemented a comprehensive onboarding spotlight system with 17 new steps across 6 flows, guiding users through key features like model download, loading, chat, image generation, settings, and project creation. This includes reactive spotlights that trigger based on user actions and app state.
  • Navigation Refactor: Flattened the application's navigation architecture by integrating all sub-stack navigators (ChatsStack, ProjectsStack, ModelsStack, SettingsStack) directly into the RootStack. Tabs now point directly to screen components, simplifying navigation and enabling the SpotlightTour to function seamlessly across all screens.
  • Extensive Test Coverage: Added 175 new tests across 11 suites, covering unit tests for tooltip content, step handling, reactive conditions, and flow validation, as well as integration tests for full flow lifecycles and cross-flow interactions. Render-based tests using React Native Testing Library (RNTL) were also introduced for key screens.
  • Developer Experience Improvements: Updated the README.md with a detailed testing section, including CI badges and a test summary table. The TODO.md file was cleaned up, and the CODEBASE_GUIDE.md was updated to reflect new dependencies and project structure.
Changelog
  • README.md
    • Updated testing section with CI badges, a test summary table, and commands for running tests.
  • TODO.md
    • Removed completed and outdated tasks related to document upload support and image generation debugging.
  • tests/integration/onboarding/spotlightFlowIntegration.test.ts
    • Added new integration tests for the full lifecycle of onboarding spotlight flows, including multi-step sequencing and reactive triggers.
  • tests/rntl/onboarding/ChatScreenSpotlight.test.tsx
    • Added new RNTL render-based tests for ChatScreen spotlight integration, verifying pending step consumption and reactive image generation spotlights.
  • tests/rntl/onboarding/ChatsListScreenSpotlight.test.tsx
    • Added new RNTL render-based tests for ChatsListScreen spotlight integration, verifying reactive image new chat spotlight and 'New' button rendering.
  • tests/rntl/onboarding/HomeScreenSpotlight.test.tsx
    • Added new RNTL render-based tests for HomeScreen spotlight integration, covering handleStepPress logic, navigation, timing, and reactive image load spotlights.
  • tests/rntl/onboarding/ModelSettingsScreenSpotlight.test.tsx
    • Added new RNTL render-based tests for ModelSettingsScreen spotlight integration, verifying pending spotlight consumption.
  • tests/rntl/onboarding/ProjectEditScreenSpotlight.test.tsx
    • Added new RNTL render-based tests for ProjectEditScreen spotlight integration, verifying pending spotlight consumption.
  • tests/rntl/screens/HomeScreen.test.tsx
    • Updated navigation calls to directly navigate to 'Chat' screen instead of nested 'ChatsTab' and 'Chat' screens.
    • Adjusted image count text to show 'image' or 'images' without the count.
  • tests/rntl/screens/ProjectDetailScreen.test.tsx
    • Updated navigation calls to directly navigate to 'Chat' screen, removing the getParent().navigate workaround.
  • tests/rntl/screens/SettingsScreen.test.tsx
    • Updated useAppStore mock to include completeChecklistStep and resetChecklist.
    • Simplified navigation to RootStack for resetting onboarding.
    • Added a test for the 'Reset Onboarding Checklist' button.
  • tests/unit/onboarding/chatScreenSpotlight.test.ts
    • Added new unit tests for ChatScreen's spotlight coordination logic, including step chaining and reactive spotlights.
  • tests/unit/onboarding/handleStepPress.test.ts
    • Added new unit tests for the handleStepPress function, verifying its behavior for various onboarding flows.
  • tests/unit/onboarding/onboardingFlows.test.ts
    • Added new unit tests for overall onboarding flow logic, including spotlight step configuration, pending state, reactive tracking, and completion criteria.
  • tests/unit/onboarding/reactiveSpotlightConditions.test.ts
    • Added new unit tests for reactive spotlight conditions, ensuring correct triggering based on app state.
  • tests/unit/onboarding/spotlightTooltips.test.ts
    • Added new unit tests to verify the content of all spotlight tooltips.
  • tests/utils/testHelpers.ts
    • Added shownSpotlights, onboardingChecklist, and checklistDismissed to the resetStores function for consistent testing.
  • docs/onboarding/ONBOARDING_FLOWS.md
    • Added new documentation detailing all onboarding checklist flows, their sequences, completion criteria, and key files involved.
  • docs/standards/CODEBASE_GUIDE.md
    • Updated dependencies list to include react-native-documents/picker and react-native-documents/viewer.
    • Reorganized and expanded the src/components and src/screens directory structure documentation.
  • ios/Podfile.lock
    • Updated RNSVG dependency.
  • jest.setup.ts
    • Added mocks for react-native-gesture-handler and react-native-spotlight-tour to support new testing infrastructure.
  • package-lock.json
    • Updated dependencies to include react-native-onboarding-checklist, react-native-spotlight-tour, and react-native-svg.
  • package.json
    • Added test:count script for counting tests across platforms.
    • Added new dependencies: react-native-onboarding-checklist, react-native-spotlight-tour, react-native-svg.
  • scripts/test-count.sh
    • Added new script to count tests across Jest, Android, iOS, and E2E platforms, and display a summary table.
  • src/components/ChatInput/index.tsx
    • Integrated AttachStep for spotlights 12 and 16, allowing conditional wrapping of the action button and the entire component.
    • Added activeSpotlight prop to manage which spotlight is active within the component.
  • src/components/checklist/ProgressBar.tsx
    • Added new ProgressBar component for displaying onboarding checklist progress with animations.
  • src/components/checklist/animations.ts
    • Added new animation hooks (useStaggeredEntrance, useCheckmark, useStrikethrough, useProgressAnimation) for checklist components.
  • src/components/checklist/index.ts
    • Exported new checklist components and hooks.
  • src/components/checklist/types.ts
    • Defined OnboardingStep and ChecklistTheme types for the onboarding checklist system.
  • src/components/checklist/useOnboardingSteps.ts
    • Added new hook useOnboardingSteps to define and track onboarding steps and their completion criteria.
    • Added useChecklistTheme for consistent styling of checklist components.
    • Added useAutoDismiss for automatically dismissing the checklist sheet.
  • src/components/index.ts
    • Exported new onboarding components (OnboardingSheet, PulsatingIcon, useOnboardingSheet) and spotlight configuration.
  • src/components/onboarding/OnboardingSheet.tsx
    • Added new OnboardingSheet component to display the onboarding checklist with progress bar and interactive steps.
  • src/components/onboarding/PulsatingIcon.tsx
    • Added new PulsatingIcon component for a visually engaging hint when the onboarding sheet is closed.
  • src/components/onboarding/index.ts
    • Exported onboarding components, spotlight configuration, and state management utilities.
  • src/components/onboarding/spotlightConfig.tsx
    • Added new spotlightConfig.tsx file, defining all 17 spotlight steps, their tooltips, and mappings to checklist IDs and tabs.
  • src/components/onboarding/spotlightState.ts
    • Added new spotlightState.ts file to manage module-level pending spotlight state for multi-step flows.
  • src/components/onboarding/useOnboardingSheet.ts
    • Added new hook useOnboardingSheet to manage the visibility and auto-opening behavior of the onboarding sheet.
  • src/navigation/AppNavigator.tsx
    • Refactored navigation to flatten sub-stack navigators (ChatsStack, ProjectsStack, ModelsStack, SettingsStack) into the RootStack.
    • Integrated SpotlightTourProvider to wrap the entire navigation stack, enabling global spotlight functionality.
    • Updated MainTabs to directly render screen components instead of stack navigators.
  • src/navigation/types.ts
    • Updated navigation types to reflect the flattened navigation structure, moving sub-screen definitions directly into RootStackParamList.
  • src/screens/ChatScreen/ChatScreenComponents.tsx
    • Added navigation prop to NoModelScreen and LoadingScreen for consistent header back button functionality.
  • src/screens/ChatScreen/index.tsx
    • Integrated useSpotlightTour to manage onboarding spotlights.
    • Added logic for consuming pending spotlights (step 3) and chaining to subsequent steps (step 12).
    • Implemented reactive spotlights for image generation (steps 15 and 16) based on app state.
    • Introduced MaybeAttachStep wrapper to conditionally apply AttachStep and prevent rendering issues.
  • src/screens/ChatScreen/useChatScreen.ts
    • Updated navigation types to align with the flattened RootStackParamList.
  • src/screens/ChatsListScreen.tsx
    • Integrated useSpotlightTour and added reactive spotlight logic for step 14 (image new chat).
    • Wrapped the 'New' button with AttachStep for spotlighting.
  • src/screens/HomeScreen/components/ActiveModelsSection.tsx
    • Wrapped TextModelCard and ImageModelCard components with AttachStep for onboarding spotlights (steps 1 and 13).
  • src/screens/HomeScreen/components/ModelPickerSheet.tsx
    • Added logic to highlight the first model item with a pulsating border when triggered by the onboarding flow (step 11).
  • src/screens/HomeScreen/hooks/useHomeScreen.ts
    • Updated navigation types to reflect the flattened navigation structure.
    • Adjusted navigation calls to directly navigate to 'Chat' screen.
  • src/screens/HomeScreen/index.tsx
    • Integrated useSpotlightTour, OnboardingSheet, and PulsatingIcon for onboarding functionality.
    • Implemented handleStepPress to manage navigation and spotlight triggering for onboarding steps.
    • Added reactive spotlight logic for step 13 (image load).
    • Adjusted image gallery text to show 'image' or 'images' without the count.
  • src/screens/HomeScreen/styles.ts
    • Adjusted header styling to accommodate the pulsating icon.
  • src/screens/ModelSettingsScreen/index.tsx
    • Integrated useSpotlightTour and added logic for consuming pending spotlights (step 6).
    • Wrapped the 'Default System Prompt' accordion header with AttachStep for spotlighting.
    • Added completeChecklistStep('exploredSettings') to mark the step as complete upon screen focus.
  • src/screens/ModelsScreen/TextModelsTab.tsx
    • Integrated useSpotlightTour and added logic for consuming pending spotlights (steps 9 and 10).
    • Wrapped the first recommended model card and the first file card with AttachStep for onboarding spotlights.
  • src/screens/ModelsScreen/index.tsx
    • Integrated useSpotlightTour and wrapped the 'Image Models' tab button with AttachStep for spotlighting (step 4).
    • Implemented useFocusEffect to reset model detail view when the tab loses focus.
  • src/screens/ModelsScreen/types.ts
    • Updated navigation types to align with the flattened RootStackParamList.
  • src/screens/ProjectDetailScreen.tsx
    • Updated navigation calls to directly navigate to 'Chat' screen, removing the getParent().navigate workaround.
  • src/screens/ProjectEditScreen.tsx
    • Integrated useSpotlightTour and added logic for consuming pending spotlights (step 8).
    • Wrapped the project name input with AttachStep for spotlighting.
  • src/screens/ProjectsScreen.tsx
    • Wrapped the 'New' button with AttachStep for onboarding spotlight (step 7).
  • src/screens/SettingsScreen.tsx
    • Integrated AttachStep for onboarding spotlight (step 5).
    • Added completeChecklistStep('exploredSettings') to mark the step as complete.
    • Updated handleResetOnboarding to use the simplified navigation structure.
    • Added a new 'Reset Onboarding Checklist' button for development purposes.
  • src/screens/SettingsScreen/styles.ts
    • Added devButtonGroup style for organizing development-only buttons.
  • src/services/imageGenerationService.ts
    • Added completeChecklistStep('triedImageGen') to mark the image generation onboarding step as complete upon successful image generation.
  • src/stores/appStore.ts
    • Added onboardingChecklist state to track completion of individual onboarding steps.
    • Introduced checklistDismissed state to manage the visibility of the onboarding sheet.
    • Added completeChecklistStep, dismissChecklist, and resetChecklist actions for managing onboarding state.
    • Added shownSpotlights state and markSpotlightShown, resetShownSpotlights actions for tracking reactive spotlight displays.
Ignored Files
  • Ignored by pattern: .github/workflows/** (1)
    • .github/workflows/ci.yml
Activity
  • The pull request introduces a comprehensive onboarding system, including new UI components for checklists and spotlights.
  • Navigation has been refactored to a flatter structure, impacting how screens are accessed and managed.
  • Extensive unit and integration tests have been added to validate the new onboarding flows and ensure the stability of navigation changes.
  • Documentation for onboarding flows and codebase standards has been updated to reflect the new features and architecture.
Using Gemini Code Assist

The full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips.

Invoking Gemini

You can request assistance from Gemini at any point by creating a comment using either /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

Customization

To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a .gemini/ folder in the base of the repository. Detailed instructions can be found here.

Limitations & Feedback

Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here.

You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension.

Footnotes

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution. ↩

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This pull request introduces a comprehensive onboarding spotlight tour system to guide new users through the application. Key changes include adding a new 'Testing' section to the README.md with CI/CD badges and test commands, and significantly refactoring the TODO.md by removing completed or deprioritized tasks related to document upload and image generation. The core of the PR involves adding extensive new unit and integration tests (__tests__/integration/onboarding/spotlightFlowIntegration.test.ts, __tests__/rntl/onboarding/*.test.tsx, __tests__/unit/onboarding/*.test.ts) to validate the six onboarding flows: downloading a model, loading a model, sending a message, trying image generation, exploring settings, and creating a project. These tests cover multi-step sequences, reactive triggers, state coordination, and tooltip content. The AppNavigator.tsx was updated to wrap the entire application with SpotlightTourProvider, and navigation logic across HomeScreen.test.tsx, ProjectDetailScreen.test.tsx, and SettingsScreen.test.tsx was simplified by flattening nested navigators and directly navigating to screens instead of using nested tab navigation. The ChatInput component now accepts an activeSpotlight prop to conditionally wrap its action button or the entire component with AttachStep for spotlighting. The ModelPickerSheet implements a pulsating border animation for onboarding. The HomeScreen now displays a pulsating icon to indicate pending onboarding steps and includes the OnboardingSheet. The ModelsScreen, ModelSettingsScreen, ProjectEditScreen, and ChatsListScreen were updated to consume pending spotlights on mount and trigger spotlights for their respective onboarding steps. The useAppStore was extended to manage onboardingChecklist flags, shownSpotlights for reactive tracking, and checklistDismissed state, with corresponding reset logic. The imageGenerationService now marks the triedImageGen checklist step as complete upon successful image generation. A new ONBOARDING_FLOWS.md document was added to detail all onboarding flows and their mechanics. A new scripts/test-count.sh script was added to count tests across platforms. Review comments highlighted the brittleness of using hardcoded setTimeout delays for triggering spotlights, suggesting InteractionManager.runAfterInteractions for more reliable timing, and noted an inefficient useEffect in SettingsScreen that causes unnecessary store updates due to an unstable dependency.

Comment thread src/screens/ChatScreen/index.tsx Outdated
Comment on lines +64 to +119
setTimeout(() => {
step3ShownRef.current = true;
goTo(3);
}, 600);
} else if (pending !== null) {
setTimeout(() => goTo(pending), 600);
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps

// Track whether we're in the middle of chaining to avoid premature cleanup
const chainingRef = useRef(false);

// When the spotlight tour stops after step 3, fire the chained step 12
useEffect(() => {
if (current === undefined && step3ShownRef.current && pendingNextRef.current !== null) {
step3ShownRef.current = false;
chainingRef.current = true;
const next = pendingNextRef.current;
pendingNextRef.current = null;
// Switch AttachStep index — need time for new AttachStep to mount + measure layout
setChatSpotlight(next);
setTimeout(() => {
chainingRef.current = false;
goTo(next);
}, 800);
} else if (current === undefined && !chainingRef.current && !step3ShownRef.current && pendingNextRef.current === null) {
// Tour stopped and no chain pending — clear spotlight
setChatSpotlight(null);
}
}, [current, goTo]);

// Reactive: image model loaded, no image generated yet → spotlight ChatInput (step 15)
useEffect(() => {
if (
chat.imageModelLoaded &&
!shownSpotlights.imageDraw &&
!onboardingChecklist.triedImageGen
) {
markSpotlightShown('imageDraw');
setChatSpotlight(IMAGE_DRAW_STEP_INDEX);
setTimeout(() => goTo(IMAGE_DRAW_STEP_INDEX), 800);
}
}, [chat.imageModelLoaded, shownSpotlights, onboardingChecklist.triedImageGen, markSpotlightShown, goTo]);

// Reactive: after first image generated → spotlight image mode toggle (step 16)
const generatedImages = useAppStore(s => s.generatedImages);
useEffect(() => {
if (
generatedImages.length > 0 &&
!shownSpotlights.imageSettings &&
onboardingChecklist.triedImageGen
) {
markSpotlightShown('imageSettings');
setChatSpotlight(IMAGE_SETTINGS_STEP_INDEX);
setTimeout(() => goTo(IMAGE_SETTINGS_STEP_INDEX), 800);
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The use of hardcoded setTimeout delays (600ms, 800ms) to trigger spotlights is brittle and can lead to race conditions on different devices or if animation timings change. This pattern is repeated for consuming pending spotlights and for reactive spotlights.

A more robust approach would be to use InteractionManager.runAfterInteractions to ensure animations and layout changes have completed before calling goTo(). This would make the spotlight timing more reliable.

For example:

useEffect(() => {
  const pending = consumePendingSpotlight();
  if (pending !== null) {
    InteractionManager.runAfterInteractions(() => {
      goTo(pending);
    });
  }
}, [goTo]);

This would apply to all setTimeout(() => goTo(...), ...) calls in this file.

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in commit cd9cb79 — replaced setTimeout with InteractionManager.runAfterInteractions() in ChatScreen for both pending spotlight consumption and reactive step 16. The remaining setTimeout calls in HomeScreen's handleStepPress are intentional — they coordinate with the sheet-close + tab-switch animation timing and fire from a user-initiated callback (not a lifecycle hook), so InteractionManager wouldn't help there.

Comment thread src/screens/SettingsScreen.tsx Outdated
Comment on lines +41 to +43
useEffect(() => {
completeChecklistStep('exploredSettings');
}, [completeChecklistStep]);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

This useEffect will run on every render because the completeChecklistStep function reference from the Zustand store is not stable. While this is idempotent, it's inefficient and causes unnecessary store updates on every render of this screen.

To fix this and ensure the effect runs only once on mount, you can use an empty dependency array and disable the lint rule for that line, as the function's identity is not important for this effect's logic.

Suggested change
useEffect(() => {
completeChecklistStep('exploredSettings');
}, [completeChecklistStep]);
useEffect(() => {
completeChecklistStep('exploredSettings');
// eslint-disable-next-line react-hooks/exhaustive-deps
}, []);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Already fixed in a previous commit — the useEffect now has an empty dependency array with an eslint-disable comment, so it only runs once on mount.

github-actions Bot and others added 2 commits March 1, 2026 10:57
- Add Step 17: spotlight first recommended image model card for download
- Make handleStepPress('triedImageGen') state-aware (3 branches: model loaded, downloaded, or missing)
- Skip image model auto-load during onboarding so reactive spotlights can fire
- Fix Step 14 tooltip missing by using multi-index AttachStep (index={[2, 14]})
- Fix Step 15 not firing by switching from reactive useFocusEffect to setPendingSpotlight mechanism
- Fix Step 16 pointing at ChatInput instead of settings icon in header
- Disable "Try image generation" checklist step until text model is loaded
- Replace setTimeout with InteractionManager.runAfterInteractions for spotlight consumers
- Fix unstable completeChecklistStep dependency in SettingsScreen

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
- Remove unused react-native-onboarding-checklist file dependency that
  broke CI (ENOENT on tgz path that only exists locally)
- Replace setTimeout with InteractionManager.runAfterInteractions for
  Step 16 reactive spotlight in ChatScreen (no cleanup to avoid race
  condition where markSpotlightShown re-triggers the effect)

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@alichherawalla

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This is an impressive pull request that delivers a comprehensive onboarding spotlight system, a significant navigation refactor, and extensive test coverage. The new onboarding flows are well-designed, using a combination of immediate and reactive spotlights to guide users through the app's core features. The navigation refactor to flatten the stack is a smart architectural change that enables the global SpotlightTourProvider to work seamlessly across all screens. The test coverage for this new functionality is excellent, with thorough unit, integration, and RNTL tests that validate the complex logic. I found one minor UI regression where the image count was removed from the gallery card, which I've flagged for review. Overall, this is a high-quality contribution that greatly enhances the new user experience.

Comment thread src/screens/HomeScreen/index.tsx Outdated
<Text style={styles.galleryCardTitle}>Image Gallery</Text>
<Text style={styles.galleryCardMeta}>
{generatedImages.length} image{generatedImages.length !== 1 ? 's' : ''}
{generatedImages.length === 1 ? 'image' : 'images'}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

The number of generated images is no longer displayed on this card, which seems like a minor regression. It's helpful for the user to see the count at a glance. I suggest re-adding the count to the text.

Suggested change
{generatedImages.length === 1 ? 'image' : 'images'}
{generatedImages.length} {generatedImages.length === 1 ? 'image' : 'images'}

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Fixed in commit 7662470 — the gallery card now shows {count} {image|images} (e.g. '2 images', '1 image').

@codecov

codecov Bot commented Mar 1, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 79.17808% with 76 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.70%. Comparing base (987c5e1) to head (0394e56).
⚠️ Report is 13 commits behind head on main.

Files with missing lines Patch % Lines
src/screens/ChatScreen/index.tsx 52.08% 18 Missing and 5 partials ⚠️
src/components/onboarding/OnboardingSheet.tsx 37.03% 17 Missing ⚠️
src/screens/ModelsScreen/ImageModelsTab.tsx 18.18% 8 Missing and 1 partial ⚠️
src/screens/ModelsScreen/TextModelsTab.tsx 73.33% 5 Missing and 3 partials ⚠️
...screens/HomeScreen/components/ModelPickerSheet.tsx 62.50% 4 Missing and 2 partials ⚠️
src/screens/HomeScreen/index.tsx 91.66% 0 Missing and 3 partials ⚠️
src/screens/ModelsScreen/index.tsx 86.36% 2 Missing and 1 partial ⚠️
src/components/onboarding/spotlightConfig.tsx 94.59% 2 Missing ⚠️
src/components/onboarding/useOnboardingSheet.ts 84.61% 0 Missing and 2 partials ⚠️
src/screens/ChatScreen/ChatScreenComponents.tsx 60.00% 2 Missing ⚠️
... and 1 more
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main      #86      +/-   ##
==========================================
- Coverage   87.14%   86.70%   -0.44%     
==========================================
  Files         155      163       +8     
  Lines        6953     7272     +319     
  Branches     1935     1994      +59     
==========================================
+ Hits         6059     6305     +246     
- Misses        485      541      +56     
- Partials      409      426      +17     
Files with missing lines Coverage Δ
src/components/checklist/ProgressBar.tsx 100.00% <100.00%> (ø)
src/components/checklist/animations.ts 100.00% <100.00%> (ø)
src/components/checklist/useOnboardingSteps.ts 100.00% <100.00%> (ø)
src/components/onboarding/PulsatingIcon.tsx 100.00% <100.00%> (ø)
src/components/onboarding/spotlightState.ts 100.00% <100.00%> (ø)
src/screens/ChatScreen/useChatScreen.ts 86.56% <ø> (ø)
src/screens/ChatsListScreen.tsx 96.92% <100.00%> (+0.37%) ⬆️
...eens/HomeScreen/components/ActiveModelsSection.tsx 90.00% <100.00%> (+0.34%) ⬆️
src/screens/HomeScreen/hooks/useHomeScreen.ts 91.04% <100.00%> (ø)
src/screens/HomeScreen/styles.ts 100.00% <ø> (ø)
... and 20 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

github-actions Bot and others added 2 commits March 1, 2026 11:34
- Remove unused react-native-onboarding-checklist file dependency (CI fix)
- Replace setTimeout with InteractionManager for Step 16 spotlight
- Move inline styles to StyleSheet (ChatInput, OnboardingSheet, ModelsScreen)
- Reduce ChatsListScreen line count below max-lines limit
- Compact handleStepPress pending map to reduce HomeScreen function length
- Remove unused createONNXImageModel import from test

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
…ression

- Extract shared test mocks into spotlightMocks.tsx to reduce duplicated lines
- Replace if-chain with pendingMap lookup in handleStepPress to reduce cognitive complexity
- Remove non-null assertion in HomeScreen STEP_INDEX_MAP access
- Restore image count display on gallery card (was showing only "images" without count)
- Sync handleStepPress test with actual implementation (missing setPendingSpotlight for step 15)
- Update HomeScreen tests for new count format

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@alichherawalla

Copy link
Copy Markdown
Collaborator Author

/gemini review

@gemini-code-assist gemini-code-assist Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Code Review

This is an impressive and substantial pull request that delivers a complete onboarding spotlight system, flattens the app's navigation architecture, and adds comprehensive test coverage. The navigation refactor is a great architectural improvement that simplifies the codebase and enables cross-tab features like the spotlight tour. The onboarding flows are well-designed, with a good mix of chained and reactive steps to guide the user effectively. The addition of over 175 new tests across 11 suites demonstrates a strong commitment to quality and maintainability. I have one suggestion for refactoring and have identified one potential bug to ensure the spotlight logic is robust across all scenarios. Overall, this is an excellent contribution.

Comment on lines +58 to +107
useEffect(() => {
const pending = consumePendingSpotlight();
if (pending === 3) {
// Chain: step 3 (ChatInput) → step 12 (VoiceRecordButton)
pendingNextRef.current = VOICE_HINT_STEP_INDEX;
step3ShownRef.current = false;
const task = InteractionManager.runAfterInteractions(() => {
step3ShownRef.current = true;
goTo(3);
});
return () => task.cancel();
} else if (pending !== null) {
const task = InteractionManager.runAfterInteractions(() => goTo(pending));
return () => task.cancel();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps

// Track whether we're in the middle of chaining to avoid premature cleanup
const chainingRef = useRef(false);

// When the spotlight tour stops after step 3, fire the chained step 12
useEffect(() => {
if (current === undefined && step3ShownRef.current && pendingNextRef.current !== null) {
step3ShownRef.current = false;
chainingRef.current = true;
const next = pendingNextRef.current;
pendingNextRef.current = null;
// Switch AttachStep index — need time for new AttachStep to mount + measure layout
setChatSpotlight(next);
setTimeout(() => {
chainingRef.current = false;
goTo(next);
}, 800);
} else if (current === undefined && !chainingRef.current && !step3ShownRef.current && pendingNextRef.current === null) {
// Tour stopped and no chain pending — clear spotlight
setChatSpotlight(null);
}
}, [current, goTo]);

// Consume pending spotlights on focus (handles reused screen instances where
// the mount-only useEffect above won't re-fire after navigation).
useFocusEffect(
useCallback(() => {
const pending = consumePendingSpotlight();
if (pending !== null) {
const task = InteractionManager.runAfterInteractions(() => goTo(pending));
return () => task.cancel();
}
}, [goTo]),
);

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

high

There are two separate effects consuming pending spotlights: a useEffect on mount and a useFocusEffect. This can lead to inconsistent behavior, as the special chaining logic for pending === 3 is only present in the mount effect. If the screen is re-focused with pending === 3, the useFocusEffect will handle it but won't set up the chain to step 12.

To ensure consistent behavior, it's best to consolidate all spotlight consumption logic into a single useFocusEffect, which runs on both initial mount and subsequent focuses.

Suggested change
useEffect(() => {
const pending = consumePendingSpotlight();
if (pending === 3) {
// Chain: step 3 (ChatInput) → step 12 (VoiceRecordButton)
pendingNextRef.current = VOICE_HINT_STEP_INDEX;
step3ShownRef.current = false;
const task = InteractionManager.runAfterInteractions(() => {
step3ShownRef.current = true;
goTo(3);
});
return () => task.cancel();
} else if (pending !== null) {
const task = InteractionManager.runAfterInteractions(() => goTo(pending));
return () => task.cancel();
}
}, []); // eslint-disable-line react-hooks/exhaustive-deps
// Track whether we're in the middle of chaining to avoid premature cleanup
const chainingRef = useRef(false);
// When the spotlight tour stops after step 3, fire the chained step 12
useEffect(() => {
if (current === undefined && step3ShownRef.current && pendingNextRef.current !== null) {
step3ShownRef.current = false;
chainingRef.current = true;
const next = pendingNextRef.current;
pendingNextRef.current = null;
// Switch AttachStep index — need time for new AttachStep to mount + measure layout
setChatSpotlight(next);
setTimeout(() => {
chainingRef.current = false;
goTo(next);
}, 800);
} else if (current === undefined && !chainingRef.current && !step3ShownRef.current && pendingNextRef.current === null) {
// Tour stopped and no chain pending — clear spotlight
setChatSpotlight(null);
}
}, [current, goTo]);
// Consume pending spotlights on focus (handles reused screen instances where
// the mount-only useEffect above won't re-fire after navigation).
useFocusEffect(
useCallback(() => {
const pending = consumePendingSpotlight();
if (pending !== null) {
const task = InteractionManager.runAfterInteractions(() => goTo(pending));
return () => task.cancel();
}
}, [goTo]),
);
useFocusEffect(
useCallback(() => {
const pending = consumePendingSpotlight();
if (pending === 3) {
// Chain: step 3 (ChatInput) → step 12 (VoiceRecordButton)
pendingNextRef.current = VOICE_HINT_STEP_INDEX;
step3ShownRef.current = false;
const task = InteractionManager.runAfterInteractions(() => {
step3ShownRef.current = true;
goTo(3);
});
return () => task.cancel();
} else if (pending !== null) {
const task = InteractionManager.runAfterInteractions(() => goTo(pending));
return () => task.cancel();
}
}, [goTo]),
);

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

The mount-only useEffect handles the step 3→12 chain (which requires tracking current and pendingNextRef). The useFocusEffect handles all other pending spotlights including step 15 (imageDraw) which arrives via setPendingSpotlight from other screens. Step 3 will always arrive on mount (first navigation to ChatScreen), not on re-focus, so the separation is intentional. If step 3 somehow arrived via re-focus, it would still fire correctly — it just wouldn't chain to step 12, which is acceptable since the chain is a nice-to-have UX touch.

Comment on lines +96 to +100
const pendingMap: Record<string, number> = {
downloadedModel: DOWNLOAD_FILE_STEP_INDEX, loadedModel: MODEL_PICKER_STEP_INDEX,
sentMessage: CHAT_INPUT_STEP_INDEX, exploredSettings: MODEL_SETTINGS_STEP_INDEX,
createdProject: PROJECT_EDIT_STEP_INDEX,
};

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For better performance and code clarity, the pendingMap object should be defined as a constant outside the HomeScreen component. Since its value is static and does not depend on component props or state, there's no need to recreate it on every render within the useCallback hook.

const PENDING_MAP: Record<string, number> = {
  downloadedModel: DOWNLOAD_FILE_STEP_INDEX,
  loadedModel: MODEL_PICKER_STEP_INDEX,
  sentMessage: CHAT_INPUT_STEP_INDEX,
  exploredSettings: MODEL_SETTINGS_STEP_INDEX,
  createdProject: PROJECT_EDIT_STEP_INDEX,
};

export const HomeScreen: React.FC<HomeScreenProps> = ({ navigation }) => {
  // ... existing component code ...

  const handleStepPress = useCallback((stepId: string) => {
    closeSheet();

    // ... existing logic for 'triedImageGen' ...

    const tab = STEP_TAB_MAP[stepId];
    const stepIndex = STEP_INDEX_MAP[stepId];

    // For multi-step flows, queue the continuation step.
    if (PENDING_MAP[stepId] !== undefined) {
      setPendingSpotlight(PENDING_MAP[stepId]);
    }

    // ... rest of the function ...
  }, [/* ... existing dependencies ... */]);

  // ... rest of the component ...
};

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Good point — however the pendingMap is already inside a useCallback so it's only created when the callback is invoked (on user press), not on every render. Moving it outside the component would be a micro-optimization with no measurable impact. Keeping it close to its usage site aids readability.

Comment thread src/stores/appStore.ts
// Device info
onboardingChecklist: OnboardingChecklist;
checklistDismissed: boolean;
completeChecklistStep: (key: string) => void;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

To improve type safety and prevent potential runtime errors from typos, the key parameter for completeChecklistStep should be strongly typed as keyof OnboardingChecklist instead of a generic string.

Suggested change
completeChecklistStep: (key: string) => void;
completeChecklistStep: (key: keyof OnboardingChecklist) => void;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Agreed — will address in a follow-up PR. The current string type works correctly at runtime and all call sites use valid keys. Narrowing to keyof OnboardingChecklist is a good improvement for IDE autocompletion.

Comment thread src/stores/appStore.ts
hasSeenCacheTypeNudge: boolean;
setHasSeenCacheTypeNudge: (v: boolean) => void;
shownSpotlights: Record<string, boolean>;
markSpotlightShown: (key: string) => void;

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

medium

For better type safety, the key for markSpotlightShown should be a specific string literal union type rather than a generic string. This will prevent invalid keys from being used and provide better autocompletion in the IDE.

Consider defining a type like type SpotlightKey = 'imageLoad' | 'imageNewChat' | 'imageDraw' | 'imageSettings'; and using it here.

Suggested change
markSpotlightShown: (key: string) => void;
markSpotlightShown: (key: 'imageLoad' | 'imageNewChat' | 'imageDraw' | 'imageSettings') => void;

Copy link
Copy Markdown
Collaborator Author

Choose a reason for hiding this comment

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

Same as above — will narrow the type in a follow-up. All current call sites use valid keys.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@sonarqubecloud

sonarqubecloud Bot commented Mar 1, 2026

Copy link
Copy Markdown

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