feat(roadmap): add planned status
Adds a fifth roadmap status, `planned`, for items that are committed and scheduled but not yet started — sitting between `in_beta` and `exploring` in the progression. Rendered with the design system's indigo pigment (#5a6d83) on the route, carousel, legend, and admin pill. Migration 0008 widens the status CHECK constraint via a table rebuild (SQLite can't alter it in place), preserving rows and attributions. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
a520e8534e
commit
59842432bd
8 changed files with 52 additions and 2 deletions
39
migrations/0008_roadmap_planned.sql
Normal file
39
migrations/0008_roadmap_planned.sql
Normal file
|
|
@ -0,0 +1,39 @@
|
||||||
|
-- Roadmap status enum gains a fifth value `planned` for items that are
|
||||||
|
-- committed and scheduled but not yet started — sitting between `in_beta`
|
||||||
|
-- and `exploring` in the progression.
|
||||||
|
--
|
||||||
|
-- SQLite can't widen a CHECK constraint in place, so this is a full table
|
||||||
|
-- rebuild (same approach as 0006). roadmap_attributions has an ON DELETE
|
||||||
|
-- CASCADE FK to roadmap_items(id), so foreign keys are toggled off around
|
||||||
|
-- the rebuild to preserve attribution rows across the DROP/RENAME. The
|
||||||
|
-- metadata_text column added in 0007 is carried through.
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = OFF;
|
||||||
|
|
||||||
|
CREATE TABLE roadmap_items_new (
|
||||||
|
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||||
|
title TEXT NOT NULL,
|
||||||
|
description TEXT NOT NULL DEFAULT '',
|
||||||
|
status TEXT NOT NULL DEFAULT 'exploring'
|
||||||
|
CHECK(status IN ('shipping','in_beta','planned','exploring','considering')),
|
||||||
|
target TEXT,
|
||||||
|
display_order INTEGER NOT NULL DEFAULT 0,
|
||||||
|
shipped_at TEXT,
|
||||||
|
metadata_text TEXT,
|
||||||
|
created_at TEXT NOT NULL DEFAULT (datetime('now')),
|
||||||
|
updated_at TEXT NOT NULL DEFAULT (datetime('now'))
|
||||||
|
);
|
||||||
|
|
||||||
|
INSERT INTO roadmap_items_new
|
||||||
|
(id, title, description, status, target, display_order, shipped_at, metadata_text, created_at, updated_at)
|
||||||
|
SELECT
|
||||||
|
id, title, description, status, target, display_order, shipped_at, metadata_text, created_at, updated_at
|
||||||
|
FROM roadmap_items;
|
||||||
|
|
||||||
|
DROP TABLE roadmap_items;
|
||||||
|
ALTER TABLE roadmap_items_new RENAME TO roadmap_items;
|
||||||
|
|
||||||
|
CREATE INDEX idx_roadmap_status ON roadmap_items(status, display_order);
|
||||||
|
CREATE INDEX idx_roadmap_shipped ON roadmap_items(shipped_at);
|
||||||
|
|
||||||
|
PRAGMA foreign_keys = ON;
|
||||||
|
|
@ -802,6 +802,7 @@
|
||||||
.pill-declined { background: rgba(0, 0, 0, 0.04); color: var(--on-surface-muted); }
|
.pill-declined { background: rgba(0, 0, 0, 0.04); color: var(--on-surface-muted); }
|
||||||
.pill-shipping { background: rgba(109, 140, 124, 0.18); color: #5a7268; }
|
.pill-shipping { background: rgba(109, 140, 124, 0.18); color: #5a7268; }
|
||||||
.pill-in-beta { background: rgba(185, 107, 88, 0.10); color: #b96b58; }
|
.pill-in-beta { background: rgba(185, 107, 88, 0.10); color: #b96b58; }
|
||||||
|
.pill-planned { background: rgba(90, 109, 131, 0.12); color: #5a6d83; }
|
||||||
.pill-exploring { background: rgba(186, 186, 176, 0.20); color: var(--on-surface-variant); }
|
.pill-exploring { background: rgba(186, 186, 176, 0.20); color: var(--on-surface-variant); }
|
||||||
.pill-considering{ background: rgba(186, 186, 176, 0.10); color: var(--on-surface-muted); }
|
.pill-considering{ background: rgba(186, 186, 176, 0.10); color: var(--on-surface-muted); }
|
||||||
.pill-active { background: rgba(109, 140, 124, 0.15); color: #6d8c7c; }
|
.pill-active { background: rgba(109, 140, 124, 0.15); color: #6d8c7c; }
|
||||||
|
|
|
||||||
|
|
@ -48,6 +48,7 @@ export const roadmapResource: Resource<RoadmapItemWithAttribution> = {
|
||||||
pillVariants: {
|
pillVariants: {
|
||||||
shipping: { label: 'Shipping', class: 'pill-shipping' },
|
shipping: { label: 'Shipping', class: 'pill-shipping' },
|
||||||
in_beta: { label: 'In beta', class: 'pill-in-beta' },
|
in_beta: { label: 'In beta', class: 'pill-in-beta' },
|
||||||
|
planned: { label: 'Planned', class: 'pill-planned' },
|
||||||
exploring: { label: 'Exploring', class: 'pill-exploring' },
|
exploring: { label: 'Exploring', class: 'pill-exploring' },
|
||||||
considering: { label: 'Considering', class: 'pill-considering' },
|
considering: { label: 'Considering', class: 'pill-considering' },
|
||||||
},
|
},
|
||||||
|
|
@ -69,6 +70,7 @@ export const roadmapResource: Resource<RoadmapItemWithAttribution> = {
|
||||||
{ key: 'all', label: 'All', predicate: () => true, isDefault: true },
|
{ key: 'all', label: 'All', predicate: () => true, isDefault: true },
|
||||||
{ key: 'shipping', label: 'Shipping', predicate: (i) => i.status === 'shipping' },
|
{ key: 'shipping', label: 'Shipping', predicate: (i) => i.status === 'shipping' },
|
||||||
{ key: 'in_beta', label: 'In beta', predicate: (i) => i.status === 'in_beta' },
|
{ key: 'in_beta', label: 'In beta', predicate: (i) => i.status === 'in_beta' },
|
||||||
|
{ key: 'planned', label: 'Planned', predicate: (i) => i.status === 'planned' },
|
||||||
{ key: 'exploring', label: 'Exploring', predicate: (i) => i.status === 'exploring' },
|
{ key: 'exploring', label: 'Exploring', predicate: (i) => i.status === 'exploring' },
|
||||||
{ key: 'considering', label: 'Considering', predicate: (i) => i.status === 'considering' },
|
{ key: 'considering', label: 'Considering', predicate: (i) => i.status === 'considering' },
|
||||||
],
|
],
|
||||||
|
|
@ -99,6 +101,7 @@ export const roadmapResource: Resource<RoadmapItemWithAttribution> = {
|
||||||
options: [
|
options: [
|
||||||
{ value: 'shipping', label: 'Shipping' },
|
{ value: 'shipping', label: 'Shipping' },
|
||||||
{ value: 'in_beta', label: 'In beta' },
|
{ value: 'in_beta', label: 'In beta' },
|
||||||
|
{ value: 'planned', label: 'Planned' },
|
||||||
{ value: 'exploring', label: 'Exploring' },
|
{ value: 'exploring', label: 'Exploring' },
|
||||||
{ value: 'considering', label: 'Considering' },
|
{ value: 'considering', label: 'Considering' },
|
||||||
],
|
],
|
||||||
|
|
|
||||||
|
|
@ -10,6 +10,7 @@ const { items } = Astro.props;
|
||||||
const STATUS_LABEL: Record<RoadmapStatus, string> = {
|
const STATUS_LABEL: Record<RoadmapStatus, string> = {
|
||||||
shipping: 'SHIPPING',
|
shipping: 'SHIPPING',
|
||||||
in_beta: 'IN BETA',
|
in_beta: 'IN BETA',
|
||||||
|
planned: 'PLANNED',
|
||||||
exploring: 'EXPLORING',
|
exploring: 'EXPLORING',
|
||||||
considering: 'CONSIDERING',
|
considering: 'CONSIDERING',
|
||||||
};
|
};
|
||||||
|
|
@ -17,6 +18,7 @@ const STATUS_LABEL: Record<RoadmapStatus, string> = {
|
||||||
const STATUS_LABEL_COLOR: Record<RoadmapStatus, string> = {
|
const STATUS_LABEL_COLOR: Record<RoadmapStatus, string> = {
|
||||||
shipping: 'var(--pigment-copper)',
|
shipping: 'var(--pigment-copper)',
|
||||||
in_beta: 'var(--pigment-terracotta)',
|
in_beta: 'var(--pigment-terracotta)',
|
||||||
|
planned: 'var(--pigment-indigo)',
|
||||||
exploring: '#b4b2a9',
|
exploring: '#b4b2a9',
|
||||||
considering: '#b4b2a9',
|
considering: '#b4b2a9',
|
||||||
};
|
};
|
||||||
|
|
@ -24,6 +26,7 @@ const STATUS_LABEL_COLOR: Record<RoadmapStatus, string> = {
|
||||||
const STATUS_DOT_COLOR: Record<RoadmapStatus, string> = {
|
const STATUS_DOT_COLOR: Record<RoadmapStatus, string> = {
|
||||||
shipping: 'var(--pigment-copper)',
|
shipping: 'var(--pigment-copper)',
|
||||||
in_beta: 'var(--pigment-terracotta)',
|
in_beta: 'var(--pigment-terracotta)',
|
||||||
|
planned: 'var(--pigment-indigo)',
|
||||||
exploring: '#b4b2a9',
|
exploring: '#b4b2a9',
|
||||||
considering: '#d4d2c8',
|
considering: '#d4d2c8',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -25,18 +25,21 @@ const travelledStop = travelledStopFor(items.map(i => i.status));
|
||||||
const STATUS_LABEL: Record<RoadmapStatus, string> = {
|
const STATUS_LABEL: Record<RoadmapStatus, string> = {
|
||||||
shipping: 'SHIPPING',
|
shipping: 'SHIPPING',
|
||||||
in_beta: 'IN BETA',
|
in_beta: 'IN BETA',
|
||||||
|
planned: 'PLANNED',
|
||||||
exploring: 'EXPLORING',
|
exploring: 'EXPLORING',
|
||||||
considering: 'CONSIDERING',
|
considering: 'CONSIDERING',
|
||||||
};
|
};
|
||||||
const STATUS_LABEL_COLOR: Record<RoadmapStatus, string> = {
|
const STATUS_LABEL_COLOR: Record<RoadmapStatus, string> = {
|
||||||
shipping: '#6d8c7c',
|
shipping: '#6d8c7c',
|
||||||
in_beta: '#b96b58',
|
in_beta: '#b96b58',
|
||||||
|
planned: '#5a6d83',
|
||||||
exploring: '#b4b2a9',
|
exploring: '#b4b2a9',
|
||||||
considering: '#b4b2a9',
|
considering: '#b4b2a9',
|
||||||
};
|
};
|
||||||
const STATUS_DOT_COLOR: Record<RoadmapStatus, string> = {
|
const STATUS_DOT_COLOR: Record<RoadmapStatus, string> = {
|
||||||
shipping: '#6d8c7c',
|
shipping: '#6d8c7c',
|
||||||
in_beta: '#b96b58',
|
in_beta: '#b96b58',
|
||||||
|
planned: '#5a6d83',
|
||||||
exploring: '#b4b2a9',
|
exploring: '#b4b2a9',
|
||||||
considering: '#d4d2c8',
|
considering: '#d4d2c8',
|
||||||
};
|
};
|
||||||
|
|
|
||||||
|
|
@ -688,7 +688,7 @@ export function countPulseParticipants(pulseId: number): number {
|
||||||
|
|
||||||
// ── Roadmap items ────────────────────────────────────────────────
|
// ── Roadmap items ────────────────────────────────────────────────
|
||||||
|
|
||||||
export type RoadmapStatus = 'shipping' | 'in_beta' | 'exploring' | 'considering';
|
export type RoadmapStatus = 'shipping' | 'in_beta' | 'planned' | 'exploring' | 'considering';
|
||||||
|
|
||||||
export interface RoadmapItem {
|
export interface RoadmapItem {
|
||||||
id: number;
|
id: number;
|
||||||
|
|
|
||||||
|
|
@ -113,7 +113,7 @@ export function computeRouteLayout(opts: LayoutOpts): LayoutResult {
|
||||||
* - Clamped to [0, 0.98] so the fade-to-ahead is always visible
|
* - Clamped to [0, 0.98] so the fade-to-ahead is always visible
|
||||||
*/
|
*/
|
||||||
export function travelledStopFor(
|
export function travelledStopFor(
|
||||||
statuses: ReadonlyArray<'shipping' | 'in_beta' | 'exploring' | 'considering'>,
|
statuses: ReadonlyArray<'shipping' | 'in_beta' | 'planned' | 'exploring' | 'considering'>,
|
||||||
): number {
|
): number {
|
||||||
if (statuses.length === 0) return 0;
|
if (statuses.length === 0) return 0;
|
||||||
let last = -1;
|
let last = -1;
|
||||||
|
|
|
||||||
|
|
@ -28,6 +28,7 @@ const items = getAllRoadmapItems()
|
||||||
<div class="roadmap-legend" aria-label="Status legend">
|
<div class="roadmap-legend" aria-label="Status legend">
|
||||||
<span><i style="background:#6d8c7c"></i>Shipping</span>
|
<span><i style="background:#6d8c7c"></i>Shipping</span>
|
||||||
<span><i style="background:#b96b58"></i>In beta</span>
|
<span><i style="background:#b96b58"></i>In beta</span>
|
||||||
|
<span><i style="background:#5a6d83"></i>Planned</span>
|
||||||
<span><i style="background:#b4b2a9"></i>Exploring</span>
|
<span><i style="background:#b4b2a9"></i>Exploring</span>
|
||||||
<span><i style="background:#d4d2c8"></i>Considering</span>
|
<span><i style="background:#d4d2c8"></i>Considering</span>
|
||||||
</div>
|
</div>
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue