Custom Patterns
Dieser Inhalt ist noch nicht in deiner Sprache verfügbar.
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
A pattern machine is a regular mashinTalk machine with a specific contract: it accepts data and responds with a components list.
machine @myorg/patterns/metric-card achieves Render a single metric value with label, formatting, and trend indicator.
accepts value as number, required label as text, required format as text trend_direction as text trend_value as number
responds with components as list
implements 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"}]}Using a pattern
Reference your pattern in a page’s render declaration:
machine dashboard responds with active_users as number revenue as number
implements compute fetch {active_users: 1234, revenue: 56789}
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: currencyThe page calls your pattern machine twice, once for each field, and renders both component trees.
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
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
Pattern machines are visible to the LLM via me.patterns:
me.patterns = [ {name: "@myorg/patterns/metric-card", description: "Single metric with trend indicator.", accepts: {value: "number, required", label: "text", format: "text", ...}}, ...]When you ask Koda “add a chart to my dashboard,” Koda reads the 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
Patterns can call other patterns:
machine @myorg/patterns/dashboard-section accepts title as text, required metric_value as number table_rows as list
responds with components as list
implements ask metric, from: @mashin/patterns/metric-card value: input.metric_value label: input.title
ask table, from: @mashin/patterns/data-table rows: input.table_rows
compute compose {components: [{type: "heading", text: input.title, level: 2}] ++ steps.metric.components ++ steps.table.components }A pattern that uses two other patterns. The composition is governed: each pattern call is a machine execution with a ledger entry.
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
The component tree is JSON. Any framework that can render JSON component specs can render mashin pages.
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
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
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
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
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
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
- Surfaces overview - All surface types
- Pages - Page surface basics
- Surface authentication - auth, scopes, allow
- expresses reference - Full syntax reference