Themes and embeds
This guide matches the theme-command-center-app and integrate-static-site-iframe skills.
Themes and static-site embedding are separate concepts that meet when a host synchronizes visual context into an iframe. This page remains at its original URL as a compact compatibility overview. Use Themes for the complete token, density, data-visualization, persistence, and CSS contract. Use Static-site embeds for the complete trust boundary, lifecycle, delegated HTTP, failure model, and production test matrix.
Theme an application
Import the base styles once, resolve a stable preset ID, and apply it to the intended root:
import "@dev-mainsequence/command-center-sdk/theme/styles.css";
import {
applyThemePresetToRoot,
commandCenterThemes,
resolveCommandCenterThemeById,
} from "@dev-mainsequence/command-center-sdk/theme";
const theme =
resolveCommandCenterThemeById(savedThemeId) ?? commandCenterThemes[0];
applyThemePresetToRoot(document.documentElement, { theme });
Tailwind v4 applications load the mapping after Tailwind and before SDK utilities:
@import "tailwindcss";
@import "@dev-mainsequence/command-center-sdk/theme/tailwind.css";
@import "@dev-mainsequence/command-center-sdk/theme/styles.css";
@import "@dev-mainsequence/command-center-sdk/theme/utilities.css";
Optional skins are separate exports. Import only those used by the application:
@import "@dev-mainsequence/command-center-sdk/theme/markdown.css";
@import "@dev-mainsequence/command-center-sdk/theme/ag-grid.css";
Use exported CSS variables, density, surface hierarchy, and data-visualization helpers instead of copying preset values. Released theme IDs and token names are compatibility contracts; renaming an ID requires migration of persisted preferences.
Treat theme variables as a closed consumer contract
Importing the base theme stylesheet means the application delegates semantic visual styling to
the SDK. Use the variables declared by the installed stylesheet for colors, surfaces, typography,
line height, radii, shadows, focus, statuses, density, and charts. Core tokens are unprefixed
semantic names such as --background, --foreground, --card, --border, and --primary.
Do not invent SDK-looking variables such as --ms-color-background, and do not write fallbacks
such as var(--background, #ffffff). Fallbacks hide contract mistakes and create partially themed
interfaces. Consumer aliases are acceptable only when derived entirely from published variables:
.application-panel {
background: var(--card);
color: var(--card-foreground);
border: 1px solid var(--border);
border-radius: var(--radius);
font-family: var(--font-sans);
font-size: var(--font-size-body);
line-height: var(--line-height-body);
}
Repeated complete-application chrome—page gutters, page headers, top-level section rhythm,
ordinary card padding, and responsive sibling-card grids—is owned by the public /layout
primitives. Specialized grid placement, split panes, editors, canvases, positioning, and intrinsic
domain geometry remain application-owned. Audit authored CSS in local checks and CI:
npx command-center-sdk theme audit --path src
The audit fails on unknown variables, theme-variable fallbacks, literal colors, and hardcoded semantic typography, radii, shadows, or other theme-owned values.
The theme audit does not prove layout conformance. Use
@dev-mainsequence/command-center-sdk/layout/testing in a real browser to verify computed sibling
gaps, standard card insets, responsive grid collapse, header-action wrapping, overflow, and
interactive geometry. See Application layout.
Embed an application-owned static site
The host supplies an authorized launch URL plus current theme/public-user context:
import { StaticSiteIframe } from "@dev-mainsequence/command-center-sdk/embed/react";
<StaticSiteIframe
src={authorizedLaunchUrl}
themeId={activeTheme.id}
themeMode={activeTheme.mode}
userUid={session?.user.publicUid ?? null}
resolveFastApiCredential={resolveFastApiCredential}
resolveFastApiWebSocketTicket={resolveFastApiWebSocketTicket}
className="h-full w-full"
/>;
resolveFastApiCredential is a host-application adapter. It closes over the trusted source release
UID and exact iframe origin, uses the host's authenticated API client, validates the backend-issued
source/target/origin/RPC URL/expiry binding, and returns only the SDK's narrow delegated credential
shape. The iframe supplies only the target ResourceRelease UID. Neither the host session nor raw
backend errors cross the iframe boundary.
The child installs its listener before announcing readiness and applies every later context update:
import {
createStaticSiteIframeClient,
type StaticSiteIframeContext,
} from "@dev-mainsequence/command-center-sdk/embed";
import {
applyThemePresetToRoot,
resolveCommandCenterThemeById,
} from "@dev-mainsequence/command-center-sdk/theme";
let markContextReady!: () => void;
const contextReady = new Promise<void>((resolve) => { markContextReady = resolve; });
const client = createStaticSiteIframeClient({
channel: "mainsequence.reports",
hostOrigin: "https://command-center.example.com",
parentWindow: window.parent,
onContext(context: StaticSiteIframeContext) {
const preset = resolveCommandCenterThemeById(context.themeId);
if (preset) applyThemePresetToRoot(document.documentElement, { theme: preset });
document.documentElement.classList.toggle("dark", context.themeMode === "dark");
renderForUser(context.userUid);
markContextReady();
},
onFastApiStateChange(state) {
renderFastApiStatus(state);
},
});
const onMessage = (event: MessageEvent<unknown>) => client.handleMessage(event);
window.addEventListener("message", onMessage);
client.announceReady();
// Issue delegated requests only after the trusted host initializes the child.
await contextReady;
const response = await client.fetchFastApi(
{
resourceReleaseUid: configuredFastApiReleaseUid,
path: "/api/report",
},
{ method: "GET" },
);
const socket = await client.createFastApiWebSocket({
resourceReleaseUid: configuredFastApiReleaseUid,
path: "/ws/report-events",
protocols: ["report-events.v1"],
});
// On permanent disposal:
window.removeEventListener("message", onMessage);
client.dispose();
For a top-level local Vite page, use the same-origin /api proxy workflow
instead of fetchFastApi; this hosted example requires an initialized trusted parent and an
authorized release UID. The static-site UID is untrusted display or routing context, not authentication. Never send session
tokens, email, name, organization, permissions, or credentials through this context. For an
authorized FastAPI release, fetchFastApi is the normal child API: it accepts only a relative path,
uses the backend-issued RPC URL, injects the delegated bearer token and canonical release header,
reuses the credential only in memory, and refreshes before expiry. Do not manually parse
postMessage, request credentials from the platform yourself, reconstruct hostnames, or store/log the token.
The client reports authorizing, runtime-starting, ready, expired,
authentication-failed, forbidden, missing-route, transient, cancelled, unavailable,
unsupported, and invalid through onFastApiStateChange and getFastApiState. It owns an
abortable maximum-three-attempt policy for replay-safe requests. Only 502, 503, and 504 mean
runtime start; 401 causes one bounded credential reacquisition, 403 is forbidden, 404 is a
missing route, and an opaque browser fetch failure stays transient/CORS-or-network rather than
being mislabeled as a starting runtime. POST and PATCH are not replayed by default.
The low-level requestFastApiCredential method is reserved for advanced transports that cannot use
fetchFastApi; it exposes the narrow token and makes the caller responsible for containing it in
memory. A direct-link static site has no trusted parent bridge and receives unsupported, with no
fallback to a normal user credential. Target CORS remains required but is not authentication, and
the FastAPI application still owns route/object authorization.
For WebSockets, resolveFastApiWebSocketTicket is a separate authenticated host adapter. It
derives the pinned child Origin, calls the one-time ticket endpoint once, validates the exact
release/path/origin/URL/expiry binding, and returns the SDK ticket shape. The child uses only
createFastApiWebSocket; it never receives a raw-ticket API or reuses the HTTP credential. The SDK
places the ticket first and mainsequence.ws-bridge.v1 second in the native protocol list. FastAPI
never receives either value, so socket.protocol is the FastAPI-selected application protocol or
the fixed non-secret acknowledgement. Reconnect by calling the method again for a fresh ticket.
For the platform itself, sendPlatformRequest is a third host adapter. It serves only the paths
the host chooses, refuses the rest with not_allowed, and sends each request with the host's own
authenticated fetch as the signed-in person. The child calls client.sendPlatformRequest(request)
with a standard Fetch Request and receives a standard Response; it never holds a platform
credential. See Static-site embeds.
StaticSiteIframe defaults to allow-forms allow-same-origin allow-scripts. Any added popups,
downloads, modals, or navigation require a security review. Production deployments must align the
host's frame-src, the child's frame-ancestors, and an operator-controlled exact-origin allowlist.
What to test
- Theme switching, fallback preset resolution, plain CSS, dark/light contrast, and every changed optional skin. Compare rendered component computed styles with root variables; a received theme ID alone is not proof that the application is themed.
- Wrong iframe origin/source, malformed or replayed messages, payload limits, timeout, navigation, repeated initialization, and teardown.
- Anonymous/public UID behavior and real-browser CSP/sandbox behavior for static sites.
- Delegated FastAPI source/origin/target validation, single-flight reuse, refresh before expiry, sanitized errors, bounded retry while the runtime starts, cancellation, exact HTTP-state classification, user/navigation/disposal clearing, direct-link failure, and absence of tokens from DOM, URLs, storage, logs, analytics, or serialized state. Exercise this path in a real browser so CORS preflight, origin binding, and the canonical release header are covered.
- Native WebSocket ticket cancellation, exact binding, platform-protocol ordering, application
negotiation, acknowledgement fallback, omitted/duplicate selection failure, bidirectional
frames, close behavior, fresh-ticket reconnect,
connect-src, and absence of tickets from URLs, storage, DOM, logs, analytics, and errors. - Platform requests: the host's allow-list, JSON and binary responses, cancellation, a user
change, an older host's
unsupported, and no platform credential reaching the child.