Skip to content

feat: expo upgrade, fix config, enhance ui, add animations#49

Merged
jaydonkc merged 13 commits intodevfrom
evan/expo-upgrade
Mar 3, 2026
Merged

feat: expo upgrade, fix config, enhance ui, add animations#49
jaydonkc merged 13 commits intodevfrom
evan/expo-upgrade

Conversation

@evan-taylor
Copy link
Copy Markdown
Collaborator

@evan-taylor evan-taylor commented Mar 2, 2026

  • Deleted Babel configuration file as it is no longer needed.
  • Updated Jest configuration to specify Babel config file path.
  • Added environment variables for email handling in backend.
  • Enhanced email validation and error handling in ResendOTP function.
  • Improved frontend components with better loading states and animations.
  • Updated dependencies in package-lock.json for compatibility with new Expo version.

Summary

I upgraded Expo to SDK 55, added tabs at the bottom of the app using the Expo native tabs layout, which includes rendering liquid glass on iOS. Adopted the tabs to be in the header of the web app and did an overall UI/animation polish. We can definitely change the UI however we want in the future. I just cleaned some things up that weren't working and added some animations for fun.

How to Test

Steps to verify locally:

  • npm run lint
  • npm run typecheck
  • npm test
  • Manual flow:
    1. npm run dev:backend (in terminal A)
    2. npm run dev (in terminal B)
    3. Verify the change: <describe expected behavior/screens>

Checklist

  • Tests added/updated (if applicable)
  • Lint/tests pass locally (npm run lint)
  • Docs updated (README/ADR/changelog if needed)
  • Follows conventional commit format
  • No merge conflicts with dev

Screenshots / Demos

CleanShot 2026-03-02 at 14 47 32 CleanShot 2026-03-02 at 14 48 20 CleanShot 2026-03-02 at 14 49 34

Summary by CodeRabbit

  • New Features

    • Native tab navigation with Home, Search, and Settings; create/edit listing flows; unified two‑step email sign‑in.
  • UI/UX Updates

    • Sitewide entrance animations, animated interactive listing cards, Pressable interactions, responsive layouts, light/dark theming, improved forms (image/tag inputs), pagination and infinite scroll.
  • Platform & Build

    • Upgraded Expo/React Native ecosystem to newer SDK and runtime versions.
  • Documentation

    • Extensive new guides for native UI, animations, controls, media, routing, storage, deployment, CI/CD, and migrations.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Mar 2, 2026

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

Adds many Expo skill docs and references; upgrades frontend to Expo/React/React Native SDK 55; applies entrance animations across UI; consolidates email+OTP login flow with configurable resend sender; adds CI/CD tooling (fetch cache + YAML validator); updates backend auth config and sender validation; redesigns screens/components and routing to a tabs layout.

Changes

