This commit is contained in:
ray zhou
2026-05-29 11:21:40 +08:00
parent 5d6d482efe
commit f71a5c59af
447 changed files with 32245 additions and 116 deletions

View File

@@ -0,0 +1,264 @@
/**
* Design tokens for `cursor/canvas` (standalone; no UI framework dependency).
*
* Color values are aligned with the Cursor app dark theme (`packages/ui` `cursor-dark` sources).
*/
export declare const canvasPaletteDark: {
readonly foreground: "#E4E4E4EB";
readonly foregroundSecondary: "#E4E4E48D";
readonly foregroundTertiary: "#E4E4E45E";
readonly foregroundQuaternary: "#E4E4E442";
readonly editor: "#181818";
readonly chrome: "#141414";
readonly sidebar: "#141414";
readonly elevated: "#181818";
readonly fillPrimary: "#E4E4E430";
readonly fillSecondary: "#E4E4E41E";
readonly fillTertiary: "#E4E4E411";
readonly fillQuaternary: "#E4E4E40A";
readonly strokePrimary: "#E4E4E433";
readonly strokeSecondary: "#E4E4E41F";
readonly strokeTertiary: "#E4E4E414";
readonly accent: "#599CE7";
readonly buttonBackground: "#599CE7";
readonly buttonForeground: "#191c22";
readonly buttonHoverBackground: "#6AABE9";
readonly link: "#87c3ff";
readonly diffInsertedLine: "#3FA26633";
readonly diffRemovedLine: "#B8004933";
readonly diffStripAdded: "#3FA2668F";
readonly diffStripRemoved: "#FC6B838F";
};
/**
* Light-mode palette derived from `packages/ui/src/tokens/themes/cursor-core/light.ts`.
* Base color: #141414. Same percentages as dark (regular light has no overrides
* in CURSOR_SEMANTIC_OVERRIDES — only high-contrast does).
*/
export declare const canvasPaletteLight: {
readonly foreground: "#141414F0";
readonly foregroundSecondary: "#141414BD";
readonly foregroundTertiary: "#1414148A";
readonly foregroundQuaternary: "#1414145C";
readonly editor: "#FCFCFC";
readonly chrome: "#F8F8F8";
readonly sidebar: "#F3F3F3";
readonly elevated: "#FCFCFC";
readonly fillPrimary: "#14141433";
readonly fillSecondary: "#14141424";
readonly fillTertiary: "#14141414";
readonly fillQuaternary: "#1414140F";
readonly strokePrimary: "#14141433";
readonly strokeSecondary: "#1414141F";
readonly strokeTertiary: "#14141414";
readonly accent: "#3685BF";
readonly buttonBackground: "#3685BF";
readonly buttonForeground: "#FCFCFC";
readonly buttonHoverBackground: "#2E76AB";
readonly link: "#3685BF";
readonly diffInsertedLine: "#1F8A651F";
readonly diffRemovedLine: "#CF2D5614";
readonly diffStripAdded: "#1F8A65CC";
readonly diffStripRemoved: "#CF2D56CC";
};
export interface CanvasPalette {
readonly foreground: string;
readonly foregroundSecondary: string;
readonly foregroundTertiary: string;
readonly foregroundQuaternary: string;
readonly editor: string;
readonly chrome: string;
readonly sidebar: string;
readonly elevated: string;
readonly fillPrimary: string;
readonly fillSecondary: string;
readonly fillTertiary: string;
readonly fillQuaternary: string;
readonly strokePrimary: string;
readonly strokeSecondary: string;
readonly strokeTertiary: string;
readonly accent: string;
readonly buttonBackground: string;
readonly buttonForeground: string;
readonly buttonHoverBackground: string;
readonly link: string;
readonly diffInsertedLine: string;
readonly diffRemovedLine: string;
readonly diffStripAdded: string;
readonly diffStripRemoved: string;
}
/**
* Chart color palette — distilled from portal-website analytics charts.
* 88% opacity (E0) softens vibrancy without dulling; palette maximizes
* hue + luminosity spread for distinguishable multi-series charts.
*/
export declare const chartPalette: {
readonly green: "#1F8A65E8";
readonly darkGreen: "#0D855AE0";
readonly lightGreen: "#52B896E0";
readonly mintGreen: "#7DCAB0E0";
readonly blue: "#2E79B5E0";
readonly lightBlue: "#70B0D8E0";
readonly indigo: "#5A6CC0F0";
readonly lightIndigo: "#9AAADCE0";
readonly purple: "#7B64B8F0";
readonly lightPurple: "#AA98D8E0";
readonly warmPink: "#C85898E0";
readonly lightPink: "#E8A0C4E0";
readonly brightOrange: "#F0A040E0";
readonly deepOrange: "#C06028E0";
readonly goldenYellow: "#E8C030E0";
readonly darkAmber: "#C04848E0";
readonly warmPeach: "#F0A088E0";
readonly vibrantTeal: "#2A9A8AE0";
readonly muted: "#8888A8E0";
readonly neutralLine: "#888899D0";
};
/**
* Shared category palette for canvas primitives that show categorical tints
* (`Swatch`, `UsageBar` segments, etc.). Uses a coherent subset of
* `chartPalette` so a category's color reads consistently across any
* primitive that consumes it.
*
* The insertion order here is the canonical category order — primitives
* that auto-assign colors (e.g. `UsageBar` segments without an explicit
* `color`) cycle through these keys in order.
*/
export declare const colorPalette: {
readonly gray: "#8888A8E0";
readonly purple: "#7B64B8F0";
readonly green: "#1F8A65E8";
readonly yellow: "#E8C030E0";
readonly pink: "#C85898E0";
readonly blue: "#2E79B5E0";
readonly orange: "#F0A040E0";
};
export type Color = keyof typeof colorPalette;
/**
* Auto-color rotation for `UsageBar` segments without an explicit `color`.
* Decoupled from `colorPalette`'s declaration order so the palette can grow
* or be reordered without changing how unspecified segments cycle.
*
* Matches the original `packages/ui` `ContextUsageTray` order so the same
* segment index lands on the same hue as the source.
*/
export declare const usageColorSequence: readonly Color[];
/**
* Ordered array for automatic series coloring — alternates dark/light across
* distinct hue families for maximum perceptual separation.
*/
export declare const chartColorSequence: readonly ["#1F8A65E8", "#70B0D8E0", "#5A6CC0F0", "#F0A040E0", "#C06028E0", "#E8C030E0", "#C85898E0", "#F0A088E0", "#7B64B8F0", "#7DCAB0E0", "#8888A8E0", "#2A9A8AE0"];
declare function buildTokens(palette: CanvasPalette): {
bg: {
editor: string;
chrome: string;
elevated: string;
};
text: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
link: string;
onAccent: string;
};
stroke: {
primary: string;
secondary: string;
tertiary: string;
};
fill: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
};
accent: {
primary: string;
control: string;
controlHover: string;
};
diff: {
insertedLine: string;
removedLine: string;
stripAdded: string;
stripRemoved: string;
};
};
/** Semantic colors for components (spacing and radius live in `theme.ts`). */
export declare const canvasTokens: {
bg: {
editor: string;
chrome: string;
elevated: string;
};
text: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
link: string;
onAccent: string;
};
stroke: {
primary: string;
secondary: string;
tertiary: string;
};
fill: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
};
accent: {
primary: string;
control: string;
controlHover: string;
};
diff: {
insertedLine: string;
removedLine: string;
stripAdded: string;
stripRemoved: string;
};
};
export declare const canvasTokensLight: {
bg: {
editor: string;
chrome: string;
elevated: string;
};
text: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
link: string;
onAccent: string;
};
stroke: {
primary: string;
secondary: string;
tertiary: string;
};
fill: {
primary: string;
secondary: string;
tertiary: string;
quaternary: string;
};
accent: {
primary: string;
control: string;
controlHover: string;
};
diff: {
insertedLine: string;
removedLine: string;
stripAdded: string;
stripRemoved: string;
};
};
export type CanvasTokens = ReturnType<typeof buildTokens>;
export {};
//# sourceMappingURL=canvas-tokens.d.ts.map

View File

