import { useId, type ReactNode } from 'react' import type { BoardType } from '../../shared/board-types.ts' import type { Phase, Status } from '../../shared/status.ts' /** * Spec 20's glyphs, custom in a 14-unit space so they sit on the sidebar's 14px rows: one per * board type (stroked in currentColor, so a row's colour - accent when current - carries over), * and Linear-style status rings (their own colours: a status reads the same in every theme). */ const TYPE_PATHS: Record, ReactNode> = { start: , feature: , surface: <>, project: <>, feedback: , context: , deck: <>, archive: <>, } /** A board type's glyph; `plain` is null - the caller keeps its own board icon. */ export function TypeIcon({ type, size = 14 }: { type: BoardType; size?: number }) { if (type === 'plain') return null return ( {TYPE_PATHS[type]} ) } const GRAY = 'var(--glass-ink-3)' const YELLOW = '#e2b203' const RED = '#e5484d' // done is the content palette's green (Apple's systemGreen, per theme - styles.css), the green Marver // keeps for done alone; archived is Apple's brown - a cardboard box - per theme as well const GREEN = 'var(--status-done, #34c759)' // Building is Marver's blue: yellow is being shaped, blue is being built, green is shipped. Its own // token, not the accent - interact mode re-tints the accent, and a status does not change with a mode const BLUE = 'var(--status-building, #0088ff)' const ARCHIVED = 'var(--status-archived, #956d51)' /** A pie wedge of the ring's interior, `f` of the way round from twelve o'clock. */ const pie = (f: number, r = 3.4) => { const a = f * 2 * Math.PI const x = 7 + r * Math.sin(a), y = 7 - r * Math.cos(a) return `M7 7 L7 ${7 - r} A${r} ${r} 0 ${f > 0.5 ? 1 : 0} 1 ${x.toFixed(2)} ${y.toFixed(2)} Z` } /** A status glyph, on one rule: a status still open is an outline in its colour (a ring - In progress * fills it by phase: a quarter at spec, half at lo-fi, three quarters at hi-fi; Building is the code * inside a blue ring, the design agreed and the code underway); a settled one is * filled - Done a green disc, Archived a solid archive box (no ring: it is out of the flow, not a * step in it). Done, reported stays an outline: a written claim is not settled until confirmed. */ export function StatusIcon({ status, fill, size = 14 }: { status: Status; fill?: Phase; size?: number }) { const ring = (c: string, dash?: string) => // Done's check is cut OUT of its disc, not painted on it: the row behind shows through - the // panel, a hover, the current row's wash - in either theme (a painted white check glared in dark) const cut = `mv-st-${useId().replace(/[^\w-]/g, '')}` return ( {status === 'backlog' && ring(GRAY, '1.3 1.75')} {status === 'todo' && ring(GRAY)} {status === 'in-progress' && (<>{ring(YELLOW)}{fill && })} {status === 'building' && (<>{ring(BLUE)})} {status === 'blocked' && (<>{ring(RED)})} {status === 'done' && (<> )} {status === 'done-reported' && (<>{ring(GREEN)})} {status === 'unknown' && (<>{ring(GRAY)})} {status === 'paused' && (<>{ring(GRAY)})} {status === 'archived' && (<> )} ) } /** A sidebar icon while an agent works on what it holds (spec 20, the live signal): accent blue, with * a highlight sweeping across it - the canvas's working shimmer in miniature, so the sidebar and the * frames read as one signal. The highlight is the same icon again, masked to a moving band; it stops * for reduced motion, leaving the blue. Automatic only - it lasts exactly as long as the work. */ export function LiveIcon({ live, children }: { live: boolean; children: ReactNode }) { if (!live) return <>{children} return {children}{children} }