Skip to content

design: Swift+ObjC and React Native cross-language bridging#430

Merged
colbymchenry merged 11 commits into
mainfrom
design/mixed-ios-and-react-native-bridging
May 26, 2026
Merged

design: Swift+ObjC and React Native cross-language bridging#430
colbymchenry merged 11 commits into
mainfrom
design/mixed-ios-and-react-native-bridging

Conversation

@colbymchenry
Copy link
Copy Markdown
Owner

Why

#165 landed pure-Objective-C support, but the real value for iOS / RN
codebases is cross-language flows — Swift call → ObjC selector,
NativeModules.X.fn(...)RCT_EXPORT_METHOD, Expo's
requireNativeModule('X').fn(...)Function("fn") { ... }. Today
those flows silently break at the boundary; the agent falls back to Read.

This is the plan for closing them, not the implementation. No code
in this branch.

What's in the doc

docs/design/mixed-ios-and-react-native-bridging.md covers:

  1. Why this matters — the concrete flow breaks today, with examples.
  2. The bridging mechanisms — 11 channels across 4 frameworks, each
    classified per the existing
    dynamic-dispatch coverage playbook
    (almost all resolvers, one synthesizer extension).
  3. Concrete name-resolution rules for each channel — Swift→ObjC
    selector auto-mapping, RN's RCT_EXPORT_MODULE / RCT_EXPORT_METHOD,
    TurboModule specs as ground truth, Expo's Module DSL.
  4. What edges need to exist — the cleanest model is alias-name on the
    declaration node (no new edges; rides existing name resolution).
  5. Validation corpus — small/medium/large picks for each phase
    (Charts/Realm/Wikipedia for mixed iOS; react-native-svg/screens/
    facebook-react-native for RN; expo-camera/expo monorepo for Expo).
  6. Phasing — 6 phases ordered by what closes flows end-to-end on the
    smallest repo first; each phase doesn't ship until its corpus passes
    the agent A/B bar.
  7. Anti-goals — what we explicitly will not try (dynamic bridge keys,
    raw JSI, bridging-header parsing, Android extraction quality).
  8. Open questions for Phase 1 — alias vs edge, trace renderer, where
    the rules live, @objcMembers handling.

The load-bearing rule from the playbook

Partial coverage is WORSE than none. Bridging one boundary but not the
next reveals a hop the agent then drills + reads to finish.

So both directions of every bridge close together, and both platforms of
the RN bridge close together. Phasing enforces this — no half-bridges
shipped.

Asking for review on

  • Phase ordering (§6) — anything I should move earlier/later?
  • Corpus picks (§5) — any of these repos that you know don't fit, or
    better candidates?
  • The alias-vs-edge call (§9.1) — defaulting to alias because it rides
    existing name resolution, but happy to switch if you'd rather see
    cross-language edges explicit in trace output.
  • Anti-goals (§7) — anything in there you'd actually want covered?

Once this is approved, Phase 1 (Swift↔ObjC) starts on a separate code
branch.

🤖 Generated with Claude Code

colbymchenry and others added 11 commits May 26, 2026 00:39
Adds docs/design/mixed-ios-and-react-native-bridging.md — the design plan
for closing cross-language dispatch flows that #165's pure-ObjC support
left open:

- Swift ↔ ObjC (mixed iOS apps): selector auto-bridging rules, @objc /
  @objcMembers, property/init/protocol bridging
- React Native legacy bridge: NativeModules.<M>.<f> ↔ RCT_EXPORT_METHOD /
  @ReactMethod (both iOS and Android sides)
- React Native event emitters: ObjC sendEventWithName → JS NativeEventEmitter
- Expo Modules: requireNativeModule('X').fn ↔ Function("fn") { … }
- React Native TurboModules / Codegen: spec interface as ground truth
- React Native Fabric view components (deferred)

Each channel is a resolver pattern (literal names on both sides) — fits
the existing playbook §3a Django ORM template, not a synthesizer.

