Skip to main content
Use this page when you need to understand how a canvas layout is defined and what canvas_update_widget_spec expects.

What a widget spec is

A widget spec defines the layout and visualization components for a canvas. In MCP, widget specs are:
  • created implicitly when a canvas is created
  • edited deterministically via canvas_update_widget_spec
  • validated against the canvas entity type and field IDs before they are saved
  • authored without top-level widget source/entity bindings, which Capsulate injects before persistence

Canonical shape

Capsulate accepts either:
  • a flat spec with components and rootId
  • a nested tree that can be flattened automatically
The canonical persisted shape is flat:
Entity widgets may include semantic ordering as data.sort:
Use ordering only when it materially affects the requested experience. If omitted, Capsulate applies deterministic newest-record-first ordering. Result limits, paging, Calendar view mode, and renderer layout metadata remain system-owned. Calendar color grouping is semantic and may be selected with props.colorField for any useful selected scalar data point, including dynamic categories without allowedValues. Explicit semantic allowedValueTones take priority; other values receive stable colors from the shared 12-color categorical chart palette, and missing values remain neutral. Omitting colorField keeps all events neutral. Kanban props.groupByField follows the same permissive scalar-field rule. allowedValues provide persistent ordered columns when available; otherwise distinct record values create dynamic columns. Explicit semantic tones take priority and stable colors from the same 12-color categorical chart palette provide the fallback. Dynamic boards show the 12 most populated values and consolidate the rest into a non-writable Other (n) column. Synthetic Other labels change deterministically when a real category would otherwise have the same display label. Pie and donut charts apply allowedValueTones from the calculation’s grouping data point when an exact category match exists. Unmatched categories use the shared categorical palette, and a synthetic metadata-marked Other remainder remains neutral. A real data value named Other stays a normal colored category; when both exist, the generated remainder is displayed as Other categories. This behavior is derived; do not author segment colors or another widget property for it. Missing grouping values are a separate neutral, metadata-marked No value category. If that label is already real, Capsulate uses Missing values with a deterministic suffix. Neither missing-value nor remainder buckets are drillable. Single-series bar charts use the same derived semantic-first coloring for categorical groups. Unmatched values receive stable categorical colors and a metadata-marked synthetic remainder remains neutral. Multi-series bar charts continue to use series colors. Do not author a separate bar-category color property. All semantic chart marks resolve through Capsulate’s shared light/dark semantic palette on desktop and mobile. Authors continue to provide semantic tones only; they do not provide platform colors or hex values. When semantic tones are unavailable, badge renderers use one shared exact-token fallback vocabulary across desktop and mobile. They do not infer a status from a substring of an unknown value.

Entity views and detail cards

Declare the main browsing view with a root-level entityViews.primary entry that references one entity-sourced visual-table and one entity-sourced card-grid. Both representations are available through the user-visible Table/Cards toggle. Do not add display, viewGroup, or viewRole to the referenced components; legacy surface-pair metadata is not inferred. Flyouts and drilldowns need a separate, explicit choice of reusable one-record card. Declare it at the widget-spec root with entityPresentations:
The detail-card component must be an entity-sourced card-grid for the same entity collection and must contain a non-empty cardTemplate. The primary mobile card can also be the detail card, but that relationship is explicit rather than inferred from component order. Capsulate infers the detail-card mapping only when there is one unambiguous candidate.

How Capsulate normalizes widget specs

Before saving, Capsulate:
  • infers rootId if the spec is already flat but missing it
  • converts nested specs into a flat components + rootId structure
  • preserves explicit entityViews mappings; legacy surface-pair metadata is not upgraded
  • preserves explicit entityPresentations mappings and infers them only when unambiguous
  • rewrites entityViews, entityPresentations, and structural component references when generated component IDs are canonicalized
  • normalizes the spec into the canonical saved shape
  • validates bindings against the canvas field and entity context
If you request return_nested_spec, the response also returns the normalized nested shape.

