From 097b6defe7e8969e9fabea4b6d5eebdde22296ee Mon Sep 17 00:00:00 2001 From: Gowtham G Date: Sat, 19 Sep 2026 22:08:41 +0530 Subject: [PATCH] perf: stabilize lists, search input and bundle config --- .../assess-react-native-migration/SKILL.md | 192 +++++++ .../agents/openai.yaml | 4 + .../create-react-native-library/SKILL.md | 73 +++ .../agents/openai.yaml | 4 + .../references/local-library.md | 86 +++ .../references/scaffold-library.md | 96 ++++ .agents/skills/github-actions/SKILL.md | 52 ++ .../skills/github-actions/agents/openai.yaml | 4 + .../gha-android-composite-action.md | 158 ++++++ .../references/gha-ios-composite-action.md | 228 ++++++++ .../references/gha-workflow-and-downloads.md | 288 ++++++++++ .../react-native-best-practices/POWER.md | 172 ++++++ .../react-native-best-practices/SKILL.md | 238 +++++++++ .../agents/openai.yaml | 4 + .../references/bundle-analyze-app.md | 208 ++++++++ .../references/bundle-analyze-js.md | 264 ++++++++++ .../references/bundle-barrel-exports.md | 250 +++++++++ .../references/bundle-code-splitting.md | 255 +++++++++ .../references/bundle-hermes-mmap.md | 167 ++++++ .../references/bundle-library-size.md | 164 ++++++ .../references/bundle-native-assets.md | 208 ++++++++ .../references/bundle-r8-android.md | 231 ++++++++ .../references/bundle-tree-shaking.md | 215 ++++++++ .../bundle-treemap-source-map-explorer.png | Bin 0 -> 667493 bytes .../images/controlled-textinput-pingpong.png | Bin 0 -> 127223 bytes .../references/images/devtools-flamegraph.png | Bin 0 -> 520655 bytes .../references/images/emerge-xray-ios.png | Bin 0 -> 439669 bytes .../references/images/expo-atlas-treemap.png | Bin 0 -> 618904 bytes .../flashlight-flatlist-vs-flashlist.png | Bin 0 -> 313215 bytes .../references/images/fps-drop-graph.png | Bin 0 -> 152493 bytes .../images/memory-heap-snapshot.png | Bin 0 -> 308032 bytes .../images/tti-warm-start-diagram.png | Bin 0 -> 127254 bytes .../images/view-hierarchy-flattening.png | Bin 0 -> 2587021 bytes .../images/xcode-instruments-templates.png | Bin 0 -> 408360 bytes .../references/images/xcode-thread-view.png | Bin 0 -> 178440 bytes .../references/js-animations-reanimated.md | 273 ++++++++++ .../references/js-atomic-state.md | 190 +++++++ .../references/js-bottomsheet.md | 339 ++++++++++++ .../references/js-concurrent-react.md | 238 +++++++++ .../references/js-lists-flatlist-flashlist.md | 223 ++++++++ .../references/js-measure-fps.md | 178 +++++++ .../references/js-memory-leaks.md | 168 ++++++ .../references/js-profile-react.md | 138 +++++ .../references/js-react-compiler.md | 261 +++++++++ .../references/js-uncontrolled-components.md | 219 ++++++++ .../native-android-16kb-alignment.md | 114 ++++ .../references/native-measure-tti.md | 221 ++++++++ .../references/native-memory-leaks.md | 206 ++++++++ .../references/native-memory-patterns.md | 240 +++++++++ .../references/native-platform-setup.md | 112 ++++ .../references/native-profiling.md | 140 +++++ .../references/native-sdks-over-polyfills.md | 192 +++++++ .../references/native-threading-model.md | 230 ++++++++ .../references/native-turbo-modules.md | 210 ++++++++ .../references/native-view-flattening.md | 180 +++++++ .../SKILL.md | 118 +++++ .../agents/openai.yaml | 4 + .../references/bare-android-aar-generation.md | 105 ++++ .../bare-android-native-integration.md | 91 ++++ .../references/bare-ios-native-integration.md | 94 ++++ .../bare-ios-xcframework-generation.md | 92 ++++ .../references/bare-quick-start.md | 58 ++ .../references/expo-android-integration.md | 91 ++++ .../references/expo-create-app.md | 61 +++ .../references/expo-ios-integration.md | 87 +++ .../references/expo-quick-start.md | 64 +++ .../references/quick-start.md | 71 +++ .../react-native-tv-best-practices/SKILL.md | 150 ++++++ .../agents/openai.yaml | 4 + .../references/a11y-checklist.md | 88 ++++ .../references/a11y-implementation.md | 89 ++++ .../references/a11y-overview.md | 47 ++ .../references/design-10foot.md | 56 ++ .../references/design-color.md | 76 +++ .../references/design-layout.md | 75 +++ .../references/design-typography.md | 68 +++ .../references/focus-management.md | 167 ++++++ .../references/focus-performance.md | 106 ++++ .../references/nav-directional.md | 99 ++++ .../references/nav-keyboard.md | 110 ++++ .../references/nav-patterns.md | 142 +++++ .../references/perf-animations.md | 80 +++ .../references/perf-lists.md | 81 +++ .../references/perf-memory.md | 70 +++ .../references/perf-network.md | 67 +++ .../references/perf-overview.md | 80 +++ .../references/release-cicd.md | 95 ++++ .../references/setup-architecture.md | 97 ++++ .../references/setup-cross-platform.md | 76 +++ .../references/setup-getting-started.md | 115 ++++ .../references/test-e2e.md | 130 +++++ .../references/test-javascript.md | 113 ++++ .../references/test-strategy.md | 43 ++ .../references/video-debugging.md | 30 ++ .../references/video-players.md | 68 +++ .../references/video-streaming.md | 37 ++ .agents/skills/react-navigation/SKILL.md | 63 +++ .../react-navigation/agents/openai.yaml | 4 + .../references/bottom-tabs.md | 496 ++++++++++++++++++ .../react-navigation/references/drawers.md | 474 +++++++++++++++++ .../react-navigation/references/form-sheet.md | 305 +++++++++++ .../react-navigation/references/header.md | 270 ++++++++++ .../references/material-top-tabs.md | 453 ++++++++++++++++ .../references/native-bottom-tabs.md | 365 +++++++++++++ .../react-navigation/references/safe-areas.md | 95 ++++ .../react-navigation/references/stacks.md | 392 ++++++++++++++ .../skills/upgrading-react-native/SKILL.md | 79 +++ .../upgrading-react-native/agents/openai.yaml | 4 + .../references/expo-sdk-upgrade.md | 65 +++ .../monorepo-singlerepo-targeting.md | 34 ++ .../references/react.md | 42 ++ .../references/upgrade-helper-core.md | 122 +++++ .../references/upgrade-verification.md | 53 ++ .../references/upgrading-dependencies.md | 41 ++ .../references/upgrading-react-native.md | 52 ++ .agents/skills/validate-skills/SKILL.md | 64 +++ .agents/skills/writing-user-docs/SKILL.md | 60 +++ .claude/skills/assess-react-native-migration | 1 + .claude/skills/create-react-native-library | 1 + .claude/skills/github-actions | 1 + .claude/skills/react-native-best-practices | 1 + .../skills/react-native-brownfield-migration | 1 + .claude/skills/react-native-tv-best-practices | 1 + .claude/skills/react-navigation | 1 + .claude/skills/upgrading-react-native | 1 + .claude/skills/validate-skills | 1 + .claude/skills/writing-user-docs | 1 + app.json | 11 +- metro.config.js | 12 +- skills-lock.json | 65 +++ src/components/ArticleFeed/index.tsx | 50 +- src/components/FitFastImage/index.tsx | 29 +- src/screens/Article/index.tsx | 22 +- src/screens/Bookmarks/index.tsx | 157 +++--- src/screens/Common/ArticleList/index.tsx | 57 +- src/screens/Search/index.tsx | 128 +++-- src/screens/TermsAndConditions/index.tsx | 2 +- src/screens/Videos/index.tsx | 114 ++-- 138 files changed, 14982 insertions(+), 236 deletions(-) create mode 100644 .agents/skills/assess-react-native-migration/SKILL.md create mode 100644 .agents/skills/assess-react-native-migration/agents/openai.yaml create mode 100644 .agents/skills/create-react-native-library/SKILL.md create mode 100644 .agents/skills/create-react-native-library/agents/openai.yaml create mode 100644 .agents/skills/create-react-native-library/references/local-library.md create mode 100644 .agents/skills/create-react-native-library/references/scaffold-library.md create mode 100644 .agents/skills/github-actions/SKILL.md create mode 100644 .agents/skills/github-actions/agents/openai.yaml create mode 100644 .agents/skills/github-actions/references/gha-android-composite-action.md create mode 100644 .agents/skills/github-actions/references/gha-ios-composite-action.md create mode 100644 .agents/skills/github-actions/references/gha-workflow-and-downloads.md create mode 100644 .agents/skills/react-native-best-practices/POWER.md create mode 100644 .agents/skills/react-native-best-practices/SKILL.md create mode 100644 .agents/skills/react-native-best-practices/agents/openai.yaml create mode 100644 .agents/skills/react-native-best-practices/references/bundle-analyze-app.md create mode 100644 .agents/skills/react-native-best-practices/references/bundle-analyze-js.md create mode 100644 .agents/skills/react-native-best-practices/references/bundle-barrel-exports.md create mode 100644 .agents/skills/react-native-best-practices/references/bundle-code-splitting.md create mode 100644 .agents/skills/react-native-best-practices/references/bundle-hermes-mmap.md create mode 100644 .agents/skills/react-native-best-practices/references/bundle-library-size.md create mode 100644 .agents/skills/react-native-best-practices/references/bundle-native-assets.md create mode 100644 .agents/skills/react-native-best-practices/references/bundle-r8-android.md create mode 100644 .agents/skills/react-native-best-practices/references/bundle-tree-shaking.md create mode 100644 .agents/skills/react-native-best-practices/references/images/bundle-treemap-source-map-explorer.png create mode 100644 .agents/skills/react-native-best-practices/references/images/controlled-textinput-pingpong.png create mode 100644 .agents/skills/react-native-best-practices/references/images/devtools-flamegraph.png create mode 100644 .agents/skills/react-native-best-practices/references/images/emerge-xray-ios.png create mode 100644 .agents/skills/react-native-best-practices/references/images/expo-atlas-treemap.png create mode 100644 .agents/skills/react-native-best-practices/references/images/flashlight-flatlist-vs-flashlist.png create mode 100644 .agents/skills/react-native-best-practices/references/images/fps-drop-graph.png create mode 100644 .agents/skills/react-native-best-practices/references/images/memory-heap-snapshot.png create mode 100644 .agents/skills/react-native-best-practices/references/images/tti-warm-start-diagram.png create mode 100644 .agents/skills/react-native-best-practices/references/images/view-hierarchy-flattening.png create mode 100644 .agents/skills/react-native-best-practices/references/images/xcode-instruments-templates.png create mode 100644 .agents/skills/react-native-best-practices/references/images/xcode-thread-view.png create mode 100644 .agents/skills/react-native-best-practices/references/js-animations-reanimated.md create mode 100644 .agents/skills/react-native-best-practices/references/js-atomic-state.md create mode 100644 .agents/skills/react-native-best-practices/references/js-bottomsheet.md create mode 100644 .agents/skills/react-native-best-practices/references/js-concurrent-react.md create mode 100644 .agents/skills/react-native-best-practices/references/js-lists-flatlist-flashlist.md create mode 100644 .agents/skills/react-native-best-practices/references/js-measure-fps.md create mode 100644 .agents/skills/react-native-best-practices/references/js-memory-leaks.md create mode 100644 .agents/skills/react-native-best-practices/references/js-profile-react.md create mode 100644 .agents/skills/react-native-best-practices/references/js-react-compiler.md create mode 100644 .agents/skills/react-native-best-practices/references/js-uncontrolled-components.md create mode 100644 .agents/skills/react-native-best-practices/references/native-android-16kb-alignment.md create mode 100644 .agents/skills/react-native-best-practices/references/native-measure-tti.md create mode 100644 .agents/skills/react-native-best-practices/references/native-memory-leaks.md create mode 100644 .agents/skills/react-native-best-practices/references/native-memory-patterns.md create mode 100644 .agents/skills/react-native-best-practices/references/native-platform-setup.md create mode 100644 .agents/skills/react-native-best-practices/references/native-profiling.md create mode 100644 .agents/skills/react-native-best-practices/references/native-sdks-over-polyfills.md create mode 100644 .agents/skills/react-native-best-practices/references/native-threading-model.md create mode 100644 .agents/skills/react-native-best-practices/references/native-turbo-modules.md create mode 100644 .agents/skills/react-native-best-practices/references/native-view-flattening.md create mode 100644 .agents/skills/react-native-brownfield-migration/SKILL.md create mode 100644 .agents/skills/react-native-brownfield-migration/agents/openai.yaml create mode 100644 .agents/skills/react-native-brownfield-migration/references/bare-android-aar-generation.md create mode 100644 .agents/skills/react-native-brownfield-migration/references/bare-android-native-integration.md create mode 100644 .agents/skills/react-native-brownfield-migration/references/bare-ios-native-integration.md create mode 100644 .agents/skills/react-native-brownfield-migration/references/bare-ios-xcframework-generation.md create mode 100644 .agents/skills/react-native-brownfield-migration/references/bare-quick-start.md create mode 100644 .agents/skills/react-native-brownfield-migration/references/expo-android-integration.md create mode 100644 .agents/skills/react-native-brownfield-migration/references/expo-create-app.md create mode 100644 .agents/skills/react-native-brownfield-migration/references/expo-ios-integration.md create mode 100644 .agents/skills/react-native-brownfield-migration/references/expo-quick-start.md create mode 100644 .agents/skills/react-native-brownfield-migration/references/quick-start.md create mode 100644 .agents/skills/react-native-tv-best-practices/SKILL.md create mode 100644 .agents/skills/react-native-tv-best-practices/agents/openai.yaml create mode 100644 .agents/skills/react-native-tv-best-practices/references/a11y-checklist.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/a11y-implementation.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/a11y-overview.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/design-10foot.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/design-color.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/design-layout.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/design-typography.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/focus-management.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/focus-performance.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/nav-directional.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/nav-keyboard.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/nav-patterns.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/perf-animations.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/perf-lists.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/perf-memory.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/perf-network.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/perf-overview.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/release-cicd.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/setup-architecture.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/setup-cross-platform.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/setup-getting-started.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/test-e2e.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/test-javascript.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/test-strategy.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/video-debugging.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/video-players.md create mode 100644 .agents/skills/react-native-tv-best-practices/references/video-streaming.md create mode 100644 .agents/skills/react-navigation/SKILL.md create mode 100644 .agents/skills/react-navigation/agents/openai.yaml create mode 100644 .agents/skills/react-navigation/references/bottom-tabs.md create mode 100644 .agents/skills/react-navigation/references/drawers.md create mode 100644 .agents/skills/react-navigation/references/form-sheet.md create mode 100644 .agents/skills/react-navigation/references/header.md create mode 100644 .agents/skills/react-navigation/references/material-top-tabs.md create mode 100644 .agents/skills/react-navigation/references/native-bottom-tabs.md create mode 100644 .agents/skills/react-navigation/references/safe-areas.md create mode 100644 .agents/skills/react-navigation/references/stacks.md create mode 100644 .agents/skills/upgrading-react-native/SKILL.md create mode 100644 .agents/skills/upgrading-react-native/agents/openai.yaml create mode 100644 .agents/skills/upgrading-react-native/references/expo-sdk-upgrade.md create mode 100644 .agents/skills/upgrading-react-native/references/monorepo-singlerepo-targeting.md create mode 100644 .agents/skills/upgrading-react-native/references/react.md create mode 100644 .agents/skills/upgrading-react-native/references/upgrade-helper-core.md create mode 100644 .agents/skills/upgrading-react-native/references/upgrade-verification.md create mode 100644 .agents/skills/upgrading-react-native/references/upgrading-dependencies.md create mode 100644 .agents/skills/upgrading-react-native/references/upgrading-react-native.md create mode 100644 .agents/skills/validate-skills/SKILL.md create mode 100644 .agents/skills/writing-user-docs/SKILL.md create mode 120000 .claude/skills/assess-react-native-migration create mode 120000 .claude/skills/create-react-native-library create mode 120000 .claude/skills/github-actions create mode 120000 .claude/skills/react-native-best-practices create mode 120000 .claude/skills/react-native-brownfield-migration create mode 120000 .claude/skills/react-native-tv-best-practices create mode 120000 .claude/skills/react-navigation create mode 120000 .claude/skills/upgrading-react-native create mode 120000 .claude/skills/validate-skills create mode 120000 .claude/skills/writing-user-docs create mode 100644 skills-lock.json diff --git a/.agents/skills/assess-react-native-migration/SKILL.md b/.agents/skills/assess-react-native-migration/SKILL.md new file mode 100644 index 00000000..b49f8657 --- /dev/null +++ b/.agents/skills/assess-react-native-migration/SKILL.md @@ -0,0 +1,192 @@ +--- +name: assess-react-native-migration +description: Assesses whether and how an existing mobile product should migrate to React Native. Use when auditing one or more product repositories for migration readiness, including products whose iOS, Android, and other clients live in separate directories or repositories; choosing brownfield, greenfield, or a checkpoint-based path; defining a representative trial; or preparing a baseline and ROI decision before implementation. When product scope or material evidence is unavailable, grills the stakeholder with exactly one question per turn instead of sending a questionnaire. +license: MIT +--- + +# Assess React Native Migration + +Produce a read-only migration decision. Diagnose the product and delivery system; do not execute the migration. + +## Establish Product Scope + +Run the assessment from a workspace that exposes as many production client codebases as possible. The current checkout is not evidence that it contains the whole product. + +Before assessing readiness: + +1. Inspect the current repository and every workspace root available to the agent. +2. Infer supported client platforms from product documentation, CI, release configuration, workspace manifests, submodules, and references to sibling repositories. +3. Locate each production client codebase, including separate native iOS and Android repositories, app variants, and any web client relevant to staffing or proposed code sharing. +4. Record a platform inventory with the client, repository or path, evidence of product membership, and access status. + +When iOS and Android are both supported, inspect both native codebases before recommending a path. If a codebase remains unavailable, mark its evidence `unknown`, state that the assessment covers only the accessible platforms, and lower confidence accordingly. Do not infer that a platform is unsupported merely because its project is absent from the current repository. + +**Scope gate:** every supported production client is listed, and each codebase is accessible, explicitly unavailable, or confirmed not to exist. + +## First Response Gate + +When the scope gate has not passed, the first response must be exactly: + +```markdown +**Question:** Where can I access the production codebase for each client platform this product supports, including iOS and Android if both exist? + +**Why it matters:** A migration path based on only one platform can miss native dependencies, product behavior, and delivery constraints that change the decision. +``` + +After the scope gate passes, grill rather than survey when repository evidence is unavailable. + +If the measurable migration driver is unknown, the first response must be exactly: + +```markdown +**Question:** What measurable delivery or business problem should a React Native migration solve? + +**Why it matters:** This determines whether migration is relevant and which outcomes the assessment must test. +``` + +If the driver is already known, ask only the next highest-impact unknown using the same two-line shape. End the turn immediately after the question and reason. Do not add a preamble, questionnaire, recommendation, or implementation guidance. + +## Rules + +- Treat every production app as a source of truth, including undocumented behavior. +- Inspect available code, CI, tests, release configuration, product documents, and runtime evidence before asking questions. +- Compare iOS and Android explicitly where their implementation, behavior, dependencies, delivery, or roadmap differ. +- Base product-wide claims only on evidence from every supported platform, or qualify their platform coverage. +- Label material claims `observed`, `measured`, `reported`, `assumed`, or `unknown`. +- Recommend from evidence, not an aggregate readiness score. +- Default to gathering evidence, not to brownfield, greenfield, or migration itself. +- Own the decision phase. Do not apply implementation skills or select Expo versus bare React Native before Path A is accepted. +- Count only React Native's marginal value over the current native system. +- Measure agents by accepted, verified work, not tokens, generated code, or pull requests. +- Make no universal claims about duration, cost, code sharing, agent productivity, or ROI. + +## Select the Evidence Mode + +Use repository-backed assessment when source code or delivery artifacts are available: + +1. Complete the platform inventory and establish which repositories the assessment can inspect. +2. For each accessible mobile codebase, locate app variants, CI, tests, release configuration, architecture records, and product documentation. +3. Search each native codebase for SDKs, permissions, app extensions, storage, authentication, push, deep links, analytics, experiments, and platform-specific behavior. +4. Cite repository names plus file paths and line numbers so evidence remains attributable when codebases are separate. +5. Ask stakeholders only for missing codebase locations or for product, organizational, and operational facts the repositories cannot establish. + +Use interview assessment when the repository is unavailable or material evidence remains missing: + +1. Start with the measurable delivery or business problem unless the user already supplied it. +2. Ask exactly one decision-changing question per turn. +3. Explain in one sentence which path, risk, or assumption the answer affects. +4. Challenge vague or contradictory answers with a narrower follow-up instead of accepting them as evidence. +5. Record the answer, update the evidence state, and choose the next highest-impact unknown. +6. Stop grilling when another answer cannot change the recommendation, confidence, or checkpoint. + +Before the evidence gate passes, every response must contain only: + +```markdown +**Question:** [one question] + +**Why it matters:** [one sentence] +``` + +Do not include a questionnaire, path recommendation, checkpoint, or implementation guidance during these turns. If the user pauses the interview, return the current evidence state and the single highest-impact unknown without pretending the assessment is complete. + +**Interview turn gate:** one answer has been requested, its decision impact is explicit, and no second question appears. + +## 1. Collect Decision Evidence + +State the decision, deadline, current alternative, and measurable driver. A framework preference is not a driver. + +Inspect these dimensions: + +| Dimension | Minimum evidence | +| --- | --- | +| Product | Supported platforms, app variants, shared versus platform-specific roadmap, critical flows, accessibility, analytics, and edge cases | +| Native surface | SDKs, modules, permissions, background work, app extensions, payments, hardware APIs, custom rendering, and viable React Native paths | +| Continuity | Auth and sessions, secure and persisted storage, push tokens, deep links, subscriptions, installed-user update, legal, security, and offline constraints | +| Verification | Reproducible builds, test accounts, manual and automated QA, device control, native-reference evidence, performance baselines, and independent review | +| Release | Current cadence and recovery, internal distribution, flags, experiments, store rollout, and desired binary plus optional OTA lanes | +| Ownership | Decision authority and owners for artifacts, parity, native boundaries, shared foundations, verification, and releases | +| Agent governance | Approved model and source boundary, protected secrets and test data, least-privilege access, evidence retention, audit trail, and human architecture and release approval | +| Delivery baseline | Duplicate implementation and review, waiting and handoffs, parity gap, two-platform verification, release metrics, defects, rework, and maintenance cost | + +For an OTA-dependent plan, require an owner plus runtime compatibility, rollout, observability, rollback or republish, and audit policy. OTA availability alone is not a migration benefit. + +Use a small migration core that combines existing product and native knowledge with React Native migration expertise. Ask only for missing facts that could change the decision; expose the rest as assumptions. + +**Gate:** every dimension has evidence or an explicit unknown, and every path-blocking unknown is named. + +## 2. Choose a Path + +Choose one outcome and state why the alternatives lose. + +| Outcome | Recommend when | +| --- | --- | +| **Path A: brownfield** | Release or installed-user continuity dominates, native coupling is deep, flows can move independently, or whole-app cutover risk is unacceptable. Include the cost of host boundaries and dual architecture. | +| **Path B: greenfield** | Behavior is recoverable, native dependencies have credible replacements, continuity can be proven, verification is strong, and legacy scope can be controlled until replacement. | +| **Path C: greenfield-first checkpoint with brownfield fallback** | Greenfield offers a simpler target but material uncertainty remains, and completed React Native work can be proven inside the native hosts before scaling. | +| **Defer** | The business case is plausible, but evidence, verification, ownership, budget, or release readiness is missing. Name the smallest readiness work and reopening condition. | +| **Do not migrate** | The native system meets the desired outcomes, duplicated mobile delivery is not material, the roadmap is asymmetric, platform-specific work dominates, or risk-adjusted return is not credible. | + +Treat Path C as Callstack's emerging post-2025 operating model, not an industry benchmark. Agent access makes behavioral porting more viable; only a measured checkpoint on this product establishes speed and quality. + +After Path A is accepted, hand implementation planning to [react-native-brownfield-migration](../react-native-brownfield-migration/SKILL.md). Do not repeat its Expo, XCFramework, AAR, or host-integration guidance. + +**Gate:** one outcome is supported by decisive evidence, rejected alternatives have reasons, and confidence reflects evidence quality. + +## 3. Define a Representative Checkpoint + +Use a checkpoint for Path C and whenever one uncertainty could invalidate the recommended path. Set a fixed calendar and effort budget supplied by the organization. Select two or three vertical flows: + +1. A common flow covering UI, data, analytics, and navigation. +2. An authenticated, stateful flow covering persistence, errors, and session behavior. +3. The boundary most likely to disprove the plan, such as a native SDK, background task, hardware API, offline behavior, app extension, accessibility requirement, or low-end Android constraint. + +Tie each flow to native source references, runtime evidence, owners, and parity scenarios. Do not select only easy screens. + +Define measurable acceptance criteria against the existing product: + +- Behavior, states, validation, errors, analytics, accessibility, and visual output match the native reference. +- Auth, storage, deep links, push, and selected native boundaries work on required devices and OS versions. +- Startup, interaction, memory, and crash behavior meet agreed baselines or tolerances. +- CI, internal distribution, observability, and the intended release lane work reliably enough to continue. +- Every flow has device-level evidence and an independent review with clean context. +- Path C packages and opens at least one representative React Native flow in each required native host. + +Run two passes on at least one flow: + +1. **Faithful pass:** preserve behavior, analytics, accessibility, states, and edge cases. Record native-shaped architecture retained for parity. +2. **Idiomatic pass:** introduce React component composition, clear state boundaries, typed navigation, reusable primitives, suitable tests, and measured performance. Repeat parity and device checks. + +Before scaling, assign owners for `MIGRATION.md`, `SCREENS.tsv`, `STATE_AND_STORAGE.tsv`, `DEPENDENCIES.tsv`, `EVENTS.tsv`, and `PARITY_CHECKS.md`. During assessment, identify missing artifacts rather than creating full inventories unless asked. + +End with one decision: continue greenfield, continue Path C, move completed work into Path A, defer, or stop. Do not extend the checkpoint merely because its result is inconvenient. + +**Gate:** flows, budget, criteria, evidence, owners, and terminal decisions are explicit. + +## 4. Test the Economics + +Map one representative change from ready-for-implementation to verified availability on both platforms. Capture two clocks: + +1. **Change lead time:** commit to an artifact available to a production cohort; separate binary and OTA delivery. +2. **Cross-platform feature lead time:** ready-for-implementation to verified availability on iOS and Android. + +Count potential return from removed duplicate implementation and business-logic review, lower parity coordination, engineer mobility, and measured release or recovery improvements. Keep iOS and Android device verification in the model. Keep web convergence outside the base case unless separately funded and assessed. + +Count full investment: checkpoint, dual maintenance, native modules, training, idiomatic second pass, testing and device automation, observability, release infrastructure, platform ownership, cutover, and native-surface retirement. + +Do not invent payback inputs. Use ranges when supplied data is uncertain. Revise, defer, or stop when verification and boundary work consume expected savings, native surfaces keep growing, dual maintenance has no retirement bound, agents add rework without verified throughput, parity improves without delivery improvement, or released capacity has no planned use. + +**Gate:** marginal benefits and full investment can be compared without treating two-platform verification as removed work. + +## Output Contract + +Use this contract only after the evidence gate passes or when the user ends the interview. + +Return a concise report in this order: + +1. **Recommendation:** outcome, confidence, decisive reason, and decision boundary. +2. **Evidence:** platform inventory, coverage limits, and material findings with source status and platform differences. +3. **Assumptions and blockers:** unknowns ordered by decision impact. +4. **Checkpoint:** budget, flows, criteria, owners, evidence, and terminal decisions. +5. **Baseline and ROI:** measured inputs, missing inputs, counted return, full investment, and exclusions. +6. **Next decision:** who decides what, using which evidence, and when. + +Keep the conclusion diagnostic. If execution comes up, state only that orchestration, task contracts, prompts, retry rules, and private agents are engagement-specific. Never expose or invent private implementation material. diff --git a/.agents/skills/assess-react-native-migration/agents/openai.yaml b/.agents/skills/assess-react-native-migration/agents/openai.yaml new file mode 100644 index 00000000..f62b0f1f --- /dev/null +++ b/.agents/skills/assess-react-native-migration/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Assess React Native Migration" + short_description: "Assess migration readiness and choose a path" + default_prompt: "Use $assess-react-native-migration to assess this mobile product. Find every production client codebase, including separate iOS and Android repositories, ask me for any locations you cannot infer, then inspect the available evidence and recommend a migration path and checkpoint." diff --git a/.agents/skills/create-react-native-library/SKILL.md b/.agents/skills/create-react-native-library/SKILL.md new file mode 100644 index 00000000..3613cd07 --- /dev/null +++ b/.agents/skills/create-react-native-library/SKILL.md @@ -0,0 +1,73 @@ +--- +name: create-react-native-library +description: Scaffolds React Native libraries with create-react-native-library for standalone libraries or local native modules and views. Use when creating or working on React Native libraries or adding native functionality in an existing app. +license: MIT +--- + +# Create React Native Library + +## Overview + +Use this skill to scaffold a standalone React Native library or a local library inside an existing app, then continue with the correct implementation docs. + +Examples: + +- JS-only library that may use other React Native libraries +- Native modules that expose native functionality to JavaScript +- Native UI components that render native views in React Native + +Choose one flow first: + +- Use [scaffold-library.md][scaffold-library] when creating a new library that may be published to npm +- Use [local-library.md][local-library] when exposing native functionality in a React Native app + +## When to Apply + +Use this skill when: + +- Creating or working on a React Native library with `create-react-native-library` +- Creating a native module or view in an existing app +- Wrapping native SDKs and exposing them to React Native + +## Quick Reference + +```bash +# Inspect current options before scaffolding +npx create-react-native-library@latest --help + +# Scaffold a library with turbo modules and the Expo example app +npx create-react-native-library@latest awesome-library \ + --no-interactive \ + --yes \ + --description "A brief description of the library" \ + --type turbo-module \ + --languages kotlin-objc \ + --example expo + +# Scaffold a local Turbo Module inside an existing app +cd MyApp +npx create-react-native-library@latest awesome-library \ + --local \ + --no-interactive \ + --yes \ + --description "A brief description of the library" \ + --type turbo-module \ + --languages kotlin-objc +``` + +## References + +| File | Description | +| --------------------------------------- | ------------------------------------------------------- | +| [scaffold-library.md][scaffold-library] | Scaffold a new library and default to the Expo example | +| [local-library.md][local-library] | Add a local library to an existing app with autolinking | + +## Problem -> Skill Mapping + +| Problem | Start With | +| -------------------------------------------- | ------------------------------------ | +| Need a new library scaffold | [scaffold-library][scaffold-library] | +| Need to add a local native library to an app | [local-library][local-library] | + +[scaffold-library]: references/scaffold-library.md +[local-library]: references/local-library.md diff --git a/.agents/skills/create-react-native-library/agents/openai.yaml b/.agents/skills/create-react-native-library/agents/openai.yaml new file mode 100644 index 00000000..58355c7f --- /dev/null +++ b/.agents/skills/create-react-native-library/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "Create React Native Library" + short_description: "Scaffold standalone libraries or local app modules" + default_prompt: "Use $create-react-native-library to scaffold a standalone React Native library or a local app module with create-react-native-library, verify the current CLI help first, and route to the correct official native docs after scaffolding." diff --git a/.agents/skills/create-react-native-library/references/local-library.md b/.agents/skills/create-react-native-library/references/local-library.md new file mode 100644 index 00000000..480c1473 --- /dev/null +++ b/.agents/skills/create-react-native-library/references/local-library.md @@ -0,0 +1,86 @@ +--- +title: Add a Local Library to an Existing App +impact: HIGH +tags: react-native, library, create-react-native-library, local, autolinking, monorepo, expo +--- + +# Skill: Add a Local Library to an Existing App + +Scaffold a local library inside an existing React Native app with `create-react-native-library`. + +## When to Use + +- Adding native functionality or UI components to an existing React Native app + +## Prerequisites + +- An existing React Native app with `package.json` at the root + +## Step-by-Step Instructions + +```text +Progress checklist: +- [ ] Create local library with CLI +- [ ] Verify symlink setup in node_modules +- [ ] Install dependencies to create symlink +- [ ] Import and use the library in the app +``` + +- Run `npx create-react-native-library@latest --help` to check the latest available options. +- Choose the appropriate `--type` based on user's requirements: + - Use `turbo-module` or `nitro-module` for exposing native APIs. + - Use `fabric-view` or `nitro-view` for rendering native UI components. +- Choose the appropriate `--languages` based on the `--type`: + - Use `kotlin-objc` for Turbo Modules or Fabric Components. + - Use `kotlin-swift` for Nitro Modules or Nitro Views. +- Run the `npx create-react-native-library@latest` command with the `--local` flag from the root of the existing app to scaffold a local library under the `modules/` folder: + + ```bash + cd MyApp + + # Scaffold a local Turbo Module inside the app repo + npx create-react-native-library@latest awesome-library \ + --local \ + --no-interactive \ + --yes \ + --description "A brief description of the library" \ + --type turbo-module \ + --languages kotlin-objc + ``` + +- Verify the local library is linked correctly in the app's `dependencies` in `package.json`: + - If the app uses Yarn, the generated dependency entry in `package.json` should use `link:` (`"react-native-awesome-library": "link:./modules/awesome-library"`) + - If the app uses npm, the generated dependency entry in `package.json` should use `file:` (`"react-native-awesome-library": "file:./modules/awesome-library"`) + - For other package manager setups, refer to the package manager documentation on how to create symlink under `node_modules` that points to the local library. +- Run `yarn install` or `npm install` from the app root to create the symlink under `node_modules` that points to the local library. +- For nitro modules or views, ensure `react-native-nitro-modules` dependency is added to the app's `package.json`. +- Import the library with its package name (e.g. `import { multiply } from 'react-native-awesome-library'`) in the app to use it. + +## Generated Project Structure + +Typical generated layout: + +```text +MyApp/ + package.json + modules/ + awesome-library/ + src/ TypeScript entry points + android/ Android native implementation + ios/ iOS native implementation + package.json Local package metadata +``` + +The local library is self contained under the `modules/` folder and is symlinked into the app's `node_modules/` for autolinking. + +## Common Pitfalls + +- **Not running the command from an app directory**: Run from a React Native app directory that already contains `package.json`. +- **Using incorrect combination of `--type` and `--languages`**: Use `kotlin-objc` for turbo modules and fabric views, and use `kotlin-swift` for nitro modules and nitro views. + +## Canonical Docs + +- [Local library](https://oss.callstack.com/react-native-builder-bob/create#local-library) +- [React Native Turbo Modules](https://reactnative.dev/docs/turbo-native-modules-introduction) +- [React Native Fabric Native Components](https://reactnative.dev/docs/fabric-native-components-introduction) +- [Nitro Modules Getting Started](https://nitro.margelo.com/docs/getting-started/what-is-nitro) diff --git a/.agents/skills/create-react-native-library/references/scaffold-library.md b/.agents/skills/create-react-native-library/references/scaffold-library.md new file mode 100644 index 00000000..9dbec73c --- /dev/null +++ b/.agents/skills/create-react-native-library/references/scaffold-library.md @@ -0,0 +1,96 @@ +--- +title: Create a React Native Library +impact: HIGH +tags: react-native, library, create-react-native-library, expo, builder-bob, scaffolding +--- + +# Skill: Create a React Native Library + +Scaffold a new library with `create-react-native-library`. + +## When to Use + +- Starting a new React Native library that may be published to npm +- Working on an existing React Native library scaffolded with `create-react-native-library` + +## Prerequisites + +- Node.js version should match one of the following: + - 20.19.0 or higher (LTS) + - 22.12.0 or higher (LTS) + - 23.4.0 or higher + +## Step-by-Step Instructions + +```text +Progress checklist: +- [ ] Scaffold library with CLI +- [ ] Read generated CONTRIBUTING.md for next steps +- [ ] Run yarn install to set up dependencies +- [ ] Run post install tasks (e.g. prebuild, pod-install, nitrogen) +- [ ] Run example app to test the library +``` + +- Run `npx create-react-native-library@latest --help` to check the latest available options. +- Choose the appropriate `--type` based on user's requirements: + - Use `turbo-module` or `nitro-module` for exposing native APIs. + - Use `fabric-view` or `nitro-view` for rendering native UI components. + - Use `library` for a JS-only package. +- Choose the appropriate `--languages` based on the `--type`: + - Use `kotlin-objc` for Turbo Modules or Fabric Components. + - Use `kotlin-swift` for Nitro Modules or Nitro Views. + - Use `js` for a JS-only library. +- Choose the appropriate `--example`: + - Use `expo` for a managed Expo example app. This is recommended for smoother upgrades unless the user explicitly requests a different example. + - Use `vanilla` for an app with React Native Community CLI. + - Use `test-app` for a Microsoft's React Native Test App example. +- Run the `npx create-react-native-library@latest` command with the appropriate flags to scaffold the library: + + ```bash + # Scaffold a library with turbo modules and the Expo example app + npx create-react-native-library@latest awesome-library \ + --no-interactive \ + --yes \ + --description "A brief description of the library" \ + --type turbo-module \ + --languages kotlin-objc \ + --example expo + ``` + +- Read the generated `CONTRIBUTING.md` for project-specific install, build, lint, test, and release instructions. +- Run `yarn install` after scaffolding to set up dependencies. +- When using expo example for native modules and views, run `yarn example expo prebuild` to generate native code for the example app. +- For native modules and views, run `cd example && npx pod-install` to install CocoaPod dependencies. +- For nitro modules and views, run `yarn nitrogen` to generate required native code before building the example app. +- Use `yarn example start` to start the metro bundler for the example app. +- Use `yarn example ios` or `yarn example android` to run the example app on a simulator or device. +- Use `yarn example web` to run the example app in a web browser if the library should work on web. + +## Generated Project Structure + +Typical generated layout: + +```text +awesome-library/ + src/ TypeScript entry points + example/ Example app to test the library during development + android/ Android native implementation when applicable + ios/ iOS native implementation when applicable + package.json Package metadata, scripts and config + CONTRIBUTING.md Development workflow for the generated project +``` + +The exact files vary by `--type`, `--languages`, and selected tools. + +## Common Pitfalls + +- **Not using Yarn**: The generated project uses a monorepo based on Yarn workspaces. Only use `yarn` and do not use other package managers. +- **Using incompatible Node.js version**: Ensure the Node.js version matches the required versions mentioned in the prerequisites. +- **Incorrectly adding native dependencies**: Any React Native libraries that contain native code and are used in the library must be added as `peerDependencies` and `devDependencies` in the library's root `package.json`, then `dependencies` of the example app's `package.json`. + +## Canonical Docs + +- [Scaffold a React Native library](https://oss.callstack.com/react-native-builder-bob/create) +- [React Native Turbo Modules](https://reactnative.dev/docs/turbo-native-modules-introduction) +- [React Native Fabric Native Components](https://reactnative.dev/docs/fabric-native-components-introduction) +- [Nitro Modules - Getting Started](https://nitro.margelo.com/docs/getting-started/what-is-nitro) diff --git a/.agents/skills/github-actions/SKILL.md b/.agents/skills/github-actions/SKILL.md new file mode 100644 index 00000000..8f420a33 --- /dev/null +++ b/.agents/skills/github-actions/SKILL.md @@ -0,0 +1,52 @@ +--- +name: github-actions +description: GitHub Actions workflow patterns for React Native iOS simulator and Android emulator cloud builds with downloadable artifacts. Use when setting up CI build pipelines or downloading GitHub Actions artifacts via gh CLI and GitHub API. +license: MIT +--- + +# GitHub Actions Build Artifacts + +## Overview + +Reusable GitHub Actions patterns to build React Native apps for iOS simulators and Android emulators in the cloud, then publish artifacts retrievable via `gh` CLI or GitHub API. + +## When to Apply + +Use this skill when: +- Creating CI workflows that build React Native simulator/emulator artifacts. +- Uploading iOS simulator and Android emulator installables from PRs or manual dispatch runs. +- Replacing local-only mobile builds with downloadable CI artifacts. +- Needing stable artifact IDs/names for scripted retrieval with `gh` or REST API. + +## Quick Reference + +1. Add composite actions from [gha-ios-composite-action.md][gha-ios-composite-action] and [gha-android-composite-action.md][gha-android-composite-action]. +2. Wire them into `.github/workflows/mobile-build.yml` from [gha-workflow-and-downloads.md][gha-workflow-and-downloads]. +3. Upload with `actions/upload-artifact@v4` and capture `artifact-id` output. +4. Download with `gh run download` or `GET /repos/{owner}/{repo}/actions/artifacts/{artifact_id}/{archive_format}`. + +## References + +| File | Description | +|------|-------------| +| [gha-ios-composite-action.md][gha-ios-composite-action] | Composite `action.yml` for iOS simulator `.app.tar.gz` builds and artifact upload | +| [gha-android-composite-action.md][gha-android-composite-action] | Composite `action.yml` for Android emulator `.apk` builds and artifact upload | +| [gha-workflow-and-downloads.md][gha-workflow-and-downloads] | End-to-end workflow wiring plus `gh` and REST download commands | + +## Problem -> Skill Mapping + +| Problem | Start With | +|---------|------------| +| Need CI iOS simulator `.app.tar.gz` artifact | [gha-ios-composite-action.md][gha-ios-composite-action] | +| Need CI Android emulator `.apk` artifact | [gha-android-composite-action.md][gha-android-composite-action] | +| Need one workflow to trigger both platform jobs | [gha-workflow-and-downloads.md][gha-workflow-and-downloads] | +| Need scripted artifact download | [gha-workflow-and-downloads.md][gha-workflow-and-downloads] | + +## Source Inspiration + +- [callstackincubator/ios/action.yml](https://github.com/callstackincubator/ios/blob/main/action.yml) +- [callstackincubator/android/action.yml](https://github.com/callstackincubator/android/blob/main/action.yml) + +[gha-ios-composite-action]: references/gha-ios-composite-action.md +[gha-android-composite-action]: references/gha-android-composite-action.md +[gha-workflow-and-downloads]: references/gha-workflow-and-downloads.md diff --git a/.agents/skills/github-actions/agents/openai.yaml b/.agents/skills/github-actions/agents/openai.yaml new file mode 100644 index 00000000..de03ab53 --- /dev/null +++ b/.agents/skills/github-actions/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "GitHub Actions Builds" + short_description: "React Native GitHub Actions simulator/emulator build artifact patterns" + default_prompt: "Use $github-actions to set up React Native GitHub Actions builds and download artifacts with gh or API." diff --git a/.agents/skills/github-actions/references/gha-android-composite-action.md b/.agents/skills/github-actions/references/gha-android-composite-action.md new file mode 100644 index 00000000..e7aca64a --- /dev/null +++ b/.agents/skills/github-actions/references/gha-android-composite-action.md @@ -0,0 +1,158 @@ +--- +title: Android Emulator Composite Action (RN CLI) +impact: HIGH +tags: android, emulator, github-actions, react-native, gradle, artifact +--- + +# Skill: Android Emulator Composite Action (RN CLI) + +Composite action template for building React Native Android emulator APKs in GitHub Actions and uploading the resulting artifact. + +## Quick Config + +1. Create `.github/actions/github-actions/android-build/action.yml`. +2. Copy the template below. +3. Set `variant` (for emulator flows, use `Debug` by default). +4. Use action outputs (`artifact-name`, `artifact-id`, `artifact-url`) in downstream jobs. + +## When to Use + +- Need cloud Android emulator build artifacts for testing. +- Need configurable debug-style builds from one action. +- Need reliable artifact retrieval through `gh` and REST API. + +## Prerequisites + +- Linux runner with JDK 17. +- React Native dependencies installed. +- Android SDK and Gradle wrapper available in the repository. + +## Template (`.github/actions/github-actions/android-build/action.yml`) + +```yaml +name: React Native Android Emulator Build +description: Build React Native Android emulator APK in GitHub Actions and upload artifact + +inputs: + working-directory: + description: Project root + required: false + default: "." + variant: + description: Build variant (Debug by default for emulator flows) + required: false + default: Debug + artifact-prefix: + description: Prefix for artifact naming + required: false + default: rn-android-emulator + custom-identifier: + description: Optional stable identifier (PR number, channel, etc.) + required: false + artifact-retention-days: + description: GitHub artifact retention + required: false + default: "7" + +outputs: + artifact-name: + description: Uploaded artifact name + value: ${{ steps.names.outputs.artifact_name }} + artifact-id: + description: Uploaded artifact id + value: ${{ steps.upload.outputs.artifact-id }} + artifact-url: + description: Uploaded artifact URL + value: ${{ steps.upload.outputs.artifact-url }} + +runs: + using: composite + steps: + - name: Resolve Android project settings + id: resolve + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + set -euo pipefail + + CONFIG_JSON="$(npx react-native config)" + ANDROID_SOURCE_DIR="$(printf '%s' "$CONFIG_JSON" | node -e "const fs=require('fs');const j=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(j.project?.android?.sourceDir || 'android')")" + APP_NAME="$(printf '%s' "$CONFIG_JSON" | node -e "const fs=require('fs');const j=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(j.project?.android?.appName || 'app')")" + + IDENTIFIER="${{ inputs.custom-identifier }}" + if [[ -z "$IDENTIFIER" ]]; then + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + IDENTIFIER="pr-${{ github.event.pull_request.number }}" + else + IDENTIFIER="${GITHUB_SHA::7}" + fi + fi + + echo "android_source_dir=$ANDROID_SOURCE_DIR" >> "$GITHUB_OUTPUT" + echo "app_name=$APP_NAME" >> "$GITHUB_OUTPUT" + echo "identifier=$IDENTIFIER" >> "$GITHUB_OUTPUT" + + - name: Build Android APK + id: build + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + set -euo pipefail + + VARIANT="${{ inputs.variant }}" + VARIANT_LOWER="$(echo "$VARIANT" | tr '[:upper:]' '[:lower:]')" + GRADLE_TASK="assemble${VARIANT}" + + ( + cd "${{ steps.resolve.outputs.android_source_dir }}" + ./gradlew ":${{ steps.resolve.outputs.app_name }}:${GRADLE_TASK}" + ) + + OUTPUT_ROOT="${{ steps.resolve.outputs.android_source_dir }}/${{ steps.resolve.outputs.app_name }}/build/outputs/apk" + SEARCH_DIR="$OUTPUT_ROOT" + if [[ -d "$OUTPUT_ROOT/$VARIANT_LOWER" ]]; then + SEARCH_DIR="$OUTPUT_ROOT/$VARIANT_LOWER" + fi + + APK_PATH="$(find "$SEARCH_DIR" -type f -name '*.apk' ! -name '*androidTest*' | sort | head -n1 || true)" + if [[ -z "$APK_PATH" ]]; then + APK_PATH="$(find "$OUTPUT_ROOT" -type f -name '*.apk' ! -name '*androidTest*' | sort | head -n1 || true)" + fi + + if [[ -z "$APK_PATH" ]]; then + echo "No Android APK found" + exit 1 + fi + + echo "apk_path=$APK_PATH" >> "$GITHUB_OUTPUT" + + - name: Build artifact name + id: names + shell: bash + run: | + set -euo pipefail + + VARIANT="$(echo "${{ inputs.variant }}" | tr '[:upper:]' '[:lower:]')" + NAME="${{ inputs.artifact-prefix }}-${VARIANT}-${{ steps.resolve.outputs.identifier }}" + echo "artifact_name=$NAME" >> "$GITHUB_OUTPUT" + + - name: Upload artifact + id: upload + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.names.outputs.artifact_name }} + path: ${{ steps.build.outputs.apk_path }} + if-no-files-found: error + retention-days: ${{ inputs.artifact-retention-days }} +``` + +## Common Pitfalls + +- Lowercase `variant` values causing wrong Gradle task names. +- Missing JDK setup in caller workflow. +- Hardcoding module name to `app` when `react-native config` reports a custom `appName`. + +## Related Skills + +- [gha-ios-composite-action.md](gha-ios-composite-action.md) +- [gha-workflow-and-downloads.md](gha-workflow-and-downloads.md) diff --git a/.agents/skills/github-actions/references/gha-ios-composite-action.md b/.agents/skills/github-actions/references/gha-ios-composite-action.md new file mode 100644 index 00000000..247f36b5 --- /dev/null +++ b/.agents/skills/github-actions/references/gha-ios-composite-action.md @@ -0,0 +1,228 @@ +--- +title: iOS Simulator Composite Action (RN CLI) +impact: HIGH +tags: ios, simulator, github-actions, react-native, xcodebuild, artifact +--- + +# Skill: iOS Simulator Composite Action (RN CLI) + +Composite action template for building React Native iOS simulator apps in GitHub Actions and uploading `.app.tar.gz` artifacts. + +## Quick Config + +1. Create `.github/actions/github-actions/ios-build/action.yml`. +2. Copy the template below. +3. Set your app `scheme` and optional `configuration`. +4. Use `actions/upload-artifact@v4` outputs (`artifact-id`, `artifact-url`). +5. Download later by ID (REST) or by run/name (`gh run download`). + +## When to Use + +- Need cloud iOS simulator build artifacts for QA or PR validation. +- Need deterministic artifact naming and machine-readable IDs. +- Need RN CLI project discovery without Rock (`npx react-native config`). + +## Prerequisites + +- macOS runner (`macos-latest` recommended). +- Xcode scheme is known and buildable in CI. +- JS dependencies installed before invoking the action. + +## Template (`.github/actions/github-actions/ios-build/action.yml`) + +```yaml +name: React Native iOS Simulator Build +description: Build React Native iOS simulator app in GitHub Actions and upload artifact + +inputs: + working-directory: + description: Project root + required: false + default: "." + scheme: + description: Xcode scheme + required: true + configuration: + description: Xcode configuration + required: false + default: Debug + workspace-path: + description: Optional path to .xcworkspace + required: false + project-path: + description: Optional path to .xcodeproj + required: false + derived-data-path: + description: DerivedData path relative to working-directory + required: false + default: build/ios/DerivedData + artifact-prefix: + description: Prefix for artifact naming + required: false + default: rn-ios-simulator + custom-identifier: + description: Optional stable identifier (PR number, channel, etc.) + required: false + artifact-retention-days: + description: GitHub artifact retention + required: false + default: "7" + +outputs: + artifact-name: + description: Uploaded artifact name + value: ${{ steps.names.outputs.artifact_name }} + artifact-id: + description: Uploaded artifact id + value: ${{ steps.upload.outputs.artifact-id }} + artifact-url: + description: Uploaded artifact URL + value: ${{ steps.upload.outputs.artifact-url }} + +runs: + using: composite + steps: + - name: Validate inputs + shell: bash + run: | + set -euo pipefail + + if [[ -n "${{ inputs.workspace-path }}" && -n "${{ inputs.project-path }}" ]]; then + echo "Use workspace-path or project-path, not both" + exit 1 + fi + + - name: Resolve iOS project settings + id: resolve + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + set -euo pipefail + + CONFIG_JSON="$(npx react-native config)" + IOS_SOURCE_DIR="$(printf '%s' "$CONFIG_JSON" | node -e "const fs=require('fs');const j=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(j.project?.ios?.sourceDir || 'ios')")" + + WORKSPACE="${{ inputs.workspace-path }}" + PROJECT="${{ inputs.project-path }}" + + if [[ -z "$WORKSPACE" && -z "$PROJECT" ]]; then + WORKSPACE="$(find "$IOS_SOURCE_DIR" -maxdepth 2 -name '*.xcworkspace' | head -n1 || true)" + PROJECT="$(find "$IOS_SOURCE_DIR" -maxdepth 2 -name '*.xcodeproj' | head -n1 || true)" + fi + + if [[ -n "$WORKSPACE" ]]; then + CONTAINER_KIND="workspace" + CONTAINER_PATH="$WORKSPACE" + elif [[ -n "$PROJECT" ]]; then + CONTAINER_KIND="project" + CONTAINER_PATH="$PROJECT" + else + echo "Could not find .xcworkspace or .xcodeproj" + exit 1 + fi + + IDENTIFIER="${{ inputs.custom-identifier }}" + if [[ -z "$IDENTIFIER" ]]; then + if [[ "${{ github.event_name }}" == "pull_request" ]]; then + IDENTIFIER="pr-${{ github.event.pull_request.number }}" + else + IDENTIFIER="${GITHUB_SHA::7}" + fi + fi + + echo "container_kind=$CONTAINER_KIND" >> "$GITHUB_OUTPUT" + echo "container_path=$CONTAINER_PATH" >> "$GITHUB_OUTPUT" + echo "identifier=$IDENTIFIER" >> "$GITHUB_OUTPUT" + + - name: Build iOS simulator + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + set -euo pipefail + + if [[ "${{ steps.resolve.outputs.container_kind }}" == "workspace" ]]; then + XCODE_CONTAINER=( -workspace "${{ steps.resolve.outputs.container_path }}" ) + else + XCODE_CONTAINER=( -project "${{ steps.resolve.outputs.container_path }}" ) + fi + + xcodebuild \ + "${XCODE_CONTAINER[@]}" \ + -scheme "${{ inputs.scheme }}" \ + -configuration "${{ inputs.configuration }}" \ + -sdk iphonesimulator \ + -destination "generic/platform=iOS Simulator" \ + -derivedDataPath "${{ inputs.derived-data-path }}" \ + CODE_SIGNING_ALLOWED=NO \ + build + + - name: Package simulator app + id: simulator + shell: bash + working-directory: ${{ inputs.working-directory }} + run: | + set -euo pipefail + + PRODUCTS_DIR="${{ inputs.derived-data-path }}/Build/Products" + CONFIG_PRODUCTS_DIR="$PRODUCTS_DIR/${{ inputs.configuration }}-iphonesimulator" + SEARCH_DIR="$PRODUCTS_DIR" + if [[ -d "$CONFIG_PRODUCTS_DIR" ]]; then + SEARCH_DIR="$CONFIG_PRODUCTS_DIR" + fi + + # Prefer the app matching the scheme, then deterministic non-test fallbacks. + APP_PATH="$(find "$SEARCH_DIR" -type d -name "${{ inputs.scheme }}.app" | sort | head -n1 || true)" + if [[ -z "$APP_PATH" ]]; then + APP_PATH="$(find "$SEARCH_DIR" -type d -name '*.app' \ + ! -name '*Tests*.app' \ + ! -name '*UITests*.app' \ + ! -name '*-Runner.app' \ + | sort | head -n1 || true)" + fi + if [[ -z "$APP_PATH" ]]; then + APP_PATH="$(find "$SEARCH_DIR" -type d -name '*.app' | sort | head -n1 || true)" + fi + + if [[ -z "$APP_PATH" ]]; then + echo "No .app found in $SEARCH_DIR" + exit 1 + fi + + mkdir -p build/ios + APP_DIR="$(dirname "$APP_PATH")" + APP_NAME="$(basename "$APP_PATH")" + TARBALL="build/ios/${APP_NAME%.app}.app.tar.gz" + tar -C "$APP_DIR" -czf "$TARBALL" "$APP_NAME" + + echo "artifact_path=$TARBALL" >> "$GITHUB_OUTPUT" + + - name: Build artifact name + id: names + shell: bash + run: | + set -euo pipefail + + CONFIG="$(echo "${{ inputs.configuration }}" | tr '[:upper:]' '[:lower:]')" + NAME="${{ inputs.artifact-prefix }}-${CONFIG}-${{ steps.resolve.outputs.identifier }}" + echo "artifact_name=$NAME" >> "$GITHUB_OUTPUT" + + - name: Upload artifact + id: upload + uses: actions/upload-artifact@v4 + with: + name: ${{ steps.names.outputs.artifact_name }} + path: ${{ steps.simulator.outputs.artifact_path }} + if-no-files-found: error + retention-days: ${{ inputs.artifact-retention-days }} +``` + +## Common Pitfalls + +- Passing both `workspace-path` and `project-path`. +- Uploading `.app` directly instead of `tar.gz` (permission loss risk). +- Using non-macOS runner for iOS jobs. + +## Related Skills + +- [gha-android-composite-action.md](gha-android-composite-action.md) +- [gha-workflow-and-downloads.md](gha-workflow-and-downloads.md) diff --git a/.agents/skills/github-actions/references/gha-workflow-and-downloads.md b/.agents/skills/github-actions/references/gha-workflow-and-downloads.md new file mode 100644 index 00000000..45a3c9e0 --- /dev/null +++ b/.agents/skills/github-actions/references/gha-workflow-and-downloads.md @@ -0,0 +1,288 @@ +--- +title: Workflow Wiring and Artifact Downloads +impact: CRITICAL +tags: github-actions, workflow, artifacts, gh-cli, rest-api, simulator, emulator +--- + +# Skill: Workflow Wiring and Artifact Downloads + +Use this workflow to run iOS simulator and Android emulator builds in cloud CI and expose artifact metadata for scripted retrieval. + +## Minimum Required Inputs + +Set these before first run: +- iOS scheme: exact Xcode scheme name (for example `YourApp`). +- Android variant: Gradle variant for emulator artifacts (usually `Debug`). +- Branch strategy: branches for `push` and `pull_request` triggers (default below uses `main`). +- Retention days: artifact retention period passed to upload steps (for example `7`). + +## Repo-Compat Checklist (Before First Run) + +- Confirm the iOS scheme exists and builds locally. +- Confirm `pod install` works in CI context from iOS source dir. +- Confirm `android/gradlew` is executable (`chmod +x android/gradlew` if needed). +- Confirm `npx react-native config` resolves valid `project.ios.sourceDir` and `project.android.sourceDir`. + +## Quick Config + +1. Create `.github/workflows/mobile-build.yml`. +2. Call local composite actions from this skill (`github-actions/ios-build`, `github-actions/android-build`). +3. Keep `actions/upload-artifact@v4` output IDs. +4. Retrieve with `gh run download` or `gh api`. + +## When to Use + +- Need one pipeline for simulator/emulator artifacts. +- Need PR, push, and manual dispatch triggers. +- Need deterministic artifact retrieval in CI/CD or external tooling. + +## Workflow Template (`.github/workflows/mobile-build.yml`) + +```yaml +name: RN Cloud Build + +on: + # Baseline trigger strategy: validate incoming changes and direct branch updates. + push: + branches: [main] + pull_request: + branches: [main] + workflow_dispatch: + inputs: + ios_scheme: + description: iOS scheme name + required: true + default: YourApp + type: string + ios_configuration: + description: iOS configuration + required: true + default: Debug + type: string + android_variant: + description: Android Gradle variant + required: true + default: Debug + type: string + artifact_retention_days: + description: Artifact retention days + required: true + default: '7' + type: string + +permissions: + contents: read + actions: read + +env: + IOS_SCHEME: YourApp + IOS_CONFIGURATION: Debug + ANDROID_VARIANT: Debug + ARTIFACT_RETENTION_DAYS: '7' + +jobs: + ios: + name: iOS simulator build + runs-on: macos-latest + outputs: + artifact_name: ${{ steps.build.outputs.artifact-name }} + artifact_id: ${{ steps.build.outputs.artifact-id }} + artifact_url: ${{ steps.build.outputs.artifact-url }} + steps: + - uses: actions/checkout@v4 + + - name: Resolve Node version from package.json engines + id: node-version + run: | + set -euo pipefail + NODE_SPEC="$(python3 - <<'PY' +import json +from pathlib import Path +pkg = Path('package.json') +if not pkg.exists(): + print('22') +else: + data = json.loads(pkg.read_text()) + print((data.get('engines', {}).get('node') or '22').strip()) +PY + )" + echo "value=$NODE_SPEC" >> "$GITHUB_OUTPUT" + + - uses: actions/setup-node@v4 + with: + node-version: ${{ steps.node-version.outputs.value }} + cache: npm + + - name: Install JS dependencies + run: npm ci + + - name: Install CocoaPods dependencies + run: | + set -euo pipefail + IOS_SOURCE_DIR="$(npx react-native config | node -e "const fs=require('fs');const j=JSON.parse(fs.readFileSync(0,'utf8'));process.stdout.write(j.project?.ios?.sourceDir || 'ios')")" + cd "$IOS_SOURCE_DIR" + pod install --repo-update + + # Optional: only add ruby/setup-ruby when this repo enforces Ruby tooling + # (for example via .ruby-version and Bundler workflow). + # - uses: ruby/setup-ruby@v1 + # with: + # bundler-cache: true + + - name: Resolve iOS inputs + id: ios-inputs + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "scheme=${{ inputs.ios_scheme }}" >> "$GITHUB_OUTPUT" + echo "config=${{ inputs.ios_configuration }}" >> "$GITHUB_OUTPUT" + echo "retention=${{ inputs.artifact_retention_days }}" >> "$GITHUB_OUTPUT" + else + echo "scheme=${{ env.IOS_SCHEME }}" >> "$GITHUB_OUTPUT" + echo "config=${{ env.IOS_CONFIGURATION }}" >> "$GITHUB_OUTPUT" + echo "retention=${{ env.ARTIFACT_RETENTION_DAYS }}" >> "$GITHUB_OUTPUT" + fi + + - name: Build iOS simulator + id: build + uses: ./.github/actions/github-actions/ios-build + with: + scheme: ${{ steps.ios-inputs.outputs.scheme }} + configuration: ${{ steps.ios-inputs.outputs.config }} + artifact-prefix: rn-ios-simulator + artifact-retention-days: ${{ steps.ios-inputs.outputs.retention }} + + android: + name: Android emulator build + runs-on: ubuntu-latest + outputs: + artifact_name: ${{ steps.build.outputs.artifact-name }} + artifact_id: ${{ steps.build.outputs.artifact-id }} + artifact_url: ${{ steps.build.outputs.artifact-url }} + steps: + - uses: actions/checkout@v4 + + - uses: actions/setup-java@v4 + with: + distribution: temurin + java-version: '17' + cache: gradle + + - name: Resolve Node version from package.json engines + id: node-version + run: | + set -euo pipefail + NODE_SPEC="$(python3 - <<'PY' +import json +from pathlib import Path +pkg = Path('package.json') +if not pkg.exists(): + print('22') +else: + data = json.loads(pkg.read_text()) + print((data.get('engines', {}).get('node') or '22').strip()) +PY + )" + echo "value=$NODE_SPEC" >> "$GITHUB_OUTPUT" + + - uses: actions/setup-node@v4 + with: + node-version: ${{ steps.node-version.outputs.value }} + cache: npm + + - name: Install JS dependencies + run: npm ci + + - name: Resolve Android inputs + id: android-inputs + run: | + if [[ "${{ github.event_name }}" == "workflow_dispatch" ]]; then + echo "variant=${{ inputs.android_variant }}" >> "$GITHUB_OUTPUT" + echo "retention=${{ inputs.artifact_retention_days }}" >> "$GITHUB_OUTPUT" + else + echo "variant=${{ env.ANDROID_VARIANT }}" >> "$GITHUB_OUTPUT" + echo "retention=${{ env.ARTIFACT_RETENTION_DAYS }}" >> "$GITHUB_OUTPUT" + fi + + - name: Build Android emulator APK + id: build + uses: ./.github/actions/github-actions/android-build + with: + variant: ${{ steps.android-inputs.outputs.variant }} + artifact-prefix: rn-android-emulator + artifact-retention-days: ${{ steps.android-inputs.outputs.retention }} + + summary: + name: Build summary + runs-on: ubuntu-latest + needs: [ios, android] + steps: + - name: Publish artifact metadata + run: | + { + echo "## RN Cloud Build Artifacts" + echo "" + echo "- iOS simulator (.app.tar.gz): name=${{ needs.ios.outputs.artifact_name }}, id=${{ needs.ios.outputs.artifact_id }}" + echo "- Android emulator (.apk): name=${{ needs.android.outputs.artifact_name }}, id=${{ needs.android.outputs.artifact_id }}" + echo "" + echo "Artifact URLs (auth required):" + echo "- iOS: ${{ needs.ios.outputs.artifact_url }}" + echo "- Android: ${{ needs.android.outputs.artifact_url }}" + } >> "$GITHUB_STEP_SUMMARY" +``` + +## CocoaPods and Ruby Notes + +- Run `pod install` from `ios/` or from `project.ios.sourceDir` resolved via `npx react-native config`. +- Do not assume Bundler or pinned Ruby is always required. +- `ruby/setup-ruby` is optional and should be added only when repo policy enforces Ruby tooling (for example `.ruby-version` and Bundler-managed pods). + +## Troubleshooting + +| Symptom | Likely Cause | Fix | +|---|---|---| +| `ruby/setup-ruby` or Bundler fails | Repo does not require Ruby toolchain in CI | Remove Ruby setup and run plain `pod install` | +| `xcodebuild: Scheme ... not found` | Wrong iOS scheme value | Use exact shared scheme from Xcode project/workspace | +| `Task ':app:assembledebug' not found` | Wrong Android variant casing | Use Gradle-style casing (`Debug`, `Release`, `StagingDebug`) | +| `pod install --repo-update` is slow or flaky | CocoaPods spec repo updates | Retry, cache Pods, or drop `--repo-update` when lockfile + mirror are stable | + +## Download Artifacts with `gh` + +```bash +# 1) Find recent runs for this workflow +gh run list --workflow "RN Cloud Build" --limit 10 + +# 2) Download by run id + artifact name +gh run download -n -D ./artifacts + +# 3) Inspect artifacts for a run (IDs + names) +gh api repos///actions/runs//artifacts \ + --jq '.artifacts[] | {id, name, size_in_bytes, expired}' +``` + +## Download Artifacts with Direct REST API + +```bash +# List repo artifacts +curl -sS \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos///actions/artifacts" | jq '.artifacts[] | {id, name}' + +# Download one artifact zip by ID +curl -L \ + -H "Authorization: Bearer $GITHUB_TOKEN" \ + -H "Accept: application/vnd.github+json" \ + "https://api.github.com/repos///actions/artifacts//zip" \ + -o artifact.zip +``` + +## Common Pitfalls + +- Forgetting to set `permissions.actions: read` for API-driven artifact listing. +- Assuming artifact URLs are public; they require authenticated access. +- Not pinning artifact names, making `gh run download -n` brittle. + +## Related Skills + +- [gha-ios-composite-action.md](gha-ios-composite-action.md) +- [gha-android-composite-action.md](gha-android-composite-action.md) diff --git a/.agents/skills/react-native-best-practices/POWER.md b/.agents/skills/react-native-best-practices/POWER.md new file mode 100644 index 00000000..15b1abe8 --- /dev/null +++ b/.agents/skills/react-native-best-practices/POWER.md @@ -0,0 +1,172 @@ +--- +name: react-native-best-practices +description: Provides React Native performance optimization guidelines for FPS, TTI, bundle size, memory leaks, re-renders, and animations. Applies to tasks involving Hermes optimization, JS thread blocking, bridge overhead, FlashList, native modules, or debugging jank and frame drops. +license: MIT +author: Callstack +keywords: ["react-native", "expo", "performance", "optimization", "profiling"] +--- + +# Onboarding + +## Step 1: Validate React Native Setup + +Before applying performance optimizations, ensure: +- **Expo CLI** or **React Native CLI** is installed + - Verify with: `npx expo --version` and `npx react-native --version` +- Metro bundler is running (**apply only for** bundle analysis) +- React Native DevTools profiling is available through `agent-device react-devtools` (**apply only for** React render profiling/debugging) + - Run `agent-device react-devtools status`, then `agent-device react-devtools wait --connected` + +## Security Guardrails + +- Review shell commands before running them and prefer version-pinned tooling from trusted sources. +- Do not pipe remote install scripts directly into a shell. +- Treat third-party packages as normal supply-chain dependencies that require provenance and version review. +- If using remote chunk loading, prefer app-bundled chunks or signed CI release manifests; hosted chunks must be first-party artifacts tied to the current release. + +# When to Load Reference Files + +Load specific reference files from `references/` based on the task: + +## JavaScript/React Performance (`js-*`) + +- **Debugging slow/janky UI or animations** → `references/js-measure-fps.md` +- **Investigating re-render issues** → `references/js-profile-react.md` → `references/js-react-compiler.md` +- **Optimizing list scrolling** → `references/js-lists-flatlist-flashlist.md` +- **Reducing re-renders with state management** → `references/js-atomic-state.md` +- **Using Concurrent React features** → `references/js-concurrent-react.md` +- **Enabling automatic memoization** → `references/js-react-compiler.md` +- **Optimizing animations** → `references/js-animations-reanimated.md` +- **Fixing TextInput lag** → `references/js-uncontrolled-components.md` +- **Hunting JavaScript memory leaks** → `references/js-memory-leaks.md` + +## Native Performance (`native-*`) + +- **Measuring startup time (TTI)** → `references/native-measure-tti.md` +- **Building native modules** → `references/native-turbo-modules.md` +- **Understanding native threading** → `references/native-threading-model.md` +- **Profiling native code** → `references/native-profiling.md` +- **Setting up native tooling** → `references/native-platform-setup.md` +- **Debugging view hierarchy** → `references/native-view-flattening.md` +- **Native memory patterns** → `references/native-memory-patterns.md` +- **Hunting native memory leaks** → `references/native-memory-leaks.md` +- **Choosing native SDKs vs polyfills** → `references/native-sdks-over-polyfills.md` +- **Fixing Android 16KB alignment** → `references/native-android-16kb-alignment.md` + +## Bundle & App Size (`bundle-*`) + +- **Analyzing bundle size** → `references/bundle-analyze-js.md` +- **Analyzing app size** → `references/bundle-analyze-app.md` +- **Fixing barrel imports** → `references/bundle-barrel-exports.md` +- **Enabling tree shaking** → `references/bundle-tree-shaking.md` +- **Android code shrinking** → `references/bundle-r8-android.md` +- **Optimizing Hermes bundle loading** → `references/bundle-hermes-mmap.md` +- **Managing native assets** → `references/bundle-native-assets.md` +- **Evaluating library size** → `references/bundle-library-size.md` +- **Code splitting** → `references/bundle-code-splitting.md` + +## Problem → Reference Mapping + +Use this quick lookup when debugging specific issues: + +| Problem | Start With | +|---------|-----------| +| App feels slow/janky | `references/js-measure-fps.md` → `references/js-profile-react.md` | +| Too many re-renders | `references/js-profile-react.md` → `references/js-react-compiler.md` | +| Slow startup (TTI) | `references/native-measure-tti.md` → `references/bundle-analyze-js.md` | +| Large app size | `references/bundle-analyze-app.md` → `references/bundle-r8-android.md` | +| Memory growing | `references/js-memory-leaks.md` or `references/native-memory-leaks.md` | +| Animation drops frames | `references/js-animations-reanimated.md` | +| List scroll jank | `references/js-lists-flatlist-flashlist.md` | +| TextInput lag | `references/js-uncontrolled-components.md` | +| Native module slow | `references/native-turbo-modules.md` → `references/native-threading-model.md` | +| Native library alignment issue | `references/native-android-16kb-alignment.md` | + +## Quick Reference Commands + +### FPS & Re-renders +```bash +agent-device react-devtools status +agent-device react-devtools wait --connected +agent-device react-devtools profile start +agent-device react-devtools profile stop +agent-device react-devtools profile slow --limit 5 +agent-device react-devtools profile rerenders --limit 5 +agent-device react-devtools profile timeline --limit 20 +``` + +Drive the target interaction with normal `agent-device` commands between `profile start` and `profile stop`. + +Manual fallback when `agent-device` is unavailable: open React Native DevTools from Metro (`j`) or the Dev Menu, use the Profiler tab, and record the same interaction. + +For release-build React component profiling, connect [`@callstack/inspector`](https://github.com/callstackincubator/inspector#inspector) first so React DevTools can attach to the release app, then run the `agent-device react-devtools` flow above. + +Baseline runtime metrics should come from the target interaction itself: +- Capture commit timeline, re-render counts, slow components, and heaviest-commit breakdown. +- Treat component tree depth and count as supporting context only. + +**Common fixes:** +- Replace ScrollView with FlatList/FlashList for lists +- After profiling shows cascading re-renders, use React Compiler for automatic memoization +- After profiling shows broad store/context updates, use atomic state (Jotai/Zustand) to reduce re-renders +- Use `useDeferredValue` for expensive computations + +**Review guardrails:** +- Check library versions before suggesting API-specific fixes. FlashList v2 deprecates `estimatedItemSize`. +- Do not suggest `useMemo` or `useCallback` dependency changes without a reproducible correctness issue or profiling evidence. +- Do not report stale closures unless the stale read path or repro is clear. + +### Analyze Bundle Size +```bash +npx react-native bundle \ + --entry-file index.js \ + --bundle-output output.js \ + --platform ios \ + --sourcemap-output output.js.map \ + --dev false --minify true + +npx source-map-explorer output.js --no-border-checks +``` + +**Common fixes:** +- Avoid barrel imports (import directly from source) +- Remove unnecessary Intl polyfills only after checking Hermes API and method coverage +- Evaluate tree shaking (Expo SDK 52+ experimental unused import/export removal, or Re.Pack only if already configured) +- Enable R8 for Android native code shrinking + +### Measure TTI +- Use `react-native-performance` for markers +- Only measure cold starts (exclude warm/hot/prewarm) + +**Common fixes:** +- For React Native 0.78 and earlier, disable Android JS bundle compression to enable Hermes mmap +- Use native navigation (react-native-screens) +- Preload commonly-used expensive screens before navigating to them + +### Native Performance + +**Profile native:** +- iOS: Xcode Instruments → Time Profiler +- Android: Android Studio → CPU Profiler + +**Common fixes:** +- Use background threads for heavy native work +- Prefer async over sync Turbo Module methods +- Use C++ for cross-platform performance-critical code + +## Priority Guidelines + +Apply optimizations in this order: + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | FPS & Re-renders | CRITICAL | `js-*` | +| 2 | Bundle Size | CRITICAL | `bundle-*` | +| 3 | TTI Optimization | HIGH | `native-*`, `bundle-*` | +| 4 | Native Performance | HIGH | `native-*` | +| 5 | Memory Management | MEDIUM-HIGH | `js-*`, `native-*` | +| 6 | Animations | MEDIUM | `js-*` | + +## Attribution + +Based on "The Ultimate Guide to React Native Optimization" by Callstack. diff --git a/.agents/skills/react-native-best-practices/SKILL.md b/.agents/skills/react-native-best-practices/SKILL.md new file mode 100644 index 00000000..86ec5ab5 --- /dev/null +++ b/.agents/skills/react-native-best-practices/SKILL.md @@ -0,0 +1,238 @@ +--- +name: react-native-best-practices +description: Provides React Native performance optimization guidelines for FPS, TTI, bundle size, memory leaks, re-renders, and animations. Applies to tasks involving Hermes optimization, JS thread blocking, bridge overhead, FlashList, native modules, or debugging jank and frame drops. +license: MIT +--- + +# React Native Best Practices + +## Overview + +Performance optimization guide for React Native applications, covering JavaScript/React, Native (iOS/Android), and bundling optimizations. Based on Callstack's "Ultimate Guide to React Native Optimization". + +## When to Apply + +Reference these guidelines when: +- Debugging slow/janky UI or animations +- Investigating memory leaks (JS or native) +- Optimizing app startup time (TTI) +- Reducing bundle or app size +- Writing native modules (Turbo Modules) +- Profiling React Native performance +- Reviewing React Native code for performance + +## Security Notes + +- Treat shell commands in these references as local developer operations. Review them before running, prefer version-pinned tooling, and avoid piping remote scripts directly to a shell. +- Treat third-party libraries and plugins as dependencies that still require normal supply-chain controls: pin versions, verify provenance, and update through your standard review process. +- Treat remote chunk loading as first-party artifact delivery only. Prefer app-bundled chunks or signed CI release manifests; hosted chunks must come from trusted HTTPS origins you control and be pinned to the current app release. + +## Priority-Ordered Guidelines + +| Priority | Category | Impact | Prefix | +|----------|----------|--------|--------| +| 1 | FPS & Re-renders | CRITICAL | `js-*` | +| 2 | Bundle Size | CRITICAL | `bundle-*` | +| 3 | TTI Optimization | HIGH | `native-*`, `bundle-*` | +| 4 | Native Performance | HIGH | `native-*` | +| 5 | Memory Management | MEDIUM-HIGH | `js-*`, `native-*` | +| 6 | Animations | MEDIUM | `js-*` | + +Impact labels are triage hints: CRITICAL first, HIGH next, MEDIUM when evidence points there. + +## Quick Reference + +### Optimization Workflow + +Follow this cycle for any performance issue: **Measure → Optimize → Re-measure → Validate** + +1. **Measure**: Capture baseline metrics before changes. For runtime issues, prefer commit timeline, re-render counts, slow components, heaviest-commit breakdown, and startup/TTI when available. Component tree depth or count are optional context, not substitutes. Do not recommend memoization, atomic state, or compiler changes without a measured render or FPS problem. +2. **Optimize**: Apply the targeted fix from the relevant reference +3. **Re-measure**: Run the same measurement to get updated metrics +4. **Validate**: Confirm improvement (e.g., FPS 45→60, TTI 3.2s→1.8s, bundle 2.1MB→1.6MB) + +If metrics did not improve, revert and try the next suggested fix. + +### Review Guardrails + +- Check library versions before suggesting API-specific fixes. Example: FlashList v2 deprecates `estimatedItemSize`, so do not flag it as missing there. +- Do not suggest `useMemo` or `useCallback` dependency changes unless behavior is demonstrably incorrect or profiling shows wasted work tied to that value. +- Do not report stale closures speculatively. Show the stale read path, a repro, or profiler evidence before calling it out. +- When profiling a flow, measure the target interaction itself. Do not treat component tree depth or component count as the main performance evidence. + +### Critical: FPS & Re-renders + +**Profile first:** +```bash +agent-device react-devtools status +agent-device react-devtools wait --connected +agent-device react-devtools profile start +agent-device react-devtools profile stop +agent-device react-devtools profile slow --limit 5 +agent-device react-devtools profile rerenders --limit 5 +agent-device react-devtools profile timeline --limit 20 +``` + +Drive the target interaction with normal `agent-device` commands between `profile start` and `profile stop`. + +Manual fallback when `agent-device` is unavailable: open React Native DevTools from Metro (`j`) or the Dev Menu, use the Profiler tab, and record the same interaction. + +For release-build React component profiling, connect [`@callstack/inspector`](https://github.com/callstackincubator/inspector#inspector) first so React DevTools can attach to the release app, then run the `agent-device react-devtools` flow above. + +**Common fixes:** +- Replace ScrollView with FlatList/FlashList/Legend List for long lists +- After profiling shows cascading re-renders, use React Compiler for automatic memoization +- After profiling shows broad store/context updates, use atomic state (Jotai/Zustand) to reduce re-renders +- Use `useDeferredValue` for expensive computations + +### Critical: Bundle Size + +**Analyze bundle:** +```bash +npx react-native bundle \ + --entry-file index.js \ + --bundle-output output.js \ + --platform ios \ + --sourcemap-output output.js.map \ + --dev false --minify true + +npx source-map-explorer output.js --no-border-checks +``` + +**Verify improvement after optimization:** +```bash +# Record baseline size before changes +ls -lh output.js # e.g., Before: 2.1 MB + +# After applying fixes, re-bundle and compare +npx react-native bundle --entry-file index.js --bundle-output output.js \ + --platform ios --dev false --minify true +ls -lh output.js # e.g., After: 1.6 MB (24% reduction) +``` + +**Common fixes:** +- Avoid barrel imports (import directly from source) +- Remove unnecessary Intl polyfills only after checking Hermes API and method coverage +- Evaluate tree shaking (Expo SDK 52+ experimental unused import/export removal, or Re.Pack only if already configured) +- Enable R8 for Android native code shrinking + +### High: TTI Optimization + +**Measure TTI:** +- Use `react-native-performance` for markers +- Only measure cold starts (exclude warm/hot/prewarm) + +**Common fixes:** +- For React Native 0.78 and earlier, disable Android JS bundle compression to enable Hermes mmap +- Use native navigation (react-native-screens) +- Preload commonly-used expensive screens before navigating to them + +### High: Native Performance + +**Profile native:** +- iOS: Xcode Instruments → Time Profiler +- Android: Android Studio → CPU Profiler + +**Common fixes:** +- Use background threads for heavy native work +- Prefer async over sync Turbo Module methods +- Use C++ for cross-platform performance-critical code + +## References + +Full documentation with code examples in [references/][references]: + +### JavaScript/React (`js-*`) + +| File | Impact | Description | +|------|--------|-------------| +| [js-lists-flatlist-flashlist.md][js-lists-flatlist-flashlist] | CRITICAL | Replace ScrollView with virtualized lists | +| [js-profile-react.md][js-profile-react] | MEDIUM | `agent-device react-devtools` profiling | +| [js-measure-fps.md][js-measure-fps] | HIGH | FPS monitoring and measurement | +| [js-memory-leaks.md][js-memory-leaks] | MEDIUM | JS memory leak hunting | +| [js-atomic-state.md][js-atomic-state] | HIGH | Jotai/Zustand patterns | +| [js-concurrent-react.md][js-concurrent-react] | HIGH | useDeferredValue, useTransition | +| [js-react-compiler.md][js-react-compiler] | HIGH | Automatic memoization | +| [js-animations-reanimated.md][js-animations-reanimated] | MEDIUM | Reanimated worklets | +| [js-bottomsheet.md][js-bottomsheet] | HIGH | Bottom sheet optimization | +| [js-uncontrolled-components.md][js-uncontrolled-components] | HIGH | TextInput optimization | + +### Native (`native-*`) + +| File | Impact | Description | +|------|--------|-------------| +| [native-turbo-modules.md][native-turbo-modules] | HIGH | Building fast native modules | +| [native-sdks-over-polyfills.md][native-sdks-over-polyfills] | HIGH | Native vs JS libraries | +| [native-measure-tti.md][native-measure-tti] | HIGH | TTI measurement setup | +| [native-threading-model.md][native-threading-model] | HIGH | Turbo Module threads | +| [native-profiling.md][native-profiling] | MEDIUM | Xcode/Android Studio profiling | +| [native-platform-setup.md][native-platform-setup] | MEDIUM | iOS/Android tooling guide | +| [native-view-flattening.md][native-view-flattening] | MEDIUM | View hierarchy debugging | +| [native-memory-patterns.md][native-memory-patterns] | MEDIUM | C++/Swift/Kotlin memory | +| [native-memory-leaks.md][native-memory-leaks] | MEDIUM | Native memory leak hunting | +| [native-android-16kb-alignment.md][native-android-16kb-alignment] | CRITICAL | Third-party library alignment for Google Play | + +### Bundling (`bundle-*`) + +| File | Impact | Description | +|------|--------|-------------| +| [bundle-barrel-exports.md][bundle-barrel-exports] | CRITICAL | Avoid barrel imports | +| [bundle-analyze-js.md][bundle-analyze-js] | CRITICAL | JS bundle visualization | +| [bundle-tree-shaking.md][bundle-tree-shaking] | HIGH | Dead code elimination | +| [bundle-analyze-app.md][bundle-analyze-app] | HIGH | App size analysis | +| [bundle-r8-android.md][bundle-r8-android] | HIGH | Android code shrinking | +| [bundle-hermes-mmap.md][bundle-hermes-mmap] | HIGH | Disable bundle compression | +| [bundle-native-assets.md][bundle-native-assets] | HIGH | Asset catalog setup | +| [bundle-library-size.md][bundle-library-size] | MEDIUM | Evaluate dependencies | +| [bundle-code-splitting.md][bundle-code-splitting] | MEDIUM | Remote chunk loading safeguards | + +## Problem → Skill Mapping + +| Problem | Start With | +|---------|------------| +| App feels slow/janky | [js-measure-fps.md][js-measure-fps] → [js-profile-react.md][js-profile-react] | +| Too many re-renders | [js-profile-react.md][js-profile-react] → [js-react-compiler.md][js-react-compiler] | +| Slow startup (TTI) | [native-measure-tti.md][native-measure-tti] → [bundle-analyze-js.md][bundle-analyze-js] | +| Large app size | [bundle-analyze-app.md][bundle-analyze-app] → [bundle-r8-android.md][bundle-r8-android] | +| Memory growing | [js-memory-leaks.md][js-memory-leaks] or [native-memory-leaks.md][native-memory-leaks] | +| Animation drops frames | [js-animations-reanimated.md][js-animations-reanimated] | +| Bottom sheet jank/re-renders | [js-bottomsheet.md][js-bottomsheet] → [js-animations-reanimated.md][js-animations-reanimated] | +| List scroll jank | [js-lists-flatlist-flashlist.md][js-lists-flatlist-flashlist] | +| TextInput lag | [js-uncontrolled-components.md][js-uncontrolled-components] | +| Native module slow | [native-turbo-modules.md][native-turbo-modules] → [native-threading-model.md][native-threading-model] | +| Native library alignment issue | [native-android-16kb-alignment.md][native-android-16kb-alignment] | + +[references]: references/ +[js-lists-flatlist-flashlist]: references/js-lists-flatlist-flashlist.md +[js-profile-react]: references/js-profile-react.md +[js-measure-fps]: references/js-measure-fps.md +[js-memory-leaks]: references/js-memory-leaks.md +[js-atomic-state]: references/js-atomic-state.md +[js-concurrent-react]: references/js-concurrent-react.md +[js-react-compiler]: references/js-react-compiler.md +[js-animations-reanimated]: references/js-animations-reanimated.md +[js-bottomsheet]: references/js-bottomsheet.md +[js-uncontrolled-components]: references/js-uncontrolled-components.md +[native-turbo-modules]: references/native-turbo-modules.md +[native-sdks-over-polyfills]: references/native-sdks-over-polyfills.md +[native-measure-tti]: references/native-measure-tti.md +[native-threading-model]: references/native-threading-model.md +[native-profiling]: references/native-profiling.md +[native-platform-setup]: references/native-platform-setup.md +[native-view-flattening]: references/native-view-flattening.md +[native-memory-patterns]: references/native-memory-patterns.md +[native-memory-leaks]: references/native-memory-leaks.md +[native-android-16kb-alignment]: references/native-android-16kb-alignment.md +[bundle-barrel-exports]: references/bundle-barrel-exports.md +[bundle-analyze-js]: references/bundle-analyze-js.md +[bundle-tree-shaking]: references/bundle-tree-shaking.md +[bundle-analyze-app]: references/bundle-analyze-app.md +[bundle-r8-android]: references/bundle-r8-android.md +[bundle-hermes-mmap]: references/bundle-hermes-mmap.md +[bundle-native-assets]: references/bundle-native-assets.md +[bundle-library-size]: references/bundle-library-size.md +[bundle-code-splitting]: references/bundle-code-splitting.md + +## Attribution + +Based on "The Ultimate Guide to React Native Optimization" by Callstack. diff --git a/.agents/skills/react-native-best-practices/agents/openai.yaml b/.agents/skills/react-native-best-practices/agents/openai.yaml new file mode 100644 index 00000000..48c283d0 --- /dev/null +++ b/.agents/skills/react-native-best-practices/agents/openai.yaml @@ -0,0 +1,4 @@ +interface: + display_name: "React Native Best Practices" + short_description: "React Native performance optimization guide" + default_prompt: "Use $react-native-best-practices to diagnose and improve React Native performance." diff --git a/.agents/skills/react-native-best-practices/references/bundle-analyze-app.md b/.agents/skills/react-native-best-practices/references/bundle-analyze-app.md new file mode 100644 index 00000000..3d7b4d1b --- /dev/null +++ b/.agents/skills/react-native-best-practices/references/bundle-analyze-app.md @@ -0,0 +1,208 @@ +--- +title: Analyze App Bundle Size +impact: HIGH +tags: app-size, ruler, emerge-tools, thinning +--- + +# Skill: Analyze App Bundle Size + +Measure iOS and Android app download/install sizes using Ruler, App Store Connect, and Emerge Tools. + +## Quick Command + +```bash +# Android (Ruler) +cd android && ./gradlew analyzeReleaseBundle + +# iOS (Xcode export with thinning) +cd ios && xcodebuild -exportArchive \ + -archivePath MyApp.xcarchive \ + -exportPath ./export \ + -exportOptionsPlist ExportOptions.plist +# Check: App Thinning Size Report.txt +``` + +## When to Use + +- App download size is too large +- Users complain about storage usage +- App approaching store limits +- Comparing releases for size regression + +> **Note**: This skill involves visual size reports (Ruler, Emerge Tools X-Ray). When regression checks include device flows, use `agent-device` for app evidence; install it through the environment's approved/trusted path or ask the user if verification needs it and it is missing. Size report analysis itself may still require exported reports, browser screenshots, or human review. Record concrete module/file names and before/after artifact sizes in text when asking an agent to reason about them. + +## Key Metrics + +| Metric | Description | User Impact | +|--------|-------------|-------------| +| Download Size | Compressed, transferred over network | Download time, data usage | +| Install Size | Uncompressed, on device storage | Storage space | + +**Google finding**: Every 6 MB increase reduces installs by 1%. + +## Android: Ruler (Spotify) + +### Setup + +Add to `android/build.gradle`: + +```groovy +buildscript { + dependencies { + classpath("com.spotify.ruler:ruler-gradle-plugin:2.0.0-beta-3") + } +} +``` + +Add to `android/app/build.gradle`: + +```groovy +apply plugin: "com.spotify.ruler" + +ruler { + abi.set("arm64-v8a") // Target architecture + locale.set("en") + screenDensity.set(480) + sdkVersion.set(34) +} +``` + +### Analyze + +```bash +cd android +./gradlew analyzeReleaseBundle +``` + +Opens HTML report with: +- Download size +- Install size +- Component breakdown (biggest → smallest) + +### CI Size Validation + +```groovy +ruler { + verification { + downloadSizeThreshold = 20 * 1024 * 1024 // 20 MB + installSizeThreshold = 50 * 1024 * 1024 // 50 MB + } +} +``` + +Build fails if thresholds exceeded. + +## iOS: Xcode App Thinning + +### Via App Store Connect (Most Accurate) + +After uploading to TestFlight: +1. Open App Store Connect +2. Go to your build +3. View size table by device variant + +**Note**: TestFlight builds include debug data, App Store builds slightly larger due to DRM. + +### Via Xcode Export + +Export an archive with app thinning enabled for all compatible device variants. + +Or in `ExportOptions.plist`: + +```xml +thinning +<thin-for-all-variants> +``` + +### Output + +Creates folder with: +- **Universal IPA**: All variants combined +- **Thinned IPAs**: One per device variant +- **App Thinning Size Report.txt**: + +``` +Variant: SampleApp-.ipa +App + On Demand Resources size: 3.5 MB compressed, 10.6 MB uncompressed +App size: 3.5 MB compressed, 10.6 MB uncompressed +``` + +- Compressed = Download size +- Uncompressed = Install size + +## Emerge Tools (Cross-Platform) + +Third-party service with visual analysis. + +### Upload + +Upload IPA, APK, or AAB through their web interface or CI integration. + +### Features + +![Emerge Tools X-Ray for iOS](images/emerge-xray-ios.png) + +- **X-Ray**: Treemap visualization (like source-map-explorer for binaries) + - Shows Frameworks (hermes.framework), Mach-O sections (TEXT, DATA), etc. + - Color-coded: Binaries, Localizations, Fonts, Asset Catalogs, Videos, CoreML Models + - Visible components: `main.jsbundle` (JS code), RCT modules, DYLD sections +- **Breakdown**: Component-by-component size +- **Insights**: Automated suggestions (use with caution) + +**Caution**: Some suggestions may not apply to React Native (e.g., "remove Hermes"). + +## Size Comparison + +| Tool | Platform | Accuracy | CI Integration | +|------|----------|----------|----------------| +| Ruler | Android | High | Yes (Gradle) | +| App Store Connect | iOS | Highest | No | +| Xcode Export | iOS | High | Yes (xcodebuild) | +| Emerge Tools | Both | High | Yes (API) | + +## Typical React Native App Sizes + +| Component | Approximate Size | +|-----------|------------------| +| Hermes engine | ~2-3 MB | +| React Native core | ~3-5 MB | +| JavaScript bundle | 1-10 MB | +| Assets (images, etc.) | Varies | + +**Baseline empty app**: ~6-10 MB download + +## Optimization Impact Example + +| Optimization | Size Reduction | +|--------------|----------------| +| Enable R8 (Android) | ~30% | +| Remove unused polyfills | 400+ KB | +| Asset catalog (iOS) | 10-50% of assets | +| Tree shaking | 10-15% | + +## Quick Commands + +```bash +# Android release bundle size +cd android && ./gradlew bundleRelease +# Check: android/app/build/outputs/bundle/release/ + +# iOS archive +cd ios && xcodebuild -workspace ios/MyApp.xcworkspace \ + -scheme MyApp \ + -configuration Release \ + -archivePath MyApp.xcarchive \ + archive + +# Export with thinning report +cd ios && xcodebuild -exportArchive \ + -archivePath MyApp.xcarchive \ + -exportPath ./export \ + -exportOptionsPlist ExportOptions.plist +``` + +## Related Skills + +- [bundle-r8-android.md](./bundle-r8-android.md) - Reduce Android size +- [bundle-native-assets.md](./bundle-native-assets.md) - Optimize asset delivery +- [bundle-analyze-js.md](./bundle-analyze-js.md) - JS bundle analysis diff --git a/.agents/skills/react-native-best-practices/references/bundle-analyze-js.md b/.agents/skills/react-native-best-practices/references/bundle-analyze-js.md new file mode 100644 index 00000000..187e2c81 --- /dev/null +++ b/.agents/skills/react-native-best-practices/references/bundle-analyze-js.md @@ -0,0 +1,264 @@ +--- +title: Analyze JS Bundle Size +impact: CRITICAL +tags: bundle, analysis, source-map-explorer, expo-atlas +--- + +# Skill: Analyze JS Bundle Size + +Use source-map-explorer and Expo Atlas to visualize what's in your JavaScript bundle. + +## Quick Command + +```bash +# React Native CLI +npx react-native bundle \ + --entry-file index.js \ + --bundle-output output.js \ + --platform ios \ + --sourcemap-output output.js.map \ + --dev false --minify true && \ +npx source-map-explorer output.js --no-border-checks + +# Expo +EXPO_UNSTABLE_ATLAS=true npx expo export --platform ios && npx expo-atlas +``` + +## When to Use + +- JS bundle seems too large +- Want to identify heavy dependencies +- Investigating startup time issues +- Before/after optimization comparison + +> **Note**: This skill involves visual treemap output (source-map-explorer, Expo Atlas). When regression checks include device flows, use `agent-device` for app evidence; install it through the environment's approved/trusted path or ask the user if verification needs it and it is missing. Treemap analysis itself may still require exported reports, browser screenshots, or human review. Record the largest modules and before/after bundle sizes in text when asking an agent to reason about them. + +## Understanding Hermes Bytecode + +Release builds using Hermes, the default engine in modern React Native, ship Hermes bytecode rather than raw JavaScript: +- Skips parsing at runtime +- Still benefits from smaller bundles +- Heavy imports still execute on startup + +**Impact of bundle size:** +- Larger bytecode = longer download from store +- More imports on init path = slower TTI + +Development builds fetch JS from the dev server, and non-Hermes engines have different startup tradeoffs. Smaller bytecode helps app size and startup, but startup also depends on what executes on the initialization path. Imports that eagerly touch native modules can defeat Turbo Module lazy loading and hurt TTI. + +## Method 1: source-map-explorer + +### Generate Bundle with Source Map + +**React Native CLI:** + +```bash +npx react-native bundle \ + --entry-file index.js \ + --bundle-output output.js \ + --platform ios \ + --sourcemap-output output.js.map \ + --dev false \ + --minify true +``` + +**Expo (SDK 51+):** + +```bash +npx expo export --platform ios --source-maps --output-dir dist +# Bundle at: dist/ios/_expo/static/js/ios/*.js +# Source map at: dist/ios/_expo/static/js/ios/*.map +``` + +### Analyze + +```bash +npx source-map-explorer output.js --no-border-checks +``` + +**Note**: `--no-border-checks` needed due to Metro's non-standard source maps. + +Opens browser with treemap visualization: + +![Bundle Treemap from source-map-explorer](images/bundle-treemap-source-map-explorer.png) + +The treemap shows: +- **Hierarchy**: `node_modules/` → `react-native/` → `Libraries/` → individual files +- **Size**: Box area proportional to file size (KB shown in labels) +- **Major components visible**: + - `react-native` (724.18 KB, 80.5%) + - `Renderer` (208.44 KB) - ReactNativeRenderer-prod.js, ReactFabric-prod.js + - `Components` (125.29 KB) - Touchable, ScrollView, etc. + - `Animated` (79.48 KB) - Animation system + - `virtualized-lists` (57.57 KB) - FlatList internals + +Click on any section to drill down into that directory. + +**Limitation**: May lose ~30% info due to mapping issues. + +## Method 2: Expo Atlas + +More accurate for Expo projects (or with workaround for bare RN). + +### For Expo Projects + +```bash +# Start with Atlas enabled +EXPO_UNSTABLE_ATLAS=true npx expo start --no-dev + +# Or export +EXPO_UNSTABLE_ATLAS=true npx expo export +``` + +Then launch UI: + +```bash +npx expo-atlas +``` + +![Expo Atlas Treemap](images/expo-atlas-treemap.png) + +Expo Atlas provides more accurate visualization for Expo projects, with similar treemap interface showing module sizes and dependencies. + +### For Non-Expo Projects + +Use `expo-atlas-without-expo` package. + +## Method 3: Re.Pack Bundle Analysis (Webpack/Rspack) + +If using Re.Pack: + +### webpack-bundle-analyzer + +```bash +rspack build --analyze +``` + +### bundle-stats / statoscope + +```bash +# Generate stats +npx react-native bundle \ + --platform android \ + --entry-file index.js \ + --dev false \ + --minify true \ + --json stats.json + +# Analyze +npx bundle-stats --html --json stats.json +``` + +### Rsdoctor + +```javascript +// rspack.config.js +const { RsdoctorRspackPlugin } = require('@rsdoctor/rspack-plugin'); + +module.exports = { + plugins: [ + process.env.RSDOCTOR && new RsdoctorRspackPlugin(), + ].filter(Boolean), +}; +``` + +Run with: + +```bash +RSDOCTOR=true npx react-native start +``` + +## What to Look For + +### Red Flags + +| Finding | Problem | Solution | +|---------|---------|----------| +| Entire library imported | Barrel exports | Use direct imports | +| Duplicate packages | Multiple versions | Dedupe in package.json | +| Dev dependencies in bundle | Incorrect imports | Check conditional imports | +| Large polyfills | Unnecessary for Hermes | Remove (see native-sdks-over-polyfills.md) | +| Moment.js with locales | Bloated date library | Switch to date-fns or dayjs | + +### Common Offenders + +- **Lodash full import**: Prefer built-ins or specific imports +- **Moment.js**: Replace with `date-fns` or `dayjs` +- **Intl polyfills**: Check Hermes API and method coverage before removing them +- **AWS SDK**: Import specific services only + +## Code Examples + +### Identify Barrel Import Impact + +```tsx +// BAD: Imports entire library through barrel +import { format } from 'date-fns'; + +// In bundle: All of date-fns loaded + +// GOOD: Direct import +import format from 'date-fns/format'; + +// In bundle: Only format function +``` + +## Comparing Bundles + +### source-map-explorer + +```bash +# Generate baseline +npx react-native bundle ... --bundle-output baseline.js --sourcemap-output baseline.js.map + +# Make changes, generate new bundle +npx react-native bundle ... --bundle-output current.js --sourcemap-output current.js.map + +# Compare manually in browser +``` + +### Re.Pack (automated) + +```bash +npx bundle-stats compare baseline-stats.json current-stats.json +``` + +## Quick Commands + +**React Native CLI:** + +```bash +# iOS bundle analysis +npx react-native bundle \ + --entry-file index.js \ + --bundle-output ios-bundle.js \ + --platform ios \ + --sourcemap-output ios-bundle.js.map \ + --dev false \ + --minify true && \ +npx source-map-explorer ios-bundle.js --no-border-checks + +# Android bundle analysis +npx react-native bundle \ + --entry-file index.js \ + --bundle-output android-bundle.js \ + --platform android \ + --sourcemap-output android-bundle.js.map \ + --dev false \ + --minify true && \ +npx source-map-explorer android-bundle.js --no-border-checks +``` + +**Expo:** + +```bash +# Use Expo Atlas (recommended for Expo projects) +EXPO_UNSTABLE_ATLAS=true npx expo export --platform ios +npx expo-atlas +``` + +## Related Skills + +- [bundle-barrel-exports.md](./bundle-barrel-exports.md) - Fix barrel import issues +- [bundle-tree-shaking.md](./bundle-tree-shaking.md) - Enable dead code elimination +- [bundle-library-size.md](./bundle-library-size.md) - Check library sizes before adding diff --git a/.agents/skills/react-native-best-practices/references/bundle-barrel-exports.md b/.agents/skills/react-native-best-practices/references/bundle-barrel-exports.md new file mode 100644 index 00000000..e492d907 --- /dev/null +++ b/.agents/skills/react-native-best-practices/references/bundle-barrel-exports.md @@ -0,0 +1,250 @@ +--- +title: Avoid Barrel Exports +impact: CRITICAL +tags: bundle, imports, barrel, tree-shaking +--- + +# Skill: Avoid Barrel Exports + +Refactor barrel imports (index files) to reduce bundle size and improve startup time. + +## Quick Pattern + +**Incorrect:** + +```tsx +import { Button } from './components'; +// Loads ALL exports from components/index.ts +``` + +**Correct:** + +```tsx +import Button from './components/Button'; +// Loads only Button +``` + +## When to Use + +- Bundle contains unused code from libraries +- Circular dependency warnings in Metro +- Hot Module Replacement (HMR) breaks frequently +- TTI is slow due to module evaluation + +## What Are Barrel Exports? + +```tsx +// components/index.ts (barrel file) +export { Button } from './Button'; +export { Card } from './Card'; +export { Modal } from './Modal'; +export { Sidebar } from './Sidebar'; + +// Usage (barrel import) +import { Button } from './components'; +``` + +## Problems with Barrel Imports + +### 1. Bundle Size Overhead + +Without effective tree shaking or a library-specific Babel plugin, barrel imports can make all re-exported modules reachable and evaluated even when only one export is used: + +```tsx +// Only need Button, but entire barrel is bundled +import { Button } from './components'; +// Card, Modal, Sidebar also included! +``` + +### 2. Runtime Overhead + +All modules evaluate before returning your import: + +```tsx +import { Button } from './components'; +// JavaScript must evaluate: +// - Button.tsx +// - Card.tsx +// - Modal.tsx +// - Sidebar.tsx +// Even though you only use Button +``` + +### 3. Circular Dependencies + +Barrel files make cycles easier to create accidentally: + +``` +Warning: Require cycle: + components/index.ts -> Button.tsx -> utils/index.ts -> components/index.ts +``` + +Breaks HMR, causes unpredictable behavior. + +## Solution 1: Direct Imports + +Replace barrel imports with direct paths: + +```tsx +// BEFORE: Barrel import +import { Button, Card } from './components'; + +// AFTER: Direct imports +import Button from './components/Button'; +import Card from './components/Card'; +``` + +### Enforce with ESLint + +```bash +npm install -D eslint-plugin-no-barrel-files +``` + +```javascript +// eslint.config.js +import noBarrelFiles from 'eslint-plugin-no-barrel-files'; + +export default [ + { + plugins: { 'no-barrel-files': noBarrelFiles }, + rules: { + 'no-barrel-files/no-barrel-files': 'error', + }, + }, +]; +``` + +## Solution 2: Tree Shaking (Automatic) + +Enable tree shaking to automatically remove unused barrel exports. + +### Expo SDK 52+ + +```tsx +// metro.config.js +const { getDefaultConfig } = require('expo/metro-config'); +const config = getDefaultConfig(__dirname); + +config.transformer.getTransformOptions = async () => ({ + transform: { + experimentalImportSupport: true, + }, +}); + +module.exports = config; +``` + +```bash +# .env +EXPO_UNSTABLE_METRO_OPTIMIZE_GRAPH=1 +EXPO_UNSTABLE_TREE_SHAKING=1 +``` + +### metro-serializer-esbuild + +```bash +npm install @rnx-kit/metro-serializer-esbuild +``` + +### Re.Pack (Webpack/Rspack) + +Tree shaking built-in. + +## Real-World Example: date-fns + +```tsx +// BAD: Imports entire library +import { format, addDays, isToday } from 'date-fns'; + +// GOOD: Direct imports +import format from 'date-fns/format'; +import addDays from 'date-fns/addDays'; +import isToday from 'date-fns/isToday'; +``` + +If the project uses a bundler/configuration with working tree shaking, top-level ESM imports from libraries such as `date-fns` may be optimized automatically. Without that, submodule imports are still the safer pattern. Measure with bundle analysis. + +## Library-Specific Solutions + +Some libraries provide Babel plugins: + +### React Native Paper + +```javascript +// babel.config.js +module.exports = { + plugins: [ + 'react-native-paper/babel', // Auto-transforms imports + ], +}; +``` + +Transforms: +```tsx +import { Button } from 'react-native-paper'; +// Into: +import Button from 'react-native-paper/lib/module/components/Button'; +``` + +## Refactoring Strategy + +### Step 1: Identify Barrel Files + +Look for `index.ts` files with multiple exports: + +```bash +grep -r "export \* from" src/ +grep -r "export { .* } from" src/ +``` + +### Step 2: Update Imports + +```tsx +// Find all usages +// VS Code: Cmd+Shift+F for "from './components'" + +// Replace each with direct import +import Button from './components/Button'; +``` + +### Step 3: (Optional) Keep Barrel for External API + +If your package is consumed by others: + +```tsx +// Keep index.ts for package API +// components/index.ts +export { Button } from './Button'; + +// Internal code uses direct imports +// src/screens/Home.tsx +import Button from '../components/Button'; +``` + +## Migration Script Example + +```bash +# Use codemod or search-replace +# Find: import { (\w+) } from '\.\/components'; +# Replace: import $1 from './components/$1'; +``` + +## Verification + +After refactoring: + +1. Run bundle analysis (see [bundle-analyze-js.md](./bundle-analyze-js.md)) +2. Compare sizes before/after +3. Check for circular dependency warnings + +## Common Pitfalls + +- **Breaking external consumers**: If publishing a library, keep barrel for public API +- **IDE auto-imports**: Configure IDE to prefer direct imports +- **Inconsistent patterns**: Enforce with ESLint across team + +## Related Skills + +- [bundle-analyze-js.md](./bundle-analyze-js.md) - Verify impact +- [bundle-tree-shaking.md](./bundle-tree-shaking.md) - Automatic solution +- [bundle-library-size.md](./bundle-library-size.md) - Check library patterns diff --git a/.agents/skills/react-native-best-practices/references/bundle-code-splitting.md b/.agents/skills/react-native-best-practices/references/bundle-code-splitting.md new file mode 100644 index 00000000..590c5a3c --- /dev/null +++ b/.agents/skills/react-native-best-practices/references/bundle-code-splitting.md @@ -0,0 +1,255 @@ +--- +title: Remote Chunk Loading +impact: MEDIUM +tags: code-splitting, lazy-loading, chunks, release-artifacts, remote-code +--- + +# Skill: Remote Chunk Loading + +Harden remote JavaScript chunk loading when a React Native app already uses Re.Pack or has an explicit remote-code-loading requirement. + +## Quick Pattern + +**Before (static import):** + +```jsx +import SettingsScreen from './screens/SettingsScreen'; +``` + +**After (lazy loaded chunk):** + +```jsx +const SettingsScreen = React.lazy(() => + import(/* webpackChunkName: "settings" */ './screens/SettingsScreen') +); + +}> + + +``` + +## When to Use + +Consider code splitting when: +- **Not using Hermes** (JSC/V8 benefits more) +- App size approaches app-store or base-module limits +- The app already has a micro-frontend architecture +- Loading features based on user permissions +- Other bundle-size optimizations are exhausted +- Remote delivery is an explicit product or release requirement + +Do not recommend adopting Re.Pack for ordinary bundle-size work. Keep the default path on Metro/Expo unless remote chunk loading is already present or specifically required. + +**Note**: Hermes already uses memory mapping for efficient bundle reading. Benefits of code splitting are minimal with Hermes or even counterproductive in some cases. + +## Security Model + +Chunks are executable application code. Prefer chunks packaged with the app or resolved from a release manifest produced by your CI. Hosted chunks are acceptable only when they are first-party release artifacts, not arbitrary runtime URLs. + +Keep these guardrails in place: +- Serve chunks only from a first-party, HTTPS-only origin you control +- Resolve `scriptId` through a fixed allowlist or signed release manifest +- If using Re.Pack, enable code signing for remotely hosted chunks and use strict signature verification in production +- Fail closed if a chunk is missing or unexpected +- Do not load chunks from user-controlled input, query params, or third-party domains + +## Prerequisites + +- Project already uses Re.Pack, or remote chunk loading is an explicit requirement approved after measuring simpler alternatives +- Remote chunks are produced by the same release pipeline as the app +- Chunk locations come from a fixed allowlist or signed release manifest + +If the project does not already use Re.Pack, do not start here. First confirm Metro/Expo bundle analysis, import cleanup, asset cleanup, native app-size work, and store delivery constraints. + +## Step-by-Step Instructions + +### 1. Create Split Point with React.lazy + +```tsx +// BEFORE: Static import +import SettingsScreen from './screens/SettingsScreen'; + +// AFTER: Dynamic import (creates split point) +const SettingsScreen = React.lazy(() => + import(/* webpackChunkName: "settings" */ './screens/SettingsScreen') +); +``` + +### 2. Wrap with Suspense + +```tsx +import React, { Suspense } from 'react'; + +const App = () => { + return ( + }> + + + ); +}; +``` + +### 3. Configure Chunk Loading + +```jsx +// index.js (before AppRegistry) +import { ScriptManager, Script } from '@callstack/repack/client'; + +const RELEASE_CHUNKS = Object.freeze({ + settings: { + release: '42', + }, +}); + +ScriptManager.shared.addResolver(async (scriptId) => { + if (__DEV__) { + return { + url: Script.getDevServerURL(scriptId), + cache: false, + }; + } + + const chunk = RELEASE_CHUNKS[scriptId]; + + if (!chunk) { + throw new Error(`Unknown chunk: ${scriptId}`); + } + + return { + url: Script.getRemoteURL( + getFirstPartyChunkBaseURL(scriptId, chunk.release) + ), + verifyScriptSignature: 'strict', + }; +}); + +function getFirstPartyChunkBaseURL(scriptId, release) { + // App-owned helper: read a signed CI manifest and return the first-party + // base URL without ".chunk.bundle"; Script.getRemoteURL appends it. + // Do not accept hostnames, paths, or script IDs from runtime input. + return ReleaseManifest.getChunkBaseURL({ scriptId, release }); +} + +AppRegistry.registerComponent(appName, () => App); +``` + +For app-bundled chunks in a Re.Pack project, configure `extraChunks` with `type: 'local'` and resolve those script IDs from the filesystem: + +```jsx +if (LOCAL_CHUNKS.has(scriptId)) { + return { + url: Script.getFileSystemURL(scriptId), + absolute: true, + }; +} +``` + +### 4. Build and Deploy Chunks + +Build generates: +- `index.bundle` - Main bundle +- `settings.chunk.bundle` - Lazy-loaded chunk + +Remote chunks are written to `build/output//remotes` by default. Deploy chunks as first-party release artifacts. Prefer app-bundled assets; if hosted, publish them through CI to an app-owned HTTPS origin and keep the allowlist or signed manifest in sync with the app release. + +## Complete Example + +```tsx +// App.tsx +import React, { Suspense, useState } from 'react'; +import { Button, View, ActivityIndicator } from 'react-native'; + +// Lazy load heavy feature +const HeavyFeature = React.lazy(() => + import(/* webpackChunkName: "heavy-feature" */ './HeavyFeature') +); + +const App = () => { + const [showFeature, setShowFeature] = useState(false); + + return ( + +