@@ -0,0 +1,200 @@
/**
* Chart primitives for `cursor/canvas` — multi-series, stacked, and pie charts
* rendered as pure inline SVG with zero external dependencies.
*
* Distilled from the portal-website Highcharts analytics charting layer.
*/
import type { CSSProperties, JSX } from "react";
/**
* Semantic tone for a chart series or slice. Mirrors the tone vocabulary
* used by `Stat`, `Pill`, `Table`, and other SDK primitives so colors
* match across a canvas — e.g. a `Stat tone="success"` and a
* `ChartSeries tone="success"` render in the same green.
*
* Omit `tone` to let the chart auto-assign a distinct color from the
* chart palette; supply `tone` only when the value carries semantic
* meaning that should match other tonal elements on the page.
*/
export type ChartTone = "success" | "danger" | "warning" | "info" | "neutral";
/** A single labeled value, used by `PieChart`. */
export type ChartDataPoint = {
label: string;
/** Non-negative numeric value. */
value: number;
};
/**
* A named data series for `BarChart` and `LineChart`.
* The `data` array aligns by index with the parent component's `categories`.
* If `tone` is omitted, a color is auto-assigned from the chart palette.
*/
export type ChartSeries = {
name: string;
data: number[];
tone?: ChartTone;
};
export type BarChartProps = {
/** Category labels along the independent axis. */
categories: string[];
/** One or more data series. Values align by index with `categories`. */
series: ChartSeries[];
height?: number;
/** Stack series on top of each other instead of grouping side-by-side. */
stacked?: boolean;
/** Render horizontal bars instead of vertical columns. */
horizontal?: boolean;
/** Show as 100% stacked (implies `stacked`). */
normalized?: boolean;
/** Suffix for y-axis tick labels (e.g. "%"). */
valueSuffix?: string;
style?: CSSProperties;
};
export type LineChartProps = {
categories: string[];
series: ChartSeries[];
height?: number;
/** Fill the area under each line with a soft tint. */
fill?: boolean;
valueSuffix?: string;
style?: CSSProperties;
};
export type PieChartProps = {
data: Array<ChartDataPoint & {
tone?: ChartTone;
}>;
size?: number;
donut?: boolean;
style?: CSSProperties;
};
/**
* Multi-series bar/column chart with optional stacking and normalization.
* Distilled from the portal-website Highcharts analytics charts.
*
* Pass `categories` for x-axis labels and one or more `series` whose `data`
* arrays align by index. With a single series you get simple bars; with
* multiple series the default is grouped (side-by-side) — set `stacked` for
* stacked columns or `normalized` for 100%-stacked share-mode.
*
* Colors are auto-assigned from the chart palette. With a **single series**,
* each bar gets a different color by category (so a chart of 5 categories
* shows 5 colors out of the box). With **multiple series**, each series gets
* its own color. A legend appears when there are 2+ series.
*
* For semantic coloring, pass `tone` on a series — it maps to the same
* palette entries used by `Stat`, `Pill`, and `Table` so your chart matches
* tonal elements elsewhere on the page.
*
* @example
* ```tsx
* // Simple single-series
* <BarChart
* categories={["Mon", "Tue", "Wed"]}
* series={[{ name: "Requests", data: [120, 90, 150] }]}
* />
*
* // Stacked multi-series (like portal AI commit chart)
* <BarChart
* categories={["Mon", "Tue", "Wed"]}
* series={[
* { name: "IDE", data: [120, 90, 150] },
* { name: "CLI", data: [30, 40, 25] },
* { name: "Cloud", data: [50, 60, 70] },
* ]}
* stacked
* />
*
* // Semantic tones — "accepted" renders in the same green as
* // <Stat tone="success"> elsewhere on the page.
* <BarChart
* categories={["Mon", "Tue", "Wed"]}
* series={[
* { name: "Accepted", data: [70, 80, 60], tone: "success" },
* { name: "Rejected", data: [30, 20, 40], tone: "danger" },
* ]}
* stacked
* />
* ```
*/
export declare function BarChart({ categories, series, height, stacked, horizontal, normalized, valueSuffix, style }: BarChartProps): JSX.Element;
/**
* Multi-series line chart with optional area fill. Distilled from the
* portal-website Highcharts analytics charts.
*
* Each series draws a polyline with dot markers at each data point.
* Set `fill` to shade the area under every line. Hover over any category
* column to see a tooltip with all series values at that point.
*
* This is **not** a time-series component — it does not parse dates.
* Pass pre-formatted date strings as `categories` if plotting over time.
*
* Colors are auto-assigned from the chart palette. For semantic coloring,
* pass `tone` on a series — it maps to the same palette entries used by
* `Stat`, `Pill`, and `Table`.
*
* @example
* ```tsx
* // Single line
* <LineChart
* categories={["Jan", "Feb", "Mar", "Apr"]}
* series={[{ name: "Revenue", data: [100, 140, 120, 180] }]}
* />
*
* // Multi-series with area fill
* <LineChart
* categories={["Jan", "Feb", "Mar", "Apr"]}
* series={[
* { name: "Accepted", data: [50, 70, 60, 90] },
* { name: "Suggested", data: [120, 140, 130, 160] },
* ]}
* fill
* />
*
* // Semantic tones — "errors" renders in the same red as a
* // <Pill tone="danger"> elsewhere on the page.
* <LineChart
* categories={["00:00", "06:00", "12:00", "18:00"]}
* series={[
* { name: "p95 latency", data: [80, 95, 110, 90], tone: "info" },
* { name: "errors", data: [2, 4, 9, 3], tone: "danger" },
* ]}
* />
* ```
*/
export declare function LineChart({ categories, series, height, fill, valueSuffix, style }: LineChartProps): JSX.Element;
/**
* Pie (or donut) chart with hover highlighting. Distilled from the
* portal-website Highcharts analytics charts.
*
* Unlike `BarChart` and `LineChart`, `PieChart` takes a flat `data` array of
* `{ label, value }` points — each slice is its own category. Colors are
* auto-assigned from the chart palette; pass `tone` on a point to give a
* slice a semantic color that matches other tonal elements on the page.
*
* Hovering a slice expands it outward and dims the others; hovering a legend
* item does the same. A tooltip with value and percentage appears below the
* chart. Set `donut` for a hollow center.
*
* **Do not** use for bar-style comparisons — use `BarChart` instead.
*
* @example
* ```tsx
* // Basic pie
* <PieChart
* data={[
* { label: "IDE", value: 120 },
* { label: "CLI", value: 30 },
* { label: "Cloud", value: 50 },
* ]}
* />
*
* // Donut with semantic tones
* <PieChart
* data={[
* { label: "Passing", value: 70, tone: "success" },
* { label: "Failing", value: 30, tone: "danger" },
* ]}
* donut
* />
* ```
*/
export declare function PieChart({ data, size, donut, style }: PieChartProps): JSX.Element;
//# sourceMappingURL=chart-primitives.d.ts.map

View File

@@ -0,0 +1,65 @@
/**
* Borderless disclosure row — chevron + structured header (title, optional
* leading slot, count, trailing slot) with a body that toggles open. Distilled
* from baby-glass `ContextTreeRow`'s lightweight list-row chrome (no card
* border, no background fill).
*
* For a bordered surface that collapses, use `<Card collapsible>` instead.
*/
import { type CSSProperties, type JSX, type ReactNode } from "react";
export type CollapsibleSectionProps = {
/** Plain-text title rendered next to the disclosure chevron. */
title: string;
/**
* Optional leading visual (e.g. `<Swatch>`) shown between the chevron and
* the title.
*/
leading?: ReactNode;
/**
* Optional small count rendered after the title (e.g. number of children).
*/
count?: number;
/**
* Optional trailing node, right-aligned (e.g. token readout, badge, button).
* Rendered with `t.text.tertiary` color hint via the wrapper; the slot can
* override.
*/
trailing?: ReactNode;
/** Body shown when expanded. */
children?: ReactNode;
style?: CSSProperties;
};
/**
* Borderless collapsible row with a structured header. Always starts closed
* (uncontrolled, no `defaultOpen`).
*
* Compose with `<Swatch>` in the `leading` slot for a colored category icon,
* and put a token readout / pill / button in `trailing`. Body content is
* indented under the row so nested `CollapsibleSection`s read as a tree.
*
* For a bordered, card-shaped collapsible surface, use `<Card collapsible>`
* instead — `CollapsibleSection` has no border or background and is meant to
* sit in a list of similar rows.
*
* @example
* ```tsx
* // Basic
* <CollapsibleSection title="Conversation">
* <Text>Messages go here.</Text>
* </CollapsibleSection>
*
* // With a colored category swatch + count + trailing token readout
* <CollapsibleSection
* title="Tools"
* count={4}
* leading={<Swatch color="purple" />}
* trailing={<Text size="small" tone="tertiary">12.3k</Text>}
* >
* <CollapsibleSection title="Grep">
* <Text>Search results.</Text>
* </CollapsibleSection>
* </CollapsibleSection>
* ```
*/
export declare function CollapsibleSection({ title, leading, count, trailing, children, style }: CollapsibleSectionProps): JSX.Element;
//# sourceMappingURL=collapsible-section.d.ts.map

