Architecture
The reactive kernel imports nothing. DOM depends only on the kernel. Routing depends on the kernel and DOM types. AI uses the kernel for action UI state and DOM types for described views. Forms depend on the kernel and schema contracts. Hea
Module boundaries
core.ts platform-neutral reactive graph and ownership
dom.ts direct DOM mounting, bindings, components, context, control flow
tsx.mjs compile-time JSX parsing and reactive-expression lowering
router.ts URL matching, navigation state, loaders, guards
ai.ts schemas, actions, discovery, semantic UI contract
forms.ts schema-aware form state, validation, submission, manifests
ui.ts disclosure, dialog, tabs, pagination, browser directives
server.ts Fetch request router and middleware
auth.ts password/session service, cookies, CSRF, roles, auth UI/client
backend.ts inferred schema/functions, SQLite documents, RPC, live queries
deploy.ts deterministic artifact config, packaging, verification, extraction
migrations.ts immutable SQL ledger, backup, restore, transactional application
platform.ts device auth, projects, secrets, audit, releases, supervision
security.ts shared URL, origin, validation-redaction, and bounded JSON helpers
ssr.ts escaped HTML rendering, document templates, serialized state
node.ts dependency-free Node HTTP and static-file adapters
index.ts public aggregate exportsThe reactive kernel imports nothing. DOM depends only on the kernel. Routing depends on the kernel and DOM types. AI uses the kernel for action UI state and DOM types for described views. Forms depend on the kernel and schema contracts. Headless UI depends only on the kernel and browser standards. The server uses the routing matcher. There are no package imports.
Reactive graph
A source owns a set of observers. During an observer execution, signal and computed reads register both sides of the dependency. Before reevaluation, prior dependencies are detached. Computed observers invalidate synchronously and propagate dirtiness; effects enter a deduplicated queue. The outermost batch drains that queue.
Computed.peek() prevents the caller from subscribing while still evaluating the computed under its own observer. This distinction keeps imperative snapshots fresh without accidentally wiring the surrounding effect to the computed.
Ownership is a separate tree. Roots collect cleanup callbacks. Components create child roots, so unmounting a subtree disposes its event handlers, directives, effects, computed values, resources, and nested mounts together.
DOM strategy
Static element structure is created once. The TSX compiler wraps each dynamic child and property in a ReactiveExpression; the renderer turns each marker into one narrow effect. Primitive child changes mutate the existing Text.data and retain the exact node. Conditional regions get start/end markers and replace only their controlled nodes.
For builds a key-to-row map. An array update is reconciled in O(n): removed keys are disposed, new keys mount once, retained object keys update lazy per-property row signals, and only reordered row ranges receive DOM move calls. With by="id", immutable object replacement updates changed row fields without replacing the row or its text nodes.
This avoids virtual-tree allocation and whole-tree diffing for ordinary state changes. See Performance model for the exact update guarantees.
Async consistency
Resources and router loaders use two defenses:
- Abort the prior operation with
AbortController. - Associate every run with a monotonically increasing revision and ignore any result that is no longer current.
This prevents stale state even when a promise cannot be physically canceled.
Agent protocol
The action manifest and semantic DOM tree are complementary:
- The manifest describes callable application capabilities and their policies.
- The semantic tree describes the currently mounted interactive surface.
An agent may choose a named action directly or operate an explicit semantic control. Both paths still pass through application validation and, when correctly configured, authorization.
Build strategy
Clank's compiler parses TSX directly, lowers elements to jsx() calls, and lowers dynamic expression sites to lazy expression() markers. Node's stripTypeScriptTypes(..., { mode: "transform" }) then removes TypeScript syntax. The build changes local .ts/.tsx import suffixes to .js and emits source maps. No bundling means browser module boundaries remain visible and debuggable.
The checked-in declaration files are the stable consumer contract. Type tests instantiate real schemas and backend functions to ensure errors appear at call sites without generated types. The repository's strict tsconfig.json validates source and the examples when a TypeScript compiler is available, while the normal build itself remains package-free.
Full-stack data flow
The schema is both a runtime validator and the root TypeScript value. Table documents, branded IDs, mutation arguments, handler results, zero-codegen API references, server calls, and browser calls are conditional types derived from that value.
SQLite stores canonical validated document JSON beside framework metadata. Owned tables store an immutable owner column outside application JSON. A mutation runs synchronously inside BEGIN IMMEDIATE; document writes, output validation, the persisted global revision, and journal records commit together. Queries use consistent read transactions and record table, document, and owner dependencies. Same-host processes catch up through the persisted journal and publish one current snapshot. A retained-history gap causes conservative full invalidation.
Auth uses internal SQLite tables through a private database capability. Password work runs outside transactions; user/session writes then enter the same revision and notification system. The backend selects the auth cache partition and database owner scope and refreshes long-lived callers before operations. Auth journal changes close affected live streams across processes, so role downgrades and revocations reconnect with current authorization.
SSR executes the same component tree without a DOM. Dynamic regions and keyed lists receive comment boundaries. Hydration walks those boundaries and installs the fine-grained effects on the existing nodes. A structural mismatch is surfaced as a warning and safely remounted instead of leaving partially attached behavior.
Deployment data flow
The deployment config is normalized before its build runs. The CLI executes the build command as an argument array, walks only explicit include roots, rejects links and sensitive paths, and creates a deterministic gzip document with individual file hashes. It vendors the current Clank package so execution does not depend on mutable global installation state.
The control plane hashes device and access credentials, intersects organization role with project-token scope on every request, stores encrypted secrets and audit metadata in its own SQLite database, and allocates a persistent project data directory. Release directories are immutable after extraction except for a platform-generated launcher.
A deployment takes the project lock, verifies and extracts a staged artifact, quiesces the previous process, snapshots SQLite, applies immutable migrations, starts a candidate, and waits for health. Activation is the final state transition. Any earlier failure restores the snapshot and previous process.
Project mutations also acquire durable distributed leases. Authenticated deployment nodes, desired generations, idempotent durable operations, lease expiry, retries, draining, capacity placement, and monotonic fences reject stale workers. The included child-process supervisor still owns processes in memory and therefore runs as one active leader per project/data directory; remote agents can implement the durable orchestration contract. Child processes support trusted operation, while Docker adds a constrained container boundary for mutually untrusted applications.