Data Views

A *data view* renders document content — cards, tables, charts, diagrams — from a WCL data structure rather than hand-authored blocks. It's how you present data gathered about systems: declare the data once, then derive every view from it. The primary tool is a component: a reusable fragment of ordinary wdoc markup with named slots.

Every example draws from one inventory:

let inventory = [
  { name: "web-1", role: "frontend", cpu: 42.0, mem: 60.0, up: true,  status: "note"    },
  { name: "db-1",  role: "database", cpu: 88.0, mem: 75.0, up: true,  status: "warning" },
  { name: "cache", role: "cache",    cpu: 25.0, mem: 40.0, up: false, status: "error"   },
]

Components

Declare a wdoc_component with wdoc_slots and a wdoc_body of ordinary markup. Reference slots in any {slot}…" interpolated string or as a bare identifier in a field (class = [status]). A slot with a default is optional. Instantiate the component by its own name:

CPU

Currently at 42%

Memory

Currently at 88%

wdoc_component dv_metric {
  wdoc_slot label
  wdoc_slot value
  wdoc_slot status { default = "note" }
  wdoc_body {
    callout $"${label}" { class = [status]  body = $"Currently at **${value}%**" }
  }
}

// ... then, anywhere a block is allowed:
dv_metric { label = "CPU" value = 42 status = "warning" }
dv_metric { label = "Memory" value = 88 }    // status defaults to "note"

Interpolating slots into text

Slot values land in text via WCL's $"…" interpolated strings — note the $ prefix. A plain "…" string is literal. Bare references in a field (like class = [status]) need no prefix.

Repeating over data

wdoc_repeater renders its body once per element of each, binding the element to the symbol named by as. Combined with a component, it stamps one card per data row:

web-1

Currently at 42%

db-1

Currently at 88%

cache

Currently at 25%

wdoc_repeater { each = inventory  as = :h
  dv_metric { label = h.name  value = h.cpu  status = h.status }
}

A repeater needs no component — its body is just markup with the loop variable in scope:

wdoc_repeater { each = inventory  as = :h
  h3 $"${h.name}"
  p  $"CPU ${h.cpu}% · Mem ${h.mem}%"
}

Repeaters inside components

A slot can hold a whole list, and a wdoc_repeater inside the body can iterate it. Bindings stack, so the loop body sees both the loop variable and the component's other slots:

Fleet

web-1

Currently at 42%

db-1

Currently at 88%

cache

Currently at 25%

wdoc_component dv_hosts {
  wdoc_slot heading
  wdoc_slot rows                       // a list-typed slot
  wdoc_body {
    h3 $"${heading}"
    wdoc_repeater { each = rows  as = :h
      dv_metric { label = h.name  value = h.cpu  status = h.status }
    }
  }
}

dv_hosts { heading = "Fleet"  rows = inventory }

Content slots (layout wrappers)

A wdoc_content block in a component body marks where the instance's *own* nested blocks render — so a component can frame arbitrary content:

Notes

Anything nested in the instance renders at wdoc_content.

wdoc_component dv_panel {
  wdoc_slot title
  wdoc_body {
    h3 $"${title}"
    wdoc_content          // the caller's nested blocks render here
  }
}

dv_panel { title = "Notes"
  p "Anything nested in the instance renders at wdoc_content."
  list { li "including lists" li "and more" }
}

Tables from data

Set a table's rows to a list of cell-lists — mapped from your data — with an optional header row. utf8 cells run through the inline-pattern engine (so bold, :icons:, links work); other scalars stringify:

HostRoleCPU %
web-1frontend42%
db-1database88%
cachecache25%
table {
  header = ["Host", "Role", "CPU %"]
  rows = map(inventory, fn(h: DvHost) -> list<utf8> { [h.name, h.role, $"${h.cpu}%"] })
}

Wrap a table in a component to reuse the layout, feeding the rows through a slot. Name the slot apart from the table's own rows field (a field that references its own name is a cycle, and renders nothing):

wdoc_component host_table {
  wdoc_slot data
  wdoc_body { table { header = ["Host", "Role", "CPU %"]  rows = data } }
}

host_table { data = map(inventory, fn(h: DvHost) -> list<utf8> { [h.name, h.role, $"${h.cpu}%"] }) }

Charts from data

Charts read their data from value fields, so you map the inventory straight into them — no wrapper needed:

