CUBSTER Join the waitlist
DOCS / ARTIFACTS / COMPONENTS

Components

This is the full reference for the fixed registry of 15 typed components an artifact document is composed from — the same registry that validates every write, so this page can't drift from what the API actually accepts. GET /api/v1/artifacts/components and cubster artifact components serve this same data, plus each component's JSON Schema.

summary

The lede: a one-sentence verdict plus 2–5 supporting points, rendered as a distinct card.

When: Open every artifact with this block so a reader has the answer in fifteen seconds. Do not use it as a mid-page section intro, and do not repeat the verdict in the prose that follows.

PROPS
  • verdict (required): string
  • points (required): string[] (2-5 items)
summary example
{
  "type": "summary",
  "props": {
    "verdict": "Move transcription onto background functions before the next launch.",
    "points": [
      "Synchronous transcription times out on any recording longer than about four minutes.",
      "Background functions raise the ceiling to fifteen minutes with no new infrastructure.",
      "The change is one endpoint, one status column, and a polling route."
    ]
  }
}
key-points

A scannable list of short statements, each optionally toned positive, negative, or warning.

When: Reach for this whenever you are about to write three paragraphs that are really a list — it is the default alternative to prose. Not for steps in a sequence, and not for anything with a number in it (use `stat-row` or `data-table`).

PROPS
  • title (optional): string
  • points (required): { text: string, tone?="neutral": "neutral" | "positive" | "negative" | "warning" }[] (1-12 items)
key-points example
{
  "type": "key-points",
  "props": {
    "title": "What changed this week",
    "points": [
      {
        "text": "Chunked uploads shipped; recordings up to 64 MB now transcribe.",
        "tone": "positive"
      },
      {
        "text": "Median transcription latency fell from 41s to 12s.",
        "tone": "positive"
      },
      {
        "text": "Two jobs failed on malformed FLAC headers and were retried by hand.",
        "tone": "negative"
      },
      {
        "text": "The provider timeout is still 8.5s on the synchronous route.",
        "tone": "warning"
      }
    ]
  }
}
detail

A titled section that is collapsed on the web and expanded in print. Holds any blocks except another `detail` or a `tabs`.

When: This is where depth goes so the top level stays short — the derivation, the full log, the rejected alternative. If a reader needs it to understand the verdict, it does not belong in here.

PROPS
  • title (required): string
  • blocks (required): any leaf block (every component except detail and tabs)[] (1-50 items)
detail example
{
  "type": "detail",
  "props": {
    "title": "How the 8.5-second timeout was chosen",
    "blocks": [
      {
        "type": "prose",
        "props": {
          "markdown": "Netlify kills a synchronous function at 10 seconds with no response body of our own. Aborting the provider call at 8.5s leaves enough room to write a 504 that carries the retry URL."
        }
      },
      {
        "type": "stat-row",
        "props": {
          "stats": [
            {
              "label": "Platform ceiling",
              "value": "10s",
              "tone": "neutral"
            },
            {
              "label": "Provider budget",
              "value": "8.5s",
              "tone": "neutral"
            },
            {
              "label": "Headroom",
              "value": "1.5s",
              "tone": "positive"
            }
          ]
        }
      }
    ]
  }
}
callout

A single boxed message in one of four tones — info, warning, success, or danger — with an optional title.

When: Use it for the one thing a reader must not miss on this page. A page with three callouts has none; if two facts both warrant a box, neither does.

PROPS
  • tone (required): "info" | "warning" | "success" | "danger"
  • title (optional): string
  • body (required): string
callout example
{
  "type": "callout",
  "props": {
    "tone": "warning",
    "title": "Migration is one-way",
    "body": "Applying this migration rewrites every stored transcript row. Take a database branch first — there is no down migration."
  }
}
prose

A block of Markdown, rendered sanitized — no raw HTML, no scripts, links restricted to http(s) and mailto.

When: For genuinely freeform writing: an argument, a caveat, a paragraph of reasoning. If the prose runs past about four sentences it is usually a `key-points`, a `data-table`, or a `comparison` in disguise — convert it rather than lengthening it.

PROPS
  • markdown (required): string
prose example
{
  "type": "prose",
  "props": {
    "markdown": "The queue is **not** the bottleneck. Every slow request we sampled spent its time waiting on the provider, not waiting for a worker.\n\nThat matters because it rules out the fix we had planned (more concurrency) and points at the one we had not (a longer budget)."
  }
}
code

