#!/usr/bin/env node // Demo seed for first-load credibility: one open pulse, one shipped roadmap // item attributed to the cab user, one dinner + one office hours event, and // a handful of hand-crafted activity rows so the ticker has something to // scroll on a fresh demo. // // Idempotent: skips if a pulse already exists. Run AFTER scripts/seed.js // and scripts/seed-roadmap.js (or via `pnpm db:setup`). import Database from 'better-sqlite3'; import { join, dirname } from 'path'; import { fileURLToPath } from 'url'; const __dirname = dirname(fileURLToPath(import.meta.url)); const dbPath = process.env.BIFROST_DB_PATH ?? join(__dirname, '..', 'bifrost.db'); const db = new Database(dbPath); db.pragma('foreign_keys = ON'); const existing = db.prepare('SELECT COUNT(*) AS n FROM pulses').get().n; if (existing > 0) { console.log(` demo data already present (${existing} pulse(s)) — skipping.`); db.close(); process.exit(0); } const users = db.prepare("SELECT id, name, role FROM users WHERE active = 1").all(); const byRole = (r) => users.find(u => u.role === r); const mette = byRole('pilot'); const lars = byRole('cab'); const jon = byRole('fenja'); if (!mette || !lars || !jon) { console.error(' seed.js users not found — run `pnpm db:seed` first.'); process.exit(1); } // Backdate Lars's cab membership to give realistic tenure on /pulse db.prepare(`UPDATE users SET cab_joined_date = date('now', '-2 years', '-4 months') WHERE id = ?`).run(lars.id); // Mark all three as recently seen so the "online now" chip strip has content // (current viewer is excluded from "others online" — see /pulse) db.prepare(`UPDATE users SET last_seen_at = datetime('now', '-2 minutes') WHERE id IN (?, ?, ?)`) .run(lars.id, mette.id, jon.id); const nowIso = (offsetSeconds = 0) => { const d = new Date(Date.now() + offsetSeconds * 1000); return d.toISOString().replace('T', ' ').slice(0, 19); }; // ── Pulse: open now, closes in 5 days ──────────────────────────────── const opensAt = nowIso(-3600); // opened an hour ago const closesAt = nowIso(5 * 24 * 3600); // closes in 5 days const options = [ 'Locking down on-prem deployment first', 'Pushing the traceability layer to GA', 'Going wide on document ingestion', 'Building the agentic query loop', ]; const pulseId = db.prepare(` INSERT INTO pulses (question, context, options, opens_at, closes_at, status, created_by) VALUES (?,?,?,?,?,?,?) `).run( 'Which milestone should we anchor Q3 around?', 'Council input on this directly shapes what the team works on in July–September. Read the roadmap before voting.', JSON.stringify(options), opensAt, closesAt, 'open', jon.id, ).lastInsertRowid; // Lars votes for the traceability option db.prepare('INSERT INTO votes (pulse_id, user_id, option_index, voted_at) VALUES (?,?,?,?)') .run(pulseId, lars.id, 1, nowIso(-2 * 3600)); // ── Roadmap: mark "Traceability layer" as shipping, attribute to Lars ── const traceability = db.prepare("SELECT id FROM roadmap_items WHERE title LIKE 'Traceability%'").get(); if (traceability) { db.prepare(`UPDATE roadmap_items SET status = 'shipping', shipped_at = datetime('now', '-2 days'), target = 'Live now' WHERE id = ?`).run(traceability.id); db.prepare('INSERT OR IGNORE INTO roadmap_attributions (roadmap_item_id, user_id) VALUES (?,?)').run(traceability.id, lars.id); } // ── Events ──────────────────────────────────────────────────────────── const dinnerStart = nowIso(38 * 24 * 3600); // ~5.5 weeks out db.prepare(` INSERT INTO events (slug, title, kind, description, location, starts_at, capacity, created_by) VALUES (?,?,?,?,?,?,?,?) `).run( 'kickoff-dinner-2026-06', 'Council kickoff dinner', 'dinner', 'A private dinner at the studio. Conversation about what we ship next, no slides.', 'Studio, Refshalevej · Copenhagen', dinnerStart, 12, jon.id, ); const dinnerId = db.prepare("SELECT id FROM events WHERE slug = 'kickoff-dinner-2026-06'").get().id; const officeHoursStart = nowIso(14 * 24 * 3600); // 2 weeks out db.prepare(` INSERT INTO events (slug, title, kind, description, location, starts_at, created_by) VALUES (?,?,?,?,?,?,?) `).run( 'office-hours-2026-05', 'Office hours with the founder', 'office_hours', '30-minute one-on-one slots. Open agenda. Book one or just drop by.', 'Virtual (link sent after RSVP)', officeHoursStart, jon.id, ); const officeHoursId = db.prepare("SELECT id FROM events WHERE slug = 'office-hours-2026-05'").get().id; // ── Activity rows ───────────────────────────────────────────────────── // Mix of real (Lars's vote, Jonathan's publish, Jonathan's ship) and // hand-crafted demo rows so the ticker has six items to scroll. const insertActivity = db.prepare(` INSERT INTO activity (actor_id, kind, subject_type, subject_id, created_at) VALUES (?,?,?,?,?) `); insertActivity.run(jon.id, 'pulse_opened', 'pulse', pulseId, nowIso(-3600)); insertActivity.run(lars.id, 'voted', 'pulse', pulseId, nowIso(-2 * 3600)); if (traceability) { insertActivity.run(jon.id, 'roadmap_shipped', 'roadmap', traceability.id, nowIso(-2 * 24 * 3600)); } insertActivity.run(lars.id, 'rsvped', 'event', dinnerId, nowIso(-8 * 3600)); insertActivity.run(mette.id,'rsvped', 'event', officeHoursId, nowIso(-30 * 60)); insertActivity.run(jon.id, 'booked_office_hours', 'event', officeHoursId, nowIso(-1 * 24 * 3600)); console.log(' demo data seeded:'); console.log(` pulse #${pulseId} (open, closes in 5 days)`); if (traceability) console.log(` roadmap #${traceability.id} → shipping, attributed to ${lars.name}`); console.log(` events: kickoff-dinner-2026-06, office-hours-2026-05`); console.log(` activity: 6 rows`); db.close();