ADR-0019: Claude Artifact Lifecycle — Draft vs. Published Storage Behavior
ADR-0019: Claude Artifact Lifecycle — Draft vs. Published Storage Behavior
Status: Accepted Date: 2026-08-06
Context
Claude artifacts live in two distinct states during their lifecycle:
- Draft — the artifact is being edited by the AI. Users see it in the preview panel on the right side of the Claude chat interface. The URL is a generic canvas URL with no artifact ID in the path:
https://www.claudeusercontent.com/?domain=claude.ai&…
- Published — the artifact has been shared via the “Publish” action. It is accessible via a permanent public URL containing the artifact UUID:
https://www.claudeusercontent.com/artifact/<uuid>?domain=claude.ai&…
The window.storage API (Claude’s custom key-value store) behaves fundamentally differently between these two states.
Draft Mode Storage Limitations
These limitations were confirmed through direct observation and are not documented by Anthropic:
-
Version-siloed storage. Each regeneration of an artifact produces a new version (A1-v1, A1-v2, …). Storage is not shared across versions — A1-v2 cannot read data written by A1-v1. Keys written during draft are ephemeral to that specific version.
-
Shared keys are broken. The
shared: trueparameter onwindow.storage.get/set/listdoes nothing in draft mode. The “shared” concept is tied to a published artifact’s public URL context. -
Storage consent modal is absent. Claude prompts users to grant storage access only when an artifact tries to use
window.storagevia the public URL. In draft mode there is no prompt — calls appear to succeed but have no persistent effect across versions. -
Storage-consent modal dismissal. Even on a published artifact, if the user closes the Claude storage-consent modal without granting access, all subsequent storage calls fail silently with a postMessage error:
event.data.type = "storageList"event.data.error = "error: Storage list failed: Unexpected response type"(The exact message varies by operation type.) The artifact remains accessible, but all
window.storagecalls return errors. A Claude account is required to access published artifacts that use storage.
Decision
1. artifactContext() utility in @alistigo/claude-artifact-api
Add a synchronous runtime helper that detects the current lifecycle state by inspecting document.baseURI:
export interface ArtifactContext { published: boolean; artifactId: string | null;}
export function artifactContext(): ArtifactContext { let path = ""; try { path = new URL(document.baseURI).pathname; } catch {} const m = path.match(/^\/artifact\/([0-9a-f-]{36})/i); return { published: Boolean(m), artifactId: m ? m[1] : null };}document.baseURI is the stable discriminator: it is /artifact/<uuid> only on the published public URL.
2. Guard writes in @alistigo/claude-storage-plugin
All four ClaudeKeyValueStore methods (get, set, del, list) throw immediately when called in draft mode via a shared requirePublished() helper.
3. Dev-mode override in the playground
alistigo-artifact-playground runs all artifacts in a srcdoc iframe. In that context document.baseURI is always the playground’s own development URL — never a Claude published URL — so artifactContext() would always return { published: false }, making it impossible to test published-mode behavior locally.
To work around this, buildIframeSrcdoc() injects a global variable when the aiContext === "claude" and the Published checkbox is checked:
<script>window.claudeArtifactStatus = "published";</script>artifactContext() checks this override before inspecting document.baseURI:
if (typeof window !== "undefined" && window.claudeArtifactStatus === "published") { return { published: true, artifactId: null };}// … URL-based detection …The Published checkbox is visible in the playground’s Config tab only when the AI context is set to claude. When unchecked, URL-based detection runs — which always resolves to draft inside the playground, correctly simulating the Claude preview panel.
artifactId is null in this mode (no real UUID is available), which is a known limitation of playground simulation.
4. Surface draft state in artifact UIs
Artifacts that rely on storage (particularly artifact-storage-explorer) must:
- Detect draft mode at mount time via
artifactContext() - Show a visible Draft badge in the
ArtifactContextMenuContainer(orange, with warning icon) - Render a full-screen grey overlay over interactive content when in draft mode, with an explanation and a call to publish
- Provide a modal (triggered by the Draft badge) explaining all three limitations above
Rationale
Without this detection and communication:
- End users run the storage explorer in draft mode and see empty data, then assume the artifact is broken.
- Developers testing in draft mode may believe storage is working (writes appear to succeed) when data is actually lost between AI regenerations.
- The shared-key feature is invisible until publication, creating a gap between what’s coded and what’s tested.
Consequences
@alistigo/claude-artifact-apinow ships runtime code (not just types). The bundle impact is negligible (~150 bytes).- The write guard in
claude-storage-pluginis a breaking change in behavior for callers that currently swallow all errors — they will now receive thrown errors on draft write attempts. This is intentional: silent failure was misleading. - Artifact UIs that use
ArtifactContextMenuContainergain astatusBadgeslot; backward compatibility is maintained (existing callers without the prop are unaffected). - Users opening a storage-aware artifact in draft mode get a clear message instead of confusing empty-state behavior.