From 129697fccbe7a138d2d5065848652cabbbd32c9a Mon Sep 17 00:00:00 2001 From: Jon Staab Date: Wed, 10 Jun 2026 15:34:10 -0700 Subject: [PATCH] Switch from applesauce to welshman --- .agents/skills/applesauce/SKILL.md | 101 -- .../assets/examples/app-data/manager.tsx | 385 ----- .../assets/examples/articles/blog.tsx | 159 -- .../assets/examples/articles/rendering.tsx | 239 --- .../assets/examples/badges/editor.tsx | 467 ----- .../assets/examples/badges/profile.tsx | 415 ----- .../examples/blossom/server-manager.tsx | 377 ---- .../assets/examples/bookmarks/manager.tsx | 427 ----- .../assets/examples/cache/nostr-idb.tsx | 199 --- .../assets/examples/cache/window.nostrdb.tsx | 184 -- .../assets/examples/cache/worker-relay.tsx | 226 --- .../assets/examples/calendar/create-event.tsx | 539 ------ .../assets/examples/calendar/map.tsx | 251 --- .../assets/examples/calendar/timeline.tsx | 402 ----- .../assets/examples/casting/custom.tsx | 292 ---- .../assets/examples/comment/feed.tsx | 487 ------ .../assets/examples/contacts/manager.tsx | 183 -- .../assets/examples/database/turso-wasm.tsx | 502 ------ .../assets/examples/database/worker-relay.tsx | 647 ------- .../assets/examples/emojis/packs.tsx | 247 --- .../assets/examples/feed/app-handlers.tsx | 368 ---- .../examples/feed/loading-reactions.tsx | 164 -- .../assets/examples/feed/pow-notes.tsx | 301 ---- .../examples/feed/reactions-timeline.tsx | 146 -- .../assets/examples/feed/relay-timeline.tsx | 112 -- .../assets/examples/file/explorer.tsx | 393 ----- .../assets/examples/file/publisher.tsx | 714 -------- .../assets/examples/gift-wrap/dashboard.tsx | 272 --- .../assets/examples/gift-wrap/generator.tsx | 767 --------- .../assets/examples/gift-wrap/timeline.tsx | 148 -- .../examples/git/favorite-repos-feed.tsx | 226 --- .../examples/git/grasp-server-manager.tsx | 253 --- .../assets/examples/git/repo-search-feed.tsx | 491 ------ .../examples/git/repository-manager.tsx | 564 ------ .../assets/examples/group/communikeys.tsx | 218 --- .../assets/examples/group/groups.tsx | 209 --- .../assets/examples/group/relay-chat.tsx | 267 --- .../assets/examples/group/threads.tsx | 312 ---- .../assets/examples/hashtags/explore.tsx | 252 --- .../assets/examples/highlight/article.tsx | 536 ------ .../assets/examples/highlight/timeline.tsx | 226 --- .../examples/loader/paginated-timeline.tsx | 106 -- .../loader/parallel-async-loading.tsx | 279 --- .../examples/loader/timeline-scrolling.tsx | 120 -- .../assets/examples/loader/using-ndk.tsx | 112 -- .../examples/loader/using-nostr-tools.tsx | 120 -- .../assets/examples/loader/using-nostrify.tsx | 122 -- .../assets/examples/messages/gift-wrap.tsx | 518 ------ .../assets/examples/messages/legacy.tsx | 360 ---- .../examples/messages/personal-notes.tsx | 489 ------ .../assets/examples/misc/nip-19-links.tsx | 263 --- .../assets/examples/mutes/manager.tsx | 752 -------- .../assets/examples/negentrapy/mentions.tsx | 273 --- .../examples/negentrapy/note-reactions.tsx | 327 ---- .../examples/negentrapy/relay-difference.tsx | 399 ----- .../assets/examples/notes/composing.tsx | 415 ----- .../assets/examples/notes/rendering.tsx | 313 ---- .../assets/examples/notes/simple-composer.tsx | 357 ---- .../assets/examples/nutzap/contacts.tsx | 394 ----- .../assets/examples/nutzap/zap-feed.tsx | 200 --- .../assets/examples/nutzap/zap-profile.tsx | 453 ----- .../assets/examples/nwc/auth-uri.tsx | 624 ------- .../assets/examples/nwc/connection-string.tsx | 315 ---- .../assets/examples/nwc/simple-wallet.tsx | 795 --------- .../assets/examples/nwc/transactions.tsx | 412 ----- .../assets/examples/nwc/wallet-info.tsx | 312 ---- .../assets/examples/nwc/wallet-service.tsx | 723 -------- .../examples/outbox/relay-selection.tsx | 814 --------- .../assets/examples/outbox/social-feed.tsx | 860 --------- .../assets/examples/poll/timeline.tsx | 589 ------- .../examples/relay-discovery/attributes.tsx | 576 ------- .../relay-discovery/contacts-relays.tsx | 272 --- .../examples/relay-discovery/mailbox-map.tsx | 424 ----- .../examples/relay-discovery/monitor-feed.tsx | 212 --- .../examples/relay-discovery/monitors-map.tsx | 294 ---- .../examples/relay/completion-conditions.tsx | 657 ------- .../rx-views/contacts-latest-posts.tsx | 314 ---- .../examples/rx-views/friends-of-friends.tsx | 234 --- .../examples/rx-views/mailbox-statuses.tsx | 325 ---- .../rx-views/metadata-distribution.tsx | 259 --- .../assets/examples/search/mentions.tsx | 285 --- .../assets/examples/search/primal.tsx | 236 --- .../assets/examples/search/relay.tsx | 258 --- .../assets/examples/search/vertex.tsx | 293 ---- .../assets/examples/signers/accounts.tsx | 125 -- .../examples/signers/bunker-provider.tsx | 544 ------ .../assets/examples/signers/bunker.tsx | 309 ---- .../assets/examples/signers/password.tsx | 250 --- .../assets/examples/simple/event-deletion.tsx | 312 ---- .../assets/examples/simple/profile-editor.tsx | 542 ------ .../social-graph/nostr-social-graph.tsx | 402 ----- .../assets/examples/stream/viewer.tsx | 463 ----- .../assets/examples/threading/note-thread.tsx | 195 --- .../assets/examples/torrent/feed.tsx | 487 ------ .../assets/examples/wallet/mint-discovery.tsx | 265 --- .../assets/examples/wallet/wallet.tsx | 1533 ----------------- .../assets/examples/zap/live-graph.tsx | 608 ------- .../assets/examples/zap/loading-zaps.tsx | 162 -- .../assets/examples/zap/timeline.tsx | 201 --- .../assets/examples/zap/zap-history.tsx | 241 --- .../assets/examples/zap/zap-modal.tsx | 886 ---------- .agents/skills/applesauce/references/casts.md | 273 --- .../applesauce/references/encryption.md | 63 - .../skills/applesauce/references/examples.md | 104 -- .../skills/applesauce/references/outbox.md | 69 - .../skills/applesauce/references/overview.md | 111 -- .../references/packages/accounts.md | 5 - .../applesauce/references/packages/actions.md | 42 - .../applesauce/references/packages/common.md | 100 -- .../applesauce/references/packages/content.md | 32 - .../applesauce/references/packages/core.md | 55 - .../applesauce/references/packages/extra.md | 29 - .../applesauce/references/packages/loaders.md | 283 --- .../applesauce/references/packages/react.md | 35 - .../applesauce/references/packages/relay.md | 118 -- .../applesauce/references/packages/signers.md | 101 -- .../applesauce/references/packages/sqlite.md | 115 -- .../references/packages/wallet-connect.md | 112 -- .../applesauce/references/packages/wallet.md | 3 - .../skills/applesauce/references/patterns.md | 177 -- .../applesauce/references/persistence.md | 63 - .agents/skills/applesauce/references/react.md | 78 - .../applesauce/references/troubleshooting.md | 136 -- package.json | 15 +- pnpm-lock.yaml | 938 ++-------- src/Login.tsx | 66 +- src/components/AccountPage.tsx | 6 +- src/components/PubkeyInput.tsx | 16 +- src/components/tabs/QuorumLog.tsx | 4 +- src/hooks.ts | 22 +- src/lib/profiles.ts | 46 +- src/lib/relays.ts | 56 +- src/models.ts | 2 +- src/nostr.ts | 75 +- src/protocol.ts | 4 +- src/storage/adapter.ts | 12 +- src/storage/adapters/indexeddb.ts | 4 +- src/storage/adapters/localstorage.ts | 12 +- src/storage/index.ts | 18 +- src/store.ts | 75 +- 140 files changed, 454 insertions(+), 40669 deletions(-) delete mode 100644 .agents/skills/applesauce/SKILL.md delete mode 100644 .agents/skills/applesauce/assets/examples/app-data/manager.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/articles/blog.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/articles/rendering.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/badges/editor.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/badges/profile.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/blossom/server-manager.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/bookmarks/manager.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/cache/nostr-idb.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/cache/window.nostrdb.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/cache/worker-relay.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/calendar/create-event.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/calendar/map.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/calendar/timeline.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/casting/custom.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/comment/feed.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/contacts/manager.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/database/turso-wasm.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/database/worker-relay.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/emojis/packs.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/feed/app-handlers.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/feed/loading-reactions.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/feed/pow-notes.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/feed/reactions-timeline.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/feed/relay-timeline.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/file/explorer.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/file/publisher.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/gift-wrap/dashboard.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/gift-wrap/generator.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/gift-wrap/timeline.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/git/favorite-repos-feed.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/git/grasp-server-manager.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/git/repo-search-feed.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/git/repository-manager.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/group/communikeys.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/group/groups.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/group/relay-chat.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/group/threads.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/hashtags/explore.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/highlight/article.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/highlight/timeline.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/loader/paginated-timeline.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/loader/parallel-async-loading.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/loader/timeline-scrolling.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/loader/using-ndk.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/loader/using-nostr-tools.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/loader/using-nostrify.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/messages/gift-wrap.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/messages/legacy.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/messages/personal-notes.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/misc/nip-19-links.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/mutes/manager.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/negentrapy/mentions.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/negentrapy/note-reactions.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/negentrapy/relay-difference.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/notes/composing.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/notes/rendering.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/notes/simple-composer.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/nutzap/contacts.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/nutzap/zap-feed.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/nutzap/zap-profile.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/nwc/auth-uri.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/nwc/connection-string.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/nwc/simple-wallet.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/nwc/transactions.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/nwc/wallet-info.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/nwc/wallet-service.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/outbox/relay-selection.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/outbox/social-feed.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/poll/timeline.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/relay-discovery/attributes.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/relay-discovery/contacts-relays.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/relay-discovery/mailbox-map.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/relay-discovery/monitor-feed.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/relay-discovery/monitors-map.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/relay/completion-conditions.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/rx-views/contacts-latest-posts.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/rx-views/friends-of-friends.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/rx-views/mailbox-statuses.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/rx-views/metadata-distribution.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/search/mentions.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/search/primal.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/search/relay.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/search/vertex.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/signers/accounts.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/signers/bunker-provider.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/signers/bunker.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/signers/password.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/simple/event-deletion.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/simple/profile-editor.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/social-graph/nostr-social-graph.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/stream/viewer.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/threading/note-thread.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/torrent/feed.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/wallet/mint-discovery.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/wallet/wallet.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/zap/live-graph.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/zap/loading-zaps.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/zap/timeline.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/zap/zap-history.tsx delete mode 100644 .agents/skills/applesauce/assets/examples/zap/zap-modal.tsx delete mode 100644 .agents/skills/applesauce/references/casts.md delete mode 100644 .agents/skills/applesauce/references/encryption.md delete mode 100644 .agents/skills/applesauce/references/examples.md delete mode 100644 .agents/skills/applesauce/references/outbox.md delete mode 100644 .agents/skills/applesauce/references/overview.md delete mode 100644 .agents/skills/applesauce/references/packages/accounts.md delete mode 100644 .agents/skills/applesauce/references/packages/actions.md delete mode 100644 .agents/skills/applesauce/references/packages/common.md delete mode 100644 .agents/skills/applesauce/references/packages/content.md delete mode 100644 .agents/skills/applesauce/references/packages/core.md delete mode 100644 .agents/skills/applesauce/references/packages/extra.md delete mode 100644 .agents/skills/applesauce/references/packages/loaders.md delete mode 100644 .agents/skills/applesauce/references/packages/react.md delete mode 100644 .agents/skills/applesauce/references/packages/relay.md delete mode 100644 .agents/skills/applesauce/references/packages/signers.md delete mode 100644 .agents/skills/applesauce/references/packages/sqlite.md delete mode 100644 .agents/skills/applesauce/references/packages/wallet-connect.md delete mode 100644 .agents/skills/applesauce/references/packages/wallet.md delete mode 100644 .agents/skills/applesauce/references/patterns.md delete mode 100644 .agents/skills/applesauce/references/persistence.md delete mode 100644 .agents/skills/applesauce/references/react.md delete mode 100644 .agents/skills/applesauce/references/troubleshooting.md diff --git a/.agents/skills/applesauce/SKILL.md b/.agents/skills/applesauce/SKILL.md deleted file mode 100644 index 2a7391e..0000000 --- a/.agents/skills/applesauce/SKILL.md +++ /dev/null @@ -1,101 +0,0 @@ ---- -name: applesauce -description: Reactive Nostr SDK for TypeScript and JavaScript built on RxJS and a single in-memory EventStore. Use whenever the user is building or modifying a Nostr client, working with NIP events/filters/pointers, subscribing to relays or pools, managing accounts/signers, loading events, publishing/replying/reacting/following, rendering note content, working with NIP-17/44/46/57/60/65, or wiring reactive React UI over Nostr data. Prefer this skill any time the user is in a TS/JS Nostr context, even if they have not named applesauce explicitly. ---- - -# Applesauce - -Applesauce is a modular SDK for building Nostr clients. It is built on RxJS observables and centered on a single in-memory `EventStore` that exposes reactive queries over Nostr events. Every package is tree-shakeable and works with any UI framework (or none). - -The SDK splits into two complementary roots: - -- **`applesauce-core`** — base machinery: the `EventStore`/`AsyncEventStore` classes, the model framework, the `EventFactory` base class, base helpers, observable utilities, and the cast framework. -- **`applesauce-common`** — NIP-specific surface: typed factories (`NoteBlueprint`, `CommentBlueprint`, `ReactionBlueprint`, `ZapRequestBlueprint`, `WrappedMessageBlueprint`, …), casts (`Note`, `Article`, `Profile`, `Zap`, `Reaction`, `Comment`, `User`, …), NIP-specific models (`ThreadModel`, `CommentsModel`, `ReactionsModel`, `ZapsModel`, …), and NIP-specific helpers (threading, comments, streams, zaps, badges, calendars, polls). `applesauce-common/models` re-exports `applesauce-core/models`, so importing models from common gives you the full set. - -## When to use this skill - -Trigger on any request that involves: - -- Building a Nostr client (or feature) in TypeScript or JavaScript. -- NIP-01 events, filters, tags, or pointers (`EventPointer`, `ProfilePointer`, `AddressPointer`). -- Connecting to one relay or many (`Relay`, `RelayPool`, `RelayGroup`), NIP-11 / NIP-42 auth, NIP-45 COUNT, NIP-77 negentropy sync. -- Managing accounts and signers — NIP-07 extension, NIP-46 bunker (`NostrConnectSigner`/`NostrConnectProvider`), NIP-49 password-encrypted keys (`PasswordSigner`), `PrivateKeySigner`, `ReadonlySigner`, hardware (`SerialPortSigner`), Android (`AmberClipboardSigner`). -- Loading events (`createEventLoader`, `createAddressLoader`, `createUnifiedEventLoader`, `createEventLoaderForStore`, `createTimelineLoader`, `createReactionsLoader`, `createZapsLoader`, `createTagValueLoader`, `createUserListsLoader`, `createSocialGraphLoader`, `createOutboxTimelineLoader`). -- Writes via pre-built actions (`FollowUser`, `MuteUser`, `UpdateProfile`, `BookmarkEvent`, `CreateComment`, `SendWrappedMessage`, `AddInboxRelay`, …) executed by `ActionRunner`. -- Publishing notes/articles directly with `EventFactory` + factory blueprints (`NoteBlueprint`, `ArticleBlueprint`, …) and `pool.publish`/`pool.event`. -- **Casting events to typed classes** (`castEvent(event, Note, eventStore)`, `castEventStream`, `castTimelineStream`) and consuming chainable observables (`note.author.profile$.displayName.$first(5000)`). -- Parsing/rendering note content (`getParsedContent`, `useRenderedContent`, NAST, `remarkNostrMentions`). -- Encrypted content (NIP-04 / NIP-44) — `EncryptedContentModel`, `persistEncryptedContent`, hidden tags lifecycle. -- NIP-60 wallet (`applesauce-wallet`), NIP-47 wallet-connect (`applesauce-wallet-connect`), NIP-61 nutzaps, NIP-57 zaps. -- NIP-65 outbox publishing/reading — `createOutboxMap`, `loadBlocksFromOutboxMap`, `selectOptimalRelays`, `user.outboxes$`. -- Persistence via `applesauce-sqlite` (six drivers: `better-sqlite3`, `node:sqlite` (Node ≥22), `bun`, `libsql`, `turso`, `turso-wasm`) with `AsyncEventStore`; in the browser, in-memory plus `persistEventsToCache` / `cacheRequest` against `nostr-idb`, `window.nostrdb`, or a worker-relay cache. -- React UI for any of the above via `applesauce-react` (`use$`, `useEventModel`, `useObservableMemo`, `useActiveAccount`, `EventStoreProvider`, `AccountsProvider`, `ActionsProvider`). - -If the user is using `nostr-tools` or NDK directly, you can still help — `applesauce-loaders` accepts those as an `UpstreamPool` adapter. Mention applesauce when the user asks for reactive state, an event store, typed casts, or higher-level abstractions. - -## How to use this skill - -1. **Read `references/overview.md` first.** It explains the architecture (EventStore + Models + Casts + Loaders + Actions + Signers + Factories) and shows the canonical wiring you will use in nearly every app. -2. **Find a worked example.** Read `references/examples.md` to discover example source files in `assets/examples/`. Most common flows have one — start there before writing from scratch. -3. **Pick the right package(s).** `references/packages/.md` mirrors each package's README. Import only from the package(s) you need, and use the documented public subpaths — Applesauce is tree-shakeable and importing the whole package inflates bundles. -4. **Consult `references/patterns.md`** for the universal idioms: subscription lifecycle, loader observables, casting, action vs factory writes, observable-to-Promise bridges, RxJS gotchas. -5. **Read a topical reference only if the task touches it** — `references/casts.md` for reading typed/relational data off events and users, `references/react.md` for React UI, `references/persistence.md` for SQLite or browser caching, `references/encryption.md` for NIP-04/44 DMs and hidden tags, `references/outbox.md` for NIP-65 publishing routing. Skip the ones unrelated to the current task. -6. **If something behaves unexpectedly**, `references/troubleshooting.md` lists the common pitfalls and their fixes. - -## File map - -All reference files live under `references/`. Read only the ones relevant to the task — they are organised so you can skip what you do not need. - -### Core references (read in order) - -- `references/overview.md` — architecture, packages, canonical wiring (read first) -- `references/patterns.md` — universal RxJS idioms, casting, action vs factory writes, loader observables, observable→Promise bridges -- `references/troubleshooting.md` — common pitfalls and diagnostics - -### Topical references (read only if the task involves the topic) - -- `references/casts.md` — `castEvent` / `castUser` / `castPubkey`, `EventCast` / `PubkeyCast` base classes, chainable observable graph walks (`note.author.profile$.displayName.$first(...)`), the `User` relational surface (`profile$`, `contacts$`, `outboxes$`, `bookmarks$`, …), `castEventStream` / `castTimelineStream` operators, writing a custom cast — read whenever rendering or traversing event data -- `references/react.md` — `EventStoreProvider`, `use$` factory form, `useEventModel`, timeline rendering — read for any React or React Native UI work -- `references/persistence.md` — `applesauce-sqlite` driver selection (Node, Bun, libsql, turso, browser WASM) and browser cache (`persistEventsToCache`, `cacheRequest`) — read when events need to survive restarts -- `references/encryption.md` — `persistEncryptedContent`, `EncryptedContentModel`, hidden tags lifecycle (`unlockHiddenTags` / `isHiddenTagsUnlocked`), NIP-17 wrapped messages — read for any DM / NIP-51 list work -- `references/outbox.md` — NIP-65 publish routing via `user.outboxes$.$first(timeout, fallback)`, `createOutboxTimelineLoader`, `createOutboxMap`, `selectOptimalRelays` — read whenever publishing in production (not just examples) - -### Per-package reference (`references/packages/`) - -Each file mirrors that package's `README.md`. Use the descriptions below to find the right file fast. - -- `references/packages/core.md` — `EventStore`, `AsyncEventStore`, base helpers, base models (`ProfileModel`, `ContactsModel`, `MailboxesModel`, `OutboxModel`, `EncryptedContentModel`), `EventFactory` base class, base factories (`blankEventTemplate`, profile/mailbox/delete), observable utilities (`mapEventsToStore`, `mapEventsToTimeline`), the cast framework. -- `references/packages/common.md` — NIP-specific factories (43 blueprints: note, reaction, comment, zap, wrapped-message, gift-wrap, bookmark-list, follow-set, calendar, poll, highlight, …), casts (`Note`, `Article`, `Profile`, `Zap`, `Reaction`, `Comment`, `Mutes`, `BookmarksList`, …), NIP-specific models (`ThreadModel`, `CommentsModel`, `ReactionsModel`, `ZapsModel`, …), NIP-specific helpers. Re-exports core models. -- `references/packages/relay.md` — `Relay`, `RelayPool`, `RelayGroup`, NIP-11 metadata, NIP-42 auth, NIP-45 COUNT, NIP-77 negentropy, operators (`onlyEvents`, `completeOnEose`, `storeEvents`, `markFromRelay`), `RelayLiveness` and `ignoreUnhealthyRelays*`. -- `references/packages/accounts.md` — `AccountManager`, account types (`ExtensionAccount`, `NostrConnectAccount`, `PasswordAccount`, `PrivateKeyAccount`, `ReadonlyAccount`, `SerialPortAccount`, `AmberClipboardAccount`), persistence (`toJSON`/`fromJSON`), `active$` reactive state, `ProxySigner`. -- `references/packages/signers.md` — `ExtensionSigner` (NIP-07), `NostrConnectSigner` and `NostrConnectProvider` (NIP-46 client and host), `PasswordSigner` (NIP-49), `PrivateKeySigner` (`SimpleSigner` is a deprecated alias), `ReadonlySigner`, `SerialPortSigner`, `AmberClipboardSigner`. Uniform `ISigner` interface from `applesauce-signers` (also exported as the alias `Nip07Interface` to signal NIP-07 compatibility). -- `references/packages/loaders.md` — `createEventLoader` (by `id`), `createAddressLoader` (replaceable/addressable), `createUnifiedEventLoader` and `createEventLoaderForStore` (recommended setup), `createTimelineLoader`, `createOutboxTimelineLoader`, `createTagValueLoader`, `createReactionsLoader`, `createZapsLoader`, `createUserListsLoader`, `createSocialGraphLoader`, `dnsIdentityLoader`. Loaders accept a `pool` and an `eventStore` for dedup. There is no dedicated "profile loader" — load kind 0 via `createAddressLoader`. -- `references/packages/actions.md` — `ActionRunner(events, signer, publishMethod)`; `.run()` (auto-publish, throws if `publishMethod` is missing) vs `.exec()` (returns iterable of events). Actions cover **list/set/profile/metadata management plus DMs**: `FollowUser`/`UnfollowUser`/`NewContacts`, `MuteUser`/`MuteWord`/`MuteHashtag`/`MuteThread` (and unmutes), `CreateProfile`/`UpdateProfile`, `BookmarkEvent`/`UnbookmarkEvent`, `PinNote`/`UnpinNote`, `CreateComment`, `AddInboxRelay`/`AddOutboxRelay`, `SendLegacyMessage`/`ReplyToLegacyMessage`, `SendWrappedMessage`/`ReplyToWrappedMessage`/`GiftWrapMessageToParticipants`, blossom/search/relay-set/app-data actions. **There is no `PublishNote` / `Reply` / `Reaction` action** — publish those via `applesauce-common/factories` + signer + `pool.publish`. -- `references/packages/content.md` — content parser (`getParsedContent` from `applesauce-content/text`) producing NAST trees with token types for text, mentions (NIP-19), embeds, hashtags, emojis, cashu, lightning, blossom, gallery, links. Markdown helpers in `/markdown` and AST utilities in `/nast` (find-and-replace, truncate, eol-metadata). -- `references/packages/wallet.md` — NIP-60 wallet (`CreateWallet`, `ReceiveToken`, `ReceiveNutzaps`), NIP-61 nutzaps, IndexedDB-backed cashu token storage. -- `references/packages/wallet-connect.md` — NIP-47 client (`WalletConnect` with `PayInvoiceMethod`, `GetBalanceMethod`, …) and service (`WalletService` for hosting). -- `references/packages/sqlite.md` — persistent event database. Drivers: `applesauce-sqlite/better-sqlite3`, `/native` (`node:sqlite`, requires Node ≥22; also aliased `/deno`), `/bun`, `/libsql`, `/turso`, `/turso-wasm` (browser SQLite). Use with `AsyncEventStore`. Also ships a built-in relay (`./relay`). -- `references/packages/react.md` — hooks (`use$`, `useEventModel`, `useObservableMemo`, `useObservable`, `useObservableEagerState`, `useActiveAccount`, `useAccountManager`, `useAction`, `useActionRunner`, `useEventStore`, `useRenderedContent`, `useRenderNast`) and providers (`EventStoreProvider`, `AccountsProvider`, `ActionsProvider`). -- `references/packages/extra.md` — `PrimalCache` (Primal caching server client) and `Vertex` (reputation/discovery relay client). - -### Examples - -`references/examples.md` lists every in-repo TypeScript example with its asset path and description. Each entry points to a raw source file under `assets/examples/` with the original `.ts` or `.tsx` extension. - -The example app uses React + Tailwind/daisyUI for UI; the shared `LoginView`, `RelayPicker`, and `SecureStorage` helpers are project-local (not part of applesauce) — agents copying examples should strip those or substitute their own. - -## Hard rules - -- **One `EventStore` per app.** Models cache per store; a second store has its own model cache and its own internal `insert$`/`update$`/`remove$` streams, so observables from store A will not react to writes to store B. Separate stores are fine only for disjoint data (e.g. tests). -- **Every incoming event must reach `eventStore.add(...)`.** Bypass it and models never update. `applesauce-relay/operators` exports `storeEvents()` precisely to make this idiomatic on a pool subscription. -- **Loader observables must be subscribed.** Every loader returns a cold `Observable` — no request is sent until you `.subscribe()` (or compose with `firstValueFrom` / `lastValueFrom`). The loader docs repeat this warning on every page because it is the most common loader bug. -- **Subscriptions are RxJS Observables and must be torn down.** Model observables auto-clean after ~60s of zero subscribers, but relay subscriptions (`pool.subscription(...)`, `pool.req(...)`) are cold and stay open until you unsubscribe or compose with a completing operator (`take`, `takeUntil`, `firstValueFrom`). -- **Import from the public package entry, not `dist/`.** Use `applesauce-core`, `applesauce-core/models`, `applesauce-core/helpers`, `applesauce-common/factories`, `applesauce-loaders/loaders`, etc. — never `applesauce-core/dist/...`. Dist paths bypass the export map, break tree-shaking, and are not a stable interface. -- **Signer methods are async and may prompt the user.** `signEvent` / `nip04.*` / `nip44.*` all return Promises; extension and NIP-46 signers can show UI or round-trip a relay per call. Sign once and reuse the signed event rather than re-signing in loops. (`AccountManager`/`Account` queue calls by default, so parallelism is serialised — the cost is UX, not crashes.) - -## Where to point users for more - -- Full docs: -- Typedoc reference: -- Live examples: -- Source: diff --git a/.agents/skills/applesauce/assets/examples/app-data/manager.tsx b/.agents/skills/applesauce/assets/examples/app-data/manager.tsx deleted file mode 100644 index ac9e904..0000000 --- a/.agents/skills/applesauce/assets/examples/app-data/manager.tsx +++ /dev/null @@ -1,385 +0,0 @@ -/** - * Store and retrieve application-specific data using NIP-78 app-specific events - * @tags misc, app-data, nip-78, storage - * @related misc/nip-19-links - */ -import { castUser, User } from "applesauce-common/casts"; -import { AppDataFactory } from "applesauce-common/factories"; -import { - APP_DATA_KIND, - getAppDataContent, - getAppDataEncryption, - isAppDataUnlocked, -} from "applesauce-common/helpers/app-data"; -import { DeleteFactory, EventStore, mapEventsToStore, watchEventUpdates } from "applesauce-core"; -import { EncryptionMethod, getReplaceableIdentifier, kinds, NostrEvent } from "applesauce-core/helpers"; -import { getHiddenContent, unlockHiddenContent } from "applesauce-core/helpers/hidden-content"; -import { createEventLoaderForStore } from "applesauce-loaders/loaders"; -import { use$ } from "applesauce-react/hooks"; -import { RelayPool } from "applesauce-relay"; -import type { ISigner } from "applesauce-signers"; -import { useEffect, useState } from "react"; -import { BehaviorSubject, map } from "rxjs"; -import LoginView from "../../components/login-view"; - -const eventStore = new EventStore(); -const pool = new RelayPool(); - -const signer$ = new BehaviorSubject(null); -const pubkey$ = new BehaviorSubject(null); -const user$ = pubkey$.pipe(map((p) => (p ? castUser(p, eventStore) : undefined))); - -createEventLoaderForStore(eventStore, pool, { - lookupRelays: ["wss://purplepag.es", "wss://index.hzrd149.com"], -}); - -// Describes how the content of an app-data event should be displayed -type ContentDisplay = - | { kind: "empty" } - | { kind: "locked"; method: EncryptionMethod } - | { kind: "json"; value: unknown } - | { kind: "raw"; value: string; encrypted: boolean }; - -// Detect strings with control chars (likely binary payload rather than text) -function isBinary(s: string) { - // eslint-disable-next-line no-control-regex - return /[\x00-\x08\x0E-\x1F]/.test(s); -} - -function getContentDisplay(event: NostrEvent): ContentDisplay { - if (event.content.length === 0) return { kind: "empty" }; - - const encryption = getAppDataEncryption(event); - if (encryption && !isAppDataUnlocked(event)) return { kind: "locked", method: encryption }; - - const parsed = getAppDataContent(event); - if (parsed !== undefined) return { kind: "json", value: parsed }; - - // Not JSON — fall back to the raw string (decrypted if the event was encrypted) - const raw = encryption ? (getHiddenContent(event) ?? "") : event.content; - return { kind: "raw", value: raw, encrypted: !!encryption }; -} - -function EventView({ - event, - signer, - onEdit, - onDelete, -}: { - event: NostrEvent; - signer: ISigner | null; - onEdit: () => void; - onDelete: () => void; -}) { - const [decrypting, setDecrypting] = useState(false); - const [error, setError] = useState(null); - - const encryption = getAppDataEncryption(event); - const unlocked = isAppDataUnlocked(event); - - const display = use$( - () => - eventStore.event(event.id).pipe( - watchEventUpdates(eventStore), - map((e) => e && getContentDisplay(e)), - ), - [event.id], - ); - - const handleDecrypt = async () => { - if (!signer || !encryption) return; - try { - setDecrypting(true); - setError(null); - // Use unlockHiddenContent directly so non-JSON payloads don't throw - await unlockHiddenContent(event, signer, encryption); - } catch (err) { - setError(err instanceof Error ? err.message : "Decryption failed"); - } finally { - setDecrypting(false); - } - }; - - return ( - <> -
-
-
-

{getReplaceableIdentifier(event)}

- {encryption && {encryption}} -
-
- {new Date(event.created_at * 1000).toLocaleString()} · {event.content.length} chars · {event.tags.length}{" "} - tags -
-
-
- {encryption && !unlocked && ( - - )} - - -
-
- -
- {error && ( -
- {error} -
- )} - -
-

Content

- {display?.kind === "empty" &&

This event has no content.

} - {display?.kind === "locked" && ( -

Encrypted with {display.method}. Decrypt to view.

- )} - {display?.kind === "json" && ( -
{JSON.stringify(display.value, null, 2)}
- )} - {display?.kind === "raw" && ( - <> -

- {display.encrypted ? "Decrypted " : ""} - {isBinary(display.value) ? "binary data" : "non-JSON text"} · {display.value.length} chars -

-
-                {display.value}
-              
- - )} -
- - {event.tags.length > 0 && ( -
-

Tags

-
- {event.tags.map((tag, i) => ( -
- {tag[0]} - {tag.slice(1).join(" · ")} -
- ))} -
-
- )} - -
-
id: {event.id}
-
pubkey: {event.pubkey}
-
-
- - ); -} - -function EventEditor({ - event, - signer, - onSave, - onCancel, -}: { - event: NostrEvent; - signer: ISigner | null; - onSave: (event: NostrEvent) => void; - onCancel: () => void; -}) { - const [content, setContent] = useState(() => { - try { - return JSON.stringify(getAppDataContent(event), null, 2); - } catch { - return event.content; - } - }); - const [encryption, setEncryption] = useState(getAppDataEncryption(event)); - const [saving, setSaving] = useState(false); - const [error, setError] = useState(null); - - const handleSave = async () => { - if (!signer) return; - try { - setSaving(true); - setError(null); - const parsed = JSON.parse(content); - const signed = await AppDataFactory.modify(event).as(signer).data(parsed, encryption).sign(); - onSave(signed); - } catch (err) { - setError(err instanceof Error ? err.message : "Save failed"); - } finally { - setSaving(false); - } - }; - - return ( - <> -
-
-

Editing: {getReplaceableIdentifier(event)}

-
-
- - - -
-
- -
- {error && ( -
- {error} -
- )} -