--- name: visualize description: "Builds a self-contained HTML page (chart, table, diagram, dashboard) to show inline in a thread. Use before calling show_html_page, html_preview or html_render, or whenever an answer reads better as a page than as prose." --- # Visualize A page shown inline sits beside a reply, in the reader's theme, and is read in a few seconds. It earns its place only by being clearer than the paragraph it replaces. This skill owns what is particular to such a page. The data work belongs to `data-scientist` and the design work to `frontend`: load those skills for their parts instead of improvising them here. ## The loop Do these in order. Never show a page you have not looked at. 1. **Data: load `data-scientist`.** Compute every figure with its engines (DuckDB or Polars), at the grain the reader needs, and follow its `references/visualization.md` for which chart answers which question. Keep the query: every number on the page must trace back to it. 2. **Design: load `frontend`.** Use it for hierarchy, type, spacing and states. This page's constraints replace its design-system gate: the page brings no palette or fonts of its own and uses the theme the host injects (below). 3. **Build one self-contained page** under the constraints below. 4. **Preview and look.** Render with `html_preview` in dark and in light, at the reply's width and at 390 px. Read the screenshot and the console, fix what you see, and preview again. Where `html_preview` is not offered, open the page file in a browser through the `browser` skill and capture the same four views. 5. **Show it** with `show_html_page` (OmO) or `html_render` (other hosts), with the height the preview measured. Then write only what the page does not already say. ## Constraints of an inline page - **No network.** Inline every script, style, font and image (`data:` URIs); the sandbox blocks every request, so a CDN library leaves a blank chart. Draw with hand-written SVG when a library cannot be inlined. - **Theme from the host.** Color only through the injected custom properties: `--background`, `--foreground`, `--muted`, `--muted-foreground`, `--card`, `--border`, `--primary`, `--accent`, `--destructive`, `--warning`, `--success`, the series colors `--chart-1` to `--chart-6`, and `--font-sans`, `--font-mono`, `--radius`. They hold in both themes and change live; a literal color does neither. A page opened outside the app gets only a few of them, so give each a fallback for both themes: `var(--chart-2, light-dark(#0d9488, #2dd4bf))`. - **Part of the reply, not a card.** Fluid width, no outer padding, border or banner. Give charts fixed pixel heights, not heights that scale with the width. A fluid SVG stretched with `preserveAspectRatio="none"` stretches its text too, so put axis labels in HTML beside it, positioned by percentage. - **Readable without scripts.** On the web a page first renders with scripts off, until the reader presses Run page. Put the figure itself in markup or SVG, and use scripts only for interaction. - **Traceable.** Show how the numbers were computed in a small, muted "How this was computed" block (the query or aggregation), and state caveats near the figure when they change how a number reads: dropped rows, a partial window, a small sample. - **Designed empty and error states.** No data, or a failed query, is stated in the same visual language, never as a blank frame. - **Links.** A link opens only after the reader confirms it in the app, which shows its full address. Keep data out of link addresses. ## Before you show it - The title states the finding, not the dataset name. - Axis labels carry units, numbers use tabular figures and a sensible precision, and nothing clips at 390 px. - The series colors separate in both themes, and the console is clean.