[data-reveal]{opacity:1!important;transform:none!important}
Block preview page
AI & coding agents

Block preview page

The hidden /dms-preview page renders any tree of registered DMS blocks from a URL or a postMessage — what powers the live previews of these docs.

/dms-preview is a public, hidden page of the DMS that renders a tree of registered DMS blocks without the dashboard header or sidebar. These docs embed it in an iframe for their live previews, and you can open it to try blocks without writing a page. Set transparent: true when the embedding page should supply the background.

Enable it

The preview is enabled automatically in AntelopeJS development mode (GetRuntimeInfo().dev). Outside that mode, opt in with docsPreview: true or DMS_DOCS_PREVIEW=1:

antelope.config.ts
dms: {
  source: { type: "package", package: "@antelopejs/dms", version: "<latest>" },
  config: {
    docsPreview: true,
    // …
  },
},

The page then answers at <clientBaseUrl>/dms-preview — http://localhost:3001/dms-preview in development. The @antelopejs/dms playground enables it.

These conditions are alternatives: docsPreview: false does not disable the preview in framework development mode or when the environment opt-in is set. NODE_ENV does not determine the framework's development mode. The DMS evaluates the gate when constructing the module; restart the backend after changing it, then restart the frontend to pick up the new configuration.

The page is public: anyone who can reach the dashboard can open it. It only draws registered block types and only lets them read relative URLs of the same site, but a block that reads a route still calls it. Enable it on a demo instance, not on a production dashboard holding real data.

Render a tree from the URL

Pass the tree in ?state=, as base64url-encoded JSON:

interface PreviewState {
  tree: PreviewNode | PreviewNode[];
  theme?: "light" | "dark";
  locale?: string;   // a locale the dashboard knows, e.g. "fr-FR"
  padding?: boolean; // pads the blocks off the frame's edges; true by default
  transparent?: boolean; // paints no background, so the embedding page shows through
}

interface PreviewNode {
  type?: string;          // a block type, e.g. "KpiCard"
  componentName?: string; // or its component, e.g. "dms-kpi-card"
  options?: Record<string, unknown>;
  children?: Array<PreviewNode & { id?: string; slot?: string; colSpan?: number }>;
}
scripts/preview-url.ts
const state = {
  tree: { type: "KpiCard", options: { title: "Revenue", staticValue: 48200, staticDelta: 12.4, showDelta: true } },
  theme: "dark",
};
const encoded = Buffer.from(JSON.stringify(state)).toString("base64url");
console.log(`http://localhost:3001/dms-preview?state=${encoded}`);

The block catalog decides what may be drawn. A node names a registered type — which maps to its component — or a componentName that belongs to a registered type. Anything else, a block that needs a backend class (a TableView, a ResourceForm), and any URL option (fetchUrl, submitUrl…) that is not a relative path of the same site render an inline notice instead of the block, never an arbitrary component.

Drive it with postMessage

Embedded in an iframe, the page talks to its parent window. Every message carries channel: "dms-preview".

DirectionMessageMeaning
iframe → parent{ channel, type: "ready" }Mounted; send render now.
parent → iframe{ channel, type: "render", tree, theme?, locale?, transparent? }Draw this tree. transparent is kept from the previous state when absent.
parent → iframe{ channel, type: "theme", theme }Switch between light and dark.
iframe → parent{ channel, type: "resize", height }The content is now this tall, in CSS pixels.
iframe → parent{ channel, type: "error", message }Something could not be drawn.
docs/preview-embed.ts
const frame = document.querySelector<HTMLIFrameElement>("#preview")!;
const CHANNEL = "dms-preview";

window.addEventListener("message", (event) => {
  if (event.source !== frame.contentWindow || event.data?.channel !== CHANNEL) return;
  if (event.data.type === "ready") {
    frame.contentWindow!.postMessage(
      { channel: CHANNEL, type: "render", tree: { type: "Banner", options: { tone: "info", title: "Hello" } } },
      "*",
    );
  }
  if (event.data.type === "resize") frame.style.height = `${event.data.height}px`;
});

By default the page paints the dashboard's own background, which shows as a lighter or darker rectangle on a page of another colour. Set transparent: true (in ?state= or a render message) to paint none: the blocks then sit on the embedding page's background. A transparent preview declares the colour scheme of its theme, and a browser keeps an iframe opaque when that scheme differs from the embedding page's: send the theme matching the page's mode (dark on a page with color-scheme: dark).

In your own components

The page renders the tree with DmsBlockPreview, a global component of the dashboard. Hand it a tree to draw blocks inside any custom component — the dms-ai chat does exactly that:

app/components/BlockIdea.vue
<template>
  <DmsBlockPreview :tree="{ type: 'EmptyState', options: { title: 'No invoices yet' } }" padding />
</template>

Outside /dms-preview, the component fetches the list of drawable block types from GET /api/block-preview/types, which requires a signed-in member.

Next steps

Components

The blocks you can preview.

Configuration

Every key of the DMS module config.