Files
eranos/.agents/skills/nostr-kind-design/SKILL.md
T
Alex Gleason 4a4ed9bc2d Split nostr-kinds skill into design and rendering
The combined skill conflated two unrelated jobs: (1) design-time
decisions when authoring a new kind (NIP-vs-custom, ranges, tag design,
NIP.md) and (2) implementation-time checklist for wiring rendering into
Ditto's many UI touchpoints. The single description sentence was
unwieldy, and its trigger ('introducing a new kind... or registering a
kind in the UI') was phrased around the author's perspective — it
didn't match user phrasing like 'support displaying kind X' or 'render
NIP-Y', so I skipped loading it when implementing NIP-84 and missed
half the registration points.

Split into:

- nostr-kind-design — NIP-vs-custom decision, kind ranges, tag design,
  content-vs-tags, NIP.md. Loads when minting or extending a schema.
- nostr-kind-rendering — the multi-location UI registration checklist.
  Loads when rendering a kind Ditto doesn't yet display, or when asked
  to 'support / display / render' a NIP or kind number.

Expanded the rendering checklist with the points I missed during the
NIP-84 pass: the six-file notification stack, the four-file AppConfig
triple for feed-toggle keys, sidebar icon registration, AppRouter route
wiring, shouldHideFeedEvent spam guards, and a 'bugs that signal a
missed step' section so the checklist reads as diagnostic too. Also
flagged the embedded-previews trap where skipping the dispatcher branch
silently feeds quoted prose through the kind-1 tokenizer.

Updated both AGENTS.md references to point at the two new skills.
2026-05-05 17:33:11 -05:00

90 lines
5.0 KiB
Markdown

---
name: nostr-kind-design
description: Decide whether to reuse an existing NIP or mint a new kind, design tag structures that relays can index, choose what goes in content vs. tags, and document new kinds or extensions in NIP.md. Load when authoring a new schema — not when wiring up rendering for a kind that already exists (use nostr-kind-rendering for that).
---
# Nostr Kinds — Design and Schema
Load this skill when:
- Minting a new event kind for a Ditto feature.
- Extending an existing NIP with new tags.
- Deciding whether an existing NIP covers a use case or whether a custom kind is warranted.
- Documenting a custom kind or extension in `NIP.md`.
**Not this skill** — if an existing NIP/kind covers your use case and you only need to render it in Ditto's UI, use the **`nostr-kind-rendering`** skill instead.
## Choosing Between Existing NIPs and Custom Kinds
1. **Thorough NIP review first.** Browse the NIP index, then read candidate NIPs in detail. The goal is to find the closest existing solution.
2. **Prefer extending existing NIPs** over creating custom kinds, even at the cost of minor schema compromises. Custom kinds fragment the ecosystem.
3. **When an existing NIP is close but not perfect**, use its kind as the base and add domain-specific tags. Document the extension in `NIP.md`.
4. **Only mint a new kind** when no existing NIP covers the core functionality, the data structure is fundamentally different, or the use case requires different storage characteristics (regular vs. replaceable vs. addressable).
5. **If a tool to generate a new kind number is available, you MUST call it.** Never pick an arbitrary number.
6. **Custom kinds MUST include a NIP-31 `alt` tag** with a human-readable description of the event's purpose.
**Example decision:**
```
Need: Equipment marketplace for farmers
Options:
1. NIP-15 (Marketplace) — too structured for peer-to-peer sales
2. NIP-99 (Classifieds) — good fit, extensible with farming tags
3. Custom kind — perfect fit, no interoperability
Decision: NIP-99 + farming-specific tags.
```
## Kind Ranges
An event's kind number determines storage semantics:
- **Regular** (1000 ≤ kind < 10000) — stored permanently by relays. Notes, articles, etc.
- **Replaceable** (10000 ≤ kind < 20000) — only the latest event per `pubkey+kind` is kept. Profile metadata, contact lists, mute lists.
- **Addressable** (30000 ≤ kind < 40000) — identified by `pubkey+kind+d-tag`; only the latest per combo is kept. Long-form content, products, definitions.
Kinds below 1000 are "legacy"; storage is per-kind (e.g. kind 1 is regular, kind 3 is replaceable).
## Tag Design Principles
- **Kind = schema, tags = semantics.** Don't mint a new kind just to represent a different category of the same data.
- **Relays only index single-letter tags.** Use `t` for categories so filters like `'#t': ['electronics']` work at the relay level. Multi-letter tags (`product_type`, etc.) force inefficient client-side filtering.
- **Filter at the relay**, not in JavaScript:
```ts
// ❌ Fetch everything, filter locally
const events = await nostr.query([{ kinds: [30402] }]);
const filtered = events.filter((e) => hasTag(e, 'product_type', 'electronics'));
// ✅ Filter at the relay
const events = await nostr.query([{ kinds: [30402], '#t': ['electronics'] }]);
```
- **For Ditto-specific niches** (community apps, regional variants), tag events with a `t` value and query on it. Don't do this for generic platforms — it would silo content.
## Content vs. Tags
- **`content`** — large freeform text or existing industry-standard JSON (GeoJSON, FHIR, Tiled maps). Kind 0 is the one exception where structured JSON goes in content.
- **Tags** — queryable metadata, structured data, anything you might filter on.
- **Empty content is fine.** `content: ""` is idiomatic for tag-only events.
- **If you need to filter by a field, it must be a tag** — relays don't index content.
```json
// ✅ Queryable
{ "kind": 30402, "content": "",
"tags": [["d", "product-123"], ["title", "Camera"], ["price", "250"], ["t", "photography"]] }
// ❌ Structured data buried in content
{ "kind": 30402, "content": "{\"title\":\"Camera\",\"price\":250}", "tags": [["d", "product-123"]] }
```
## `NIP.md`
`NIP.md` documents Ditto's custom kinds and any extensions to existing NIPs. Whenever you mint a new kind or change a custom schema, **create or update `NIP.md`** with the tag list, content format, and intended usage. If a kind you add is effectively the same shape as an existing NIP, note the NIP reference rather than duplicating the spec.
Standard NIPs (like NIP-84 Highlights, NIP-23 Articles) do **not** go in `NIP.md` — only Ditto-custom kinds and Ditto-specific extensions.
## After Designing — What's Next?
Once you've settled on a kind number and tag shape, you still need to render it in Ditto's UI. Load the **`nostr-kind-rendering`** skill for the full multi-location registration checklist (feed cards, detail pages, embedded previews, kind-label maps, notifications, feed-toggle registration).