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 <noreply@anthropic.com>
This commit is contained in:
Jeff Emmett 2026-03-24 14:01:01 -07:00
parent 9ae88e14b7
commit d8736ba341
2 changed files with 46 additions and 31 deletions

View File

@ -275,10 +275,7 @@ function seedDemoIfEmpty(space: string) {
if (d.meta) (d.meta as any).seeded = true; if (d.meta) (d.meta as any).seeded = true;
}); });
// Verify tags survived Automerge change console.log("[Cal] Demo data seeded: 2 sources, 7 events");
const verifyDoc = _syncServer!.getDoc<CalendarDoc>(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 || {})}`);
} }
// ── API: Events ── // ── API: Events ──
@ -340,25 +337,6 @@ routes.get("/api/events", async (c) => {
return c.json({ count: rows.length, results: rows }); 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 // POST /api/events — create event
routes.post("/api/events", async (c) => { 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<CalendarDoc>(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<string, string[]> = {
"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<CalendarDoc>(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 = { export const calModule: RSpaceModule = {
id: "rcal", id: "rcal",
name: "rCal", name: "rCal",
@ -1069,6 +1082,16 @@ export const calModule: RSpaceModule = {
_syncServer = ctx.syncServer; _syncServer = ctx.syncServer;
// Seed demo data for the default space // Seed demo data for the default space
seedDemoIfEmpty("demo"); 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: [ feeds: [
{ {

View File

@ -186,14 +186,6 @@ async function scanDir(dir: string, syncServer: SyncServer): Promise<number> {
const doc = Automerge.load(bytes); const doc = Automerge.load(bytes);
const docId = pathToDocId(fullPath); 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); syncServer.setDoc(docId, doc);
count++; count++;
} catch (e) { } catch (e) {