# AGENTS.md — building this Flourish template You are helping build a **Flourish SDK template** — a reusable interactive chart or visualisation that runs inside Flourish. A template is a JavaScript bundle plus a `template.yml` config file. Once published, non-technical users create and update visualisations from it without touching code. This file is the source of truth for how Flourish templates must be structured. Do not rely on general knowledge about Flourish — the rules and gotchas below are specific and easy to get wrong. The user may not be a developer; explain steps clearly and check things actually render before reporting success. If you (or the user) have a specific question this file doesn't answer — how a particular module, setting, API, or feature works — search the official developer docs rather than guessing (see §12). Prefer the docs and this file over recalled general knowledge. --- ## 0. Check the user is set up Before any template work, confirm the prerequisites (walk the user through any that are missing): 1. **Node.js** (v18+): `node --version` — install from https://nodejs.org 2. **Flourish SDK**: `flourish --version` — install with `npm install -g @flourish/sdk` 3. **Logged in**: `flourish whoami` — log in with `flourish login` To cut down on approval prompts, batch related shell commands into one call. If prompts are still frequent, offer to set up an allowlist file for the user's tool. --- ## 1. Core contract — `data` / `state` / `draw` / `update` Every template exports four things (see `src/index.js`): ```js export const data = { data: [] }; // Flourish populates this from data tables export const state = {}; // current values of all settings export function draw() { ... } // called once on load — create the DOM here export function update() { ... } // called on every settings/data change ``` Rules: - `draw()` **must call `update()` at the end** so the first render reflects initial state. - `update()` must be safe to call **many times** — no DOM creation, only updates. - `update()` also fires on every slide change in the Flourish story editor — make transitions work when triggered repeatedly, not just on first load. - `state` must be JSON-serialisable (no functions, DOM nodes, date objects). - Pre-initialise `data` with its shape: `export const data = { data: [] }`. An empty `{}` can make `show_if`/`hide_if` conditions misfire before data loads. - The validator warns if `update()` takes parameters — use an internal wrapper if you need to pass flags. - Optional 5th export `screenshot(opt, takeScreenshot)`: only if the DOM must be prepared before Flourish captures a static image. ### `template.yml` ↔ `state` alignment (do this every time) - Every property in `template.yml` `settings` must exist in `state` with a default. - Every `state` property the user should control must appear in `template.yml`. - When you add a setting, add it to **both files at the same time**. - If `state` and `template.yml` disagree on a default, Flourish uses the **state** default for rendering — keep them in sync. --- ## 2. Build in stages (do not skip) **Stage 0 — Grill an ambiguous from-scratch request.** When the request is a one-liner with no detail ("build me a bubble chart", "make a bar chart race"), stop and ask before building — a vague brief is a lottery, and one round of questions gets a far better first result than guessing. Ask for the few things that most change the outcome: - **A reference image** — a screenshot, sketch, or link to a chart you like. This is the single fastest way to pin down layout, labels, and overall shape. Ask for it first. - **Sample data** — a paste of 3–5 rows with column headers, or a description of the columns and their types (see Stage 1). - **Key behaviour** — colour by category? filter by group? animate on slide change? tooltips on hover? Anything interactive or conditional. Grill **once**, concisely, then proceed — don't interrogate turn after turn. If the user says "just build it", they're running unattended, or the answers are already implied, pick sensible defaults, state your assumptions inline, and build a minimum working version they can react to. A rough chart to correct beats a long questionnaire. **Stage 1 — Clarify the data.** Data shape drives every binding decision. If the user already gave column names/types or a sample, proceed. If it's genuinely unclear, ask for a paste of 3–5 sample rows with headers. If they have no data, agree column names first, then generate a representative sample CSV. Don't invent column names and run. **Assume users always upload their own data.** The baked-in dataset in `data/` is only a replaceable sample — the user will swap in their own data once published. So never hard-code column names or row counts: drive everything through bindings, handle missing/empty values gracefully, and don't assume a fixed number of rows. Key questions (ask only what isn't already clear): - Column names and data types? - Roughly how many rows — a handful or thousands? Assume tooltips are wanted for any template with discrete marks (dots, bars, candlesticks): add `@flourish/info-popup`. Only omit if the user says no tooltips. **Stage 2 — Write a short plan and get approval** (unless the request is already a complete spec, the user says to just build, or you're running unattended — then state the plan inline and proceed). Decide modules as part of the plan, not as polish. Cover: - Which `@flourish` modules are needed vs dropped (a module that isn't used in rendering should not be imported — it still clutters the settings panel). - For `@flourish/colors` and `@flourish/facets`: explicitly say "yes, because…" or "not needed, because…". - Data binding structure (dataset name, binding key names). - Which settings appear in `template.yml`. - The rendering approach in one sentence. - Mobile aspect ratio and whether marks/labels must scale or hide at narrow widths. The first build is a **minimum working version** that renders correctly with the sample data. Polish and edge cases come later. --- ## 3. Settings (`template.yml`) Setting types: `color` (single hex), `colors` (palette array), `number` (add `min`/`max`/`step`), `string` (add a `choices:` block for a dropdown — order is **`[label, value]`**), `text` (textarea), `boolean` (`choices: [[Yes, true],[No, false]]` for a button group), `font`, `code`, `url`, `html`, `hidden`. There is **no `type: choices`** — `choices` is always a modifier on `type: string`. - Section headings are plain strings (no `property`). - Conditional display: `show_if: my_bool`, `hide_if: my_bool`, or `show_if: { my_prop: value }`. - `new_section: true` (rule) or `new_section: "Subheading"` is a **field on a setting entry**, not a standalone item — it needs a `property` or the SDK rejects it. - Widths: `full` (default), `three quarters`, `half`, `quarter`. Keep names short for half/quarter width. - `optional: true` adds a null/none option (only on single bindings/colors). ### Importing module settings ```yaml settings: - import: "@flourish/layout" property: layout ``` Section order convention: **[Chart name] → Colors → Axes → Legends → Popups → Chart background → Number formatting → Layout**. - The **first section is named after the chart type** ("Scatter", "Bar chart", "Box plot") — never "Size" or anything generic. - Height/size settings (`height_mode`, `aspect_ratio`) go at the **bottom of the chart-name section** — no separate section. - **"Layout" is reserved** for `@flourish/layout` (it generates its own heading) — don't name a custom section "Layout". - Aim for 6–8 sections. Use `new_section: "Subheading"` to group within a section. - Narrow broad module imports deliberately: for any imported module with multiple modes (colors, legend, axis min/max), hide unused modes with `show_if: false`. --- ## 4. Data bindings (`template.yml`) Each binding maps a user column to a key in `data.`: ```yaml data: - dataset: data key: label name: Label type: column # single column column: "Data::A" data_type: [string] ``` - Access data through bindings — **never hard-code column names**. The accessor matches the `dataset:` name; the skeleton uses `dataset: data`, so it's `data.data` (e.g. `data.data.map(d => d.label)`), **not** `data.values`. - `data.data.column_names` maps key → the user's original header (use for axis labels: `const x_label = data.data.column_names.x || "X"`). - `type: columns` (plural) binds many columns at once; the value is an array of column names. For "no default" use `columns: "Data::"` (sheet + no letter); `""` errors. **`optional: true` is only valid on `type: column` (singular)** — never on `type: columns`. - **"Info for popups" pattern**: instead of one binding per tooltip field, use a single `type: columns` binding keyed `metadata`. Then call `popup.setColumnNames(data.data.column_names).update()` once so the popup renders "Header: value" pairs. - Consistent key names (themes and tooling depend on them): `x, y, label, color, size, group, filter, facet, metadata, latitude, longitude, source, target, image`. The binding key is **`facet`** (singular); `state.facets` (plural) is the facets module config — different things. - **Keep binding keys AND display `name:`s generic, not tied to the sample data.** Users will bind all kinds of datasets, so name bindings by their *role* in the chart, never after a column in the default CSV. Use `Label`, `X value` / `Y value`, `Size by`, `Colour by`, `Group by`, `Filter by`, `Facet by`, `Info for popups` — not `Country`, `Sales`, `Population`, etc. Same for keys: use `x`, `size`, `color` (the role), not `sales` or `year`. --- ## 5. Modules — pick before writing code Install with `npm install --save @flourish/`. **State property names matter** (themes depend on them) — use exactly the name listed. | Need | Module | state property | |---|---|---| | title, subtitle, header, footer/source, legend containers + responsive sizing — **ALWAYS** | `@flourish/layout` | `layout` | | controls / filters — dropdowns, sliders, buttons, search | `@flourish/controls` + `@flourish/ui-styles` | per control | | colour varies by data | `@flourish/colors` | `color` | | discrete marks (dots, bars…) → tooltips | `@flourish/info-popup` | `popup` | | labelled x/y axes + plot backgrounds | `@flourish/chart-layout` | `x`, `y`, `chart_bg` | | legends | `@flourish/legend` | `legend_container`, `legend_categorical`, `legend_continuous`, `legend_size` | | number / date formatting | `@flourish/formatters` | `x_format`, `y_format` | | grid of charts / small multiples | `@flourish/facets` | `facets` | If a module applies, use it — **do not hand-roll the equivalent** (palette arrays, manual grids, custom HTML title/footer or `