Files
cinny/src/app/hooks/useUserNotes.ts
T
jaredandClaude Opus 4.8 4fc3f7a35f DP13: extract shared account-data list-store engine
useBookmarks / useReminders / useUserNotes were near-verbatim copies of a
concurrency-critical engine (module-scoped singleton, per-client
subscribe/teardown, a serialized write-queue that prevents lost-update
clobbering, a listener Set, and the account-data subscription).

Extract it into createAccountDataListStore<T, C>({ eventType, read, write }) in
src/app/hooks/createAccountDataListStore.ts. The write-serialization semantics
are preserved identically (still the lost-update fix). The differing payload
shapes are parameterized via read/write: bookmarks/reminders wrap a list
({bookmarks}/{reminders}); notes is a flat Record passed through unchanged.

The three hooks become thin wrappers with their exact public APIs unchanged
(same exported names, signatures, return shapes, and mutators), so no call site
changes. The DP16 setAccountData helper is used inside the queue.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-07-07 23:24:08 -04:00

49 lines
1.5 KiB
TypeScript

import { useCallback } from 'react';
import { useMatrixClient } from './useMatrixClient';
import { createAccountDataListStore } from './createAccountDataListStore';
const NOTES_KEY = 'io.lotus.user_notes';
export const USER_NOTE_MAX_LENGTH = 500;
type UserNotesContent = Record<string, string>;
// Shared, concurrency-safe store. See createAccountDataListStore for why the
// snapshot + write queue must be module-scoped (writes are serialized to avoid
// lost updates, since setAccountData replaces the whole content with no merge).
//
// Notes are a flat Record<userId, note> rather than a wrapped list, so `read`
// and `write` pass the content through unchanged.
const notesStore = createAccountDataListStore<UserNotesContent, UserNotesContent>({
eventType: NOTES_KEY,
read: (content) => content ?? {},
write: (notes) => notes,
});
export function useUserNotes(): {
getNote: (userId: string) => string;
setNote: (userId: string, note: string) => Promise<void>;
} {
const mx = useMatrixClient();
const notes = notesStore.useValue(mx);
const getNote = useCallback((userId: string) => notes[userId] ?? '', [notes]);
const setNote = useCallback(
(userId: string, note: string) => {
const trimmed = note.trim().slice(0, USER_NOTE_MAX_LENGTH);
return notesStore.enqueueWrite(mx, (current) => {
const updated = { ...current };
if (trimmed) {
updated[userId] = trimmed;
} else {
delete updated[userId];
}
return updated;
});
},
[mx],
);
return { getNote, setNote };
}