diff --git a/migrations/0007_roadmap_metadata.sql b/migrations/0007_roadmap_metadata.sql new file mode 100644 index 0000000..bde60b4 --- /dev/null +++ b/migrations/0007_roadmap_metadata.sql @@ -0,0 +1,6 @@ +-- Roadmap items gain an optional metadata_text field — a short admin-set +-- narrative cue shown in the route card's hover expansion. Free-form, +-- ~60 chars suggested in admin helper text. NULL when not set; UI hides +-- the line in that case. + +ALTER TABLE roadmap_items ADD COLUMN metadata_text TEXT; diff --git a/src/components/admin/RoadmapTab.astro b/src/components/admin/RoadmapTab.astro index ce6546f..db42c6b 100644 --- a/src/components/admin/RoadmapTab.astro +++ b/src/components/admin/RoadmapTab.astro @@ -66,6 +66,20 @@ const grouped: Record = { +
+ + + A short narrative cue shown on hover in /roadmap. Optional. +
+
Attributed members (who shaped this) {cabUsers.map(u => ( @@ -183,4 +197,6 @@ const grouped: Record = { padding: 0; } .action-link:hover { color: var(--on-surface-variant); border-bottom: none; } + + .muted { color: var(--on-surface-muted); } diff --git a/src/lib/db.ts b/src/lib/db.ts index 0ef8e12..4edba6f 100644 --- a/src/lib/db.ts +++ b/src/lib/db.ts @@ -675,6 +675,7 @@ export interface RoadmapItem { target: string | null; display_order: number; shipped_at: string | null; + metadata_text: string | null; // short narrative cue shown on hover in /roadmap created_at: string; updated_at: string; } @@ -689,11 +690,12 @@ export function createRoadmapItem(data: { status: RoadmapStatus; target?: string | null; display_order?: number; + metadata_text?: string | null; }): number { const shipped_at = data.status === 'shipping' ? new Date().toISOString().slice(0, 19).replace('T', ' ') : null; const r = db.prepare(` - INSERT INTO roadmap_items (title, description, status, target, display_order, shipped_at) - VALUES (?,?,?,?,?,?) + INSERT INTO roadmap_items (title, description, status, target, display_order, shipped_at, metadata_text) + VALUES (?,?,?,?,?,?,?) `).run( data.title, data.description, @@ -701,6 +703,7 @@ export function createRoadmapItem(data: { data.target ?? null, data.display_order ?? 0, shipped_at, + data.metadata_text ?? null, ); return Number(r.lastInsertRowid); } @@ -715,6 +718,7 @@ export function updateRoadmapItem(id: number, data: { status: RoadmapStatus; target: string | null; display_order: number; + metadata_text?: string | null; }): { shippedNow: boolean } { const current = db.prepare('SELECT status, shipped_at FROM roadmap_items WHERE id = ?') .get(id) as { status: RoadmapStatus; shipped_at: string | null } | undefined; @@ -728,9 +732,9 @@ export function updateRoadmapItem(id: number, data: { db.prepare(` UPDATE roadmap_items SET title = ?, description = ?, status = ?, target = ?, display_order = ?, - shipped_at = ?, updated_at = datetime('now') + shipped_at = ?, metadata_text = ?, updated_at = datetime('now') WHERE id = ? - `).run(data.title, data.description, data.status, data.target, data.display_order, shipped_at, id); + `).run(data.title, data.description, data.status, data.target, data.display_order, shipped_at, data.metadata_text ?? null, id); return { shippedNow }; } diff --git a/src/pages/admin/index.astro b/src/pages/admin/index.astro index e3294ea..8ca3a6e 100644 --- a/src/pages/admin/index.astro +++ b/src/pages/admin/index.astro @@ -195,17 +195,18 @@ if (Astro.request.method === 'POST') { // ── Roadmap ────────────────────────────────────────────────── } else if (action === 'create_roadmap' || action === 'update_roadmap') { - const title = String(data.get('title') ?? '').trim(); - const description = String(data.get('description') ?? '').trim(); - const status = String(data.get('status') ?? '') as RoadmapStatus; - const target = String(data.get('target') ?? '').trim() || null; - const displayOrder = Number(data.get('display_order') ?? 0); + const title = String(data.get('title') ?? '').trim(); + const description = String(data.get('description') ?? '').trim(); + const status = String(data.get('status') ?? '') as RoadmapStatus; + const target = String(data.get('target') ?? '').trim() || null; + const displayOrder = Number(data.get('display_order') ?? 0); + const metadataText = String(data.get('metadata_text') ?? '').trim() || null; const attributedIds = data.getAll('attributed_user_ids').map(v => Number(v)).filter(Boolean); if (!title || !['shipping','in_beta','exploring','considering'].includes(status)) { formError = 'Title and status are required.'; } else if (action === 'create_roadmap') { - const id = createRoadmapItem({ title, description, status, target, display_order: displayOrder }); + const id = createRoadmapItem({ title, description, status, target, display_order: displayOrder, metadata_text: metadataText }); setRoadmapAttributions(id, attributedIds); if (status === 'shipping') recordActivity(user.id, 'roadmap_shipped', 'roadmap', id); return Astro.redirect('/admin?tab=roadmap&msg=roadmap_created'); @@ -213,7 +214,7 @@ if (Astro.request.method === 'POST') { const id = Number(data.get('roadmap_id')); if (id) { const { shippedNow } = updateRoadmapItem(id, { - title, description, status, target, display_order: displayOrder, + title, description, status, target, display_order: displayOrder, metadata_text: metadataText, }); setRoadmapAttributions(id, attributedIds); if (shippedNow) recordActivity(user.id, 'roadmap_shipped', 'roadmap', id); @@ -284,11 +285,11 @@ function moveRoadmapItem(id: number, dir: 'up' | 'down'): void { const other = sameStatus[swapIdx]; updateRoadmapItem(item.id, { title: item.title, description: item.description, status: item.status, - target: item.target, display_order: other.display_order, + target: item.target, display_order: other.display_order, metadata_text: item.metadata_text, }); updateRoadmapItem(other.id, { title: other.title, description: other.description, status: other.status, - target: other.target, display_order: item.display_order, + target: other.target, display_order: item.display_order, metadata_text: other.metadata_text, }); }