102
skills-cursor/canvas/sdk/dag-layout.d.ts vendored Normal file
View File

@@ -0,0 +1,102 @@
/**
* Pure layout math for directed acyclic graphs. Returns positioned node
* coordinates, edge anchor points, rank bounding boxes, and back-edge flags.
* Rendering is the caller's responsibility.
*
* Handles cycles gracefully: back-edges are detected via DFS, excluded from
* ranking, and flagged in the output so the caller can render them differently
* (e.g. dashed arcs).
*/
export type DAGLayoutOptions = {
/** Nodes to lay out. Only `id` is required. */
nodes: Array<{
id: string;
}>;
/** Directed edges. */
edges: Array<{
from: string;
to: string;
}>;
/** Flow direction. Default `"vertical"` (top-to-bottom). */
direction?: "vertical" | "horizontal";
/** Node box width in px. Default 160. */
nodeWidth?: number;
/** Node box height in px. Default 40. */
nodeHeight?: number;
/** Gap between ranks (layers) in px. Default 64. */
rankGap?: number;
/** Gap between sibling nodes in the same rank in px. Default 48. */
nodeGap?: number;
/** Padding around the bounding box in px. Default 24. */
padding?: number;
};
export type DAGLayoutNode = {
id: string;
/** Left edge of the node box. */
x: number;
/** Top edge of the node box. */
y: number;
/** Layer index (0 = root). */
rank: number;
/** Position within the rank (0-indexed). */
order: number;
};
export type DAGLayoutEdge = {
from: string;
to: string;
/** Suggested source anchor point (center of the outgoing side). */
sourceX: number;
sourceY: number;
/** Suggested target anchor point (center of the incoming side). */
targetX: number;
targetY: number;
/** True when this edge was identified as a back-edge (part of a cycle). */
isBackEdge: boolean;
};
export type DAGLayoutRank = {
/** Rank index (0 = root). */
rank: number;
/** Left edge of the rank bounding box. */
x: number;
/** Top edge of the rank bounding box. */
y: number;
/** Width of the rank bounding box. */
width: number;
/** Height of the rank bounding box. */
height: number;
/** Node ids in this rank, in order. */
nodeIds: string[];
};
export type DAGLayoutResult = {
nodes: DAGLayoutNode[];
edges: DAGLayoutEdge[];
/** Bounding box per rank — useful for drawing layer bands. */
ranks: DAGLayoutRank[];
/** The direction used for this layout. */
direction: "vertical" | "horizontal";
/** Total width of the bounding box. */
width: number;
/** Total height of the bounding box. */
height: number;
};
/**
* Compute a hierarchical layout for a directed graph.
*
* Returns node positions, edge anchor points, rank bounding boxes, and
* back-edge flags. The caller handles all rendering.
*
* @example
* ```ts
* const layout = computeDAGLayout({
* nodes: [{ id: "a" }, { id: "b" }, { id: "c" }],
* edges: [{ from: "a", to: "b" }, { from: "b", to: "c" }],
* });
*
* // layout.nodes[i].x / .y → position your own SVG/HTML elements
* // layout.edges[i].sourceX/Y, targetX/Y → draw lines between them
* // layout.edges[i].isBackEdge → style cycle edges differently
* // layout.ranks[i] → draw layer bands behind each rank
* ```
*/
export declare function computeDAGLayout(options: DAGLayoutOptions): DAGLayoutResult;
//# sourceMappingURL=dag-layout.d.ts.map

130
skills-cursor/canvas/sdk/diff-view.d.ts vendored Normal file
View File

@@ -0,0 +1,130 @@
/**
* Low-level diff primitives for the canvas SDK.
*
* The canvas diff surface is intentionally minimal — two components that
* compose with the generic `Card` / `CardHeader` / `CardBody` / `Pill` /
* `Text` primitives to build any diff layout an agent can imagine:
*
* - `DiffView` — a monospaced, syntax-highlighted unified diff renderer.
* Drops into any container (a `Card`, a table cell, a bare layout,
* nothing at all). No card chrome, no header, no path display. Pass
* `path` to auto-detect the language for highlighting, or `language`
* to override.
*
* - `DiffStats` — the canonical `+N` green / `-N` red glyph pair. Use
* it anywhere a small "added/deleted" summary makes sense — in a
* `CardHeader`'s `trailing` slot, next to a filename in a file tree,
* inside a status row, etc.
*
* For file-level metadata the preferred composition is to use the
* generic `Card` family:
*
* ```tsx
* <Card collapsible>
* <CardHeader trailing={<DiffStats additions={5} deletions={2} />}>
* src/utils.ts
* </CardHeader>
* <CardBody style={{ padding: 0 }}>
* <DiffView path="src/utils.ts" lines={lines} />
* </CardBody>
* </Card>
* ```
*/
import type { CSSProperties, JSX } from "react";
export type DiffStatsProps = {
additions?: number;
deletions?: number;
style?: CSSProperties;
};
/**
* Inline `+N` / `-N` glyph pair. Green additions, red deletions, with
* tabular numerals so columns of stats line up. Renders nothing when
* both counts are zero.
*
* @example
* ```tsx
* <CardHeader trailing={<DiffStats additions={12} deletions={3} />}>
* src/utils.ts
* </CardHeader>
*
* <Row gap={8}>
* <Text>Refactor pass</Text>
* <DiffStats additions={42} deletions={17} />
* </Row>
* ```
*/
export declare function DiffStats({ additions, deletions, style }: DiffStatsProps): JSX.Element | null;
export type DiffLineType = "added" | "removed" | "unchanged";
export type DiffLineData = {
type: DiffLineType;
content: string;
lineNumber?: number;
};
export type DiffViewProps = {
lines: DiffLineData[];
/**
* File path used to infer the syntax-highlighting language from the
* extension (e.g. `"src/utils.ts"` → `typescript`). The most ergonomic
* way to enable highlighting — pass the same path you show in the
* enclosing card header. Unknown extensions silently render as plain
* text.
*
* If both `path` and `language` are provided, `language` wins.
*/
path?: string;
/**
* Explicit language override for syntax highlighting (e.g.
* `"typescript"`, `"python"`, `"tsx"`). Use this when no file path is
* available, when the path's extension is misleading, or when the
* content is a snippet rather than a real file. Accepts common
* aliases (`ts`, `py`, `rs`, `md`, etc.). Unknown languages silently
* fall back to plain text.
*
* Highlighting is applied per line, so multi-line constructs (block
* comments, template literals) may not colorize perfectly across line
* boundaries. For typical diff-sized inputs this is fine.
*/
language?: string;
/** Show line numbers in the gutter. Default `true`. */
showLineNumbers?: boolean;
/** Color line numbers green/red for added/removed lines. Default `true`. */
coloredLineNumbers?: boolean;
/** Show a 3px accent strip on the left edge for changed lines. Default `true`. */
showAccentStrip?: boolean;
style?: CSSProperties;
};
/**
* Unified diff body renderer with monospaced type, colored line
* backgrounds, line-number gutter, accent strip, and optional Shiki
* syntax highlighting.
*
* `DiffView` does not provide any surrounding chrome — place it inside
* a `Card` + `CardBody` (with `padding: 0`) when you want the standard
* bordered "file diff" look, or drop it anywhere else if you want the
* bare renderer.
*
* Pass `path` to enable syntax highlighting from the file extension.
*
* @example
* ```tsx
* <Card>
* <CardHeader trailing={<DiffStats additions={2} deletions={1} />}>
* src/utils.ts
* </CardHeader>
* <CardBody style={{ padding: 0 }}>
* <DiffView
* path="src/utils.ts"
* lines={[
* { type: "unchanged", content: "export function add(a: number, b: number): number {", lineNumber: 1 },
* { type: "removed", content: " return a + b;", lineNumber: 2 },
* { type: "added", content: " const result = a + b;", lineNumber: 2 },
* { type: "added", content: " return result;", lineNumber: 3 },
* { type: "unchanged", content: "}", lineNumber: 4 },
* ]}
* />
* </CardBody>
* </Card>
* ```
*/
export declare function DiffView({ lines, path, language, showLineNumbers, coloredLineNumbers, showAccentStrip, style }: DiffViewProps): JSX.Element;
//# sourceMappingURL=diff-view.d.ts.map

