mirror of
https://github.com/purrgrammer/grimoire.git
synced 2026-04-10 23:47:12 +02:00
* 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>
428 lines
12 KiB
Markdown
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)
|