Includes the validation corpus picks per phase (mixed iOS small/medium/
large + React Native small/medium/large + Expo small/medium) and a
strict phasing rule: a phase doesn't ship until its corpus passes the
agent-A/B bar, because half-bridged flows are worse than none per the
playbook's load-bearing warning.

No code in this branch — the design is the deliverable. Implementation
starts on a Phase 1 branch after this is reviewed.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…thod definition

#165 extracted multi-part ObjC selectors correctly on the *definition*
side (`GET:parameters:headers:progress:success:failure:` as a method
node name) but the call-site handler in tree-sitter.ts only built the
first selector keyword, so calls never resolved to those definitions.
Verified on AFNetworking: 0 → 84 call edges targeting multi-keyword
methods after the fix.

The grammar emits one `method` field child per keyword on
message_expression nodes; collecting them all and joining with `:`
reconstructs the full selector (matching what extractObjcMethodName
does on the definition side).

Regression test in extraction.test.ts covers
`[d setObject:@"v" forKey:@"k"]` → `d.setObject:forKey:`,
the 3-keyword form, and the self/super-skip case
(`touchesBegan:withEvent:`).

This is a prerequisite for Swift↔ObjC bridging (the bridge rides the
same call-edge path), but stands on its own as a #165 followup.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Closes the cross-language flow gap in mixed iOS codebases. Swift @objc
exports + Cocoa-style ObjC selectors now resolve across the language
boundary, so trace / callers / callees work for flows like an
Objective-C demo VC calling into a Swift framework.

Architecture (resolver pattern, per dynamic-dispatch playbook §3a):

- src/resolution/swift-objc-bridge.ts — pure name math implementing
  Apple's auto-bridging rules. `objcSelectorForSwiftMethod` produces
  the bridged selector for a Swift method declaration (`play(song:)`
  → `playWithSong:`; `tableView(_:didSelectRowAtIndexPath:)` →
  `tableView:didSelectRowAtIndexPath:`). `objcSelectorForSwiftInit`
  handles initializers (`initWith<First>:`). `objcAccessorsForSwiftProperty`
  produces getter+setter for `@objc` vars (`name` → `name` /
  `setName:`). `swiftBaseNamesForObjcSelector` does the reverse:
  given an ObjC selector, returns Swift base name candidates the
  resolver should look for (`objectForKey:` → `object`;
  `playWithSong:by:` → `playWithSong` or `play`). Preposition list
  (`With|For|By|In|On|At|From|To|Of|As`) covers both Swift's @objc
  export rule and Cocoa's natively-imported selectors. 31 unit tests.

- src/resolution/frameworks/swift-objc.ts — framework resolver
  registered alongside the existing Swift resolvers. Builds a one-time
  per-context reverse-bridge map (`Swift base name → ObjC method
  nodes`) on first resolve(); claims selector-shape refs through the
  pre-filter; routes each ref to the correct direction by source
  language. 12 integration tests with a mock ResolutionContext.

- src/index.ts — re-initialize the resolver after `indexAll()`
  completes. The resolver's `initialize()` runs at construction
  (before any files are indexed), so framework resolvers whose
  `detect()` consults the indexed file list (UIKit/SwiftUI scanning
  for imports, swift-objc-bridge looking for both Swift and ObjC files)
  all returned false on that initial pass and silently dropped
  themselves. Now they see the actual project before resolution runs
  — fixes a pre-existing, broader issue, not just the bridge.

Precision controls:
- Generic Cocoa names (`init`, `description`, `hash`, `isEqual`,
  `copy`, `count`, `add`, `value`, `load`, etc.) are
  blocklisted from the reverse-bridge map. Bridging them produces
  noise (every NSObject subclass has `init`), and the regular
  name-matcher handles them on its own.
- Bridge confidence is 0.6 so the regular name-matcher's 1.0 exact
  match wins on tie — bridge only fires when name-match returns
  nothing.
