SuperAPI AI Directory

datalens-html-pages

Frontend & websites

View repository

Updated 10-15 20:33

About this skill

DataLens HTML pages

DataLens serves standalone HTML pages — whole documents, usually AI-generated reports — by storing them in a private bucket and rendering them in a locked-down sandboxed iframe from a short-TTL presigned URL, with a Content-Security-Policy injected as a <meta> tag at upload. You author a complete <!DOCTYPE html> page; scripts run, but only from an allowlist of CDNs, and most browser APIs are blocked.

Not this skill: HTML inside a chart — markup functions, HTML-markup table cells, ChartEditor.generateHtml. That is a different feature with a strict tag/attribute allowlist and no scripts at all. This skill is only for standalone HTML pages.

The runtime in one picture

  • Sandboxed iframe: sandbox="allow-scripts", allow="", referrerpolicy="no-referrer", and crucially no allow-same-origin — the page runs in an opaque origin (parent also sends Cross-Origin-Opener-Policy: same-origin). Same-origin isolation depends on this; never assume same-origin access.
  • Injected CSP (you do not write it — the platform prepends it, idempotently): default-src 'none' with a small allowlist for script/style/font/img/media, connect-src opened only to the Yandex Maps hosts, and form-action, frame-src, object-src, worker-src, base-uri all 'none'. Full policy in references/csp-and-sandbox.md.
  • Served from a presigned GET that expires in 10–30 seconds, after a server-side permission check. Theme/language ride along as signed query params.

The net effect: make the page fully self-contained. Inline your own CSS/JS (the CSP allows 'unsafe-inline'/'unsafe-eval'), pull libraries only from allowlisted CDNs, and never rely on network, storage, or the parent page.

Authoring rules

Allowed external resources (everything else is blocked by the CSP):

Resource Allowed from
Scripts cdn.jsdelivr.net, cdnjs.cloudflare.com, cdn.tailwindcss.com, yastatic.net, Yandex Maps (api-maps.yandex.ru, *.api-maps.yandex.ru, *.maps.yandex.net, suggest-maps.yandex.ru) (+ inline)
Styles the CDNs above + fonts.googleapis.com, blob: (+ inline)
Fonts cdn.jsdelivr.net, cdnjs.cloudflare.com, fonts.gstatic.com, data:
Images yastatic.net, the Yandex Maps hosts, data:, blob:
Media data:, blob: only
Network (connect-src) the Yandex Maps hosts only — for the Maps API itself, not for your data

Load Google Fonts the standard way (<link> CSS from fonts.googleapis.com, font files from fonts.gstatic.com). Mirror off-allowlist libraries (unpkg, plot.ly, d3js.org, jQuery) through jsdelivr/cdnjs.

Yandex Maps JS API 2.1 works out of the box: load it from api-maps.yandex.ru (with the user's API key when they have one) and render into a fixed-height container. Scripts, tiles and the API's own requests are allowed. Use 2.1, not v3 — v3 refuses to load without a key and needs workers the sandbox blocks. Everything else about the sandbox still holds — your own data is inlined, not fetched.

<script src="https://api-maps.yandex.ru/2.1/?apikey=YOUR_KEY&lang=ru_RU"></script>
<div id="map" style="height: 400px"></div>
<script>ymaps.ready(() => new ymaps.Map('map', {center: [55.76, 37.64], zoom: 10}));</script>

Blocked — do not use (they throw or silently fail):

  • Storage: localStorage, sessionStorage, indexedDB, document.cookie, Cache API → keep all state in memory.
  • Network: fetch, XMLHttpRequest, WebSocket, EventSource, sendBeacon to anything except the Yandex Maps hosts — connect-src allows nothing else. Inline your data into the page instead of fetching it.
  • Frames/objects/forms: nested <iframe>, <object>, <embed>, <form> submission, <base>.
  • Workers, popups, dialogs, downloads, camera/geolocation/fullscreen, navigating the parent frame, and ordinary link navigation — an <a href> won't open on its own; route clicks through OPEN_URL (see Opening links below).

Theme & language — read them from the query string:

const theme = new URLSearchParams(location.search).get('theme'); // 'light' | 'dark' | 'system'
const lang  = new URLSearchParams(location.search).get('lang');  // 'ru' | 'en'

When designing and generating HTML, try to create a page that supports available themes, languages and also adaptive: some users will view them via mobile web version of the DataLens.

Exporting a file — the download APIs are blocked; hand data to the host instead. The host verifies event.source, applies a MIME allowlist + size cap, sanitizes the filename, and creates the download:

parent.postMessage({ code: 'EXPORT', data: { name: 'report.csv', mime: 'text/csv', data: csvString } }, '*');

Opening links — an ordinary <a href> does not navigate inside the sandbox (opaque origin; even target="_blank" only reaches about:blank). To open a URL, ask the host: intercept the click, preventDefault(), and post OPEN_URL. One delegated listener covers every link — gate it on being framed so the file still works opened top-level or previewed locally.

if (window.parent !== window) {
  document.addEventListener('click', (e) => {
    const a = e.target.closest('a[href]');
    if (!a) return;
    const href = a.getAttribute('href');
    if (!href || href.startsWith('#')) return; // in-page anchors scroll normally — leave them
    e.preventDefault();
    parent.postMessage({ code: 'OPEN_URL', data: { url: a.href } }, '*');
  });
}

Encoding & size (enforced at upload):

  • Valid UTF-8; declare <meta charset="utf-8"> within the first 1024 bytes. The server detects mojibake but never repairs it.
  • Do not wrap the document in markdown code fences (```html) — they prematurely close <head>.
  • Page size up to ~10 MB, ideally keep below ~5 MB.

See references/authoring-constraints.md for the exhaustive lists, and assets/report.template.html for a compliant starting point.

Design — before building a dashboard or report page, ask the user whether they want it styled per the DataLens design principles (unless they already said so). If you cannot ask — a non-interactive run with no way to get an answer — default to not applying them: style the page plainly and say in your output that the design principles were skipped. If they agree, follow references/design-principles.md — layout grid, chart-type selection, colors, number formatting, and theming. It relies on the DataLens design tokens in assets/dl-theme-tokens.css and base dashboard styles in assets/dl-dashboard.css — inline both files' contents into the page's <style> (the page must be self-contained) and set g-root g-root_theme_light|dark on the root element per the theme query param. If the user declines, skip these three files and style the page however they ask; the sandbox and CSP rules above still apply.

Validate before you upload

Lint the page against the exact CSP allowlist and sandbox constraints — a clean run means nothing will be blocked or stripped at render:

python scripts/validate_page.py report.html          # errors + warnings
python scripts/validate_page.py --strict report.html # warnings fail too (CI mode)
cat report.html | python scripts/validate_page.py -   # from stdin

It flags off-allowlist resource hosts, blocked APIs (storage/fetch/workers/…), blocked tags, missing charset, mojibake, wrapping code fences, and oversize pages. Run python scripts/validate_page.py --self-test to check the linter itself.

Serve & publish

To upload or publish a page and return its permanent link, follow references/publication.md.

Serving is server-side; know the shape so you author correctly:

  • Presigned GET, 10–30 s TTL, minted only after a permission check (no TOCTOU gap). Don't build flows that assume a long-lived URL.
  • Object stored with Content-Type: text/html; charset=utf-8, Content-Disposition: inline, Cache-Control: no-store. Upload is server-side JSON-RPC (no presigned PUTs — the server owns CSP injection and metadata).
  • The iframe carries the sandbox above; the parent sends COOP: same-origin.

Accepted risks to design around (details in references/csp-and-sandbox.md): a URL opened top-level (within its TTL) loses the iframe sandbox but keeps the injected CSP; a page can self-navigate via <meta http-equiv="refresh">; allowlisted CDNs are not SRI-pinned. Because the frame is same-origin-isolated and network-less, do not put viewer data, parameters, or live LLM output that must stay private into the page — it is a report, not a trusted app surface.

Common pitfalls → fixes

Symptom Cause Fix
Library never loads / CSP error in console script/style host off the allowlist serve it from jsdelivr / cdnjs / tailwind / yastatic
Data never appears code calls fetch/XHR inline the dataset into the page at generation time
"localStorage is not available" / throws storage API in sandbox keep state in memory
Fonts don't render font host off-allowlist Google Fonts (fonts.googleapis.com CSS + fonts.gstatic.com files) or jsdelivr/cdnjs
Image is blank http:// or off-allowlist host use yastatic.net, data:, or blob:
Yandex map is blank Maps v3 used, API loaded from a mirror, or the container has no height use JS API 2.1 from api-maps.yandex.ru and give the container an explicit height
Download button does nothing download APIs blocked post {code:'EXPORT', data:{name,mime,data}} to the parent
Clicking a link does nothing links don't navigate in the sandbox intercept the click and post {code:'OPEN_URL', data:{url}}
Page renders as plain text / broken head wrapping markdown code fences remove the ``` fences
Upload rejected > 10 MB or invalid UTF-8 shrink assets; ensure UTF-8 + early <meta charset>