page
Build and publish a self-contained HTML page to Superset, then answer the comments readers pin to it. Use when the user asks to make or publish a page, turn a report, dashboard, chart, doc, or analysis into a shareable link, update or re-version a page already published, or work through comments left on one, including "make me a page for this", "publish this as a page", "share it as a link", "add a version", "address the comments on that page".
git clone --depth 1 https://github.com/superset-sh/superset /tmp/page && cp -r /tmp/page/plugins/superset/skills/page ~/.claude/skills/pageSKILL.md
# Superset Pages
A page is an **`.html` document** published to a URL people in the org can
open. Publish a single file and it must be self-contained; publish a directory
and its `index.html` is the document, with the images, stylesheets, and media
it references by relative path published alongside it. Every publish mints a version, so a page has history. Readers can
pin a comment to any element on it, and those comments come back to an agent to
fix. That is what makes a page a working surface rather than an export.
Pages are served from their own origin under a strict content policy. Most of
the work in this skill is respecting that policy; a page that ignores it looks
fine locally and breaks silently once published.
## When a page is the right surface
Publish a page when the work has a **reader** and wants a **link**: a report
someone will skim, a dashboard for a standup, a comparison table, a diagram, a
walkthrough of what you changed.
Don't publish when the artifact belongs in the repo (source, docs, config: put
those in files and commit them), or when it genuinely needs a server, a
database, or a login. A page has none of those.
If you're unsure, ask. Publishing is cheap and reversible, but a page the user
didn't want is noise in their org's list.
## The content policy, which is what actually bites
Every page gets its own origin, `https://<pageId>.frame.supersetusercontent.com`, and
is framed with `sandbox="allow-scripts allow-same-origin allow-forms
allow-popups"`. So the page is a real origin of its own, and a locked-down
one. The policy is `default-src 'none'` with a short allowlist, and it is
enforced identically in the desktop pane and the web viewer:
- **No network from script.** `fetch`, `XHR`, `EventSource` and WebSockets are
all blocked, and so is `fetch("data:...")`: a page cannot read its own
inlined data URIs back out. Write pages that need no network at all: bake
the data into the document as a literal, or decode base64 in JavaScript
(`atob`, then `Uint8Array.from`).
- **No compiling code at runtime.** `script-src` carries no `'unsafe-eval'`,
so `eval()` and `new Function()` both raise an `EvalError`. This rules out
inlining any library that builds functions at runtime, which includes
several chart and templating libraries and a number of date and expression
helpers. Check for it before you reach for a dependency: the page renders
nothing and gives no visible reason why.
- **No scripts or stylesheets from a remote host.** `<script
src="https://…">` and `<link rel="stylesheet" href="https://…">` are
blocked, Google Fonts `<link>` tags included. A directory publish's own
files load fine (relative `src`/`href`), and a remote font *file* is
allowed, so an inline `@font-face { src: url(https://…) }` works.
- **Images, video and audio may be remote** (`https:`, `data:` or `blob:`),
but prefer `data:` URIs for anything the page cannot do without: a reader
with the network off sees nothing, and a remote image makes every reader's
browser call that host directly, which hands a third party the IP address
of everyone who opens the page.
- **Storage works** and is scoped to the page: `localStorage`,
`sessionStorage`, `indexedDB` and cookies persist across reloads and across
versions of the same page. Use it for a chosen tab or filter, never for
anything the page cannot rebuild from its own content.
- **No parent access.** The viewer is a different origin, so
`window.parent.document` and `window.top.location` throw. Superset injects
one script into the page for comment anchoring; nothing else listens to
`postMessage`, so don't build a handshake on it.
- **No form submission.** `form-action 'none'`: a `<form>` may exist for its
controls, but submitting it goes nowhere. Handle inputs in script.
Scripts and popups *do* work. Inline JS runs normally, so charts, filters,
sorting, tabs, and interactive controls are all fine, as long as everything
they need is already in the file.
## The other hard limits
1. **`.html` only.** Any other extension is rejected at the CLI.
2. **One file, or one directory.** `superset pages publish ./report/`
publishes a directory: `index.html` is the page, and every other file
ships at its relative path, so `<video src="demo.mp4">`,
`<link href="site.css">` and `<script src="app.js">` all work. Asset
paths may not start with `versions/`, `files/`, `_superset/` or `~`, or
be named `thumbnail.jpg`. Assets go up to 1 GiB each; on republish,
unchanged assets are not re-uploaded. Prefer H.264 MP4 or WebM for
video: iPhone `.mov` recordings may not play in every browser. Remote
CDN links and external stylesheets are still blocked; for a single-file
page, inline all CSS and JS and embed images as `data:` URIs.
3. **3 MB maximum for the HTML document itself**, and base64 `data:` URIs
count toward it at ~1.37× their
raw size. A few small SVGs or PNGs are fine; a photo gallery is not.
4. **Full-bleed frame with a white default background.** Set your own `body`
background explicitly rather than inheriting.
Check before publishing: no `<script src>` or `<link rel="stylesheet">` pointing
at a remote host, no `fetch` of any kind including of a `data:` URI, no `eval`
or `new Function` anywhere in the file or in anything you inlined, page fits in
3 MB, opens correctly from `file://` with the network disabled. Remote images
are the one permitted exception: they go blank offline, which is the price of
not inlining them.
## Design
The page should look deliberate. Avoid the house style of generic AI output:
purple-to-blue gradients, everything centered, uniform pill-rounded corners on
every element, Inter (or system-sans) for every line, and emoji as section
icons. Those read as "generated" at a glance.
Instead: pick a real palette and hold to it, set a typographic scale with actual
contrast between heading and body, and let the layout follow the content: a
data-dense table wantsCanonical three-section structure for Linear and Superset tickets in this repo. Use when creating, drafting, or grooming a ticket.
Validates project structure against co-location and architecture patterns defined in AGENTS.md
Create workspaces, spawn agents, schedule automations, and manage Superset projects/tasks/hosts via the `superset` CLI. Use to orchestrate coding agents across devices from the terminal.
Verify UI behavior end-to-end by driving the running desktop app over the Chrome DevTools Protocol. Use when asked to verify, reproduce, or confirm a UI change, bug, or regression in the real app rather than in tests.
Create a database migration with Drizzle on a fresh Neon branch. Use when changing the packages/db schema or generating migrations.
Walk the user through design or implementation decisions one at a time, or review completed code one change at a time. Use when the user says "walk me through each decision", "let's decide together", "help me work through these choices", "walk me through what you did", or "QA step by step". Present concise context, mutually exclusive options, log each answer, and finish with a summary.
Critique and improve the visual design of an existing UI component with concrete implementation guidance. Use when the user asks to redesign, restyle, reimagine, polish, or improve a component, screen, or interface, especially in React and Tailwind codebases.
Read and update the Superset desktop app's user settings (theme, fonts, terminal, git, notifications, behavior) via the superset CLI, including creating and installing custom themes from JSON. Use when asked to change app settings, switch or create a theme, adjust fonts, or configure desktop preferences without opening the settings UI.