View File

@@ -0,0 +1,194 @@
/**
* Form primitives for `cursor/canvas`. Provides themed, controlled form controls
* for interactive canvas apps with persistent state. All `onChange` callbacks
* receive the **value directly** (not a DOM event), so they pair naturally
* with `useCanvasState`:
*
* ```tsx
* const [name, setName] = useCanvasState("name", "");
* <TextInput value={name} onChange={setName} placeholder="Enter name…" />
* ```
*/
import { type CSSProperties, type JSX, type ReactNode } from "react";
export type TextInputProps = {
value?: string;
/** Called with the new string value on every keystroke. */
onChange?: (value: string) => void;
placeholder?: string;
disabled?: boolean;
type?: "text" | "email" | "password" | "number" | "url" | "search";
style?: CSSProperties;
};
/**
* Single-line text input (28px height). Use for names, titles, search
* queries, and short text fields.
*
* `onChange` receives the **string value**, not a DOM event — this pairs
* directly with `useCanvasState` setters.
*
* @example
* ```tsx
* const [name, setName] = useCanvasState("name", "");
*
* <TextInput value={name} onChange={setName} placeholder="Task title…" />
* ```
*/
export declare function TextInput({ value, onChange, placeholder, disabled, type, style }: TextInputProps): JSX.Element;
export type TextAreaProps = {
value?: string;
/** Called with the new string value on every keystroke. */
onChange?: (value: string) => void;
placeholder?: string;
disabled?: boolean;
/** Minimum visible rows. Defaults to 3. */
rows?: number;
style?: CSSProperties;
};
/**
* Multi-line text input that auto-resizes to fit its content.
* Use for notes, descriptions, comments, and multi-line text fields.
*
* The textarea grows as the user types. Set `rows` for the minimum visible
* height (defaults to 3). Override with `style={{ height: "100px" }}` for a
* fixed size.
*
* @example
* ```tsx
* const [notes, setNotes] = useCanvasState("notes", "");
*
* <TextArea value={notes} onChange={setNotes} placeholder="Add notes…" rows={4} />
* ```
*/
export declare function TextArea({ value, onChange, placeholder, disabled, rows, style }: TextAreaProps): JSX.Element;
export type CheckboxProps = {
checked?: boolean;
/** Called with the new boolean value when toggled. */
onChange?: (checked: boolean) => void;
disabled?: boolean;
/** Optional label rendered beside the checkbox. Clicking the label toggles the checkbox. */
label?: ReactNode;
style?: CSSProperties;
};
/**
* Checkbox with optional label (accent blue when checked).
*
* Pass `label` to render a clickable text label beside the checkbox. Without a
* label, provide `title` or wrap in your own `<label>` for accessibility.
*
* @example
* ```tsx
* const [agreed, setAgreed] = useCanvasState("agreed", false);
*
* <Checkbox checked={agreed} onChange={setAgreed} label="I agree to the terms" />
* ```
*
* @example
* ```tsx
* // Checkbox in a list — no label, parent handles layout
* <Checkbox checked={item.done} onChange={(v) => toggleItem(item.id, v)} />
* ```
*/
export declare function Checkbox({ checked, onChange, disabled, label, style }: CheckboxProps): JSX.Element;
export type ToggleProps = {
checked?: boolean;
/** Called with the new boolean value when toggled. */
onChange?: (checked: boolean) => void;
disabled?: boolean;
/** `sm` = 16px track, `md` = 20px track (default `sm`). */
size?: "sm" | "md";
style?: CSSProperties;
};
/**
* Boolean toggle switch. Uses the accent color for the "on" state and a
* neutral fill for "off".
*
* @example
* ```tsx
* const [enabled, setEnabled] = useCanvasState("enabled", false);
*
* <Row gap={8} align="center">
* <Text>Notifications</Text>
* <Spacer />
* <Toggle checked={enabled} onChange={setEnabled} />
* </Row>
* ```
*/
export declare function Toggle({ checked, onChange, disabled, size, style }: ToggleProps): JSX.Element;
export type SelectOption = {
value: string;
label: string;
disabled?: boolean;
};
export type SelectProps = {
value?: string;
/** Called with the new selected value. */
onChange?: (value: string) => void;
/** List of options. Each must have a unique `value`. */
options: SelectOption[];
/** Placeholder shown when no value is selected. */
placeholder?: string;
disabled?: boolean;
style?: CSSProperties;
};
/**
* Dropdown select (native `<select>` with themed styling).
*
* Uses a native `<select>` under the hood for reliable keyboard, screen-reader,
* and mobile support. The dropdown list uses OS-native styling.
*
* @example
* ```tsx
* const [priority, setPriority] = useCanvasState("priority", "medium");
*
* <Select
* value={priority}
* onChange={setPriority}
* options={[
* { value: "low", label: "Low" },
* { value: "medium", label: "Medium" },
* { value: "high", label: "High" },
* ]}
* />
* ```
*/
export declare function Select({ value, onChange, options, placeholder, disabled, style }: SelectProps): JSX.Element;
export type IconButtonProps = {
/** Icon content: an SVG element, emoji, or unicode character. */
children: ReactNode;
onClick?: () => void;
disabled?: boolean;
/** Tooltip text. Always provide for accessibility since there is no text label. */
title?: string;
/**
* `"default"` is transparent until hovered; `"circle"` has a permanent
* background fill.
*/
variant?: "default" | "circle";
/** `sm` = 16px, `md` = 20px (default `md`). */
size?: "sm" | "md";
style?: CSSProperties;
};
/**
* Compact icon-only button for inline actions on list items (delete, edit,
* expand, etc.). Accepts **any** `children` as the icon — use an inline SVG,
* an emoji, or a unicode character.
*
* Always provide `title` for accessibility (screen-reader label + tooltip).
*
* Canvas has no icon font, so pass icon content directly.
*
* @example
* ```tsx
* // Delete button on a card
* <IconButton title="Delete" onClick={() => remove(id)}>✕</IconButton>
*
* // Edit button with an SVG icon
* <IconButton title="Edit" variant="circle" size="sm" onClick={edit}>
* <svg width={12} height={12} viewBox="0 0 12 12" fill="none">
* <path d="M8.5 1.5l2 2L4 10H2V8z" stroke="currentColor" strokeWidth={1.2} />
* </svg>
* </IconButton>
* ```
*/
export declare function IconButton({ children, onClick, disabled, title, variant, size, style }: IconButtonProps): JSX.Element;
//# sourceMappingURL=form-primitives.d.ts.map

141
skills-cursor/canvas/sdk/hooks.d.ts vendored Normal file
View File