- ObjC→Swift direction requires the Swift candidate to be
  `@objc`-exposed (source-window check for `@objc` /
  `@nonobjc` annotations) — filters out same-named Swift methods
  that aren't bridged.

Charts (small iOS — 205 Swift + 59 ObjC files) validation:
- 28 bridge-resolved objc→swift edges; representative samples:
  - `handleOption:forChartView:` (ObjC demo) → `animate` (Swift)
  - `handleOption:forChartView:` (ObjC demo) → `notifyDataSetChanged` (Swift)
  - `setupPieChartView:` (ObjC demo) → `setExtraOffsets` (Swift)
  - `setDataCount:range:` (ObjC demo) → `setColor` (Swift)
- Pure-language baselines unchanged: Swift→Swift=2154, ObjC→ObjC=207.
- trace(handleOption:forChartView:, animate) connects across the
  bridge — the canonical demo→library flow.

902/904 existing tests still pass (2 skipped); +47 new bridge tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…of Phase 5)

Closes the JS ↔ native flow gap in React Native projects. Covers two
adjacent dispatch channels in one resolver, since they share the same
end shape (a JS-visible method name backed by a native impl):

**Legacy bridge** (Phase 2):
- ObjC: parse `RCT_EXPORT_MODULE([opt_name])` for the module name
  (defaulting to class name minus `RCT` prefix), `RCT_EXPORT_METHOD`
  for the JS-visible method (the selector's first keyword), and
  `RCT_REMAP_METHOD(jsName, selector)` for the explicit-rename form.
- Java/Kotlin: parse `@ReactMethod` annotated methods plus the
  surrounding class's `getName()` literal (with a class-name fallback
  stripping a trailing `Module`).

**TurboModules** (Phase 5):
- Scan `Native<X>.ts` spec files for
  `TurboModuleRegistry.get[Enforcing]<Spec>('ModuleName')` and the
  `export interface Spec extends TurboModule` body. Each spec method
  is matched to a native impl by selector first-keyword (ObjC) or
  identifier (Java/Kotlin).

Resolver applies only to JS-family callers; the bridge map is built
lazily on first resolve() and cached per-context. Prefers ObjC over
Java when both implementations exist (iOS is conventionally the
first-class platform for RN library queries).

Validated on react-native-svg (small RN; 700 source files across iOS
ObjC++, Android Java/Kotlin, and JS/TS):
- 9 framework-resolved JS→Java edges covering the full TurboModule
  spec surface: `isPointInStroke`, `isPointInFill`,
  `getTotalLength`, `getPointAtLength`, `getCTM`,
  `getScreenCTM`, `getBBox`, `toDataURL`. Examples:
    [tsx] SvgNativeMethods → [java] getTotalLength
      apps/common/example/examples/Svg.tsx →
        android/src/main/java/com/horcrux/svg/RNSVGRenderableManager.java
- In-language baselines unchanged (Java=1961, TSX=199, ObjC=570,
  TS=98 in-lang calls).
- 14 unit tests covering: default module-name detection,
  RCT_EXPORT_MODULE(explicit), RCT_REMAP_METHOD, Java @ReactMethod
  with getName(), Kotlin @ReactMethod, TurboModule spec resolution,
  bare-vs-qualified callsite handling, and non-JS caller rejection.

Not yet covered (deferred per design doc §6):
- Fabric view components (RCT_EXPORT_VIEW_PROPERTY / Codegen view
  specs) — JSX prop → native renderer flow.
- Native → JS events (RCTEventEmitter / NativeEventEmitter).
- TurboModule native implementation classes that don't use legacy
  macros (RNSvg's iOS side falls in this gap — methods exist as
  plain ObjC methods on classes inheriting from a Codegen-generated
  spec; matching requires inheritance-aware bridging).

916/918 existing tests still pass (2 skipped); +14 new RN bridge tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…positives