A syntax-highlighted code block with an optional filename and highlighted lines. Copyable on the web, ink-on-paper in print.

When: Show the code a reader would otherwise have to be told about in words — a signature, a config block, the three lines that changed. Do not paste a whole file; excerpt it and put the rest behind a `detail`.

PROPS
  • language (optional, default "text"): string
  • filename (optional): string
  • code (required): string
  • highlightLines (optional, default []): integer[] (0-100 items)
code example
{
  "type": "code",
  "props": {
    "language": "typescript",
    "filename": "netlify/functions/transcribe.ts",
    "code": "export default async function handler(request: Request) {\n  const auth = await authenticate(request, { service: \"transcription\" });\n  if (!auth.ok) return auth.response;\n  return Response.json({ ok: true });\n}\n",
    "highlightLines": [
      2,
      3
    ]
  }
}
image

A single image with required alt text and an optional caption. Any http(s) URL, including a Cubster asset URL.

When: Use it for a screenshot or a photograph. If the picture is a diagram you authored, publish it with `cubster diagram` and use the `diagram` block instead — that version stays editable, prints crisply, and respects its own visibility.

PROPS
  • src (required): string<uri>
  • alt (required): string
  • caption (optional): string
  • width (optional): integer
  • height (optional): integer
image example
{
  "type": "image",
  "props": {
    "src": "https://app.cubster.dev/img/example-latency-dashboard.png",
    "alt": "Latency dashboard showing p95 dropping from 41 seconds to 12 seconds after the chunked-upload release",
    "caption": "p95 transcription latency, 30 days",
    "width": 1200,
    "height": 675
  }
}
diagram

Embeds a published Cubster diagram by slug, resolved and rendered inline.

When: Reach for this to show a flow, an architecture, or a state machine you have already published with `cubster diagram`. A diagram only renders when it is at least as visible as the artifact and lives in the same workspace — otherwise the reader sees a placeholder, so do not embed a private diagram in a page you intend to share.

PROPS
  • slug (required): string
  • caption (optional): string
diagram example
{
  "type": "diagram",
  "props": {
    "slug": "aB3dE5fG7hJ9",
    "caption": "Request flow from the CLI through the function to the provider"
  }
}
divider

A horizontal rule (`line`) or a run of blank space (`space`) between sections.

When: Use it sparingly to separate two parts of a long page. A `detail` block or a `key-points` title usually does the job better — a divider adds a boundary without saying what is on either side of it.

PROPS
  • style (optional, default "line"): "line" | "space"
divider example
{
  "type": "divider",
  "props": {
    "style": "line"
  }
}
stat-row

A row of 1–6 headline numbers, each with a label, a value, and an optional change indicator.

When: This is where numbers belong — never inside a paragraph. Use it for the handful of figures a reader should carry away; anything with more than six values, or more than one dimension, is a `data-table` or a `chart`.

PROPS
  • stats (required): { label: string, value: string, delta?: string, tone?="neutral": "neutral" | "positive" | "negative" | "warning" }[] (1-6 items)
stat-row example
{
  "type": "stat-row",
  "props": {
    "stats": [
      {
        "label": "Transcripts this month",
        "value": "1,284",
        "delta": "+18%",
        "tone": "positive"
      },
      {
        "label": "Median latency",
        "value": "12s",
        "delta": "-29s",
        "tone": "positive"
      },
      {
        "label": "Failure rate",
        "value": "0.6%",
        "delta": "+0.2pp",
        "tone": "negative"
      },
      {
        "label": "Storage used",
        "value": "412 MB",
        "tone": "neutral"
      }
    ]
  }
}
data-table

A table of keyed rows with typed columns — sortable, and optionally searchable and filterable on the web. Prints whole and unfiltered.

When: The workhorse for anything with more than one row and more than one dimension. Use it instead of a bulleted list of 'X: 12, Y: 34'. Not for a two-column list of options against criteria — that is `comparison`.

PROPS
  • columns (required): { key: string, label: string, type?="string": "string" | "number" | "date", align?: "left" | "center" | "right" }[] (1-12 items)
  • rows (required): { [key: string]: string | number | null }[] (0-500 items)
  • sortable (optional, default true): boolean
  • filterable (optional, default false): boolean
  • searchable (optional, default false): boolean
  • caption (optional): string