Cohort / File(s) Summary
Agent Skills & References
.agents/skills/building-native-ui/SKILL.md, .agents/skills/building-native-ui/references/*, .agents/skills/expo-api-routes/SKILL.md, .agents/skills/expo-cicd-workflows/SKILL.md, .agents/skills/expo-deployment/SKILL.md, .agents/skills/expo-dev-client/SKILL.md, .agents/skills/expo-tailwind-setup/SKILL.md, .agents/skills/native-data-fetching/SKILL.md, .agents/skills/upgrading-expo/SKILL.md, .agents/skills/use-dom/SKILL.md
Bulk addition of skill guides and many reference documents (native UI, animations, controls, routing, tabs, media, WebGPU, API routes, CI/CD, deployment, dev-client, tailwind, data fetching, upgrading, DOM).
CI/CD Tooling & Scripts
.agents/skills/expo-cicd-workflows/scripts/fetch.js, .agents/skills/expo-cicd-workflows/scripts/validate.js, .agents/skills/expo-cicd-workflows/scripts/package.json, skills-lock.json
Adds fetchCached with ETag/TTL caching, YAML validator CLI (Ajv + remote schema), package.json for tooling, and skills-lock manifest.
Backend Auth & Email
backend/convex/ResendOTP.ts, backend/convex/auth.config.ts, backend/.env.example, docs/contributing.md
Introduces AUTH_RESEND_FROM env var, validates sender (rejects onboarding@resend.dev), uses configurable sender for OTP emails, and exports Convex auth.config.
Frontend SDK / Config
frontend/package.json, frontend/app.json, frontend/babel.config.js, frontend/tsconfig.json, jest.config.js, .eslintrc.js, .cursor/settings.json
Upgrades to Expo/React/React Native SDK 55 ecosystem, adds babel-preset-expo and frontend babel config, adjusts TS includes, updates Jest transform config, adds ESLint override, enables Convex plugin in Cursor.
App Layout & Navigation
frontend/app/_layout.tsx, frontend/app/(tabs)/_layout.tsx
Introduces ThemeProvider driven by color scheme, rewires Stack and NativeTabs, hides selected headers, and adds a web-specific top tab header layout.
Screens & Flows
frontend/app/(tabs)/index.tsx, frontend/app/(tabs)/search.tsx, frontend/app/(tabs)/settings.tsx, frontend/app/auth/login.tsx, frontend/app/listings/[id].tsx, frontend/app/listings/[id]/edit.tsx, frontend/app/listings/new.tsx
Adds redesigned home/search/settings/listing screens, pagination and filter sync, unified two‑step email+OTP login with redirect handling, image upload/tag support, auth guards, and improved loading/empty states.
Components & Animations
frontend/components/* (CategoryPicker, FilterBar, PriceRangePicker, TagInput, TagPicker, ListingCard, ListingUnavailable, HiddenBanner), frontend/hooks/useEntranceAnimation.ts
Adds useEntranceAnimation (opacity + translateY), applies entrance animations across many components, converts Touchables to Pressable, adds ListingCard index-based stagger and hover/press states; updates several component props/APIs (TagInput, TagPicker, ListingCard). Review focus: native-driver usage, animation timing/delays, updated component props/API.
Removed / Moved
frontend/app/index.tsx
Removed legacy home index in favor of new (tabs)/index.tsx and routing updates.
Misc Frontend
frontend/babel.config.js, frontend/package.json
Adds babel config and dependency upgrades; adds babel-preset-expo devDependency. Review dependency bumps and compatibility.
Project Tooling & CI config
.coderabbit.yaml, skills-lock.json
Adds reviewer config to exclude .agents/** and a skills lock manifest.

Sequence Diagram(s)

sequenceDiagram
    participant User
    participant LoginScreen
    participant Convex
    participant ResendAPI as Resend API
    participant Email

    User->>LoginScreen: Submit email
    LoginScreen->>Convex: create OTP request
    Convex->>Convex: validate AUTH_RESEND_FROM & API key
    Convex->>ResendAPI: POST email payload (sender = AUTH_RESEND_FROM)
    ResendAPI->>Email: Deliver OTP
    Email-->>User: OTP received
    User->>LoginScreen: Submit OTP
    LoginScreen->>Convex: verify OTP
    Convex-->>LoginScreen: auth session/token
    LoginScreen->>User: navigate into app
Loading
sequenceDiagram
    participant Component
    participant Hook as useEntranceAnimation
    participant NativeDriver as Animated Native Driver

    Component->>Hook: call useEntranceAnimation(delay,distance)
    Hook->>NativeDriver: create opacity & translateY animated values (native driver)
    Component->>NativeDriver: start parallel animation (320ms, cubic, delay)
    NativeDriver->>Component: update animated styles each frame
    Component->>Component: render with animated style applied
    Component->>NativeDriver: on unmount -> stop animations
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~60 minutes

Possibly related PRs

Suggested reviewers

  • jaydonkc

Poem

🐰 I hopped through docs and code so bright,
I nudged the tabs and tuned the light.
OTPs now flutter from a proper name,
Cards spring in with animated fame.
Hooray — the app now feels delightfully game!

🚥 Pre-merge checks | ✅ 2 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 3.13% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title 'feat: expo upgrade, fix config, enhance ui, add animations' accurately summarizes the main changes: Expo SDK upgrade, configuration fixes, UI improvements, and animation additions.
Description check ✅ Passed The PR description includes most required sections: summary of changes, how to test, completed checklist items, and screenshots/demos. However, the 'Linked Issues' section is missing, and the 'How to Test' section lacks specific verification details for the described behavior changes.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch evan/expo-upgrade

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.

Copy link
Copy Markdown
Contributor

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

Actionable comments posted: 15

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (3)
frontend/app/auth/login.tsx (2)

55-71: ⚠️ Potential issue | 🟡 Minor

Enforce numeric OTP format, not only length

Current validation accepts any 8-character string. Since the UI asks for an 8-digit code, validate digits explicitly before submit.

Suggested fix
   const handleVerifyCode = async () => {
-    if (!code.trim() || code.trim().length !== 8) {
+    const normalizedCode = code.trim();
+    if (!/^\d{8}$/.test(normalizedCode)) {
       setError('Please enter the 8-digit code');
       return;
     }
@@
     try {
-      await signIn('resend-otp', { email: step.email, code: code.trim() });
+      await signIn('resend-otp', { email: step.email, code: normalizedCode });
       // On success, redirect to home
       router.replace('/');
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/auth/login.tsx` around lines 55 - 71, The OTP validation in
handleVerifyCode currently only checks length and non-empty; update the
validation to require exactly 8 numeric digits (e.g., test code against
/^\d{8}$/) before calling signIn('resend-otp'); if the check fails
setError('Please enter the 8-digit numeric code') and return. Locate the handler
function handleVerifyCode and the variables code and step to implement this
stricter validation and keep subsequent flow (setIsLoading, setError(null),
try/await signIn(...)) unchanged.

41-43: ⚠️ Potential issue | 🟡 Minor

Clear stale success state when changing auth actions

successMessage is not cleared in send/verify/back handlers, so old success text can persist into unrelated states (including alongside new errors).

Suggested fix
   const handleSendCode = async () => {
@@
     setIsLoading(true);
     setError(null);
+    setSuccessMessage(null);
@@
   const handleVerifyCode = async () => {
@@
     setIsLoading(true);
     setError(null);
+    setSuccessMessage(null);
@@
   const handleBack = () => {
     setStep('email');
     setCode('');
     setError(null);
+    setSuccessMessage(null);
   };

Also applies to: 66-68, 100-104

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

In `@frontend/app/auth/login.tsx` around lines 41 - 43, Old success text can
persist because successMessage isn't cleared; wherever you set loading or clear
errors (the spots calling setIsLoading(true) and setError(null)) also call the
success setter (e.g., setSuccessMessage(null) or setSuccessMessage('')) so the
success state is reset. Update the send, verify and back handlers (the functions
handling send/verify/back actions) to clear successMessage at the start,
mirroring the setIsLoading/setError calls so no stale success text remains when
switching actions.
frontend/app/(tabs)/index.tsx (1)

92-105: ⚠️ Potential issue | 🟠 Major

Avoid one-time gating for initial cursor updates.

Like the search screen, this block can lock first-page data after first render because the initial cursor is marked processed and never reapplied.

🔧 Suggested fix
-      if (!processedCursorsRef.current.has(cursorId)) {
-        if (cursor === null) {
-          // First page - replace all listings
-          setAllListings(listingsResult.page);
-        } else {
-          // Subsequent pages - append to existing listings
-          setAllListings((prev) => [...prev, ...listingsResult.page]);
-        }
-        setIsDone(listingsResult.isDone);
-        setIsLoadingMore(false);
-
-        // Mark this cursor as processed
-        processedCursorsRef.current.add(cursorId);
-      }
+      if (cursor === null) {
+        // Keep initial page reactive for live updates.
+        setAllListings(listingsResult.page);
+        setIsDone(listingsResult.isDone);
+        setIsLoadingMore(false);
+        return;
+      }
+
+      if (!processedCursorsRef.current.has(cursorId)) {
+        setAllListings((prev) => [...prev, ...listingsResult.page]);
+        setIsDone(listingsResult.isDone);
+        setIsLoadingMore(false);
+        processedCursorsRef.current.add(cursorId);
+      }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(tabs)/index.tsx around lines 92 - 105, The current logic uses
processedCursorsRef.current.add(cursorId) to permanently skip reapplying the
same cursorId, which can lock the initial page and prevent updates when the
first-page data changes; modify the handling in the block around
processedCursorsRef, cursorId, cursor, setAllListings and listingsResult.page so
that initial cursor updates are not one-time gated — for example only treat
cursorId as processed for pagination loads (when cursor !== null) or use a more
specific key (e.g., include a version/timestamp) before adding to
processedCursorsRef, and ensure setIsDone and setIsLoadingMore are still updated
on new first-page responses rather than skipping when cursorId was seen before.
🟡 Minor comments (23)
.agents/skills/expo-dev-client/SKILL.md-93-99 (1)

93-99: ⚠️ Potential issue | 🟡 Minor

Add simulator build profile prerequisite to iOS simulator installation instructions.

The simulator installation steps extract a .app from a .tar.gz, but the earlier documentation states that iOS local builds output .ipa by default. The .app-in-.tar.gz output only occurs when building with ios.simulator: true. Without this prerequisite, users will fail to find the expected .app artifact. Add a note clarifying the required build profile:

Proposed fix
 Install iOS build on simulator:
 
 ```bash
-# Find the .app in the .tar.gz output
+# Requires a simulator build profile (e.g., `"ios": { "simulator": true }`)
+# Find the .app in the .tar.gz output
 tar -xzf build-*.tar.gz
 xcrun simctl install booted ./path/to/App.app

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-dev-client/SKILL.md around lines 93 - 99, Add a
prerequisite note to the "Install iOS build on simulator" instructions
clarifying that the .app inside a .tar.gz is produced only for simulator builds
(e.g., set ios.simulator: true) so users know to use a simulator build profile;
update the explanatory text immediately above the tar/xcrun commands in the same
code block (the lines starting with "tar -xzf build-*.tar.gz" and "xcrun simctl
install booted ./path/to/App.app") to include this requirement.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/use-dom/SKILL.md-198-205 (1)</summary><blockquote>

`198-205`: _⚠️ Potential issue_ | _🟡 Minor_

**Fix mismatch between CSS import path and comment.**

Line 204 says “CSS file in same directory,” but `@/styles.css` typically points to an alias/root path, not same directory. Update either the comment or import path to avoid confusion.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/use-dom/SKILL.md around lines 198 - 205, The comment next to
the CSS import in components/styled-component.tsx is misleading: it says “CSS
file in same directory” but the import uses the root alias '@/styles.css';
either change the import to a relative path (e.g., './styles.css') to match the
comment or update the comment to reflect that '@/styles.css' is a root/alias
path; edit the 'use dom' component file and adjust either the import statement
or the comment so they consistently describe the file location.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/use-dom/SKILL.md-63-75 (1)</summary><blockquote>

`63-75`: _⚠️ Potential issue_ | _🟡 Minor_

**Make `dom` prop guidance consistent across examples.**

Line 63 says “Always type it,” but examples alternate between required and optional `dom`. Please standardize the rule (required vs optional) and align all snippets to it.  



Also applies to: 129-133, 160-161, 180-181, 294-295, 316-317

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/use-dom/SKILL.md around lines 63 - 75, The docs alternate
between making the dom prop optional and required—standardize to a required dom
prop: update every example (including the snippet with interface Props and
MyComponent and the other occurrences at the noted ranges) to include dom:
import('expo/dom').DOMProps in the Props interface and to destructure dom from
the component parameters (e.g., function MyComponent({ content, dom }: Props)),
and ensure the prose (“Always type it”) and all code examples consistently show
dom as a required prop rather than optional.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/upgrading-expo/references/react-19.md-22-22 (1)</summary><blockquote>

`22-22`: _⚠️ Potential issue_ | _🟡 Minor_

**Tighten sentence grammar.**

Line 22 should read “This simplifies…” instead of a comma splice.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/upgrading-expo/references/react-19.md at line 22, The
sentence "use can be called conditionally, this simplifies components that
consume multiple contexts." contains a comma splice; change it to two sentences
by replacing the comma with a period and capitalizing "This" so it reads "use
can be called conditionally. This simplifies components that consume multiple
contexts." Ensure the exact phrase is updated wherever it appears in the
referenced markdown.


</details>

</blockquote></details>
<details>
<summary>frontend/tsconfig.json-18-18 (1)</summary><blockquote>

`18-18`: _⚠️ Potential issue_ | _🟡 Minor_

**Verify that Expo-generated type includes won't be needed if generated files are created.**

Line 18 restricts the include to `["**/*.ts", "**/*.tsx"]`. While `expo-env.d.ts` and `.expo/` directory don't currently exist, the codebase actively uses `EXPO_PUBLIC_*` environment variables and Expo Router's typed route parameters (`useLocalSearchParams<>`). If Expo generates ambient type files via `expo prebuild` or similar tooling, the current tsconfig won't pick them up because those generated files would fall outside the narrowed include pattern.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @frontend/tsconfig.json at line 18, The tsconfig "include" is currently
limited to ["/*.ts","/.tsx"] so Expo-generated ambient declaration files
(e.g., expo-env.d.ts or files under .expo/) and other .d.ts files (needed for
EXPO_PUBLIC_
env types and useLocalSearchParams<> route types) won't be picked
up; update the tsconfig include to also capture declaration and Expo-generated
files—e.g., add patterns like "/*.d.ts" and ".expo/" or explicitly include
"expo-env.d.ts"—so TypeScript will load those ambient types used by
EXPO_PUBLIC_* and useLocalSearchParams.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/building-native-ui/references/gradients.md-111-111 (1)</summary><blockquote>

`111-111`: _⚠️ Potential issue_ | _🟡 Minor_

**Soften the absolute prohibition on `expo-linear-gradient` to reflect it as a preference.**

The package is not deprecated and remains officially supported in Expo SDKs. Line 111's "Do NOT use" language is too absolute. Reframe as: "Prefer CSS gradients when they align with your target platform requirements and New Architecture compatibility goals."

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/gradients.md at line 111,
Update the phrasing at the line currently saying "Do NOT use
expo-linear-gradient — use CSS gradients instead" to soften the prohibition:
replace it with guidance such as "Prefer CSS gradients when they align with your
target platform requirements and New Architecture compatibility goals;
expo-linear-gradient remains supported and may be used when appropriate."
Ensure the new sentence explicitly frames expo-linear-gradient as a supported
option rather than deprecated while still recommending CSS gradients as the
preferred approach for stated goals.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/building-native-ui/references/zoom-transitions.md-155-155 (1)</summary><blockquote>

`155-155`: _⚠️ Potential issue_ | _🟡 Minor_

**Fix hyphenation typo.**

Line 155 should use “off-screen” for correct compound adjective usage.

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/zoom-transitions.md at line
155, Replace the phrase "scrolled off screen" in the sentence "When source is
unavailable (e.g., scrolled off screen), the transition zooms from the center of
the screen" with the hyphenated compound adjective "off-screen" so the line
reads "When source is unavailable (e.g., scrolled off-screen), the transition
zooms from the center of the screen"; update the text in the zoom-transitions.md
file accordingly.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/building-native-ui/references/search.md-164-173 (1)</summary><blockquote>

`164-173`: _⚠️ Potential issue_ | _🟡 Minor_

**Update this NativeTabs example to match the SDK 55 syntax documented in tabs.md.**

Lines 167-172 use standalone `<Label>` and `<Icon>` components, but this conflicts with the SDK 55 pattern established in your tabs.md reference guide, which documents these as `NativeTabs.Trigger.Label` and `NativeTabs.Trigger.Icon`. Align search.md with your documented API for consistency.

<details>
<summary>Proposed doc fix</summary>

```diff
 <NativeTabs>
   <NativeTabs.Trigger name="(home)">
-    <Label>Home</Label>
-    <Icon sf="house.fill" />
+    <NativeTabs.Trigger.Label>Home</NativeTabs.Trigger.Label>
+    <NativeTabs.Trigger.Icon sf="house.fill" md="home" />
   </NativeTabs.Trigger>
   <NativeTabs.Trigger name="(search)" role="search">
-    <Label>Search</Label>
+    <NativeTabs.Trigger.Label>Search</NativeTabs.Trigger.Label>
   </NativeTabs.Trigger>
 </NativeTabs>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/search.md around lines 164 -
173, The NativeTabs example uses standalone Label and Icon components which
conflict with SDK 55 API; update the app/_layout.tsx example to use the nested
SDK 55 form by replacing the standalone components inside NativeTabs.Trigger
with NativeTabs.Trigger.Label and NativeTabs.Trigger.Icon so the triggers read
NativeTabs.Trigger.Label and NativeTabs.Trigger.Icon (keep NativeTabs and
NativeTabs.Trigger as-is).
.agents/skills/building-native-ui/SKILL.md-14-30 (1)

14-30: ⚠️ Potential issue | 🟡 Minor

Specify code-block languages for the two plain fenced blocks.

Both blocks currently trigger MD040 and may fail markdown lint checks.

🛠 Suggested patch
-```
+```text
 references/
   animations.md          Reanimated: entering, exiting, layout, scroll-driven, gestures
   controls.md            Native iOS: Switch, Slider, SegmentedControl, DateTimePicker, Picker
   ...
-```
+```

 ...

-```
+```text
 app/
   _layout.tsx — <NativeTabs />
   (index,search)/
     _layout.tsx — <Stack />
     index.tsx — Main list
     search.tsx — Search view
-```
+```

Also applies to: 249-256

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

In @.agents/skills/building-native-ui/SKILL.md around lines 14 - 30, The fenced
code blocks in SKILL.md (the "references/" list block and the "app/" list block
around the example sections) are plain fences and trigger MD040; update both
fenced blocks to specify a language (e.g., change the opening ``` to ```text) so
the markdown linter accepts them, ensuring you modify the block that contains
the references/ list and the block that contains the app/ layout example (also
apply the same change at the other occurrence around lines 249-256).
.agents/skills/building-native-ui/SKILL.md-24-25 (1)

24-25: ⚠️ Potential issue | 🟡 Minor

Resolve conflicting storage guidance.

This reference line says storage.md covers AsyncStorage, but the storage reference explicitly says to never use AsyncStorage. Please align this to one recommendation.

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

In @.agents/skills/building-native-ui/SKILL.md around lines 24 - 25, The
SKILL.md index currently lists "storage.md             SQLite, AsyncStorage,
SecureStore" which conflicts with the storage guidance that forbids
AsyncStorage; update the SKILL.md entry to match the canonical guidance by
removing AsyncStorage and listing only approved options (e.g., "storage.md 
SQLite, SecureStore" or whichever approved set your storage reference mandates).
Make the change to the SKILL.md line that mentions storage.md so the index and
storage.md guidance are consistent.
.agents/skills/expo-api-routes/SKILL.md-35-43 (1)

35-43: ⚠️ Potential issue | 🟡 Minor

Add a language identifier to the fenced directory-tree block.

This currently violates MD040 and can fail docs lint checks.

🛠 Suggested patch
-```
+```text
 app/
   api/
     hello+api.ts          → GET /api/hello
     users+api.ts          → /api/users
     users/[id]+api.ts     → /api/users/:id
   (tabs)/
     index.tsx

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-api-routes/SKILL.md around lines 35 - 43, The fenced
directory-tree block that begins with and contains the lines starting with "app/", "api/", and the file listings should include a language identifier to satisfy MD040; change the opening fence from to text so the block becomes a labeled code block (e.g., replace the existing before the directory tree
with ```text).


</details>

</blockquote></details>
<details>
<summary>frontend/components/PriceRangePicker.tsx-36-36 (1)</summary><blockquote>

`36-36`: _⚠️ Potential issue_ | _🟡 Minor_

**Entrance animation likely runs only once per component mount.**

Because the hook animation starts on mount, reopening the modal may skip the entrance transition. Please trigger/restart animation on `visible` transitions.


<details>
<summary>🎬 Suggested direction</summary>

```diff
-  const entranceStyle = useEntranceAnimation(40, 8);
+  const entranceStyle = useEntranceAnimation(visible, 40, 8);
```

```ts
// frontend/hooks/useEntranceAnimation.ts
export function useEntranceAnimation(visible: boolean, delay = 0, distance = 16) {
  // reset values and start animation when `visible` becomes true
}
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/PriceRangePicker.tsx` at line 36, The entrance animation
currently only runs on mount because useEntranceAnimation is called without the
modal visibility state; update PriceRangePicker to pass the modal `visible` (or
`isOpen`) boolean into useEntranceAnimation (e.g., useEntranceAnimation(visible,
40, 8)) and modify the hook signature useEntranceAnimation(visible: boolean,
delay = 0, distance = 16) so the hook resets animation values and re-triggers
the animation whenever `visible` becomes true (reset internal animated
values/timers on visible transitions and start the entrance animation).
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/building-native-ui/references/tabs.md-305-309 (1)</summary><blockquote>

`305-309`: _⚠️ Potential issue_ | _🟡 Minor_

**Add a language tag to the route-structure fenced block.**

This block triggers `MD040` and can fail docs lint checks.


<details>
<summary>🛠 Suggested patch</summary>

```diff
-```
+```text
 app/
   _layout.tsx          # NativeTabs for iOS/Android
   _layout.web.tsx      # Headless tabs for web (expo-router/ui)
-```
+```
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/tabs.md around lines 305 - 309,
The fenced route-structure block containing the lines "_layout.tsx #
NativeTabs for iOS/Android" and "_layout.web.tsx # Headless tabs for web
(expo-router/ui)" needs a language tag to satisfy MD040; update the opening
fence from totext so the block is explicitly marked as plain text (leave
the closing fence unchanged) to fix the lint error in tabs.md.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/native-data-fetching/SKILL.md-16-20 (1)</summary><blockquote>

`16-20`: _⚠️ Potential issue_ | _🟡 Minor_

**Specify fence languages for code blocks (MD040).**

Line 16 and Line 415 should label fenced blocks for markdownlint compliance.


<details>
<summary>📝 Proposed fix</summary>

```diff
-```
+```text
 references/
   expo-router-loaders.md   Route-level data loading with Expo Router loaders (web, SDK 55+)
 ```

```diff
-```
+```text
 User asks about networking
   |-- Route-level data loading (web, SDK 55+)?
@@
       \-- Cancellation -> AbortController or React Query
 ```
```
</details>


Also applies to: 415-449

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/native-data-fetching/SKILL.md around lines 16 - 20, Add
explicit fence languages to the two unlabeled fenced code blocks in
.agents/skills/native-data-fetching/SKILL.md (the block around the "references/"
listing near the top and the larger example block around lines ~415-449). Change
the triple-backtick lines to include a language token (e.g., ```text) so both
fenced blocks are labeled for markdownlint MD040; update both occurrences (the
brief "references/" block and the longer example block) to use the same or
appropriate language label.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/building-native-ui/references/webgpu-three.md-492-503 (1)</summary><blockquote>

`492-503`: _⚠️ Potential issue_ | _🟡 Minor_

**Add language tags to fenced code blocks to satisfy lint.**

Line 492 and Line 507 use unlabeled fenced blocks; markdownlint MD040 flags these.


<details>
<summary>📝 Proposed fix</summary>

```diff
-```
+```text
 src/
 ├── app/
 │   └── index.tsx           # Entry point with lazy loading
@@
 └── complex scenes → LOD (Level of Detail)
-```
+```
```

```diff
-```
+```text
 Need 3D graphics?
 ├── Simple shapes → mesh + geometry + material
@@
 └── Complex scenes → LOD (Level of Detail)
-```
+```
```
</details>


Also applies to: 507-523

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/webgpu-three.md around lines
492 - 503, The markdown has unlabeled fenced code blocks in
.agents/skills/building-native-ui/references/webgpu-three.md (the block starting
with "src/" and the block starting with "Need 3D graphics?") which trigger
markdownlint MD040; add a language tag (e.g., text) to both opening fences for those two blocks so they are labeled, ensuring the closing fences remain
and no block content changes; update the instances around the "src/" tree and
the "Need 3D graphics?" list accordingly.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/building-native-ui/references/media.md-5-5 (1)</summary><blockquote>

`5-5`: _⚠️ Potential issue_ | _🟡 Minor_

**Hyphenate compound adjective for clarity.**

At Line 5, use “full-screen camera” instead of “full screen camera.”

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/media.md at line 5, Edit the
phrase in the markdown content where it currently reads "full screen camera" and
change it to the hyphenated compound adjective "full-screen camera" (e.g.,
update the line that says "Hide navigation headers when there's a full screen
camera" to "Hide navigation headers when there's a full-screen camera") to
improve clarity.
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/native-data-fetching/SKILL.md-497-500 (1)</summary><blockquote>

`497-500`: _⚠️ Potential issue_ | _🟡 Minor_

**Fix env var name typo in examples.**

Line 497 and Line 500 use `EXPO*PUBLIC*`; this should be `EXPO_PUBLIC_` to avoid confusion.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/native-data-fetching/SKILL.md around lines 497 - 500, Replace
the incorrect env var prefix `EXPO*PUBLIC*` used in the examples and explanatory
text with the correct `EXPO_PUBLIC_` prefix (e.g., `EXPO_PUBLIC_MY_API_KEY`)
across the SKILL.md examples that mention .env.development and .env.production,
and update any surrounding guidance so client-safe keys are shown using
`EXPO_PUBLIC_` while secret keys remain unprefixed for API routes.
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/expo-cicd-workflows/scripts/validate.js-59-64 (1)</summary><blockquote>

`59-64`: _⚠️ Potential issue_ | _🟡 Minor_

**Return success exit code for explicit help.**

At Line 59–Line 64, `--help` currently exits with code `1` when no files are supplied. Help output should exit `0`.


<details>
<summary>✅ Proposed fix</summary>

```diff
-  if (files.length === 0 || args.includes('--help') || args.includes('-h')) {
+  const showHelp = args.includes('--help') || args.includes('-h');
+  if (files.length === 0 || showHelp) {
     console.log(`Usage: validate <workflow.yml> [workflow2.yml ...]
 
 Validates EAS workflow YAML files against the official schema.`);
-    process.exit(files.length === 0 ? 1 : 0);
+    process.exit(showHelp ? 0 : 1);
   }
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-cicd-workflows/scripts/validate.js around lines 59 - 64,
Change the exit logic in the help/usage branch so explicit help always returns
success: in the if block that checks (files.length === 0 ||
args.includes('--help') || args.includes('-h')), update the process.exit(...)
call to return 0 when args includes '--help' or '-h' and only return 1 when
files.length === 0 and help flags are not present; reference the existing
variables/expressions files, args.includes('--help'), args.includes('-h'), and
the process.exit(...) invocation to locate and adjust the conditional.
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/expo-deployment/SKILL.md-123-125 (1)</summary><blockquote>

`123-125`: _⚠️ Potential issue_ | _🟡 Minor_

**Fix broken internal links (`reference` → `references`)**

Later cross-links use a non-existent folder name and will break navigation.



<details>
<summary>Suggested fix</summary>

```diff
-- See ./reference/testflight.md for credential setup
-- See ./reference/ios-app-store.md for App Store submission
+- See ./references/testflight.md for credential setup
+- See ./references/ios-app-store.md for App Store submission
@@
-- See ./reference/play-store.md for detailed setup
+- See ./references/play-store.md for detailed setup
@@
-- See ./reference/workflows.md for CI/CD automation
+- See ./references/workflows.md for CI/CD automation
@@
-See ./reference/workflows.md for more workflow examples.
+See ./references/workflows.md for more workflow examples.
```
</details>


Also applies to: 130-137, 165-165

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-deployment/SKILL.md around lines 123 - 125, The markdown
links use the incorrect folder name "reference" and should be updated to
"references": replace occurrences of "./reference/testflight.md" and
"./reference/ios-app-store.md" (and the other instances at the same pattern,
e.g., any "./reference/..." links found later) with "./references/testflight.md"
and "./references/ios-app-store.md" respectively so all internal cross-links
point to the existing references directory in SKILL.md.
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/building-native-ui/references/route-structure.md-18-24 (1)</summary><blockquote>

`18-24`: _⚠️ Potential issue_ | _🟡 Minor_

**Add fenced code languages to satisfy markdownlint (MD040)**

Several code fences are missing a language identifier. Add `text` (or `plaintext`) for directory-tree examples to keep lint clean.



<details>
<summary>Suggested fix pattern</summary>

```diff
-```
+```text
 app/
   users/
     [id].tsx        # Matches /users/123, /users/abc
     [id]/
       posts.tsx     # Matches /users/123/posts
 ```
```
</details>


Also applies to: 30-34, 69-77, 95-106, 112-119, 155-169

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/route-structure.md around lines
18 - 24, The markdown code fences in route-structure.md containing
directory-tree examples (for example the block showing "app/ users/ [id].tsx
[id]/ posts.tsx") are missing language identifiers which triggers MD040; update
each of those fenced code blocks (including the ones at the other ranges noted:
30-34, 69-77, 95-106, 112-119, 155-169) by adding a language token such as
"text" or "plaintext" after the opening so the directory-tree examples (e.g., the block with "app/", "users/", "[id].tsx", "[id]/", "posts.tsx") becometext ... ``` to satisfy markdownlint.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/expo-deployment/references/ios-app-store.md-355-355 (1)</summary><blockquote>

`355-355`: _⚠️ Potential issue_ | _🟡 Minor_

**Hyphenate “up-to-date” in the tip text.**

Small docs wording fix for compound adjective usage.



<details>
<summary>📝 Suggested fix</summary>

```diff
-- Keep demo account credentials up to date
+- Keep demo account credentials up-to-date
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-deployment/references/ios-app-store.md at line 355,
Change the tip text string "Keep demo account credentials up to date" to use a
hyphenated compound adjective: replace it with "Keep demo account credentials
up-to-date" wherever that exact phrase appears (e.g., the tip line containing
Keep demo account credentials up to date in ios-app-store.md).
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/expo-deployment/references/ios-app-store.md-227-227 (1)</summary><blockquote>

`227-227`: _⚠️ Potential issue_ | _🟡 Minor_

**Use a non-stale scheduled-release timestamp example.**

Line 227 currently shows **March 1, 2025**, which is already in the past as of **March 2, 2026**. Prefer a placeholder to avoid date drift.



<details>
<summary>📝 Suggested fix</summary>

```diff
-      "automaticRelease": "2025-03-01T10:00:00Z"
+      "automaticRelease": "<YYYY-MM-DDTHH:mm:ssZ>"
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-deployment/references/ios-app-store.md at line 227,
Replace the stale scheduled-release timestamp value for the "automaticRelease"
field (currently "2025-03-01T10:00:00Z") with a non-stale example or a
placeholder to avoid date drift—for example use an ISO timestamp in the future
or a placeholder like "YYYY-MM-DDTHH:MM:SSZ" so the example remains valid;
update the string value for "automaticRelease" accordingly.
```

</details>

</blockquote></details>
<details>
<summary>frontend/app/listings/[id].tsx-95-100 (1)</summary><blockquote>

`95-100`: _⚠️ Potential issue_ | _🟡 Minor_

**Hide the Share action when the listing is hidden.**

Line 95 renders `Share` even for hidden-owner view. This allows sharing links that resolve to unavailable pages for other users. Gate the button with `!isHidden`.


<details>
<summary>Suggested fix</summary>

```diff
-          <Pressable
-            style={({ pressed }) => [styles.shareButton, pressed && styles.buttonPressed]}
-            onPress={shareListing}
-          >
-            <Text style={styles.shareButtonText}>Share</Text>
-          </Pressable>
+          {!isHidden && (
+            <Pressable
+              style={({ pressed }) => [styles.shareButton, pressed && styles.buttonPressed]}
+              onPress={shareListing}
+            >
+              <Text style={styles.shareButtonText}>Share</Text>
+            </Pressable>
+          )}
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/listings/`[id].tsx around lines 95 - 100, The Share button is
rendered for hidden listings; wrap the Pressable that calls shareListing (the
element using styles.shareButton and styles.shareButtonText) in a guard so it
only renders when the listing is not hidden (use the isHidden boolean: render
the Pressable when !isHidden), ensuring the shareListing action and its UI are
completely omitted when isHidden is true.
```

</details>

</blockquote></details>

</blockquote></details>

<details>
<summary>🧹 Nitpick comments (8)</summary><blockquote>

<details>
<summary>backend/convex/ResendOTP.ts (1)</summary><blockquote>

`12-12`: **Consider trimming sender env value before validation/use.**

Whitespace-only or padded values can bypass the current truthy check and fail later at send time.


<details>
<summary>♻️ Optional hardening</summary>

```diff
-const resendFromAddress = process.env.AUTH_RESEND_FROM ?? process.env.RESEND_FROM;
+const resendFromAddress =
+  (process.env.AUTH_RESEND_FROM ?? process.env.RESEND_FROM)?.trim();
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@backend/convex/ResendOTP.ts` at line 12, Trim whitespace from the environment
sender value before using or validating it: in the logic that determines
resendFromAddress (the constant resendFromAddress), call .trim() on
process.env.AUTH_RESEND_FROM and process.env.RESEND_FROM (guarding for
undefined) so that values that are whitespace-only become empty strings and the
truthy check fails early; then use the trimmed value for subsequent
validation/send operations to avoid passing padded or whitespace-only senders to
the mailer.
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/expo-tailwind-setup/SKILL.md (2)</summary><blockquote>

`214-217`: **Clarify the dual contentClassName mapping.**

Both `contentClassName` and `contentContainerClassName` are mapped to `contentContainerStyle`. This might be intentional to accept either prop name, but it could confuse users about which to use.

Consider adding a comment explaining this design choice or standardizing on one prop name.



<details>
<summary>📝 Suggested clarification</summary>

```diff
 export const AnimatedScrollView = (
   props: React.ComponentProps<typeof Animated.ScrollView> & {
     className?: string;
-    contentClassName?: string;
+    contentClassName?: string; // Alias for contentContainerClassName
     contentContainerClassName?: string;
   }
 ) => {
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-tailwind-setup/SKILL.md around lines 214 - 217, The
mapping in useCssElement for Animated.ScrollView sets both contentClassName and
contentContainerClassName to 'contentContainerStyle', which is ambiguous; update
the code in useCssElement (the Animated.ScrollView return) to either standardize
on a single prop name (prefer contentContainerClassName) and map the legacy
alias (contentClassName) to that canonical name, or add a clear inline comment
explaining that both are intentionally accepted as aliases; ensure props and
returned mapping use the chosen canonical symbol consistently (e.g.,
contentContainerClassName -> 'contentContainerStyle') and document the alias
handling so users aren’t confused.
```

</details>

---

`281-301`: **Consider adding an example of importing global.css.**

The usage section shows component usage but doesn't demonstrate how to import the `global.css` file in the app entry point. This is mentioned in troubleshooting (line 441) but would be helpful to show upfront.



<details>
<summary>📚 Suggested addition before the Usage section</summary>

```markdown
## Import Global Styles

Import the global CSS file in your app entry point (e.g., `app/_layout.tsx`):

```tsx
import '../global.css';

export default function RootLayout() {
  // ... your layout code
}
```
```

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-tailwind-setup/SKILL.md around lines 281 - 301, Add a
short "Import Global Styles" snippet before the Usage section showing how to
import the global.css in the app entry point: instruct the reader to add an
import of global.css at the top of their RootLayout (or app entry component) so
styles are loaded globally, and mention placing the import before the
export/default component definition; reference the global.css filename and the
RootLayout component to locate where to add this example.
```

</details>

</blockquote></details>
<details>
<summary>.agents/skills/building-native-ui/references/animations.md (1)</summary><blockquote>

`3-3`: **Soften the "Avoid" guidance to better reflect official React Native recommendations and current codebase patterns.**

Line 3 presents an absolute prohibition, but React Native's `Animated` API remains officially supported and is used in `frontend/hooks/useEntranceAnimation.ts` for simple entrance transitions. The React Native documentation lists it as one of two complementary animation systems. Reframe this as a preference for complex/performance-critical cases rather than a blanket restriction.

<details>
<summary>Suggested wording</summary>

```diff
-Use Reanimated v4. Avoid React Native's built-in Animated API.
+Prefer Reanimated v4 for complex and performance-critical animations. React Native's built-in Animated API is acceptable for simple transitions.
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/animations.md at line 3, Line
3's guidance is too absolute; update the wording to recommend Reanimated v4 for
complex or performance-critical animations while acknowledging React Native's
built-in Animated API remains supported and is appropriate for simple
transitions (as used in frontend/hooks/useEntranceAnimation.ts). Replace "Avoid
React Native's built-in Animated API" with a softer preference statement that
explains when to choose Reanimated v4 versus Animated and references
performance/complexity trade-offs.
```

</details>

</blockquote></details>
<details>
<summary>frontend/components/CategoryPicker.tsx (1)</summary><blockquote>

`20-20`: **Re-trigger entrance animation on modal reopen.**

At Line 20, `useEntranceAnimation` is initialized once; if this component stays mounted and only `visible` toggles, the entrance effect may not replay on subsequent opens. Consider keying/restarting the animation from `visible`.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/CategoryPicker.tsx` at line 20, The entrance animation is
only initialized once via useEntranceAnimation(40, 8) so reopening the modal
(CategoryPicker) while it remains mounted doesn't replay the effect; update the
component to restart or re-create the animation when the modal's visible prop
changes — e.g., pass visible into useEntranceAnimation or call its restart
method inside a useEffect that watches visible (referencing
useEntranceAnimation, entranceStyle, and the CategoryPicker visible prop) so the
entranceStyle is recomputed/restarted whenever visible becomes true.
```

</details>

</blockquote></details>
<details>
<summary>frontend/app/(tabs)/index.tsx (1)</summary><blockquote>

`249-249`: **Simplify unreachable null-check in empty-state condition.**

`listings` is always an array (`allListings.filter(...)`), so `!listings` is unnecessary noise.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(tabs)/index.tsx at line 249, The ternary conditional includes
an unnecessary null-check for listings which is always an array from
allListings.filter(...); update the render condition that currently reads ") :
!listings || listings.length === 0 ? (" by removing the "!listings ||" part so
the branch only checks listings.length === 0, ensuring the empty-state is
determined solely by the array length; locate the conditional surrounding the
variable listings in the component render (where allListings.filter(...) sets
listings) and simplify it accordingly.
```

</details>

</blockquote></details>
<details>
<summary>frontend/components/ListingCard.tsx (1)</summary><blockquote>

`24-51`: **Optional: consolidate entrance animation with the shared hook.**

This block reimplements the same fade/translate pattern already centralized in `frontend/hooks/useEntranceAnimation.ts` (Lines 3-36). Consider extending that hook (e.g., optional duration) and reusing it here to keep animation tuning centralized.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/ListingCard.tsx` around lines 24 - 51, The ListingCard
component duplicates the fade/translate entrance animation implemented in
useEntranceAnimation; extend useEntranceAnimation to accept optional parameters
(e.g., duration and/or delay multiplier) and return the animated values and
style, then remove the local useEffect and local opacity/translateY setup in
ListingCard and call useEntranceAnimation(index, { duration, delayMultiplier })
(or similar) to obtain opacity, translateY, and animatedStyle; update
ListingCard to use the hook's returned animatedStyle instead of its own
animatedStyle so animation tuning is centralized.
```

</details>

</blockquote></details>
<details>
<summary>frontend/app/listings/new.tsx (1)</summary><blockquote>

`139-270`: **Extract a shared listing form component to avoid create/edit drift.**

This form is now near line-for-line duplicated with `frontend/app/listings/[id]/edit.tsx` (fields, image controls, category/condition chips, tags, and button block). Pulling shared UI/validation wiring into one reusable form component will reduce future divergence.

<details>
<summary>🤖 Prompt for AI Agents</summary>

```
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/listings/new.tsx` around lines 139 - 270, The Listing create
form is duplicated in listings/[id]/edit.tsx; extract a reusable ListingForm
component that encapsulates the UI and validation wiring (fields: title,
description, price, category, condition, images with
handleImageChange/handleRemoveImage/handleAddImage, TagInput, submit/cancel
buttons and isSubmitting state) and replace the duplicated JSX in new.tsx and
edit.tsx with <ListingForm initialValues={...} onSubmit={handleSubmit}
onCancel={() => router.back()} /> (or similar); ensure the new component exports
props for initialValues, onChange callbacks, validation, and styles so existing
handlers (handleSubmit, TagInput, images array logic) can be moved into the
component or passed in as props to avoid create/edit drift.
```

</details>

</blockquote></details>

</blockquote></details>

---

<details>
<summary>ℹ️ Review info</summary>

**Configuration used**: defaults

**Review profile**: CHILL

**Plan**: Pro

<details>
<summary>📥 Commits</summary>

Reviewing files that changed from the base of the PR and between a3a5ecca4a925f793e82351ae4b6f78dbf5f67bb and 98ebfde073a956e2e13850b188e0d4e6f14c9b77.

</details>

<details>
<summary>⛔ Files ignored due to path filters (1)</summary>

* `package-lock.json` is excluded by `!**/package-lock.json`

</details>

<details>
<summary>📒 Files selected for processing (69)</summary>

* `.agents/skills/building-native-ui/SKILL.md`
* `.agents/skills/building-native-ui/references/animations.md`
* `.agents/skills/building-native-ui/references/controls.md`
* `.agents/skills/building-native-ui/references/form-sheet.md`
* `.agents/skills/building-native-ui/references/gradients.md`
* `.agents/skills/building-native-ui/references/icons.md`
* `.agents/skills/building-native-ui/references/media.md`
* `.agents/skills/building-native-ui/references/route-structure.md`
* `.agents/skills/building-native-ui/references/search.md`
* `.agents/skills/building-native-ui/references/storage.md`
* `.agents/skills/building-native-ui/references/tabs.md`
* `.agents/skills/building-native-ui/references/toolbar-and-headers.md`
* `.agents/skills/building-native-ui/references/visual-effects.md`
* `.agents/skills/building-native-ui/references/webgpu-three.md`
* `.agents/skills/building-native-ui/references/zoom-transitions.md`
* `.agents/skills/expo-api-routes/SKILL.md`
* `.agents/skills/expo-cicd-workflows/SKILL.md`
* `.agents/skills/expo-cicd-workflows/scripts/fetch.js`
* `.agents/skills/expo-cicd-workflows/scripts/package.json`
* `.agents/skills/expo-cicd-workflows/scripts/validate.js`
* `.agents/skills/expo-deployment/SKILL.md`
* `.agents/skills/expo-deployment/references/app-store-metadata.md`
* `.agents/skills/expo-deployment/references/ios-app-store.md`
* `.agents/skills/expo-deployment/references/play-store.md`
* `.agents/skills/expo-deployment/references/testflight.md`
* `.agents/skills/expo-deployment/references/workflows.md`
* `.agents/skills/expo-dev-client/SKILL.md`
* `.agents/skills/expo-tailwind-setup/SKILL.md`
* `.agents/skills/native-data-fetching/SKILL.md`
* `.agents/skills/native-data-fetching/references/expo-router-loaders.md`
* `.agents/skills/upgrading-expo/SKILL.md`
* `.agents/skills/upgrading-expo/references/expo-av-to-audio.md`
* `.agents/skills/upgrading-expo/references/expo-av-to-video.md`
* `.agents/skills/upgrading-expo/references/native-tabs.md`
* `.agents/skills/upgrading-expo/references/new-architecture.md`
* `.agents/skills/upgrading-expo/references/react-19.md`
* `.agents/skills/upgrading-expo/references/react-compiler.md`
* `.agents/skills/use-dom/SKILL.md`
* `.cursor/settings.json`
* `.eslintrc.js`
* `babel.jest.config.js`
* `backend/.env.example`
* `backend/convex/ResendOTP.ts`
* `backend/convex/auth.config.ts`
* `docs/contributing.md`
* `frontend/app.json`
* `frontend/app/(tabs)/_layout.tsx`
* `frontend/app/(tabs)/index.tsx`
* `frontend/app/(tabs)/search.tsx`
* `frontend/app/(tabs)/settings.tsx`
* `frontend/app/_layout.tsx`
* `frontend/app/auth/login.tsx`
* `frontend/app/listings/[id].tsx`
* `frontend/app/listings/[id]/edit.tsx`
* `frontend/app/listings/new.tsx`
* `frontend/babel.config.js`
* `frontend/components/CategoryPicker.tsx`
* `frontend/components/FilterBar.tsx`
* `frontend/components/HiddenBanner.tsx`
* `frontend/components/ListingCard.tsx`
* `frontend/components/ListingUnavailable.tsx`
* `frontend/components/PriceRangePicker.tsx`
* `frontend/components/TagInput.tsx`
* `frontend/components/TagPicker.tsx`
* `frontend/hooks/useEntranceAnimation.ts`
* `frontend/package.json`
* `frontend/tsconfig.json`
* `jest.config.js`
* `skills-lock.json`

</details>

</details>

<!-- This is an auto-generated comment by CodeRabbit for review status -->

Comment thread .agents/skills/building-native-ui/references/controls.md
Comment thread .agents/skills/building-native-ui/references/controls.md
Comment thread .agents/skills/building-native-ui/references/tabs.md
Comment thread .agents/skills/building-native-ui/references/webgpu-three.md
Comment thread .agents/skills/expo-cicd-workflows/scripts/fetch.js
Comment thread .agents/skills/upgrading-expo/references/react-compiler.md
Comment thread .agents/skills/use-dom/SKILL.md
Comment thread backend/convex/auth.config.ts
Comment thread frontend/app/(tabs)/search.tsx Outdated
Comment thread frontend/babel.config.js Outdated
Copy link
Copy Markdown
Contributor

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

Actionable comments posted: 12

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (2)
frontend/app/auth/login.tsx (1)

55-59: ⚠️ Potential issue | 🟡 Minor

Consider validating that the code contains only digits.

keyboardType="number-pad" suggests numeric input but doesn't prevent non-numeric characters on all platforms (especially web). The validation at line 56 checks length but not content.

🔧 Suggested fix
   const handleVerifyCode = async () => {
-    if (!code.trim() || code.trim().length !== 8) {
-      setError('Please enter the 8-digit code');
+    const trimmedCode = code.trim();
+    if (!trimmedCode || trimmedCode.length !== 8 || !/^\d{8}$/.test(trimmedCode)) {
+      setError('Please enter the 8-digit code (numbers only)');
       return;
     }

Also applies to: 159-165

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

In `@frontend/app/auth/login.tsx` around lines 55 - 59, The code-length check in
handleVerifyCode only enforces an 8-character length but not that the code is
numeric; update handleVerifyCode to validate that the trimmed code is exactly 8
digits (e.g., match against a digits-only pattern) and set a clear error when it
contains non-digit characters, and apply the same digits-only validation to the
other code-validation handler in this module so both enforce an 8-digit numeric
code consistently.
frontend/app/listings/new.tsx (1)

1-393: ⚠️ Potential issue | 🟡 Minor

CI formatting warning still applies here.

GitHub Actions reports Prettier issues; please run formatting on this file to clear the pipeline warning.

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

In `@frontend/app/listings/new.tsx` around lines 1 - 393, This file failed CI
formatting (Prettier) — run the project's formatter on this file (e.g., npm run
format or prettier --write) and save the changes so the Prettier rules are
applied; ensure NewListingScreen and its surrounding imports/JSX/StyleSheet are
properly formatted and that there are no trailing commas/spacing issues left by
the linter before committing.
♻️ Duplicate comments (5)
.agents/skills/expo-dev-client/SKILL.md (1)

170-173: ⚠️ Potential issue | 🔴 Critical

eas update is the wrong command for CLI upgrades (previously flagged).

This issue was raised in a previous review but remains unfixed. The eas update command publishes over-the-air updates to your app, not upgrades the CLI itself. Running this command could inadvertently publish an OTA update to users. The correct way to upgrade the EAS CLI is via the package manager.

📝 Proposed documentation fix
 **Check EAS CLI version:**
 
 ```bash
 eas --version