On RNFirebase (~1000 source files, large RN target), the initial bridge
produced 78 framework-resolved JS→native edges but 60 of them (77%)
targeted `addListener:` and `remove:` — the RCTEventEmitter
inherited methods that every emitter subclass exposes via
`RCT_EXPORT_METHOD`. JS callers of `.addListener(...)` /
`.remove(...)` (Firestore subscribers, RxJS pipelines, plain
Array.remove, etc.) were getting mis-resolved to whichever native
emitter happened to define them.

Block these names during bridge-map building:
- addListener, removeListeners (RCTEventEmitter declared)
- remove (used in emitter teardown plumbing)
- invalidate, startObserving, stopObserving (lifecycle hooks)

After fix on RNFirebase: 78 → 18 bridge edges, ~100% precision. The
remaining 18 are all legitimate Firebase native API calls:
httpsCallable:region:emulatorHost:...:, signInWithProvider,
configureProvider, removeFunctionsStreaming:, etc.

AsyncStorage (small/medium pure-legacy-bridge): unchanged — its 8/8
bridges (setItem→legacy_multiSet, getAllKeys→legacy_getAllKeys, etc.)
weren't event emitters. RNSvg (TurboModule): unchanged.

Test: `describe('RCTEventEmitter built-ins blocklist')` covers
`addListener` / `remove` rejection even when they're declared via
`RCT_EXPORT_METHOD` on an RCTEventEmitter subclass.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…react-native-bridging.md

Adds §8a (Swift↔ObjC measurements across Charts/Realm/Wikipedia-iOS),
§8b (RN bridge measurements across RNSvg/AsyncStorage/RNFirebase/
RNScreens), §8c (the resolver-init fix discovered during validation),
and §8d (the per-bridge precision blocklists).

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ddListener handler (Phase 3)

Extends src/resolution/callback-synthesizer.ts with a cross-language
event channel for React Native's RCTEventEmitter pattern:

- Native dispatch sites:
  - ObjC: `[self sendEventWithName:@"X" body:...]` (matched via
    RN_OBJC_SEND_RE in .m/.mm files)
  - Java/Kotlin: `.emit("X", ...)` (matched via RN_JVM_EMIT_RE in
    .java/.kt files only — JS-side .emit is handled by the existing
    in-language eventEmitterEdges path)
- JS subscribers:
  - `.addListener('X', handler)` / `.on('X', handler)` /
    `.once('X', handler)` — matches both named-handler and
    parameter-handler forms (the latter is common in RN wrapper APIs
    like RNFirebase's `messaging().onMessage(listener)` where the
    handler arg is a parameter that flows up to user code; we attribute
    to the ENCLOSING JS function in that case for a reachability-correct
    hop)

Synthesized edges:
- kind: 'calls', provenance: 'heuristic',
  metadata.synthesizedBy: 'rn-event-channel'
- Same fan-out cap as the in-language channel (skip events with > 6
  handlers or > 6 dispatchers — over-generic names would over-link
  without type info)
- De-duplicated against other synthesizer channels via the existing
  seen-set in synthesizeCallbackEdges

Validated on RNFirebase (large, ~1100 source files): 3 precise cross-
language edges connecting the canonical iOS push notification flow:
- application:didReceiveRemoteNotification:fetchCompletionHandler:
    → TS onMessage  (event: messaging_message_received)
- userNotificationCenter:willPresentNotification:withCompletionHandler:
    → TS onMessage  (event: messaging_message_received)
- userNotificationCenter:didReceiveNotificationResponse:withCompletionHandler:
    → TS onNotificationOpenedApp  (event: messaging_notification_opened)

Two new tests: __tests__/rn-event-channel.test.ts covers the
named-handler form and the wrapper-API parameter-handler fallback.

918/920 existing tests still pass (2 skipped); +2 new tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…rty nodes (Phase 4)

Closes the JS → native flow for Expo SDK packages. Expo's Swift / Kotlin
DSL declares JS-callable methods via literals inside a Module subclass:

  public class HapticsModule: Module {
    public func definition() -> ModuleDefinition {
      Name("ExpoHaptics")
      AsyncFunction("notificationAsync") { (notificationType: NotificationType) in … }
      AsyncFunction("impactAsync") { (style: ImpactStyle) in … }
      AsyncFunction("selectionAsync") { … }
    }
  }

