Skip to content

Spec vs hand-authored

The YAML spec is the supported path. What the two escape hatches buy you, and what falling back to raw HTML costs.

There are two ways a dashboard can reach Dashies, and they are not equally supported.

The spec path: your AI writes a small YAML document naming a connection, a schedule, datasets, and tiles. The server compiles that into the HTML, the data island, and the refresh manifest, and enforces that all three agree.

The raw body path: your AI writes the finished HTML itself, with the markup, the island, and a separate manifest, and uploads the bytes.

The spec path is the supported one. The raw body path exists because it predates the spec and because some dashboards genuinely need it.

What the compiler does for you

When you publish a spec, the server owns three artifacts you never write:

  • the markup, including every data-dash slot and its attributes
  • the data island, seeded with real numbers from your SQL
  • the refresh manifest that the scheduler will re-run

Because it owns all three, it can check them against each other. A tile bound to a measure your query never returns is a publish error naming the exact field. A measure that cannot be re-aggregated in the mode you chose is a publish error. A funnel stage that is not one of the declared values of its dimension is a publish error, because that stage would render as absent rather than as a drop to zero.

None of those are rendering surprises. They are refusals with a pointer at the field, before anything is stored.

What the spec looks like

A complete, minimal one. This publishes as written, against the built-in self connection:

dashies: 1
title: Dashboards published
source:
  connection: self
  schedule: daily
datasets:
  main:
    sql: select day, sum(dashboards_published) as dashboards from dashies_usage_metrics group by day order by day
    dimensions:
      day:
        type: date
    measures:
      dashboards:
        agg: sum
tiles:
  - type: kpi
    measure: dashboards
    title: Dashboards published
  - type: chart
    chart: line
    x: day
    measure: dashboards

Four required keys at the top level: dashies, title, source, and datasets. source needs a connection and a schedule. Each dataset needs sql, dimensions, and measures. Then either tiles or the whole-look escape hatch below, never both.

Note that connection: self is written out. The convenience of omitting it applies to the exploration tools, not to a spec, where leaving it out is a missing-required-property error.

The two escape hatches

Both stay on the spec path, so you keep validation, seeding, and scheduled refresh. You give up only the parts you explicitly opt out of.

A custom tile

One tile whose HTML and JavaScript you write yourself, reading from named datasets. Everything else on the dashboard stays a managed tile. Use it for a visualization the tile vocabulary does not cover.

Your HTML is mounted verbatim, and your JavaScript runs after the island exists, so it can read the data. A <script> tag inside the custom HTML is inert and produces a warning rather than executing.

The whole-look look field

look replaces the entire page body with HTML you supply, while the datasets, the seeding, and the schedule stay managed. It is mutually exclusive with tiles, and with theme and layout, because it owns the whole page.

The variant worth knowing is look: { from: <slug> }, which references the current published body of the dashboard you are publishing to. That is how you change a dataset or a schedule without re-sending the markup, and how an existing hand-authored dashboard converts to the spec path without its appearance changing by a byte.

The raw body path, and why not to fall back to it

You can still publish finished HTML with a hand-written manifest. Dashboards published that way keep refreshing forever on their own contracts.

The trap is treating it as the fallback when a spec publish is refused. It is not. A refusal is a bug report with a pointer at the field, and it should be fixed in the spec, the SQL, or the publish arguments. Falling back costs more than it looks like it does:

  • You take over hand-maintaining the island and the manifest that the scheduler rewrites, which is where a wrong number becomes permanent.
  • You lose the publish-time structural validation entirely.
  • The whole dataset moves through your AI's context. On the spec path the server runs the query and seeds the rows, so the data never enters the AI's context at all. On the body path the numbers are baked into the bytes the AI sends, so they pass through it twice.

That last point is the practical binding limit. The advertised publish ceiling is 5 MiB, but a real session hit its context wall at around 40 KB of island and spent seven rounds shrinking, dropping a whole dataset, three dimensions, half the time window and most of a detail table to make it fit. If you find yourself deleting real content to make a payload fit, that is the signal to go back to the spec.

Editing a published dashboard

Edit the spec, never the served HTML. A refresh rewrites the island, so a hand-edit to the served bytes is either lost or left inconsistent with the manifest.

Read the stored spec back, change one thing, and republish to the same slug carrying the hash you read. That hash is a lost-update guard: if the stored spec changed since you read it, the publish is rejected and the live dashboard is left untouched, rather than your edit silently overwriting someone else's.

Renaming is a separate operation, not a spec edit. Changing the spec's slug field is refused, because the publish target is the path you publish to; a rename goes through the rename tool, which leaves the old URL redirecting to the new one.

Next

Datasets and the four modes is the decision inside the spec that matters most.

Last updated 2026-08-04