-eas update
+
+# Upgrade EAS CLI to latest
+npm install --global eas-cli@latest
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-dev-client/SKILL.md around lines 170 - 173, The doc
currently shows the wrong command ("eas update") for upgrading the EAS CLI;
replace the second line in the code block that currently reads "eas update" with
an explicit package-manager install to upgrade the CLI (for example "npm install
--global eas-cli@latest") and keep "eas --version" for verifying the CLI version
so the code block demonstrates checking the version and the correct upgrade
command instead of publishing an OTA update.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/expo-tailwind-setup/SKILL.md (1)</summary><blockquote>

`25-25`: _⚠️ Potential issue_ | _🟠 Major_

**Replace nightly `react-native-css` with a stable release.**

Line 25 still pins a nightly build (`react-native-css@0.0.0-nightly.5ce6396`), which makes this setup less stable for adopters. This was already flagged in an earlier review and appears unresolved.

<details>
<summary>Suggested doc fix</summary>

```diff
-npx expo install tailwindcss@^4 nativewind@5.0.0-preview.2 react-native-css@0.0.0-nightly.5ce6396 `@tailwindcss/postcss` tailwind-merge clsx
+npx expo install tailwindcss@^4 nativewind@5.0.0-preview.2 react-native-css@^3.0.3 `@tailwindcss/postcss` tailwind-merge clsx
As of March 2026, what are the current stable and preview dist-tags/versions for npm packages `react-native-css` and `nativewind`?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-tailwind-setup/SKILL.md at line 25, The install command
pins a nightly build for react-native-css; update the npx install line (the
shown "npx expo install tailwindcss@^4 nativewind@5.0.0-preview.2
react-native-css@0.0.0-nightly.5ce6396 ...") to use a stable react-native-css
release (either remove the explicit nightly version or pin to the official
stable semver) and likewise avoid preview tags for nativewind unless
intentionally desired (replace nativewind@5.0.0-preview.2 with the
stable/nativewind@latest or a documented stable version); make the change in the
npx command and any accompanying text in SKILL.md so the example shows stable
packages instead of the nightly preview build.
.agents/skills/upgrading-expo/references/react-19.md (1)

9-10: ⚠️ Potential issue | 🟠 Major

use should be documented as a flexible alternative, not a replacement for useContext.

This wording is still incorrect for React 19 and can mislead migrations. Both are supported; use is more flexible in specific cases.

Proposed doc fix
-The `use` hook replaces `useContext`:
+The `use` hook is a more flexible alternative to `useContext` in some cases:

- [ ] Replace `useContext` with `use`
+ [ ] Consider `use` where conditional context reads or Promise-based resources are needed
According to the official React 19 docs, does `use(resource)` replace `useContext`, or are both APIs supported with different constraints?

Also applies to: 77-77

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

In @.agents/skills/upgrading-expo/references/react-19.md around lines 9 - 10,
The sentence claiming "`use` replaces `useContext`" is misleading; update the
text to clarify that React 19 supports both APIs and that `use(resource)` is a
more flexible alternative for some patterns but does not remove or replace
`useContext`; specifically edit the statement mentioning `use` and `useContext`
so it reads that both are supported, outline when to prefer `use` (e.g., async
resources, Suspense-driven data) versus `useContext` (context propagation), and
adjust any examples or guidance that assert replacement to instead compare
trade-offs between `use` and `useContext`.
.agents/skills/building-native-ui/references/tabs.md (1)

397-399: ⚠️ Potential issue | 🟠 Major

Fix ThemeProvider prop name (value, not theme).

Lines 397 and 412 use theme={...}; ThemeProvider expects value={...}. This example will mislead consumers and fail in typed usage.

In `@react-navigation/native`, what prop does ThemeProvider accept for the theme object: `value` or `theme`?
🛠 Suggested docs patch
-    <ThemeProvider theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
+    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
       <Stack />
     </ThemeProvider>
-    <ThemeProvider theme={DarkTheme}>
+    <ThemeProvider value={DarkTheme}>
       <Stack />
     </ThemeProvider>

Also applies to: 412-414

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

In @.agents/skills/building-native-ui/references/tabs.md around lines 397 - 399,
The ThemeProvider usage is passing the theme object via the wrong prop name
(`theme`); update both ThemeProvider instances (wrapping <Stack /> and the other
occurrence around the other component) to use value={colorScheme === 'dark' ?
DarkTheme : DefaultTheme} instead of theme={...}; ensure you only rename the
prop on the ThemeProvider components (symbol: ThemeProvider) so typed consumers
of `@react-navigation/native` receive the theme via the expected value prop.
.agents/skills/building-native-ui/references/webgpu-three.md (1)

164-206: ⚠️ Potential issue | 🟠 Major

Prevent re-initializing the WebGPU canvas on every render.

This useEffect has no dependency array, so the setup/cleanup cycle runs each render. In docs code, that’s a costly and misleading pattern.

🔧 Suggested docs patch
-  useEffect(() => {
+  useEffect(() => {
     const context = canvasRef.current!.getContext('webgpu')!;
     const renderer = makeWebGPURenderer(context);
@@
     root.current.render(children);
     return () => {
       if (canvas != null) {
         unmountComponentAtNode(canvas!);
       }
     };
-  });
+  }, [camera, scene]);
+
+  useEffect(() => {
+    if (root.current) {
+      root.current.render(children);
+    }
+  }, [children]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/webgpu-three.md around lines
164 - 206, The effect is missing a dependency array so the WebGPU canvas and
react-three root are recreated on every render; change the useEffect to run only
once by adding an empty dependency array (useEffect(..., [])) and move dynamic
updates (like re-rendering children) into a separate effect that watches
children (e.g. useEffect(() => root.current?.render(children), [children]));
keep the existing cleanup that calls unmountComponentAtNode(canvas) and ensure
you still reference canvasRef, createRoot, root.current.configure, and
root.current.render in their current places.
🟡 Minor comments (13)
.agents/skills/expo-deployment/references/app-store-metadata.md-128-133 (1)

128-133: ⚠️ Potential issue | 🟡 Minor

Make keywords example format consistent across the doc.

Line 131 uses a CSV string inside an array, while other sections show an array of keyword values. Pick one canonical format to avoid copy/paste misconfiguration.

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

In @.agents/skills/expo-deployment/references/app-store-metadata.md around lines
128 - 133, The example for the JSON "keywords" field is inconsistent: it
currently shows a single CSV string inside the array; update it to the canonical
format used elsewhere by making "keywords" an array of individual keyword
strings (e.g., "keywords": ["finance","budget","expense",...]) so copy/paste
users get the correct type; locate the JSON example that contains the "keywords"
key and replace the CSV-in-array value with an array of separate string entries.
.agents/skills/expo-tailwind-setup/SKILL.md-39-40 (1)

39-40: ⚠️ Potential issue | 🟡 Minor

Minor wording/capitalization cleanup for readability.

Line 39 and Line 40 use lowercase product names (lightningcss, expo). In user-facing docs, use canonical names (Lightning CSS, Expo).

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

In @.agents/skills/expo-tailwind-setup/SKILL.md around lines 39 - 40, Update the
user-facing text to use canonical product names: replace occurrences of
"lightningcss" with "Lightning CSS" and "expo" with "Expo" in the SKILL.md
content lines that read "autoprefixer is not needed in Expo because of
lightningcss" and "postcss is included in expo by default" so both references
use proper capitalization and naming. Ensure only the product names are changed
and surrounding wording remains unchanged.
.agents/skills/expo-cicd-workflows/scripts/validate.js-59-63 (1)

59-63: ⚠️ Potential issue | 🟡 Minor

Help flow returns the wrong exit code for --help.

Line 63 exits with code 1 when --help is used without file args, which should be success (0).

Proposed fix
-  if (files.length === 0 || args.includes('--help') || args.includes('-h')) {
+  const showHelp = args.includes('--help') || args.includes('-h');
+  if (files.length === 0 || showHelp) {
@@
-    process.exit(files.length === 0 ? 1 : 0);
+    process.exit(showHelp ? 0 : 1);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-cicd-workflows/scripts/validate.js around lines 59 - 63,
The help path incorrectly returns exit code 1 when no files are passed but help
flags are present; update the exit logic around the conditional that checks
files/args (using the variables files, args and calling process.exit) so that if
args.includes('--help') || args.includes('-h') the script always exits with 0,
otherwise keep the current behavior of exiting 1 when files.length === 0 and 0
when files exist.
.agents/skills/building-native-ui/references/zoom-transitions.md-155-155 (1)

155-155: ⚠️ Potential issue | 🟡 Minor

Use hyphenated form “off-screen” at Line 155.

Minor wording fix for consistency and readability.

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

In @.agents/skills/building-native-ui/references/zoom-transitions.md at line
155, Update the sentence "When source is unavailable (e.g., scrolled off
screen), the transition zooms from the center of the screen" to use the
hyphenated form "off-screen" so it reads "When source is unavailable (e.g.,
scrolled off-screen), the transition zooms from the center of the screen";
locate this exact line in zoom-transitions.md and make the single-word
hyphenation change.
.agents/skills/upgrading-expo/references/react-19.md-22-22 (1)

22-22: ⚠️ Potential issue | 🟡 Minor

Fix the sentence at Line 22 for readability.

The clause uses a comma splice; split into two sentences.

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

In @.agents/skills/upgrading-expo/references/react-19.md at line 22, The
sentence "`use` can be called conditionally, this simplifies components that
consume multiple contexts." contains a comma splice; split it into two sentences
by changing it to "`use` can be called conditionally. This simplifies components
that consume multiple contexts." to improve readability.
.agents/skills/upgrading-expo/references/native-tabs.md-29-29 (1)

29-29: ⚠️ Potential issue | 🟡 Minor

Fix or clarify the iOS version notation in BottomAccessory description.

iOS +26 is both ambiguous in notation and appears inconsistent with documented requirements. The Expo SDK 55 minimum supported iOS version is iOS 15.1+. Clarify what iOS +26 refers to—whether it's a typo (e.g., iOS 16+, iOS 17+), a specific BottomAccessory requirement above the SDK baseline, or something else. Use standard notation like iOS 16+ if specifying a version.

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

In @.agents/skills/upgrading-expo/references/native-tabs.md at line 29, The
BottomAccessory description uses the ambiguous string "iOS +26"; update this to
a standard, unambiguous iOS version notation (e.g., "iOS 16+" or the correct
target such as "iOS 17+") or add a short clarifying parenthetical explaining
what "+26" means, and ensure the text references the Expo SDK 55 baseline (iOS
15.1+) if this requirement is above that baseline; modify the BottomAccessory
line in native-tabs.md to replace "iOS +26" with the chosen standard notation
and a one-line note if needed to avoid confusion.
.agents/skills/building-native-ui/references/tabs.md-305-309 (1)

305-309: ⚠️ Potential issue | 🟡 Minor

Add language to the fenced route-structure block.

Line 305 starts an unlabeled fence; add text to resolve markdownlint MD040.

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

In @.agents/skills/building-native-ui/references/tabs.md around lines 305 - 309,
The fenced route-structure block containing the lines with "_layout.tsx         
# NativeTabs for iOS/Android" and "_layout.web.tsx      # Headless tabs for web
(expo-router/ui)" is missing a language label; update the opening code fence
(the triple backticks that begin that block) to include the label text (i.e.,
change ``` to ```text) to satisfy markdownlint MD040.
.agents/skills/building-native-ui/SKILL.md-14-30 (1)

14-30: ⚠️ Potential issue | 🟡 Minor

Add language identifiers to unlabeled fenced blocks.

Lines 14 and 249 start fences without language tags. Use text for directory-tree snippets to satisfy markdownlint MD040.

Also applies to: 249-256

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

In @.agents/skills/building-native-ui/SKILL.md around lines 14 - 30, The
unlabeled fenced code blocks in SKILL.md (the directory-tree snippets shown in
the references/ block and the other block around lines 249-256) need explicit
language tags to satisfy markdownlint MD040; add the language identifier `text`
to the opening fences for those snippets (i.e., change ``` to ```text for the
directory-tree blocks) so the fenced blocks in the references section and the
block at lines ~249-256 are labeled.
.agents/skills/building-native-ui/references/webgpu-three.md-492-503 (1)

492-503: ⚠️ Potential issue | 🟡 Minor

Add language tags to the last two fenced blocks.

Lines 492 and 507 open unlabeled fences; use text for these tree/decision blocks to resolve markdownlint MD040.

Also applies to: 507-523

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

In @.agents/skills/building-native-ui/references/webgpu-three.md around lines
492 - 503, The final two fenced code blocks that show the project tree
(containing entries like "src/", "components/", "lib/", and filenames such as
"fiber-canvas.tsx", "make-webgpu-renderer.ts", "orbit-controls.tsx") are missing
language labels; update those two fences to use the "text" language tag (i.e.,
change ``` to ```text) so the tree/decision blocks are labeled and satisfy
markdownlint MD040.
.agents/skills/expo-api-routes/SKILL.md-35-43 (1)

35-43: ⚠️ Potential issue | 🟡 Minor

Add a language tag to the fenced block.

Line 35 starts an unlabeled code fence, which triggers markdownlint MD040. Use text for the directory tree block.

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

In @.agents/skills/expo-api-routes/SKILL.md around lines 35 - 43, The fenced
code block that shows the directory tree (the block starting with the lines
showing "app/", "api/", "hello+api.ts", etc.) is unlabeled and triggers
markdownlint MD040; add a language tag "text" to the opening triple backticks so
the block reads as a text code fence (```text) to silence the lint error and
keep the directory tree formatting intact in SKILL.md.
.agents/skills/building-native-ui/references/route-structure.md-18-24 (1)

