From 8df4577185d8dc21c8f561ee8bc552c877db9e5c Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Sun, 19 Jul 2026 23:41:17 -0700 Subject: [PATCH 1/3] docs: add minor merge and release skill Document fork, stable, stack recreation, and focused extraction workflows with strict topology, validation, evidence, and publication guards. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .ai/commands/merge-minor.md | 704 ++++++++++++++++++++++++++++++++ .ai/index.md | 5 + .claude/commands/merge-minor.md | 3 + 3 files changed, 712 insertions(+) create mode 100644 .ai/commands/merge-minor.md create mode 100644 .claude/commands/merge-minor.md diff --git a/.ai/commands/merge-minor.md b/.ai/commands/merge-minor.md new file mode 100644 index 000000000000..d8fafe2795ae --- /dev/null +++ b/.ai/commands/merge-minor.md @@ -0,0 +1,704 @@ +# Merge React Native Minor Release(s) + +Prepare, recreate, validate, and document React Native minor-version merge and +stable-release branches without flattening history or mixing mainline and stable +DAGs. + +## Arguments + +Accept: + +```text + [--mode fork-merge|stable-sync|stack|stack-recreate|focused-extraction] [--base ] [--onto ] [--dry-run] +.. [--mode stack|stack-recreate] [--base ] [--onto ] [--dry-run] +``` + +Examples: + +```text +0.84 +0.84 --mode fork-merge +0.84 --mode stable-sync --base 0.84-merge --onto 0.84-stable +0.84..0.87 --mode stack +0.86 --mode stack-recreate --base --onto +0.87 --mode focused-extraction +``` + +- Normalize each minor to `0.`. +- Default one minor to `fork-merge`; a range defaults to `stack`. +- Process ranges in ascending order. +- `--base` overrides the Microsoft predecessor ref. +- `--onto` overrides the Microsoft destination/base ref. +- `--dry-run` performs discovery, graph simulation, assertions, conflict + counting, and reporting without creating refs, merging, rebasing, committing, + pushing, or changing publication state. + +## Conventions + +### Non-negotiable DAG model + +Maintain two separate histories: + +```text +Microsoft mainline: +main -> 0.84-merge -> 0.85-merge -> 0.86-merge -> 0.87-merge + \ exact upstream fork points only + +Stable release lines: +0.84-merge (landed result) -> 0.84-stable -> pinned facebook/0.84-stable tip +0.85-merge (landed result) -> 0.85-stable -> pinned facebook/0.85-stable tip +``` + +- A mainline minor chains from the prior landed `*-merge` result, or `main` for + the first requested minor. +- A stable line forks from the matching landed minor result. +- Never merge a `facebook/-stable` tip into `main`, another mainline + merge branch, or a different minor. +- The `fork-merge` mechanical commit is an ordered two-parent Git merge: + parent 1 is the exact Microsoft predecessor and parent 2 is the exact + Facebook main/stable fork point. +- The `stable-sync` mechanical commit is also ordered: parent 1 is the exact + matching Microsoft stable base and parent 2 is the pinned Facebook stable + tip. +- Refuse to mutate `main`, any `*-stable` branch, or the main checkout. Every + writer uses a dedicated feature worktree/session. + +`*-merge`, release, and stacked branches are recreatable preparation baselines, +not immutable release truth. Human review edits can require rebasing or +recreation. Preserve backup refs, evidence, and old-to-new commit maps, then +rerun every semantic gate and obtain a fresh independent judgment. The upstream +fork-point or stable-tip SHA selected for one run remains immutable; a moved +upstream ref starts a new run. + +### Git is authoritative + +Resolve all refs to full object IDs before canonical commands: + +```bash +BASE_SHA=$(git rev-parse --verify "^{commit}") +TARGET_SHA=$(git rev-parse --verify "^{commit}") +HEAD_SHA=$(git rev-parse --verify "HEAD^{commit}") +printf '%s\n' "$BASE_SHA" "$TARGET_SHA" "$HEAD_SHA" +``` + +Use full SHAs in state, assertions, merge/rebase commands, evidence, and PR +notes. Abbreviations are display-only. Record relevant repository-local config, +Git version, argument order and label text, and `LC_ALL`; conflict renderings and +derived hashes can change with `merge.conflictStyle`, `core.abbrev`, argument +labels/order, Git version, and locale. Compare canonical objects and trees, not +unstable display text. + +### Roles and publication boundary + +Use distinct real app sessions, all in autopilot, for: + +1. **Planner:** discovers scope and writes the approved plan; never writes or + publishes repository changes. +2. **Executor:** implements only the approved plan and returns commit/evidence; + never pushes, creates/updates a PR or preview anchor, marks a PR ready, or + merges. +3. **Judge:** independently judges the executor's actual execution and evidence, + including a fresh diff-tag audit; never lands. +4. **Coordinator:** routes work, preserves durable evidence, requests explicit + user approval, and is the sole role allowed to publish or land. + +Delegated lanes do not prompt the user. They stop and return ambiguity or +failure to the coordinator. A PASS is not publication approval. + +### State and resume contract + +Persist state outside the repository: + +```text +discovered +backed-up +merge-paused +adjudicating +merge-committed +ports-committed +validated +judged +publication-approved +fork-pushed +preview-anchored +pr-created-or-updated +ready-approved +complete +``` + +For `stable-sync`, use `stable-merge-paused` and record the pinned stable-tip +SHA. Before every action: + +1. Read state and the current Git graph. +2. Reassert every completed state. +3. Resume at the first incomplete state. +4. Stop if persisted SHAs disagree with refs or unexplained files are present. +5. Never repeat a push, PR mutation, anchor mutation, or merge. + +Only one writer may mutate a branch/worktree or run a heavy build at a time. +Each live cmux/tmux surface has a unique lane/session name, a transcript, and an +ownership marker. Never send keys to an inherited pane. Quarantine ambiguous +panes and confirm old processes have exited before handing off ownership. + +Detect stale executors and races before accepting a handoff: compare the +executor's recorded branch, HEAD, base, target, worktree, and session identity to +live state. Reject results produced from a superseded baseline. Copy evidence to +coordinator-owned storage before archiving a dead or completed session; archive +only after no lane depends on its worktree, logs, or refs. + +## Steps + +### 1. Common preflight + +1. Assert a clean worktree, expected feature branch, exact HEAD, and no other + writer. +2. Fetch only needed refs: + + ```bash + git fetch origin + git fetch upstream main + git fetch facebook main -stable + ``` + +3. Resolve and record full SHAs for local HEAD, origin branch, `upstream/main`, + `facebook/main`, `facebook/-stable`, merge bases, and any PR + base/head. Record tree SHAs, ahead/behind counts, first-parent history, and a + decorated graph. +4. Store snapshots outside the repository. +5. Enable rerere repository-locally only: + + ```bash + git config --local rerere.enabled true + git config --local rerere.autoupdate false + ``` + + Rerere output is a candidate, never authorization to stage. +6. Create a local backup ref. A remote backup is a publication action and needs + explicit user approval through the coordinator. If a backup already exists, + require the exact recorded SHA. +7. Refuse a rewrite unless the base actually moved, the old base is an ancestor + of the new base, backup/evidence are durable, and the coordinator has the + required approval. Prefer normal/additive pushes; never use plain `--force`. + +### 2. Optional resolution engine: isolated Jujutsu v0.43 + +Jujutsu is optional. Use exactly an isolated, non-colocated jj v0.43 workspace; +never initialize jj in the canonical Git worktree or allow auto-snapshot to +mutate it. Git refs, commits, ordered merge parents, trees, and validation remain +authoritative. + +1. Import the exact Git objects into a disposable non-colocated workspace. +2. Record jj Change IDs and their imported Git commit/tree IDs. +3. Resolve by Change ID in focused changes. Avoid whole-file restores and + working-copy-wide resolution at semantic layer boundaries: either can absorb + unrelated changes or collapse reversible layers. +4. Compare jj's conflict set with Git's index conflict set. The sets may differ; + reconcile every path/hunk rather than assuming either representation is + complete. +5. Set a temporary export bookmark at the reviewed Change ID, export it to the + isolated workspace's Git store, and copy that exact full-SHA object to a + temporary ref in the canonical Git repository: + + ```bash + jj --repository "$JJ_WORKSPACE" bookmark set "export-" -r "" + jj --repository "$JJ_WORKSPACE" git export + ``` + +6. Verify the copied Git object: + + ```bash + EXPORTED_SHA=$(git rev-parse --verify "^{commit}") + git fsck --no-dangling "$EXPORTED_SHA" + git diff --exit-code "" "$EXPORTED_SHA^{tree}" + git rev-parse "$EXPORTED_SHA^1" "$EXPORTED_SHA^2" + ``` + +7. Move the preparation branch only after Git verifies the expected tree, + parent order, conflict absence, and Change-ID-to-Git-object map. The optional + engine's only accepted output is verified Git objects. + +### 3. Mode: `fork-merge` + +Compute the only valid upstream target: + +```bash +FACEBOOK_MAIN_SHA=$(git rev-parse --verify "facebook/main^{commit}") +STABLE_TIP_SHA=$(git rev-parse --verify "facebook/-stable^{commit}") +TARGET_SHA=$(git merge-base "$FACEBOOK_MAIN_SHA" "$STABLE_TIP_SHA") +test -n "$TARGET_SHA" +git merge-base --is-ancestor "$TARGET_SHA" "$STABLE_TIP_SHA" +``` + +Assert that `TARGET_SHA` is the upstream main commit where the stable line +forked, is not the stable tip, and is divergent from the Microsoft predecessor +when a real merge is expected. Pin it in state. + +Create `-merge` from `BASE_SHA` and run: + +```bash +git merge --no-ff "$TARGET_SHA" +``` + +Resolve mechanically and minimally: + +- adopt non-semantic upstream structure; +- preserve legitimate macOS behavior through narrow additive `[macOS]` clauses; +- do not select an entire semantic side when hunks have different provenance; +- avoid opportunistic cleanup; +- keep the merge focused on safe structural resolution; +- defer coherent cross-layer ports to named follow-up commits. + +Commit only after every index conflict and serialized marker is gone. Verify the +mechanical commit has exactly two ordered parents: + +```bash +MERGE_SHA=$(git rev-parse --verify "HEAD^{commit}") +test "$(git rev-list --parents -n 1 "$MERGE_SHA" | wc -w | tr -d ' ')" = 3 +test "$(git rev-parse "$MERGE_SHA^1")" = "$BASE_SHA" +test "$(git rev-parse "$MERGE_SHA^2")" = "$TARGET_SHA" +``` + +The stable tip must not be an ancestor: + +```bash +if git merge-base --is-ancestor "$STABLE_TIP_SHA" HEAD; then + echo "stable tip leaked into mainline" >&2 + exit 1 +fi +``` + +### 4. Mode: `stable-sync` + +Require the matching landed minor result. Resolve and pin the stable tip: + +```bash +MATCHING_BASE_SHA=$(git rev-parse --verify "^{commit}") +STABLE_TIP_SHA=$(git rev-parse --verify "facebook/-stable^{commit}") +git switch -c "/release" "$MATCHING_BASE_SHA" +git merge --no-ff "$STABLE_TIP_SHA" +``` + +If the remote stable branch moves, report the new full SHA and begin a new run; +never widen the current run. + +Use Changesets, not the obsolete `yarn nx release plan` flow: + +```bash +yarn change +yarn change:check +yarn changeset status --since="" +``` + +#### Prerelease lifecycle + +- Verify `.changeset/config.json`, public and private package versions, ignored + packages, exact `react-native` peer pins, and whether `.changeset/pre.json` + exists before doing anything. +- For an RC line, enter prerelease mode explicitly with + `yarn changeset pre enter rc` and record `pre.json`. The intended transition + is `rc.0` plus prerelease mode plus a patch Changeset to `rc.1`; inspect + `yarn changeset status` before materializing versions. +- Run `yarn changeset:version` at most once for the approved Changeset state. + That script already runs `yarn changeset version`, post-bumps native artifacts, + and updates the lockfile. Do not run raw `yarn changeset version` first or + otherwise double-version. +- Compare all public/private package versions, changelogs, native artifact + versions, and exact peer dependencies before and after materialization. +- Refuse an accidental final version, a duplicate bump, a broad peer range, or a + missing/private package transition. +- Keep `pre.json` for subsequent RCs. Run `yarn changeset pre exit` only for an + explicitly approved final release, inspect the planned final versions, + materialize once, and verify `pre.json` removal. Never infer that RC + completion authorizes a final. + +Verify the stable merge's parent 1 is `MATCHING_BASE_SHA` and parent 2 is the +pinned `STABLE_TIP_SHA`. + +### 5. Mode: `stack` + +For `..`: + +1. Discover and pin every exact Facebook fork point first. +2. Build each Microsoft `-merge` from the prior minor result. +3. Keep every Facebook stable tip out of cross-minor ancestry. +4. Do not start a later heavy writer until its predecessor has durable local + refs and coordinator-owned evidence. +5. Treat any preview anchor as optional publication infrastructure, never as + release truth. +6. After a predecessor changes or lands, recreate, retarget, revalidate, and + rejudge each downstream branch. + +### 6. Mode: `stack-recreate` + +Use after human review edits, focused PR landings, or a moved Microsoft base: + +```bash +OLD_BASE_SHA=$(git rev-parse --verify "^{commit}") +NEW_BASE_SHA=$(git rev-parse --verify "^{commit}") +git merge-base --is-ancestor "$OLD_BASE_SHA" "$NEW_BASE_SHA" +git rebase --rebase-merges --onto "$NEW_BASE_SHA" "$OLD_BASE_SHA" -merge +``` + +Inspect the generated todo before continuing. Git may try to replay +Facebook-side ancestry. The recreated mechanical root must still have the new +Microsoft base as parent 1 and the original pinned fork point as parent 2. If +necessary: + +1. Save the audited resolved tree and abort the invalid replay. +2. Recreate the mechanical root: + + ```bash + NEW_MERGE_SHA=$(printf '%s\n' "" | + git commit-tree "" \ + -p "$NEW_BASE_SHA" \ + -p "$TARGET_SHA") + ``` + +3. Replay only focused follow-ups: + + ```bash + git rebase --rebase-merges --empty=keep \ + --onto "$NEW_MERGE_SHA" "$OLD_MERGE_SHA" -merge + ``` + +Keep intentionally empty follow-ups when their fix is now upstream. Preserve the +old-to-new map. Rerun conflict provenance, diff tags, generated artifacts, +determinism, builds, evidence, and independent judgment for this execution. + +### 7. Mode: `focused-extraction` + +Extract a coherent change when it can land independently and make the large +merge smaller or reviewable: + +1. Identify the earliest valid Microsoft base, normally `main`. +2. Extract only the focused commit(s); do not copy the mechanical merge. +3. Preserve bilateral provenance, diff tags, tests, and a focused description. +4. Record the candidate branch/commit in merge state and evidence. +5. Do not push or open a PR; return the prepared local result to the coordinator. +6. After the focused change lands, recreate the merge so the conflict becomes + upstreamed or mechanical. Never duplicate the change in both histories. + +Good candidates include a new iOS/RN feature port, cross-layer API migration, +release/tooling fix, generator hardening, codegen path quoting, or a general +Hermes correction. + +### 8. Conflict evidence gate + +Count unique conflicted paths, index entries, and serialized conflict blocks +separately: + +```bash +git ls-files -u | cut -f2 | LC_ALL=C sort -u > conflicted-paths.txt +git ls-files -u | wc -l +wc -l < conflicted-paths.txt +git diff --name-only --diff-filter=U | LC_ALL=C sort -u +git status --porcelain=v1 +git grep -n -E '^(<<<<<<<|=======|>>>>>>>)( |$)' -- . || true +grep -c '^<<<<<<< ' "" +``` + +- The unique `git ls-files -u` path count is authoritative for index conflicts; + stage-entry count is not a path count. +- Count conflict hunks from one canonical serialized representation as the + number of opening markers, and retain the serialized file/hash with its Git + config, labels, command arguments, version, and locale. +- Raw conflict text/hashes are diagnostic only. Normalize paths, full SHAs, + labels, line endings, and locale before comparing runs, and retain both raw + and normalized hashes. +- Verify required terms are present and forbidden/stale terms are absent. + Presence-only checks miss bad combined resolutions. +- Reconcile every discrepancy among index paths, diff-filter output, jj + conflicts, rerere status, and serialized markers. + +Before staging each conflict, record bilateral provenance **per hunk**: + +- path and hunk identifier; +- upstream introducing full SHA, subject/PR, intent, and superseding commits; +- local macOS full SHA, subject/PR, intent, and current legitimacy; +- rerere candidate disposition; +- decision: upstream delta is authoritative because local is stale, preserve + legitimate macOS behavior on upstream structure, combine independent valid + changes, or stop; +- final basis and follow-up layer. + +Use path/hunk-aware `git log -p`, `git blame`, and rename-following history. A +tag is not provenance. Never choose `ours`, `theirs`, or a whole semantic file +when different hunks require different decisions. For modify/delete conflicts, +find the upstream replacement owner and map the local requirement there; never +resurrect a deleted file without current evidence. Audit rerere against index +stages 1/2/3 and stage accepted paths individually. + +### 9. Diff-tag gate + +Read `docsite/docs/contributing/diffs-with-upstream.md`. Audit each semantic +layer against its selected Microsoft base, not only the final combined diff. + +- Preserve inherited valid findings and their provenance across layers. +- Compute both the raw delta and a normalized NEW delta that removes moves, + formatting, generated churn, and already-inherited tagged fork differences. + Tag only genuinely new/retained fork behavior, not normalization noise. +- Use `// [macOS]` for a modified line, `[macOS` / `macOS]` around additions, + and a file-level marker near the top of macOS-only files, including `.mts` and + `.d.ts`. +- Audit multiline preprocessor expressions as one condition. Prefer the minimal + additive condition, such as adding `|| TARGET_OS_OSX` to the existing iOS + clause, rather than restructuring the entire branch. +- Apply documented shapes to iOS-only guards, mixed `iOS || macOS` conditions, + `#else` branches, and block endings. +- Search NEW additions for `TARGET_OS_OSX`, `RCTPlatform*`, `RCTUIKit`, AppKit, + `.macos.*`, and untagged macOS file patterns. Record justified exceptions. +- Remove a tag only when its fork difference genuinely disappeared or moved to + a documented new owner. + +Rerun the raw/normalized audit after the mechanical merge and after every +semantic, generated, Apple, Hermes, tooling, and CI layer. Executor and judge +each produce independent evidence; tag compliance is a publication blocker. + +### 10. Determinism gates + +#### Yarn + +- Preserve the baseline lockfile and package-manager version. Do not enable + global age-zero behavior or `YARN_ENABLE_HARDENED_MODE`, and do not perform a + full lock regeneration to solve a targeted tuple. +- Respect Yarn's package-age gate. If a required artifact is too new, record the + exact descriptor/resolution/checksum and wait or use the repository-approved + targeted mechanism; never disable the gate globally. +- Reconstruct only affected descriptors from the same baseline and record tuple + provenance: package, descriptor, locator, version, source/registry, checksum, + dependents, and why it changed. +- Run targeted lock generation twice from identical clean baselines and require + byte-identical `yarn.lock` hashes. Then run normal `yarn install --immutable`; + the normal immutable run is authoritative. +- Reject opportunistic upgrades, unexpected tuple churn, or baseline drift. + +#### Ruby, Bundler, and CocoaPods + +- Keep `Gemfile` and `Gemfile.lock` consistent; use the locked Bundler, Ruby, and + CocoaPods versions rather than a host default. +- Run pod generation from the same path shape and environment. Record podspec + source paths, pod checksums, lockfile hashes, and generated-project deltas. +- Treat path-derived podspec content, changing checksums, absolute build paths, + or an unlocked CocoaPods version as nondeterministic failures. +- Exercise the embedded + `packages/react-native/scripts/hermes/prepare-hermes-for-build.js` path and the + `prepare_command` in + `packages/react-native/sdks/hermes-engine/hermes-engine.podspec`, not a + hand-simulated substitute. + +#### Public C++ API + +- For release/stable generation, use the canonical Linux environment with + Doxygen 1.16.1 and Python 3.12. Do not regenerate with host macOS tools. +- Run the generator twice from the same clean input and require byte-identical + output. Compare against the expected public API snapshot and reject + truncation, path leakage, ordering drift, or partial/corrupt output. +- Skip this generator in the `fork-merge` workflow when the canonical upstream + workflow skips it; do not commit a host-generated replacement merely to make + the snapshot quiet. +- Keep `private/cxx-public-api` checks separate from the canonical release + Doxygen generator when both exist in the selected minor. + +#### Hermes + +- Verify the exact Hermes commit/version pins, host compiler, CMake version, and + explicit `CMAKE_BUILD_TYPE`; never infer build type from a reused directory. +- Use the path-independent podspec at + `packages/react-native/sdks/hermes-engine/hermes-engine.podspec`. Its + source/prebuilt choice, embedded prepare command, and package mapping must work + outside the repository's absolute path. +- Decide and record prebuilt versus source. Prebuilt validation checks archive + hashes, all required platform binaries, public headers, inspector headers, + module/header mappings, and podspec pins. Source validation checks the pinned + commit, host `hermesc`, target frameworks, and packaging. +- Treat absent inspector/public headers as a packaging failure, not an optional + omission. Do not paper over them with stale headers from another artifact. +- If source or exported headers change after an RC artifact was built or + published, generate fresh matching prebuilts and rerun packaging before any + publication. A prior green artifact is stale. + +### 11. Reversible history and review workflow + +Keep reviewable commits in order: + +1. mechanical ordered two-parent merge; +2. focused macOS API/type ports; +3. generated API snapshots; +4. Apple source/platform ports; +5. Hermes/toolchain compatibility; +6. narrowly scoped corrections; +7. CI fixes tied to distinct root causes. + +Do not amend or squash by default. Keep backups and evidence across recreation. +After human feedback, use `stack-recreate`, preserve coherent review layers, +rerun every downstream semantic gate, and obtain a fresh judge decision. + +Normal/additive publication is preferred. A history rewrite is exceptional and +requires explicit approval plus an exact lease: + +```bash +git push \ + --force-with-lease=refs/heads/: \ + origin +``` + +Never use plain `--force`; stop if the remote moved. + +### 12. Validation and CI + +Run focused checks first, then full gates in this order: + +1. immutable Yarn install, Changesets checks, constraints, format, lint, Jest, + and Flow as applicable; +2. build/generated types **before** legacy TypeScript: + + ```bash + yarn build-types + yarn test-generated-typescript + yarn test-typescript + ``` + +3. CocoaPods and SwiftPM manifests/generation; +4. public API and deterministic generators; +5. Hermes source/prebuilt/header packaging; +6. Apple platform builds and RNTester. + +The generated-types command validates the output of `build-types`; do not omit +that generation step or reverse the generated and legacy +`packages/react-native/types` checks. + +For Apple prebuilds, require the five release slices recorded for the target +release: iOS device, iOS simulator, macOS, visionOS device, and visionOS +simulator. Validate Mac Catalyst too when included by the selected release +configuration. Compose the XCFramework, verify every expected library and +architecture, and retain matching dSYMs. Run RNTester on the required iOS/macOS +architectures and old/new architecture matrix; do not substitute a packaging +success for product execution. + +Use a visible TTY with teed logs for long commands. Only one heavy writer/build +may run on a host at once. Diagnose CPU, memory, disk, simulator, runner, and +timeout saturation before labeling red as product failure. Distinguish: + +- **harness/infrastructure error:** no meaningful product step ran, runner or + simulator failed, host overloaded, or logs prove setup failure; +- **product failure:** a relevant compile, test, package, or runtime step ran + against the exact head and failed. + +Record exact head and toolchain provenance for both. Hosted CI is a fallback +only after explicit user approval through the coordinator; never push merely to +obtain compute. + +### 13. Evidence corpus and review body + +Before session archival, copy executor and judge evidence to a +coordinator-owned artifact directory. Include: + +- plan, prompts, state, refs, full-SHA graphs, Git config, and environment; +- raw and normalized conflict inventories/hashes; +- per-hunk bilateral provenance and decisions; +- per-layer raw/normalized diff-tag audits; +- old-to-new commit maps, deterministic two-run hashes, validation logs, and + exact-head proof; +- package/lock/pod/Hermes/prebuilt provenance; +- final diff, status, commit bundle, and judge verdict. + +Create a durable archive and Git bundle: + +```bash +tar -C "" -czf ".tar.gz" "" +shasum -a 256 ".tar.gz" > ".tar.gz.sha256" +git bundle create ".bundle" "" +shasum -a 256 ".bundle" > ".bundle.sha256" +``` + +Verify archives can be listed and the bundle verifies before deleting any +session/worktree. Keep artifacts out of the repository. + +Generate PR body text locally even when publication is not authorized. Preserve +both exact bytes and a normalized form. Normalize line endings to LF and define +whether the canonical body has exactly one trailing newline before hashing: + +```bash +perl -pi -e 's/\r\n?/\n/g' "" +printf '\n' >> "" +perl -0pi -e 's/\n+\z/\n/' "" +shasum -a 256 "" +``` + +If a PR already exists, fetch the live body bytes, normalize with the same +procedure, and compare both byte/hash forms. Update SHAs, links, conflict totals, +test evidence, and body hashes after final gates and any recreation. + +### 14. Focused mainline candidates + +Record each candidate as already on main, extracted locally, pending, rejected, +or obsolete. Do not silently reimplement something already present: + +- Changesets origin/base fallback; +- canonical C++ generator hardening; +- codegen path quoting; +- general Hermes resolution, podspec, or packaging fixes; +- `test-all`/CI build-types-before-legacy-TypeScript ordering; +- removal of obsolete `yarn nx release plan` documentation. + +Keep candidates out of the merge resolution unless required for coherent +behavior. Use `focused-extraction` when independent review is better. + +### 15. Publication controls + +Never push any ref, create or update a PR, create/delete a preview anchor, mark a +PR ready, or merge without explicit user approval obtained by the coordinator +for that exact action, destination, and SHA. Executor and judge do no +publication. Coordinator is the sole landing role. + +After approval, the coordinator still rechecks the exact judged SHA, remote +state, evidence hashes, and downstream dependencies. A changed SHA invalidates +the judgment and requires recreation/revalidation/rejudgment. Preview anchors +must be new additive refs, must never rewrite unrelated refs, and require +separate approval to delete. + +### 16. Final graph and completion gates + +For `fork-merge`, assert with full SHAs: + +```bash +test "$(git rev-parse "$MERGE_SHA^1")" = "$BASE_SHA" +test "$(git rev-parse "$MERGE_SHA^2")" = "$TARGET_SHA" +test "$(git merge-base "$BASE_SHA" HEAD)" = "$BASE_SHA" +git rev-list --left-right --count "$BASE_SHA...HEAD" +``` + +Require: + +- exactly one intended mechanical merge on first-parent history; +- exact ordered parents and no stable-tip ancestry; +- expected merge base and ahead/behind topology; +- clean index/worktree and no serialized conflict markers; +- reconciled conflict metrics and bilateral per-hunk decisions; +- every semantic layer's raw/normalized diff-tag audit; +- deterministic dependency, generator, pod, and Hermes results; +- required type, Apple slice, XCFramework/dSYM, and RNTester gates; +- fresh matching prebuilts after any post-RC source/header change; +- durable coordinator evidence, archive/hash/bundle verification; +- fresh independent judge PASS for the exact current SHA. + +For `stable-sync`, also require the pinned stable tip as parent 2, correct +Changesets prerelease/final state, exact peer pins, and proof that no later stable +commit drifted into the run. + +Stop rather than guess when: + +- graph, parent, target, ref, or state assertions fail; +- conventions or provenance are ambiguous; +- conflict sets or metrics do not reconcile; +- a local behavior has no owner after upstream deletion; +- diff-tag, determinism, packaging, build, or product validation fails; +- a harness failure cannot be separated from product red; +- source/header changes lack fresh matching prebuilts; +- another lane moved the branch, remote, PR, or evidence baseline; +- credentials would need weakening; +- publication approval is absent or does not match the exact action/SHA. + +Report final refs and parents, topology, conflict/decision totals, old-to-new +maps, validation logs, artifact hashes, judge state, and all blockers to the +coordinator. Stop before publication. diff --git a/.ai/index.md b/.ai/index.md index 74b69b6817a7..0272ea8123de 100644 --- a/.ai/index.md +++ b/.ai/index.md @@ -1,3 +1,8 @@ # AI Notes Use [Conventional Commits](https://www.conventionalcommits.org/) for PR titles. + +## Commands + +- [Backport](commands/backport.md) — backport feature commits to stable release branches. +- [Merge minor](commands/merge-minor.md) — prepare, recreate, and validate minor merge and stable-release branches. diff --git a/.claude/commands/merge-minor.md b/.claude/commands/merge-minor.md new file mode 100644 index 000000000000..38805b3dbdd5 --- /dev/null +++ b/.claude/commands/merge-minor.md @@ -0,0 +1,3 @@ +Follow the instructions in .ai/commands/merge-minor.md + +Minor / range: $ARGUMENTS From 88032aedde7e546d37436e6103bb7c12a13e2f6d Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Tue, 22 Sep 2026 16:38:48 -0700 Subject: [PATCH 2/3] fix(ci): refresh validated snapshots and platform fixtures Apply the independently reviewed branch-specific CI follow-up while preserving public-registry locks and the linear stack. --- packages/react-native/ReactNativeApi.d.ts | 580 ++++++++++++++++-- .../ios-prebuild/__tests__/hermes-test.js | 25 +- 2 files changed, 561 insertions(+), 44 deletions(-) diff --git a/packages/react-native/ReactNativeApi.d.ts b/packages/react-native/ReactNativeApi.d.ts index 4d1c2bb2796b..cf092733713e 100644 --- a/packages/react-native/ReactNativeApi.d.ts +++ b/packages/react-native/ReactNativeApi.d.ts @@ -4,7 +4,7 @@ * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * - * @generated SignedSource<<40653256edf0f5263500411e60c8bf2c>> + * @generated SignedSource<<9ca14c8274fb5a5e314064d7ee652a0b>> * * This file was generated by scripts/js-api/build-types/index.js. */ @@ -21,17 +21,15 @@ /* eslint-disable redundant-undefined/redundant-undefined */ -import { ListRenderItem } from "@react-native-macos/virtualized-lists" -import { ListRenderItemInfo } from "@react-native-macos/virtualized-lists" -import { ListViewToken } from "@react-native-macos/virtualized-lists" +import type { FocusEvent as FocusEvent_2 } from "react-native" +import type { LayoutChangeEvent as LayoutChangeEvent_2 } from "react-native" +import type { LayoutRectangle as LayoutRectangle_2 } from "react-native" import * as React from "react" -import { ScrollToLocationParamsType } from "@react-native-macos/virtualized-lists" -import { SectionBase } from "@react-native-macos/virtualized-lists" -import type { SectionData as SectionListData } from "@react-native-macos/virtualized-lists" -import { Separators } from "@react-native-macos/virtualized-lists" -import { VirtualizedListProps } from "@react-native-macos/virtualized-lists" -import VirtualizedLists from "@react-native-macos/virtualized-lists" -import { VirtualizedSectionListProps } from "@react-native-macos/virtualized-lists" +import type { ScrollResponderType as ScrollResponderType_2 } from "react-native" +import { ScrollView as ScrollView_2 } from "react-native" +import type { ScrollViewProps as ScrollViewProps_2 } from "react-native" +import type { StyleProp as StyleProp_2 } from "react-native" +import type { ViewStyle as ViewStyle_2 } from "react-native" declare const $$AndroidSwitchNativeComponent: NativeType declare const $$AnimatedFlatList: ( props: Omit>, "ref"> & { @@ -87,6 +85,14 @@ declare const $$AnimatedView: AnimatedComponentType< React.ComponentRef > declare const $$flattenStyle: typeof flattenStyle_default +declare const $$index: { + keyExtractor: typeof keyExtractor + get FillRateHelper(): FillRateHelperT + get ViewabilityHelper(): ViewabilityHelperT + get VirtualizedList(): VirtualizedListT + get VirtualizedListContextResetter(): VirtualizedListContextResetterT + get VirtualizedSectionList(): AnyVirtualizedSectionList +} declare const $$NativeDeviceInfo: typeof NativeDeviceInfo_default declare const $$NativeDialogManagerAndroid: null | Spec | undefined declare const $$ProgressBarAndroidNativeComponent: HostComponent @@ -567,8 +573,23 @@ declare const View: typeof View_default declare const ViewNativeComponent_default: HostComponent declare const VirtualizedList: typeof VirtualizedListComponent_default declare const VirtualizedListComponent_default: VirtualizedListType +declare const VirtualizedListContext: React.Context declare const VirtualizedSectionList: typeof VirtualizedSectionList_default declare const VirtualizedSectionList_default: VirtualizedSectionListType +declare const VirtualizedSectionListComponent_default: < + ItemT, + SectionT extends SectionBase< + ItemT, + DefaultVirtualizedSectionT + > = DefaultVirtualizedSectionT, +>( + props: Omit, "ref"> & { + ref?: React.Ref<{ + getListRef(): undefined | VirtualizedList_default + scrollToLocation(params: ScrollToLocationParamsType): void + }> + }, +) => React.ReactNode declare type ____BlendMode_Internal = | "color-burn" | "color-dodge" @@ -1005,6 +1026,7 @@ declare type $$AnimatedSectionList = typeof $$AnimatedSectionList declare type $$AnimatedText = typeof $$AnimatedText declare type $$AnimatedView = typeof $$AnimatedView declare type $$flattenStyle = typeof $$flattenStyle +declare type $$index = typeof $$index declare type $$NativeDeviceInfo = typeof $$NativeDeviceInfo declare type $$NativeDialogManagerAndroid = typeof $$NativeDialogManagerAndroid declare type $$ProgressBarAndroidNativeComponent = @@ -1625,6 +1647,8 @@ declare type AnimationConfig = { readonly useNativeDriver: boolean } declare type AnyAttributeType = AttributeType +declare type AnyVirtualizedSectionList = + typeof VirtualizedSectionListComponent_default declare type AppConfig = { appKey: string component?: ComponentProvider @@ -1798,7 +1822,6 @@ declare type ButtonProps = { readonly nextFocusRight?: number readonly nextFocusUp?: number readonly onAccessibilityAction?: (event: AccessibilityActionEvent) => unknown - readonly onAccessibilityAction?: (event: AccessibilityActionEvent) => unknown readonly onBlur?: (e: BlurEvent) => void readonly onFocus?: (e: FocusEvent) => void readonly testID?: string @@ -1810,6 +1833,40 @@ declare type ButtonProps = { declare type ButtonRef = React.ComponentRef declare function cancelHeadlessTask(taskId: number, taskKey: string): void declare type Category = string +declare type CellMetricProps = { + data: VirtualizedListProps["data"] + getItem: VirtualizedListProps["getItem"] + getItemCount: VirtualizedListProps["getItemCount"] + getItemLayout?: VirtualizedListProps["getItemLayout"] + keyExtractor?: VirtualizedListProps["keyExtractor"] +} +declare type CellMetrics = { + index: number + isMounted: boolean + length: number + offset: number +} +declare type CellRegion = { + first: number + isSpacer: boolean + last: number +} +declare type CellRendererProps = { + readonly cellKey: string + readonly children: React.ReactNode + readonly index: number + readonly item: ItemT + readonly style: StyleProp_2 + readonly onFocusCapture?: (event: FocusEvent_2) => void + readonly onLayout?: (event: LayoutChangeEvent_2) => void +} +declare class CellRenderMask { + addCells(cells: { first: number; last: number }): void + constructor(numCells: number) + enumerateRegions(): ReadonlyArray + equals(other: CellRenderMask): boolean + numCells(): number +} declare type Clipboard = typeof Clipboard declare type CodeFrame = { readonly collapse?: boolean @@ -1873,6 +1930,28 @@ declare function configureNext( onAnimationDidFail?: OnAnimationDidFailCallback, ): void declare type ContentAvailable = 1 | null | void +declare type Context = { + readonly cellKey: string | undefined + readonly horizontal: boolean | undefined + readonly getOutermostParentListRef: () => VirtualizedList_default + readonly getScrollMetrics: () => { + contentLength: number + dOffset: number + dt: number + offset: number + timestamp: number + velocity: number + visibleLength: number + zoomScale: number + } + readonly registerAsNestedChild: ($$PARAM_0$$: { + cellKey: string + ref: VirtualizedList_default + }) => void + readonly unregisterAsNestedChild: ($$PARAM_0$$: { + ref: VirtualizedList_default + }) => void +} declare function counterEvent(eventName: EventName, value: number): void declare type create = typeof create declare type createAnimatedComponent = typeof createAnimatedComponent @@ -1995,6 +2074,10 @@ declare type DefaultSectionT = { [key: string]: any } declare type DefaultTypes = boolean | number | ReadonlyArray | string +declare type DefaultVirtualizedSectionT = { + [key: string]: any + data: any +} declare type delay = typeof delay declare interface DEPRECATED_RCTExport { readonly getConstants?: () => {} @@ -2298,6 +2381,34 @@ declare type FetchResult = { NoData: "UIBackgroundFetchResultNoData" ResultFailed: "UIBackgroundFetchResultFailed" } +declare class FillRateHelper_default { + activate(): void + static addListener(callback: ($$PARAM_0$$: FillRateInfo) => void): { + remove: () => void + } + computeBlankness( + props: CellMetricProps & { + initialNumToRender?: number + }, + cellsAroundViewport: { + first: number + last: number + }, + scrollMetrics: { + dOffset: number + offset: number + velocity: number + visibleLength: number + }, + ): number + constructor(listMetrics: ListMetricsAggregator_default) + deactivateAndFlush(): void + enabled(): boolean + static setMinSampleCount(minSampleCount: number): void + static setSampleRate(sampleRate: number): void +} +declare type FillRateHelperT = typeof FillRateHelper_default +declare type FillRateInfo = Info declare type FilterFunction = | { blur: number | string @@ -2748,6 +2859,18 @@ declare type IndeterminateProgressBarAndroidStyleAttrProp = { | "SmallInverse" } declare type IndicatorSize = "large" | "small" | number +declare class Info { + any_blank_count: number + any_blank_ms: number + any_blank_speed_sum: number + mostly_blank_count: number + mostly_blank_ms: number + pixels_blank: number + pixels_sampled: number + pixels_scrolled: number + sample_count: number + total_time_spent: number +} declare type InnerViewInstance = React.ComponentRef declare type InputAccessoryView = typeof InputAccessoryView declare type InputAccessoryViewProps = { @@ -2870,6 +2993,7 @@ declare interface IPerformanceLogger { stopTimespan(key: string, timestamp?: number, extras?: Extras): void } declare function isEnabled(): boolean +declare type Item = any declare type Keyboard = typeof Keyboard declare class KeyboardAvoidingView extends React.Component< KeyboardAvoidingViewProps, @@ -2986,6 +3110,7 @@ declare type KeyEventProps = { readonly onKeyUp?: (event: KeyUpEvent) => void readonly onKeyUpCapture?: (event: KeyUpEvent) => void } +declare function keyExtractor(item: any, index: number): string declare type KeysOfUnion = T extends any ? keyof T : never declare type KeyUpEvent = NativeSyntheticEvent declare type LayoutAnimation = typeof LayoutAnimation @@ -3072,6 +3197,57 @@ declare class LinkingImpl extends NativeEventEmitter { }>, ): Promise } +declare class ListMetricsAggregator_default { + cartesianOffset(flowRelativeOffset: number): number + flowRelativeOffset( + layout: LayoutRectangle_2, + referenceContentLength?: null | number | undefined, + ): number + getAverageCellLength(): number + getCellMetrics( + index: number, + props: CellMetricProps, + ): CellMetrics | null | undefined + getCellMetricsApprox(index: number, props: CellMetricProps): CellMetrics + getCellOffsetApprox(index: number, props: CellMetricProps): number + getContentLength(): number + getHighestMeasuredCellIndex(): number + hasContentLength(): boolean + notifyCellLayout($$PARAM_0$$: { + cellIndex: number + cellKey: string + layout: LayoutRectangle_2 + orientation: ListOrientation + }): boolean + notifyCellUnmounted(cellKey: string): void + notifyListContentLayout($$PARAM_0$$: { + layout: { + readonly height: number + readonly width: number + } + orientation: ListOrientation + }): void +} +declare type ListOrientation = { + horizontal: boolean + rtl: boolean +} +declare type ListRenderItem = ( + info: ListRenderItemInfo, +) => React.ReactNode +declare type ListRenderItemInfo = { + index: number + isSelected: boolean | undefined + item: ItemT + separators: Separators +} +declare type ListViewToken = { + index: number | undefined + isViewable: boolean + item: any + key: string + section?: any +} declare type LogBox = typeof LogBox declare type LogData = { readonly category: Category @@ -3491,6 +3667,79 @@ declare type OptionalSectionListProps = { removeClippedSubviews?: boolean renderItem?: SectionListRenderItem } +declare type OptionalVirtualizedListProps = { + CellRendererComponent?: React.ComponentType> + debug?: boolean + disableVirtualization?: boolean + extraData?: any + horizontal?: boolean + initialNumToRender?: number + initialScrollIndex?: number + inverted?: boolean + ItemSeparatorComponent?: React.ComponentType | React.JSX.Element + keyExtractor?: (item: Item, index: number) => string + ListEmptyComponent?: React.ComponentType | React.JSX.Element + ListFooterComponent?: React.ComponentType | React.JSX.Element + ListFooterComponentStyle?: StyleProp_2 + ListHeaderComponent?: React.ComponentType | React.JSX.Element + ListHeaderComponentStyle?: StyleProp_2 + ListItemComponent?: React.ComponentType | React.JSX.Element + maxToRenderPerBatch?: number + onEndReached?: (info: { distanceFromEnd: number }) => void + onEndReachedThreshold?: number + onRefresh?: () => void + onScrollToIndexFailed?: (info: { + averageItemLength: number + highestMeasuredFrameIndex: number + index: number + }) => void + onStartReached?: (info: { distanceFromStart: number }) => void + onStartReachedThreshold?: number + onViewableItemsChanged?: (info: { + changed: Array + viewableItems: Array + }) => void + persistentScrollbar?: boolean + progressViewOffset?: number + refreshControl?: React.JSX.Element + refreshing?: boolean + removeClippedSubviews?: boolean + renderItem?: ListRenderItem + updateCellsBatchingPeriod?: number + viewabilityConfig?: ViewabilityConfig + viewabilityConfigCallbackPairs?: Array + windowSize?: number + getItemLayout?: ( + data: any, + index: number, + ) => { + index: number + length: number + offset: number + } + renderScrollComponent?: (props: ScrollViewProps_2) => React.JSX.Element +} +declare type OptionalVirtualizedSectionListProps< + ItemT, + SectionT = DefaultVirtualizedSectionT, +> = { + onEndReached?: ($$PARAM_0$$: { distanceFromEnd: number }) => void + renderSectionFooter?: (info: { section: SectionT }) => null | React.ReactNode + renderSectionHeader?: (info: { section: SectionT }) => null | React.ReactNode + SectionSeparatorComponent?: React.ComponentType + stickySectionHeadersEnabled?: boolean + renderItem?: (info: { + index: number + isSelected: boolean | undefined + item: ItemT + section: SectionT + separators: { + highlight: () => void + unhighlight: () => void + updateProps: (select: "leading" | "trailing", newProps: Object) => void + } + }) => null | React.ReactNode +} declare type OrientationChangeEvent = { readonly orientation: "landscape" | "portrait" } @@ -4159,6 +4408,17 @@ declare type RequiredFlatListProps = { declare type RequiredSectionListProps = { sections: ReadonlyArray> } +declare type RequiredVirtualizedListProps = { + data?: any + getItem: (data: any, index: number) => Item | undefined + getItemCount: (data: any) => number +} +declare type RequiredVirtualizedSectionListProps< + ItemT, + SectionT = DefaultVirtualizedSectionT, +> = { + sections: ReadonlyArray> +} declare type requireNativeComponent = typeof requireNativeComponent declare type ResolvedAssetSource = { readonly height: number | undefined @@ -4303,6 +4563,13 @@ declare type ScheduleLocalNotificationDetails = } declare type ScrollEvent = NativeSyntheticEvent declare type ScrollResponderType = ScrollViewImperativeMethods +declare type ScrollToLocationParamsType = { + animated?: boolean + itemIndex: number + sectionIndex: number + viewOffset?: number + viewPosition?: number +} declare type ScrollView = typeof ScrollView declare type ScrollViewBaseProps = { readonly children?: React.ReactNode @@ -4516,6 +4783,28 @@ declare type ScrollViewStickyHeaderProps = { readonly scrollViewHeight: number | undefined readonly onLayout: (event: LayoutChangeEvent) => void } +declare type SectionBase< + SectionItemT, + SectionT = DefaultVirtualizedSectionT, +> = { + data: ReadonlyArray + ItemSeparatorComponent?: React.ComponentType | React.JSX.Element + key?: string + renderItem?: (info: { + index: number + item: SectionItemT + section: SectionListData + separators: { + highlight: () => void + unhighlight: () => void + updateProps: (select: "leading" | "trailing", newProps: Object) => void + } + }) => null | React.JSX.Element + keyExtractor?: ( + item: SectionItemT | undefined, + index?: number | undefined, + ) => string +} declare class SectionList< ItemT = any, SectionT = DefaultSectionT, @@ -4529,6 +4818,13 @@ declare class SectionList< scrollToLocation(params: ScrollToLocationParamsType): void setNativeProps(props: Object): void } +declare type SectionListData< + SectionItemT, + SectionT = DefaultVirtualizedSectionT, +> = + | (Readonly> & SectionT) + | (SectionBase & SectionT) + | SectionT declare type SectionListProps = Omit< Omit< VirtualizedSectionListProps, @@ -4552,13 +4848,18 @@ declare type SectionListRenderItem = ( declare type SectionListRenderItemInfo< ItemT, SectionT = DefaultSectionT, -> = Omit, "section"> & { +> = ListRenderItemInfo & { section: SectionListData } declare type Selection = { readonly end: number readonly start: number } +declare type Separators = { + highlight: () => void + unhighlight: () => void + updateProps: (select: "leading" | "trailing", newProps: Object) => void +} declare type sequence = typeof sequence declare function setColorScheme(colorScheme: ColorSchemeName): void declare function setComponentProviderInstrumentationHook( @@ -4824,6 +5125,35 @@ declare function startHeadlessTask( taskKey: string, data: any, ): void +declare type State = { + cellsAroundViewport: { + first: number + last: number + } + firstVisibleItemKey: string | undefined + pendingScrollUpdateCount: number + renderMask: CellRenderMask + selectedRowIndex: number +} +declare class StateSafePureComponent_default< + Props, + State extends {}, +> extends React.PureComponent { + constructor(props: Props) + setState( + partialState: + | ( + | (( + $$PARAM_0$$: State, + $$PARAM_1$$: Props, + ) => null | Pick | undefined) + | Pick + ) + | null + | undefined, + callback?: () => unknown, + ): void +} declare class StatusBar extends React.Component { static currentHeight: null | number | undefined componentDidMount(): void @@ -5613,6 +5943,55 @@ declare function View_default( ref?: React.Ref> }, ): React.ReactNode +declare type ViewabilityConfig = { + readonly itemVisiblePercentThreshold?: number + readonly minimumViewTime?: number + readonly viewAreaCoveragePercentThreshold?: number + readonly waitForInteraction?: boolean +} +declare type ViewabilityConfigCallbackPair = { + viewabilityConfig: ViewabilityConfig + onViewableItemsChanged: (info: { + changed: Array + viewableItems: Array + }) => void +} +declare class ViewabilityHelper_default { + computeViewableItems( + props: CellMetricProps, + scrollOffset: number, + viewportHeight: number, + listMetrics: ListMetricsAggregator_default, + renderRange?: { + first: number + last: number + }, + ): Array + constructor(config?: ViewabilityConfig) + dispose(): void + onUpdate( + props: CellMetricProps, + scrollOffset: number, + viewportHeight: number, + listMetrics: ListMetricsAggregator_default, + createViewToken: ( + index: number, + isViewable: boolean, + props: CellMetricProps, + ) => ListViewToken, + onViewableItemsChanged: ($$PARAM_0$$: { + changed: Array + viewableItems: Array + }) => void, + renderRange?: { + first: number + last: number + }, + ): void + recordInteraction(): void + resetViewableIndices(): void +} +declare type ViewabilityHelperT = typeof ViewabilityHelper_default declare type ViewBaseProps = { readonly children?: React.ReactNode readonly collapsable?: boolean @@ -5691,10 +6070,133 @@ declare type ViewPropsIOS = { declare type ViewStyle = ____ViewStyle_Internal declare type ViewStyleProp = ____ViewStyleProp_Internal declare type VirtualizedList = typeof VirtualizedList -declare type VirtualizedListType = typeof VirtualizedLists.VirtualizedList +declare class VirtualizedList_default extends StateSafePureComponent_default< + VirtualizedListProps, + State +> { + static contextType: typeof VirtualizedListContext + state: State + componentDidMount(): void + componentDidUpdate(prevProps: VirtualizedListProps): void + componentWillUnmount(): void + constructor(props: VirtualizedListProps) + ensureItemAtIndexIsVisible(rowIndex: number): void + flashScrollIndicators(): void + static getDerivedStateFromProps( + newProps: VirtualizedListProps, + prevState: State, + ): State + getScrollableNode(): null | number | undefined + getScrollRef(): null | React.ComponentRef | undefined + getScrollResponder(): null | ScrollResponderType_2 | undefined + hasMore(): boolean + measureLayoutRelativeToContainingList(): void + recordInteraction(): void + render(): React.ReactNode + scrollToEnd( + params?: + | null + | undefined + | { + animated?: boolean + }, + ): void + scrollToIndex(params: { + animated?: boolean + index: number + viewOffset?: number + viewPosition?: number + }): any + scrollToItem(params: { + animated?: boolean + item: Item + viewOffset?: number + viewPosition?: number + }): void + scrollToOffset(params: { animated?: boolean; offset: number }): void + selectRowAtIndex(rowIndex: number): void + setNativeProps(props: Object): void +} +declare type VirtualizedListContext = typeof VirtualizedListContext +declare function VirtualizedListContextResetter($$PARAM_0$$: { + children: React.ReactNode +}): React.ReactNode +declare type VirtualizedListContextResetterT = + typeof VirtualizedListContextResetter +declare type VirtualizedListMacOSProps = { + enableSelectionOnKeyPress?: boolean + initialSelectedIndex?: number + onSelectionChanged?: (info: { + item: Item | undefined + newSelection: number + previousSelection: number + }) => void + onSelectionEntered?: (item: Item | undefined) => void + rowIndex?: number + sectionIndex?: number +} +declare type VirtualizedListProps = Omit< + ScrollViewProps_2, + | "data" + | "getItem" + | "getItemCount" + | "enableSelectionOnKeyPress" + | "initialSelectedIndex" + | "onSelectionChanged" + | "onSelectionEntered" + | "rowIndex" + | "sectionIndex" + | "CellRendererComponent" + | "debug" + | "disableVirtualization" + | "extraData" + | "getItemLayout" + | "horizontal" + | "initialNumToRender" + | "initialScrollIndex" + | "inverted" + | "ItemSeparatorComponent" + | "keyExtractor" + | "ListEmptyComponent" + | "ListFooterComponent" + | "ListFooterComponentStyle" + | "ListHeaderComponent" + | "ListHeaderComponentStyle" + | "ListItemComponent" + | "maxToRenderPerBatch" + | "onEndReached" + | "onEndReachedThreshold" + | "onRefresh" + | "onScrollToIndexFailed" + | "onStartReached" + | "onStartReachedThreshold" + | "onViewableItemsChanged" + | "persistentScrollbar" + | "progressViewOffset" + | "refreshControl" + | "refreshing" + | "removeClippedSubviews" + | "renderItem" + | "renderScrollComponent" + | "updateCellsBatchingPeriod" + | "viewabilityConfig" + | "viewabilityConfigCallbackPairs" + | "windowSize" + | never +> & + RequiredVirtualizedListProps & + OptionalVirtualizedListProps & + VirtualizedListMacOSProps +declare type VirtualizedListT = typeof VirtualizedList_default +declare type VirtualizedListType = typeof $$index.VirtualizedList declare type VirtualizedSectionList = typeof VirtualizedSectionList -declare type VirtualizedSectionListType = - typeof VirtualizedLists.VirtualizedSectionList +declare type VirtualizedSectionListProps< + ItemT, + SectionT = DefaultVirtualizedSectionT, +> = RequiredVirtualizedSectionListProps & + OptionalVirtualizedSectionListProps & + Omit +declare type VirtualizedSectionListType = typeof $$index.VirtualizedSectionList declare enum VirtualViewMode { Hidden = 2, Prerender = 1, @@ -5810,7 +6312,7 @@ export { AlertOptions, // 39b16cfa AlertType, // 5ab91217 AndroidKeyboardEvent, // e03becc8 - Animated, // 4c0878c1 + Animated, // 2fb628e4 AppConfig, // ebddad4b AppRegistry, // eb54df94 AppState, // 12012be5 @@ -5822,8 +6324,8 @@ export { BackPressEventName, // 4620fb76 BlurEvent, // 870b9bb5 BoxShadowValue, // b679703f - Button, // 07bdb19b - ButtonProps, // 612202e0 + Button, // 91e87f16 + ButtonProps, // 9ec3afed Clipboard, // 41addb89 CodegenTypes, // 030a94b8 ColorSchemeName, // 31a4350e @@ -5859,8 +6361,8 @@ export { EventSubscription, // b8d084aa ExtendedExceptionData, // 5a6ccf5a FilterFunction, // bf24c0e3 - FlatList, // 1fe07604 - FlatListProps, // ec6dc7bf + FlatList, // b4aa90b3 + FlatListProps, // 3069cec7 FocusEvent, // 529b43eb FontVariant, // 7c7558bb GestureResponderEvent, // 52d0886d @@ -5915,9 +6417,9 @@ export { LayoutConformanceProps, // 055f03b8 LayoutRectangle, // 6601b294 Linking, // b5645d2b - ListRenderItem, - ListRenderItemInfo, - ListViewToken, + ListRenderItem, // 3ba527db + ListRenderItemInfo, // d21a2b9b + ListViewToken, // 833d3481 LogBox, // 04665d29 LogData, // 89af6d4c MeasureInWindowOnSuccessCallback, // a285f598 @@ -5987,7 +6489,7 @@ export { ReturnKeyTypeOptions, // afd47ba3 Role, // af7b889d RootTag, // 3cd10504 - RootTagContext, // cc287c11 + RootTagContext, // 38bfc8f6 RootViewStyleProvider, // 267bc77f Runnable, // 2cb32c54 Runnables, // d3749ae1 @@ -5995,20 +6497,20 @@ export { ScaledSize, // 07e417c7 ScrollEvent, // 7d125b6d ScrollResponderType, // 8d21b9e2 - ScrollToLocationParamsType, - ScrollView, // 2beda4fc + ScrollToLocationParamsType, // d7ecdad1 + ScrollView, // 5da7e3dd ScrollViewImperativeMethods, // b0280545 ScrollViewProps, // 195ca6be ScrollViewPropsAndroid, // 84e2134b ScrollViewPropsIOS, // fb759b3a ScrollViewScrollToOptions, // 3313411e - SectionBase, - SectionList, // 568a8d2e - SectionListData, - SectionListProps, // d2fcd6f4 - SectionListRenderItem, // c118edfb - SectionListRenderItemInfo, // bd038703 - Separators, + SectionBase, // b376bddc + SectionList, // 23854a1b + SectionListData, // 119baf83 + SectionListProps, // 1c9565a0 + SectionListRenderItem, // 9600767c + SectionListRenderItemInfo, // 158b913b + Separators, // 6a45f7e3 Settings, // 2be0c61e Share, // e4591b32 ShareAction, // ead1004a @@ -6033,7 +6535,7 @@ export { TaskProvider, // 266dedf2 Text, // f6d197f4 TextContentType, // 239b3ecc - TextInput, // b3b654d7 + TextInput, // e47fde03 TextInputAndroidProps, // 3f09ce49 TextInputChangeEvent, // 380cbe93 TextInputContentSizeChangeEvent, // 5fba3f54 @@ -6069,10 +6571,10 @@ export { ViewPropsIOS, // 58ee19bf ViewStyle, // c0170ec0 VirtualViewMode, // 6be59722 - VirtualizedList, // d4e15eaa - VirtualizedListProps, - VirtualizedSectionList, // 486a3a9d - VirtualizedSectionListProps, + VirtualizedList, // 68c7345e + VirtualizedListProps, // cf03a29a + VirtualizedSectionList, // 9fd9cd61 + VirtualizedSectionListProps, // e136f7bb WrapperComponentProvider, // 9cf3844c codegenNativeCommands, // 628a7c0a codegenNativeComponent, // a733b8b6 diff --git a/packages/react-native/scripts/ios-prebuild/__tests__/hermes-test.js b/packages/react-native/scripts/ios-prebuild/__tests__/hermes-test.js index 61220678d60e..01a14a9df179 100644 --- a/packages/react-native/scripts/ios-prebuild/__tests__/hermes-test.js +++ b/packages/react-native/scripts/ios-prebuild/__tests__/hermes-test.js @@ -145,16 +145,29 @@ function populateExtractedFramework() { } test.each(['Debug', 'Release'])( - 'uses checked-in metadata with the 1000.0.0 RN package for %s', + 'builds checked-in source-sentinel metadata with the 1000.0.0 RN package for %s', async flavor => { properties = checkedInProperties; const version = metadata.HERMES_VERSION_NAME; expect(await prepareHermesArtifactsAsync('1000.0.0', flavor)).toBe( artifacts, ); - const url = releaseUrl(version, flavor.toLowerCase()); - expect(global.fetch.mock.calls).toEqual([[url, {method: 'HEAD'}], [url]]); - expect(readFileSync(versionFile, 'utf8')).toBe(`${version}-${flavor}`); + expect(version).toBe('1000.0.0'); + expect(global.fetch).not.toHaveBeenCalled(); + expect(hermesCommitAtMergeBase).toHaveBeenCalledWith(); + expect(execFileSync).toHaveBeenCalledWith( + 'bash', + [expect.stringContaining('build-ios-framework.sh')], + expect.objectContaining({ + env: expect.objectContaining({ + BUILD_TYPE: flavor, + RELEASE_VERSION: version, + }), + }), + ); + expect(readFileSync(versionFile, 'utf8')).toBe( + `source-0123456789abcdef0123456789abcdef01234567-${flavor}`, + ); expect(fs.existsSync(path.join(artifacts, 'hermes-ios.download'))).toBe( false, ); @@ -326,7 +339,9 @@ test('reuses only the matching Hermes version, flag and flavor cache', async () execFileSync.mockClear(); await prepareHermesArtifactsAsync('0.83.1', 'Debug'); expect(global.fetch).not.toHaveBeenCalled(); - expect(execFileSync.mock.calls.map(([command]) => command)).toEqual(['plutil']); + expect(execFileSync.mock.calls.map(([command]) => command)).toEqual([ + 'plutil', + ]); properties = 'HERMES_VERSION_NAME=123.4.58\nHERMES_V1_VERSION_NAME=234.5.67'; await prepareHermesArtifactsAsync('0.83.1', 'Debug'); expect(global.fetch).toHaveBeenCalledWith(releaseUrl('123.4.58')); From b62db850f25001644390d8836706b2a9ad92f1d0 Mon Sep 17 00:00:00 2001 From: Saad Najmi Date: Tue, 22 Sep 2026 18:46:34 -0700 Subject: [PATCH 3/3] fix(ci): align Flow annotations and Jest configuration Apply the independently reviewed branch-specific CI follow-up while preserving public-registry locks and the linear stack. --- scripts/js-api/build-types/buildApiSnapshot.js | 2 +- .../typescript/canonicalizeLocalPackageImports.js | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/scripts/js-api/build-types/buildApiSnapshot.js b/scripts/js-api/build-types/buildApiSnapshot.js index e60d756c57a8..cbbe5b4fbe64 100644 --- a/scripts/js-api/build-types/buildApiSnapshot.js +++ b/scripts/js-api/build-types/buildApiSnapshot.js @@ -254,7 +254,7 @@ async function rewriteLocalImports( async function getProcessedSnapshotResult( tempDirectory: string, options: BuildApiSnapshotOptions, - packages: $ReadOnlyArray<{directory: string, name: string}>, + packages: ReadonlyArray<{directory: string, name: string}>, ): Promise { const rollupPath = path.join( tempDirectory, diff --git a/scripts/js-api/build-types/transforms/typescript/canonicalizeLocalPackageImports.js b/scripts/js-api/build-types/transforms/typescript/canonicalizeLocalPackageImports.js index 9c97ce7f8118..2a71455d03f7 100644 --- a/scripts/js-api/build-types/transforms/typescript/canonicalizeLocalPackageImports.js +++ b/scripts/js-api/build-types/transforms/typescript/canonicalizeLocalPackageImports.js @@ -18,7 +18,7 @@ import type { function canonicalizeSource( source: string, - packageNames: $ReadOnlyArray, + packageNames: ReadonlyArray, ): string { if (!source.startsWith('./') && !source.startsWith('../')) { return source; @@ -37,8 +37,8 @@ function canonicalizeSource( } function canonicalizeLocalPackageImports( - packageNames: $ReadOnlyArray, -): PluginObj { + packageNames: ReadonlyArray, +): PluginObj { function canonicalizeNodeSource( nodePath: NodePath< ExportAllDeclaration | ExportNamedDeclaration | ImportDeclaration,