Inferred dashboard composition

Capsulate applies conservative composition guardrails when a dashboard has only a generic, root-level layout:
  • a metric-only SimpleGrid without cols receives responsive column defaults
  • two or more leading metric-card widgets in a root Stack are grouped into a responsive summary grid
  • one root-level detail view plus exactly two supporting charts can use the standard responsive 8/4 detail-and-analysis composition
This inference does not add semantic-role fields to the persisted spec. Configured Grid and SimpleGrid layouts, nested layouts, surface-specific views, workflow layouts, spacing values, typography, and widget height props are preserved. New and refined dashboards store presentation.version: 2 at the root. The renderer only applies these composition defaults to that version, so older saved dashboards keep their existing layout. Set presentation.autoCompose: false to disable inferred composition while retaining all other renderer behavior:

Renderer-owned chart presentation

Canvas chart widgets share one renderer-owned frame for card borders, padding, title treatment, definition help, approximation labels, and empty/error states. Widget titles remain 13px. On desktop, row heights are derived at render time to align neighboring widgets. Grid columns are grouped by their desktop spans, so a wrapped second row is sized independently from the first. For a detail widget beside a stack, the stack’s combined height matches the neighboring widget and its children divide the available space. props.layoutHeight is renderer-owned state and is not a valid authored property. Generated and refined saved specs must omit it. The desktop renderer normalizes each supported row as one automatic composition instead of exposing a separate custom sizing mode. Mobile previews do not apply desktop row equalization. Rows containing flow widgets such as kanban boards, or unsupported nested compositions, keep their natural sizing. A flow widget in one wrapped row does not disable automatic sizing for other rows in the same grid. Grids that change their column spans again above the desktop breakpoint remain fluid rather than receiving heights derived from an uncertain row grouping. Renderer defaults use the widget’s interaction needs rather than one universal height: charts use 300px total cards, tables and card grids use 400px, recent items use 280px, timelines use 320px, checklists use 360px, task lists use 440px, and calendars use 360–480px by available desktop width. Kanban boards retain their flow layout. Unsafe chart overrides are clamped to the minimum complete card height so the plot cannot overflow its frame. The renderer also supplies chart presentation defaults when a spec does not explicitly override them:
  • a stable series palette
  • legends for multi-series charts, without redundant single-series legends
  • compact axis labels with long-label truncation
  • horizontal grid guidance for axis charts
  • useful fixed-height no-data and currency-error explanations
Explicit series colors, including black, are preserved. The stable palette is only assigned when a series omits its color. Calculated bar-chart, line-chart, donut-chart, pie-chart, and heatmap-chart widgets can open the existing matching-items drilldown when their calculation fields support a deterministic record filter. Exact categories use equality; numeric buckets use inclusive lower and exclusive upper bounds; temporal line buckets use timezone-aware exclusive-end ranges; and datetime heatmaps match their derived weekday/hour parts in the chart timezone. Heatmap axes can open the broader one-dimensional subset. Period-normalized numeric buckets remain noninteractive because their displayed ranges cannot be mapped safely to an unnormalized record-field predicate. Empty and zero-value heatmap cells are not interactive; populated cells expose keyboard controls and descriptive accessibility labels. Across the desktop and mobile renderers, interactive widget rows, cards, chart drilldowns, calendar items, and workflow controls expose platform-appropriate accessibility labels and states. Widget surfaces and calendar cells use theme tokens instead of assuming a light background. Tables, card grids, boards, charts, calendars, checklists, and task lists distinguish loading, empty, and failed states so a request failure is not presented as an empty result. Mobile chart and table content scrolls within the available screen width and continues to use natural mobile heights rather than desktop row equalization. Widget generation receives both a small row sample and a deterministic profile of all available preview rows. The profile includes field cardinality and missingness, numeric ranges/totals, date coverage, and common bounded values so the generated spec can avoid weak or misleading charts.

How field and entity context is resolved