data-table example
{
  "type": "data-table",
  "props": {
    "columns": [
      {
        "key": "provider",
        "label": "Provider",
        "type": "string"
      },
      {
        "key": "median",
        "label": "Median latency",
        "type": "number",
        "align": "right"
      },
      {
        "key": "p95",
        "label": "p95 latency",
        "type": "number",
        "align": "right"
      },
      {
        "key": "cost",
        "label": "Cost / hour",
        "type": "string",
        "align": "right"
      }
    ],
    "rows": [
      {
        "provider": "Deepgram nova-3",
        "median": 12,
        "p95": 31,
        "cost": "$0.26"
      },
      {
        "provider": "Whisper large-v3",
        "median": 34,
        "p95": 96,
        "cost": "$0.36"
      },
      {
        "provider": "AssemblyAI best",
        "median": 19,
        "p95": 44,
        "cost": "$0.37"
      }
    ],
    "sortable": true,
    "filterable": false,
    "searchable": false,
    "caption": "Measured on 200 sample recordings, 30 seconds to 12 minutes."
  }
}
comparison

A criteria-by-options grid with an optional winner marked per row — the 'which should I pick' component.

When: Use it any time a page weighs two to five alternatives; it replaces the paragraph-per-option pattern that makes a decision unreadable. Not for a single option's pros and cons — that is `key-points`.

PROPS
  • title (optional): string
  • options (required): { name: string, tone?="neutral": "neutral" | "positive" | "negative" | "warning" }[] (2-5 items)
  • criteria (required): { label: string, values: string[], winner?: integer }[] (1-20 items)
comparison example
{
  "type": "comparison",
  "props": {
    "title": "Where the transcription queue should run",
    "options": [
      {
        "name": "Synchronous function",
        "tone": "negative"
      },
      {
        "name": "Background function",
        "tone": "positive"
      },
      {
        "name": "External worker",
        "tone": "neutral"
      }
    ],
    "criteria": [
      {
        "label": "Time ceiling",
        "values": [
          "10 seconds",
          "15 minutes",
          "Unbounded"
        ],
        "winner": 2
      },
      {
        "label": "New infrastructure",
        "values": [
          "None",
          "None",
          "A queue and a host"
        ],
        "winner": 1
      },
      {
        "label": "Work to migrate",
        "values": [
          "Already there",
          "One endpoint, one column",
          "Two weeks"
        ],
        "winner": 0
      },
      {
        "label": "Fits recordings up to 3 hours",
        "values": [
          "No",
          "Yes",
          "Yes"
        ],
        "winner": 1
      }
    ]
  }
}
chart

A bar, line, area, pie, or heatmap chart over one to six series (heatmap: rows × columns of values), drawn on the validated Cubster palette with a 'Show data' table beside it.

When: Use it when the shape of the change is the point — a trend, a split, a comparison across time, or (heatmap) a grid of intensity. When the exact figures are the point, use a `data-table`; when there are only a handful of them, a `stat-row`. Every field beyond `kind`/`labels`/`series` is optional: `subtitle`, `footnote`, `valueLabels` (print the number on each mark), up to 4 `annotations` (bar/line/area only), `yFormat: "percent"`, and `null` data points for a gap in the series. `size` selects the canvas for a published chart image and is accepted-and-ignored here — an inline chart always draws at the artifact size.