18-24: ⚠️ Potential issue | 🟡 Minor

Label all directory-tree code fences with a language.

Lines 18, 30, 69, 95, 112, and 155 start unlabeled fences. Add text to satisfy markdownlint MD040.

Also applies to: 30-34, 69-77, 95-106, 112-119, 155-169

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

In @.agents/skills/building-native-ui/references/route-structure.md around lines
18 - 24, The markdown contains unlabeled fenced code blocks showing directory
trees (e.g., the block starting with "app/", "users/", "[id].tsx", and similar
directory-tree blocks elsewhere) which trigger markdownlint MD040; update each
of those code fences to include the language label "text" (so the opening fence
becomes ```text) for all directory-tree examples (including the blocks that
correspond to the examples shown and the other unlabeled blocks referenced) to
satisfy the linter.
frontend/app/(tabs)/_layout.tsx-68-71 (1)

68-71: ⚠️ Potential issue | 🟡 Minor

iOS tab label color may not work correctly in dark mode.

tabTint correctly adapts to dark mode (line 69), but nativeTabLabelStyle (line 70) always applies styles.iosTabLabel which hardcodes color: '#111111'. In dark mode, this results in dark text on a dark background.

🔧 Suggested fix
-  const nativeTabLabelStyle = Platform.OS === 'ios' ? styles.iosTabLabel : undefined;
+  const nativeTabLabelStyle =
+    Platform.OS === 'ios' ? { color: isDarkMode ? '#ffffff' : '#111111' } : undefined;

And remove the unused iosTabLabel style:

-  iosTabLabel: {
-    color: '#111111',
-  },

Also applies to: 161-163

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

In `@frontend/app/`(tabs)/_layout.tsx around lines 68 - 71, The iOS tab label
color is fixed to styles.iosTabLabel (which hardcodes color '#111111') causing
unreadable text in dark mode; update nativeTabLabelStyle assignment to choose
styles.iosTabLabelDark when isDarkMode is true (or compute a dynamic style using
isDarkMode) so the label color matches tabTint, and remove the now-unused
iosTabLabel style definition; change references around nativeTabLabelStyle,
isDarkMode, tabTint, and styles.iosTabLabel so both label color and tint adapt
to dark mode (also apply the same fix to the other occurrence at the later block
referenced).
.agents/skills/building-native-ui/references/media.md-5-5 (1)

5-5: ⚠️ Potential issue | 🟡 Minor

Use “full-screen” in docs text.

Please hyphenate “full-screen” for clarity and consistency in the guidance line.

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

In @.agents/skills/building-native-ui/references/media.md at line 5, The
documentation line "Hide navigation headers when there's a full screen camera"
should be updated to hyphenate "full-screen" for consistency; edit the sentence
in media.md (the line containing "Hide navigation headers when there's a full
screen camera") to read "Hide navigation headers when there's a full-screen
camera".
🧹 Nitpick comments (8)
backend/convex/ResendOTP.ts (1)

43-47: Consider a more precise check for the test sender address.

Using .includes('onboarding@resend.dev') could theoretically match unintended strings (e.g., a custom domain containing that substring). While the practical risk is low since this is a developer-controlled setting, an exact match or a regex anchored to the email portion would be more precise.

♻️ Suggested refinement
-    if (resendFromAddress.toLowerCase().includes('onboarding@resend.dev')) {
+    // Extract email from "Name <email>" format or use as-is
+    const emailMatch = resendFromAddress.match(/<([^>]+)>/) ?? [null, resendFromAddress];
+    const senderEmail = (emailMatch[1] ?? resendFromAddress).toLowerCase().trim();
+    if (senderEmail === 'onboarding@resend.dev') {
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@backend/convex/ResendOTP.ts` around lines 43 - 47, The check in ResendOTP.ts
uses resendFromAddress.toLowerCase().includes('onboarding@resend.dev') which can
match unintended strings; change it to a precise comparison (e.g., compare the
lowercased whole address for equality) or use an anchored regex that validates
the email portion equals onboarding@resend.dev before throwing the ConvexError,
updating the conditional that references resendFromAddress and the ConvexError
message accordingly.
.agents/skills/upgrading-expo/SKILL.md (1)

40-44: Add a cross-platform fallback for cache-clearing commands.

watchman watch-del-all is not available in many environments (especially Windows/CI without Watchman). Add a short fallback note so this step doesn’t block upgrades.

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

In @.agents/skills/upgrading-expo/SKILL.md around lines 40 - 44, The step
currently tells users to run "watchman watch-del-all", which isn't available on
many platforms; update SKILL.md to add a cross-platform fallback note: if
watchman isn't installed, instruct users to either run a platform-safe
cache/cleanup command (examples: "expo start -c", "git clean -fdx" for CI, or
use a Node tool like "npx rimraf .expo" / platform-specific removal on Windows)
or skip with a clear message, and show a brief conditional suggestion like "if
watchman exists run it otherwise run fallback" so the upgrade step doesn't block
on Windows/CI.
.agents/skills/building-native-ui/references/icons.md (1)

16-19: Add language specifier to fenced code block.

The references directory listing should have a language specifier for consistency and to satisfy linting rules.

📝 Suggested fix
-```
+```text
 references/
   expo-router-loaders.md   Route-level data loading with Expo Router loaders (web, SDK 55+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/icons.md around lines 16 - 19,
The fenced code block in .agents/skills/building-native-ui/references/icons.md
is missing a language specifier (closing/backtick block around the references/
listing); update the opening fence to include a language (e.g., ```text) so the
snippet becomes ```text ... ``` to satisfy linting and maintain consistency with
other docs.
.agents/skills/native-data-fetching/SKILL.md (2)

16-19: Add language specifier to fenced code block.

The references directory listing should have a language specifier.

📝 Suggested fix
-```
+```text
 references/
   expo-router-loaders.md   Route-level data loading with Expo Router loaders (web, SDK 55+)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/native-data-fetching/SKILL.md around lines 16 - 19, The
fenced code block showing the references directory listing in SKILL.md is
missing a language specifier; update the opening triple-backtick for the block
that contains "references/" and "expo-router-loaders.md" to include a language
tag (e.g., use ```text) so the block is properly rendered and highlighted in
markdown.

415-449: Add language specifier to decision tree code block.

The decision tree should have a language specifier (e.g., text or plaintext) for consistency.

📝 Suggested fix
-```
+```text
 User asks about networking
   |-- Route-level data loading (web, SDK 55+)?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/native-data-fetching/SKILL.md around lines 415 - 449, The
fenced decision-tree code block starting with "User asks about networking" lacks
a language specifier; update that code fence to include a plain-text language
tag (e.g., ```text or ```plaintext) so the block is consistently highlighted and
formatted—locate the triple-backtick fence before the "User asks about
networking" block in SKILL.md and add the language token immediately after the
opening backticks.
frontend/app/(tabs)/search.tsx (1)

35-36: Consider simplifying the filter version tracking.

Using two separate refs (filterVersionRef and currentFilterVersionRef) that are always incremented and assigned together adds complexity. A single ref would suffice since queryFilterVersion (line 57) captures the current value at render time, which is then compared inside the effect.

♻️ Suggested simplification
-  const filterVersionRef = useRef(0);
-  const currentFilterVersionRef = useRef(0);
+  const filterVersionRef = useRef(0);
   const processedCursorsRef = useRef(new Set<string>());

   // ...

   useEffect(() => {
     filterVersionRef.current += 1;
-    currentFilterVersionRef.current = filterVersionRef.current;
     setCursor(null);
     // ...
   }, [searchTerm]);

-  const queryFilterVersion = currentFilterVersionRef.current;
+  const queryFilterVersion = filterVersionRef.current;

   // ...

   useEffect(() => {
     if (listingsResult && queryFilterVersion === filterVersionRef.current) {

Also applies to: 57-57

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

In `@frontend/app/`(tabs)/search.tsx around lines 35 - 36, The two refs
filterVersionRef and currentFilterVersionRef are redundant—keep a single ref
(e.g., filterVersionRef) and use it both to read the render-time version
(queryFilterVersion) and to mark the effect-consumed version; update places that
increment or assign currentFilterVersionRef to instead increment
filterVersionRef and compare queryFilterVersion against filterVersionRef inside
the useEffect that processes filter changes (the effect that currently checks
those two refs), and remove all references to currentFilterVersionRef to
simplify tracking.
frontend/components/TagInput.tsx (1)

63-97: Comma-based tag addition logic is functional but verbose.

The handleInputChange function handles multiple edge cases (empty tags, duplicates, length limits, max tags). While the logic works correctly, consider extracting the validation into a helper function to reduce nesting depth.

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

In `@frontend/components/TagInput.tsx` around lines 63 - 97, Refactor the
comma-add logic in handleInputChange by extracting the per-tag validation into a
helper (e.g., validateAndProcessTag or isValidTag) so the main function stops
nesting; move checks that use tagToAdd, tags, maxLength, and maxTags into that
helper which should return a result object (status: 'add'|'error'|'ignore',
errorMessage?, remainingText?) so handleInputChange can call the helper, apply
onChange([...tags, tagToAdd]) when status==='add', or setError(errorMessage) and
always setInputText(remainingText) otherwise; keep references to onChange,
setError, setInputText, tags, maxLength, and maxTags intact.
frontend/components/ListingCard.tsx (1)

21-51: Prefer the shared entrance-animation hook here.

This local animation setup duplicates logic now provided by frontend/hooks/useEntranceAnimation.ts; reusing the hook would reduce maintenance drift across cards/screens.

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

In `@frontend/components/ListingCard.tsx` around lines 21 - 51, This component
duplicates entrance animation logic; replace the local refs/effect/memo with the
shared hook by importing useEntranceAnimation and calling it inside ListingCard
to obtain the animated values/style instead of manually creating opacity,
translateY, useEffect, and animatedStyle; remove the local constants (opacity,
translateY, animation useEffect, and animatedStyle) and use the hook's returned
animatedStyle (or values) where the component currently consumes animatedStyle
so ListingCard uses the centralized useEntranceAnimation implementation.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In @.agents/skills/building-native-ui/references/form-sheet.md:
- Around line 42-43: The layout file is incorrectly rendering <Stack.Header> as
a child of <Stack.Screen> (e.g., the <Stack.Header> inside the <Stack.Screen> in
_layout.tsx); remove these nested <Stack.Header> blocks entirely and rely on the
existing options prop (e.g., headerTransparent: true) on <Stack.Screen>, and if
you need to use the composition API (<Stack.Header>, <Stack.Header.Right>, etc.)
move those into the individual route component files (e.g., app/yourRoute.tsx)
rather than keeping them inside the layout.

In @.agents/skills/expo-api-routes/SKILL.md:
- Around line 166-178: The corsHeaders example is too permissive: update the
cors logic used by corsHeaders and the OPTIONS/GET handlers to enforce an
explicit origin allowlist instead of Access-Control-Allow-Origin: '*', add a
"Vary: Origin" header, and only include "Authorization" in
Access-Control-Allow-Headers (and set Access-Control-Allow-Credentials if you
intend to support credentials) when the incoming Origin matches the allowlist;
locate and modify the corsHeaders constant and the OPTIONS and GET functions to
compute headers per-request (using the request Origin) rather than returning a
static permissive object.

In @.agents/skills/expo-cicd-workflows/scripts/validate.js:
- Around line 15-19: The fetchSchema function lacks defensive error handling
around the network/parse/compile path; wrap the fetchCached call and JSON.parse
inside a try/catch in fetchSchema, validate that parsed body and body.data exist
(throw a clear error or return a safe default), and on failure log a descriptive
error and exit with a non-zero code (or rethrow a wrapped error) so the CLI
doesn't crash unpredictably; apply the same pattern to the other parse/compile
step referenced in the diff (the compile/parse call around lines 66-67—e.g.,
compileSchema or wherever JSON.parse/compile is used) to ensure all external
calls and parsing have guarded error handling and clear logs.
- Around line 27-35: The validateFile function currently calls
readFile(filePath, 'utf-8') without handling IO errors, so file read failures
(ENOENT, EACCES, etc.) can crash the process; wrap the readFile call in a
try/catch (or add a separate try around it) and on failure return a per-file
result like { valid: false, error: `File read error: ${err.message}` } (include
err.code or err.message for diagnostics), ensuring the rest of validateFile
(yaml.load and its existing catch) only runs when the file was read
successfully.
- Line 55: The check using import.meta.main is unsupported on Node <22.18.0;
replace it with a Node-compatible entrypoint detection by computing the module
file path via fileURLToPath(import.meta.url) and comparing it to process.argv[1]
(i.e., treat the module as main when fileURLToPath(import.meta.url) ===
process.argv[1]). Update the conditional that currently uses import.meta.main in
validate.js to use this fileURLToPath(import.meta.url) vs process.argv[1]
comparison so it works on Node.js >=18 as declared.

In @.agents/skills/expo-deployment/references/app-store-metadata.md:
- Line 143: Remove the bullet that reads "Include competitor brand names
(carefully)" from app-store-metadata.md and replace it with safer guidance:
advise using descriptive, non-infringing, brand-safe keywords (e.g., feature-
and category-focused terms) and explicitly warn against using competitor
trademarks to avoid App Store policy/trademark issues; update any surrounding
list items or examples to reflect this change.
- Around line 77-83: Replace the plaintext demo credential in the example
object's review.notes with a non-sensitive placeholder and add a brief note to
inject real secrets from secure storage; specifically update the "review"
example (keys: firstName, lastName, email, phone, notes) so notes uses a
placeholder like "Demo account: <USERNAME> / <PASSWORD>" or "DO NOT COMMIT: set
via env/CI", and mention in the surrounding documentation that actual
credentials must be provided from secure environment variables or a secrets
manager during deployment.

In @.agents/skills/expo-deployment/references/ios-app-store.md:
- Around line 39-54: Add an explicit security note warning contributors to never
commit .p8 private keys and recommend using a secret manager or CI-injected
environment variables (the same mechanism shown by EXPO_ASC_API_KEY_PATH /
EXPO_ASC_API_KEY_ISSUER_ID / EXPO_ASC_API_KEY_ID). Insert a short paragraph or
blockquote near the existing examples that states: "Security: Never commit `.p8`
private keys to the repository. Store them in a secure secret manager and inject
paths/contents via CI environment variables." and place it directly above or
below the env var example so readers see the precaution when configuring ASC
keys.

In @.agents/skills/native-data-fetching/references/expo-router-loaders.md:
- Around line 205-215: The current snippet fetches Stripe balance before
validating the request and returns raw balance data; change flow to first
validate the session token (extracting sessionToken from request.headers as
currently done) and refuse/throw or return an unauthenticated response unless
the session is valid, then call the privileged Stripe API only when
authenticated; after fetching, map the Stripe response to a minimal safe shape
(e.g., available_balance and currency) instead of returning the full JSON, and
ensure the fetch uses server-only secret access (Authorization: Bearer from
process.env) without echoing secrets or raw error objects in responses—apply
these changes around the existing sessionToken extraction and the balance
fetch/return logic.

In `@frontend/app/listings/`[id].tsx:
- Around line 47-52: Replace the hardcoded origin when building the share URL so
it uses the environment variable; update the construction of shareUrl (used
before calling Share.share) to read process.env.EXPO_PUBLIC_APP_ORIGIN and
concatenate `/l/${listing._id}` instead of `"https://polybuys.com"`, and ensure
Share.share still receives the same message, url, and title values (refer to
shareUrl, Share.share, and listing._id in the change).

In `@frontend/app/listings/`[id]/edit.tsx:
- Around line 236-240: Update the cancel button logic so it is disabled while
there are pending uploads: in the Pressable that currently uses
disabled={isSubmitting}, include hasPendingUploads in the condition (e.g.,
disabled={isSubmitting || hasPendingUploads}) or alternatively guard router.back
with a confirmation when hasPendingUploads is true; modify the Pressable's
onPress handler (referencing Pressable, onPress, router.back, and the
isSubmitting/hasPendingUploads flags) to prevent navigation during active
uploads or prompt confirmation.

In `@frontend/app/listings/new.tsx`:
- Around line 241-245: The Cancel Pressable currently only uses isSubmitting to
disable navigation and should also block leaving when uploads are in progress;
update the Pressable disabled condition (and any onPress logic that calls
router.back()) to include hasPendingUploads (e.g., disabled={isSubmitting ||
hasPendingUploads}) or show a confirmation prompt before calling router.back();
adjust any related UX (styles.cancelButton/pressed behavior) so the button is
inert while hasPendingUploads is true.

---

Outside diff comments:
In `@frontend/app/auth/login.tsx`:
- Around line 55-59: The code-length check in handleVerifyCode only enforces an
8-character length but not that the code is numeric; update handleVerifyCode to
validate that the trimmed code is exactly 8 digits (e.g., match against a
digits-only pattern) and set a clear error when it contains non-digit
characters, and apply the same digits-only validation to the other
code-validation handler in this module so both enforce an 8-digit numeric code
consistently.

In `@frontend/app/listings/new.tsx`:
- Around line 1-393: This file failed CI formatting (Prettier) — run the
project's formatter on this file (e.g., npm run format or prettier --write) and
save the changes so the Prettier rules are applied; ensure NewListingScreen and
its surrounding imports/JSX/StyleSheet are properly formatted and that there are
no trailing commas/spacing issues left by the linter before committing.

---

Minor comments:
In @.agents/skills/building-native-ui/references/media.md:
- Line 5: The documentation line "Hide navigation headers when there's a full
screen camera" should be updated to hyphenate "full-screen" for consistency;
edit the sentence in media.md (the line containing "Hide navigation headers when
there's a full screen camera") to read "Hide navigation headers when there's a
full-screen camera".

In @.agents/skills/building-native-ui/references/route-structure.md:
- Around line 18-24: The markdown contains unlabeled fenced code blocks showing
directory trees (e.g., the block starting with "app/", "users/", "[id].tsx", and
similar directory-tree blocks elsewhere) which trigger markdownlint MD040;
update each of those code fences to include the language label "text" (so the
opening fence becomes ```text) for all directory-tree examples (including the
blocks that correspond to the examples shown and the other unlabeled blocks
referenced) to satisfy the linter.

In @.agents/skills/building-native-ui/references/tabs.md:
- Around line 305-309: The fenced route-structure block containing the lines
with "_layout.tsx          # NativeTabs for iOS/Android" and "_layout.web.tsx   
# Headless tabs for web (expo-router/ui)" is missing a language label; update
the opening code fence (the triple backticks that begin that block) to include
the label text (i.e., change ``` to ```text) to satisfy markdownlint MD040.

In @.agents/skills/building-native-ui/references/webgpu-three.md:
- Around line 492-503: The final two fenced code blocks that show the project
tree (containing entries like "src/", "components/", "lib/", and filenames such
as "fiber-canvas.tsx", "make-webgpu-renderer.ts", "orbit-controls.tsx") are
missing language labels; update those two fences to use the "text" language tag
(i.e., change ``` to ```text) so the tree/decision blocks are labeled and
satisfy markdownlint MD040.

In @.agents/skills/building-native-ui/references/zoom-transitions.md:
- Line 155: Update the sentence "When source is unavailable (e.g., scrolled off
screen), the transition zooms from the center of the screen" to use the
hyphenated form "off-screen" so it reads "When source is unavailable (e.g.,
scrolled off-screen), the transition zooms from the center of the screen";
locate this exact line in zoom-transitions.md and make the single-word
hyphenation change.

In @.agents/skills/building-native-ui/SKILL.md:
- Around line 14-30: The unlabeled fenced code blocks in SKILL.md (the
directory-tree snippets shown in the references/ block and the other block
around lines 249-256) need explicit language tags to satisfy markdownlint MD040;
add the language identifier `text` to the opening fences for those snippets
(i.e., change ``` to ```text for the directory-tree blocks) so the fenced blocks
in the references section and the block at lines ~249-256 are labeled.

In @.agents/skills/expo-api-routes/SKILL.md:
- Around line 35-43: The fenced code block that shows the directory tree (the
block starting with the lines showing "app/", "api/", "hello+api.ts", etc.) is
unlabeled and triggers markdownlint MD040; add a language tag "text" to the
opening triple backticks so the block reads as a text code fence (```text) to
silence the lint error and keep the directory tree formatting intact in
SKILL.md.

In @.agents/skills/expo-cicd-workflows/scripts/validate.js:
- Around line 59-63: The help path incorrectly returns exit code 1 when no files
are passed but help flags are present; update the exit logic around the
conditional that checks files/args (using the variables files, args and calling
process.exit) so that if args.includes('--help') || args.includes('-h') the
script always exits with 0, otherwise keep the current behavior of exiting 1
when files.length === 0 and 0 when files exist.

In @.agents/skills/expo-deployment/references/app-store-metadata.md:
- Around line 128-133: The example for the JSON "keywords" field is
inconsistent: it currently shows a single CSV string inside the array; update it
to the canonical format used elsewhere by making "keywords" an array of
individual keyword strings (e.g., "keywords":
["finance","budget","expense",...]) so copy/paste users get the correct type;
locate the JSON example that contains the "keywords" key and replace the
CSV-in-array value with an array of separate string entries.

In @.agents/skills/expo-tailwind-setup/SKILL.md:
- Around line 39-40: Update the user-facing text to use canonical product names:
replace occurrences of "lightningcss" with "Lightning CSS" and "expo" with
"Expo" in the SKILL.md content lines that read "autoprefixer is not needed in
Expo because of lightningcss" and "postcss is included in expo by default" so
both references use proper capitalization and naming. Ensure only the product
names are changed and surrounding wording remains unchanged.

In @.agents/skills/upgrading-expo/references/native-tabs.md:
- Line 29: The BottomAccessory description uses the ambiguous string "iOS +26";
update this to a standard, unambiguous iOS version notation (e.g., "iOS 16+" or
the correct target such as "iOS 17+") or add a short clarifying parenthetical
explaining what "+26" means, and ensure the text references the Expo SDK 55
baseline (iOS 15.1+) if this requirement is above that baseline; modify the
BottomAccessory line in native-tabs.md to replace "iOS +26" with the chosen
standard notation and a one-line note if needed to avoid confusion.

In @.agents/skills/upgrading-expo/references/react-19.md:
- Line 22: The sentence "`use` can be called conditionally, this simplifies
components that consume multiple contexts." contains a comma splice; split it
into two sentences by changing it to "`use` can be called conditionally. This
simplifies components that consume multiple contexts." to improve readability.

In `@frontend/app/`(tabs)/_layout.tsx:
- Around line 68-71: The iOS tab label color is fixed to styles.iosTabLabel
(which hardcodes color '#111111') causing unreadable text in dark mode; update
nativeTabLabelStyle assignment to choose styles.iosTabLabelDark when isDarkMode
is true (or compute a dynamic style using isDarkMode) so the label color matches
tabTint, and remove the now-unused iosTabLabel style definition; change
references around nativeTabLabelStyle, isDarkMode, tabTint, and
styles.iosTabLabel so both label color and tint adapt to dark mode (also apply
the same fix to the other occurrence at the later block referenced).

---

Duplicate comments:
In @.agents/skills/building-native-ui/references/tabs.md:
- Around line 397-399: The ThemeProvider usage is passing the theme object via
the wrong prop name (`theme`); update both ThemeProvider instances (wrapping
<Stack /> and the other occurrence around the other component) to use
value={colorScheme === 'dark' ? DarkTheme : DefaultTheme} instead of
theme={...}; ensure you only rename the prop on the ThemeProvider components
(symbol: ThemeProvider) so typed consumers of `@react-navigation/native` receive
the theme via the expected value prop.

In @.agents/skills/building-native-ui/references/webgpu-three.md:
- Around line 164-206: The effect is missing a dependency array so the WebGPU
canvas and react-three root are recreated on every render; change the useEffect
to run only once by adding an empty dependency array (useEffect(..., [])) and
move dynamic updates (like re-rendering children) into a separate effect that
watches children (e.g. useEffect(() => root.current?.render(children),
[children])); keep the existing cleanup that calls
unmountComponentAtNode(canvas) and ensure you still reference canvasRef,
createRoot, root.current.configure, and root.current.render in their current
places.

In @.agents/skills/expo-dev-client/SKILL.md:
- Around line 170-173: The doc currently shows the wrong command ("eas update")
for upgrading the EAS CLI; replace the second line in the code block that
currently reads "eas update" with an explicit package-manager install to upgrade
the CLI (for example "npm install --global eas-cli@latest") and keep "eas
--version" for verifying the CLI version so the code block demonstrates checking
the version and the correct upgrade command instead of publishing an OTA update.

In @.agents/skills/expo-tailwind-setup/SKILL.md:
- Line 25: The install command pins a nightly build for react-native-css; update
the npx install line (the shown "npx expo install tailwindcss@^4
nativewind@5.0.0-preview.2 react-native-css@0.0.0-nightly.5ce6396 ...") to use a
stable react-native-css release (either remove the explicit nightly version or
pin to the official stable semver) and likewise avoid preview tags for
nativewind unless intentionally desired (replace nativewind@5.0.0-preview.2 with
the stable/nativewind@latest or a documented stable version); make the change in
the npx command and any accompanying text in SKILL.md so the example shows
stable packages instead of the nightly preview build.

In @.agents/skills/upgrading-expo/references/react-19.md:
- Around line 9-10: The sentence claiming "`use` replaces `useContext`" is
misleading; update the text to clarify that React 19 supports both APIs and that
`use(resource)` is a more flexible alternative for some patterns but does not
remove or replace `useContext`; specifically edit the statement mentioning `use`
and `useContext` so it reads that both are supported, outline when to prefer
`use` (e.g., async resources, Suspense-driven data) versus `useContext` (context
propagation), and adjust any examples or guidance that assert replacement to
instead compare trade-offs between `use` and `useContext`.

---

Nitpick comments:
In @.agents/skills/building-native-ui/references/icons.md:
- Around line 16-19: The fenced code block in
.agents/skills/building-native-ui/references/icons.md is missing a language
specifier (closing/backtick block around the references/ listing); update the
opening fence to include a language (e.g., ```text) so the snippet becomes
```text ... ``` to satisfy linting and maintain consistency with other docs.

In @.agents/skills/native-data-fetching/SKILL.md:
- Around line 16-19: The fenced code block showing the references directory
listing in SKILL.md is missing a language specifier; update the opening
triple-backtick for the block that contains "references/" and
"expo-router-loaders.md" to include a language tag (e.g., use ```text) so the
block is properly rendered and highlighted in markdown.
- Around line 415-449: The fenced decision-tree code block starting with "User
asks about networking" lacks a language specifier; update that code fence to
include a plain-text language tag (e.g., ```text or ```plaintext) so the block
is consistently highlighted and formatted—locate the triple-backtick fence
before the "User asks about networking" block in SKILL.md and add the language
token immediately after the opening backticks.

In @.agents/skills/upgrading-expo/SKILL.md:
- Around line 40-44: The step currently tells users to run "watchman
watch-del-all", which isn't available on many platforms; update SKILL.md to add
a cross-platform fallback note: if watchman isn't installed, instruct users to
either run a platform-safe cache/cleanup command (examples: "expo start -c",
"git clean -fdx" for CI, or use a Node tool like "npx rimraf .expo" /
platform-specific removal on Windows) or skip with a clear message, and show a
brief conditional suggestion like "if watchman exists run it otherwise run
fallback" so the upgrade step doesn't block on Windows/CI.

In `@backend/convex/ResendOTP.ts`:
- Around line 43-47: The check in ResendOTP.ts uses
resendFromAddress.toLowerCase().includes('onboarding@resend.dev') which can
match unintended strings; change it to a precise comparison (e.g., compare the
lowercased whole address for equality) or use an anchored regex that validates
the email portion equals onboarding@resend.dev before throwing the ConvexError,
updating the conditional that references resendFromAddress and the ConvexError
message accordingly.

In `@frontend/app/`(tabs)/search.tsx:
- Around line 35-36: The two refs filterVersionRef and currentFilterVersionRef
are redundant—keep a single ref (e.g., filterVersionRef) and use it both to read
the render-time version (queryFilterVersion) and to mark the effect-consumed
version; update places that increment or assign currentFilterVersionRef to
instead increment filterVersionRef and compare queryFilterVersion against
filterVersionRef inside the useEffect that processes filter changes (the effect
that currently checks those two refs), and remove all references to
currentFilterVersionRef to simplify tracking.

In `@frontend/components/ListingCard.tsx`:
- Around line 21-51: This component duplicates entrance animation logic; replace
the local refs/effect/memo with the shared hook by importing
useEntranceAnimation and calling it inside ListingCard to obtain the animated
values/style instead of manually creating opacity, translateY, useEffect, and
animatedStyle; remove the local constants (opacity, translateY, animation
useEffect, and animatedStyle) and use the hook's returned animatedStyle (or
values) where the component currently consumes animatedStyle so ListingCard uses
the centralized useEntranceAnimation implementation.

In `@frontend/components/TagInput.tsx`:
- Around line 63-97: Refactor the comma-add logic in handleInputChange by
extracting the per-tag validation into a helper (e.g., validateAndProcessTag or
isValidTag) so the main function stops nesting; move checks that use tagToAdd,
tags, maxLength, and maxTags into that helper which should return a result
object (status: 'add'|'error'|'ignore', errorMessage?, remainingText?) so
handleInputChange can call the helper, apply onChange([...tags, tagToAdd]) when
status==='add', or setError(errorMessage) and always setInputText(remainingText)
otherwise; keep references to onChange, setError, setInputText, tags, maxLength,
and maxTags intact.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 98ebfde and a002909.

⛔ Files ignored due to path filters (1)
  • package-lock.json is excluded by !**/package-lock.json
📒 Files selected for processing (69)
  • .agents/skills/building-native-ui/SKILL.md
  • .agents/skills/building-native-ui/references/animations.md
  • .agents/skills/building-native-ui/references/controls.md
  • .agents/skills/building-native-ui/references/form-sheet.md
  • .agents/skills/building-native-ui/references/gradients.md
  • .agents/skills/building-native-ui/references/icons.md
  • .agents/skills/building-native-ui/references/media.md
  • .agents/skills/building-native-ui/references/route-structure.md
  • .agents/skills/building-native-ui/references/search.md
  • .agents/skills/building-native-ui/references/storage.md
  • .agents/skills/building-native-ui/references/tabs.md
  • .agents/skills/building-native-ui/references/toolbar-and-headers.md
  • .agents/skills/building-native-ui/references/visual-effects.md
  • .agents/skills/building-native-ui/references/webgpu-three.md
  • .agents/skills/building-native-ui/references/zoom-transitions.md
  • .agents/skills/expo-api-routes/SKILL.md
  • .agents/skills/expo-cicd-workflows/SKILL.md
  • .agents/skills/expo-cicd-workflows/scripts/fetch.js
  • .agents/skills/expo-cicd-workflows/scripts/package.json
  • .agents/skills/expo-cicd-workflows/scripts/validate.js
  • .agents/skills/expo-deployment/SKILL.md
  • .agents/skills/expo-deployment/references/app-store-metadata.md
  • .agents/skills/expo-deployment/references/ios-app-store.md
  • .agents/skills/expo-deployment/references/play-store.md
  • .agents/skills/expo-deployment/references/testflight.md
  • .agents/skills/expo-deployment/references/workflows.md
  • .agents/skills/expo-dev-client/SKILL.md
  • .agents/skills/expo-tailwind-setup/SKILL.md
  • .agents/skills/native-data-fetching/SKILL.md
  • .agents/skills/native-data-fetching/references/expo-router-loaders.md
  • .agents/skills/upgrading-expo/SKILL.md
  • .agents/skills/upgrading-expo/references/expo-av-to-audio.md
  • .agents/skills/upgrading-expo/references/expo-av-to-video.md
  • .agents/skills/upgrading-expo/references/native-tabs.md
  • .agents/skills/upgrading-expo/references/new-architecture.md
  • .agents/skills/upgrading-expo/references/react-19.md
  • .agents/skills/upgrading-expo/references/react-compiler.md
  • .agents/skills/use-dom/SKILL.md
  • .cursor/settings.json
  • .eslintrc.js
  • babel.jest.config.js
  • backend/.env.example
  • backend/convex/ResendOTP.ts
  • backend/convex/auth.config.ts
  • docs/contributing.md
  • frontend/app.json
  • frontend/app/(tabs)/_layout.tsx
  • frontend/app/(tabs)/index.tsx
  • frontend/app/(tabs)/search.tsx
  • frontend/app/(tabs)/settings.tsx
  • frontend/app/_layout.tsx
  • frontend/app/auth/login.tsx
  • frontend/app/listings/[id].tsx
  • frontend/app/listings/[id]/edit.tsx
  • frontend/app/listings/new.tsx
  • frontend/babel.config.js
  • frontend/components/CategoryPicker.tsx
  • frontend/components/FilterBar.tsx
  • frontend/components/HiddenBanner.tsx
  • frontend/components/ListingCard.tsx
  • frontend/components/ListingUnavailable.tsx
  • frontend/components/PriceRangePicker.tsx
  • frontend/components/TagInput.tsx
  • frontend/components/TagPicker.tsx
  • frontend/hooks/useEntranceAnimation.ts
  • frontend/package.json
  • frontend/tsconfig.json
  • jest.config.js
  • skills-lock.json
✅ Files skipped from review due to trivial changes (1)
  • .agents/skills/upgrading-expo/references/expo-av-to-video.md
🚧 Files skipped from review as they are similar to previous changes (29)
  • .agents/skills/building-native-ui/references/gradients.md
  • frontend/components/HiddenBanner.tsx
  • .cursor/settings.json
  • .agents/skills/building-native-ui/references/animations.md
  • backend/.env.example
  • .agents/skills/expo-deployment/references/workflows.md
  • .agents/skills/upgrading-expo/references/react-compiler.md
  • .eslintrc.js
  • docs/contributing.md
  • .agents/skills/expo-deployment/SKILL.md
  • .agents/skills/building-native-ui/references/search.md
  • skills-lock.json
  • .agents/skills/upgrading-expo/references/expo-av-to-audio.md
  • .agents/skills/expo-cicd-workflows/SKILL.md
  • .agents/skills/expo-cicd-workflows/scripts/fetch.js
  • .agents/skills/use-dom/SKILL.md
  • frontend/hooks/useEntranceAnimation.ts
  • backend/convex/auth.config.ts
  • frontend/components/CategoryPicker.tsx
  • .agents/skills/upgrading-expo/references/new-architecture.md
  • frontend/app/_layout.tsx
  • frontend/app/(tabs)/index.tsx
  • .agents/skills/expo-deployment/references/play-store.md
  • frontend/components/PriceRangePicker.tsx
  • .agents/skills/building-native-ui/references/storage.md
  • frontend/babel.config.js
  • frontend/tsconfig.json
  • .agents/skills/expo-deployment/references/testflight.md
  • .agents/skills/building-native-ui/references/controls.md

Comment thread .agents/skills/building-native-ui/references/form-sheet.md
Comment thread .agents/skills/expo-api-routes/SKILL.md
Comment thread .agents/skills/expo-cicd-workflows/scripts/validate.js
Comment thread .agents/skills/expo-cicd-workflows/scripts/validate.js
Comment thread .agents/skills/expo-cicd-workflows/scripts/validate.js
Comment thread .agents/skills/expo-deployment/references/ios-app-store.md
Comment thread frontend/app/listings/[id].tsx Outdated
Comment thread frontend/app/listings/[id]/edit.tsx
Comment thread frontend/app/listings/new.tsx
- Deleted Babel configuration file as it is no longer needed.
- Updated Jest configuration to specify Babel config file path.
- Added environment variables for email handling in backend.
- Enhanced email validation and error handling in ResendOTP function.
- Improved frontend components with better loading states and animations.
- Updated dependencies in package-lock.json for compatibility with new Expo version.
- Updated auth configuration to use a unified provider domain from environment variables.
- Simplified search component logic to enhance performance and maintain reactivity for first-page results.
- Removed unnecessary cursor handling for improved clarity in state management.
…n package files

- Upgraded expo-image-manipulator to version 55.0.9 and expo-image-picker to version 55.0.10 in both package-lock.json and package.json for improved functionality and compatibility.
- Added a function to dynamically determine the app origin for shareable links in the ListingDetailScreen.
- Updated the cancel button in both NewListingScreen and EditListingScreen to be disabled during submissions or pending uploads, with visual feedback for disabled state.
Copy link
Copy Markdown
Contributor

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

Actionable comments posted: 8

Note

Due to the large number of review comments, Critical, Major severity comments were prioritized as inline comments.

Caution

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

⚠️ Outside diff range comments (2)
frontend/app/listings/new.tsx (1)

66-69: ⚠️ Potential issue | 🟠 Major

Treat empty price as missing before numeric parsing.

Line 66 currently allows an empty input ('') to pass as 0. Add an explicit non-empty check before Number(...).

🛠️ Proposed fix
-    const parsedPrice = Number(price);
+    const normalizedPrice = price.trim();
+    if (!normalizedPrice) {
+      Alert.alert('Missing fields', 'Price is required.');
+      return;
+    }
+
+    const parsedPrice = Number(normalizedPrice);
     if (!Number.isFinite(parsedPrice) || parsedPrice < 0) {
       Alert.alert('Invalid price', 'Please enter a valid non-negative price.');
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/listings/new.tsx` around lines 66 - 69, The current logic
converts price to a Number which treats an empty string as 0; before calling
Number(price) and checking Number.isFinite(parsedPrice) in the form submission
(the code that defines parsedPrice and uses Alert.alert), add an explicit check
that price is non-empty (e.g., price !== '' or price.trim() !== '') and treat an
empty value as missing (show the same "Invalid price" Alert.alert or a specific
"Price required" message) and return early; keep the subsequent Number(...) and
Number.isFinite(parsedPrice) / parsedPrice < 0 checks unchanged.
frontend/app/auth/login.tsx (1)

100-104: ⚠️ Potential issue | 🟡 Minor

Clear success state when returning to the email step

handleBack leaves successMessage intact, so the resend-success banner can appear on the email step with stale context.

🔧 Suggested fix
   const handleBack = () => {
     setStep('email');
     setCode('');
     setError(null);
+    setSuccessMessage(null);
   };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/auth/login.tsx` around lines 100 - 104, handleBack currently
resets step, code, and error but leaves successMessage set, causing a stale
resend-success banner on the email step; update the handler (handleBack) to also
clear the success state by invoking the state setter for successMessage (e.g.,
setSuccessMessage(null) or an empty string) when navigating back so the banner
is removed alongside setStep('email'), setCode(''), and setError(null).
♻️ Duplicate comments (17)
.agents/skills/expo-dev-client/SKILL.md (1)

168-173: ⚠️ Potential issue | 🟠 Major

Replace incorrect CLI upgrade command in Line 172.

eas update publishes app OTA updates; it does not upgrade EAS CLI. In this section, keep eas --version and replace Line 172 with an actual CLI upgrade command.

Proposed doc fix
 **Check EAS CLI version:**
 
 ```bash
 eas --version
-eas update
+# Upgrade EAS CLI
+npm install --global eas-cli@latest
</details>

  

```web
Expo official docs: what does `eas update` do, and what is the recommended way to upgrade/install the latest `eas-cli`?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-dev-client/SKILL.md around lines 168 - 173, In the
"Check EAS CLI version" doc block replace the incorrect `eas update` command
(which publishes OTA updates) with the actual CLI upgrade instruction; keep the
existing `eas --version` check and change the second command to the recommended
installer command (for example, use npm install --global eas-cli@latest) so the
section correctly verifies version and shows how to upgrade the EAS CLI.
.agents/skills/use-dom/SKILL.md (2)

261-271: ⚠️ Potential issue | 🟠 Major

Document missing router caveats (canGoBack / canDismiss).

The unsupported-routing section should also call out router.canGoBack() and router.canDismiss() as values to marshal from native, alongside the listed synchronous hooks.

Proposed doc patch
 These hooks don't work directly in DOM components because they need synchronous access to native routing state:
@@
 - `useRootNavigation()`
 - `useRootNavigationState()`
+- `router.canGoBack()`
+- `router.canDismiss()`
Expo DOM components + expo-router: are `router.canGoBack()` and `router.canDismiss()` unsupported directly inside DOM components and expected to be marshalled from native?
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/use-dom/SKILL.md around lines 261 - 271, The docs list router
hooks that require synchronous native routing state but omitted
router.canGoBack() and router.canDismiss(); update the "Router APIs That Require
Props" section to explicitly mention that router.canGoBack() and
router.canDismiss() are unsupported directly in DOM components and must be
marshalled from native (alongside useLocalSearchParams(), usePathname(),
useSegments(), useRootNavigation(), useRootNavigationState(), etc.), and add a
short note or example clarifying that these boolean methods should be derived on
the native side and passed into the DOM component as props.

63-71: ⚠️ Potential issue | 🟠 Major

Normalize dom prop guidance (currently contradictory).

Line 63 says “Every DOM component receives dom…”, but examples alternate between required and optional typing. Make the docs consistent by typing dom?: import('expo/dom').DOMProps unless an example specifically requires explicit webview options.

Proposed doc patch
-Every DOM component receives a special `dom` prop for webview configuration. Always type it in your props:
+DOM components can receive a special `dom` prop for webview configuration. Type it as optional unless your component requires specific webview options:
-export default function WebChart({ data }: { data: number[]; dom: import('expo/dom').DOMProps }) {
+export default function WebChart({ data }: { data: number[]; dom?: import('expo/dom').DOMProps }) {
Expo DOM components docs: is the `dom` prop optional at runtime and should TypeScript props usually declare `dom?: DOMProps`?

Also applies to: 39-40, 180-181, 206-207, 231-232, 249-250, 331-332

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

In @.agents/skills/use-dom/SKILL.md around lines 63 - 71, The docs are
inconsistent about whether the special dom prop is required; update the examples
to make dom optional by changing prop typings like the interface named Props
(the example block starting with 'interface Props { content: string; dom:
import('expo/dom').DOMProps; }') to use dom?: import('expo/dom').DOMProps so
examples consistently show dom as optional unless a specific example needs
explicit webview options; apply the same normalization to all other example
occurrences referenced (lines around 39-40, 180-181, 206-207, 231-232, 249-250,
331-332) so every example uses dom? unless the example explicitly documents
required webview options.
.agents/skills/expo-cicd-workflows/scripts/validate.js (3)

55-55: ⚠️ Potential issue | 🟠 Major

Replace import.meta.main with a Node-compatible main-module check.

Line 55 depends on import.meta.main, which is not consistently available across older Node runtimes. Use fileURLToPath(import.meta.url) === process.argv[1] for broader compatibility.

Proposed fix
 import { resolve } from 'node:path';
+import { fileURLToPath } from 'node:url';
 import process from 'node:process';
@@
-if (import.meta.main) {
+const isMain = process.argv[1] === fileURLToPath(import.meta.url);
+if (isMain) {
#!/bin/bash
set -euo pipefail

# Verify declared Node engine range(s)
rg -n -A3 -B2 '"engines"\s*:\s*\{' package.json .agents/skills/expo-cicd-workflows/scripts/package.json

# Verify use of import.meta.main in this script
rg -nP '\bimport\.meta\.main\b' .agents/skills/expo-cicd-workflows/scripts/validate.js
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-cicd-workflows/scripts/validate.js at line 55, The
script uses import.meta.main which is not available on older Node runtimes;
replace the main-module check by converting import.meta.url to a file path and
comparing it to process.argv[1] (use fileURLToPath(import.meta.url) ===
process.argv[1]) so the top-level invocation detection is Node-compatible; add
the import/require for fileURLToPath if needed and update the conditional that
currently references import.meta.main to use this new comparison (look for the
occurrence of import.meta.main and import.meta.url in validate.js).

27-35: ⚠️ Potential issue | 🟠 Major

Handle file read errors per file instead of crashing the whole run.

Line 28 can throw and currently bypasses your YAML error path. Return a per-file validation error when read fails.

Proposed fix
 async function validateFile(validator, filePath) {
-  const content = await readFile(filePath, 'utf-8');
+  let content;
+  try {
+    content = await readFile(filePath, 'utf-8');
+  } catch (e) {
+    return { valid: false, error: `File read error: ${e.message}` };
+  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-cicd-workflows/scripts/validate.js around lines 27 - 35,
The validateFile function currently calls readFile(filePath, 'utf-8') which can
throw and bypass the existing YAML parse error handling; wrap the readFile call
in its own try/catch inside validateFile (before calling yaml.load) and on error
return a per-file validation object like { valid: false, error: `File read
error: ${err.message}` } (include filePath/context if useful) so failures to
read one file don't crash the whole run; keep the existing yaml.load try/catch
for parse errors.

15-19: ⚠️ Potential issue | 🟠 Major

Guard schema fetch/parse/compile failures and return a deterministic CLI error.

Line 15 and Line 66 currently allow uncaught failures in fetch/JSON parse/schema compile. This can abort the validator without a clear CI-facing error report.

Proposed fix
 async function fetchSchema() {
-  const data = await fetchCached(SCHEMA_URL);
-  const body = JSON.parse(data);
-  return body.data;
+  let body;
+  try {
+    const data = await fetchCached(SCHEMA_URL);
+    body = JSON.parse(data);
+  } catch (e) {
+    throw new Error(`Failed to fetch/parse workflow schema: ${e.message}`);
+  }
+  if (!body?.data || typeof body.data !== 'object') {
+    throw new Error('Invalid schema response: missing `data` object');
+  }
+  return body.data;
 }
@@
-  const schema = await fetchSchema();
-  const validator = createValidator(schema);
+  let validator;
+  try {
+    const schema = await fetchSchema();
+    validator = createValidator(schema);
+  } catch (e) {
+    console.error(`Failed to initialize schema validator: ${e.message}`);
+    process.exit(1);
+  }

Also applies to: 66-67

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

In @.agents/skills/expo-cicd-workflows/scripts/validate.js around lines 15 - 19,
The fetchSchema flow (async function fetchSchema) currently permits uncaught
failures from fetchCached and JSON.parse (and similarly the schema compile step
referenced around lines 66-67); wrap the fetchCached call, JSON.parse, and the
schema compilation in a single try/catch, validate that the parsed object has
the expected data property before returning, and on any error log a
deterministic, CI-friendly error message and exit with a non-zero status (or
throw a well-formed Error) so the validator fails predictably instead of
aborting with an uncaught exception.
.agents/skills/expo-deployment/references/app-store-metadata.md (2)

143-143: ⚠️ Potential issue | 🟠 Major

Remove recommendation to use competitor brand names in keywords.

Line 143 can lead to trademark/policy risk in App Store submissions. Prefer descriptive, non-infringing, brand-safe terms instead.

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

In @.agents/skills/expo-deployment/references/app-store-metadata.md at line 143,
Remove the recommendation "Include competitor brand names (carefully)" and
replace it with guidance to avoid competitor/trademarked brand names in App
Store keywords and metadata; update the text surrounding the phrase so it
advises using descriptive, non-infringing, brand-safe keywords and terms instead
(search for the exact phrase "Include competitor brand names (carefully)" to
locate the line).

77-83: ⚠️ Potential issue | 🟠 Major

Remove plaintext credential patterns from review examples.

Lines 77-83 and 377-387 normalize storing demo usernames/passwords directly in config examples. Use placeholders and explicitly direct readers to secure secret injection.

Suggested patch
-      "notes": "Demo account: test@example.com / password123"
+      "notes": "Demo account credentials are injected via secure CI secrets. Do not commit real credentials."
@@
-    "demoUsername": "demo@example.com",
-    "demoPassword": "ReviewDemo2025!",
-    "notes": "To test premium features:\n1. Log in with demo credentials\n2. Navigate to Settings > Subscription\n3. Tap 'Restore Purchase' - sandbox purchase will be restored\n\nFor location features, allow location access when prompted."
+    "demoUsername": "<DEMO_USERNAME_FROM_SECRET>",
+    "demoPassword": "<DEMO_PASSWORD_FROM_SECRET>",
+    "notes": "Demo credentials are provided securely via CI/secret manager. Do not commit real credentials."

Also applies to: 377-387

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

In @.agents/skills/expo-deployment/references/app-store-metadata.md around lines
77 - 83, Replace plaintext demo credentials in the "review" example objects (the
review JSON with keys firstName, lastName, email, phone, notes) with
non-sensitive placeholders (e.g., "<REVIEWER_EMAIL>", "<DEMO_USERNAME>",
"<DEMO_PASSWORD>") and add a brief note directing readers to inject real secrets
via secure secret management or environment/CI secret injection rather than
hardcoding; update both occurrences of the "review" example to use placeholders
and the explanatory sentence about secure secret injection.
.agents/skills/expo-deployment/references/ios-app-store.md (1)

39-54: ⚠️ Potential issue | 🟠 Major

Add an explicit “never commit .p8 keys” warning near the API key examples.

This security warning is still missing around Lines 39-54 where key paths/env vars are shown.

Suggested patch
 Or use environment variables:
@@
 EXPO_ASC_API_KEY_ID=XXXXXXXXXX

+> Security: Never commit .p8 private keys to the repository. Store them in a secure secret manager and inject paths/contents via CI environment variables.

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-deployment/references/ios-app-store.md around lines 39 -
54, Add an explicit security warning immediately above or below the API key
examples that show "ascApiKeyPath" and the corresponding EXPO_ASC_* environment
variables: state clearly "Security: Never commit .p8 private keys to the
repository. Store them in a secure secret manager and inject paths/contents via
CI environment variables." Ensure this notice is prominent and adjacent to the
code block that lists ascApiKeyPath/ascApiKeyIssuerId/ascApiKeyId and the
EXPO_ASC_API_KEY_* env var examples so readers cannot miss it.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/expo-api-routes/SKILL.md (1)</summary><blockquote>

`166-178`: _⚠️ Potential issue_ | _🟠 Major_

**Harden the default CORS template; current example is overly permissive.**

Line 167 uses wildcard origin while Line 169 allows `Authorization`. For auth-capable APIs, this should be origin-scoped and include `Vary: Origin`.




<details>
<summary>🔐 Suggested docs patch</summary>

```diff
-const corsHeaders = {
-  'Access-Control-Allow-Origin': '*',
-  'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
-  'Access-Control-Allow-Headers': 'Content-Type, Authorization',
-};
+const ALLOWED_ORIGINS = ['https://app.example.com'];
+
+function corsHeaders(origin: string | null) {
+  const allowOrigin = origin && ALLOWED_ORIGINS.includes(origin) ? origin : ALLOWED_ORIGINS[0];
+  return {
+    'Access-Control-Allow-Origin': allowOrigin,
+    Vary: 'Origin',
+    'Access-Control-Allow-Credentials': 'true',
+    'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
+    'Access-Control-Allow-Headers': 'Content-Type, Authorization',
+  };
+}

-export function OPTIONS() {
-  return new Response(null, { headers: corsHeaders });
+export function OPTIONS(request: Request) {
+  return new Response(null, { headers: corsHeaders(request.headers.get('Origin')) });
 }

-export function GET() {
-  return Response.json({ data: 'value' }, { headers: corsHeaders });
+export function GET(request: Request) {
+  return Response.json({ data: 'value' }, { headers: corsHeaders(request.headers.get('Origin')) });
 }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-api-routes/SKILL.md around lines 166 - 178, The CORS
template is too permissive: update the cors handling in the OPTIONS and GET
handlers to echo the incoming Origin header (instead of '*') and add a Vary:
Origin header so caches handle per-origin responses; preserve allowed methods
and headers (including Authorization) but ensure you read
request.headers.get('Origin') and set 'Access-Control-Allow-Origin' to that
value (or omit if absent), and include 'Vary': 'Origin' in the response headers;
adjust corsHeaders usage or build headers per-request inside the OPTIONS and GET
functions so the origin is scoped securely.
.agents/skills/native-data-fetching/references/expo-router-loaders.md (1)

201-215: ⚠️ Potential issue | 🟠 Major

Gate authentication before privileged fetch and avoid returning raw Stripe payload.

At Line 205, the Stripe balance call happens before auth is validated (Line 211), and Line 214 returns the full upstream object. This example should gate first and return a minimal safe shape.

🔐 Suggested docs patch
-import { type LoaderFunction } from 'expo-server';
+import { StatusError, type LoaderFunction } from 'expo-server';

 export const loader: LoaderFunction<{ balance: any; isAuthenticated: boolean }> = async (
   request,
   params
 ) => {
+  const sessionToken = request?.headers.get('cookie')?.match(/session=([^;]+)/)?.[1];
+  if (!sessionToken) {
+    throw new StatusError(401, 'Unauthorized');
+  }
+
   const data = await fetch('https://api.stripe.com/v1/balance', {
     headers: {
       Authorization: `Bearer ${process.env.STRIPE_SECRET_KEY}`,
     },
   });

-  const sessionToken = request?.headers.get('cookie')?.match(/session=([^;]+)/)?.[1];
-
   const balance = await data.json();
-  return { balance, isAuthenticated: !!sessionToken };
+  return {
+    balance: { available: balance.available },
+    isAuthenticated: true,
+  };
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/native-data-fetching/references/expo-router-loaders.md around
lines 201 - 215, The loader currently calls
fetch('https://api.stripe.com/v1/balance') before checking authentication and
returns the raw Stripe payload in balance; change loader to first extract and
validate sessionToken (request.headers.get + match) and immediately
deny/short-circuit (isAuthenticated: false) if missing/invalid, only then
perform the Stripe fetch when authenticated, and when building the return value
map the Stripe response into a minimal safe shape (e.g., only numeric balances
like available and pending or other specific fields needed) instead of returning
the full balance object to avoid exposing upstream metadata/secrets.
.agents/skills/building-native-ui/references/controls.md (2)

165-170: ⚠️ Potential issue | 🟠 Major

Stepper is not exported by React Native core.

import { Stepper } from 'react-native' will not compile. React Native does not provide a Stepper component. Use a third-party package such as react-native-ios-kit, react-native-ui-stepper, or react-native-simple-stepper.

Suggested fix
-import { Stepper } from 'react-native';
+// React Native core does not provide Stepper.
+// Use a third-party package. Example using react-native-ios-kit:
+// npm install react-native-ios-kit
+import { Stepper } from 'react-native-ios-kit';
 import { useState } from 'react';

 const [count, setCount] = useState(0);

 <Stepper value={count} onValueChange={setCount} minimumValue={0} maximumValue={10} />;

,

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

In @.agents/skills/building-native-ui/references/controls.md around lines 165 -
170, The file incorrectly imports Stepper from React Native core; replace the
import of Stepper with a supported third‑party package (e.g.,
react-native-ios-kit, react-native-ui-stepper, or react-native-simple-stepper)
and update the import statement to the chosen package, keeping the component
usage (Stepper, value, onValueChange, minimumValue, maximumValue) and state
hooks (useState, setCount) intact; ensure you install the package, import the
correct exported component name from that package, and adjust any prop names if
the chosen library uses different prop names than minimumValue/maximumValue.

56-63: ⚠️ Potential issue | 🟠 Major

SegmentedControl.values uses unsupported object shape — must be string array.

The values prop for @react-native-segmented-control/segmented-control accepts only string[], not objects with label and icon properties. This example will fail type-checking and at runtime.

Suggested fix
 <SegmentedControl
-  values={[
-    { label: 'List', icon: 'list.bullet' },
-    { label: 'Grid', icon: 'square.grid.2x2' },
-  ]}
+  values={['List', 'Grid']}
   selectedIndex={index}
   onChange={({ nativeEvent }) => setIndex(nativeEvent.selectedSegmentIndex)}
 />

,

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

In @.agents/skills/building-native-ui/references/controls.md around lines 56 -
63, The example passes an array of objects to SegmentedControl.values which only
accepts string[]; update the SegmentedControl usage (the values prop on the
SegmentedControl component) to supply an array of strings (e.g.,
['List','Grid']) instead of objects with label/icon, keep selectedIndex and
onChange/setIndex as-is, and if icons are required move icon rendering to a
custom component or use a different control that supports icons.
.agents/skills/building-native-ui/references/webgpu-three.md (1)

164-206: ⚠️ Potential issue | 🟠 Major

Add dependency array to useEffect to prevent re-initialization on every render.

The useEffect in FiberCanvas has no dependency array, causing the WebGPU renderer to be repeatedly initialized on each render. This is incorrect and misleading for developers copying this sample code.

Suggested fix
   useEffect(() => {
     // ...existing initialization code...
     return () => {
       if (canvas != null) {
         unmountComponentAtNode(canvas!);
       }
     };
-  });
+  }, [children, scene, camera]);

Note: If children, scene, or camera shouldn't trigger re-initialization, use [] for mount-only behavior and handle updates differently.

,

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

In @.agents/skills/building-native-ui/references/webgpu-three.md around lines
164 - 206, The useEffect in FiberCanvas is missing a dependency array, causing
createRoot, makeWebGPURenderer and related initialization (canvasRef,
root.current, renderer, and the onCreated hook) to run on every render; add an
appropriate dependency array to the useEffect (e.g. [] for mount-only or
[children, scene, camera] if those should trigger re-init) so the WebGPU
renderer and root are only initialized when intended and avoid repeated
re-initialization.
.agents/skills/building-native-ui/references/tabs.md (1)

397-397: ⚠️ Potential issue | 🟠 Major

Fix ThemeProvider prop name: use value, not theme.

The ThemeProvider component from @react-navigation/native expects the value prop, not theme. The current examples will fail type-checking and at runtime.

Suggested fix
 // Line 397
-    <ThemeProvider theme={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
+    <ThemeProvider value={colorScheme === 'dark' ? DarkTheme : DefaultTheme}>
       <Stack />
     </ThemeProvider>

 // Line 412
-    <ThemeProvider theme={DarkTheme}>
+    <ThemeProvider value={DarkTheme}>
       <Stack />
     </ThemeProvider>

,

Also applies to: 412-412

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

In @.agents/skills/building-native-ui/references/tabs.md at line 397, The
ThemeProvider from `@react-navigation/native` is using the wrong prop name;
replace the `theme` prop with `value` on the ThemeProvider instance so it
receives the theme object (e.g., change ThemeProvider theme={colorScheme ===
'dark' ? DarkTheme : DefaultTheme} to use the `value` prop). Update both
occurrences (the one referencing `colorScheme`, `DarkTheme`, and `DefaultTheme`)
so type-checking and runtime behavior work with ThemeProvider.
.agents/skills/building-native-ui/references/form-sheet.md (1)

42-43: ⚠️ Potential issue | 🟠 Major

Remove nested <Stack.Header> from <Stack.Screen> layout examples.

These snippets show an invalid Expo Router pattern in _layout.tsx. Keep header config in options for layout-level screens, and use composition API in route component files instead.

In Expo Router (SDK 55), can <Stack.Screen> in app/_layout.tsx render children like <Stack.Header>, or should it only be configured via name/options?
🛠️ Proposed doc correction
       <Stack.Screen
         name="about"
         options={{
           presentation: 'formSheet',
           sheetAllowedDetents: [0.25],
           headerTransparent: true,
           contentStyle: { backgroundColor: 'transparent' },
           sheetGrabberVisible: true,
         }}
-      >
-        <Stack.Header style={{ backgroundColor: 'transparent' }}></Stack.Header>
-      </Stack.Screen>
+      />
       <Stack.Screen
         name="confirm"
         options={{
           contentStyle: { backgroundColor: 'transparent' },
           presentation: 'formSheet',
           title: '',
           sheetGrabberVisible: true,
           sheetAllowedDetents: [0.25],
           headerTransparent: true,
         }}
-      >
-        <Stack.Header style={{ backgroundColor: 'transparent' }}>
-          <Stack.Header.Right />
-        </Stack.Header>
-      </Stack.Screen>
+      />

Also applies to: 154-156

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

In @.agents/skills/building-native-ui/references/form-sheet.md around lines 42 -
43, The example incorrectly nests <Stack.Header> inside <Stack.Screen> in
_layout.tsx; remove any nested <Stack.Header> from the <Stack.Screen> examples
and instead configure headers via the <Stack.Screen options={...}> prop for
layout-level screens, and demonstrate using the composition API (i.e., placing
<Stack.Header /> inside individual route component files) to customize per-route
headers; update snippets referencing Stack.Screen and Stack.Header accordingly.
frontend/app/(tabs)/index.tsx (1)

82-94: ⚠️ Potential issue | 🟠 Major

Keep the initial page reactive instead of deduping it

Using 'initial' in processedCursorsRef blocks later cursor === null updates, which can freeze first-page results after the first fetch.

🔧 Suggested fix
   useEffect(() => {
     if (listingsResult && queryFilterVersion === filterVersionRef.current) {
-      const cursorId = cursor || 'initial';
-      if (!processedCursorsRef.current.has(cursorId)) {
-        if (cursor === null) {
-          setAllListings(listingsResult.page);
-        } else {
-          setAllListings((prev) => [...prev, ...listingsResult.page]);
-        }
-        setIsDone(listingsResult.isDone);
-        setIsLoadingMore(false);
-        processedCursorsRef.current.add(cursorId);
-      }
+      if (cursor === null) {
+        setAllListings(listingsResult.page);
+        setIsDone(listingsResult.isDone);
+        setIsLoadingMore(false);
+        return;
+      }
+
+      if (!processedCursorsRef.current.has(cursor)) {
+        setAllListings((prev) => [...prev, ...listingsResult.page]);
+        setIsDone(listingsResult.isDone);
+        setIsLoadingMore(false);
+        processedCursorsRef.current.add(cursor);
+      }
     }
   }, [listingsResult, cursor, queryFilterVersion]);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/`(tabs)/index.tsx around lines 82 - 94, The code currently maps
cursor === null to the string 'initial' and stores it in processedCursorsRef,
which prevents subsequent first-page updates; change the logic so that null (the
initial page) is never deduped: when cursor === null always treat it as a fresh
load (call setAllListings to replace, update setIsDone/setIsLoadingMore) and do
not add any 'initial' key to processedCursorsRef; only add actual non-null
cursor values to processedCursorsRef to dedupe subsequent pages. Update the
useEffect containing listingsResult, queryFilterVersion, filterVersionRef,
cursor, processedCursorsRef, and setAllListings to implement this behavior.
🟡 Minor comments (14)
.agents/skills/native-data-fetching/SKILL.md-16-19 (1)

16-19: ⚠️ Potential issue | 🟡 Minor

Add a language to this fenced code block.

This block is missing a language identifier, which triggers markdownlint MD040 and reduces editor rendering quality.

Suggested fix
-```
+```text
 references/
   expo-router-loaders.md   Route-level data loading with Expo Router loaders (web, SDK 55+)
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/native-data-fetching/SKILL.md around lines 16 - 19, The
fenced code block that contains the directory listing starting with
"references/" is missing a language identifier; update the opening
triple-backtick fence to include a language (e.g., change totext) so the
block becomes "```text" to satisfy markdownlint MD040 and improve editor
rendering in SKILL.md.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/native-data-fetching/SKILL.md-415-449 (1)</summary><blockquote>

`415-449`: _⚠️ Potential issue_ | _🟡 Minor_

**Add a language to the decision-tree fenced block.**

This code fence also lacks a language tag (MD040). Use `text` for plain decision-tree formatting.


<details>
<summary>Suggested fix</summary>

```diff
-```
+```text
 User asks about networking
   |-- Route-level data loading (web, SDK 55+)?
   |   \-- Expo Router loaders — see references/expo-router-loaders.md
@@
       |-- Deduplication -> React Query handles this
       \-- Cancellation -> AbortController or React Query
 ```
```
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/native-data-fetching/SKILL.md around lines 415 - 449, The
fenced decision-tree block starting with "User asks about networking" is missing
a language tag; update the triple-backtick fence to include "text" (i.e.,

.agents/skills/native-data-fetching/SKILL.md; ensure the opening fence is
changed only to ```text and the closing fence remains ``` without altering the
block content.
.agents/skills/native-data-fetching/SKILL.md-497-500 (1)

497-500: ⚠️ Potential issue | 🟡 Minor

Fix env var prefix typo in examples.

EXPO*PUBLIC* is incorrect and can mislead readers; it should be EXPO_PUBLIC_.

Suggested fix
-User: "How do I configure different API URLs for dev and prod?"
--> Use EXPO*PUBLIC* env vars with .env.development and .env.production files
+User: "How do I configure different API URLs for dev and prod?"
+-> Use EXPO_PUBLIC_ env vars with .env.development and .env.production files

 User: "Where should I put my API key?"
--> Client-safe keys: EXPO*PUBLIC* in .env. Secret keys: non-prefixed env vars in API routes only
+-> Client-safe keys: EXPO_PUBLIC_ in .env. Secret keys: non-prefixed env vars in API routes only
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/native-data-fetching/SKILL.md around lines 497 - 500, Update
the typoed env var prefix occurrences `EXPO*PUBLIC*` in the SKILL.md examples
and explanatory text to the correct `EXPO_PUBLIC_` (e.g., in the "Use
EXPO*PUBLIC* env vars with .env.development and .env.production files" line and
the "Client-safe keys: EXPO*PUBLIC* in .env" sentence), ensuring all example env
var names and guidance mention `EXPO_PUBLIC_` consistently.
.agents/skills/upgrading-expo/references/react-19.md-22-22 (1)

22-22: ⚠️ Potential issue | 🟡 Minor

Fix comma splice.

The sentence contains a comma splice. Use a semicolon, conjunction, or split into two sentences for proper grammar.

✍️ Suggested grammar fix
-- `use` can be called conditionally, this simplifies components that consume multiple contexts.
+- `use` can be called conditionally, which simplifies components that consume multiple contexts.

Or alternatively:

-- `use` can be called conditionally, this simplifies components that consume multiple contexts.
+- `use` can be called conditionally; this simplifies components that consume multiple contexts.

As per static analysis hint: "To form a complete sentence, be sure to include a subject."

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

In @.agents/skills/upgrading-expo/references/react-19.md at line 22, The
sentence "`use` can be called conditionally, this simplifies components that
consume multiple contexts." contains a comma splice; update it to form a
complete sentence by replacing the comma with a semicolon or splitting into two
sentences (e.g., "`use` can be called conditionally; this simplifies components
that consume multiple contexts." or "`use` can be called conditionally. This
simplifies components that consume multiple contexts.") so the clause after the
comma has its own subject and reads grammatically correct in react-19.md.
.agents/skills/upgrading-expo/references/react-19.md-3-3 (1)

3-3: ⚠️ Potential issue | 🟡 Minor

Align SDK version with the actual upgrade target.

The documentation references "Expo SDK 54," but this PR upgrades to SDK 55. Consider updating to "Expo SDK 55" or using "Expo SDK 54+" to accurately reflect the version context.

📝 Suggested documentation fix
-React 19 is included in Expo SDK 54. This release simplifies several common patterns.
+React 19 is included in Expo SDK 55. This release simplifies several common patterns.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/upgrading-expo/references/react-19.md at line 3, Update the
documentation line that currently reads "React 19 is included in Expo SDK
54."—replace the "Expo SDK 54" reference with the correct upgrade target (e.g.,
"Expo SDK 55" or a range like "Expo SDK 54+") so the sentence ("React 19 is
included in Expo SDK 54.") accurately reflects the PR's SDK upgrade.
.agents/skills/expo-cicd-workflows/scripts/validate.js-59-64 (1)

59-64: ⚠️ Potential issue | 🟡 Minor

Return exit code 0 for explicit help requests.

At Line 63, validate --help exits with code 1 when no files are passed. Help should be a successful exit path.

Proposed fix
-  if (files.length === 0 || args.includes('--help') || args.includes('-h')) {
+  const wantsHelp = args.includes('--help') || args.includes('-h');
+  if (files.length === 0 || wantsHelp) {
     console.log(`Usage: validate <workflow.yml> [workflow2.yml ...]
@@
-    process.exit(files.length === 0 ? 1 : 0);
+    process.exit(wantsHelp ? 0 : 1);
   }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/expo-cicd-workflows/scripts/validate.js around lines 59 - 64,
The script currently treats a help request as an error when no files are passed;
change the exit logic so explicit help requests return 0. In
.agents/skills/expo-cicd-workflows/scripts/validate.js update the conditional
around files/args (variables files and args in the shown block) so that if
args.includes('--help') || args.includes('-h') the process.exit call uses 0 even
when files.length === 0 (e.g., compute an exitCode = argsIncludesHelp ? 0 :
(files.length === 0 ? 1 : 0) and call process.exit(exitCode) or split the
branches so help always exits 0).
.agents/skills/expo-tailwind-setup/SKILL.md-39-39 (1)

39-39: ⚠️ Potential issue | 🟡 Minor

Minor docs wording/casing cleanup.

Use Lightning CSS (proper name/casing) for clarity in setup docs.

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

In @.agents/skills/expo-tailwind-setup/SKILL.md at line 39, The docs line
currently says "lightningcss" in lowercase; update the wording in SKILL.md (the
line that reads "autoprefixer is not needed in Expo because of lightningcss") to
use the proper product casing "Lightning CSS" so it reads e.g. "autoprefixer is
not needed in Expo because of Lightning CSS". Ensure only the casing/wording is
changed and preserve the rest of the sentence.
.agents/skills/upgrading-expo/SKILL.md-111-113 (1)

111-113: ⚠️ Potential issue | 🟡 Minor

The postcss.config.mjs requirement should be softened—it's recommended, not mandatory.

While autoprefixer removal in SDK 53+ is accurate (Expo's Metro handles vendor prefixing natively), the statement "Use postcss.config.mjs in SDK +53" overstates the requirement. Both postcss.config.js and postcss.config.mjs are supported; .mjs is recommended primarily when using ESM syntax (export default), but .js with CommonJS syntax is equally valid. Align guidance with your project's conventions rather than prescribing one format.

Suggested doc fix
- `autoprefixer` isn't needed in SDK +53. Remove it from dependencies and check `postcss.config.js` or `postcss.config.mjs` to remove it from the plugins list.
- Use `postcss.config.mjs` in SDK +53.
+ Remove `autoprefixer` from dependencies and plugins list in SDK +53 (Expo's Metro handles vendor prefixing natively).
+ Use `postcss.config.mjs` (ESM) or `postcss.config.js` (CommonJS) per your project conventions.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/upgrading-expo/SKILL.md around lines 111 - 113, Update the
guidance to soften the mandate about postcss config and clarify autoprefixer
removal: state that removing `autoprefixer` is recommended for Expo SDK 53+
(since Metro handles vendor prefixes) and instruct to remove it from
dependencies and from any `postcss.config.js` or `postcss.config.mjs` plugins
list; change the sentence "Use `postcss.config.mjs` in SDK +53" to recommend
`postcss.config.mjs` only when using ESM (`export default`) and otherwise allow
`postcss.config.js` with CommonJS, advising to follow the project's existing
convention rather than forcing one format.
.agents/skills/expo-deployment/SKILL.md-123-137 (1)

123-137: ⚠️ Potential issue | 🟡 Minor

Fix broken reference paths (referencereferences).

Lines 123-137 and Line 165 use ./reference/..., but the directory is ./references/.... These links will break for readers.

Suggested patch
-- See ./reference/testflight.md for credential setup
-- See ./reference/ios-app-store.md for App Store submission
+- See ./references/testflight.md for credential setup
+- See ./references/ios-app-store.md for App Store submission
@@
-- See ./reference/play-store.md for detailed setup
+- See ./references/play-store.md for detailed setup
@@
-- See ./reference/workflows.md for CI/CD automation
+- See ./references/workflows.md for CI/CD automation
@@
-See ./reference/workflows.md for more workflow examples.
+See ./references/workflows.md for more workflow examples.

Also applies to: 165-165

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

In @.agents/skills/expo-deployment/SKILL.md around lines 123 - 137, Update all
broken link paths that use "./reference/..." to the correct "./references/..."
in SKILL.md: search for occurrences of the string "./reference/" (notably the
links under the iOS, Android, and Web sections and the occurrence at line 165)
and replace them with "./references/"; ensure each referenced file name (e.g.,
testflight.md, ios-app-store.md, play-store.md, workflows.md) remains unchanged
and links still resolve after the path fix.
.agents/skills/expo-deployment/references/play-store.md-77-82 (1)

77-82: ⚠️ Potential issue | 🟡 Minor

Align track naming with Google Play Developer API terminology.

The table uses internal as the track identifier for internal testing, but the official Google Play Developer API uses qa for this purpose. The Play Console UI labels this as "Internal testing", while the API track ID is qa. Update the table to use qa instead of internal for clarity and accuracy, or add a note explaining that Expo's EAS abstracts this mapping. Additionally, clarify whether alpha refers to a custom closed testing track or the predefined alpha API track, as closed testing tracks can be custom-named in Play Console.

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

In @.agents/skills/expo-deployment/references/play-store.md around lines 77 -
82, Update the Play Store track table to use the Google Play Developer API track
ID `qa` instead of `internal` (or explicitly note that Expo/EAS maps Play
Console "Internal testing" to API `qa`), and clarify the `alpha` row to state
whether it refers to the predefined API `alpha` track or to custom-named closed
testing tracks in Play Console; update the table entries and add a short
parenthetical note after `qa` and `alpha` to make the mapping explicit (refer to
the table rows for `internal`/`qa` and `alpha` in the README fragment).
.agents/skills/expo-api-routes/SKILL.md-35-43 (1)

35-43: ⚠️ Potential issue | 🟡 Minor

Add a language to the fenced code block to satisfy markdown linting.

At Line 35, specify the fence language (for example, text) to avoid MD040 warnings.

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

In @.agents/skills/expo-api-routes/SKILL.md around lines 35 - 43, The fenced
code block in SKILL.md is missing a language tag which triggers MD040; update
the block fence that shows the app/api tree (the triple-backtick block around
the listing) to include a language identifier such as text (e.g., ```text) so
the markdown linter recognizes the block language and the warning is resolved.
.agents/skills/building-native-ui/references/zoom-transitions.md-155-155 (1)

155-155: ⚠️ Potential issue | 🟡 Minor

Minor wording fix: hyphenate “off-screen.”

At Line 155, use “off-screen” for the compound modifier.

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

In @.agents/skills/building-native-ui/references/zoom-transitions.md at line
155, Update the sentence that currently reads "When source is unavailable (e.g.,
scrolled off screen), the transition zooms from the center of the screen" by
hyphenating the compound modifier: change "scrolled off screen" to "scrolled
off-screen" so it reads "When source is unavailable (e.g., scrolled off-screen),
the transition zooms from the center of the screen."
.agents/skills/building-native-ui/references/media.md-97-101 (1)

97-101: ⚠️ Potential issue | 🟡 Minor

GlassButton component is used but not defined.

The GlassButton component is referenced here but not shown in this file. This will confuse readers trying to implement this camera UI. Either define GlassButton in this document or reference where it's defined (e.g., in visual-effects.md).

Suggested addition before usage
// Define GlassButton helper
function GlassButton({ onPress, icon }: { onPress: () => void; icon: string }) {
  return (
    <GlassView isInteractive style={{ borderRadius: 50, padding: 12 }}>
      <TouchableOpacity onPress={onPress}>
        <SymbolView name={icon} tintColor="white" size={24} />
      </TouchableOpacity>
    </GlassView>
  );
}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/media.md around lines 97 - 101,
The document references the GlassButton component (used in the snippet with
onPress={selectPhoto} and onPress={() => setType(...)} and icons
"photo"/"arrow.triangle.2.circlepath") but never defines or links to it; either
add a short local definition for GlassButton (e.g., a wrapper using GlassView
and SymbolView with TouchableOpacity) above its usage or add a clear
reference/link to the file where it’s implemented (for example
visual-effects.md), so readers can find the GlassButton implementation and its
props.
frontend/components/PriceRangePicker.tsx-36-36 (1)

36-36: ⚠️ Potential issue | 🟡 Minor

Entrance animation doesn't replay when modal reopens.

The useEntranceAnimation hook's effect only runs once (its dependencies [delay, opacity, translateY] are stable Ref objects). Since the Modal keeps children mounted while toggling visible, the Animated.View retains its final animation state (opacity: 1, translateY: 0). When the modal reopens, the custom entrance animation won't replay.

Add visible as a trigger dependency to reset animation values on reopen:

Fix: Add visible as dependency
// In useEntranceAnimation
export function useEntranceAnimation(delay = 0, distance = 16, visible?: boolean) {
  const opacity = useRef(new Animated.Value(0)).current;
  const translateY = useRef(new Animated.Value(distance)).current;

  useEffect(() => {
    opacity.setValue(0);
    translateY.setValue(distance);
    
    const animation = Animated.parallel([
      Animated.timing(opacity, {
        toValue: 1,
        duration: 320,
        delay,
        easing: Easing.out(Easing.cubic),
        useNativeDriver: true,
      }),
      Animated.timing(translateY, {
        toValue: 0,
        duration: 320,
        delay,
        easing: Easing.out(Easing.cubic),
        useNativeDriver: true,
      }),
    ]);

    animation.start();
    return () => animation.stop();
  }, [delay, opacity, translateY, visible]);

  return useMemo(() => ({ opacity, transform: [{ translateY }] }), [opacity, translateY]);
}

// In PriceRangePicker
const entranceStyle = useEntranceAnimation(40, 8, visible);
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/PriceRangePicker.tsx` at line 36, The entrance animation
never replaying is caused by useEntranceAnimation only depending on stable refs;
modify useEntranceAnimation to accept a visible boolean (e.g., add parameter
visible) and include that visible in the effect dependency array so the effect
resets opacity and translateY (call .setValue(0) and .setValue(distance) or
equivalent) and restarts the Animated.parallel when visible changes; then update
the PriceRangePicker call to pass its visible prop into useEntranceAnimation
(e.g., useEntranceAnimation(40, 8, visible)).
🧹 Nitpick comments (7)
skills-lock.json (1)

4-48: Consider automating hash integrity checks for this lock manifest.

To prevent silent drift, add/keep a CI check that recomputes each skill hash from its source doc and fails on mismatch.

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

In `@skills-lock.json` around lines 4 - 48, The lockfile contains computedHash
entries for skills (e.g., keys like "building-native-ui", "expo-api-routes",
"expo-dev-client") but there is no automated check to detect drift; add a
validation step by creating a script (e.g., validateSkillHashes or
computeSkillHash) that fetches/parses each skill source doc, recomputes its hash
and compares against the value in skills-lock.json, exiting non‑zero on any
mismatch, and wire that script into CI as a job (e.g.,
.github/workflows/validate-skills.yml) so the pipeline fails when any skill hash
differs.
.agents/skills/building-native-ui/references/route-structure.md (1)

18-24: Add language specifiers to directory structure code blocks.

Multiple code blocks showing directory structures lack language specifiers. Use text or plaintext for consistency and to satisfy linting.

Suggested fix for all occurrences
 ## Dynamic Routes
 
 Use square brackets for dynamic segments:
 
-```
+```text
 app/
   users/
     [id].tsx        # Matches /users/123, /users/abc

Apply similar changes to code blocks at lines 30, 69, 95, 112, and 155.

Also applies to: 30-34, 69-77, 95-106, 112-119, 155-169

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

In @.agents/skills/building-native-ui/references/route-structure.md around lines
18 - 24, Update the fenced code blocks that show directory structures (for
example the block containing "app/ users/ [id].tsx        # Matches /users/123,
/users/abc" and the similar blocks later) to include a language specifier such
as ```text or ```plaintext instead of bare ``` so linting passes; locate the
directory-structure blocks in route-structure.md (the ones indicated in the
review) and change their opening fences to ```text (apply the same change to the
other occurrences noted in the comment).
.agents/skills/building-native-ui/references/tabs.md (1)

305-309: Add language specifier to fenced code block.

The code block at line 305 showing the file structure lacks a language specifier, which affects syntax highlighting and linting.

Suggested fix
-```
+```text
 app/
   _layout.tsx          # NativeTabs for iOS/Android
   _layout.web.tsx      # Headless tabs for web (expo-router/ui)
</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/tabs.md around lines 305 - 309,
The fenced code block showing the app file structure is missing a language
specifier; update the block in tabs.md that contains the lines referencing
_layout.tsx and _layout.web.tsx to use a language tag (e.g., "text") for the
fenced code block so syntax highlighting and linters recognize it properly.


</details>

</blockquote></details>
<details>
<summary>.agents/skills/building-native-ui/references/webgpu-three.md (1)</summary><blockquote>

`492-503`: **Add language specifiers to directory structure and decision tree code blocks.**

Code blocks at lines 492 and 507 lack language specifiers.

<details>
<summary>Suggested fix</summary>

```diff
 ## File Structure
 
-```
+```text
 src/
 ├── app/
 │   └── index.tsx           # Entry point with lazy loading
 ## Decision Tree
 
-```
+```text
 Need 3D graphics?
 ├── Simple shapes → mesh + geometry + material

Also applies to: 507-523

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

In @.agents/skills/building-native-ui/references/webgpu-three.md around lines
492 - 503, The two fenced code blocks (the directory tree beginning with "src/"
and the decision tree beginning with "Need 3D graphics?") are missing language
specifiers; update both fenced blocks to include an appropriate specifier (e.g.,
```text or ```bash) so syntax highlighting and rendering are correct for the
directory structure and decision-tree snippets in webgpu-three.md.
.agents/skills/building-native-ui/references/media.md (1)

1-11: Minor: Use hyphenated "full-screen" as a compound adjective.

Line 5 uses "full screen camera" which should be "full-screen camera" when used as a compound adjective.

Suggested fix
-- Hide navigation headers when there's a full screen camera
+- Hide navigation headers when there's a full-screen camera
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/building-native-ui/references/media.md around lines 1 - 11,
Update the compound adjective in the "## Camera" section by replacing "full
screen camera" with the hyphenated form "full-screen camera" (i.e., edit the
line that currently reads "Hide navigation headers when there's a full screen
camera" to use "full-screen camera") so the phrase is grammatically correct as a
modifier.
.agents/skills/building-native-ui/SKILL.md (1)

14-30: Add language specifiers to directory structure code blocks.

Code blocks at lines 14 and 249 showing directory structures lack language specifiers.

Suggested fix
 ## References
 
 Consult these resources as needed:
 
-```
+```text
 references/
   animations.md          Reanimated: entering, exiting, layout, scroll-driven, gestures

Apply similarly to line 249.

Also applies to: 249-256

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

In @.agents/skills/building-native-ui/SKILL.md around lines 14 - 30, Update the
two directory-structure fenced code blocks in SKILL.md (the block that begins
with "references/" near the top and the similar block later in the file) to
include a language specifier; change the opening triple backticks from ``` to
```text so the listings render as plain text (apply the same fix to both
directory blocks, e.g., replace ``` with ```text for the block that starts with
"references/" and the matching later block).
frontend/components/FilterBar.tsx (1)

128-133: Mount TagPicker only when visible so entrance animation runs on open.

Line 128 keeps TagPicker mounted continuously; with hook-driven entrance animation, the animation can complete before the modal is first shown.

💡 Proposed fix
-      <TagPicker
-        visible={tagPickerVisible}
-        selectedTags={selectedTags}
-        onSelectTags={onTagsChange}
-        onClose={() => setTagPickerVisible(false)}
-      />
+      {tagPickerVisible && (
+        <TagPicker
+          visible={tagPickerVisible}
+          selectedTags={selectedTags}
+          onSelectTags={onTagsChange}
+          onClose={() => setTagPickerVisible(false)}
+        />
+      )}
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/components/FilterBar.tsx` around lines 128 - 133, TagPicker is
always mounted which prevents its entrance animation from running on first open;
change the render in FilterBar to conditionally mount TagPicker only when
tagPickerVisible is true (e.g., render TagPicker when tagPickerVisible && ...)
and keep the same props (selectedTags, onSelectTags={onTagsChange}, onClose={()
=> setTagPickerVisible(false)}) so it unmounts on close and its entrance
animation will play when opened.

Comment thread .agents/skills/building-native-ui/references/gradients.md

const AnimatedExpoImage = Animated.createAnimatedComponent(RNImage);

export type ImageProps = React.ComponentProps<typeof Image>;
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

fd -t f "SKILL.md" | head -20

Repository: codebox-calpoly/PolyBuys

Length of output: 50


🏁 Script executed:

# First, let's find and read the SKILL.md file
find . -name "SKILL.md" -type f

Repository: codebox-calpoly/PolyBuys

Length of output: 452


🏁 Script executed:

cat -n ".agents/skills/expo-tailwind-setup/SKILL.md" | sed -n '230,260p'

Repository: codebox-calpoly/PolyBuys

Length of output: 1272


🌐 Web query:

TypeScript circular type reference self-referential type alias React.ComponentProps

💡 Result:

A “circular type reference” usually happens when you try to define a component’s props type in terms of the component itself, e.g.:

const Button = (props: ButtonProps) => {/*...*/}

type ButtonProps = React.ComponentProps<typeof Button>; // ❌ circular

Here, typeof Button depends on ButtonProps (because it’s used in the function signature), and ButtonProps depends on typeof Button, so TypeScript reports a circular/self-referential type alias (TS2456). TypeScript only allows certain kinds of recursive aliases, and “alias through another alias” patterns are a common way to hit TS2456. [2][3][7]

Fixes

1) Define props first (recommended)

type ButtonProps = {
  variant?: "primary" | "secondary";
};

const Button = (props: ButtonProps) => {/*...*/};

2) If you’re trying to extend another component / intrinsic element, point ComponentProps at that, not at yourself

React.ComponentProps<T> is meant for extracting props from some other component or tag. [1]

type ButtonProps = { variant?: "primary" | "secondary" }
  & React.ComponentPropsWithoutRef<"button">;

function Button(props: ButtonProps) {
  return <button {...props} />;
}

3) If you need “props of this component” elsewhere, export them directly

export type ButtonProps = { /*...*/ };

export function Button(props: ButtonProps) { /*...*/ }

Rule of thumb: React.ComponentProps<typeof X> is great for reusing/extracting props from an existing X, but it can’t be used to “discover” a component’s own props from itself without creating a cycle. [1][7]


🏁 Script executed:

# Check if the file exists and get its location
find . -path "*expo-tailwind-setup/SKILL.md" -type f

Repository: codebox-calpoly/PolyBuys

Length of output: 113


🏁 Script executed:

# Read the relevant section around line 244
sed -n '220,280p' ".agents/skills/expo-tailwind-setup/SKILL.md" | cat -n

Repository: codebox-calpoly/PolyBuys

Length of output: 2422


Remove self-referential ImageProps type alias and extract props from the base component.

Line 244 references Image before it is declared, creating a forward reference issue. Since Image is defined later (line 43) with props based on CSSImage, the type alias creates an indirect circular dependency.

Fix
-export type ImageProps = React.ComponentProps<typeof Image>;
+export type ImageProps = React.ComponentProps<typeof AnimatedExpoImage> & { className?: string };
@@
-export const Image = (props: React.ComponentProps<typeof CSSImage> & { className?: string }) => {
+export const Image = (props: ImageProps) => {
   return useCssElement(CSSImage, props, { className: 'style' });
 };

Define the type based on AnimatedExpoImage (which is already declared) instead of referencing the wrapper component itself.

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

In @.agents/skills/expo-tailwind-setup/SKILL.md at line 244, The ImageProps
alias currently references Image before it's declared causing a
forward-reference/circular type; replace its definition to extract props from
the already-declared AnimatedExpoImage instead of Image (i.e., make ImageProps =
React.ComponentProps<typeof AnimatedExpoImage>), ensuring compatibility with
CSSImage-based props and removing the self-referential dependency.

Comment thread .agents/skills/upgrading-expo/references/expo-av-to-audio.md
Comment on lines +105 to +109
await setAudioModeAsync({
playsInSilentMode: true,
shouldPlayInBackground: true,
interruptionMode: 'doNotMix',
});
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n ".agents/skills/upgrading-expo/references/expo-av-to-audio.md"

Repository: codebox-calpoly/PolyBuys

Length of output: 5421


🌐 Web query:

Expo SDK 55 expo-audio setAudioModeAsync recording iOS option documentation

💡 Result:

In Expo SDK 55 using expo-audio, the iOS “enable recording” flag for setAudioModeAsync is:

  • allowsRecording: boolean (iOS only) — “Whether the audio session allows recording” (default false). [1]

Example (from Expo docs):

import { setAudioModeAsync } from 'expo-audio';

await setAudioModeAsync({
  allowsRecording: true,
  playsInSilentMode: false,
});

This differs from the older expo-av API/docs, which used:

  • allowsRecordingIOS: boolean [2]

Sources: Expo Audio (expo-audio) docs (v54 / latest) [1], older Expo AV Audio docs (v51) showing allowsRecordingIOS [2].


Add missing allowsRecording option to the "After" example for iOS recording support.

The "After" example at lines 105–109 is incomplete. The "Before" example includes allowsRecordingIOS: true, which maps to allowsRecording: true in Expo SDK 55's expo-audio. Without this option in the "After" example, iOS recording functionality will fail for developers following this migration guide.

Update the "After" example to include:

await setAudioModeAsync({
  allowsRecording: true,
  playsInSilentMode: true,
  shouldPlayInBackground: true,
  interruptionMode: 'doNotMix',
});
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/upgrading-expo/references/expo-av-to-audio.md around lines
105 - 109, The "After" example using setAudioModeAsync is missing the
allowsRecording option needed for iOS recording support; update the call to
setAudioModeAsync in the example to include allowsRecording: true alongside
playsInSilentMode, shouldPlayInBackground, and interruptionMode so it matches
the original Before behavior and enables iOS recording.

Comment on lines +11 to +12
- ./references/react-19.md -- SDK +54: React 19 changes (useContext → use, Context.Provider → Context, forwardRef removal)
- ./references/react-compiler.md -- SDK +54: React Compiler setup and migration guide
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

React 19 useContext removed replaced "use" hook official documentation

💡 Result:

useContext was not removed in React 19. It’s still part of the React API reference and works as before. [1]

React 19 introduced/expanded the use API so you can read context via use(ThemeContext). The official docs state that when a context is passed to use, it works similarly to useContext, but useContext must be called at the top level while use can be called inside conditionals/loops—and use is preferred over useContext because it is more flexible.” [2]

Official docs:

  • useContext(SomeContext) reference: [1]
  • use(resource) reference + “Reading context with use”: [2]

Example (docs-equivalent):

import { use } from "react";

const theme = use(ThemeContext);

🌐 Web query:

React 19 forwardRef removal deprecated official changelog

💡 Result:

React 19 did not remove forwardRef, but it made it unnecessary for function components by introducing ref as a prop”.

  • Official React 19 changelog / release post (Dec 5, 2024): “New function components will no longer need forwardRef… In future versions we will deprecate and remove forwardRef.” (in the ref as a prop” section) [1]
  • Official forwardRef reference page: Marks forwardRef as Deprecated, stating: “In React 19, forwardRef is no longer necessary. Pass ref as a prop instead. forwardRef will be deprecated in a future release.” [2]

Bottom line: in React 19, forwardRef is effectively obsolete for new function components, and React’s official docs say it’s slated for deprecation/removal in a future release—not removed in 19 itself. [1][2]

Sources: [1] [2]


Fix inaccurate React 19 API claims.

Lines 11–12 claim useContext → use and forwardRef removal, but both are incorrect. useContext remains valid in React 19; use() is a more flexible alternative for context consumption but does not replace it. forwardRef is deprecated but not removed—React 19 introduces ref as a prop as the preferred approach for new code.

Update the reference summary to reflect accurate guidance:

Suggested fix
- ./references/react-19.md -- SDK +54: React 19 changes (useContext → use, Context.Provider → Context, forwardRef removal)
+ ./references/react-19.md -- SDK +54: React 19 changes (use() for flexible context reading, ref as a prop for function components, Context.Provider changes)
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/upgrading-expo/SKILL.md around lines 11 - 12, Update the
React 19 reference summary to correct the API claims: state that useContext
remains valid (do not replace it with use), describe use() as a more flexible
alternative for context consumption (optional for new patterns), clarify that
forwardRef is deprecated but not removed and that the new preferred pattern is
passing ref as a prop for new code, and adjust the Context.Provider → Context
phrasing to indicate the provider API still exists while Context usage patterns
are evolving; edit the summary lines referencing useContext, use, forwardRef,
and Context.Provider to reflect these accurate statements.


- Review release notes for the target SDK version at https://expo.dev/changelog
- If using Expo SDK 54 or later, ensure react-native-worklets is installed — this is required for react-native-reanimated to work.
- Enable React Compiler in SDK 54+ by adding `"experiments": { "reactCompiler": true }` to app.json — it's stable and recommended
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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

cat -n .agents/skills/upgrading-expo/SKILL.md | sed -n '70,80p'

Repository: codebox-calpoly/PolyBuys

Length of output: 846


🌐 Web query:

React Compiler Expo SDK 54 55 stability production recommended

💡 Result:

React Compiler is stable and production-ready as of React Compiler v1.0 (Oct 7, 2025)—the React team says it’s “fully production-ready” and has been battle-tested at Meta, and they explicitly partnered with Expo so new apps can start with it enabled. [1]

For Expo SDK 54:

  • Expo’s SDK 54 changelog (Sep 10, 2025) says React Compiler is enabled in the default template and they recommend using it (logs may still say “Experimental … enabled” because it was RC at the time). [2]
  • Expo’s React Compiler guide still documents enabling it via the experiments.reactCompiler: true flag and installing babel-plugin-react-compiler@beta (last updated Nov 3, 2025), which is why it can feel “experimental” in Expo even though the compiler itself is now stable. [3]

For Expo SDK 55:

  • SDK 55 ships React 19.2 and React Native 0.83, so it’s on the modern baseline where React Compiler is intended to work well. [4]

Production recommendation (SDK 54 / 55): Yes—generally recommended for production, but treat Expo’s integration as “opt-in / verify in your app” (since Expo still exposes it behind an experiments flag and uses @beta package tags in docs). Use the healthcheck and be ready to opt out specific files/components if you hit edge cases. [1][3]

Sources:
[1] React: React Compiler v1.0 (Oct 7, 2025)
[2] Expo: SDK 54 changelog (Sep 10, 2025)
[3] Expo Docs: React Compiler guide (updated Nov 3, 2025)
[4] Expo: SDK 55 changelog (Feb 2026)


🌐 Web query:

React Compiler compatibility caveats Expo known issues

💡 Result:

Expo + React Compiler: compatibility caveats / known issues

  • SDK requirement & setup differences: Expo SDK 54+ has the React Compiler Babel integration wired into babel-preset-expo; older SDKs require manual setup and (for some versions) adding react-compiler-runtime. [1]
  • Expo’s compiler scope is limited by design: In Expo, the compiler only runs on your application code (not node_modules) and only for client bundles (it’s disabled for server rendering). [1]
  • Class components aren’t optimized: React Compiler optimizes function components/hooks; Expo docs explicitly note class components won’t be optimized (migrate to function components for benefit). [1]
  • Incompatible libraries/patterns can “freeze” UI with memoization: React’s linter flags certain libraries/APIs as incompatible with memoization, and React Compiler may need to skip affected components to avoid breaking behavior (or you may need to opt out with "use no memo"). [2]
  • Babel plugin interactions can break builds: React warns that some Babel plugins can conflict with React Compiler; mitigation includes placing babel-plugin-react-compiler early in the plugin list and disabling conflicting optimizations. [3]
  • Expo SDK 54 known issue (strict mode crash): In Expo SDK 54, setting compilationMode: "strict" has been reported to crash with Unexpected compilationMode "strict" (i.e., that option isn’t accepted in that setup). [4]
  • “Experimental/RC” status: Expo has described React Compiler support as experimental / release-candidate and notes the defaults may change depending on stability feedback. [5]

Sources: Expo React Compiler guide [1], React linter rule incompatible-library [2], React “Compiling Libraries” troubleshooting notes [3], Expo GitHub issue #39579 [4], Expo SDK 54 beta changelog [5].


Frame React Compiler enablement as conditional on app validation, not universal best practice.

React Compiler is production-ready (v1.0, Oct 2025) and Expo recommends it, but Expo's integration has documented compatibility requirements: certain libraries are incompatible with memoization, Babel plugin conflicts can break builds, class components aren't optimized, and SDK 54 has a known strict mode issue. Unconditionally recommending it without qualification could lead users to enable it in incompatible environments. The guidance should ask users to validate their toolchain and libraries first.

Suggested fix
-- Enable React Compiler in SDK 54+ by adding `"experiments": { "reactCompiler": true }` to app.json — it's stable and recommended
+- Consider enabling React Compiler in SDK 54+ via `"experiments": { "reactCompiler": true }` after validating library and tooling compatibility in your app.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
- Enable React Compiler in SDK 54+ by adding `"experiments": { "reactCompiler": true }` to app.json — it's stable and recommended
- Consider enabling React Compiler in SDK 54+ via `"experiments": { "reactCompiler": true }` after validating library and tooling compatibility in your app.
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/upgrading-expo/SKILL.md at line 74, Update the sentence that
unconditionally recommends enabling the React Compiler (the line that currently
instructs: add "experiments": { "reactCompiler": true } to app.json) to instead
advise conditional enablement: instruct users to validate their app/toolchain
and libraries first (checking for memoization-incompatible libs, Babel plugin
conflicts, class component behavior, and known SDK 54 strict-mode issues) and
only enable the React Compiler in app.json once compatibility is confirmed or
after testing in a staging build; reference the exact config key
"experiments.reactCompiler" and mention testing/rollback guidance so users know
how to safely try it.

Comment on lines +82 to +90
| Old Package | Replacement |
| -------------------- | ---------------------------------------------------- |
| `expo-av` | `expo-audio` and `expo-video` |
| `expo-permissions` | Individual package permission APIs |
| `@expo/vector-icons` | `expo-symbols` (for SF Symbols) |
| `AsyncStorage` | `expo-sqlite/localStorage/install` |
| `expo-app-loading` | `expo-splash-screen` |
| expo-linear-gradient | experimental_backgroundImage + CSS gradients in View |

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.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

# First, confirm the file content at the specified lines
if [ -f ".agents/skills/upgrading-expo/SKILL.md" ]; then
  sed -n '82,90p' ".agents/skills/upgrading-expo/SKILL.md"
else
  find . -name "SKILL.md" -path "*upgrading-expo*" -type f
fi

Repository: codebox-calpoly/PolyBuys

Length of output: 708


🌐 Web query:

Expo vector-icons @expo/vector-icons current status 2025 deprecation

💡 Result:

@expo/vector-icons was not deprecated in 2025 and is still a supported, documented Expo library as of March 2, 2026—it’s installed by default in create-expo-app templates and described as part of the Expo ecosystem. [1]

What did happen around the 2024–2025 timeframe is maintenance/behavior changes, not deprecation:

  • In Expo SDK 50 (Jan 18, 2024), @expo/vector-icons was updated to use react-native-vector-icons@10.0.0, which introduced some icon-name changes (notably Ionicons dropping ios- / md- prefixes). [2]
  • Also in SDK 50, Expo changed how react-native-vector-icons is aliased to @expo/vector-icons (moving it from the Babel preset to the Metro resolver), which affected some setups—especially web/storybook configurations. [2]

Current version signals:

  • Expo’s Icon Explorer currently references @expo/vector-icons@14.0.4. [3]
  • Package deprecation trackers report no deprecated versions of @expo/vector-icons (and show recent published versions in 2024). [4]

Sources:
[1] Expo docs: “Expo Vector Icons” guide (last updated Feb 17, 2026) (docs.expo.dev)
[2] Expo SDK 50 changelog (vector-icons + resolver/aliasing notes) (expo.dev)
[3] Expo Icon Explorer showing @expo/vector-icons@14.0.4 (icons.expo.app)
[4] Deprecation/version tracker showing “No deprecated versions” (xeol.io)

Citations:


🌐 Web query:

expo-linear-gradient deprecation status 2025 Expo SDK

💡 Result:

expo-linear-gradient is not deprecated in Expo SDK “latest” docs (as of the current Expo documentation), and Expo still documents installing and using it as the supported LinearGradient component (bundled version shown in docs: ~15.0.8). [1]

What was deprecated/removed (years earlier) was the old global access pattern (for example global.expo.LinearGradient) that used to come from the main expo package; since SDK 41 you’re expected to install/import the package directly: import { LinearGradient } from 'expo-linear-gradient';. [2]

There is a July 13, 2025 open docs issue suggesting the docs could also mention newer React Native alternatives (like experimental_backgroundImage)—but that issue is not a deprecation notice for expo-linear-gradient. [3]

Sources: [1] [2] [3]


🌐 Web query:

AsyncStorage recommended package Expo SDK 2025 @react-native-async-storage``

💡 Result:

For Expo SDK (including 2025-era SDKs), the recommended AsyncStorage library is:

  • Package: @react-native-async-storage/async-storage (docs.expo.dev)
  • Install (recommended in Expo): npx expo install @react-native-async-storage/async-storage`` (this pins a version compatible with your Expo SDK) (docs.expo.dev)

Expo’s SDK docs list it as the supported AsyncStorage solution (with a “bundled version” shown on the page; e.g., it listed 2.1.2 and was last updated April 16, 2025). (docs.expo.dev)

Citations:


Three deprecated package mappings (lines 86–90) are incorrect and could mislead migrations.

  • @expo/vector-icons is not deprecated; it's still actively maintained and bundled in create-expo-app. expo-symbols is an iOS-specific SF Symbols option, not a general replacement.
  • expo-linear-gradient is not deprecated; it remains the documented package for gradients in Expo.
  • AsyncStorage mapping is wrong; the correct package is @react-native-async-storage/async-storage, not expo-sqlite/localStorage/install.

Update these three rows to reflect current Expo SDK guidance:

Suggested fix
-| `@expo/vector-icons` | `expo-symbols` (for SF Symbols)                      |
-| `AsyncStorage`       | `expo-sqlite/localStorage/install`                   |
-| expo-linear-gradient | experimental_backgroundImage + CSS gradients in View |
+| `@expo/vector-icons` | Keep; use `expo-symbols` only for iOS SF Symbols     |
+| `AsyncStorage`       | `@react-native-async-storage/async-storage`          |
+| `expo-linear-gradient` | Keep; still the official Expo gradient package       |
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In @.agents/skills/upgrading-expo/SKILL.md around lines 82 - 90, Update the
three incorrect mappings in the table: replace the `@expo/vector-icons` ->
`expo-symbols` row to indicate `@expo/vector-icons` is still maintained and
bundled (remove `expo-symbols` as a general replacement; note `expo-symbols` is
iOS-only), change the `expo-linear-gradient` row to keep `expo-linear-gradient`
as the recommended package instead of marking it deprecated, and correct the
`AsyncStorage` mapping to point to `@react-native-async-storage/async-storage`
(remove `expo-sqlite/localStorage/install`). Locate and edit the table entries
referencing `@expo/vector-icons`, `expo-symbols`, `expo-linear-gradient`,
`AsyncStorage`, and `@react-native-async-storage/async-storage`.

Comment thread frontend/app/auth/login.tsx Outdated
- Integrated useLocalSearchParams to retrieve returnTo parameter for post-auth redirection.
- Updated router.replace to redirect users to the specified return path after successful login, improving user experience.
Copy link
Copy Markdown
Contributor

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@frontend/app/auth/login.tsx`:
- Around line 183-187: The success banner currently shows whenever
successMessage is set, causing stale "code sent" text after navigating back to
the email-entry UI; update the conditional render around successMessage so it
also checks the current flow step and only displays when the component is in the
verification step (e.g., only render when step/currentStep/authStep === 'verify'
or similar), and apply the same conditional to the other successMessage render
block at lines ~205-217; locate the successMessage render blocks to add the step
check (referencing successMessage and your step state variable) so the banner is
restricted to the verification step.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between b11d43c and d8cef9e.

📒 Files selected for processing (2)
  • .coderabbit.yaml
  • frontend/app/auth/login.tsx

Comment thread frontend/app/auth/login.tsx Outdated
- Introduced a new state variable to manage the verification step in the login process.
- Updated the rendering logic to display success messages conditionally based on the verification step, improving user feedback during authentication.
Copy link
Copy Markdown
Contributor

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

Caution

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

⚠️ Outside diff range comments (2)
frontend/app/auth/login.tsx (2)

65-67: ⚠️ Potential issue | 🟡 Minor

Enforce numeric OTP input in client validation.

Current check validates only length, so non-digit 8-char values pass client-side despite the “8-digit code” requirement.

💡 Suggested fix
-    if (!code.trim() || code.trim().length !== 8) {
+    if (!/^\d{8}$/.test(code.trim())) {
       setError('Please enter the 8-digit code');
       return;
     }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/auth/login.tsx` around lines 65 - 67, The client-side validation
currently only checks length, allowing non-numeric 8-char values; update the
validation around the "code" variable (the same block that calls
setError('Please enter the 8-digit code')) to require exactly eight digits (e.g.
test with a regex like /^\d{8}$/) and call setError if the test fails so only
numeric 8-digit OTPs pass; keep the existing error message and return early as
before.

108-112: ⚠️ Potential issue | 🟡 Minor

Clear successMessage when leaving verification step.

handleBack does not reset successMessage, so a previous “code resent” banner can reappear when the user re-enters verification with a different email.

💡 Suggested fix
 const handleBack = () => {
   setStep('email');
   setCode('');
   setError(null);
+  setSuccessMessage(null);
 };
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@frontend/app/auth/login.tsx` around lines 108 - 112, The handleBack function
currently resets step, code, and error but leaves successMessage intact, causing
prior "code resent" banners to reappear; update handleBack (in
frontend/app/auth/login.tsx) to also clear the successMessage state by calling
the success message setter (e.g., setSuccessMessage('') or
setSuccessMessage(null) consistent with its type) alongside setStep('email'),
setCode(''), and setError(null).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@frontend/app/auth/login.tsx`:
- Around line 65-67: The client-side validation currently only checks length,
allowing non-numeric 8-char values; update the validation around the "code"
variable (the same block that calls setError('Please enter the 8-digit code'))
to require exactly eight digits (e.g. test with a regex like /^\d{8}$/) and call
setError if the test fails so only numeric 8-digit OTPs pass; keep the existing
error message and return early as before.
- Around line 108-112: The handleBack function currently resets step, code, and
error but leaves successMessage intact, causing prior "code resent" banners to
reappear; update handleBack (in frontend/app/auth/login.tsx) to also clear the
successMessage state by calling the success message setter (e.g.,
setSuccessMessage('') or setSuccessMessage(null) consistent with its type)
alongside setStep('email'), setCode(''), and setError(null).

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d8cef9e and 31e288f.

📒 Files selected for processing (1)
  • frontend/app/auth/login.tsx

- Added expo-router version 55.0.3 to package.json and package-lock.json for improved routing capabilities.
- Updated TypeScript configuration in tsconfig.json to include additional file types and improve module resolution.
- Updated the updateListing action to return specific moderation error responses instead of throwing exceptions.
- Added a new query to retrieve the current user's listings, including hidden and inactive items.
- Introduced a new query to fetch the current authenticated user's full profile, including non-public fields.
- Enhanced profile creation and update mutations to include additional fields and validation for email, year, and other profile attributes.
- Improved error handling in listing creation and editing to provide user-friendly feedback for moderation issues.
- Updated frontend components to reflect new profile and listing functionalities, including loading states and input validations.
- Updated the test case for createProfile to clarify rejection conditions when no email is provided or available on identity.
- Enhanced error message to be more user-friendly, specifying that an email is required to create a profile.
@jaydonkc jaydonkc merged commit 24a1d97 into dev Mar 3, 2026
3 checks passed
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.

2 participants