@@ -0,0 +1,141 @@
import type { CanvasPalette, CanvasTokens } from "./canvas-tokens.js";
import { type CanvasAction } from "./internal/canvas-action-dispatch.js";
/**
* Host theme for the current canvas. Semantic color groups (`text`, `bg`,
* `fill`, `stroke`, `accent`, `diff`) live at the top level for ergonomic
* inline-style access; `tokens` is also present as a self-reference for
* callers that prefer a namespaced form.
*/
export interface CanvasHostTheme extends CanvasTokens {
readonly kind: string;
readonly tokens: CanvasTokens;
readonly palette: CanvasPalette;
}
/**
* Returns the current host theme. Falls back to dark mode when no host
* state is available.
*
* Semantic color groups are available directly on the returned object —
* `accent`, `text`, `bg`, `fill`, `stroke`, `diff` — as well as `kind`
* (`"dark"` | `"light"` | …) and `palette` (the flat color palette).
*
* Call `useHostTheme()` inside each component that needs theme access —
* the returned object is scoped to that component, not shared across
* function boundaries.
*
* Stable paths for `style={{ ... }}` usage:
* - `text.primary / secondary / tertiary / quaternary` — text hierarchy
* - `bg.editor / chrome / elevated` — surface backgrounds
* - `fill.primary / secondary / tertiary / quaternary` — tinted fills
* - `stroke.primary / secondary / tertiary` — borders and dividers
* - `accent.primary / control` — accent blue and button background
* - `text.link` — link color
* - `text.onAccent` — text on accent-colored surfaces
*
* Prefer built-in components (`Card`, `Button`, `Text`, etc.) over raw
* token usage. Reach for tokens only when no component covers the case,
* and stick to flat solid colors — no gradients, no box-shadows.
*
* @example
* ```tsx
* function Overview() {
* const theme = useHostTheme();
* return (
* <div style={{ background: theme.fill.tertiary, color: theme.text.secondary, padding: 8 }}>
* <span style={{ color: theme.accent.primary }}>Accent text</span>
* </div>
* );
* }
* ```
*/
export declare function useHostTheme(): CanvasHostTheme;
/**
* Setter for `useCanvasState`. Accepts either a new value or an updater
* function that receives the previous value.
*/
export type SetCanvasState<T> = (action: T | ((prev: T) => T)) => void;
/**
* Persistent state hook for canvas applications. Works like `React.useState`
* but the value survives rebuilds, reloads, and IDE restarts — it is stored
* in a `.canvas.data.json` sidecar file next to the canvas source.
*
* Each key is a unique string chosen by the canvas author. Keys are stable
* regardless of hook call order or component tree structure.
*
* @param key - Unique string identifier for this piece of state.
* @param defaultValue - Value returned when no persisted value exists for `key`.
* @returns A `[value, setValue]` tuple, same shape as `React.useState`.
*
* @example
* ```tsx
* function Counter() {
* const [count, setCount] = useCanvasState("count", 0);
* return <Button onClick={() => setCount(c => c + 1)}>{count}</Button>;
* }
* ```
*
* @example
* ```tsx
* interface Column { id: string; title: string; cardIds: string[] }
* function KanbanBoard() {
* const [columns, setColumns] = useCanvasState<Column[]>("columns", [
* { id: "todo", title: "To Do", cardIds: [] },
* { id: "doing", title: "In Progress", cardIds: [] },
* { id: "done", title: "Done", cardIds: [] },
* ]);
* // ...
* }
* ```
*/
export declare function useCanvasState<T>(key: string, defaultValue: T): [T, SetCanvasState<T>];
export type { CanvasAction };
/**
* Returns a stable `dispatch` function for triggering IDE actions from
* canvas buttons. Actions are fire-and-forget — the canvas does not
* receive a response.
*
* ## Available actions
*
* **`openAgent`** — Navigate the IDE to an agent conversation.
* `agentId` is the conversation UUID (the filename stem from the
* `agent-transcripts/` directory). Works for both local and
* cloud/background agents in Glass and classic IDE modes.
*
* **`newComposerChat`** — Open a new local agent chat with the
* dispatching canvas file as an @mention. Optional `userPrompt` is
* appended after the mention. The canvas file is resolved from the
* action route's `canvasId`; do not pass the path from canvas UI.
*
* @example
* ```tsx
* function AgentLink({ agentId, title }: { agentId: string; title: string }) {
* const dispatch = useCanvasAction();
* return (
* <Button onClick={() => dispatch({ type: "openAgent", agentId })}>
* {title}
* </Button>
* );
* }
* ```
*
* @example
* ```tsx
* function AskAboutCanvas() {
* const dispatch = useCanvasAction();
* return (
* <Button
* onClick={() =>
* dispatch({
* type: "newComposerChat",
* userPrompt: "please explain what is using the most context",
* })
* }
* >
* Ask in chat
* </Button>
* );
* }
* ```
*/
export declare function useCanvasAction(): (action: CanvasAction) => void;
//# sourceMappingURL=hooks.d.ts.map

59
skills-cursor/canvas/sdk/index.d.ts vendored Normal file
View File

@@ -0,0 +1,59 @@
/**
* Public API for authoring `.canvas.tsx` files via `cursor/canvas`.
*
* Be creative with layout — use Grid, Row, cards, charts, tables, and raw SVG
* in whatever combination serves the content. Read the canvas skill for full
* design guidance. Key constraints:
*
* - Colors from `useHostTheme()` tokens. No hardcoded hex.
* - No gradients, no box-shadows, no emojis as decoration.
* - Don't wrap every section in Card — mix open sections with cards.
* - Run the pre-delivery self-check before returning code.
*/
/** Shared category color palette used by `Swatch`, `UsageBar`, etc. */
export type { Color } from "./canvas-tokens.js";
export { colorPalette, usageColorSequence } from "./canvas-tokens.js";
/** Charts. */
export type { BarChartProps, ChartDataPoint, ChartSeries, ChartTone, LineChartProps, PieChartProps, } from "./chart-primitives.js";
export { BarChart, LineChart, PieChart } from "./chart-primitives.js";
/** Borderless collapsible disclosure row with a structured header. */
export type { CollapsibleSectionProps } from "./collapsible-section.js";
export { CollapsibleSection } from "./collapsible-section.js";
/** DAG layout. */
export type { DAGLayoutEdge, DAGLayoutNode, DAGLayoutOptions, DAGLayoutRank, DAGLayoutResult, } from "./dag-layout.js";
export { computeDAGLayout } from "./dag-layout.js";
/**
* Diff rendering. Compose with the generic `Card` family for file-level
* chrome: use `DiffView` inside a `CardBody` (with `padding: 0`) and put
* `DiffStats` in the enclosing `CardHeader`'s `trailing` slot.
*/
export type { DiffLineData, DiffLineType, DiffStatsProps, DiffViewProps, } from "./diff-view.js";
export { DiffStats, DiffView } from "./diff-view.js";
/** Form controls. */
export type { CheckboxProps, IconButtonProps, SelectOption, SelectProps, TextAreaProps, TextInputProps, ToggleProps, } from "./form-primitives.js";
export { Checkbox, IconButton, Select, TextArea, TextInput, Toggle, } from "./form-primitives.js";
/** Host state hooks. */
export type { CanvasAction, CanvasHostTheme, SetCanvasState } from "./hooks.js";
export { useCanvasAction, useCanvasState, useHostTheme } from "./hooks.js";
/** Colored category swatch (uses the shared `Color` palette). */
export type { SwatchProps } from "./swatch.js";
export { Swatch } from "./swatch.js";
/** Semantic design tokens for custom styling. */
export type { CanvasPalette, CanvasTokens } from "./theme.js";
export { canvasPaletteDark, canvasPaletteLight, canvasTokens, canvasTokensLight, } from "./theme.js";
export type { TodoItem, TodoListCardProps, TodoListProps, TodoStatus, } from "./todo-list.js";
export { TodoList, TodoListCard } from "./todo-list.js";
/** Component props types. */
export type { ButtonProps, CalloutProps, CalloutTone, CardBodyProps, CardHeaderProps, CardProps, CardSize, CardVariant, CodeProps, DividerProps, GridProps, H1Props, H2Props, H3Props, LinkProps, PillProps, PillSize, PillTone, RowProps, StackProps, StatProps, StatTone, TableColumnAlign, TableProps, TableRowTone, TextProps, TextWeight, } from "./ui-primitives.js";
/** Layout. */
/** Typography. */
/** Surfaces. */
/** Actions. */
/** Feedback. */
export { Button, Callout, Card, CardBody, CardHeader, Code, Divider, Grid, H1, H2, H3, Link,
/** Shallow-merge two style objects. Useful for combining tokens with overrides. */
mergeStyle, Pill, Row, Spacer, Stack, Stat, Table, Text, } from "./ui-primitives.js";
/** Usage bar — segmented progress meter with optional labels above. */
export type { UsageBarProps, UsageBarSegment } from "./usage-bar.js";
export { UsageBar } from "./usage-bar.js";
//# sourceMappingURL=index.d.ts.map

40
skills-cursor/canvas/sdk/swatch.d.ts vendored Normal file
View File

@@ -0,0 +1,40 @@
/**
* Colored category swatch — a small filled rounded box. Intended for inline
* list/row decoration (category badges, the leading slot of
* `CollapsibleSection`, etc.).
*
* Pulls colors from the shared `Color` palette so a category's swatch and
* its `UsageBar` segment for the same `color` stay visually coherent.
*/
import type { CSSProperties, JSX } from "react";
import { type Color } from "./canvas-tokens.js";
export type SwatchProps = {
/** One of the 7 shared category hues. Matches `UsageBar` segment colors. */
color: Color;
style?: CSSProperties;
};
/**
* Filled, rounded category swatch (24px). Use as the leading visual on
* category rows, list items, or as the `leading` slot of a
* `CollapsibleSection`.
*
* Colors come from the shared 7-hue `Color` palette, so a category's swatch
* matches its `UsageBar` segment for the same `color`.
*
* @example
* ```tsx
* // Standalone — purple "tools" swatch
* <Swatch color="purple" />
*
* // As the `leading` slot on a CollapsibleSection
* <CollapsibleSection
* title="Tools"
* count={4}
* leading={<Swatch color="purple" />}
* >
* <Text>Tool calls go here.</Text>
* </CollapsibleSection>
* ```
*/
export declare function Swatch({ color, style }: SwatchProps): JSX.Element;
//# sourceMappingURL=swatch.d.ts.map

61
skills-cursor/canvas/sdk/theme.d.ts vendored Normal file
View File

@@ -0,0 +1,61 @@
export type { CanvasPalette, CanvasTokens } from "./canvas-tokens.js";
export { canvasPaletteDark, canvasPaletteLight, canvasTokens, canvasTokensLight, } from "./canvas-tokens.js";
/** Typography presets used by the built-in `cursor/canvas` components. */
export declare const canvasTypography: {
readonly h1: {
readonly fontSize: "24px";
readonly lineHeight: "30px";
readonly fontWeight: 590;
};
readonly h2: {
readonly fontSize: "18px";
readonly lineHeight: "24px";
readonly fontWeight: 590;
};
readonly h3: {
readonly fontSize: "16px";
readonly lineHeight: "22px";
readonly fontWeight: 590;
};
readonly body: {
readonly fontSize: "14px";
readonly lineHeight: "20px";
readonly fontWeight: 400;
};
readonly small: {
readonly fontSize: "12px";
readonly lineHeight: "16px";
readonly fontWeight: 400;
};
};
/** Spacing scale (px). */
export declare const canvasSpacing: {
readonly "0.5": 2;
readonly "1": 4;
readonly "1.5": 6;
readonly "2": 8;
readonly "2.5": 10;
readonly "3": 12;
readonly "3.5": 14;
readonly "4": 16;
readonly "4.5": 18;
readonly "5": 20;
readonly "6": 24;
readonly "7": 28;
readonly "8": 32;
readonly "9": 36;
readonly "10": 40;
};
export type CanvasSpacing = typeof canvasSpacing;
/** Border radius (px). */
export declare const canvasRadius: {
readonly none: 0;
readonly xs: 2;
readonly sm: 4;
readonly md: 6;
readonly lg: 8;
readonly xl: 12;
readonly full: 9999;
};
export type CanvasRadius = typeof canvasRadius;
//# sourceMappingURL=theme.d.ts.map

49
skills-cursor/canvas/sdk/todo-list.d.ts vendored Normal file
View File

@@ -0,0 +1,49 @@
import type { CSSProperties, JSX } from "react";
export type TodoStatus = "pending" | "in_progress" | "completed" | "cancelled";
export interface TodoItem {
readonly id: string;
readonly content: string;
readonly status: TodoStatus;
}
export type TodoListProps = {
todos: readonly TodoItem[];
dimmedTodoIds?: ReadonlySet<string>;
/** Called when a todo row is clicked (entire row is a button). */
onTodoClick?: (todo: TodoItem) => void;
style?: CSSProperties;
};
/**
* Task list with status icons and wrapping text. Each row is a **clickable**
* button; use `onTodoClick` to handle selection or navigation.
*
* @example
* ```tsx
* <TodoList
* todos={items}
* onTodoClick={(todo) => setActiveId(todo.id)}
* />
* ```
*/
export declare function TodoList({ todos, dimmedTodoIds, onTodoClick, style }: TodoListProps): JSX.Element | null;
export type TodoListCardProps = {
todos: readonly TodoItem[];
dimmedTodoIds?: ReadonlySet<string>;
defaultExpanded?: boolean;
onTodoClick?: (todo: TodoItem) => void;
style?: CSSProperties;
};
/**
* Bordered, collapsible todo list with summary header (N of M Done). Compose
* with `onTodoClick` for row actions.
*
* @example
* ```tsx
* <TodoListCard
* todos={items}
* defaultExpanded
* onTodoClick={(todo) => console.log(todo.id)}
* />
* ```
*/
export declare function TodoListCard({ todos, dimmedTodoIds, defaultExpanded, onTodoClick, style }: TodoListCardProps): JSX.Element | null;
//# sourceMappingURL=todo-list.d.ts.map

View File

