# Natives Binding Contract (JavaScript/TypeScript Side)

This document defines the JS/TS contract between `@oh-my-pi/pi-natives` callers and the loaded N-API addon.

Current package shape is direct-to-native: there is no `packages/natives/src/<module>` TypeScript wrapper layer. The public API is the generated `packages/natives/native/index.d.ts` declaration file, the ESM loader/export wrapper in `packages/natives/native/index.js`, and the Rust `#[napi]` exports in `crates/pi-natives/src`.

## Implementation files

- `packages/natives/native/index.js`
- `packages/natives/native/index.d.ts`
- `packages/natives/native/loader-state.js`
- `packages/natives/scripts/build-native.ts`
- `packages/natives/scripts/gen-enums.ts`
- `packages/natives/package.json`
- `crates/pi-natives/src/lib.rs`
- Rust modules under `crates/pi-natives/src/*.rs`

## Contract model

The contract has three parts:

1. **ESM runtime loader/export wrapper** (`native/index.js`)
   - calls `loadNative()` from `loader-state.js`, which `require(...)`s the `.node` addon;
   - binds generated classes/functions as explicit named ESM exports;
   - emits enum runtime objects generated by `scripts/gen-enums.ts`.
2. **Generated TypeScript declarations** (`native/index.d.ts`)
   - generated by napi-rs during `scripts/build-native.ts`;
   - declares exported functions, classes, object interfaces, and native enums;
   - is the package `types` entry.
3. **Rust N-API exports** (`crates/pi-natives/src`)
   - `#[napi]` functions/classes/objects/enums are the source of generated declarations and runtime symbols;
   - snake_case Rust names become camelCase JavaScript names by napi-rs convention.

There is no current `NativeBindings` declaration-merging lifecycle and no full required-export list in the loader. Install/compiled loads do validate the package-version sentinel export; workspace-dev loads skip that check.

## Public export surface organization

`packages/natives/package.json` exposes the package root only:

```json
{
  "main": "./native/index.js",
  "types": "./native/index.d.ts",
  "exports": {
    ".": {
      "types": "./native/index.d.ts",
      "import": "./native/index.js"
    }
  }
}
```

Consumers in `packages/coding-agent` and `packages/tui` import directly from `@oh-my-pi/pi-natives`.

## JS API ↔ native export mapping (representative)

| Category          | Public JS API                                                                                             | Rust source                                      | Return style               |
| ----------------- | --------------------------------------------------------------------------------------------------------- | ------------------------------------------------ | -------------------------- |
| Grep              | `grep(options, onMatch?)`                                                                                 | `grep.rs`                                        | `Promise<GrepResult>`      |
| Grep              | `search(content, options)`                                                                                | `grep.rs`                                        | `SearchResult`             |
| Grep              | `hasMatch(content, pattern, ignoreCase?, multiline?)`                                                     | `grep.rs`                                        | `boolean`                  |
| Fuzzy path search | `fuzzyFind(options)`                                                                                      | `fd.rs`                                          | `Promise<FuzzyFindResult>` |
| Glob/workspace    | `glob(options, onMatch?)`, `listWorkspace(options)`                                                       | `glob.rs`, `workspace.rs`                        | `Promise<...>`             |
| Glob cache        | `invalidateFsScanCache(path?)`                                                                            | `fs_cache.rs`                                    | `void`                     |
| AST/block/summary | `astGrep(options)`, `astMatch(options)`, `astEdit(options)`, `blockRangeAt(options)`, `enclosingBlockBoundaries(options)`, `summarizeCode(options)` | `ast.rs`, `block.rs`, `summary.rs`               | mixed                      |
| Shell             | `executeShell(options, onChunk?)`                                                                         | `shell.rs`                                       | `Promise<ShellRunResult>`  |
| Shell             | `new Shell(options?)`, `shell.run(...)`, `shell.abort()`                                                  | `shell.rs`                                       | class / promises           |
| Shell             | `applyBashFixups(command)`                                                                                | `shell.rs`                                       | `BashFixupResult`          |
| PTY               | `new PtySession()`, `start/write/resize/kill`                                                             | `pty.rs`                                         | class / promises           |
| Process           | `Process.fromPid/fromPath`, `status/children/killTree/terminate/waitForExit`                              | `ps.rs`                                          | class / mixed              |
| Keys              | `parseKey`, `matchesKey`, Kitty/legacy helpers                                                            | `keys.rs`                                        | sync                       |
| Text              | `wrapTextWithAnsi`, `truncateToWidth`, `sliceWithWidth`, `extractSegments`, `visibleWidth`                | `text.rs`                                        | sync                       |
| Highlight         | `highlightCode`, `supportsLanguage`, `getSupportedLanguages`                                              | `highlight.rs`                                   | sync                       |
| HTML              | `htmlToMarkdown(html, options?)`                                                                          | `html.rs`                                        | `Promise<string>`          |
| SIXEL             | `encodeSixel`                                                                                             | `sixel.rs`                                       | sync                       |
| Snapcompact       | `renderSnapcompactPng(text, options)`                                                                     | `snapcompact.rs`                                 | sync                       |
| Clipboard         | `copyToClipboard`, `readImageFromClipboard`                                                               | `clipboard.rs`                                   | sync / promise             |
| Tokens            | `countTokens(input, encoding?)`                                                                           | `tokens.rs`                                      | sync                       |
| System/isolation  | `detectMacOSAppearance`, `MacAppearanceObserver`, `MacOSPowerAssertion`, `getWorkProfile`, `iso*` helpers | `appearance.rs`, `power.rs`, `prof.rs`, `iso.rs` | mixed                      |

## Sync vs async contract differences

The contract preserves Rust/N-API call style:

- **Promise-returning exports** for worker-thread or async runtime work (`grep`, `glob`, `fuzzyFind`, `astGrep`, `astMatch`, `astEdit`, `htmlToMarkdown`, shell/PTY runs, `isoStart`/`isoStop`/`isoDiff`, clipboard image read, workspace scan).
- **Synchronous exports** for deterministic in-memory transforms/parsers or direct system calls (`search`, `hasMatch`, highlighting, text utilities, token counting, process construction/status, `copyToClipboard`, `encodeSixel`, isolation probe/resolve helpers).
- **Constructor exports** for stateful runtime objects (`Shell`, `PtySession`, `Process`, macOS observer/power handles).

Changing sync ↔ async for an existing export is a breaking public API change because consumers call these exports directly.

## Object and enum typing patterns

### Object patterns

`#[napi(object)]` Rust structs become TS interfaces, for example:

- `GrepResult`, `SearchResult`, `GlobResult`, `FuzzyFindResult`
- `ShellRunResult`, `PtyRunResult`, `MinimizerResult`
- `AstFindResult`, `AstReplaceResult`, `BlockRange`, `SummaryResult`
- `System`/media/isolation payloads such as `ClipboardImage`, `WorkProfile`, `ParsedKittyResult`, `IsoResolveResult`

Runtime shape correctness is owned by napi-rs and the Rust implementation.

### Enum patterns

Native enums are represented in generated declarations and also emitted as runtime objects by `scripts/gen-enums.ts`, because napi-rs string enums are TS-only without explicit JS exports. Current enum objects include:

- `AstMatchStrictness`
- `Ellipsis`
- `Encoding`
- `FileType`
- `GrepOutputMode`
- `IsoBackendKind`
- `IsoChangeKind`
- `KeyEventType`
- `MacOSAppearance`
- `ProcessStatus`

## Error behavior and caveats

- Addon load failure or unsupported platform throws during package import from `native/index.js`.
- The loader rejects install/compiled candidates that lack the package-version sentinel export. It does not verify the full export set after `require(...)`; stale same-version or incomplete binaries surface as native load errors or missing members at use sites.
- N-API conversion validates basic argument conversion, but TS optional fields do not guarantee semantic validity for untyped callers.
- Numeric enum declarations do not prevent out-of-range numeric values from untyped callers unless the Rust function rejects them during conversion.
- Callback exports use napi-rs `ThreadsafeFunction` shape: `(error: Error | null, value) => void`. Native code generally emits successful values; hard failures reject/throw through the owning call.

## Maintainer checklist for binding changes

When adding/changing an export, update all of:

1. Rust `#[napi]` implementation in the owning `crates/pi-natives/src/<module>.rs`.
2. `crates/pi-natives/src/lib.rs` if a new module is added.
3. Any consumer imports/callsites in `packages/coding-agent` or `packages/tui`.
4. Build output by running the natives build so `native/index.d.ts` and `native/index.js` stay in sync.
5. `scripts/gen-enums.ts` if enum runtime export patching needs to change.

Do not add a parallel TS wrapper convention unless the package design intentionally moves back to wrappers; current consumers depend on the direct generated API.