Validation uses the canvas field context from:
  1. canvas.extraction_patterns.data_points if present
  2. otherwise top-level canvas.data_points
That means field validation is grounded in the actual canvas pattern, not just the submitted spec.

Authoring rule: use exact field IDs

When binding widgets to fields, use the exact field IDs from the canvas pattern or records_schema. Do not invent new field names and do not switch between display names and IDs. Good:
Avoid:
Static Text and Title content belongs in top-level children. Dynamic text binds through the component’s top-level data field:
Do not place structural children or field-binding objects in props.children.

Common component families

Layout and structure

  • Stack
  • Group
  • Box
  • SimpleGrid
  • Grid
  • Grid.Col
  • Container
  • Card
  • Center
  • Divider
  • Space

Display and text

  • Text
  • Title
  • Badge
  • List
  • List.Item
  • Table
  • Button

Visualization widgets

  • metric-card
  • visual-table
  • line-chart
  • bar-chart
  • donut-chart
  • pie-chart
  • heatmap-chart
  • calendar
  • timeline
  • kanban-board
  • checklist
  • task-list
  • recent-items-list
  • card-grid
timeline is a semantic entity widget. Use dateField for the start time and optionally provide endDateField, originField, destinationField, locationField, categoryField, statusField, and timezoneField. groupByDate adds renderer-owned day headings, while showTemporalStatus derives Upcoming, In progress, or Completed from the configured date range. These fields must reference canvas data points; do not compose timeline rows from primitive Timeline.Item nodes. When categoryField is booking_type, desktop/web timeline markers and mobile row titles show travel emojis: ✈️ flight, 🏨 hotel, 🚗 car rental, 🚆 train, 🚢 cruise, 🗺️ tour, 🎟️ activity, and 📍 other. Category labels remain visible. Matching ignores case and accepts spaces or hyphens in place of underscores; unknown values and unrelated category fields retain neutral presentation. Existing saved travel timelines receive this presentation without a spec migration.

Card subcomponents

  • card-header
  • card-kv
  • card-kv-row
  • card-status-chip
  • card-status-stepper
  • card-countdown
  • card-metadata-grid
  • card-visual
  • card-section-divider
  • card-progress
  • card-actions
  • card-journey
  • card-row
  • card-text
  • card-avatar
Use card-metadata-grid when key/value rows and countdowns share one semantic reading order. Its ordered children must be card-kv or card-countdown nodes. Visible children compact into the next available cell in a one- or two-column grid; the renderer does not reorder nodes by component type. Field-backed card-kv, card-kv-row, card-countdown, and card-progress nodes bind data points without authoring separate labels; the renderer derives their labels from the bound field names. card-countdown reports dates without inferring completion state. It always shows the absolute date and adds relative context for past dates, today, tomorrow, and the next three days. Cards present this compactly as Jun 29 (21d ago), while the accessible label retains the complete context, such as Renewal Date: Jun 29 (21 days ago). It never infers overdue status; an explicit sourced status can be displayed separately. Past dates also remain visually neutral because the date alone does not establish whether an obligation is still incomplete. Status progress components must use canonical stored values. When card-progress.status uses statusOrder, or card-status-stepper.value uses stages, each ordered value must match the bound data point’s allowedValues. If the order is omitted or empty and the bound field has allowedValues, validation can inherit that canonical order. Runtime matching is case-insensitive but preserves punctuation and spacing, so A-B, A B, and AB remain distinct.

Data binding rules

Many widgets require a data binding. The validator enforces this. Widgets that require data binding include:
  • metric-card
  • visual-table
  • line-chart
  • bar-chart
  • donut-chart
  • pie-chart
  • heatmap-chart
  • calendar
  • timeline
  • kanban-board
  • checklist
  • task-list
  • recent-items-list
  • card-grid

