コンテンツにスキップ
Developer Preview — APIs and language features may change before 1.0

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.

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.

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.

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.

Publish to the Kura registry like any krate:

Terminal window
mashin publish

Anyone can install it:

Terminal window
mashin install @myorg/patterns/metric-card

And use it in their machines:

render revenue as @myorg/patterns/metric-card

No JavaScript. No npm. No frontend rebuild. The pattern is a machine.

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.

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.

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

The component tree is JSON. Any framework that can render JSON component specs can render mashin pages.

A renderer maps component types to framework-native widgets. For example, a Vue renderer:

vue-mashin-renderer.js
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}]`);
});
}

For Rails, Phoenix, Go, or any server-rendered framework:

app/helpers/mashin_helper.rb
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"] }
end
end
lib/my_app_web/components/mashin_components.ex
def mashin_component(%{type: "text"} = spec) do
~H|<p><%= spec["text"] %></p>|
end
def mashin_component(%{type: "table"} = spec) do
~H|<table>...</table>|
end
templates/mashin_component.go
func 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)
}
}

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
}

For iOS (SwiftUI) or Android (Compose):

MashinRenderer.swift
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)]")
}
}
}

Every renderer implements the same interface:

  1. Fetch the component tree (JSON) from the mashin API
  2. Map each component type to a native widget
  3. Handle events by POSTing back to the mashin API
  4. 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.

Every pattern machine execution is governed:

  • The behavioral ledger records the render (who, when, what data)
  • The ensures section applies (a pattern that is not allowed to show PII will redact)
  • Cost is tracked (patterns with :reason steps 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.