Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 46 additions & 0 deletions skills/brownfield-navigation/SKILL.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: brownfield-navigation
description: Allows presenting existing native screens from React Native. Define the schema in TypeScript, run codegen to generate the bindings, invoke the function on the JS side, generate the XCFramework/AAR, and integrate native bindings such as the delegate into the host app.
license: MIT
metadata:
author: Callstack
tags: react-native, expo, brownfield, navigation
---

# Overview

Brownfield Navigation flows from a TypeScript contract (`brownfield.navigation.ts`) through `npx brownfield navigation:codegen`, which generates JS bindings, native stubs, and delegate protocols. Host apps implement `BrownfieldNavigationDelegate`, register it with `BrownfieldNavigationManager` at startup, then React Native code calls the generated `@callstack/brownfield-navigation` module.

# When to Apply

Reference these skills when:
- App uses brownfield setup
- Presenting existing native screen from React Native
- Configuring schema for brownfield navigation
- Implementing the brownfield navigation delegate

# Quick Reference

- Generate the files using codegen script:
```bash
npx brownfield navigation:codegen
```
- Brownfield packaging commands also run the same navigation codegen as part of packaging workflows:
```bash
# iOS
npx brownfield package:ios

# android
npx brownfield package:android
npx brownfield publish:android
```

## Routing (concern → file)

Concern | Read
--------|------
JS call sites, `BrownfieldNavigation.*` usage, `undefined is not a function`, params vs generated API | [`javascript-usage.md`](references/javascript-usage.md)
`BrownfieldNavigationDelegate`, `setDelegate` / `shared.setDelegate`, startup crashes, no-op / wrong native route | [`native-integration.md`](references/native-integration.md)
Contract placement, `BrownfieldNavigationSpec` / `Spec`, `navigation:codegen`, generated artifacts, stale or missing outputs | [`setup-codegen.md`](references/setup-codegen.md)


95 changes: 95 additions & 0 deletions skills/brownfield-navigation/references/javascript-usage.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,95 @@
# Brownfield Navigation JavaScript Usage

## Discoverability triggers

- "how to call BrownfieldNavigation from JS"
- "`undefined is not a function` on a Brownfield method"
- "JS method missing after updating `brownfield.navigation.ts`"
- "Brownfield JS method exists but opens wrong destination"

## Scope

In scope:
- Calling `BrownfieldNavigation.<method>()` from React Native code.
- JS call-site patterns for buttons/screens and parameter passing.
- Runtime troubleshooting for JS-facing failures (`undefined is not a function`, missing methods, API drift signals).
- Reminding users when contract changes require codegen and native rebuild.

Out of scope:
- Authoring `brownfield.navigation.ts` and codegen mechanics. For that, read [`setup-codegen.md`](setup-codegen.md) in this folder.
- Android/iOS delegate implementation and startup registration details. For that, read [`native-integration.md`](native-integration.md) in this folder.

## Procedure

1. Confirm readiness before discussing JS calls
- Native delegate registration must already be in place before JS uses the module.
- If registration/startup order is uncertain, read [`native-integration.md`](native-integration.md) in this folder.

2. Provide the default JS invocation pattern
- Import `BrownfieldNavigation` from `@callstack/brownfield-navigation`.
- Call generated methods directly from handlers (for example, button `onPress`).
- Keep method names and argument shape aligned with the generated API.

3. Recommend call-site best practices
- Pass stable explicit params (`userId`, IDs, flags), not transient UI-derived data.
- Keep each JS method call mapped to a clearly named native destination.
- Use simple direct calls first; avoid wrapping in unnecessary abstractions while debugging.

4. Apply troubleshooting flow for runtime failures
- `undefined is not a function`: method changed in spec but codegen/rebuild not reapplied.
- Native crash on call: likely delegate registration/startup ordering issue; hand off implementation details to [`native-integration.md`](native-integration.md) in this folder.
- Method exists but wrong route/no-op: generated method present, but native delegate wiring likely incorrect; route delegate fixes to [`native-integration.md`](native-integration.md) in this folder.

