| name | flow |
| description | End-to-end process for shipping a feature or bug fix in react-native-audio-api. Covers all required deliverables in order: Web Audio API spec review, TypeScript interface and types, C++ AudioNode implementation, HostObject wiring, TypeScript class, TurboModule spec (when needed), C++ tests (Google Test), JS tests (Jest), documentation (audiodocs MDX), and post-work checks. Also covers the bug-fix flow: MRE first, C++ test when applicable, root-cause analysis, post-mortem. Use this skill at the start of any feature implementation or bug fix. Trigger phrases: "implement a feature", "add a node", "fix a bug", "what steps", "where do I start", "PR checklist", "how to write tests".
|
Skill: Feature Implementation Flow
Quick Reference
Feature checklist (all 9 steps required for a PR):
- Read spec → define exactly what you're building
- TypeScript interface + option types (
src/interfaces/, src/types/)
- C++ AudioNode (
core/<category>/MyNode.h/.cpp) — see audio-nodes skill
- HostObject (
HostObjects/MyNodeHostObject.h/.cpp) — see host-objects skill
- TypeScript class (
src/core/MyNode.ts)
- TurboModule spec entry — only if a new RN native method is needed
- C++ tests (Google Test) — write tests first, confirm they fail, then implement
- JS/TS tests (Jest)
- Docs page (
packages/audiodocs/)
Then: post-work-checks skill
Bug fix checklist:
- Reproduce (write failing test or MRE first)
- Identify the layer (use the table in §Bug Fix Flow)
- Fix + verify tests pass
- Post-mortem (update skill if a new pitfall discovered)
Feature Implementation Flow
1 — Define exactly what you are building
Before touching any code. If spec behavior is unclear → fetch https://webaudio.github.io/web-audio-api/ before proceeding.
- Read the Web Audio API spec section for the node/feature. Nail down:
- All properties and methods
- Parameter default values, min/max, and automation behaviour
- Edge cases (what happens when
start() is called twice? what if feedback[0] === 0?)
- Decide the TypeScript interface shape: what does the JS consumer see?
- Identify if native platform code is needed (e.g. new permission, device routing, new Oboe/CoreAudio API surface). If yes, plan iOS + Android separately.
2 — TypeScript interface and types
Files: packages/react-native-audio-api/src/
-
Add the interface in src/interfaces/ — IMyNode.ts
export interface IMyNode extends IAudioNode {
readonly myParam: IAudioParam;
someMethod(arg: number): void;
}
-
Add the options type in src/types/ — NodeOptions.ts (or add to existing file)
export interface MyNodeOptions extends AudioNodeOptions {
myParam?: number;
}
-
Export both from src/interfaces/index.ts and src/types/index.ts.
3 — C++ AudioNode
Files: packages/react-native-audio-api/common/cpp/audioapi/core/
See the audio-nodes skill for the full contract. If unsure which base class to use → check the class hierarchy in that skill. Summary:
- Create
core/<category>/MyNode.h and MyNode.cpp.
- Subclass the right base (
AudioNode, AudioScheduledSourceNode, AudioBufferBaseSourceNode).
- Annotate every method with
// JS-thread only or // Audio-thread only.
- Declare
processNode() in protected: — audio thread.
- Preallocate all
AudioParams and scratch buffers in the constructor (JS thread).
- Add
createMyNode(const MyNodeOptions &options) factory to BaseAudioContext.
- Add the
.cpp file to CMakeLists.txt (check existing entries for the pattern).
No allocations, no locks, no blocking I/O in processNode().
4 — HostObject
Files: packages/react-native-audio-api/common/cpp/audioapi/HostObjects/
See the host-objects skill for the full macro system and shadow state patterns. If unsure whether to use shadow state or atomics → check the decision table in that skill. Summary:
- Create
HostObjects/MyNodeHostObject.h and MyNodeHostObject.cpp.
- Extend
AudioNodeHostObject.
- Expose properties with
JSI_PROPERTY_GETTER/SETTER_DECL/IMPL + JSI_EXPORT_PROPERTY_GETTER/SETTER.
- Expose methods with
JSI_HOST_FUNCTION_DECL/IMPL + JSI_EXPORT_FUNCTION.
- Use shadow state (JS-thread copy +
scheduleAudioEvent) for any property that: is read back by JS, is written from JS, AND must be applied on the audio thread. See host-objects.md for the decision table.
- Wire factory in
BaseAudioContextHostObject: add createMyNode host function that calls context->createMyNode(options) and wraps in MyNodeHostObject.
- Add to
CMakeLists.txt.
5 — TypeScript class
Files: packages/react-native-audio-api/src/core/
-
Create src/core/MyNode.ts — extends AudioNode, wraps the JSI HostObject.
import { IMyNode } from '../interfaces';
import AudioNode from './AudioNode';
import BaseAudioContext from './BaseAudioContext';
import { MyNodeOptions } from '../types';
export default class MyNode extends AudioNode implements IMyNode {
constructor(context: BaseAudioContext, options: MyNodeOptions) {
const node = context.context.createMyNode(options);
super(context, node);
}
}
-
Add a factory method createMyNode(options?) to src/core/BaseAudioContext.ts.
-
Export from src/index.ts.
-
Add web passthrough in src/web-core/MyNode.ts if the node has a browser equivalent, or add a stub/throw if not supported on web. The web system always delegates to the browser's AudioContext.
6 — TurboModule spec (only if new native entry point needed)
File: src/specs/NativeAudioAPIModule.ts
Only needed if the feature requires a new RN native method (e.g. a permission check, a new audio session configuration). Most audio nodes do not need this — they are exposed entirely through JSI HostObjects.
If needed: add the method signature to the spec, then implement on iOS (AudioAPIModule.mm) and Android (AudioAPIModule.kt).
7 — Tests
C++ tests (Google Test)
Path: common/cpp/test/src/core/<category>/MyNodeTest.cpp
Pattern:
#include <audioapi/core/OfflineAudioContext.h>
#include <audioapi/core/effects/MyNode.h>
#include <gtest/gtest.h>
#include <test/src/MockAudioEventHandlerRegistry.h>
#include <memory>
using namespace audioapi;
class MyNodeTest : public ::testing::Test {
protected:
std::shared_ptr<MockAudioEventHandlerRegistry> eventRegistry;
std::shared_ptr<OfflineAudioContext> context;
static constexpr int sampleRate = 44100;
void SetUp() override {
eventRegistry = std::make_shared<MockAudioEventHandlerRegistry>();
context = std::make_shared<OfflineAudioContext>(
2, 5 * sampleRate, sampleRate, eventRegistry, RuntimeRegistry{});
context->initialize();
}
};
class TestableMyNode : public MyNode {
public:
explicit TestableMyNode(std::shared_ptr<BaseAudioContext> ctx)
: MyNode(ctx, MyNodeOptions()) {}
std::shared_ptr<AudioBuffer> processNode(
const std::shared_ptr<AudioBuffer> &buf, int frames) override {
return MyNode::processNode(buf, frames);
}
};
TEST_F(MyNodeTest, CanBeCreated) { ... }
TEST_F(MyNodeTest, ProcessesAudioCorrectly) { ... }
TEST_F(MyNodeTest, EdgeCase) { ... }
New test files are picked up automatically by GLOB_RECURSE — no CMakeLists edit needed.
Run with:
yarn test
JS/TS tests (Jest)
Path: packages/react-native-audio-api/tests/
integration.test.ts — graph construction, node creation, property access through the mock API
mock.test.ts — mock implementation correctness
Run with:
yarn workspace react-native-audio-api test
Example app (fabric-example)
If the feature has a visible or audible component, add a demo in apps/fabric-example/App.tsx (or a sub-screen). This acts as a manual smoke test and documents intended usage.
8 — Documentation
Path: packages/audiodocs/
Every public API that ships must have a documentation page. The docs package uses MDX. Look at an existing node's page for the format. Cover:
- Constructor / factory call
- All properties and their types/ranges
- All methods with parameter descriptions
- A minimal usage example (TypeScript snippet)
- Any spec deviations or limitations (e.g. "offline rendering not supported")
9 — Post-work checks
See the post-work-checks skill. At minimum before any PR:
yarn format
yarn lint
yarn typecheck
yarn test
yarn check-audio-enum-sync
Bug Fix Flow
1 — Reproduce (MRE first)
- If an MRE is provided → good, use it as your starting point.
- If no MRE → reproduce in
apps/fabric-example/App.tsx. Add the minimal code that triggers the bug. Keep it in the app until the fix is verified, then remove or clean it up.
- If the bug is in C++ DSP/processing logic → write a failing C++ test first (
common/cpp/test/). This gives a fast edit-compile-run loop without needing a device, and the test stays as a permanent regression guard.
2 — Identify the layer
| Symptom | Likely layer | Where to look |
|---|
| Wrong default value / validation error | TypeScript | src/core/MyNode.ts, src/types/ |
| Property read-back returns wrong value | HostObject shadow state | HostObjects/MyNodeHostObject |
| Wrong argument parsed from JS | HostObject | argument parsing in get/call |
| Audio output sounds wrong | C++ processNode() | core/<category>/MyNode.cpp |
| DSP math wrong | DSP helpers | dsp/ utilities |
| Crash on audio thread | Thread safety violation | cross-thread patterns — audio-nodes.md |
| Platform-specific (only iOS or only Android) | Native layer | ios/ or android/ |
| Only in new RN architecture | TurboModule / JSI wiring | src/specs/, HostObject init |
3 — Fix and verify
- Make the fix.
- If a C++ test was written in step 1 → it must now pass.
- If no C++ test was written but the fix touches
processNode() or DSP code → add one now. It will stay as a regression test.
- Run post-work checks (see above).
4 — Post-mortem (short, but do it)
After the fix is working, spend 2 minutes asking:
- Why did this bug exist? Was it a missing validation? A wrong default value? A thread-safety assumption that wasn't documented?
- Should a skill file be updated? If the bug revealed a non-obvious invariant (e.g. "feedback[0] must not be 0"), add it to the relevant skill (
audio-nodes.md, host-objects.md, etc.) under a pitfalls or constraints section.
- Should a new test be added? If no test existed that would have caught this, add one.
- Should docs be updated? If the spec says X but the implementation did Y, and the fix brings it in line — update the docs page.
These small post-mortems compound over time and prevent the same class of bug from recurring.
Maintenance: see maintenance.md.