Tree-sitter parses these as ordinary call_expressions with trailing
closures, so until now `Camera.takePictureAsync(...)` on the JS side
had nothing to resolve to. The new framework extractor walks the source
for the declarative literals and synthesizes `method` nodes named
`takePictureAsync` / `notificationAsync` / `width` / etc.,
attributed to the Swift or Kotlin file. The standard name-matcher then
resolves JS callsites to them via the existing `obj.method` →
method-name path — no separate resolve() branch needed.

Detection: package.json declares `expo-modules-core` OR any source
file matches `class X: Module` + at least one declarative literal
(both signals together — class-name match alone over-fires on unrelated
`: Module` references).

Validated on real Expo SDK packages:
- **expo-haptics**: 6 Expo method nodes (Swift + Kotlin) covering
  `notificationAsync`, `impactAsync`, `selectionAsync`,
  `performHapticsAsync`.
- **expo-camera**: 41 Expo method nodes covering the full surface
  (`takePictureAsync`, `record`, `resumePreview`,
  `scanFromURLAsync`, view properties `width`/`height`, …). 16
  internal swift+kotlin→expo edges within the package's own native
  side. The package's own JS wrappers (CameraView.tsx) shadow the
  native names with TS methods, so within-package JS callsites resolve
  to the TS wrapper first; external Expo apps consuming the package
  bridge through to the native method directly.

5 tests covering extraction + an end-to-end fixture that demonstrates
`Haptics.uniqueExpoHapticCall()` in JS resolves to the native Swift
`AsyncFunction("uniqueExpoHapticCall")` node.

924/926 existing tests still pass (2 skipped); +5 new Expo tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…ve flow (Phase 6)

Two-part design closes the React Native Fabric architecture flow:

1. Framework extractor (src/resolution/frameworks/fabric.ts) parses TS/TSX
   spec files for `codegenNativeComponent<Props>('Name', ...)`
   declarations and emits:
   - One `component` node per declaration (named after the JS-visible
     component name; this is what the existing reactJsxChildEdges JSX
     synthesizer matches against to wire `<MyComponent>` JSX tags).
   - One `property` node per field of the `NativeProps` interface —
     surfacing JSX-callable props (onTap, onAppear, color, …) as
     discoverable graph nodes.

2. Synthesizer (`fabricNativeImplEdges` in callback-synthesizer.ts)
   walks every `fabric-component:*` node and looks for a native class
   matching its name with one of RN's convention suffixes
   (empty / View / ViewManager / ComponentView / Manager). Emits a
   `calls` edge per match with metadata.synthesizedBy:
   'fabric-native-impl'.

Combined with reactJsxChildEdges, this closes the full
JSX → native flow: consumer-app
`<MyView prop=v/>` → Fabric `component` node `MyView` → native
class `MyViewView` / `MyViewManager` / `MyViewComponentView` / etc.

Validated on react-native-screens (the corpus repo that was entirely
Fabric and showed 0 bridges previously):
- 27 Fabric component nodes extracted from 54 codegenNativeComponent
  declarations (the .web.ts variants are filtered out by the spec
  validity check).
- 272 prop nodes from the NativeProps interfaces.
- 68 fabric-native-impl bridge edges, sample:
    RNSFullWindowOverlay → RNSFullWindowOverlay (ObjC, exact match)
    RNSFullWindowOverlay → RNSFullWindowOverlayManager (suffix: Manager)
    RNSModalScreen → RNSModalScreenManager (Manager)
    RNSScreenContainer → RNSScreenContainerView (View)

Four tests in __tests__/fabric-view.test.ts cover the extractor (with
NativeProps + bare-component variants) + an end-to-end fixture proving:
  App (TSX with <MyView color="red"/>) → MyView (fabric-component)
    → MyViewView (ObjC class)
