feat: phase 3 renderers & ergonomics — index/console, ⌘K palette, icon suggest

- Index renderer (editorial): serif masthead + section heads, numbered
  full-width rows, live stat strip (time/services/up/down/widgets)
- Console renderer (terminal): $ dash status banner, bordered stat panels,
  ## section heads, › prompt rows, [ up ]/[ down ] tags
- Renderer contract gains index prop for row/section numbering
- Settings: 2x2 renderer radio grid with per-renderer descriptions
- ⌘K/Ctrl+K// palette: actions + service endpoints, arrow-key nav,
  zero-dep implementation on the existing Dialog primitive
- Icon picker: dashboard-icons slug suggestion chip + live preview
- Fonts: Instrument Serif, JetBrains Mono (fontsource)
- Fix: global :focus-visible rule was unlayered and silently overrode all
  focus-visible: utilities since phase 1 — moved into @layer base

Verified: typecheck/lint/build clean, dnd + collapse + popover in both new
renderers, palette filter + execute, icon suggest resolves real CDN assets,
360px + light theme, docker image 43.9 MB serving board + widget types.

Generated with [Devin](https://devin.ai)

Authored By: TDvorak <[email protected]>
Co-Authored-By: Devin <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
Tomas Dvorak committed 2026-09-17 20:29:22 +02:00
1 parent 8afc7df53b
commit 55fb57456e
16 files changed
+803 -51

No files matched your search

+22 -11
View File
@@ -1,7 +1,8 @@
import { useRef, useState } from "react";
import { Download, Upload } from "lucide-react";
import { useBoard } from "@/board/store";
import { rendererLabels, rendererList } from "@/renderers";
import { rendererDescriptions, rendererLabels, rendererList } from "@/renderers";
import { cn } from "@/lib/utils";
import { Button } from "@/components/ui/button";
import {
Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle,
@@ -46,16 +47,26 @@ export function SettingsDialog({ open, onOpenChange }: { open: boolean; onOpenCh
<div className="flex flex-col gap-4">
<div className="flex flex-col gap-1.5">
<Label>Renderer</Label>
<Select value={board.renderer} onValueChange={(v) => {
if (v === "bento" || v === "cards") void board.setSetting("renderer", v);
}}>
<SelectTrigger><SelectValue /></SelectTrigger>
<SelectContent>
{rendererList.map((r) => (
<SelectItem key={r} value={r}>{rendererLabels[r]}</SelectItem>
))}
</SelectContent>
</Select>
<div className="grid grid-cols-2 gap-1.5" role="radiogroup" aria-label="Renderer">
{rendererList.map((r) => (
<button
key={r}
type="button"
role="radio"
aria-checked={board.renderer === r}
onClick={() => void board.setSetting("renderer", r)}
className={cn(
"rounded-[8px] border px-3 py-2.5 text-left transition-colors",
board.renderer === r
? "border-border-strong bg-surface-hover"
: "border-border hover:border-border-strong hover:bg-surface-hover",
)}
>
<span className="block text-[12.5px] font-medium">{rendererLabels[r]}</span>
<span className="block font-mono text-[10px] text-text-faint">{rendererDescriptions[r]}</span>
</button>
))}
</div>
</div>
<div className="flex flex-col gap-1.5">