# Publishing a node-pack

Once your `.nodepack.json` validates locally (every kind shows in the picker + compiles cleanly), publish to the Hub for others to install. Two paths — local-test then publish, or smoke-test in a fresh project first.

## Local test loop

The fastest iteration loop bypasses the Hub entirely:

```
1. packs.node.install { manifest: <your full .nodepack.json object> }
2. packs.list         { kind: "node" }                → confirm packId, isBundled:false
3. documents.add        { type: "builder", title: "Pack test" }        → module_id
4. builder.graph.node.add       { module_id, kind: "<your_kind_id>" }          → node id
5. builder.graph.node.add       { module_id, kind: "output" }                  → out id
6. builder.graph.connect        { module_id, source_node_id: <yours>, target_node_id: <out>, target_port: "in_l" }
7. dsp.compile        { module_id }
8. builder.source.get     { module_id }   → verify your toBox emits what you expect
```

(Not a click-chain — steps 1, 4 and 6 need YOUR pack's manifest + kind ids; ask the assistant to run the loop against your file.)

If compile fails, the error string includes the libfaust diagnostic — usually a missing param ref, an arity mismatch, or a typo in a `lib()` call. Fix the `.nodepack.json`, `packs.node.install` again (writes overwrite atomic), retry.

## Publishing to the Hub

When the local loop is happy:

```
hub.publish {
  kind: "node-pack",
  file_path: "asset:assets/my-pack.nodepack.json",
  slug: "my-pack",
  title: "My Pack — short tagline",
  description: "Longer description for the Hub item page."
}
```

The Hub backend's `NodePackInspector` validates the manifest server-side + extracts derived metadata (kindCount, packId, ownerHandle). On success you get back the item's `id` — record it for follow-up version uploads.

### Subsequent versions

```
hub.version.upload {
  kind: "node-pack",
  item_id: "<id from the publish response>",
  file_path: "asset:assets/my-pack-v2.nodepack.json"
}
```

The Hub computes the new version number automatically (existing-version + 1). The local pack store refuses to downgrade (incoming version must be ≥ installed) — bump the `version` field in the manifest on every upload.

### Forking + iterating

Hub items can be forked: `hub.fork { item_id }` creates a copy in your namespace. Useful for "I want to base my pack on `faust-vocal` but add a new vocal effect" — fork, edit, publish under your handle.

## Kb-pack-style packs as a doc surface

If your node-pack covers a meaningful domain (a vocal effects suite, a granular synth toolkit, a vintage gear bundle), pair it with a **book** documenting the kinds. The book lives separately on the Hub (`hub.publish kind:"book"`) but Users find both when searching for the domain.

The Book Reader module's tutorial blocks can link to your kinds with `companionPatches` references + assistant hints. See **FaustWave — Hub** §3 for the publish flow.

## Bundling for FaustWave team packs

If you're shipping a FaustWave-team pack that should auto-restore on launch:

1. Drop the `.nodepack.json` in `packages/app/build-resources/bundled-node-packs/`.
2. Add the packId to `BUNDLED_PACK_IDS` in `packages/app/electron/node-pack-store.ts`.
3. Bump the `version` field — `restoreBundledPacks` compares against installed and only re-writes when the bundled file is newer.

Community packs DON'T go in `BUNDLED_PACK_IDS`. They stay Hub-only — users install them on demand.

## Common rejection paths

The validator + adapter at install time check for the most common authoring traps. If any fail, the install rejects + emits a per-line error in Logs:

- **Unknown port `id` referenced from `input('xyz')`** — the input port `xyz` doesn't exist in `inputs[]`.
- **Unknown param `id` referenced from `param('xyz')`** — same for params.
- **Forward def ref** — `def('A')` referenced before `defs.A` is declared (defs resolve in declaration order).
- **Arity mismatch in `seq(a, b)`** — `a`'s output arity != `b`'s input arity.
- **Missing `toBox.outputs.<port>`** — every output port needs a key. Sinks (zero outputs) use the reserved `__process` key.
- **Bare-name kind not in scope** — `prim('XYZ', …)` where `XYZ` isn't in stdfaust + the kind doesn't list the relevant library in `imports`. Add the lib to the kind's `imports` field.
- **`imports` entry not ending in `.lib`** — the validator rejects entries that don't end in the `.lib` suffix.

## Where to read more

- **Source of truth** — `packages/extension-dsp-builder/src/lib/packKindData.ts` carries the `NodeKindData` validator + the `validateNodeKindData()` function that runs at install time. The validator's error strings are precise; read it when in doubt.
- **Adapter** — `packKindAdapter.ts` walks `toBox` and produces the runtime `NodeKindDef`. Read it to understand the evaluation order + the substitution env.
- **Examples** — every file under `packages/app/build-resources/bundled-node-packs/` is real production code. Look at the simpler ones first (`faust-physical-models`, `faust-spatial`) for clean templates.
- **Architecture** — `docs/NODE-PACK-ARCHITECTURE-ZIEL.md` for the bundled-vs-Hub classification and the long-term direction.
