Custom Patterns
このコンテンツはまだ日本語訳がありません。
Patterns are machines. A pattern machine accepts data and produces a component tree. The component tree renders as a web page, an embedded widget, a mobile screen, or a CLI table. You write one pattern; it works everywhere.
Your first pattern
Section titled “Your first pattern”A pattern machine is a regular mashinTalk machine with a specific contract: it accepts data and responds with a components list.
machine metric_card "Metric Card Pattern" achieves goal "Render a single metric value with label, formatting, and trend indicator."
accepts value as number, is required label as text, is required format as text trend_direction as text trend_value as number
responds with components as list
behaves compute render {components: [ {type: "metric-card", value: input.value, label: input.label, format: input.format || "number", trend: {direction: input.trend_direction, value: input.trend_value}} ]}
verifies test "renders with label and value" given {value: 42, label: "Users"} expect {components: [{type: "metric-card", value: 42, label: "Users", format: "number", trend: {direction: null, value: null}}]}You publish this to the registry as @myorg/patterns/metric-card; the declared machine name (metric_card) is its local identifier.
Using a pattern
Section titled “Using a pattern”Reference your pattern in a page’s render declaration (the expresses block of a dashboard machine):
expresses page title: "Dashboard" render active_users as @myorg/patterns/metric-card label: "Active Users" render revenue as @myorg/patterns/metric-card label: "Revenue" format: "currency"The page calls your pattern machine twice, once for each field, and renders both component trees.
The component tree
Section titled “The component tree”Pattern machines produce JSON arrays of atomic component specs. There are ~20 component types that map to platform widgets:
| Component type | What it renders |
|---|---|
text |
Paragraph text |
heading |
Section heading (level 1-6) |
markdown |
Rendered markdown content |
code |
Syntax-highlighted code block |
image |
Image with alt text |
divider |
Horizontal rule |
table |
Data table with rows and columns |
list |
Ordered or unordered list |
key-value |
Labeled key-value pairs |
json-tree |
Expandable JSON viewer |
metric-card |
Single metric with trend |
sparkline |
Inline mini chart |
chart |
Line, bar, or pie chart |
stat-group |
Grid of metrics |
form |
Input form with fields |
button |
Action button |
button-group |
Row of buttons |
alert |
Info, warning, error, success message |
progress |
Progress bar |
spinner |
Loading indicator |
Your pattern composes these types. The renderer handles the pixels. You never write HTML, CSS, or framework code.
Publishing a pattern
Section titled “Publishing a pattern”Publish to the Kura registry like any krate:
mashin publishAnyone can install it:
mashin install @myorg/patterns/metric-cardAnd use it in their machines:
render revenue as @myorg/patterns/metric-cardNo JavaScript. No npm. No frontend rebuild. The pattern is a machine.
AI and patterns
Section titled “AI and patterns”Pattern machines are discoverable through the registry: each published pattern carries its own accepts contract and description, so the catalog of installed patterns is a machine-readable index of what can render what.
When you ask Koda “add a chart to my dashboard,” Koda reads that pattern catalog, selects a pattern whose accepts contract matches your output data, and adds the render declaration. If no pattern fits, Koda can create a new one as a machine.
Composing patterns
Section titled “Composing patterns”Patterns can call other patterns:
machine dashboard_section "Dashboard Section Pattern" achieves goal "Compose a heading, a metric card, and a data table into one section."
accepts title as text, is required metric_value as number table_rows as list
responds with components as list
behaves ask metric, from: @myorg/patterns/metric-card value: input.metric_value label: input.title
ask table, from: @myorg/patterns/data-table rows: input.table_rows
compute compose {components: [{type: "heading", text: input.title, level: 2}] ++ steps.metric.components ++ steps.table.components }
verifies test "composes heading, metric, and table" assuming metric {components: [{type: "metric-card", value: 42, label: "Revenue"}]} assuming table {components: [{type: "table", rows: []}]} given {title: "Sales", metric_value: 42, table_rows: []} expect {components: [{type: "heading", text: "Sales", level: 2}, {type: "metric-card", value: 42, label: "Revenue"}, {type: "table", rows: []}]}A pattern that uses two other patterns. The composition is governed: each pattern call is a machine execution with a ledger entry.
Testing patterns
Section titled “Testing patterns”Pattern machines use the same verifies section as any machine:
verifies test "renders empty table for no rows" given {rows: [], columns: ["a", "b"]} expect {components: [{type: "table", rows: []}]}
test "auto-derives columns" given {rows: [{x: 1, y: 2}]} expect {components: [{type: "table", columns: ["x", "y"]}]}Run tests: mashin test my_pattern.mashin
Adding renderers for new frameworks
Section titled “Adding renderers for new frameworks”The component tree is JSON. Any framework that can render JSON component specs can render mashin pages.
Writing a renderer
Section titled “Writing a renderer”A renderer maps component types to framework-native widgets. For example, a Vue renderer:
const componentMap = { 'text': (props) => h('p', props.text), 'heading': (props) => h(`h${props.level}`, props.text), 'table': (props) => h(VueTable, { rows: props.rows, columns: props.columns }), 'metric-card': (props) => h(VueMetricCard, { value: props.value, label: props.label }), // ... map all ~20 types};
export function renderComponentTree(components) { return components.map(spec => { const render = componentMap[spec.type]; return render ? render(spec) : h('div', `[Unknown: ${spec.type}]`); });}Server-side HTML renderer
Section titled “Server-side HTML renderer”For Rails, Phoenix, Go, or any server-rendered framework:
def render_mashin_component(spec) case spec["type"] when "text" content_tag(:p, spec["text"]) when "heading" content_tag(:"h#{spec['level']}", spec["text"]) when "table" render partial: "mashin/table", locals: { rows: spec["rows"], columns: spec["columns"] } when "metric-card" render partial: "mashin/metric_card", locals: { value: spec["value"], label: spec["label"] } endenddef mashin_component(%{type: "text"} = spec) do ~H|<p><%= spec["text"] %></p>|end
def mashin_component(%{type: "table"} = spec) do ~H|<table>...</table>|endfunc renderComponent(spec map[string]interface{}) template.HTML { switch spec["type"] { case "text": return template.HTML(fmt.Sprintf("<p>%s</p>", spec["text"])) case "table": return renderTable(spec) }}WASM renderer
Section titled “WASM renderer”For high-performance client-side rendering:
// rust wasm renderer#[wasm_bindgen]pub fn render_component_tree(json: &str) -> String { let components: Vec<ComponentSpec> = serde_json::from_str(json).unwrap(); let mut html = String::new(); for spec in components { match spec.component_type.as_str() { "text" => html.push_str(&format!("<p>{}</p>", spec.text)), "table" => html.push_str(&render_table(&spec)), _ => html.push_str(&format!("<div>[Unknown: {}]</div>", spec.component_type)), } } html}Native renderer
Section titled “Native renderer”For iOS (SwiftUI) or Android (Compose):
struct MashinComponentView: View { let spec: ComponentSpec
var body: some View { switch spec.type { case "text": Text(spec.text) case "heading": Text(spec.text).font(.title) case "table": MashinTable(rows: spec.rows, columns: spec.columns) case "metric-card": MetricCard(value: spec.value, label: spec.label) default: Text("[Unknown: \(spec.type)]") } }}The contract
Section titled “The contract”Every renderer implements the same interface:
- Fetch the component tree (JSON) from the mashin API
- Map each component type to a native widget
- Handle events by POSTing back to the mashin API
- Subscribe to WebSocket for real-time updates (optional)
The component types never change. New patterns produce the same types in new arrangements. Your renderer works with every pattern, including ones that haven’t been written yet.
Governance
Section titled “Governance”Every pattern machine execution is governed:
- The behavioral ledger records the render (who, when, what data)
- The
ensuressection applies (a pattern thatis not allowed toshow PII will redact) - Cost is tracked (patterns with
:reasonsteps record LLM token usage) - The evolution ledger versions pattern changes
A pattern is not “just rendering.” It is a governed transformation of data into a visual interface. The same governance that protects machine execution protects rendering.
See also
Section titled “See also”- Surfaces overview - All surface types
- Pages - Page surface basics
- Surface authentication - auth, scopes, allow
- expresses reference - Full syntax reference