Utilisationcpumem022446688web-1db-1cache%
bar_chart {
  categories = map(inventory, fn(h: DvHost) -> utf8 { h.name })
  series = [
    { name: "cpu", values: map(inventory, fn(h: DvHost) -> f64 { h.cpu }) },
    { name: "mem", values: map(inventory, fn(h: DvHost) -> f64 { h.mem }) },
  ]
}

Connections from data

A wdoc_repeater inside a diagram generates one shape per data element; give each a data-derived id. Then set the diagram's edges to a computed list — mapped from the data's own relationships — and layout = :layered (or :force) positions the graph automatically. Connect by id: each edge's source/destination names a node's id.

WebAPIDBCache
type DvSvc { key: utf8  name: utf8  deps: list<utf8> }

let services = [
  { key: "web",   name: "Web",   deps: ["api"] },
  { key: "api",   name: "API",   deps: ["db", "cache"] },
  { key: "db",    name: "DB",    deps: [] },
  { key: "cache", name: "Cache", deps: [] },
]

// One edge per (service → dependency) pair, derived from the data.
let service_links = flatten(map(services, fn(s: DvSvc) -> list<Edge> {
  map(s.deps, fn(d: utf8) -> Edge { { source: s.key, destination: d } })
}))

diagram { width = 460  height = 300  layout = :layered
  wdoc_repeater { each = services  as = :s
    process $"${s.name}" { id = s.key }   // node label from data, id = key
  }
  edges = service_links
}

Name the binding apart from `edges`

Assign the computed edges from a differently-named binding (edges = service_links), not edges = edges — a field that references its own name is a cycle and renders nothing.

Documenting types

The built-in type_table component documents a schema type by reflecting it — no hand-maintained field lists. type_table { type = Image } renders a table of the type's properties (name, type, whether it's required, and a description), including fields inherited via extends; a second "Child blocks" table lists its @child / @children slots. It's built on the type_fields reflection builtin.

Descriptions and visibility are authored on the schema itself:

DecoratorEffect
@doc("…")Sets the description shown for the field.
@hiddenDrops the field from the generated table.

Function-typed fields (every block's lower hook) are skipped automatically. This very page's Pages and Images field tables are generated this way.

Documenting your own schema

type_table reflects *any* type, so it documents the blocks you declare just as well as wdoc's built-ins. Declare your own root @document alongside import <wdoc.wcl> (the two schemas merge), give each block type a @block("…") kind and @doc descriptions, then reflect them:

import <wdoc.wcl>

@document
type MyDoc {
  @children("project_meta") metas: list<ProjectMeta>
  @child("settings") settings: Settings
}
@block("project_meta") type ProjectMeta { @inline(0) id: identifier  @doc("the owner") owner: utf8 }
@block("settings")     type Settings     { @doc("UI theme") theme: utf8 }

page reference {
  h2 "Top-level blocks"
  block_reference { type = MyDoc }    // one heading + property table per block
}

block_reference { type = MyDoc } walks the document's @child / @children slots and emits an h3 (the block's @block kind) plus a type_table for each — no hand-maintained list, and the reference can't drift from the schema. It's a thin wrapper over the child_types reflection builtin, which returns the element type references of a type's block slots. Drop to the repeater directly when you want a different layout:

wdoc_repeater { each = child_types(MyDoc)  as = :b
  type_table { type = b }
}

Union and interface slots

child_types resolves a @child / @children slot to its declared element type. A slot that accepts a union or interface resolves to that type's name; since type_table documents a single concrete type / interface, such slots aren't expanded into a table per variant — document those members individually.

The pattern

ToolUse it for
wdoc_componentA reusable fragment of wdoc markup with slots — cards, panels, sections. The everyday tool.
wdoc_repeaterRendering a body (or a component) once per element of a data list.
wdoc_contentA component that wraps arbitrary caller-provided content.
table computed rowsA data-driven <table>table { header = […] rows = map(data, …) }.
wdoc_repeater + computed edgesA data-driven diagram — repeater-generated nodes (id from data) wired by a computed edges list, auto-positioned by :layered/:force.
Chart value fieldsFeeding mapped data straight into bar_chart / line_chart / pie_chart.
type_tableAuto-documenting a schema type's fields — reflected via type_fields, described with @doc / @hidden.
block_referenceAuto-documenting every top-level block your @document declares — a heading + type_table per block, via child_types.