5. Enforce regeneration rule when JS/native API drift appears
- If method names, params, or return types changed in `brownfield.navigation.ts`, rerun:
`npx brownfield navigation:codegen`
- Then rebuild native apps before retesting JS calls.

6. Route non-JS root causes quickly
- Spec placement/signature/codegen output questions → [`setup-codegen.md`](setup-codegen.md) in this folder.
- Delegate implementation/registration/lifecycle questions → [`native-integration.md`](native-integration.md) in this folder.

## Minimal TSX example

Assume the generated contract includes a method like `openNativeProfile(userId: string): void`. The exact method names and params come from the generated `@callstack/brownfield-navigation` module after running codegen.

```tsx
import React from 'react';
import {Button, SafeAreaView} from 'react-native';
import BrownfieldNavigation from '@callstack/brownfield-navigation';

export function ProfileLauncherScreen(): React.JSX.Element {
return (
<SafeAreaView>
<Button
title="Open native profile"
onPress={() => {
BrownfieldNavigation.openNativeProfile('user-123');
}}
/>
</SafeAreaView>
);
}
```

Portable takeaways:
- Import the generated module from `@callstack/brownfield-navigation`.
- Call the generated method directly from a user action such as `onPress`.
- If this method is missing or throws `undefined is not a function`, treat it as a codegen/rebuild drift signal first.

## Quick reference

- Import: `import BrownfieldNavigation from '@callstack/brownfield-navigation'`
- Typical calls:
- `BrownfieldNavigation.navigateToSettings()`
- `BrownfieldNavigation.navigateToReferrals('user-123')`
- Regenerate on contract change: `npx brownfield navigation:codegen`
- Retest order:
1. Confirm contract shape
2. Regenerate
3. Rebuild native apps
4. Retest JS call sites
- Error cues this skill should handle first:
- `undefined is not a function` on a Brownfield method
- JS method missing after updating `brownfield.navigation.ts`
- JS call parameters appear out of sync with generated API
149 changes: 149 additions & 0 deletions skills/brownfield-navigation/references/native-integration.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,149 @@
# Brownfield Navigation Native Integration

## Discoverability triggers

- "implement `BrownfieldNavigationDelegate`"
- "where to call `BrownfieldNavigationManager.setDelegate(...)`"
- "where to call `BrownfieldNavigationManager.shared.setDelegate(...)`"
- "navigation call crashes at app startup or upon invocation"

## Scope

In scope:
- Implementing generated `BrownfieldNavigationDelegate` methods in Android and iOS host code.
- Registering the delegate with `BrownfieldNavigationManager` during app startup.
- Enforcing startup/lifecycle ordering (delegate registered before JS calls).
- Troubleshooting native wiring issues (crash/no-op/wrong route).

Out of scope:
- Authoring `brownfield.navigation.ts` and running codegen. For that, read [`setup-codegen.md`](setup-codegen.md) in this folder.
- JavaScript call-site usage patterns in RN screens. For that, read [`javascript-usage.md`](javascript-usage.md) in this folder.

## Procedure

1. Confirm prerequisites
- `BrownfieldNavigation.xcframework` is linked in the iOS host app.
- If applicable, use the artifact produced by `npx brownfield package:ios` in the current project. The exact output path can vary by package manager and workspace layout.
- `Gson` dependency is added to the Android host app.

2. Implement Android delegate
- Implement generated `BrownfieldNavigationDelegate` in the host `Activity` (or class with navigation context).
- Wire each generated method to the native destination and map params exactly.
- Typical implementation starts Android `Activity` instances with `Intent` extras.

3. Register Android delegate during startup
- Call `BrownfieldNavigationManager.setDelegate(...)` in startup flow (for example `onCreate`).
- Registration must happen before any React Native screen can call `BrownfieldNavigation.*`.

4. Implement iOS delegate
- Create a class conforming to `BrownfieldNavigationDelegate`.
- Wire each generated method to the intended native presentation flow (UIKit/SwiftUI).
- Ensure screen presentation runs on the main/UI thread.

5. Register iOS delegate during startup
- Call `BrownfieldNavigationManager.shared.setDelegate(navigationDelegate: ...)` at app startup (for example in app `init`).
- Registration must happen before RN-rendered routes can invoke module methods.