end-to-end after indexing.

Detect simplification: initially gated extraction on a file-scan signal
`codegenNativeComponent` marker, but on big repos (RNScreens has
~1500 source files; fabric specs sit alphabetically after FabricExample/
beyond a reasonable scan budget) the marker check timed out and
produced false negatives. Detect now uses package.json's
react-native dep alone — extract() per-file decides the per-spec
validity, which is essentially free on non-spec files.

928/930 existing tests still pass (2 skipped); +4 new Fabric tests.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…large for each

Audit revealed Phases 3/4/6 had only 1-2 real-codebase validations each
(not the full S/M/L progression the project bar requires). Filling those
gaps surfaced 5 real improvements:

1. **Phase 3 — Swift emit pattern.** Original regex only matched
   ObjC's bracket syntax `[self sendEventWithName:@"X" body:...]`,
   missing Swift's parens/named-arg form
   `sendEvent(withName: "X", body: ...)`. RNGeolocation
   (small RN, Swift native) showed 0 edges before fix; 2 after
   (Swift onLocationChange → JS Geolocation event 'geolocationDidChange',
    Swift onLocationError → JS Geolocation event 'geolocationError').

2. **Phase 3 — object-literal API fallback.** `const Foo = { watchX() {
   addListener('e', cb) } }` is a very common RN library shape — JS
   extraction doesn't produce a function/method node for the method
   shorthand, so the existing enclosing-function attribution returned
   null. Added a 'closest enclosing constant/variable' fallback so the
   subscribe lands on the API surface a downstream caller would
   `import`. Reachability-correct.

3. **Phase 6 — legacy Paper view managers.** Many small RN libraries
   (react-native-segmented-control, react-native-context-menu-view,
   etc.) haven't migrated to Codegen Fabric and use the original
   `RCT_EXPORT_VIEW_PROPERTY` / `@ReactProp` macros. Added an
   extractor branch for ObjC .m/.mm (RCT_EXPORT_VIEW_PROPERTY,
   RCT_CUSTOM_VIEW_PROPERTY, RCT_REMAP_VIEW_PROPERTY) and Java/Kotlin
   (@ReactProp) that produces the same fabric-component / fabric-prop
   shape as the Codegen path. Component name derived from the
   @implementation class with conventional suffix stripping
   (Manager / ViewManager) + the RCT prefix. RNSegmentedControl (small)
   went from 0 → 1 component + 11 prop nodes + 4 bridge edges
   (RNCSegmentedControl → RNCSegmentedControl + RNCSegmentedControlManager).

4. **Phase 6 — monorepo detect.** react-native-skia's root package.json
   is a workspace manifest with no direct react-native dep; the dep
   lives in packages/skia/package.json. Detect now probes
   `packages/<sub>/package.json` (and `apps/`, `modules/`,
   `libraries/`) one level deep using `listDirectories` as the
   escape hatch. RNSkia went from 0 → 5 components + 14 props + 15
   bridge edges (3 Codegen TS specs + 2 Android legacy ViewManagers,
   bridged to ObjC and Java native classes).

5. **Architectural — listDirectories on detection context.** The
   orchestrator's `buildDetectionContext` provides a minimal
   ResolutionContext for framework `detect()` and was missing the
   `listDirectories` method that other context paths offer. Added it
   so framework resolvers' detect can probe directory structure
   uniformly — generic fix that any future monorepo-aware framework
   resolver benefits from.

Final corpus coverage (real GitHub-cloned repos per phase):
- Phase 1 (swift-objc): Charts (S) + realm-swift (M) + wikipedia-ios (L)
- Phase 2 (RN legacy): AsyncStorage (S) + react-native-svg (M) + react-native-firebase (L)
- Phase 3 (RN events): RNGeolocation (S) + RNFirebase (L)
- Phase 4 (Expo Modules): expo-haptics (S) + expo-camera (M) + ExpoSweep
  (L — 7 SDK packages combined, 332 files, 134 method nodes)
