Draft the OKR for Q3
Outline goals, key results, and timelines for the next quarter.
Schema-driven task card with time-aware color coding, popup and inline editing, clipboard and drag-drop payloads, and permission predicates.
Task Card is a time-aware task renderer for workflow apps, content schedulers, and agent run-queues. It carries the standard task fields (name, description, status, active, target/creator person, multi-image, multi-link, dates) and paints urgency onto the card border via an OKLCH ramp (green → red, urgency direction; past expireAt pins full red). The engine accepts setAt + startAt + expireAt OR duration with deterministic precedence (expireAt wins). Two edit modes ship in v0.1: a popup dialog form (default) and an inline-toggle mode (editable=true) where fields swap to inputs in place. JSON I/O mirrors card-tree patterns — controlled (value) or uncontrolled (defaultValue), 12-method imperative handle (getValue / getTree / isDirty / markClean / focusItem / copy / paste / setBorderColor / toggleActive / setLocked / openEdit / closeEdit), clipboard via application/x-ilinxa-task+json MIME + Cmd/Ctrl+C/V, HTML5 DnD with the same payload. Granular events (onFieldEdited / onStatusChanged / onItemAdded / onItemRemoved / onItemMoved / onColorOverridden / onActiveToggled / onLockedToggled / onCopy / onPaste / onChange) cover every mutation. Permissions mirror card-tree: declarative permissions matrix (default / byLevel / byItem with inherit cascade) + six per-action predicates + onPermissionDenied for analytics. Companion: ships taskCardKanbanRenderer for kanban-board with dragHandle:'shell' (v0.3 — the whole card is the drag activator; the adapter passes canDragItem={()=>false} to disable the card's own HTML5 drag so the two DnD systems don't fight). Per-card collapsibility (chevron in header) hides body + nested children to compact deep trees. v0.3 adds: a two-line header so the title never truncates to nothing; an editable status badge that becomes a status-change dropdown (statusEditable, default true); status tones (TaskStatusOption.tone done/blocked → gray border + dimmed overlay + auto-collapse) that decouple the status signal from the time-driven urgency border; and a priority + labels model extension (TaskItem.priority / labels with priorityOptions / labelOptions) rendered as a header meta row. Color override surfaces as a centered Dialog with palette swatches + custom CSS color input + an Auto button that clears the override and hands control back to the time engine.
pnpm dlx shadcn@latest initpnpm dlx shadcn@latest add @ilinxa/task-cardAdd -fixtures for dummy data:
pnpm dlx shadcn@latest add @ilinxa/task-card-fixturesCLI can't resolve @ilinxa? The namespace is listed in the official shadcn registry directory, so current CLIs need no configuration. If yours can't resolve it (older or pinned versions, self-hosted mirrors), register it manually in components.json:
"registries": {
"@ilinxa": "https://ui.ilinxa.com/r/{name}.json"
}Demo clock frozen at 2026-05-20T12:00:00.000Z so the border colors stay deterministic.
Outline goals, key results, and timelines for the next quarter.
Auth middleware rewrite — flagged by legal compliance.
Three-level nested example. Each child has its own dates.
The header gains a priority badge + label chips (border/text color only). The done / blocked statuses carry a tone so terminal cards get a gray border, a dimmed overlay, and auto-collapse. Turn on editableabove, then use the status badge (or the overlay's badge) to change status — changing a terminal status clears the overlay.
Priority + labels surface in the header meta row.
4 demos · click the pencil icon (or the action menu) to edit · Cmd/Ctrl+C / Cmd/Ctrl+V on a focused card copies / pastes as child · drag a card onto another's children area to paste.
kanban-boardThe same TaskCard slots into a kanban column via the exported taskCardKanbanRenderer. Grab a card anywhere to drag it across columns and drop it anywhere in a column; the card body keeps full edit / collapse semantics (renderer's dragHandle:"shell" + canDragItem disabled so the two drag systems don't fight).
Outline goals, key results, and timelines for the next quarter.
Auth middleware rewrite — flagged by legal compliance.
Note + todo renderers co-exist on the same board. Grab a todo card anywhere to drag it (shell-drag), and drop it anywhere in a column. The card body keeps full edit / collapse semantics.
"use client"; import { useState } from "react";import { Switch } from "@/components/ui/switch";import { Label } from "@/components/ui/label";import { TaskCard } from "./task-card";import { taskCardKanbanRenderer } from "./parts/kanban-adapter";import { BLOCKED_TASK, DEMO_ITEMS, DEMO_LABEL_OPTIONS, DEMO_NOW, DEMO_PRIORITY_OPTIONS, DEMO_STATUS_OPTIONS, DEMO_STATUS_OPTIONS_TONED, DONE_TASK, FRESH_TASK, NESTED_FAMILY, OVERDUE_TASK, PRIORITIZED_TASK, URGENT_TASK,} from "./dummy-data";import type { TaskColorRampPreset, TaskItem } from "./types";import { KanbanBoard, kanbanCardRenderer, kanbanNoteRenderer, type KanbanData,} from "@/registry/components/data/kanban-board"; const RAMPS: TaskColorRampPreset[] = ["default", "muted", "vivid", "monochrome"]; const KANBAN_INITIAL: KanbanData = { swimlanes: [], columns: [ { id: "col-todo", title: "To do", color: "slate", items: [ { id: `kanban-${FRESH_TASK.id}`, rendererId: "task-card", data: FRESH_TASK satisfies TaskItem, }, ], }, { id: "col-doing", title: "In progress", color: "lime", items: [ { id: `kanban-${URGENT_TASK.id}`, rendererId: "task-card", data: URGENT_TASK satisfies TaskItem, }, { id: "kanban-note-1", rendererId: "kanban-note", data: { title: "Demo note", body: "Note + todo renderers co-exist on the same board. Grab a todo card anywhere to drag it (shell-drag), and drop it anywhere in a column. The card body keeps full edit / collapse semantics.", }, }, ], }, { id: "col-done", title: "Done", color: "emerald", items: [ { id: `kanban-${OVERDUE_TASK.id}`, rendererId: "task-card", data: OVERDUE_TASK satisfies TaskItem, }, ], }, ],}; export default function TaskCardDemo() { const [editable, setEditable] = useState(false); const [ramp, setRamp] = useState<TaskColorRampPreset>("default"); return ( <div className="space-y-6"> <div className="flex flex-wrap items-center gap-4 rounded-lg border border-border bg-muted/40 p-3 text-sm"> <Label htmlFor="editable" className="flex items-center gap-2"> <Switch id="editable" checked={editable} onCheckedChange={setEditable} /> editable (inline-toggle mode) </Label> <div className="flex items-center gap-2"> <span className="text-muted-foreground">Ramp:</span> <div className="flex gap-1"> {RAMPS.map((r) => ( <button key={r} type="button" onClick={() => setRamp(r)} className={ "rounded border px-2 py-0.5 text-xs " + (ramp === r ? "border-foreground bg-foreground text-background" : "border-border bg-card hover:bg-muted") } > {r} </button> ))} </div> </div> <p className="text-xs text-muted-foreground"> Demo clock frozen at {DEMO_NOW.toISOString()} so the border colors stay deterministic. </p> </div> <section className="space-y-2"> <h3 className="text-sm font-semibold">Fresh task — early ramp (green)</h3> <TaskCard defaultValue={FRESH_TASK} editable={editable} colorRamp={ramp} now={DEMO_NOW} statusOptions={DEMO_STATUS_OPTIONS} /> </section> <section className="space-y-2"> <h3 className="text-sm font-semibold">Urgent task — late ramp (orange/red)</h3> <TaskCard defaultValue={URGENT_TASK} editable={editable} colorRamp={ramp} now={DEMO_NOW} statusOptions={DEMO_STATUS_OPTIONS} /> </section> <section className="space-y-2"> <h3 className="text-sm font-semibold">Overdue — pinned full red, with images</h3> <TaskCard defaultValue={OVERDUE_TASK} editable={editable} colorRamp={ramp} now={DEMO_NOW} statusOptions={DEMO_STATUS_OPTIONS} /> </section> <section className="space-y-2"> <h3 className="text-sm font-semibold"> Nested 3-level family — independent per-item dates, locked child, custom override </h3> <TaskCard defaultValue={NESTED_FAMILY} editable={editable} colorRamp={ramp} now={DEMO_NOW} statusOptions={DEMO_STATUS_OPTIONS} /> </section> <section className="space-y-2"> <h3 className="text-sm font-semibold"> v0.3 — priority & labels meta row + status tones </h3> <p className="text-xs text-muted-foreground"> The header gains a priority badge + label chips (border/text color only). The{" "} <code>done</code> / <code>blocked</code> statuses carry a <code>tone</code> so terminal cards get a gray border, a dimmed overlay, and auto-collapse. Turn on{" "} <strong>editable</strong> above, then use the status badge (or the overlay's badge) to change status — changing a terminal status clears the overlay. </p> <div className="grid gap-3 sm:grid-cols-3"> <TaskCard defaultValue={PRIORITIZED_TASK} editable={editable} colorRamp={ramp} now={DEMO_NOW} statusOptions={DEMO_STATUS_OPTIONS_TONED} priorityOptions={DEMO_PRIORITY_OPTIONS} labelOptions={DEMO_LABEL_OPTIONS} /> <TaskCard defaultValue={DONE_TASK} editable={editable} colorRamp={ramp} now={DEMO_NOW} statusOptions={DEMO_STATUS_OPTIONS_TONED} priorityOptions={DEMO_PRIORITY_OPTIONS} labelOptions={DEMO_LABEL_OPTIONS} /> <TaskCard defaultValue={BLOCKED_TASK} editable={editable} colorRamp={ramp} now={DEMO_NOW} statusOptions={DEMO_STATUS_OPTIONS_TONED} priorityOptions={DEMO_PRIORITY_OPTIONS} labelOptions={DEMO_LABEL_OPTIONS} /> </div> </section> <p className="text-xs text-muted-foreground"> {`${DEMO_ITEMS.length} demos · click the pencil icon (or the action menu) to edit · Cmd/Ctrl+C / Cmd/Ctrl+V on a focused card copies / pastes as child · drag a card onto another's children area to paste.`} </p> <section className="space-y-2 pt-4"> <h3 className="text-sm font-semibold"> Inside <code>kanban-board</code> </h3> <p className="text-xs text-muted-foreground"> {`The same TaskCard slots into a kanban column via the exported `} <code>taskCardKanbanRenderer</code> {`. Grab a card anywhere to drag it across columns and drop it anywhere in a column; the card body keeps full edit / collapse semantics (renderer's dragHandle:"shell" + canDragItem disabled so the two drag systems don't fight).`} </p> <KanbanBoard renderers={[ kanbanCardRenderer, kanbanNoteRenderer, taskCardKanbanRenderer, ]} defaultData={KANBAN_INITIAL} aria-label="Task kanban demo" /> </section> </div> );} Reach for TaskCard when you need a time-aware task surface — agent run queues, content schedules, sprint cards, workflow tasks. The card carries the standard task fields out of the box, paints urgency directly onto the chrome via the time engine, and is fully data-interactive (JSON I/O, clipboard, drag-drop).
For lightweight tree-row rendering (collapsible sub-items, inline checkbox, single-line description preview), reach for the sibling task-tree procomp — shares the same TaskItem schema so drags + clipboard payload work cross-procomp. For kanban, use kanban-board + the renderer exported below.
import { TaskCard } from "@/components/task-card";
export function Example() {
return (
<TaskCard
defaultValue={{
id: "task-001",
name: "Review pull request #482",
status: "in-progress",
active: true,
setAt: "2026-05-19T09:00:00Z",
expireAt: "2026-05-21T17:00:00Z",
}}
editable
/>
);
}The engine normalizes (now − startAt) / (expireAt − startAt) to [0, 1] and maps it through an OKLCH ramp — green at 0, red at 1. Past expireAt pins to full red. If expireAt is absent, duration is used instead. Per-item borderColor overrides the engine for that node. Set colorRamp to swap presets: 'default' | 'muted' | 'vivid' | 'monochrome' — or pass a custom function for full control.
onPermissionDenied fires with reason: 'locked'.Every card has a chevron at the left of its header. Clicking it collapses the card's body (description, time-info, chips, links, images) andits nested children — only the header stays visible. Per-item, independent at every depth. The collapse flag is UI-only state in the component's reducer (collapsedIds: ReadonlySet<string>) — it is not stored on TaskItem, so JSON I/O round-trips ignore it. Remounting via key resets all collapse state.
Click the action menu (...) on any card and choose Override color… to open a centered Dialog: 8 curated OKLCH palette swatches, a free-text input for any CSS color string, and an Autobutton (with sparkle icon) that clears the override and hands rendering back to the time engine. Dialog (vs Popover) avoids an outside-click race with the action menu's dismissal.
Use the imperative ref handle to copy(), paste(), getValue(), or getTree(). When a card has keyboard focus, Cmd/Ctrl+C copies it and Cmd/Ctrl+V pastes from the clipboard as a child. Drag a card onto another card's children area to drop the JSON payload as a child. The clipboard uses MIME application/x-ilinxa-task+json with a text/plain JSON fallback for browsers without custom-MIME support.
The engine reads new Date() at render time, which means server- and client-rendered HTML can compute slightly different border colors when nowisn't frozen. The card mitigates this with suppressHydrationWarning on the article root — the visual flash is sub-frame and the data is consistent server→client. For deterministic SSR (snapshot tests, server-rendered HTML caching), pass a frozen now: now={new Date("2026-05-20T12:00:00Z")}.
The package exports taskCardKanbanRenderer — a typed KanbanCardRenderer<TaskItem> with dragHandle: "header" so the kanban grip strip handles outer column reorder while the card body keeps its full edit / collapse / nested-DnD semantics. Drop it into the KanbanBoard renderers array alongside (or instead of) the built-in card renderers. The kanban-board demo on its own detail page shows mixed renderers; below is the minimum wiring.
import { KanbanBoard, kanbanCardRenderer } from "@/components/kanban-board";
import { taskCardKanbanRenderer } from "@/components/task-card";
import type { KanbanData } from "@/components/kanban-board";
const initial: KanbanData = {
swimlanes: [],
columns: [
{
id: "col-todo",
title: "To do",
color: "slate",
items: [
{
id: "kanban-task-1",
rendererId: "task-card", // matches renderer.id
data: {
id: "task-001",
name: "Review PR #482",
status: "todo",
active: true,
setAt: "2026-05-19T09:00:00Z",
expireAt: "2026-05-21T17:00:00Z",
},
},
],
},
{ id: "col-doing", title: "In progress", color: "lime", items: [] },
{ id: "col-done", title: "Done", color: "emerald", items: [] },
],
};
export function TaskKanbanExample() {
return (
<KanbanBoard
renderers={[kanbanCardRenderer, taskCardKanbanRenderer]}
defaultData={initial}
aria-label="Task kanban"
/>
);
}rendererId: "task-card" on each kanban item must match taskCardKanbanRenderer.id — the board uses this to look up the renderer for each item. Items with no matching renderer show a placeholder card.
todo-card-tree-node sibling adapter, shipping separately): exposes a NodeRenderer that wraps this card.TaskItem schema and DnD payload so drags cross-procomp.