6. Enforce lifecycle requirements
- Delegate must be present before JS usage; missing delegate is a startup bug.
- Re-register if the host object owning the delegate is recreated.
- Keep navigation/presentation work on main thread.

7. Triage runtime integration failures
- Method added/changed in TS but missing natively: rerun `npx brownfield navigation:codegen` and rebuild.
- Crash on launch or first method call: verify delegate registration order vs RN route rendering.
- Method exists but wrong destination/no-op: verify delegate implementation wiring and parameter mapping.

## Minimal native examples

Assume the generated contract includes a method like `openNativeProfile(userId: string): void`. The generated delegate method name and parameter types come from the current project's `brownfield.navigation.ts`.

### Kotlin example

Use this pattern when the host screen or activity owns Android navigation context:

```kotlin
import android.content.Intent
import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.callstack.nativebrownfieldnavigation.BrownfieldNavigationDelegate
import com.callstack.nativebrownfieldnavigation.BrownfieldNavigationManager

class MainActivity : AppCompatActivity(), BrownfieldNavigationDelegate {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
BrownfieldNavigationManager.setDelegate(this)
}

override fun openNativeProfile(userId: String) {
val intent = Intent(this, ProfileActivity::class.java).apply {
putExtra("userId", userId)
}
startActivity(intent)
}
}
```

Portable takeaways:
- Implement the generated `BrownfieldNavigationDelegate`.
- Register the delegate before any React Native code can call `BrownfieldNavigation.*`.
- Map each generated method to an explicit native destination and pass params through unchanged.

### Swift example

Use this pattern when an app-level object can own navigation setup and present UIKit or SwiftUI flows:

```swift
import BrownfieldNavigation
import UIKit

final class AppNavigationDelegate: BrownfieldNavigationDelegate {
func openNativeProfile(userId: String) {
DispatchQueue.main.async {
guard let rootViewController = UIApplication.shared.connectedScenes
.compactMap({ $0 as? UIWindowScene })
.flatMap(\.windows)
.first(where: \.isKeyWindow)?
.rootViewController else {
return
}

let viewController = ProfileViewController(userId: userId)
rootViewController.present(viewController, animated: true)
}
}
}

final class AppDelegate: UIResponder, UIApplicationDelegate {
private let navigationDelegate = AppNavigationDelegate()

func application(
_ application: UIApplication,
didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]? = nil
) -> Bool {
BrownfieldNavigationManager.shared.setDelegate(navigationDelegate: navigationDelegate)
return true
}
}
```

Portable takeaways:
- Keep a strong reference to the delegate for as long as React Native might call it.
- Register the delegate during startup, before RN screens that use Brownfield navigation are shown.
- Perform presentation on the main thread and keep the implementation focused on routing plus param mapping.

## Quick reference

- Android delegate type: `BrownfieldNavigationDelegate`
- Android registration: `BrownfieldNavigationManager.setDelegate(...)`
- iOS delegate type: `BrownfieldNavigationDelegate`
- iOS registration: `BrownfieldNavigationManager.shared.setDelegate(navigationDelegate: ...)`
- Integration order:
1. Generate/update contract outputs
2. Implement delegate methods natively
3. Register delegate at startup
4. Render RN routes that call `BrownfieldNavigation.*`
- Error cues this skill should address:
- Crashes when JS calls navigation methods early
- Missing delegate registration at startup
- Wrong native screen opened from a JS call
92 changes: 92 additions & 0 deletions skills/brownfield-navigation/references/setup-codegen.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
# Brownfield Navigation Setup and Codegen

## Discoverability triggers

- "where to put `brownfield.navigation.ts`"
- "run brownfield navigation codegen"
- "generated files missing after codegen"
- "spec changes not reflected in generated APIs"

## Scope

In scope:
- Authoring and updating `brownfield.navigation.ts` in the React Native app root.
- Enforcing supported signature rules (`BrownfieldNavigationSpec`/`Spec`, typed params, optional params, `void`-first guidance).
- Running `npx brownfield navigation:codegen`.
- Explaining generated artifacts and when regeneration + native rebuild are required.
- First-pass setup/codegen triage (missing files, stale API after spec changes).

