docs(nostr): add reusable Nostr patterns reference

Living reference at docs/nostr-patterns/ that future Claude Code sessions
(per memory directive) and human contributors must consult before writing
Nostr code, and update when implementing or refining patterns.

Six topic files covering 18 patterns harvested from existing modules
(activities, base, forum, market, chat, tasks, nostr-feed):

- subscriptions.md   — RelayHub lifecycle, EOSE, visibility-aware
                       reconnect, per-event-id dedup
- replaceable-events.md — monotonic created_at, per-pubkey latest-wins,
                          replaceable-list rewrite, Vue 3 nested ref<Map>
                          reactivity gotcha
- publishing.md      — result.success > 0 checks, optimistic-on-success,
                       pending-coord debounce, finalizeEvent with bytes
- reactions-and-deletions.md — NIP-25 toggle-as-delete, NIP-09 pubkey
                                check, dedup-before-mutate
- profiles.md        — kind-0 batch fetch with request dedup,
                       unsubscribe-on-EOSE for snapshot fetches
- services-and-di.md — BaseService lifecycle, injectService vs
                       tryInjectService, expose state via getters

Each pattern points at a canonical implementation (file:line) and notes
the *why* behind each pattern so a new caller doesn't trip on the same
edge case the canonical implementation already learned about.

Recurring deep-dive issue (#42) tracks mining patterns from Coracle,
Snort, NoStrudel, Damus, Habla, Highlighter, Flotilla, Zap.cooking, NDK
that we haven't reinvented yet — findings land in this directory.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
Padreug 2026-05-05 20:24:26 +02:00
commit 8303b0981b
7 changed files with 557 additions and 0 deletions

View file

@ -0,0 +1,66 @@
# Nostr patterns
Living reference for reusable Nostr patterns that show up across modules
(activities, forum, market, chat, tasks, base, nostr-feed).
**Read before writing any new Nostr code in this repo.** **Update whenever you
introduce, refine, or correct a pattern.** Each section has a "Canonical
implementation" line — that's the file the pattern was harvested from. If a
caller deviates, document why or align with the canonical version.
The single biggest reason this directory exists: Nostr's edge cases (relay
dedup, replaceable events, reactivity-of-nested-Map, EOSE timing) are subtle
enough that re-deriving them per module produces different bugs each time.
Consolidating the *resolution* prevents that.
## Index
- [**Subscriptions & lifecycle**](./subscriptions.md) — RelayHub usage, EOSE,
unsubscribe on unmount, visibility-aware reconnect.
- [**Replaceable events**](./replaceable-events.md) — NIP-01 §13 semantics,
monotonic `created_at`, per-pubkey latest-wins state, replaceable bookmark
lists.
- [**Publishing & confirmation**](./publishing.md) — `RelayHub.publishEvent`
result checks, optimistic updates, signing with `nostr-tools`, pending-coord
debounce.
- [**Reactions, deletions & dedup**](./reactions-and-deletions.md) — NIP-25
toggle, NIP-09 deletion handling, per-event-id dedup.
- [**Profiles & batch fetch**](./profiles.md) — kind-0 caching, request dedup.
- [**Services, DI & reactivity**](./services-and-di.md) — `BaseService`,
`tryInjectService`, exposing service state via computed, Vue 3 nested
ref-Map reactivity gotchas.
Patterns specific to a single NIP that doesn't repeat across modules
(currently: NIP-59 gift-wrapped market orders) are not listed here yet — only
the cross-cutting patterns. When a second consumer adopts an NIP-specific
pattern, promote it.
## Conventions
- **Canonical implementation** is a single file path with line numbers. If a
pattern lives in multiple modules, list one canonical and the rest as
"alternates" with a short note on what differs.
- **Why** sections explain the failure mode the pattern prevents — not just
what it does. If the why is obvious from the code, omit it.
- **Cross-link** by file when patterns compose (e.g. replaceable events almost
always pair with the pending-coord debounce).
- Don't duplicate code into the docs. Reference file:line and quote at most
the 5-line core. Drift between code and docs is the failure mode.
## Updating
When you implement a new pattern (or fix a subtle bug in an existing one):
1. Add or amend the relevant topic file — leave a brief "added 2026-MM-DD,
from <module>" note if it helps trace provenance.
2. If it's a brand-new topic, add a section to this README's index and create
a new topic file.
3. If the pattern obsoletes an alternate implementation listed here, either
align that implementation or note explicitly why it diverges.
## Improving
We periodically deep-dive into well-known open-source Nostr apps (Coracle,
Snort, Damus, NoStrudel, Habla, Highlighter, Flotilla, Zap.cooking) to mine
patterns we haven't reinvented yet. Tracked as a recurring issue on Forgejo
(`aiolabs/webapp`). Findings land here.