Today Canvas Kit
SDK packages

@todayai-labs/tck-host

Browser-side host runtime. Manifest registry, widget loader, layout grid, patch dispatcher, agent channel, two-tier mount surface.

The browser-side host runtime. A host app (today-platform-web, the workbench, the native WebView shell) wires up a single TodayHost and consumes it through React. tck-host owns the doc store, the patch dispatcher, the manifest registry, the widget loader, the layout canvas, the bus the agent talks on, and the <WidgetMount> mount primitive.

Widgets do not import this package — it's host-only, and the bundler enforces that absence at build time.

The package is split into multiple subpaths to keep the import graph minimal per consumer-class — a Next.js server module evaluating getHostHeadNodes must not transitively reach createContext, and an import-map-served mountHost must not drag in the full React component graph. The split is structurally enforced by __tests__/export-safety.test.ts; see the design memo for the rationale.

Subpaths

@todayai-labs/tck-host (root)

The core runtime. Safe to import anywhere a client React runtime exists.

ExportWhat it does
TodayHostThe host root. Owns registry + loader + dispatcher + docStore + canvas state. See § TodayHost.
TodayHostOptionsConstructor options: storage, loaderOptions, onSizeMismatch.
WidgetInstanceImmutable per-mount metadata returned by host.mountWidget / host.restoreWidget.
SizeMismatchPolicy'warn' | 'throw' — see container contract.
TodayWidgetCanvasReact component: the grid surface. Drag-to-move, tile chrome.
TodayWidgetCanvasPropsProps + types for canvas tile rendering (WidgetTileRenderArgs, CanvasTileDragBindings).
TodayFeedStackReact component: the feed surface (linear column of full-width cards).
TodayFeedStackPropsProps + types for feed card rendering (FeedCardRenderArgs).
WidgetLoaderGiven a bundle URL → WidgetModule. Verifies named exports, ABI label, manifest schema.
WidgetLoaderOptions, WidgetModule, ModuleMap, DEFAULT_HOST_ABILoader plumbing.
WidgetLoadErrorLoader-side rejection (malformed bundle / missing exports / failed manifest validation).
WidgetAbiErrormanifest.engines.abi does not match the host's ABI label.
WidgetSizeErrorMount offered a size not in manifest.sizes. See container contract.
WidgetMountLow-level shadow-DOM mount primitive (also re-exported by /bootstrap).
WidgetMountProps, WidgetMountFallbackControls<WidgetMount> types.
PatchDispatcherSingle sequencer per doc. Applies RFC-6902 patches; rejects stale baseVersion; idempotent on patchId.
PatchDispatcherOptionsDispatcher tuning.
ManifestRegistryIn-memory {manifestId → RegisteredWidget} index.
RegisteredWidgetRegistry entry shape.
DocStore, ephemeralStoragePer-instance JSON-doc snapshots; pluggable storage backends.
DocSnapshot, DocStorageStorage backend contract.
migrateDoc, MigrationErrorForward-only state migrations on schemaVersion mismatch.
WidgetErrorBoundaryPer-widget React error boundary. Chrome-level "Reload widget" + "Reset to defaults".
AgentChannelImpl, makeWidgetAgentChannelHost-side agent bus implementation.
Layout helpersDEFAULT_GRID, canPlaceAt, canvasWidth, effectiveFootprint, findFirstFreeSlot, gridCssVars, pointerToGridPosition, widgetPixelSize, TodayGridConfig.

@todayai-labs/tck-host/bootstrap

Tier-1 mount components — closed-contract single-widget surfaces. Client React only.

ExportWhat it does
TodayHostBoundaryReact provider exposing the host + docStore to descendant <TodayWidget> mounts.
TodayHostBoundaryPropsProps.
TodayWidgetClosed-contract widget mount; accepts {src, instanceId} OR {devModule, instanceId}. No loadModule escape hatch — every module flows through WidgetLoader.parseModule. See Tier 1 mount surface.
TodayWidgetPropsDiscriminated-union prop shape.
TodayWidgetMountedInfoonMounted callback payload.
WidgetMountLow-level shadow-DOM primitive (re-exported from root).

@todayai-labs/tck-host/server

SSR-head slice. Safe to import in server modules — does not call createContext or touch the DOM.

ExportWhat it does
getHostHeadNodesReturns <link> / <script type="importmap"> / <html data-theme> React elements for the host's <head>.

@todayai-labs/tck-host/mount

Client-imperative slice — mountHost only. Safe to import anywhere; no React, no createContext.

ExportWhat it does
mountHost(document, options)Installs document-level host concerns: CSS <link>, importmap, data-theme attribute. Returns a MountHostControl.
MountHostControl{ setTheme, dispose } — runtime theme flips + cleanup.
MountHostOptions{ theme: 'light' | 'dark' | 'system', hostCssUrl? }.
HostBootstrapErrorThrown when mountHost cannot verify the import-map's presence on the document.

@todayai-labs/tck-host/vite

Vite plugin: build-time importmap injection. For no-SSR Vite hosts (workbench, tck-preview) — the dev-server equivalent of getHostHeadNodes.

ExportWhat it does
hostImportMapPlugin()Vite plugin. Reads tck-shared-deps's manifest.json at transformIndexHtml, injects <script type="importmap"> before any module script.

@todayai-labs/tck-host/messages

Cross-window message types for <TodayWidget>'s iframe-mode bridge. Internal-protocol; typed shapes only.

@todayai-labs/tck-host/style.css

Default host stylesheet (preflight + theme tokens + the [data-theme='dark'] variant binding). Import once at the host app's root, or copy alongside tck-shared-deps's output for serving over importmap-resolved URLs.

import '@todayai-labs/tck-host/style.css'

TodayHost

The canvas-tier API. Use this when building a Tier-2 host (workbench, drag-canvas, persisted layout); for embedding surfaces, prefer <TodayWidget> from /bootstrap.

import { TodayHost, ephemeralStorage } from '@todayai-labs/tck-host'

const host = new TodayHost({
  storage: ephemeralStorage, // or an idb-keyval-backed DocStorage
  loaderOptions: { loadModule: (url) => import(url) },
  onSizeMismatch: 'warn', // or 'throw' for debug surfaces
})

host.registerWidget({
  manifest: parsedManifest,
  bundleUrl: 'https://.../widget.mjs',
  cssUrl: 'https://.../widget.css',
})

const instance = await host.mountWidget({
  manifestId: 'com.example.counter',
  position: { col: 0, row: 0 },
  size: '2x1', // optional; defaults to manifest.defaultSize
  seedState: { count: 0 }, // optional; defaults to manifest.defaultState
})

Methods:

  • registerWidget / mountWidget / restoreWidget / resetWidget / removeWidget
  • moveWidget(instanceId, position) / resizeWidget(instanceId, size)
  • setTransient(instanceId, transient) — flip the suggestion / pinned bit
  • setAmbientWidgetCss(css) — for unbundled (workspace dev) widgets
  • list() / get(instanceId) / subscribeLayout(fn)
  • submitPatch({instanceId, baseVersion, patch}) — build envelope + dispatch + handle stale rebase
  • publishAgent(message) — broadcast onto the agent bus

Source

On this page