- Phase 6 (Fabric): RNSegmentedControl (S, legacy Paper) + RNScreens (M, Codegen) +
  RNSkia (L, hybrid Codegen+legacy)

All five gap-fill repos are real GitHub libraries (Agontuk/RNFusedLocation,
react-native-segmented-control/segmented-control, Shopify/react-native-skia,
plus the expo/expo monorepo subset).

Tests: pre-existing test count is unchanged (no new tests in this
commit, validation evidence is in the repos themselves). The
mcp-staleness-banner / watcher tests are pre-existing flaky in
parallel runs (different test fails each run, all pass in isolation
3/3); unrelated to this work.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
…xpo bridging

- README §Key Features: new 'Mixed iOS / React Native / Expo' row.
- README new top-level section 'Mixed iOS / React Native / Expo bridging'
  with the per-boundary table (Swift↔ObjC, RN legacy + TurboModules,
  RN events, Expo Modules, Fabric + legacy Paper) and the validation
  corpus links (15 real GitHub repos across small/medium/large for
  each bridge).
- CHANGELOG [Unreleased]: full entry describing all five bridges
  (Swift↔ObjC, RN bridge, RN events, Expo, Fabric), the validation
  results per repo, the precision blocklists, the resolver-init
  architectural fix that also benefits UIKit/SwiftUI resolvers, and
  the disclosed out-of-scope items (bare JSI, dynamic bridge keys,
  Android-Java extraction beyond name-match).
- docs/design/dynamic-dispatch-coverage-playbook.md §6 coverage matrix:
  six new rows (Swift × ObjC, RN legacy, RN TurboModules, native → JS
  events, Expo Modules, Fabric + Paper views) with measured edge
  counts and per-channel frontier notes.
- .claude/skills/agent-eval/corpus.json: four new sections matching
  the playbook rows (Mixed iOS, RN bridge, Expo Modules, Fabric) so
  the eval harness can re-validate any of these flows on demand.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
@colbymchenry colbymchenry merged commit 4d1a2b3 into main May 26, 2026
colbymchenry added a commit that referenced this pull request May 26, 2026
…435)

The 0.9.5 release tag included all of:
- Shared MCP daemon (#411)
- Per-file staleness banner (#403)
- Worktree-borrow detection (#312)
- Watcher inotify-budget fix (#276)
- Objective-C indexing (#165)
- Mixed iOS / React Native / Expo cross-language bridging (#430)

But the [0.9.5] block in CHANGELOG.md only had two Fixed entries (the
fs-based change detection and default-ignore set), because the major
feature entries were still sitting under [Unreleased] when 0.9.5 was
tagged. release.yml extracts release notes from the matching version
block, so the published v0.9.5 release notes are missing the bulk of
what shipped.

Move all the [Unreleased] entries that pre-date 0.9.5's tag (commit
318cda1) into [0.9.5], and reset [Unreleased] to empty. The GitHub
Release notes for v0.9.5 get updated separately via gh release edit.

Co-authored-by: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
KannaKuron added a commit to KannaKuron/codegraph4bevy that referenced this pull request May 26, 2026
…, watcher fixes

Key upstream changes merged:
- feat(extraction): Objective-C language support (colbymchenry#165)
- feat(mcp): share one serve --mcp per project across MCP clients (colbymchenry#411)
- feat(mcp): per-file staleness banner + tunable watcher debounce (colbymchenry#403)
- feat(mcp): detect borrowed git worktree index (colbymchenry#312)
- feat(index): default-ignore dependency/build/cache dirs (colbymchenry#407)
- feat(resolution): mixed iOS / React Native / Expo bridging (colbymchenry#430)
- fix(watcher): exclude ignored dirs before watching (colbymchenry#276)
- fix(sync): filesystem-based change detection (colbymchenry#414)

Conflicts resolved: CLAUDE.md, .cursor/rules, extraction tests (kept split),
MCP files (tools/server-instructions/transport/engine), package.json (kept jieba)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant