Agent Skills › mindsdb/anton › build-html-dashboard

build-html-dashboard

GitHub

用于手动编写自包含 HTML 数据仪表板,规定使用 Apache ECharts、暗色主题及内联资源等规范,强调增量写入与内存控制以规避失败。

anton/core/memory/builtin_skills/build-html-dashboard/SKILL.md mindsdb/anton

Trigger Scenarios

需要手写 HTML 图表或仪表板 generate_artifact 生成失败需手动修复

Install

npx skills add mindsdb/anton --skill build-html-dashboard -g -y
More Options

Non-standard path

npx skills add https://github.com/mindsdb/anton/tree/main/anton/core/memory/builtin_skills/build-html-dashboard -g -y

Use without installing

npx skills use mindsdb/anton@build-html-dashboard

指定 Agent (Claude Code)

npx skills add mindsdb/anton --skill build-html-dashboard -a claude-code -g -y

安装 repo 全部 skill

npx skills add mindsdb/anton --all -g -y

预览 repo 内 skill

npx skills add mindsdb/anton --list

SKILL.md

Frontmatter
{
    "name": "build-html-dashboard",
    "metadata": {
        "provenance": "builtin",
        "display_name": "HTML dashboard & visualization output format"
    },
    "description": "ONLY for writing dashboard or chart HTML BY HAND. NOT needed on the normal path — create_artifact(type=\"html-app\") followed by generate_artifact writes the dashboard itself and carries this contract internally. NOT needed to change the content of a report or add sections (report_tools does that; see DIRECT REPORTS). Recall this before rewriting the code of a generated dashboard, or when generate_artifact failed and the user asked you to continue manually: it is the output contract — self-contained file rules, Apache ECharts setup, dark theme, layout\/design standards, large-dataset handling. Not needed to repair a page generate_artifact returned as browser_check_failed: that result's instruction says how."
}

LIST THE INSIGHTS (terse — one line each, not an essay): Before coding, list the insights you want to present/convey/highlight as 1 - <chart/infographic/etc>: <insight it conveys and why it matters>.. Example: 1 - Line chart of weekly signups: shows growth inflection after the March launch, flags whether momentum is sustained. This is a checklist, not a brief — no narrative prose, no design discussion.

BUILD THE DASHBOARD — use multiple scratchpad cells, but produce ONE single self-contained HTML file:

Before the first write, call create_artifact(type="html-app", name=..., description=..., primary="index.html") and use the returned <artifact_path> for every file you write. All paths below referring to "the output directory" mean <artifact_path>. The final dashboard MUST be a single .html file with all data, CSS, JS and images inlined, with no exception: the web app preview shows only the page itself, so a separate file next to it (dashboard_data.js, logo.png) does not load there, whatever its size. Build every page this way so it renders on every deployment. Keep the whole page under 8 MB; a larger page cannot be checked in a browser.

REROUND DISCIPLINE (critical — most "round-cap exhaustion" failures we've seen on real dashboards come from drifting off one or more of these):

  1. ONE scratchpad, ONE name. Pick a name on the first cell (e.g. dash) and reuse it for the entire build. Switching names (build_pres → write_html → pres1 …) creates separate isolated environments — variables in one don't exist in another — and burns rounds on recovery.
  2. WRITE TO DISK INCREMENTALLY. Open the output .html once in 'w' mode, then open(path, 'a') to append head → body skeleton → each chart section → nav/JS → closing tags. Each cell appends a small chunk you can sanity-check. Do NOT build a single 20KB+ HTML string in memory and write it at the end.
  3. CAP STRING SIZE PER CELL at ~5KB. Large-string scratchpad calls are the single biggest cause of silent failures (the tool occasionally drops the code payload on oversized inputs and the cell comes back with an empty-code error, which still counts against the round cap). If a section is too big, split it.
  4. NEVER re-emit the full HTML mid-build. Append deltas, don't re-print the world. Assembly is a one-line concat at the end, not a re-render of everything you've written so far.
  5. KEEP READS SMALL. To verify what landed, os.path.getsize(path) or the read_text_file tool with start_line=-20 — never read a multi-KB HTML whole.

HOST CONTRACT (critical — the checks generate_artifact runs on every page it produces: 1–7 fail the step (<html lang> in 1 is not checked), 8–9 are warnings, 10 runs in a headless browser. A hand-built page gets the browser check too: after every scratchpad cell that changes an artifact's .html file, the host loads the page in a browser where the deployment can and adds what it found under [artifact lint] in the cell's result, with a screenshot of the page when the check sent one. When it could not check the page, that line says the page was not checked in a browser and why: then tell the user the page is unverified in a browser. Items 1–9 are not checked for a hand-built page, so verify them yourself before you finish):

  1. A complete HTML document with an explicit <body>...</body>, and <html lang> set to the language the user writes in, unless they ask for another language for the page (UI text follows it too).
  2. <meta name="viewport" content="width=device-width, initial-scale=1.0"> in <head>.
  3. No absolute URL in any fetch() call. A map loads its atlas, https://cdn.jsdelivr.net/npm/world-atlas@2.0.2/countries-110m.json, with d3.json(...) (item 9), not with fetch(). No root-relative path either, in src, href, fetch() or new EventSource(): a value that starts with a single / (/logo.png, /api/export) breaks in the app preview and once published, because the page is served under a path prefix that a path from the root drops. Use a relative path for a link to another page (details.html); in a fullstack app, call the backend through the api() helper that build-fullstack-backend defines. https://, //, data:, blob:, # and mailto: values are fine, so href/src links to external sources stay allowed. Moving the same root-relative path into a JS string does not fix it.
  4. Every opened <script> block is closed with </script> — an unclosed one silently disables all JS.
  5. The global name window.__antonCommentsLayer is never used; the host app reserves it for its comment layer.
  6. No universal * { ... !important } rule (a reduced-motion media query is the one accepted exception).
  7. Every z-index is 1000 or below — the host app's own overlays sit above the page.
  8. Significant block containers (div, section, table, main, article) carry stable id attributes — the host app attaches comments to them.
  9. The web app preview runs the page in a sandboxed frame under a content security policy that blocks what it does not allow. Build to that policy so the page renders on every deployment. Scripts load only from https://cdn.jsdelivr.net/npm/<package>@<exact version>/...: pin an exact version, since a floating @4, @5 or @latest cannot be checked. Styles are inline only (<style> or style=): no external stylesheet, so no Leaflet CSS and no web fonts. No external images or map tiles: embed an image as a data: URI. No eval/new Function and no web workers. The default external resources are the two CDN scripts named under Visual design (Tailwind, ECharts); another library only when the user asked for it, from jsDelivr at an exact version. For a map or a globe, use a tested recipe: d3@7.9.0 with topojson-client@3.1.0 and world-atlas@2.0.2, loading countries-110m.json with d3.json (item 3) and turning it into GeoJSON with topojson.feature(world, world.objects.countries). Draw that GeoJSON with d3 into an <svg>; or with ECharts 5.5.1 through echarts.registerMap(name, geoJson) and a map series; or on a globe with globe.gl@2.34.2 with polygonsData. Also tested: three@0.169.0 as an ES module, 3d-force-graph@1.73.4, gsap@3.12.5. Leaflet, raster tiles, MapLibre (it needs a worker) and echarts-gl (it needs eval) break that policy, so they do not render in the web app preview.
  10. Loaded the way the app preview loads it, at a desktop and a phone width, the page logs no console error or uncaught exception, breaks no rule of the preview's content security policy, asks for no separate file, leaves no chart, map or canvas blank, leaves no loading message showing over a view that has drawn, is no wider than the screen, and renders visible content without user interaction. Start every view through this one helper: each chart, map, globe or other view that runs setup code, on load or on its tab's first visit. A view whose setup throws or whose promise rejects then fails alone, with a plain-text message in its place, and every other view still draws: function runView(name, el, setup) { const fail = (err) => { const why = err && err.message ? err.message : String(err); if (el) { el.setAttribute('data-view-failed', name); el.textContent = name + ' could not load.'; } console.error(name + ' failed: ' + why); if (typeof reportError === 'function') reportError(new Error(name + ': ' + why)); }; try { const done = setup(el); if (done && typeof done.then === 'function') done.catch(fail); } catch (err) { fail(err); } }. Call it with the view's title as the page shows it, its element and its setup function, which may be async: runView('Shipment map', document.getElementById('world-map'), drawMap);. Await or return every promise the setup starts, such as the atlas's d3.json(...). Don't end it with a .catch of your own: the helper must see the failure to name and mark the view. Keep the helper as written: its console.error and reportError name the failed view to the check and to the preview's error list, and data-view-failed lets the check find the failed view whatever its message says. A view's own try/catch that only writes a message hides the error from the check. Log with console.error only a view that failed to draw. An expected failure stays silent or uses console.warn: the storage helper in item 11 catches a throw on every load in the preview frame, and a console.error there fails the check. Not checked by the pipeline, but the page breaks inside the frame without them:
  11. BROWSER STORAGE (critical): localStorage / sessionStorage / indexedDB / document.cookie may be unavailable — the in-app preview runs the page in a sandboxed frame. Never touch them directly during top-level initialisation: a throw there aborts the script before any listener is bound, and the page renders with every control dead. Go through one guarded helper with an in-memory fallback.
  12. LINKS: the page runs inside a frame, in the app preview and on the published link alike. target="_top" and target="_parent" do not work; give a link to another artifact or an external site target="_blank", or it opens inside the frame.

SECURITY (critical): Dashboards may be published to the web. NEVER embed API keys, tokens, passwords, connection strings, or any credentials in the HTML, JS, or inline data. Fetch data in scratchpad cells using credentials from environment variables, then serialize only the resulting data into the dashboard. If the user explicitly asks to embed a credential (e.g. for a live-updating dashboard), warn them that publishing will expose it and get confirmation before proceeding.

Build the parts in separate cells, then assemble at the end:

CELL 1 — Serialize data to a JS string variable (programmatic, no HTML): Serialize all computed data (dataframes, metrics, KPIs) into a Python string. Build a Python dict with keys like "kpis", "tables", "charts" — each containing the relevant data. Convert DataFrames with df.to_dict(orient='records'). Use json.dumps(data, default=str) to handle dates, Decimal, numpy types. Store as a Python variable: data_js = 'const D = ' + json_string + ';' — do NOT write to a separate file.

CELL 2 — Build CSS + HTML structure as a Python string variable: Write the HTML head (styles, CDN script tags) and body structure (header, KPIs, chart divs, tabs, tables) as a Python string variable html_body. This cell builds the template.

CELL 3+ — Build JS chart rendering logic as Python string variables: Write the JavaScript that initializes charts, populates tables, handles tabs, etc. Define the runView helper from item 10 first, and start every view through it, views behind tabs included. Split across multiple cells if needed to avoid token limits. Store as js_charts etc.

FINAL CELL — Assemble and write the HTML file: Combine: html = html_body.replace('</body>', f'<script>{data_js}{js_charts}</script></body>') or similar.

SELF-CONTAINED OUTPUT (critical): Inline everything, however large: CSS in <style>, JS in <script>, data as JS variables, images as data: URIs. A big dataset goes into the page as a const D = ...; script, not into a dashboard_data.js beside it, and an image into an <img src="data:...">, not a screenshot.png beside it. The web app preview loads only the page itself, so a separate file does not show there. If the data makes the page too heavy, aggregate it in the cell first instead of splitting it out.

UPLOADED IMAGES AND BINARY ASSETS: Files the user attached or pasted appear with ABSOLUTE paths in the conversation context (.cowork/files/<uuid>/<name> in the app, .anton/uploads/clipboard_*.png for a CLI paste). Use the path you were given — never guess one or scan directories. If the user refers to a file whose path you cannot see, ask them to attach it to the conversation — never ask for a filesystem path.

Never point the HTML at that original path: it sits outside <artifact_path>, and the web app preview loads only the page itself. Embed the image instead, as a data: URI, in one scratchpad cell:

  import base64, mimetypes
  from pathlib import Path
  mime = mimetypes.guess_type(src)[0] or "application/octet-stream"
  b64 = base64.b64encode(Path(src).read_bytes()).decode()
  img_src = f"data:{mime};base64,{b64}"

A photo of several MB makes the page heavy: shrink it first (Pillow, a smaller width, JPEG) when the page would pass a few MB. For uploaded DATA files (CSV/JSON) do not use a data: URI — read them in CELL 1 and inline the result as a JS variable like any other dataset.

WHY: (1) The web app preview loads only the page itself: a separate file does not reach it there. (2) Splitting the build across cells catches JS/CSS errors early: if a cell has a syntax issue in a string, you'll see it before the final assembly. (3) Large datasets in single cells timeout. (4) Self-contained files can be published to the web via /publish without missing assets.

PYTHON → JS STRING SAFETY (critical): When building JS code inside Python strings, escape sequences get resolved by Python BEFORE writing to the file. This means '\n' in Python becomes a literal newline in the output, which breaks JavaScript string literals. Rules:

  • Use '\n' in Python if you need a literal \n in the JS output
  • Use raw strings (r"...") for JS code blocks when possible
  • NEVER use '\n', '\t', or '"' inside JS strings within Python — double-escape them
  • After writing the file, sanity-check that no string literals span multiple lines

Output format:

  • Unless the user explicitly asks for a different format, always output visualizations as polished, single-file HTML pages — never raw PNGs or bare image files.

Visual design:

  • Make it look good by default. Use a dark theme (#0d1117 background, #e6edf3 text), clean typography (system sans-serif stack), generous padding, and responsive layout.
  • STYLING: Tailwind CSS is the recommended way to style the page. Load it once in <head> via CDN: <script src="https://cdn.jsdelivr.net/npm/@tailwindcss/browser@4.3.3"></script> and use utility classes for layout, spacing, typography and colour (bg-[#0d1117] text-[#e6edf3] for the theme). Hand-written CSS in a <style> block stays allowed wherever utilities fall short — keyframes, chart containers, complex selectors — or when the page is too small to need a framework. No other CSS or JS library unless the user asked for one (host contract item 9): the two CDN scripts named in these rules (Tailwind, ECharts), at exactly the versions shown, are the default external resources. Both are fetched every time the page opens, so a page that must work OFFLINE or without external resources (the user says so) skips Tailwind and styles itself with hand-written CSS.
  • ALWAYS use Apache ECharts for interactive charts. Load it via CDN: <script src="https://cdn.jsdelivr.net/npm/echarts@5.5.1/dist/echarts.min.js"></script>. No Python dependencies needed — just write the HTML with inline JS. Use ECharts' built-in dark theme: echarts.init(dom, 'dark'), then customize colors to match #0d1117 background.
  • NEVER use Plotly, matplotlib, or other charting libraries unless the user explicitly asks.

Chart type by purpose (the same rule generate_artifact gives its generators):

  • Line for change over time; bar for comparing categories (horizontal bars when labels are long); stacked bar for composition across categories; scatter for the relation between two measures.
  • Pie or donut ONLY for a simple part-of-a-whole with few slices — never for comparison or change over time.
  • A value with no dimension is a KPI card, not a chart. Titles, legends and axis labels name what the data IS. No chart junk: 3D, gradients, decorative markers.

Axes and units (critical — a number without a unit is a guess for the reader):

  • Every axis label names the measure AND its unit (Revenue, USD, Latency, ms, Share, %).
  • Every number in a tooltip, axis or KPI goes through a formatter: thousands separators, fixed decimals, currency or percent sign, unit suffix — never a raw float.
  • One date format per page, consistent between axes and tooltips. Say when a value is estimated, sampled or converted, and from which unit.

Line smoothing (critical — smooth: true misrepresents volatile data):

  • DEFAULT: smooth: false on ALL line series. Straight segments between data points are the honest representation — they show actual volatility, drawdowns, and inflection points.
  • EXCEPTION: Use smooth: true ONLY for cumulative/monotonic series (cumulative returns, running totals, growth curves) where the trend matters more than point-to-point moves.
  • Decision heuristic: Does the line ever reverse direction meaningfully? If yes → smooth: false. Is it a running sum, cumulative metric, or long-horizon trend? → smooth: true is acceptable.
  • Line widths: 2.5 for hero/primary lines, 1.5 for multi-line comparisons, 1 for secondary/reference lines.

Chart readability (critical — labels must NEVER overlap):

  • Use axisLabel: { rotate: -45 } or { rotate: 45 } on crowded axes. Set grid: { containLabel: true } so labels never clip. Use legend: { type: 'scroll', bottom: 0 } to place scrollable legends below the chart. For pie/donut charts use label: { show: true, position: 'outside' } with labelLayout: { hideOverlap: true }. For bar charts with many categories, use horizontal bars (yAxis as category) or abbreviate labels with axisLabel: { formatter }. Always configure rich tooltip with formatter functions for precise value display on hover. Use dataZoom for time series so users can zoom into ranges.

Multi-tab / multi-view dashboards (critical — charts fail silently on hidden containers):

  • ECharts, Chart.js, and Plotly all render nothing when called on a container with display: none or 0×0 dimensions — no error, no warning, just a blank chart. NEVER call echarts.init() inside DOMContentLoaded for tabs/pages that start hidden.
  • Initialize charts lazily, gated on first visibility: in the tab-click handler, check a Set of already-rendered tabs and call the page's init function only on first visit. Example pattern: const _rendered = new Set(['overview']); function showPage(name) { /* toggle classes */ if (!_rendered.has(name)) { _rendered.add(name); initChartsFor(name); } } — only the default-visible page initializes on load. initChartsFor(name) starts each view on that tab through runView (item 10), so a view that fails on a tab fails alone.

Layout and composition:

  • For non-chart visualizations (tables, reports, dashboards), write clean HTML/CSS directly. Use CSS grid or flexbox. Add subtle styling: rounded corners, soft shadows, hover effects.
  • When showing multiple related visuals, combine them into a single page with sections, not separate files. Ensure each chart has enough height (min 400px) and breathing room between them so nothing feels cramped.
  • Hero KPI cards at the top (large numbers, color-coded positive/negative, with delta arrows).
  • Main narrative chart immediately below the KPIs — this is the chart that tells the story.
  • Supporting charts below, each with a clear subtitle explaining what it reveals.
  • Annotations on charts: use ECharts markLine for thresholds, markPoint for outliers, and markArea for highlighted regions. A chart without annotations is a missed opportunity.
  • The goal: every visualization should look like a polished product page, not a homework assignment. Think dark-mode dashboard, not Jupyter default.

Responsive layout (critical — dashboards must work on phones too):

  • ALWAYS include <meta name="viewport" content="width=device-width, initial-scale=1.0"> in <head>. Without this, mobile browsers render at desktop width and the user pinch-zooms.
  • Multi-card sections use grid-template-columns: repeat(auto-fit, minmax(360px, 1fr)) (or 300px on dense layouts). This lets the browser reflow to single-column on narrow viewports without a media query — cards stack vertically instead of getting squashed into unreadable columns.
  • Chart containers use width: 100% and height: min(420px, 60vh) (NOT fixed pixel widths). For each ECharts instance, register a window resize hook so it refits: window.addEventListener('resize', () => myChart.resize()); — without this, rotating a phone or resizing the window leaves charts the wrong size.
  • Tables wrap in <div style="overflow-x: auto;"> so they scroll horizontally on narrow screens rather than overflowing the page. Do NOT set fixed table widths.
  • Default to one column on narrow viewports unless the user explicitly asks for a fixed multi-column layout (e.g. for a printable PDF).

Version History

  • 161d2dd Current 2026-11-03 00:54

    新增对仅浏览器检查失败页面的修复流程支持,并优化了视图隔离与错误处理机制。

  • cf92364 2026-10-19 07:51

    默认文件名从 dashboard.html 改为 index.html

  • 18b043f 2026-09-03 08:35

    从系统提示词迁移为内置Skill,优化召回机制与版本管理,增强安全性与稳定性。

  • 0f2b69b 2026-08-20 00:13

Same Skill Collection

anton/core/memory/builtin_skills/public-data-sources/SKILL.md
anton/core/memory/builtin_skills/build-fullstack-backend/SKILL.md
anton/core/memory/builtin_skills/cowork-product/SKILL.md

Metadata

Files
0
Version
161d2dd
Hash
7fe9d1f4
Indexed
2026-08-20 00:13

Accueil - Wiki
Copyright © 2011-2026 iteam. Current version is 2.155.2. UTC+08:00, 2026-11-03 01:47
浙ICP备14020137号-1