Out of scope:
- Android/iOS delegate implementation and registration details. For that, read [`native-integration.md`](native-integration.md) in this folder.
- JavaScript screen usage patterns and runtime call ergonomics. For that, read [`javascript-usage.md`](javascript-usage.md) in this folder.

## Procedure

1. Confirm prerequisites
- `@callstack/brownfield-navigation` is installed. Otherwise, install the latest version
- Babel deps used by codegen are available (`@babel/core`, `@react-native/babel-preset`). Otherwise, install the compatible version OR ask the user.

2. Verify contract file placement and shape
- File name is exactly `brownfield.navigation.ts`.
- File is in the React Native app root.
- Interface is `BrownfieldNavigationSpec` (or `Spec`).

3. Validate method signatures before codegen
- Method names are valid TypeScript identifiers.
- Typed params and optional params are allowed.
- Prefer synchronous `void` navigation methods.
- Warn that Promise-based methods are not currently supported by generated native implementations on iOS/Android; they may compile but reject with `not_implemented`.

4. Choose the right codegen invocation
- Default form: `npx brownfield navigation:codegen`
- Explicit path form: `npx brownfield navigation:codegen <specPath>`
- Use the default form when your current working directory is the React Native app root and the contract file is the default `./brownfield.navigation.ts`.
- Use the explicit-path form when you are running from another directory, when the app lives inside a workspace/monorepo, or when you want to remove ambiguity about which spec file should be parsed.
- Relative `specPath` values are resolved from the directory where you run the command; absolute paths also work.

5. Understand the artifact root before verifying outputs
- Codegen writes into the installed `@callstack/brownfield-navigation` package root, not into the app directory that contains `brownfield.navigation.ts`.
- The exact absolute location depends on the consumer's package manager and workspace layout. It may live under a local `node_modules` tree, a hoisted workspace dependency, or another package-store-managed install location.
- Treat the package root as the stable anchor, then verify these generated relative paths beneath it:
- `src/NativeBrownfieldNavigation.ts`
- `src/index.ts`
- `lib/commonjs/index.js`
- `lib/module/index.js`
- `lib/typescript/commonjs/src/index.d.ts`
- `lib/typescript/module/src/index.d.ts`
- `ios/BrownfieldNavigationDelegate.swift`
- `ios/BrownfieldNavigationModels.swift` when complex model types are generated
- `ios/NativeBrownfieldNavigation.mm`
- Android files under `android/src/main/java/<generated-package>/`, including `BrownfieldNavigationDelegate.kt`, `NativeBrownfieldNavigationModule.kt`, and `BrownfieldNavigationModels.kt` when complex model types are generated

6. Enforce rerun/rebuild rule
- Any change that affects the contract surface in `brownfield.navigation.ts` requires rerunning codegen, then rebuilding native apps.
- This includes adding, removing, renaming, or retyping methods; changing params or optionality; and introducing/removing model types used by params.
- If JavaScript can no longer see a generated method, or native code still behaves like the old contract, assume regeneration or rebuild was skipped.
- Safe order:
1) update the contract
2) rerun codegen
3) rebuild iOS and/or Android before retesting

7. Handoff when issue is outside setup/codegen
- Delegate lifecycle/startup order: [`native-integration.md`](native-integration.md) in this folder.
- JS invocation/runtime call-site guidance: [`javascript-usage.md`](javascript-usage.md) in this folder.

## Quick reference

- Primary commands:
- `npx brownfield navigation:codegen`
- `npx brownfield navigation:codegen <specPath>`
- Contract file: `brownfield.navigation.ts` at React Native app root
- Default command behavior: reads `./brownfield.navigation.ts` from the current working directory
- Output location: generated files are written under the resolved `@callstack/brownfield-navigation` package root
- Parser-supported interface names: `BrownfieldNavigationSpec` or `Spec`
- Safe default return type: `void`
- Important order:
1. Define/update TS contract
2. Run codegen
3. Rebuild native apps
- Error cues this skill should address:
- Generated files missing or stale after spec edits
- JS/native surfaces not reflecting renamed/added methods
- Promise-based navigation method behaving as `not_implemented`
Loading