/** * Local (optimistic) content-deletion tombstones. * * WHY THIS EXISTS * --------------- * A NIP-09 deletion request (kind 5) is only honoured by relays, and the client * never applies it locally. Until the relays have processed it — and some relays * never do — a deleted event can still come back from the React Query * persistence cache or the Dexie offline cache, so a deleted spot flashes on the * map for a few seconds on refresh before the fresh relay result removes it. * * Recording a tombstone the moment the author deletes something lets every list * filter drop it from the very first render, independent of relay timing. The * tombstone is device-local and best-effort; it is NOT a substitute for the * kind-5 event (that is what removes the content for everyone else). * * KEYING * ------ * A target is keyed BOTH by its addressable coordinate (`kind:pubkey:d`) and by * its concrete event id (`e:`) when both are known, so a list filter matches * whichever form it has. Entries carry a timestamp so the store can be pruned * later if needed. */ export interface LocalDeletionEntry { /** Unix seconds. */ at: number; } const STORAGE_KEY = 'skatespots_local_deletions_v1'; type Store = Record; function read(): Store { try { const raw = localStorage.getItem(STORAGE_KEY); if (!raw) return {}; const parsed = JSON.parse(raw); return parsed && typeof parsed === 'object' ? (parsed as Store) : {}; } catch { return {}; } } function write(store: Store): void { try { localStorage.setItem(STORAGE_KEY, JSON.stringify(store)); } catch { /* storage full / unavailable — the optimistic tombstone is best-effort */ } } /** Record that a target was deleted by its author on this device. */ export function addLocalDeletion(opts: { coord?: string; id?: string }): void { const { coord, id } = opts; if (!coord && !id) return; const store = read(); const at = Math.floor(Date.now() / 1000); if (coord) store[coord] = { at }; if (id) store[`e:${id}`] = { at }; write(store); } /** Undo a tombstone (e.g. the deletion was cancelled or failed to publish). */ export function removeLocalDeletion(opts: { coord?: string; id?: string }): void { const { coord, id } = opts; const store = read(); let changed = false; if (coord && coord in store) { delete store[coord]; changed = true; } if (id && `e:${id}` in store) { delete store[`e:${id}`]; changed = true; } if (changed) write(store); } /** Every locally-recorded deletion, keyed by coord or `e:`. */ export function getLocalDeletions(): Map { return new Map(Object.entries(read())); } /** True when this target carries a local deletion tombstone. */ export function hasLocalDeletion(opts: { coord?: string; id?: string }): boolean { const store = read(); if (opts.coord && opts.coord in store) return true; if (opts.id && `e:${opts.id}` in store) return true; return false; }