On this pageFunctions
Runtime
/**
* Starts a Foldkit runtime under a host-controlled lifecycle and returns an
* `EmbedHandle`. This is the entry point for embedding a Foldkit app inside
* another application: the host pushes values in through the handle's inbound
* Ports, listens to outbound Ports, and calls `dispose` when it unmounts the
* app. The host never touches the Model or dispatches Messages directly; the
* Schema-typed Ports are the whole boundary.
*
* Works with programs from both `makeApplication` and `makeElement`; for a
* widget on a page the host owns, `makeElement` is the natural fit.
*
* A program can be embedded once at a time (it owns one container). After
* `dispose`, the same container can be embedded again with a fresh program.
*
* ```ts
* const handle = Runtime.embed(element)
*
* handle.ports.stepChanged.send(5)
* const unsubscribe = handle.ports.countChanged.subscribe(count => {
* console.log(count)
* })
*
* handle.dispose()
* ```
*/
<P extends Readonly<{
inbound: Readonly<Record<string, Inbound<any, any>>>
outbound: Readonly<Record<string, Outbound<any, any>>>
}> | undefined = undefined>(program: MakeRuntimeReturn<P>): EmbedHandle<P>/**
* Creates a Foldkit app scoped to its container and returns a runtime that
* can be passed to `run`.
*
* Unlike `makeApplication`, the `view` returns `Html` directly rather than a
* `Document`, and the runtime never touches the document `<head>`. This lets a
* Foldkit app be embedded at a node (a widget on a page it does not own)
* without clobbering the host page's `title`, `canonical`, or `og:url`. Use
* `makeApplication` when the app owns the page and should manage those tags, and
* `makeElement` when it is one component among others on a page it does not
* control. Embedded apps do not own the URL bar, so `makeElement` has no
* `routing` config.
*/
<Model, Message extends {
_tag: string
}, Flags, Resources = never, ManagedResourceServices = never, P extends Readonly<{
inbound: Readonly<Record<string, Inbound<any, any>>>
outbound: Readonly<Record<string, Outbound<any, any>>>
}> | undefined = undefined>(config: ElementConfigWithFlags<Model, Message, Flags, Resources, ManagedResourceServices, P>): MakeRuntimeReturn<P>
<Model, Message extends {
_tag: string
}, Resources = never, ManagedResourceServices = never, P extends Readonly<{
inbound: Readonly<Record<string, Inbound<any, any>>>
outbound: Readonly<Record<string, Outbound<any, any>>>
}> | undefined = undefined>(config: ElementConfig<Model, Message, Resources, ManagedResourceServices, P>): MakeRuntimeReturn<P>/**
* Starts a Foldkit runtime that owns the page for the page's whole lifetime,
* with HMR support for development. To start a runtime under a
* host-controlled lifecycle instead, use `embed`.
*/
(program: MakeRuntimeReturn<Ports | undefined>): void/** Configuration for `makeApplication` without flags or URL routing. */
type ApplicationConfig = BaseApplicationConfig<Model, Message, Resources, ManagedResourceServices, P> & Readonly<{
init: () => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]
}>/** Configuration for `makeApplication` with flags but no URL routing. */
type ApplicationConfigWithFlags = BaseApplicationConfig<Model, Message, Resources, ManagedResourceServices, P> & Readonly<{
flags: Effect.Effect<Flags>
Flags: Schema.Codec<Flags, any, unknown, unknown>
init: (flags: Flags) => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]
}>/** The `init` function type for a `makeApplication` app without URL routing. */
type ApplicationInit = Flags extends void
? () => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]
: (flags: Flags) => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]/** Configuration for crash handling, with custom crash UI and/or crash reporting. */
type CrashConfig = Readonly<{
report: (context: CrashContext<Model, Message>) => void
view: (context: CrashContext<Model, Message>) => Document
}>/**
* Context provided to crash.view and crash.report when the runtime encounters
* an unrecoverable error. `message` is the Message being processed when the
* crash occurred, present as an `Option` because a crash during the initial
* render has no triggering Message.
*/
type CrashContext = Readonly<{
error: Error
message: Option.Option<Message>
model: Model
}>/**
* DevTools configuration.
*
* Pass `false` to disable DevTools entirely.
*
* - `show`: `'Development'` (default) enables in dev mode only, `'Always'` enables in all environments including production.
* - `position`: Where the badge and panel appear. Defaults to `'BottomRight'`.
* - `mode`: `'TimeTravel'` (default) enables full time-travel debugging. `'Inspect'` allows browsing state snapshots without pausing the app. Pass `{ development, production }` to use different modes per environment. Useful when DevTools is shown in production (`show: 'Always'`) and you want `'TimeTravel'` only in local development.
* - `banner`: Optional text shown as a banner at the top of the panel.
* - `overlay`: The in-browser overlay factory from `@foldkit/devtools`. Without it, DevTools still records history and serves the WebSocket bridge (so the DevTools MCP server works), but no visual overlay is mounted. Pass `DevTools.overlay` to show the panel.
* - `excludeFromHistory`: Message `_tag` values whose dispatches should not be recorded in DevTools history. The Messages still drive `update` and the runtime as usual; they just don't appear in the history panel and don't pay the per-Message diff cost. Use for high-frequency Messages (animation frames, pointer moves, scroll events) that would flood history without adding insight.
* - `maxEntries`: Maximum number of recorded Messages retained in history before the oldest is evicted. Defaults to 100. Clamped to the range 20-500: smaller values keep the panel snappy under high message rates, larger values give you more scroll-back. Each retained entry stores a full Model snapshot, so memory cost scales linearly with both `maxEntries` and your Model size.
* - `keyframeInterval`: Number of recorded Messages between full Model snapshots. Defaults to 31. Time-travel to an index replays `update` forward from the nearest earlier keyframe, so this is a memory/time tradeoff: smaller values store more snapshots (more memory) but make each jump cheaper, down to `1` where every jump is a constant-time snapshot lookup with no replay. Reach for a denser interval when the app has a heavy `update` and time-travel jumps feel sluggish. Clamped to a minimum of 1. Forced to 1 automatically when `excludeFromHistory` is active, since excluded Messages are never replayed.
*/
type DevToolsConfig = false | Readonly<{
banner: string
excludeFromHistory: ReadonlyArray<string>
keyframeInterval: number
maxEntries: number
Message: Schema.Codec<any, any, unknown, unknown>
mode: DevToolsModeConfig
overlay: DevToolsOverlay
position: DevToolsPosition
show: Visibility
}>/**
* Controls DevTools interaction mode.
*
* - `'Inspect'`: Messages stream in and clicking a row shows its state snapshot without pausing the app.
* - `'TimeTravel'`: Clicking a row pauses the app at that historical state. Resume to continue.
*/
type DevToolsMode = "Inspect" | "TimeTravel"/**
* Mode value for the DevTools panel. Either a single mode used in every
* environment, or an object selecting different modes for development and
* production. Use the object form to keep `'TimeTravel'` for local debugging
* while shipping the safer `'Inspect'` mode to users. `'TimeTravel'` in
* production pauses the user's app when a history row is clicked.
*/
type DevToolsModeConfig = DevToolsMode | Readonly<{
development: DevToolsMode
production: DevToolsMode
}>/**
* Factory that mounts the in-browser DevTools overlay against a recording
* store. The runtime keeps the store and the WebSocket bridge (so external
* tooling like the DevTools MCP server works without an overlay); the visual
* overlay is injected so it can live in `@foldkit/devtools` and pull in
* `@foldkit/ui` without coupling the core runtime to either.
*
* Pass `overlay` from `@foldkit/devtools` as `DevToolsConfig.overlay`.
*/
type DevToolsOverlay = (store: DevToolsStore, position: DevToolsPosition, mode: DevToolsMode, maybeBanner: Option.Option<string>) => Effect.Effect<void, never, Scope.Scope>/** Position of the DevTools badge and panel on screen. */
type DevToolsPosition = "BottomRight" | "BottomLeft" | "TopRight" | "TopLeft"/** Configuration for `makeElement` without flags. */
type ElementConfig = BaseElementConfig<Model, Message, Resources, ManagedResourceServices, P> & Readonly<{
init: () => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]
}>/** Configuration for `makeElement` with flags. */
type ElementConfigWithFlags = BaseElementConfig<Model, Message, Resources, ManagedResourceServices, P> & Readonly<{
flags: Effect.Effect<Flags>
Flags: Schema.Codec<Flags, any, unknown, unknown>
init: (flags: Flags) => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]
}>/**
* Configuration for crash handling in a `makeElement` app. The crash view
* returns `Html`, not a `Document`, because a scoped app never owns the
* document `<head>`.
*/
type ElementCrashConfig = Readonly<{
report: (context: CrashContext<Model, Message>) => void
view: (context: CrashContext<Model, Message>) => Html
}>/**
* The `init` function type for a `makeElement` app. A scoped app never owns
* the URL, so its `init` has the same shape as a non-routing
* `ApplicationInit`: argless, or receiving flags when `Flags` is set.
*/
type ElementInit = ApplicationInit<Model, Message, Flags, Resources, ManagedResourceServices>/**
* The handle returned by `embed`. The host talks to the embedded app only
* through it: `ports.<name>.send` pushes values in, `ports.<name>.subscribe`
* listens to values the app emits, and `dispose` shuts the runtime down.
*
* `dispose` is idempotent. It interrupts the runtime and runs all cleanup:
* Subscriptions, ManagedResources, Mounts, listeners, and in-flight Commands
* stop, and the rendered DOM is removed with the container element restored
* empty in its place, ready for a fresh `embed`.
*/
type EmbedHandle = Readonly<{
dispose: () => void
ports: PortHandles<P>
}>/**
* Host-side handle for one inbound Port. `send` validates the value by
* decoding it against the Port's Schema: on success the decoded value enters
* the app through the Port's Subscription; on failure nothing reaches the
* app, the failure is logged, and the returned `Exit` carries the
* `SchemaError`. Sends after `dispose` are no-ops.
*/
type InboundPortHandle = Readonly<{
send: (value: Encoded) => Exit.Exit<void, Schema.SchemaError>
}>/**
* The inbound half of `PortHandles`: one `InboundPortHandle` per declared
* inbound Port, keyed by Port name.
*/
type InboundPortHandles = InboundPorts extends Readonly<Record<string, Inbound<any, any>>>
? {
readonly [Name in keyof InboundPorts]: InboundPorts[Name] extends Inbound<any, infer Encoded>
? InboundPortHandle<Encoded>
: never
}
: unknown/**
* A configured Foldkit runtime returned by `makeApplication` or `makeElement`.
* Pass it to `run` to start a page-owning app, or to `embed` to start it under
* a host-controlled lifecycle handle. `ports` is the Ports record from the
* config (or `undefined` when the config declared none); it types the
* `EmbedHandle` that `embed` returns.
*/
type MakeRuntimeReturn = Readonly<{
ports: P
runtimeId: string
start: (hmrModel?: unknown) => Effect.Effect<void>
}>/**
* Host-side handle for one outbound Port. `subscribe` registers a listener
* for the encoded values the app emits with `Port.emit` and returns an
* unsubscribe function. Multiple listeners receive each value in
* registration order.
*/
type OutboundPortHandle = Readonly<{
subscribe: (listener: (value: Encoded) => void) => () => void
}>/**
* The outbound half of `PortHandles`: one `OutboundPortHandle` per declared
* outbound Port, keyed by Port name.
*/
type OutboundPortHandles = OutboundPorts extends Readonly<Record<string, Outbound<any, any>>>
? {
readonly [Name in keyof OutboundPorts]: OutboundPorts[Name] extends Outbound<any, infer Encoded>
? OutboundPortHandle<Encoded>
: never
}
: unknown/**
* The `ports` field of an `EmbedHandle`: one `InboundPortHandle` or
* `OutboundPortHandle` per declared Port, keyed by Port name.
*/
type PortHandles = P extends Ports
? InboundPortHandles<P["inbound"]> & OutboundPortHandles<P["outbound"]>
: unknown/** Configuration for `makeApplication` with URL routing but no flags. */
type RoutingApplicationConfig = BaseApplicationConfig<Model, Message, Resources, ManagedResourceServices, P> & Readonly<{
init: (url: Url) => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]
routing: RoutingConfig<Message>
}>/** Configuration for `makeApplication` with flags and URL routing. */
type RoutingApplicationConfigWithFlags = BaseApplicationConfig<Model, Message, Resources, ManagedResourceServices, P> & Readonly<{
flags: Effect.Effect<Flags>
Flags: Schema.Codec<Flags, any, unknown, unknown>
init: (flags: Flags, url: Url) => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]
routing: RoutingConfig<Message>
}>/** The `init` function type for a `makeApplication` app with URL routing, receives the current URL and optional flags. */
type RoutingApplicationInit = Flags extends void
? (url: Url) => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]
: (flags: Flags, url: Url) => readonly [Model, ReadonlyArray<Command<Message, never, Resources | ManagedResourceServices>>]/** Configuration for URL routing with handlers for URL requests and URL changes. */
type RoutingConfig = Readonly<{
onUrlChange: (url: Url) => Message
onUrlRequest: (request: UrlRequest) => Message
}>/**
* Slow-phase warning configuration.
*
* By default, all phases are enabled in development with Foldkit's default
* thresholds. Pass `false` to disable warnings entirely. Pass an object to
* refine those defaults.
*
* - `show`: `'Development'` (default) enables warnings only when Vite HMR is active. `'Always'` enables them in every environment.
* - `measuredPhases`: Phases to measure. Defaults to every slow warning phase.
* - `thresholdOverrides`: Per-phase budget overrides. Omitted fields keep defaults; overrides for unmeasured phases are ignored.
* - `onSlow`: Callback for every measured phase that exceeds its budget. Replaces Foldkit's default `console.warn`; Foldkit will not also warn for tags your callback ignores.
*/
type SlowConfig = false | Readonly<{
measuredPhases: ReadonlyArray<SlowPhase>
onSlow: (context: SlowContext<Model, Message>) => void
show: Visibility
thresholdOverrides: SlowThresholdOverrides
}>/** Tagged union of every slow-phase context passed to `slow.onSlow`. */
type SlowContext = SlowViewContext<Model, Message> | SlowUpdateContext<Model, Message> | SlowPatchContext<Model, Message> | SlowSubscriptionDependenciesContext<Model>/** Context provided when DOM patching exceeds its configured time budget. */
type SlowPatchContext = Readonly<{
_tag: "Patch"
durationMs: number
message: Option.Option<Message>
model: Model
thresholdMs: number
}>/** Context provided when subscription dependency extraction exceeds its configured time budget. */
type SlowSubscriptionDependenciesContext = Readonly<{
_tag: "SubscriptionDependencies"
durationMs: number
model: Model
subscriptionKey: string
thresholdMs: number
}>/** Budget overrides for slow warning phases. Omitted fields use Foldkit defaults. */
type SlowThresholdOverrides = Readonly<{
Patch: number
SubscriptionDependencies: number
Update: number
View: number
}>/** Context provided when update exceeds its configured time budget. */
type SlowUpdateContext = Readonly<{
_tag: "Update"
durationMs: number
message: Message
nextModel: Model
previousModel: Model
thresholdMs: number
}>/** Context provided when view construction exceeds its configured time budget. */
type SlowViewContext = Readonly<{
_tag: "View"
durationMs: number
message: Option.Option<Message>
model: Model
thresholdMs: number
}>