PROPS
  • **When kind is "bar":**
  • - kind (required): "bar"
  • - title (optional): string
  • - subtitle (optional): string
  • - footnote (optional): string
  • - labels (required): string[] (1-100 items)
  • - series (required): { name: string, data: (number | null)[] (0-100 items) }[] (1-6 items)
  • - stacked (optional, default false): boolean
  • - yLabel (optional): string
  • - yFormat (optional): "number" | "percent"
  • - valueLabels (optional, default false): boolean
  • - size (optional, default "landscape"): "landscape" | "square"
  • - annotations (optional): { at: integer, series?: string, text: string }[] (0-4 items)
  • **When kind is "line":**
  • - kind (required): "line"
  • - title (optional): string
  • - subtitle (optional): string
  • - footnote (optional): string
  • - labels (required): string[] (1-100 items)
  • - series (required): { name: string, data: (number | null)[] (0-100 items) }[] (1-6 items)
  • - stacked (optional, default false): boolean
  • - yLabel (optional): string
  • - yFormat (optional): "number" | "percent"
  • - valueLabels (optional, default false): boolean
  • - size (optional, default "landscape"): "landscape" | "square"
  • - annotations (optional): { at: integer, series?: string, text: string }[] (0-4 items)
  • **When kind is "area":**
  • - kind (required): "area"
  • - title (optional): string
  • - subtitle (optional): string
  • - footnote (optional): string
  • - labels (required): string[] (1-100 items)
  • - series (required): { name: string, data: (number | null)[] (0-100 items) }[] (1-6 items)
  • - stacked (optional, default false): boolean
  • - yLabel (optional): string
  • - yFormat (optional): "number" | "percent"
  • - valueLabels (optional, default false): boolean
  • - size (optional, default "landscape"): "landscape" | "square"
  • - annotations (optional): { at: integer, series?: string, text: string }[] (0-4 items)
  • **When kind is "pie":**
  • - kind (required): "pie"
  • - title (optional): string
  • - subtitle (optional): string
  • - footnote (optional): string
  • - labels (required): string[] (1-100 items)
  • - series (required): { name: string, data: (number | null)[] (0-100 items) }[] (1-6 items)
  • - stacked (optional, default false): boolean
  • - yLabel (optional): string
  • - yFormat (optional): "number" | "percent"
  • - valueLabels (optional, default false): boolean
  • - size (optional, default "landscape"): "landscape" | "square"
  • **When kind is "heatmap":**
  • - kind (required): "heatmap"
  • - title (optional): string
  • - subtitle (optional): string
  • - footnote (optional): string
  • - rows (required): string[] (1-31 items)
  • - columns (required): string[] (1-60 items)
  • - values (required): (number | null)[] (1-60 items)[] (1-31 items)
  • - size (optional, default "landscape"): "landscape" | "square"
chart example
{
  "type": "chart",
  "props": {
    "kind": "bar",
    "title": "Transcription minutes by week",
    "labels": [
      "Week 1",
      "Week 2",
      "Week 3",
      "Week 4"
    ],
    "series": [
      {
        "name": "Synchronous",
        "data": [
          420,
          405,
          310,
          180
        ]
      },
      {
        "name": "Background",
        "data": [
          0,
          60,
          240,
          520
        ]
      }
    ],
    "stacked": true,
    "yLabel": "Minutes",
    "valueLabels": false,
    "size": "landscape"
  }
}
chart-embed

Embeds a published Cubster chart by slug, resolved and rendered inline.

When: Reach for this to show a chart you have already published with `cubster chart create` and want to reuse or keep in sync across pages. For a one-off chart, write an inline `chart` instead. A chart only renders when it is at least as visible as the artifact and lives in the same workspace — otherwise the reader sees a placeholder, so do not embed a private chart in a page you intend to share.

PROPS
  • slug (required): string
  • caption (optional): string
chart-embed example
{
  "type": "chart-embed",
  "props": {
    "slug": "wN4jC8pXaTsQ",
    "caption": "Published weekly from the transcription export, kept in sync across every page that links it"
  }
}
tabs

Two to five labelled panels over the same subject. Prints as stacked sections, each label becoming a heading.

When: Use it for genuine alternatives a reader picks between — one language per tab, one environment per tab. Never for sequential content or for hiding the part of the page that matters: only one panel is visible at a time on the web.

PROPS
  • tabs (required): { label: string, blocks: any leaf block (every component except detail and tabs)[] (1-50 items) }[] (2-5 items)
tabs example
{
  "type": "tabs",
  "props": {
    "tabs": [
      {
        "label": "CLI",
        "blocks": [
          {
            "type": "code",
            "props": {
              "language": "bash",
              "code": "cubster artifact validate report.json\ncubster artifact create --file report.json\n",
              "highlightLines": []
            }
          }
        ]
      },
      {
        "label": "SDK",
        "blocks": [
          {
            "type": "code",
            "props": {
              "language": "typescript",
              "code": "const artifact = await client.artifacts.create({ title, blocks });\nconsole.log(artifact.url);\n",
              "highlightLines": []
            }
          }
        ]
      },
      {
        "label": "REST",
        "blocks": [
          {
            "type": "code",
            "props": {
              "language": "bash",
              "code": "curl -X POST https://app.cubster.dev/api/v1/artifacts \\\n  -H \"Authorization: Bearer $CUBSTER_API_KEY\" \\\n  -d @report.json\n",
              "highlightLines": []
            }
          }
        ]
      }
    ]
  }
}