Files
grimoire/PLAN-APPLESAUCE-V5.md
Alejandro b2b398b9fb docs: add applesauce v5 upgrade plan (#39)
* docs: add applesauce v5 upgrade plan

Comprehensive migration plan covering:
- Package updates (add applesauce-common, update to v5)
- EventFactory import migration (applesauce-factory → applesauce-core)
- Unified event loader setup
- ActionHub → ActionRunner migration
- useObservableMemo → use$ hook migration
- New features: casting system, encrypted content caching
- Documentation and skills updates needed

* feat: upgrade applesauce libraries to v5

Major upgrade from applesauce v4 to v5 with breaking changes:

Package updates:
- applesauce-core: ^4.0.0 → ^5.0.0
- applesauce-actions: ^4.0.0 → ^5.0.0
- applesauce-loaders: ^4.0.0 → ^5.0.0
- applesauce-react: ^4.0.0 → ^5.0.0
- applesauce-relay: ^4.0.0 → ^5.0.0
- applesauce-signers: ^4.0.0 → ^5.0.0
- applesauce-accounts: ^4.0.0 → ^5.0.0
- Added new applesauce-common: ^5.0.0 package

API migrations:
- EventFactory: applesauce-factory → applesauce-core/event-factory
- ActionHub → ActionRunner with async function pattern (not generators)
- useObservableMemo → use$ hook across all components
- Helper imports: article, highlight, threading, zap, comment, lists
  moved from applesauce-core to applesauce-common
- parseCoordinate → parseReplaceableAddress
- Subscription options: retries → reconnect
- getEventPointerFromETag now returns null instead of throwing

New features:
- Unified event loader via createEventLoaderForStore
- Updated loaders.ts to use v5 unified loader pattern

Documentation:
- Updated CLAUDE.md with v5 patterns and migration notes
- Updated applesauce-core skill for v5 changes
- Created new applesauce-common skill

Test fixes:
- Updated publish-spellbook.test.ts for v5 ActionRunner pattern
- Updated publish-spell.test.ts with eventStore mock
- Updated relay-selection.test.ts with valid test events
- Updated loaders.test.ts with valid 64-char hex event IDs
- Added createEventLoaderForStore mock

---------

Co-authored-by: Claude <noreply@anthropic.com>
2026-01-05 14:54:21 +01:00

428 lines
12 KiB
Markdown

# Applesauce v5 Upgrade Plan for Grimoire
## Overview
This plan outlines the migration from applesauce v4 to v5, covering breaking changes, new features to adopt, and documentation updates.
## Phase 1: Package Updates & Import Migration
### 1.1 Update package.json Dependencies
**Current versions:**
```json
"applesauce-accounts": "^4.1.0",
"applesauce-actions": "^4.0.0",
"applesauce-content": "^4.0.0",
"applesauce-core": "latest",
"applesauce-loaders": "^4.2.0",
"applesauce-react": "^4.0.0",
"applesauce-relay": "latest",
"applesauce-signers": "^4.1.0"
```
**Target versions:**
```json
"applesauce-accounts": "^5.0.0",
"applesauce-actions": "^5.0.0",
"applesauce-common": "^5.0.0", // NEW - required for v5
"applesauce-content": "^5.0.0",
"applesauce-core": "^5.0.0",
"applesauce-loaders": "^5.0.0",
"applesauce-react": "^5.0.0",
"applesauce-relay": "^5.0.0",
"applesauce-signers": "^5.0.0"
```
**Remove:**
```json
"applesauce-factory": "..." // Removed in v5 - EventFactory now in applesauce-core
```
### 1.2 EventFactory Import Migration
**File:** `src/services/hub.ts`
```typescript
// Before (v4)
import { EventFactory } from "applesauce-factory";
// After (v5)
import { EventFactory } from "applesauce-core/event-factory";
```
### 1.3 Helper Import Migration
Many helpers moved from `applesauce-core/helpers` to `applesauce-common/helpers`. Need to audit all imports:
**Helpers that moved to applesauce-common/helpers:**
- Profile helpers: `getDisplayName`, `getProfilePicture`
- Social graph: `groupPubkeysByRelay`, `getSeenRelays`
- Threading: `getNip10References`, `interpretThreadTags`
- Zaps: `getZapAmount`, `getZapSender`, `isValidZap`
- Lists: `FAVORITE_RELAYS_KIND`, `getListTags`, `getRelaysFromList`
- Article helpers: `getArticleTitle`, `getArticleSummary`, etc.
- Highlight helpers: all `getHighlight*` functions
- Comment helpers: `getCommentReplyPointer`
**Files to update (from exploration):**
- `src/lib/nostr-utils.ts`
- `src/lib/event-title.ts`
- `src/lib/nip34-helpers.ts`
- `src/lib/nip-c0-helpers.ts`
- `src/services/loaders.ts`
- `src/services/relay-selection.ts`
- `src/hooks/useProfile.ts`
- `src/hooks/useNostrEvent.ts`
- `src/components/nostr/RichText.tsx`
- All kind renderers in `src/components/nostr/kinds/`
**Strategy:** Use grep to find all `applesauce-core/helpers` imports and update to `applesauce-common/helpers` where appropriate. Some low-level helpers remain in `applesauce-core/helpers`.
---
## Phase 2: Loader Migration (Unified Event Loader)
### 2.1 Current Loader Architecture
**File:** `src/services/loaders.ts`
Currently uses:
- `createEventLoader` - for single events
- `createAddressLoader` - for replaceable/addressable events
- `createTimelineLoader` - for timelines
- Custom `eventLoader` wrapper with smart relay hint merging
### 2.2 Unified Loader Setup
Replace separate loaders with unified loader:
```typescript
// Before (v4)
import {
createEventLoader,
createAddressLoader,
createTimelineLoader,
} from "applesauce-loaders/loaders";
const baseEventLoader = createEventLoader(pool, { eventStore, extraRelays });
export const addressLoader = createAddressLoader(pool, { eventStore, extraRelays });
export const profileLoader = createAddressLoader(pool, { eventStore, bufferTime: 200, extraRelays });
// After (v5)
import { createEventLoaderForStore } from "applesauce-loaders/loaders";
// One-time setup - attaches loader to eventStore
createEventLoaderForStore(eventStore, pool, {
bufferTime: 200,
followRelayHints: true,
extraRelays: AGGREGATOR_RELAYS,
lookupRelays: ["wss://purplepag.es/", "wss://index.hzrd149.com/"],
});
// Usage changes - use eventStore methods directly
eventStore.event({ id: "event_id" }).subscribe(...);
eventStore.replaceable({ kind: 0, pubkey: "pubkey" }).subscribe(...);
eventStore.addressable({ kind: 30023, pubkey, identifier }).subscribe(...);
```
### 2.3 Custom eventLoader Wrapper
The current smart relay hint merging in `eventLoader()` needs to be preserved. Options:
**Option A:** Keep custom wrapper, use unified loader internally
```typescript
export function eventLoader(pointer, context) {
// Keep existing relay hint merging logic
const enhancedPointer = mergeRelayHints(pointer, context);
// Use eventStore.event() from unified loader
return eventStore.event(enhancedPointer);
}
```
**Option B:** Move relay hint logic to a cacheRequest/extraRelays callback
```typescript
createEventLoaderForStore(eventStore, pool, {
bufferTime: 200,
followRelayHints: true,
extraRelays: (pointer) => getRelayHintsForPointer(pointer),
});
```
**Recommendation:** Option A - Keep custom wrapper for backward compatibility and control over relay selection.
### 2.4 Hook Updates for Unified Loader
**Files to update:**
- `src/hooks/useNostrEvent.ts` - Use eventStore.event() / eventStore.replaceable()
- `src/hooks/useProfile.ts` - Use eventStore.replaceable() for kind 0
- `src/hooks/useTimeline.ts` - May continue using createTimelineLoader
- `src/hooks/useLiveTimeline.ts` - No change (uses pool.subscription directly)
- `src/hooks/useAccountSync.ts` - Use eventStore.addressable() for kind 10002
---
## Phase 3: Action System Migration (ActionHub → ActionRunner)
### 3.1 Breaking Changes
**Before (v4):** Actions are async generators that `yield` events
```typescript
export function PublishSpellbook(options) {
return async function* ({ factory }: ActionContext): AsyncGenerator<NostrEvent> {
const draft = await factory.build({ kind, content, tags });
const event = await factory.sign(draft);
yield event; // ActionHub handles publishing
};
}
```
**After (v5):** Actions are async functions that call `context.publish()`
```typescript
export function PublishSpellbook(options) {
return async function ({ factory, publish }: ActionContext): Promise<void> {
const draft = await factory.build({ kind, content, tags });
const event = await factory.sign(draft);
await publish(event); // Explicit publish call
};
}
```
### 3.2 Files to Migrate
1. **`src/services/hub.ts`:**
```typescript
// Before
import { ActionHub } from "applesauce-actions";
export const hub = new ActionHub(eventStore, factory, publishEvent);
// After
import { ActionRunner } from "applesauce-actions";
export const hub = new ActionRunner(eventStore, factory, publishEvent);
```
2. **`src/actions/publish-spellbook.ts`:**
- Convert from async generator to async function
- Replace `yield event` with `await publish(event)`
3. **`src/actions/publish-spell.ts`:**
- Same conversion pattern
4. **`src/actions/delete-event.ts`:**
- Already uses direct factory.sign + pool.publish pattern
- Consider migrating to ActionRunner for consistency
### 3.3 Action Context Changes
v5 ActionContext provides additional features:
- `publish(event, relays?)` - Explicit publish with optional relay override
- `cast(event, Cast)` - Cast events to typed classes
- Sub-action support for composing actions
---
## Phase 4: React Hooks Migration (use$ Hook)
### 4.1 New use$ Hook
Replace `useObservableMemo` with `use$`:
```typescript
// Before (v4)
import { useObservableMemo } from "applesauce-react/hooks";
const event = useObservableMemo(() => eventStore.event(eventId), [eventId]);
const activeAccount = useObservableMemo(() => accounts.active$, []);
// After (v5)
import { use$ } from "applesauce-react/hooks";
const event = use$(() => eventStore.event(eventId), [eventId]);
const activeAccount = use$(accounts.active$); // Direct observable, no factory needed
```
### 4.2 Files to Update
Search for all `useObservableMemo` imports and update:
- `src/hooks/useNostrEvent.ts`
- `src/hooks/useTimeline.ts`
- `src/hooks/useLiveTimeline.ts`
- `src/hooks/useAccountSync.ts`
- `src/hooks/useStable.ts`
- Any component using `useObservableMemo` directly
### 4.3 Type Differences
`use$` has better TypeScript inference:
- `BehaviorSubject<T>` → returns `T` (never undefined)
- `Observable<T>` → returns `T | undefined`
---
## Phase 5: Adopt New Features
### 5.1 Casting System
Add casting for improved type safety and reactive patterns:
```typescript
import { castEvent, Note, User, Profile } from "applesauce-common/casts";
// Cast events to typed classes
const note = castEvent(event, Note, eventStore);
// Synchronous properties
console.log(note.id, note.createdAt, note.isReply);
// Reactive observables
const profile = use$(note.author.profile$);
const replies = use$(note.replies$);
```
**Potential use cases in Grimoire:**
- Note renderers - cast kind 1 events for cleaner access
- Profile components - use `User` cast for profile data
- Reply threads - use reactive `replies$` observable
- Zap displays - cast for amount/sender access
### 5.2 Encrypted Content Caching
For DM/encrypted content features:
```typescript
import { persistEncryptedContent } from "applesauce-common/helpers";
// Setup in services/event-store.ts or main.tsx
persistEncryptedContent(eventStore, storage);
// Decrypted content automatically cached
await unlockHiddenBookmarks(bookmarks, signer);
```
### 5.3 Blueprints and Operations
Move event creation to blueprints for cleaner code:
```typescript
// Before - manual event building
const draft = await factory.build({
kind: 30777,
content: JSON.stringify(content),
tags: [["d", slug], ["title", title]]
});
// After - using blueprints (if available for kind 30777)
import { SpellbookBlueprint } from "applesauce-common/blueprints";
const draft = await factory.build(SpellbookBlueprint({ title, content, slug }));
```
Note: Custom kinds like 30777 may not have built-in blueprints, but the pattern can be used for standard kinds.
---
## Phase 6: Documentation Updates
### 6.1 CLAUDE.md Updates
Update the following sections:
1. **Package structure** - Add `applesauce-common` to stack description
2. **Import patterns** - Update helper import paths
3. **Loader documentation** - Document unified loader pattern
4. **Action documentation** - Document ActionRunner pattern
5. **Hook documentation** - Document `use$` hook
6. **New features** - Add casting system documentation
7. **Helper caching note** - Update import paths in examples
### 6.2 Skill Updates
**`.claude/skills/applesauce-core/SKILL.md`:**
- Update import paths for helpers that moved to applesauce-common
- Add section on unified event loader
- Update examples to use `use$` hook
- Add casting system documentation
- Note v5 breaking changes
**New skill: `.claude/skills/applesauce-common/SKILL.md`:**
- Document casting system (Note, User, Profile, etc.)
- Document helpers that moved from applesauce-core
- Document blueprints and operations
- Document encrypted content caching
**`.claude/skills/applesauce-signers/SKILL.md`:**
- Verify no breaking changes (appears stable)
- Update if any signer interface changes
### 6.3 Code Comments
Update inline comments in affected files to reflect v5 patterns.
---
## Phase 7: Testing & Verification
### 7.1 Test Updates
Update test files for new patterns:
- `src/actions/publish-spell.test.ts`
- `src/actions/publish-spellbook.test.ts`
- Any tests using mocked applesauce imports
### 7.2 Verification Steps
1. Run `npm install` after package.json updates
2. Fix all TypeScript compilation errors
3. Run `npm run lint` and fix issues
4. Run `npm run test:run` and fix failing tests
5. Run `npm run build` to verify production build
6. Manual testing of critical flows:
- Profile loading
- Timeline feeds
- Event detail views
- Publishing events
- Account login/logout
---
## Migration Order
Recommended order to minimize breakage:
1. **Phase 1.1-1.3**: Package updates and imports (required first)
2. **Phase 3**: Action system migration (isolated change)
3. **Phase 2**: Loader migration (larger change, test carefully)
4. **Phase 4**: Hook migration (use$ is compatible alongside useObservableMemo)
5. **Phase 5**: New features adoption (optional enhancements)
6. **Phase 6**: Documentation updates
7. **Phase 7**: Final testing and verification
---
## Rollback Plan
If v5 migration encounters blocking issues:
1. Revert package.json to v4 versions
2. Run `npm install` to restore v4 packages
3. Git revert any code changes
4. Document blocking issues for resolution
---
## Estimated Scope
**Files to modify:**
- `package.json` - 1 file
- Service files (`src/services/`) - 3-4 files
- Hook files (`src/hooks/`) - 5-6 files
- Action files (`src/actions/`) - 3 files
- Helper usage across codebase - 20+ files
- Documentation files - 4-5 files
**New files:**
- `.claude/skills/applesauce-common/SKILL.md`
**Risk areas:**
- Loader migration (most complex, affects data loading)
- Helper import paths (many files, easy to miss some)
- Action generator → async function (behavior change)