Files
grimoire/src/hooks/useAccountSync.ts
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

87 lines
2.6 KiB
TypeScript

import { useEffect } from "react";
import { useEventStore, use$ } from "applesauce-react/hooks";
import accounts from "@/services/accounts";
import { useGrimoire } from "@/core/state";
import { addressLoader } from "@/services/loaders";
import type { RelayInfo } from "@/types/app";
import { normalizeRelayURL } from "@/lib/relay-url";
/**
* Hook that syncs active account with Grimoire state and fetches relay lists
*/
export function useAccountSync() {
const { setActiveAccount, setActiveAccountRelays } = useGrimoire();
const eventStore = useEventStore();
// Watch active account from accounts service
const activeAccount = use$(accounts.active$);
// Sync active account pubkey to state
useEffect(() => {
setActiveAccount(activeAccount?.pubkey);
}, [activeAccount?.pubkey, setActiveAccount]);
// Fetch and watch relay list (kind 10002) when account changes
useEffect(() => {
if (!activeAccount?.pubkey) {
return;
}
const pubkey = activeAccount.pubkey;
let lastRelayEventId: string | undefined;
// Subscribe to kind 10002 (relay list)
const subscription = addressLoader({
kind: 10002,
pubkey,
identifier: "",
}).subscribe();
// Watch for relay list event in store
const storeSubscription = eventStore
.replaceable(10002, pubkey, "")
.subscribe((relayListEvent) => {
if (!relayListEvent) return;
// Only process if this is a new event
if (relayListEvent.id === lastRelayEventId) return;
lastRelayEventId = relayListEvent.id;
// Parse relays from tags (NIP-65 format)
// Tag format: ["r", "relay-url", "read|write"]
// If no marker, relay is used for both read and write
const relays: RelayInfo[] = [];
const seenUrls = new Set<string>();
for (const tag of relayListEvent.tags) {
if (tag[0] === "r" && tag[1]) {
try {
const url = normalizeRelayURL(tag[1]);
if (seenUrls.has(url)) continue;
seenUrls.add(url);
const marker = tag[2];
relays.push({
url,
read: !marker || marker === "read",
write: !marker || marker === "write",
});
} catch (error) {
console.warn(
`Skipping invalid relay URL in Kind 10002 event: ${tag[1]}`,
error,
);
}
}
}
setActiveAccountRelays(relays);
});
return () => {
subscription.unsubscribe();
storeSubscription.unsubscribe();
};
}, [activeAccount?.pubkey, eventStore, setActiveAccountRelays]);
}