Back to Impact Yard
SOURCE / PINNED RELEASE

Made of little things.

Impact Yard

Release
09fe6aebec3c
Author-recorded commit
0696a4211733…
License
LICENSE
Author’s source reference
nostr://npub1n8ga89w8h6tvwamxusfyzexw8gjy84yxu9rxgnmk955cxtml4ujswzxydd/wss%3A%2F%2Fgit.napplet.soy%2F/n-0b3d7b60562

Archive hash verified: 823f5de0d2677ef2. 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)

A napplet that fulfils an archetype role advertises one contract per entry through the vite-plugin `archetypes` option, which the CLI's `napplet init --archetype slug:napplet:<archetype>/<intent>` also records in `.napplet/config.json`:

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

This emits the manifest tag `["archetype", "note", "napplet:note/open"]`. The role `slug` and the convention's archetype segment are independent under NAP-INTENT. 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(
  'profile',
  { pubkey: 'abc123' },
  { convention: 'napplet:profile/open', behavior: { newWindow: true } },
);
if (!result.handled) showIntentError(result.error);
```

- Dispatch with `intent.invoke(request)` or the `intent.open(archetype, payload?, opts?)` convenience.
- Results always carry `ok`, `archetype`, `action`, and `handled`; use `handled` to distinguish "dispatched" from "someone acted on it". Behavior hints: `focus`, `newWindow`, `reuse`.
- Archetype routing and optional convention-based payload interpretation are orthogonal; do not couple intent delivery to INC.

## 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 `handled` drives 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 manifest `requires` 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 returned result object only.