From d8736ba34145cf5f55b066ed8685ace3f1b35f48 Mon Sep 17 00:00:00 2001 From: Jeff Emmett Date: Tue, 24 Mar 2026 14:01:01 -0700 Subject: [PATCH] fix(rcal): add post-load migration for tags field on existing events Automerge docs created before the tags schema don't have the field. The migration runs 5s after startup (after loadAllDocs completes), patches missing tags onto events, and assigns known demo event tags. Also removes debug endpoint and tracing logs. Co-Authored-By: Claude Opus 4.6 --- modules/rcal/mod.ts | 69 ++++++++++++++++++--------- server/local-first/doc-persistence.ts | 8 ---- 2 files changed, 46 insertions(+), 31 deletions(-) diff --git a/modules/rcal/mod.ts b/modules/rcal/mod.ts index 25e9647..3080f2e 100644 --- a/modules/rcal/mod.ts +++ b/modules/rcal/mod.ts @@ -275,10 +275,7 @@ function seedDemoIfEmpty(space: string) { if (d.meta) (d.meta as any).seeded = true; }); - // Verify tags survived Automerge change - const verifyDoc = _syncServer!.getDoc(docId); - const firstEv = verifyDoc ? Object.values(verifyDoc.events)[0] : null; - console.log(`[Cal] Demo data seeded: 2 sources, 7 events — tags: ${JSON.stringify(firstEv?.tags)}, has_key: ${'tags' in (firstEv || {})}`); + console.log("[Cal] Demo data seeded: 2 sources, 7 events"); } // ── API: Events ── @@ -340,25 +337,6 @@ routes.get("/api/events", async (c) => { return c.json({ count: rows.length, results: rows }); }); -// DEBUG: raw Automerge event data (temporary) -routes.get("/api/events-debug", async (c) => { - const space = c.req.param("space") || "demo"; - const dataSpace = c.get("effectiveSpace") || space; - const doc = ensureDoc(dataSpace); - const events = Object.values(doc.events).slice(0, 3); - const raw = events.map((e) => ({ - title: e.title, - tags_value: e.tags, - tags_type: typeof e.tags, - tags_truthy: !!e.tags, - tags_isArray: Array.isArray(e.tags), - tags_json: JSON.stringify(e.tags), - tags_arrayFrom: e.tags ? Array.from(e.tags) : 'WAS_FALSY', - has_tags_key: 'tags' in e, - all_keys: Object.keys(e), - })); - return c.json(raw); -}); // POST /api/events — create event routes.post("/api/events", async (c) => { @@ -1054,6 +1032,41 @@ export function getUpcomingEventsForMI(space: string, days = 14, limit = 5): MIC })); } +/** + * Migrate existing events: add `tags` field where missing. + * Automerge docs created before the tags schema update won't have the key. + * This runs on every startup — it's a no-op if tags already exist. + */ +function migrateTagsField(space: string) { + if (!_syncServer) return; + const docId = calendarDocId(space); + const doc = _syncServer.getDoc(docId); + if (!doc) return; + + const needsMigration = Object.values(doc.events).some((e) => !('tags' in e)); + if (!needsMigration) return; + + // Tag assignment for known demo events + const demoTags: Record = { + "rSpace Launch Party": ["launch", "community"], + "Provider Onboarding Workshop": ["onboarding", "cosmolocal"], + "Weekly Community Standup": ["recurring", "community"], + "Sprint: Module Seeding & Polish": ["sprint", "dev"], + "rFlows Budget Review": ["governance", "finance"], + "Cosmolocal Design Sprint": ["sprint", "design", "travel"], + "Q1 Retrospective": ["recurring", "community", "retrospective"], + }; + + _syncServer.changeDoc(docId, 'migrate: add tags field', (d) => { + for (const ev of Object.values(d.events)) { + if (!('tags' in ev)) { + (ev as any).tags = demoTags[ev.title] || null; + } + } + }); + console.log(`[Cal] Migrated tags field for ${space}`); +} + export const calModule: RSpaceModule = { id: "rcal", name: "rCal", @@ -1069,6 +1082,16 @@ export const calModule: RSpaceModule = { _syncServer = ctx.syncServer; // Seed demo data for the default space seedDemoIfEmpty("demo"); + + // Migrate tags field for all spaces that have cal docs (runs after loadAllDocs via setTimeout) + setTimeout(() => { + for (const docId of _syncServer!.listDocs()) { + if (docId.endsWith(':cal:events')) { + const space = docId.split(':')[0]; + migrateTagsField(space); + } + } + }, 5000); // Run after loadAllDocs completes }, feeds: [ { diff --git a/server/local-first/doc-persistence.ts b/server/local-first/doc-persistence.ts index 6e9b350..3cdd13a 100644 --- a/server/local-first/doc-persistence.ts +++ b/server/local-first/doc-persistence.ts @@ -186,14 +186,6 @@ async function scanDir(dir: string, syncServer: SyncServer): Promise { const doc = Automerge.load(bytes); const docId = pathToDocId(fullPath); - // Debug: check cal doc tags during load - if (docId.includes(':cal:events')) { - const events = (doc as any).events; - if (events) { - const first = Object.values(events)[0] as any; - console.log(`[DocStore] Loading ${docId}: tags=${JSON.stringify(first?.tags)}, has_tags=${'tags' in (first || {})}, keys=${Object.keys(first || {}).filter((k: string) => k.startsWith('t'))}`); - } - } syncServer.setDoc(docId, doc); count++; } catch (e) {