# Artifact Architecture

> The foundational constraints and structure of a published Claude artifact - the strict CSP that blocks every external host, the self-contained requirement, the snapshot-versus-live data decision, page budget, title and favicon discipline, and redeploy semantics. Read this before building any artifact, app, or dashboard. Trigger on "artifact", "build a dashboard", "publish a page", "make an app", "interactive page", "why won't my chart library load", "CSP", "self-contained".

- Skill: `lukehle/artifact-architecture` (Agent Skill)
- Install (CLI): `npx skillmds@latest add lukehle/artifact-architecture`
- Raw SKILL.md: https://api.skillmd.com/api/skills/lukehle/artifact-architecture/raw
- Safety review: pending
- Works with: Claude Code, Claude.ai, OpenAI Codex
- Category: Finance & Business
- Author: Lukehle (https://skillmd.com/u/lukehle)
- Updated: 2026-09-17
- Page: https://skillmd.com/skills/lukehle/artifact-architecture

---


# Artifact architecture

Read this before writing a line of an artifact. Most of what goes wrong is decided in the first
minute, by assuming an environment that does not exist.

---

## The constraint that changes everything: no external hosts

A published artifact runs under a **strict Content Security Policy that blocks requests to every
external host**. Not "discouraged" — blocked. That includes:

- CDN scripts (D3, Recharts, Chart.js, Plotly, Highcharts, ECharts)
- External stylesheets and web fonts (Google Fonts, any `@import` of a remote URL)
- Remote images
- `fetch` / `XHR` / WebSockets to any other origin

**Everything must be inlined**: CSS in `<style>`, JS in `<script>`, images as `data:` URIs, fonts as
base64 or not at all. There is no build step and no bundler — you write the page.

Three consequences that people discover the hard way:

1. **You will hand-build your charts.** See `svg-charting`, and the `chartkit.js` utility that ships
   with it. This is less painful than it sounds — scales, ticks, and paths are a few dozen lines,
   and you avoid inlining 300KB of library to use 5% of it.
2. **Mermaid is the exception.** Artifacts render mermaid natively — ` ```mermaid ` fences in
   Markdown, `<pre class="mermaid">` in HTML. No library involved. See `mermaid-in-artifacts`.
3. **System font stacks only**, unless you embed a font as base64 and can afford the bytes.

---

## Write the page, not the document

The file you hand to the publisher is wrapped in `<!doctype html><head>…</head><body>` at publish
time. So write **page content directly** — no `<!DOCTYPE>`, `<html>`, `<head>`, or `<body>` tags of
your own. A minimal CSS reset is already applied.

```html
<title>ARR Bridge</title>
<style>
  :root { --bg: #fff; --ink: #14161a; /* … */ }
</style>
<main> … </main>
<script> … </script>
```

**The `<title>` must be in the first 8KB of the file** — that is all that gets scanned. Put it at the
very top, before a large inline `<style>` block pushes it out of range.

---

## Naming

The title is a **name, not a summary**. It appears in the browser tab and in a gallery beside many
other pages, so it needs to be picked out at a glance:

| Good | Bad | Why |
|---|---|---|
| `ARR Bridge` | `Q3 ARR Bridge Analysis and Commentary` | The second is a description |
| `Runway Model` | `Financial Dashboard` | Generic label, indistinguishable from ten others |
| `Cohort Explorer` | `Cohort Explorer — Retention by Acquisition Month` | The explainer belongs in the description field |

Two to four words, distinctive to *this* page. Keep it **stable across redeploys** — a title that
changes reads as a different page. Same for the favicon emoji: users find their tab by its icon, so
only change it on a hard pivot in what the page is about.

The one-sentence explanation goes in the `description`, which becomes the gallery card's subtitle.

---

## The data tier decision

Decide this before anything else, because it determines the whole architecture.

### Tier 1 — baked snapshot (default)

Data pulled at build time and embedded in the page. Needs no runtime capability, works everywhere,
is reproducible, and **can be shared**.

Choose it for anything reported, sent, or presented. For financial reporting it is usually correct
on the merits, not just as a fallback: a figure that changes between when a deck is sent and when it
is read is a defect. Stamp the as-of.

### Tier 2 — live connector data

The page calls the **viewer's** claude.ai connectors at open time. Two constraints decide most cases:

- The source must be a connector **on the organization's account**. A locally-configured MCP server
  is not reachable from a published page.
- A page declaring connector access **cannot be shared publicly** — each viewer authenticates as
  themselves.

Choose it for an operational monitor someone watches — cash position, collections, pipeline. See
`live-data-artifacts`.

### Tier 3 — the page is the record

Viewers write state back and everyone sees it: trackers, checklists, sign-off boards, polls. See
`stateful-artifacts`.

**Tiers combine.** A close tracker is Tier 3 for its checklist and Tier 1 for its reference figures.

---

## Budget

The rendered page must be **≤16MB**, and `data:` URIs count. In practice you want far less — a page
a viewer waits on is a page they stop opening.

| Element | Realistic budget |
|---|---|
| Markup + CSS + JS | 50-200KB |
| Embedded data | keep under ~500KB; pre-aggregate rather than shipping raw rows |
| Images | inline SVG where possible; a base64 PNG is rarely worth it |
| Fonts | usually skip; a system stack costs nothing |

If your data does not fit, the answer is aggregation, not compression. See `artifact-data-loading`.

---

## Responsive and theme-aware, always

- Relative units, flexbox/grid, `max-width: 100%` on media.
- **Wide content scrolls inside its own container** (`overflow-x: auto`) — tables, chart canvases,
  code. The page body must never scroll horizontally.
- The viewer's theme has **three** states, not two: explicit dark, explicit light, and system
  default. Getting this wrong is the single most common visual bug. See `artifact-theming`.

---

## Redeploy semantics

- **Same file path → same URL.** Edit the file, publish again, it updates in place.
- **Different file path → a new artifact** at a new URL. Only do that when you intend a separate
  page.
- Updating an artifact from an earlier session needs its **URL** passed explicitly; publishing
  without it creates a separate artifact rather than updating the existing one.
- Artifacts are **private by default**. Publishing does not share; the user decides that separately.

---

## Before you build, answer these

- [ ] Which data tier, and why that one?
- [ ] What is the as-of, and where does it appear on the page?
- [ ] Does anything here need to be shared? (If yes, Tier 2 is out.)
- [ ] What is the title — a name, two to four words?
- [ ] What does the page do when data is unavailable, empty, or stale?
- [ ] Does it read in both themes and print in greyscale?
- [ ] Is every byte self-contained?

---

## Related skills

- `svg-charting` — building charts without a library
- `mermaid-in-artifacts` — the one thing that renders natively
- `live-data-artifacts` / `stateful-artifacts` — tiers 2 and 3
- `artifact-data-loading` — fitting data in the budget
- `artifact-theming`, `artifact-accessibility`, `artifact-performance`, `artifact-testing`

