Back to Body Journal
SOURCE / PINNED RELEASE

Made of little things.

Body Journal

Release
e8049f8da4e2…
Author-recorded commit
61c04bf0e8aa…
License
LICENSE
Author’s source reference
nostr://npub182jczunncwe0jn6frpqwq3e0qjws7yqqnc3auccqv9nte2dnd63scjm4rf/wss%3A%2F%2Fgit.napplet.soy%2F/n-6c4554844c7

Archive hash verified: 4369d778da046a8b…. The source-to-build association is the author’s claim; it has not been independently rebuilt.

.claude/skills/napplet-interop/SKILL.md
---
name: napplet-interop
description: Use only when a napplet talks to other napplets - NAP-INC topics (inc.emit / inc.on), NAP-INTENT dispatch (intent.invoke / intent.open), archetype manifest metadata, and the stable queryless convention identity rule (napplet:<archetype>/<intent>). Covers the emit-only query shorthand, payload validation, and what must stay exact-match. Skip for napplets that never emit, subscribe, or dispatch.
---

# Inter-Napplet Conventions (INC, INTENT, archetypes)

Load this only when the design spec names a cross-napplet feature: opening a note/profile/DM in another napplet, exposing an archetype role, broadcasting to sibling napplets. Protocol truth is the living [NAP-INC](https://github.com/napplet/naps/blob/master/naps/NAP-INC.md) (draft history in [PR #89](https://github.com/napplet/naps/pull/89)) and [NAP-INTENT](https://github.com/napplet/naps/blob/master/naps/NAP-INTENT.md) documents. If a needed behavior is not there, flag the gap; do not invent it.

## Convention identity

One convention is one stable, **queryless** string: `napplet:<archetype>/<intent>`, e.g. `napplet:note/open`, `napplet:profile/open`, `napplet:dm/open`. It is an identifier, not a payload schema and not a version.

- Subscriptions, manifest discovery, and handler routing use **exact** matching on that identity. No query parsing, prefix, wildcard, canonicalization, payload-schema, or multi-convention matching anywhere in those paths.
- Never put query text in manifest metadata or in a subscription topic.
- Payload shape is a local choice per convention. Every received payload is untrusted: validate it against the real upstream convention when one exists; never infer a schema from the topic name and never recreate numbered payload contracts.

## Archetype metadata (manifest)

In soyLI, put archetypes and accepted intents in napplet.json as described in docs/napplet-manifest.md. This upstream example describes an archetype contract; do not copy it into the pinned Vite plugin:

```ts
archetypes: [{ slug: 'note', convention: 'napplet:note/open' }],
```

This emits the manifest tag `["z", "note"]` and `["i", "napplet:note/open"]`. The convention's archetype segment matches a `z` role on the same manifest for dispatch eligibility; trailing `i` values advertise parameter names. Do not add payload, version, or negotiation fields.

## NAP-INC — broadcast between napplets

```ts
import { inc } from '@napplet/sdk';

inc.emit('napplet:profile/open', { pubkey: 'abc123' });

const sub = inc.on('napplet:profile/open', (event) => {
  if (!isValidProfileOpenPayload(event.payload)) return;   // untrusted input
  openProfile(event.payload);
});
sub.close();
```

`inc.on` delivers one `IncEvent` with the exact topic, a runtime-attested sender, and an optional payload.

### Emit-only query shorthand

Outbound `inc.emit(topic, payload?)` may take a queried convention URI as developer shorthand:

```ts
inc.emit('napplet:profile/open?pubkey=abc123');
// runtime sends { type: 'inc.emit', topic: 'napplet:profile/open', payload: { pubkey: 'abc123' } }
```

The runtime percent-decodes the shallow text query into the payload (`+` stays a literal plus) and routes the **queryless** topic, which subscribers match exactly. This is outbound preprocessing only, not a routing rule. These throw synchronously: a fragment, malformed percent encoding, a repeated decoded name, and a query combined with an explicit payload. For structured or non-text data use a queryless topic plus an explicit payload object.

The deprecated `ifc` SDK subpath is only an INC compatibility alias; new code imports `inc`.

## NAP-INTENT — ask the shell to route an action

```ts
import { intent } from '@napplet/sdk';

const result = await intent.open('napplet:profile/open', { payload: { pubkey: 'abc123' }, behavior: { reuse: false } });
if (!result.ok) showIntentError(result.error);
```

- Dispatch with `intent.invoke(uri, options?)` or the `intent.open(uri, options?)` convenience.
- An `ok: true` result accepts delivery responsibility; failures contain `error`. Targets register `onDelivery` and validate the runtime-attested payload. Behavior hints: `focus`, `reuse`.
- The URI derives archetype and action; target delivery is independent of INC and source lifetime.

## Spec lines to fill in

Add these to the `napplet-design` spec when interop is in scope:

```
archetype metadata: <none | slug + napplet:<archetype>/<intent>>
INC topics: <none | exact topic(s) emitted / subscribed + payload validation source>
intent dispatch: <none | invoke/open request + how acceptance and onDelivery drive the UI>
```

## Pitfalls

- Subscribing to a queried topic (`inc.on('napplet:profile/open?pubkey=…')`) — never matches; subscribe to the queryless identity.
- Treating a topic as a schema (`if (topic.endsWith('/open')) payload.pubkey…`) — validate the payload itself.
- Adding `inc` or `intent` to local `requires` (published as `R`) for an optional handoff — gate on `window.napplet?.inc` / `?.intent` and hide the affordance when absent.
- Inventing delivery identifiers, acknowledgement lifecycles, or version negotiation around intents — not in the living spec; use the acceptance result and onDelivery contract.

## soyLI manifest metadata

Before publication or changing capabilities/discovery, read docs/napplet-manifest.md. Save metadata in napplet.json; the publisher creates the signed event. Advertise only implemented intents and optional integrations, and verify their absent-domain fallbacks.