-
Notifications
You must be signed in to change notification settings - Fork 36
feat: brownfield navigation skills #303
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 2 commits
Commits
Show all changes
4 commits
Select commit
Hold shift + click to select a range
File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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. Defines the schema in Typescript, run the codegen to generate the bindings, add the function invocation on the JS side. Generate the xcframework/AAR, then add the native bindings like delegate to the native 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) | | ||
|
hurali97 marked this conversation as resolved.
Outdated
|
||
|
|
||
|
|
||
95 changes: 95 additions & 0 deletions
95
.agents/skills/brownfield-navigation/references/javascript-usage.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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
149
.agents/skills/brownfield-navigation/references/native-integration.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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 prerequisite | ||
|
hurali97 marked this conversation as resolved.
Outdated
|
||
| - `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.brownfieldnavigation.BrownfieldNavigationDelegate | ||
| import com.callstack.brownfieldnavigation.BrownfieldNavigationManager | ||
|
hurali97 marked this conversation as resolved.
Outdated
|
||
|
|
||
| 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
92
.agents/skills/brownfield-navigation/references/setup-codegen.md
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| 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`). | ||
|
hurali97 marked this conversation as resolved.
Outdated
|
||
|
|
||
| 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` | ||
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.