Allowed data sources by widget

  • metric-card: calculated
  • visual-table: entity
  • card-grid: entity
  • kanban-board: entity
  • calendar: entity
  • timeline: entity
  • checklist: entity
  • task-list: entity
  • recent-items-list: entity
  • charts (line-chart, bar-chart, donut-chart, pie-chart, heatmap-chart): calculated

Kanban grouping fields

Use any useful scalar data point for groupByField. Prefer status, state, or stage when the board represents workflow progression, but dynamic categories such as assignee, category, or type are valid. The Canvas-owned canvas.kanban_status field remains available for a generic workflow board. Never author props.columns for a Kanban board.
  • Extracted fields with allowedValues use those values as persistent writable columns in their saved order, without a runtime column cap.
  • Extracted fields without allowedValues use the 12 most populated distinct non-empty record values as writable columns. Additional values are consolidated into Other (n), where n is the number of hidden values. A dynamic column disappears when no matching records remain anywhere in the filtered Canvas scope.
  • Empty extracted values appear in a writable No value column only while occupied.
  • Values outside an extracted field’s allowedValues are consolidated into one non-droppable Other column; cards can move out to a configured value.
  • The dynamic overflow Other (n) column is also non-droppable. Cards can move out to any visible dynamic column, but Capsulate never writes the synthetic Other label into the grouped field.
  • canvas.kanban_status supplies not_started (Not started), in_progress (In progress), and done (Done). Missing values resolve to not_started; unexpected stored values appear in non-droppable Other.

Calculation guidance

For calculated widgets:
  • use single-value calculations for KPI-style widgets
  • use grouped calculations for charts
  • use time-based grouping for trend charts
For grouped chart outputs, Capsulate derives renderer data keys and series from the calculation. Example:

Important validation rules

metric-card

metric-card cannot rely on a static number alone. It must have a real binding. Invalid:
Valid:
Authoring note:
  • keep metric values neutral by default
  • use status badges or chart colors for semantic emphasis instead of coloring every KPI number

visual-table

visual-table requires:
  • props.title
  • props.columns
  • props.columns with at least one field-backed column
Each column requires field. Its visible name is derived from that data point by default. Add header only when the column needs an intentional shorter or domain-specific name; omitting it is the canonical form. type controls presentation and may be inferred from field metadata. Supported column types include:
  • text
  • logo
  • badge
  • currency
  • date
  • datetime
  • email
  • url
  • boolean
  • percentage
  • number
Authoring note:
  • keep tables focused on the most important columns
  • prefer a few high-signal columns over wide tables with every field

line-chart

line-chart must provide a grouped calculation; Capsulate adds the calculated source. For grouped time-series calculations:
  • groupBy should be a date/datetime field when appropriate for time-series output

bar-chart, donut-chart, pie-chart, heatmap-chart

These widgets also expect calculated data. Use them for:
  • category comparisons
  • distribution breakdowns
  • aggregate rollups
  • two-dimensional summaries for heatmaps

card-header

card-header requires props.title.

card-status-chip

card-status-chip requires props.value. Allowed variant values are:
  • light
  • filled
  • outline-solid
  • dot

Validation feedback

When possible, the save path returns validation feedback that helps you correct the spec. This can include:
  • errors
  • warnings
  • fixes
  • summary
  • normalized_spec

Failure behavior

If validation fails, canvas_update_widget_spec returns:
  • errors
  • warnings
  • fixes
  • summary
  • normalized_spec
Nothing is persisted until the spec is valid.

Minimal valid example

MCP guidance

Recommended widget-spec workflow:
  1. fetch(canvas:...) to inspect the current canvas and pattern
  2. records_schema to confirm available field IDs
  3. prepare a complete widget_spec
  4. canvas_update_widget_spec
  5. use the returned normalized_spec, warnings, and fixes to refine if needed
When in doubt:
  • bind widgets to real fields or calculations
  • omit widget source and entity bindings; Capsulate applies the Canvas entity consistently
  • prefer compact layouts with explicit IDs and a single clear root
  • start with a few strong widgets instead of trying to visualize every field at once