@@ -0,0 +1,549 @@
/**
* UI primitives for `cursor/canvas`. Styling follows the Cursor dark theme; no extra packages required.
*/
import { type CSSProperties, type JSX, type ReactNode } from "react";
/**
* Shallow-merge style objects with `override` taking precedence.
*
* Use for small tweaks on built-in components (e.g. extra padding or width).
* **Do not** use this to build elaborate custom chrome — prefer the built-in
* components and flat solid token colors. No gradients, no box-shadows.
*
* @example
* ```tsx
* // Good — minor override on a built-in component
* <CardBody style={mergeStyle({ padding: 16 })}>…</CardBody>
*
* // Bad — hand-rolled decorative styling
* <div style={mergeStyle(base, { background: "linear-gradient(…)" })}>…</div>
* ```
*/
export declare function mergeStyle(base: CSSProperties, override?: CSSProperties): CSSProperties;
export type StackProps = {
children?: ReactNode;
gap?: number;
style?: CSSProperties;
};
/**
* Vertical flex column. Use as the top-level page wrapper or to stack cards/sections.
*
* @example
* ```tsx
* <Stack gap={16}>
* <H1>Dashboard</H1>
* <Card>…</Card>
* <Card>…</Card>
* </Stack>
* ```
*/
export declare function Stack({ children, gap, style }: StackProps): JSX.Element;
export type RowProps = {
children?: ReactNode;
gap?: number;
align?: "start" | "center" | "end" | "stretch";
justify?: "start" | "center" | "end" | "space-between";
wrap?: boolean;
style?: CSSProperties;
};
/**
* Horizontal flex row. Use for inline groups of buttons, badges, or metadata.
*
* @example
* ```tsx
* <Row gap={8} align="center">
* <Button variant="primary">Save</Button>
* <Button variant="ghost">Cancel</Button>
* </Row>
* ```
*/
export declare function Row({ children, gap, align, justify, wrap, style }: RowProps): JSX.Element;
/**
* CSS Grid with tokenized gap. Prefer this over `Row` + `wrap` when you need a
* fixed number of equal-width columns: wrapped flex items can land on their
* own row and grow to full width (`flex-grow`), which is often surprising for
* boards and dashboards.
*/
export type GridProps = {
children?: ReactNode;
/**
* Equal columns: pass a number (uses `repeat(n, minmax(0, 1fr))`), or a CSS
* `grid-template-columns` string (e.g. `"1fr 2fr"` or `"minmax(0, 200px) 1fr"`).
*/
columns: number | string;
gap?: number;
align?: "start" | "center" | "end" | "stretch";
style?: CSSProperties;
};
export declare function Grid({ children, columns, gap, align, style }: GridProps): JSX.Element;
export type DividerProps = {
style?: CSSProperties;
};
/**
* Horizontal line for visually separating sections. Uses `stroke.tertiary`
* to match the Card/Table hairline weight.
*
* @example
* ```tsx
* <Stack>
* <Text>Section one</Text>
* <Divider />
* <Text>Section two</Text>
* </Stack>
* ```
*/
export declare function Divider({ style }: DividerProps): JSX.Element;
/**
* Flex spacer that pushes siblings apart. Place inside a `Row` to push
* trailing content to the right edge.
*
* @example
* ```tsx
* <Row>
* <Text>Title</Text>
* <Spacer />
* <Button variant="primary">Save</Button>
* </Row>
* ```
*/
export declare function Spacer(): JSX.Element;
/** Horizontal alignment for a table column. */
export type TableColumnAlign = "left" | "center" | "right";
/** Semantic tone for a table row — renders a translucent tinted background. */
export type TableRowTone = "success" | "danger" | "warning" | "info" | "neutral";
export type TableProps = {
/** Column titles, left to right. Column count is fixed by this array. */
headers: ReactNode[];
/**
* Body rows. Each row is an array of cells in the same order as `headers`.
* Shorter rows are padded with empty cells; extra cells are ignored.
*/
rows: ReactNode[][];
/** Optional alignment per column index (headers/rows). Defaults to left. */
columnAlign?: Array<TableColumnAlign | undefined>;
/**
* Optional semantic tone per row index. Applies a translucent tinted
* background — use for status highlighting (e.g. failing services, warnings).
* Sparse: `undefined` entries are uncolored.
*/
rowTone?: Array<TableRowTone | undefined>;
/** When true (default), bordered rounded shell with horizontal scroll if needed. */
framed?: boolean;
/** Alternate subtle fill on even rows for easier scanning in large tables. */
striped?: boolean;
/** Stick the header row when the framed container scrolls vertically. */
stickyHeader?: boolean;
style?: CSSProperties;
/** Shown in a single spanning cell when `rows` is empty. */
emptyMessage?: ReactNode;
};
/**
* Data table with column headers and rows. Framed by default with its own
* bordered container — **do not wrap in a Card** unless the card itself is
* a named entity that happens to contain a table. Render directly under a
* heading in the normal case.
*
* @example
* ```tsx
* // Good — table directly under a heading
* <H2>Active services</H2>
* <Table
* headers={["Service", "Status", "RPS"]}
* rows={[
* ["api-gateway", "Steady", "3.2k"],
* ["workers", "Hot", "8.1k"],
* ]}
* columnAlign={["left", "left", "right"]}
* />
*
* // Allowed — table inside a named-entity card
* <Card>
* <CardHeader>billing-service</CardHeader>
* <CardBody><Table headers={…} rows={…} /></CardBody>
* </Card>
* ```
*/
export declare function Table({ headers, rows, columnAlign, rowTone, framed, striped, stickyHeader, style, emptyMessage }: TableProps): JSX.Element;
export type TextWeight = "normal" | "medium" | "semibold" | "bold";
export type TextProps = {
children?: ReactNode;
tone?: "primary" | "secondary" | "tertiary" | "quaternary";
size?: "body" | "small";
/**
* Element tag to render. Defaults to `"p"` for top-level body copy and
* automatically switches to `"span"` when nested inside another typography
* container so inline emphasis stays valid HTML.
*/
as?: "p" | "span";
/** Font weight. Default is `"normal"` (400). Use `"semibold"` or `"bold"` for emphasis. */
weight?: TextWeight;
/** Render as italic. */
italic?: boolean;
/**
* Truncate overflowing text with an ellipsis on a single line.
* - `true` / `"end"` — ellipsis at the end (default truncation).
* - `"start"` — ellipsis at the start. Useful for file paths where the
* filename matters more than the directory prefix.
*
* Requires the parent to have a bounded width (flex child with
* `minWidth: 0`, fixed width, etc.) — otherwise the text just expands
* and never overflows.
*/
truncate?: boolean | "start" | "end";
style?: CSSProperties;
};
/**
* Body text with tone, size, weight, and italic variants.
*
* Top-level `Text` renders a `<p>`. Nested `Text` automatically renders a
* `<span>` so inline emphasis like `<Text>Use <Text weight="semibold">this</Text></Text>`
* does not emit invalid nested paragraphs. Use `as` to override when needed.
*
* Compose with `<Code>` for inline code and `<Link>` for hyperlinks inside
* the text flow.
*
* @example
* ```tsx
* <Text>Primary body text.</Text>
* <Text weight="semibold">Important note.</Text>
* <Text italic tone="secondary">Supplementary remark.</Text>
* <Text>Run <Code>npm install</Code> to get started.</Text>
* <Text>See the <Link href="https://example.com">docs</Link> for details.</Text>
* ```
*/
export declare function Text({ children, tone, size, as, weight, italic, truncate, style }: TextProps): JSX.Element;
export type H1Props = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Page-level heading. Use once at the top of a canvas.
* **Do not** place inside `CardHeader` — card headers use their own label.
*
* @example
* ```tsx
* <Stack>
* <H1>Performance Report</H1>
* <Card>…</Card>
* </Stack>
* ```
*/
export declare function H1({ children, style }: H1Props): JSX.Element;
export type H2Props = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Section heading. Use between groups of cards or sections.
* **Do not** place inside `CardHeader` — card headers use their own label.
*
* @example
* ```tsx
* <Stack>
* <H2>Recent activity</H2>
* <Card>…</Card>
* <Card>…</Card>
* </Stack>
* ```
*/
export declare function H2({ children, style }: H2Props): JSX.Element;
export type H3Props = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Sub-section heading. Use below `H2` for finer hierarchy.
*
* @example
* ```tsx
* <Stack>
* <H2>API Reference</H2>
* <H3>Authentication</H3>
* <Text>All requests require a bearer token.</Text>
* </Stack>
* ```
*/
export declare function H3({ children, style }: H3Props): JSX.Element;
export type CodeProps = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Inline `<code>` span for identifiers, file names, or short snippets.
* Uses `0.92em` so it scales with surrounding text (headings, body, etc.).
*
* Prefer writing backtick markdown inside `Text` — e.g. `` <Text>Run `npm install`</Text> `` —
* which is automatically parsed. Use `<Code>` only when you need an explicit element.
*
* @example
* ```tsx
* <Text>Run <Code>npm install</Code> to get started.</Text>
* ```
*/
export declare function Code({ children, style }: CodeProps): JSX.Element;
export type LinkProps = {
children?: ReactNode;
href: string;
style?: CSSProperties;
};
/**
* Inline link that opens in the user's default browser.
*
* Prefer writing markdown links inside `Text` — e.g. `<Text>See the [docs](url)</Text>` —
* which are automatically parsed. Use `<Link>` when you need an explicit anchor
* outside of a text flow or when composing with other elements.
*
* @example
* ```tsx
* <Link href="https://docs.example.com">View documentation</Link>
* ```
*/
export declare function Link({ children, href, style }: LinkProps): JSX.Element;
export type CardSize = "base" | "lg";
export type CardVariant = "default" | "borderless";
/**
* Inline chevron SVG used by disclosure-style controls (collapsible cards,
* expandable list items, etc.). Shared by `Card` and `todo-list.tsx` so
* every disclosure in the canvas SDK uses the same glyph.
*/
export declare function CanvasChevron({ expanded }: {
expanded: boolean;
}): JSX.Element;
export type CardProps = {
children?: ReactNode;
/** Default: bordered surface with radius; `borderless` removes both. */
variant?: CardVariant;
/** `lg` uses a taller header and roomier title padding (matches packages/ui). */
size?: CardSize;
/**
* When true, the header uses `position: sticky` so it stays visible while
* the card body scrolls. Requires the card (or a parent) to have a
* constrained height and `overflow: auto` — the canvas host controls this,
* so sticky behavior depends on the host viewport.
*/
stickyHeader?: boolean;
/**
* Make the card collapsible. The header becomes a clickable toggle with
* a leading chevron; `CardBody` renders nothing while the card is closed.
*/
collapsible?: boolean;
/** Initial open state in uncontrolled mode. Ignored when `open` is set. */
defaultOpen?: boolean;
/** Controlled open state. Pair with `onOpenChange`. */
open?: boolean;
/** Fires on every toggle with the next open state. */
onOpenChange?: (open: boolean) => void;
style?: CSSProperties;
};
/**
* Bordered surface for a **labeled, self-contained unit** — a file, a service,
* a config block, or a table with a title. Compose with `CardHeader` + `CardBody`.
*
* **When to use Card:**
* - Displaying a named entity (file path, service name, resource).
* - Wrapping a `<Table>` or `<DiffView>` that needs a title.
* - A distinct, bounded section the user might scan by header label.
*
* **When NOT to use Card:**
* - General text sections — use `<H2>` + `<Text>` instead. Not every section
* needs a border.
* - Page-level layout — use `<Stack>` with headings. A canvas should not be a
* wall of stacked cards.
* - Nesting — do not put cards inside cards. Use `<Divider>` within a card body.
*
* Pass **plain text** as `CardHeader` children — the header provides its own
* 12px font. Do **not** put `<H1>` or `<H2>` inside a card header.
*
* Set `collapsible` to make the header a toggle that shows/hides `CardBody`.
*
* @example
* ```tsx
* // Card wraps a titled diff
* <Card>
* <CardHeader trailing={<DiffStats additions={5} deletions={2} />}>
* src/utils.ts
* </CardHeader>
* <CardBody style={{ padding: 0 }}>
* <DiffView path="src/utils.ts" lines={lines} />
* </CardBody>
* </Card>
*
* // Collapsible card
* <Card collapsible defaultOpen={false}>
* <CardHeader>deploy-service.ts</CardHeader>
* <CardBody>Service handles rolling deployments across regions.</CardBody>
* </Card>
*
* // Bad — card wrapping plain text that should just be a heading
* // Use <H2>Overview</H2><Text>…</Text> instead.
* ```
*/
export declare function Card({ children, variant, size, stickyHeader, collapsible, defaultOpen, open: openProp, onOpenChange, style }: CardProps): JSX.Element;
export type CardHeaderProps = {
/** Plain text title. Do **not** pass headings, buttons, pills, or layout rows. */
children?: ReactNode;
/** Small trailing content aligned to the right edge — a status label, a
* single pill, or a short metadata string. Keep it compact. */
trailing?: ReactNode;
style?: CSSProperties;
};
/**
* 28px header row (32px at `size="lg"`). A compact label for the card.
*
* **`children`** — plain text only. This is a 12px label, not a toolbar.
* Do **not** pass `<H1>`, `<H2>`, `<Text weight="bold">`, `<Pill>`,
* `<Button>`, `<Row>`, or any interactive/layout elements as children.
*
* **`trailing`** — optional right-aligned slot for small status indicators
* (a short label, a single `<Pill>`, a metadata string like a timestamp).
*
* @example
* ```tsx
* // Good — plain title
* <CardHeader>config.yaml</CardHeader>
*
* // Good — title with trailing status
* <CardHeader trailing={<Pill active>Healthy</Pill>}>
* billing-service
* </CardHeader>
*
* // Bad — heading in header (use CardHeader text, not H2)
* // Bad — buttons in header (no room, wrong context)
* // Bad — multiple pills in header (use trailing for one, or move to CardBody)
* ```
*/
export declare function CardHeader({ children, trailing, style }: CardHeaderProps): JSX.Element;
export type CardBodyProps = {
children?: ReactNode;
style?: CSSProperties;
};
/**
* Padded content area inside a Card.
* Override `style` to adjust padding for custom layouts.
*
* @example
* ```tsx
* <Card>
* <CardHeader>Overview</CardHeader>
* <CardBody>This service is currently healthy.</CardBody>
* </Card>
* ```
*/
export declare function CardBody({ children, style }: CardBodyProps): JSX.Element | null;
export type ButtonProps = {
children?: ReactNode;
variant?: "primary" | "secondary" | "ghost";
disabled?: boolean;
type?: "button" | "submit" | "reset";
style?: CSSProperties;
onClick?: () => void;
};
/**
* Action button (24px height, sized to its label). **Never stretch to full
* width** — buttons are always inline and hug their text.
*
* @example
* ```tsx
* <Row gap={8}>
* <Button variant="primary" onClick={handleSave}>Save</Button>
* <Button variant="secondary">Export</Button>
* <Button variant="ghost">Cancel</Button>
* </Row>
* ```
*/
export declare function Button({ children, variant, disabled, type, style, onClick }: ButtonProps): JSX.Element;
export type PillTone = "neutral" | "added" | "deleted" | "renamed" | "success" | "warning" | "info";
export type PillSize = "sm" | "md";
export type PillProps = {
children?: ReactNode;
/** Whether the pill is in its selected/active state (filled background). */
active?: boolean;
/**
* Semantic tone. Recolors the border and text. When `active` is also
* set, fills the background with the tone color at low opacity.
* Defaults to `neutral` (current stroke/text tokens).
*/
tone?: PillTone;
/**
* Visual size. `"md"` (default) is the standard pill. `"sm"` is a
* compact variant with smaller text, tighter padding, and no border —
* designed for tight spaces like `CardHeader` trailing slots.
*/
size?: PillSize;
/** Shown before the label (icon, emoji, etc.). */
leadingContent?: ReactNode;
/** e.g. shortcut hint — matches ui `Pill` ghost keyboard hint (muted primary). */
keyboardHint?: string;
disabled?: boolean;
title?: string;
style?: CSSProperties;
onClick?: () => void;
};
/**
* Pill-shaped label or toggle button. Use for tab bars, filter groups, or
* action suggestions. Set `active` for the selected state (filled background).
*
* @example
* ```tsx
* // Tab-style selector
* <Row gap={8}>
* {tabs.map(tab => (
* <Pill key={tab} active={tab === selected} onClick={() => setSelected(tab)}>
* {tab}
* </Pill>
* ))}
* </Row>
*
* // Action suggestion with shortcut hint
* <Pill onClick={handlePlan} keyboardHint="⇧Tab">Plan new idea</Pill>
* ```
*/
export declare function Pill({ children, active, tone, size, leadingContent, keyboardHint, disabled, title, style, onClick }: PillProps): JSX.Element;
export type StatTone = "success" | "danger" | "warning" | "info";
export type StatProps = {
/** The primary metric value (number, percentage, short string). */
value: ReactNode;
/** Label below the value. */
label: string;
/** Semantic color for the value. Omit for default primary text. */
tone?: StatTone;
style?: CSSProperties;
};
/**
* Single metric display — a large value with a compact label beneath it.
* Use inside `<Grid>` for dashboard summary strips.
*
* @example
* ```tsx
* <Grid columns={3} gap={16}>
* <Stat value="4" label="Healthy" tone="success" />
* <Stat value="1" label="Degraded" tone="warning" />
* <Stat value="99.2%" label="Avg Uptime" />
* </Grid>
* ```
*/
export declare function Stat({ value, label, tone, style }: StatProps): JSX.Element;
export type CalloutTone = "info" | "success" | "warning" | "danger" | "neutral";
export type CalloutProps = {
/** Body content. Plain strings, `<Text>`, `<Code>`, `<Link>`, or short lists. */
children?: ReactNode;
/** Semantic tone. Recolors the border, background tint, and title text. */
tone?: CalloutTone;
/** Optional bold title line, shown above the body in the tone color. */
title?: ReactNode;
/** Optional leading icon (emoji, inline SVG, or short text glyph). */
icon?: ReactNode;
style?: CSSProperties;
};
/**
* Tinted, bordered notice block for warnings, tips, or short status messages
* inline within a section.
*
* @example
* ```tsx
* <Callout tone="warning" title="Heads up">
* Rolling deploy is in progress. Metrics may be noisy for 10 minutes.
* </Callout>
* ```
*/
export declare function Callout({ children, tone, title, icon, style }: CalloutProps): JSX.Element;
//# sourceMappingURL=ui-primitives.d.ts.map

