From 67a715d4b62de55f686e7394655a3a699abc8f0a Mon Sep 17 00:00:00 2001 From: Alexander Taylor Date: Sat, 28 Feb 2026 23:58:41 +0000 Subject: [PATCH 1/3] fix: minor improvements to event attendance fixes from https://github.com/dxe/adb/pull/297/ --- .../src/app/event/activist-registry.ts | 24 ++++++++----------- frontend-v2/src/app/event/activist-storage.ts | 7 ++++-- .../src/app/event/useActivistRegistry.ts | 9 ++++--- 3 files changed, 19 insertions(+), 21 deletions(-) diff --git a/frontend-v2/src/app/event/activist-registry.ts b/frontend-v2/src/app/event/activist-registry.ts index e950a8f2..0275af99 100644 --- a/frontend-v2/src/app/event/activist-registry.ts +++ b/frontend-v2/src/app/event/activist-registry.ts @@ -25,11 +25,10 @@ export class ActivistRegistry { private activistsById: Map private storage?: ActivistStorage - constructor(storage?: ActivistStorage) { + constructor() { this.activists = [] this.activistsByName = new Map() this.activistsById = new Map() - this.storage = storage } /** @@ -48,14 +47,9 @@ export class ActivistRegistry { /** * Loads activists from IndexedDB storage into memory. * Call once after construction, before first use of the registry. - * @throws Error if storage is not configured */ - async loadFromStorage(): Promise { - if (!this.storage) { - throw new Error( - 'Cannot load activists from storage: storage not configured.', - ) - } + async loadFromStorage(storage: ActivistStorage): Promise { + this.storage = storage const stored = await this.storage.getAllActivists() this.activists = stored @@ -65,7 +59,7 @@ export class ActivistRegistry { } /** - * Merges new activists with existing data, replacing duplicates by id. + * Merges new activists with existing data, replacing duplicates by id and name. * If storage is configured, persists updates to IndexedDB. */ async mergeActivists(newActivists: ActivistRecord[]): Promise { @@ -116,14 +110,16 @@ export class ActivistRegistry { const idsToRemove = new Set(ids) - this.activists = this.activists.filter((activist) => { + const remainingActivists: ActivistRecord[] = [] + for (const activist of this.activists) { if (idsToRemove.has(activist.id)) { this.activistsByName.delete(activist.name) this.activistsById.delete(activist.id) - return false + continue } - return true - }) + remainingActivists.push(activist) + } + this.activists = remainingActivists // Write through to storage if configured await this.storage?.deleteActivistsByIds(ids) diff --git a/frontend-v2/src/app/event/activist-storage.ts b/frontend-v2/src/app/event/activist-storage.ts index 1322cb74..2b4fb43a 100644 --- a/frontend-v2/src/app/event/activist-storage.ts +++ b/frontend-v2/src/app/event/activist-storage.ts @@ -3,7 +3,7 @@ * Provides caching and incremental sync capabilities. * * Why IndexedDB instead of localStorage: - * - Data size: ~3MB currently, approaching localStorage's 5-10MB limit + * - Data size: ~3MB as of January 2026, approaching localStorage's 5-10MB limit * - Async operations: Avoid blocking main thread with large JSON parse/stringify * - Structured storage: Store by ID with built-in indexing */ @@ -121,7 +121,10 @@ export class ActivistStorage { const request = timestamp === null ? store.delete('lastSync') - : store.put({ lastSyncTime: timestamp }, 'lastSync') + : store.put( + { lastSyncTime: timestamp } satisfies SyncMetadata, + 'lastSync', + ) request.onsuccess = () => resolve() request.onerror = () => reject(request.error) diff --git a/frontend-v2/src/app/event/useActivistRegistry.ts b/frontend-v2/src/app/event/useActivistRegistry.ts index 07a3482a..bc8b6afa 100644 --- a/frontend-v2/src/app/event/useActivistRegistry.ts +++ b/frontend-v2/src/app/event/useActivistRegistry.ts @@ -15,7 +15,8 @@ import toast from 'react-hot-toast' */ export function useActivistRegistry() { // Single registry instance with write-through storage to IndexedDB (if available) - const registryRef = useRef(new ActivistRegistry(activistStorage)) + const registry = new ActivistRegistry() + const registryRef = useRef(registry) const [isStorageLoaded, setIsStorageLoaded] = useState(false) const [isServerLoaded, setIsServerLoaded] = useState(false) @@ -33,7 +34,7 @@ export function useActivistRegistry() { } registryRef.current - .loadFromStorage() + .loadFromStorage(activistStorage) .then(() => { if (mounted) setIsStorageLoaded(true) }) @@ -131,9 +132,7 @@ export function useActivistRegistry() { } // Merge newer activists (registry handles both memory and storage) - if (activistsToUpdate.length > 0) { - await registryRef.current.mergeActivists(activistsToUpdate) - } + await registryRef.current.mergeActivists(activistsToUpdate) // Update last sync timestamp await registryRef.current.setLastSyncTime(new Date().toISOString()) From 72837ff42c8076bbf09d67a2d94c67a4340d52a4 Mon Sep 17 00:00:00 2001 From: Alexander Taylor Date: Sun, 1 Mar 2026 00:54:55 +0000 Subject: [PATCH 2/3] fixup! fix: minor improvements to event attendance --- .../src/app/event/activist-registry.ts | 21 +++++-------------- 1 file changed, 5 insertions(+), 16 deletions(-) diff --git a/frontend-v2/src/app/event/activist-registry.ts b/frontend-v2/src/app/event/activist-registry.ts index 0275af99..c86d732d 100644 --- a/frontend-v2/src/app/event/activist-registry.ts +++ b/frontend-v2/src/app/event/activist-registry.ts @@ -51,15 +51,14 @@ export class ActivistRegistry { async loadFromStorage(storage: ActivistStorage): Promise { this.storage = storage - const stored = await this.storage.getAllActivists() - this.activists = stored + this.activists = await this.storage.getAllActivists() this.sortActivists() - this.activistsByName = new Map(stored.map((a) => [a.name, a])) - this.activistsById = new Map(stored.map((a) => [a.id, a])) + this.activistsByName = new Map(this.activists.map((a) => [a.name, a])) + this.activistsById = new Map(this.activists.map((a) => [a.id, a])) } /** - * Merges new activists with existing data, replacing duplicates by id and name. + * Merges new activists with existing data. * If storage is configured, persists updates to IndexedDB. */ async mergeActivists(newActivists: ActivistRecord[]): Promise { @@ -73,27 +72,17 @@ export class ActivistRegistry { const existingIndex = indexById.get(activist.id) ?? -1 if (existingIndex >= 0) { - // Update existing activist (handles renames properly) - const oldActivist = this.activists[existingIndex] this.activists[existingIndex] = activist - - // Remove old name from index if name changed - if (oldActivist.name !== activist.name) { - this.activistsByName.delete(oldActivist.name) - } } else { - // Add new activist this.activists.push(activist) indexById.set(activist.id, this.activists.length - 1) } - // Update indexes - this.activistsByName.set(activist.name, activist) this.activistsById.set(activist.id, activist) } - // Re-sort after batch updates to maintain sort order this.sortActivists() + this.activistsByName = new Map(this.activists.map((a) => [a.name, a])) // Write through to storage if configured await this.storage?.saveActivists(newActivists) From f89d6ea589b237263dc6521b46609596a446bacf Mon Sep 17 00:00:00 2001 From: Alexander Taylor Date: Sun, 1 Mar 2026 00:59:15 +0000 Subject: [PATCH 3/3] fixup! fixup! fix: minor improvements to event attendance --- frontend-v2/src/app/event/activist-registry.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/frontend-v2/src/app/event/activist-registry.ts b/frontend-v2/src/app/event/activist-registry.ts index c86d732d..bc82fa0b 100644 --- a/frontend-v2/src/app/event/activist-registry.ts +++ b/frontend-v2/src/app/event/activist-registry.ts @@ -15,9 +15,7 @@ export type ActivistRecord = { * Reads are synchronous (from memory) for fast autocomplete/filtering. * Writes are async and automatically persist to IndexedDB when storage is configured. * - * @param storage - Optional IndexedDB storage for persistence. - * When provided, enables automatic write-through caching. - * When omitted, registry operates in memory-only mode. + * When storage is not configured, registry operates in memory-only mode. */ export class ActivistRegistry { private activists: ActivistRecord[]