diff --git a/skills/brownie/SKILL.md b/skills/brownie/SKILL.md new file mode 100644 index 00000000..cfa7e6e5 --- /dev/null +++ b/skills/brownie/SKILL.md @@ -0,0 +1,51 @@ +--- +name: brownie +description: Provides guidance for shared state in React Native brownfield apps with @callstack/brownie. Define stores in TypeScript, run brownfield codegen, use the generated APIs in React Native and native hosts, and package native artifacts for integration. +license: MIT +metadata: + author: Callstack + tags: react-native, expo, brownfield, state-management, brownie +--- + +# Overview + +Brownie provides cross-platform shared state for React Native brownfield apps. The workflow starts from `*.brownie.ts` store definitions, runs `brownfield codegen` (or packaging commands that include codegen), then uses typed APIs on TypeScript, Android, and iOS sides. + +# When to Apply + +Reference this skill when: +- Setting up `@callstack/brownie` in an existing brownfield app +- Defining or changing store schemas in `*.brownie.ts` +- Using `useStore`, `subscribe`, `getSnapshot`, or `setState` on the JS side +- Registering and consuming stores in Android or iOS hosts +- Packaging and embedding iOS frameworks that include Brownie + +# Quick Reference + +- Install Brownie: +```bash +npm install @callstack/brownie +``` +- Generate native types: +```bash +npx brownfield codegen +``` +- Packaging commands also include Brownie codegen: +```bash +# iOS +npx brownfield package:ios --scheme YourScheme --configuration Release + +# Android +npx brownfield package:android --module-name :YourModuleName --variant release +npx brownfield publish:android --module-name :YourModuleName +``` + +## Routing (concern -> file) + +Concern | Read +--------|------ +Initial install, prerequisites, setup sequence, first integration pass | [`getting-started.md`](references/getting-started.md) +`*.brownie.ts` schema rules, `BrownieStores` augmentation, type mapping, when to rerun codegen | [`store-definition-and-codegen.md`](references/store-definition-and-codegen.md) +`useStore` selectors, update patterns, low-level APIs (`subscribe`, `getSnapshot`, `setState`) | [`typescript-usage.md`](references/typescript-usage.md) +Android registration lifecycle, serializer options, package settings, build and publish flow | [`android-usage.md`](references/android-usage.md) +Swift store registration and UI usage, plus XCFramework packaging and embedding | [`swift-and-xcframework.md`](references/swift-and-xcframework.md) diff --git a/skills/brownie/references/android-usage.md b/skills/brownie/references/android-usage.md new file mode 100644 index 00000000..3e1a5317 --- /dev/null +++ b/skills/brownie/references/android-usage.md @@ -0,0 +1,83 @@ +# Brownie Android Usage + +## Discoverability triggers + +- "registerStoreIfNeeded brownie" +- "Brownie Android setup package.json kotlin path" +- "Brownie custom serializer kotlin" +- "brownfield package android brownie publish" + +## Scope + +In scope: +- Android-side store registration lifecycle. +- Brownie Android package configuration and Gson dependency requirements. +- Easy path and advanced serializer options in Kotlin. +- Packaging and publish flow for AAR distribution. + +Out of scope: +- Initial installation and first setup checklist. For that, read [`getting-started.md`](getting-started.md) in this folder. +- Store schema authoring and codegen file design. For that, read [`store-definition-and-codegen.md`](store-definition-and-codegen.md) in this folder. +- React Native hook-level patterns. For that, read [`typescript-usage.md`](typescript-usage.md) in this folder. +- iOS Swift and XCFramework guidance. For that, read [`swift-and-xcframework.md`](swift-and-xcframework.md) in this folder. + +## Procedure + +1. Confirm Android configuration + - `package.json` includes a `brownie` block with: + - `kotlin` output directory + - `kotlinPackageName` + +2. Ensure serializer dependency strategy + - Brownie Android default serializer path requires Gson at runtime. + - Hard requirement: do not complete integration unless `com.google.code.gson:gson` is present in the brownfield module or native app dependency graph. + +3. Generate/build/publish artifacts + - Build AAR with: + - `npx brownfield package:android --module-name :YourModuleName --variant release` + - Publish to local Maven with: + - `npx brownfield publish:android --module-name :YourModuleName` + - `package:android` also runs Brownie codegen before packaging. + +4. Register store at startup + - Use `registerStoreIfNeeded` once during startup to avoid duplicate registration. + - Keep registration before native UI or JS flow assumes store availability. + - `registerStoreIfNeeded` returns `null` if the store key is already registered. + +5. Access and update typed store + - Retrieve with `StoreManager.shared.store(STORE_NAME)`. + - Read from `store.state`, update via `store.set { ... }`, observe via `store.subscribe(...)`. + +6. Use advanced serializers only when required + - Use `brownieStoreDefinition(..., serializer = ...)` or `encode`/`decode` lambdas. + - Keep decode/encode logic deterministic and version aware for backward compatibility. + +## Compact Kotlin example + +```kotlin +import com.callstack.brownie.StoreManager +import com.callstack.brownie.registerStoreIfNeeded +import com.callstack.brownie.store +import com.rnapp.brownfieldlib.AppStore + +registerStoreIfNeeded(storeName = AppStore.STORE_NAME) { + AppStore(counter = 0.0) +} + +val store = StoreManager.shared.store(AppStore.STORE_NAME) +store?.set { state -> state.copy(counter = state.counter + 1) } +``` + +## Quick reference + +- Startup registration: `registerStoreIfNeeded(storeName) { initialState }` +- Store lookup: `StoreManager.shared.store(STORE_NAME)` +- Required runtime dependency: `com.google.code.gson:gson` +- Packaging: + - `npx brownfield package:android --module-name :YourModuleName --variant release` + - `npx brownfield publish:android --module-name :YourModuleName` +- Error cues this guide handles: + - Store not found due to missing registration + - `ClassNotFoundException: com.google.gson.Gson` due to missing Gson dependency + - Decode/encode issues in custom serializer path + - Generated classes not found due to wrong `kotlinPackageName` or output path diff --git a/skills/brownie/references/getting-started.md b/skills/brownie/references/getting-started.md new file mode 100644 index 00000000..c057f4b1 --- /dev/null +++ b/skills/brownie/references/getting-started.md @@ -0,0 +1,80 @@ +# Brownie Getting Started + +## Discoverability triggers + +- "install @callstack/brownie" +- "set up Brownie in brownfield app" +- "first Brownie store setup" +- "how to start Brownie on iOS or Android" + +## Scope + +In scope: +- Prerequisites and initial installation. +- First store definition and app entry import. +- Platform handoff sequence for iOS and Android setup. +- First-pass verification that state sync works. + +Out of scope: +- Deep schema modeling details and codegen internals. For that, read [`store-definition-and-codegen.md`](store-definition-and-codegen.md) in this folder. +- Advanced JS runtime patterns and low-level TypeScript APIs. For that, read [`typescript-usage.md`](typescript-usage.md) in this folder. +- Platform-specific deep dives. For Android details, read [`android-usage.md`](android-usage.md). For iOS details, read [`swift-and-xcframework.md`](swift-and-xcframework.md) in this folder. + +## Procedure + +1. Confirm prerequisites + - React Native brownfield setup is already complete. + - `@callstack/react-native-brownfield` is installed. + - Native host app(s) exist for iOS and/or Android. + +2. Install Brownie in the React Native app + - Run `npm install @callstack/brownie`. + +3. Define the first store + - Create a file ending in `.brownie.ts`. + - Declare an interface extending `BrownieStore`. + - Add module augmentation for `BrownieStores`. + +4. Ensure augmentation is loaded + - Import the `.brownie.ts` file from the app entry (`App.tsx` or `index.js`). + +5. Add first TypeScript usage + - Use `useStore('StoreName', selector)` in a component. + - Perform one update via object or callback updater. + +6. Platform handoff + - iOS path: + 1. Run `npx brownfield package:ios --scheme YourScheme --configuration Release`. + 2. Confirm package output contains your module XCFramework plus `Brownie.xcframework`, `ReactBrownfield.xcframework`, and Hermes (`hermesvm.xcframework` or `hermes.xcframework`). + 3. Embed generated frameworks as `Embed & Sign`. + 4. Register the store in app startup. + - Android path: + 1. Configure `brownie` settings in `package.json` (`kotlin`, `kotlinPackageName`). + 2. Ensure Gson dependency exists in brownfield module or app. + 3. Run `npx brownfield package:android --module-name :YourModuleName --variant release`. + 4. Run `npx brownfield publish:android --module-name :YourModuleName`. + 5. Register store during startup with `registerStoreIfNeeded`. + - Note: both `package:ios` and `package:android` run Brownie codegen as part of packaging. + +7. Verify sync + - Update state from React Native and confirm native UI observes updates. + - Update from native and confirm React Native reflects changes. + - If one side is stale after schema edits, rerun packaging/codegen and rebuild host apps. + - Android runtime gate: + - If launch fails with `ClassNotFoundException: com.google.gson.Gson`, add `implementation("com.google.code.gson:gson:")` to brownfield module or host app and rebuild. + +## Quick reference + +- Install: `npm install @callstack/brownie` +- Store file pattern: `*.brownie.ts` +- Codegen/build entry points: + - `npx brownfield codegen` + - `npx brownfield package:ios --scheme YourScheme --configuration Release` + - `npx brownfield package:android --module-name :YourModuleName --variant release` + - `npx brownfield publish:android --module-name :YourModuleName` +- iOS packaging outputs (CLI docs): + - `ios/.brownfield/package/build/` +- Error cues this guide handles first: + - Store file exists but typed APIs are missing + - Native setup done but store not registered at startup + - First state update does not appear on one side diff --git a/skills/brownie/references/store-definition-and-codegen.md b/skills/brownie/references/store-definition-and-codegen.md new file mode 100644 index 00000000..bc724524 --- /dev/null +++ b/skills/brownie/references/store-definition-and-codegen.md @@ -0,0 +1,92 @@ +# Brownie Store Definition and Codegen + +## Discoverability triggers + +- "where to put .brownie.ts files" +- "BrownieStores module augmentation" +- "brownfield codegen for brownie" +- "generated Swift or Kotlin types are stale" + +## Scope + +In scope: +- Authoring `*.brownie.ts` stores and augmentation rules. +- Supported schema patterns and type mapping expectations. +- Running `brownfield codegen` and knowing when to rerun. +- Verifying generated artifacts and resolving common drift issues. + +Out of scope: +- Initial installation and first integration sequence. For that, read [`getting-started.md`](getting-started.md) in this folder. +- React component usage ergonomics (`useStore`, selectors, updates). For that, read [`typescript-usage.md`](typescript-usage.md) in this folder. +- Platform registration and runtime integration details. For Android, read [`android-usage.md`](android-usage.md). For iOS, read [`swift-and-xcframework.md`](swift-and-xcframework.md) in this folder. + +## Procedure + +1. Validate store file shape + - File name ends with `.brownie.ts`. + - Store interface extends `BrownieStore`. + - `declare module '@callstack/brownie'` augments `BrownieStores`. + - Store key in `BrownieStores` matches intended public store name. + +2. Confirm schema expectations before generation + - Scalars (`number`, `string`, `boolean`) are supported. + - Nested object types are supported and generate native structs/classes. + - Union string literals generate native enum types across platforms; for example, `theme: 'light' | 'dark'` emits Kotlin and Swift enum types (such as `Theme`), with generated enum/type names following codegen naming and casing conventions rather than remaining raw string fields. + +3. Ensure augmentation is loaded by TypeScript + - Import each `.brownie.ts` file from app entry point. + +4. Run codegen + - Primary command: `npx brownfield codegen`. + - Optional platform scope (if needed by CLI config): `npx brownfield codegen -p swift`. + - Discovery rule: the CLI recursively scans the project for `*.brownie.ts` files (excluding `node_modules`). + +5. Verify generated outputs + - Swift output root: `node_modules/@callstack/brownie/ios/Generated/`. + - Confirm store type files exist and include store name bindings/protocol conformance. + - Android generation is driven by brownfield Android packaging flow; confirm generated Kotlin output path aligns with package configuration. + +6. Apply rerun and rebuild rule + - Any schema surface change requires rerunning codegen. + - If app behavior still reflects old schema, rebuild native artifacts after codegen. + +7. Triage common failures + - Missing generated files: confirm file extension and augmentation block. + - Types not updated: verify app entry imports and rerun codegen from project root. + - Runtime mismatch after schema edits: rerun codegen and rebuild platform artifacts. + +## Compact schema example + +```ts +import type {BrownieStore} from '@callstack/brownie'; + +interface AppStore extends BrownieStore { + counter: number; + user: {name: string}; +} + +declare module '@callstack/brownie' { + interface BrownieStores { + AppStore: AppStore; + } +} +``` + +## Quick reference + +- File contract: + - `*.brownie.ts` + - `interface X extends BrownieStore` + - `declare module '@callstack/brownie' { interface BrownieStores { ... } }` +- Type mapping (most common): + - `number` -> `Double` (Swift) + - `string` -> `String` + - `boolean` -> `Bool` + - nested object -> generated native type +- Commands: + - `npx brownfield codegen` + - `npx brownfield codegen -p swift` +- Regeneration triggers: + - Added/removed store + - Renamed key or changed field types + - Nested model changes diff --git a/skills/brownie/references/swift-and-xcframework.md b/skills/brownie/references/swift-and-xcframework.md new file mode 100644 index 00000000..e82accd1 --- /dev/null +++ b/skills/brownie/references/swift-and-xcframework.md @@ -0,0 +1,100 @@ +# Brownie Swift Usage and XCFramework Packaging + +## Discoverability triggers + +- "register brownie store in iOS app startup" +- "UseStore swiftui brownie" +- "brownie uikit subscribe storemanager" +- "Brownie.xcframework embed and sign" + +## Scope + +In scope: +- Registering stores in iOS app startup. +- SwiftUI `@UseStore` patterns and UIKit subscription patterns. +- XCFramework packaging output and embedding flow for Brownie-enabled modules. +- Common packaging and runtime wiring checks. + +Out of scope: +- Initial installation and first setup sequence. For that, read [`getting-started.md`](getting-started.md) in this folder. +- Android integration specifics. For that, read [`android-usage.md`](android-usage.md) in this folder. +- TypeScript store schema and codegen troubleshooting. For that, read [`store-definition-and-codegen.md`](store-definition-and-codegen.md) in this folder. +- React Native hook-level usage patterns. For that, read [`typescript-usage.md`](typescript-usage.md) in this folder. + +## Procedure + +1. Package iOS artifacts + - Run `npx brownfield package:ios --scheme YourScheme --configuration Release`. + - Locate generated XCFrameworks under `ios/.brownfield/package/build/` + - Confirm output includes: + - `.xcframework` + - `Brownie.xcframework` + - `ReactBrownfield.xcframework` + - `hermesvm.xcframework` (or `hermes.xcframework` on older RN) + +2. Embed frameworks into native app + - Add all generated frameworks to the Xcode project. + - Set each to `Embed & Sign` in target settings. + +3. Register stores at startup + - Create initial typed state. + - Call `YourStore.register(initialState)` during app initialization before usage. + +4. Apply SwiftUI usage pattern + - Use `@UseStore(\Store.field)` for reactive selected slices. + - Update with projected binding (`$value`) and `set` helper when needed. + +5. Apply UIKit usage pattern + - Resolve typed store from `StoreManager`. + - Use `subscribe` and store the cancellation closure. + - Unsubscribe on deinit or view lifecycle teardown. + +6. Troubleshoot common iOS integration failures + - Missing symbols/import failures: verify all frameworks are embedded and signed. + - No updates in UI: confirm store registration occurred before first read. + - Old schema behavior: rerun codegen/package and replace embedded frameworks. + - `No such module 'Brownie'`: verify `Brownie.xcframework` is in the target and set to `Embed & Sign`. + +## Compact Swift examples + +```swift +import Brownie +import SwiftUI + +struct CounterView: View { + @UseStore(\AppStore.counter) var counter + + var body: some View { + Button("Increment") { + $counter.set { $0 + 1 } + } + } +} +``` + +```swift +import Brownie + +let store = StoreManager.get(key: AppStore.storeName, as: AppStore.self) +let cancel = store?.subscribe(\.counter) { value in + print("Counter: \(value)") +} +``` + +## Quick reference + +- iOS package command: `npx brownfield package:ios --scheme YourScheme --configuration Release` +- Required frameworks to embed: + - `.xcframework` + - `Brownie.xcframework` + - `ReactBrownfield.xcframework` + - `hermesvm.xcframework` (or `hermes.xcframework`) +- Swift APIs: + - `YourStore.register(initialState)` + - `@UseStore(\YourStore.someField)` + - `StoreManager.get(key:as:)` + - `store.subscribe(...)` +- Error cues this guide handles: + - `No such module 'Brownie'` + - Store reads return nil due to missing registration + - UI never re-renders because subscription or selector path is incorrect diff --git a/skills/brownie/references/typescript-usage.md b/skills/brownie/references/typescript-usage.md new file mode 100644 index 00000000..d0b316a5 --- /dev/null +++ b/skills/brownie/references/typescript-usage.md @@ -0,0 +1,84 @@ +# Brownie TypeScript Usage + +## Discoverability triggers + +- "how to use useStore with brownie" +- "Brownie selector rerender behavior" +- "update nested state with setState" +- "subscribe getSnapshot setState brownie" + +## Scope + +In scope: +- `useStore` usage and selector patterns in React Native. +- Update styles (partial object and callback updater). +- Nested update patterns that preserve object shape. +- Low-level APIs: `subscribe`, `getSnapshot`, `setState`. + +Out of scope: +- Installation, prerequisites, and first-pass setup sequence. For that, read [`getting-started.md`](getting-started.md) in this folder. +- Store schema authoring and codegen troubleshooting. For that, read [`store-definition-and-codegen.md`](store-definition-and-codegen.md) in this folder. +- Android/iOS native registration and host usage details. For Android, read [`android-usage.md`](android-usage.md). For iOS, read [`swift-and-xcframework.md`](swift-and-xcframework.md) in this folder. + +## Procedure + +1. Confirm setup assumptions + - `@callstack/brownie` is installed. + - Store definitions are imported so typings are available. + +2. Use `useStore` with explicit selector + - Select only the state slice needed by a component. + - Treat selectors as render boundaries (smaller selection means fewer rerenders). + - `useStore` expects a selector; selecting whole state is valid but broadens rerender scope. + +3. Pick an update pattern + - Partial object updates for direct field assignment. + - Callback updates when derived from previous state. + - For nested objects, spread previous nested values to avoid dropping sibling keys. + +4. Apply low-level APIs when hooks are not enough + - Use `subscribe` for non-React or service-layer listeners. + - Use `getSnapshot` for point-in-time reads. + - Use `setState` to apply updates outside components. + +5. Troubleshoot JS-side drift + - Type errors on store key/fields often indicate missing augmentation import. + - Runtime mismatch with native side after schema edits typically means codegen/rebuild was skipped. + - If APIs changed, rerun `npx brownfield codegen` or platform packaging, then rebuild host apps. + +## Compact examples + +```tsx +import {useStore} from '@callstack/brownie'; + +const [counter, setState] = useStore('AppStore', (s) => s.counter); +setState((prev) => ({counter: prev.counter + 1})); +``` + +```ts +import {subscribe, getSnapshot, setState} from '@callstack/brownie'; + +const unsubscribe = subscribe('AppStore', () => { + const snapshot = getSnapshot('AppStore'); + console.log(snapshot.counter); +}); + +setState('AppStore', (prev) => ({counter: prev.counter + 1})); +unsubscribe(); +``` + +## Quick reference + +- Hook: `useStore(storeKey, selector)` +- Updaters: + - `setState({field: value})` + - `setState((prev) => ({...}))` +- Low-level APIs: + - `subscribe(storeKey, onChange)` + - `getSnapshot(storeKey)` + - `setState(storeKey, patchOrUpdater)` +- Error cues this guide addresses: + - Unexpected rerenders from broad selectors + - Nested field updates losing sibling state + - TS key mismatch after adding new stores + - JS updates apply locally but native host remains stale