View File

@@ -0,0 +1,2 @@
export {};
//# sourceMappingURL=ui-primitives.test.d.ts.map

56
skills-cursor/canvas/sdk/usage-bar.d.ts vendored Normal file
View File

@@ -0,0 +1,56 @@
/**
* Segmented usage bar primitive — proportional pills + remainder, with an
* optional one-line label row above. Visually matches `packages/ui`
* `ContextUsageTray`'s usage bar, themed for canvas via the shared `Color`
* palette and `useHostTheme()` semantic tokens.
*/
import type { CSSProperties, JSX, ReactNode } from "react";
import { type Color } from "./canvas-tokens.js";
export interface UsageBarSegment {
/** Stable identifier (used as React key). */
readonly id: string;
/** Proportional weight for this segment. Non-finite or `<= 0` is treated as 0. */
readonly value: number;
/**
* Optional explicit color. Defaults to a rotation through
* `usageColorSequence` by index, matching the original `packages/ui`
* `ContextUsageTray` order so the same index lands on the same hue.
*/
readonly color?: Color;
}
export type UsageBarProps = {
/** Segments rendered left-to-right; widths are proportional to `value`. */
readonly segments: readonly UsageBarSegment[];
/** Total weight of the bar. The remainder span fills `max(0, total - sum(values))`. */
readonly total: number;
/** Optional small label rendered above the bar, left-aligned. */
readonly topLeftLabel?: ReactNode;
/** Optional small label rendered above the bar, right-aligned. */
readonly topRightLabel?: ReactNode;
readonly style?: CSSProperties;
};
/**
* Segmented horizontal usage bar — proportional category pills with a
* remainder span. Use to visualize a fixed-budget breakdown (context window
* tokens, storage usage, etc.).
*
* @example
* ```tsx
* <UsageBar
* total={120_000}
* topLeftLabel="64% Full"
* topRightLabel="76.8K / 120K Tokens"
* segments={[
* { id: "system", value: 8_000, color: "gray" },
* { id: "tools", value: 24_000, color: "purple" },
* { id: "rules", value: 12_000, color: "green" },
* { id: "skills", value: 6_000, color: "yellow" },
* { id: "mcp", value: 4_000, color: "pink" },
* { id: "subagents", value: 8_800, color: "blue" },
* { id: "conversation", value: 14_000, color: "orange" },
* ]}
* />
* ```
*/
export declare function UsageBar({ segments, total, topLeftLabel, topRightLabel, style }: UsageBarProps): JSX.Element;
//# sourceMappingURL=usage-bar.d.ts.map