How it works
This walkthrough follows an application window loading a local route. See Architecture for component responsibilities, Compiled templates for authoring and Runtime protocol for the internal message contract.
Compilation and first render
Before the application runs, a view's template declaration compiles its HTML/ECR file during Crystal compilation. Relative filenames resolve beside the declaring Crystal file. The generated definition preserves static HTML and supplies typed Crystal readers, callbacks and structural regions in the caller's scope. The browser later parses that HTML.
- Application code configures routes, assets and windows. Routes and assets belong to
Content. Application#runresolves local routes toapp://crykiton macOS, creates native windows, installs resource handlers and bridge/runtime scripts, starts expiry scheduling and enters the application's native runtime. Backends without native resource loading are rejected.- A bridge registers a navigation token. Local window URLs carry it so resource requests can be associated with their webview owner.
Content::Navigationcreates a document and invokes its route with a buffered response. The handler mounts a view throughViews::Registry.Views::Mountobtains the view's compiled definition and creates aTemplates::Session. Its retained instances allocate identities and evaluate the initial parts and region selections. Mount starts each participating view's lifecycle before registry publication, flushing initial hook changes.- The handler wraps the fragment in a
data-crykit-viewroot. A document layout, when supplied, renders once and must include that fragment exactly once. - The bridge registers the completed document on the owning UI context. The dispatcher records its mount set and returns a token inserted into the response's JSON metadata. Publication and lifecycle hooks run outside bridge and registry membership locks.
- The browser parses the document and registers each mount's explicit targets and region anchors. Target manifests must agree with the parsed DOM before registration succeeds. The browser sends
ready; the dispatcher validates ownership and the exact mount set, then creates an event session with revision zero for every mount.
sequenceDiagram
participant Web as Native webview
participant Route as Native resource / Content
participant Mount as Registry / Mount / Template session
participant UI as Bridge / Dispatcher
Web->>Route: GET app://crykit route with navigation token
Route->>Mount: Instantiate templates, start hooks, publish
Mount-->>Route: Mount ID and marked HTML
Route->>UI: Register completed document on UI context
UI-->>Route: Document token
Route-->>Web: HTML shell, fragment and metadata
Web->>Web: Parse HTML and validate managed targets
Web->>UI: ready(document, mounts)
UI-->>Web: session and initial revisions
The bridge bootstrap runs only on the configured application origin. Static pages without document metadata do not start a mounted event connection. Documents and assets use the native resource handler; events and mutations use native bindings. Both paths stay inside the application process.
Static HTML, SVG and custom elements use ordinary browser parsing. The runtime indexes managed element and text targets rather than assigning identities to every static node. It removes paired text markers after retaining their text nodes and keeps region boundaries for later structural edits. Per-instance manifests detect missing targets, conflicting kinds or targets moved outside their owning range by HTML parsing.
A button click
The browser finds the nearest eligible element with a registered click subscription and queues that element's own managed identity. It sends the session, sequence, mount ID, current revision, event name and target. The dispatcher authenticates the owner and checks ordering before calling the mount.
The retained template instance resolves the target's committed callback. It runs untracked in the owning view's update scope. Reactive state writes invalidate the consumers that previously read those fields. The template session then reevaluates dirty readers and selectors, stages their changes and publishes the resulting mutations. A counter can change one text part while unrelated readers and the document layout remain untouched. A handled callback with no dirty readers produces an empty mutation batch.
Text and attribute changes emit scalar mutations. Conditional, keyed-list and selected-child changes operate on anchored ranges. An unchanged conditional selection retains its branch, and keyed reordering moves existing row ranges. Replacing a row's model refreshes its lexical readers and callbacks; a compatible shape keeps its identities. New view owners start their lifecycles before publication, and removed owners are deactivated and cleaned up afterward. See Rendering for evaluation and publication details.
sequenceDiagram
participant Browser as Browser connection
participant Dispatch as Events::Dispatcher
participant Mount as Mount / Template session
participant DOM as DOM interpreter
Browser->>Dispatch: event(session, sequence, revision, target)
Dispatch->>Mount: Run callback and flush dirty computations
Mount-->>Dispatch: Scalar and range mutations
Dispatch-->>Browser: batch(baseRevision, revision, mutations)
Browser->>DOM: Apply mutations and DOM effects in order
Browser->>Dispatch: ack(sequence, view, revision)
Dispatch-->>Browser: acknowledged
Only one event/update request and acknowledgement cycle is active per document connection. A handled callback advances its mount revision even when its mutation list is empty. An ignored event consumes a sequence without advancing the revision or requiring an acknowledgement.
For inputs, the browser captures the string value at event time and targets the control itself. A binding acknowledges the canonical server value even when it matches the previous committed value, allowing normalization or rejection of an edit. Older replies preserve live values that already have a newer input event queued. Native input and textarea adapters also preserve selection when updating those values.
Range insertion parses HTML in its actual parent context and validates all new managed identities, event metadata and boundaries before attaching it. Removal retires the range's targets and callbacks. Moves retain the existing DOM nodes, including editable state, focus and selection. The browser applies a batch in order; a later application failure can leave an already applied prefix.
Scheduled UI updates
Reactive writes invalidate retained readers and schedule UI updates automatically. Repeated invalidations are coalesced, including changes made directly in native callbacks or mount hooks. DOM effect methods schedule their own delivery when called outside an active view event.
async submits work to the worker executor. job.notify submits a state-change block to the mount's UI queue and requests a wakeup. It does not wait for that block or a browser acknowledgement; the worker can continue computing. The job and its mounted view own that delivery; synchronous native subscriptions created inside it belong to the view.
The bridge tells the browser that an update is available. The browser queues an update request alongside events. On acceptance, the mount takes at most 128 queued entries, runs those whose scopes and optional jobs remain valid, and flushes dirty computations in one mutation batch. Remaining entries trigger another notification. Events can run between chunks, although the execution time of an individual UI block is not bounded by the queue.
Results produced before readiness remain pending for the handshake. Removing a view, cancelling a job or closing a document invalidates the corresponding queued deliveries. See Background jobs.
Navigation, reload and closure
Same-origin link navigation is queued after preceding view work and its acknowledgements. Window navigation hooks can cancel the destination. Approved local URLs carry the navigation owner token. Fragment-only links, downloads, modified clicks, external origins and links targeting another browsing context retain their ordinary browser behavior.
When the native webview reports document replacement, the bridge invalidates the previous generation and the dispatcher releases its active document event session. Registry release closes each template session, deactivates update scopes and runs unmount cleanup in reverse lifecycle order. The next document has its own mounts, token, event session and revisions. View-local state is recreated unless the application supplies state held outside the view.
Dev hotpatch can update ordinary text and existing safe presentation attribute values while the compiled shape, callbacks and bindings stay unchanged. Those literal readers use session-local reactive sources and publish ordinary scalar mutations. Dev hotreload compiles and restarts the app for other source edits. Asset hotreload replaces local CSS in the mounted document and updates tray icons referenced through asset_path. See Development updates.
Window closure also revokes its navigation registration and releases every document owned by that bridge. Application shutdown attempts each cleanup stage even when an earlier stage fails; see Application.
Failure boundaries
A template compilation error prevents the application from building. Initial reader or mount-hook failures prevent document publication and clean up entered lifecycles. A rejected update leaves committed parts, callbacks, region ownership and reactive subscriptions intact; state changes already performed by application code are not undone.
A callback or evaluation exception fails that mount and closes its update queue and retained session. Protocol ordering errors, transport errors, invalid managed DOM, mutation application failures and timeouts can terminate the document connection. The runtime reports whether reload is required; it does not silently retry events or reload the page. See Runtime protocol.