diff --git a/docs/design/0003-web-platform-first-frontend.md b/docs/design/0003-web-platform-first-frontend.md index 012aac4..676ad00 100644 --- a/docs/design/0003-web-platform-first-frontend.md +++ b/docs/design/0003-web-platform-first-frontend.md @@ -5,7 +5,15 @@ | Status | Development | | Scope | Kernel, Experience | | Created | 2026-07-27 | -| Last updated | 2026-07-28 | +| Last updated | 2026-08-02 | + +> [!NOTE] +> [0010: Deno runtime and Web Components frontend](0010-deno-runtime-and-web-components-frontend.md) +> proposes replacing this record in full while carrying forward its Web +> Platform, accessibility, headless-UI, Zod, dependency-review, and security +> decisions. This record continues to describe the implemented Development +> direction until 0010 becomes Stable; both records and the index must then be +> updated together as this record moves to Legacy. ## Summary diff --git a/docs/design/0010-deno-runtime-and-web-components-frontend.md b/docs/design/0010-deno-runtime-and-web-components-frontend.md new file mode 100644 index 0000000..2a8bae8 --- /dev/null +++ b/docs/design/0010-deno-runtime-and-web-components-frontend.md @@ -0,0 +1,616 @@ +# 0010: Deno runtime and Web Components frontend + +| Field | Value | +| ------------ | ------------------ | +| Status | Draft | +| Scope | Kernel, Experience | +| Created | 2026-08-02 | +| Last updated | 2026-08-02 | + +## Summary + +Hyperkernel intends to replace Svelte, SvelteKit, Node.js, and npm as its +primary application-development stack with a Deno workspace and a frontend built +primarily from Web Components. + +Deno will own TypeScript execution, workspace resolution, tasks, formatting, +linting, type checking, testing, and the server runtime. Public UI packages will +expose standards-based custom elements instead of requiring consumers to use +Svelte. Preact will be retained only as a small rendering engine: Hyperkernel +may use JSX to describe a render tree and Preact to turn that tree into DOM or +HTML, but it will not adopt hooks, signals, context, or Preact's application +state model. + +The Hyperkernel shell is not being designed around server-side rendering. +Reusable UI primitives must nevertheless support applications that render on the +server. Their server representation will use Declarative Shadow DOM where +encapsulation is required. Declarative Shadow DOM solves HTML serialization of a +shadow tree; it does not by itself solve hydration, event attachment, state +reconciliation, or component composition. Those contracts require implementation +evidence before this record can advance. + +This record proposes a complete replacement for +[0003: Web-platform-first frontend](0003-web-platform-first-frontend.md). It +changes the Svelte and SvelteKit decisions while carrying forward that record's +Web Platform, accessibility, headless-UI, Zod, dependency-review, and security +decisions. It does not claim that the replacement has already been implemented. +The current repository remains a SvelteKit application running on Node.js and +managed with npm until an incremental migration satisfies the evidence in this +record and the canonical guidance in `README.md`, `AGENTS.md`, and +`CONTRIBUTING.md` is updated. + +### Relationship to record 0003 + +Record 0003 continues to describe the implemented frontend and tooling baseline +while this record is Draft, Development, or Evaluation. Advancing this record to +Development also ends new investment in the Svelte and SvelteKit direction +except for compatibility, security, and migration work needed to preserve a +runnable system. + +Record 0003 will move directly from Development to Legacy in the same change +that advances this record to Stable. That change must update both records and +the design-record index. Because this record carries forward every decision +from record 0003 that remains applicable, the transition will not retire the Web +Platform, accessibility, headless-UI, Zod, dependency-review, or security +contracts. + +## Problem + +Hyperkernel is becoming a monorepo so that its kernel, storage, interface, and +other reusable contracts can be developed together and published as packages. +That work exposed a mismatch between the intended package boundary and the +current frontend boundary. + +A Svelte component package is easiest to consume from another Svelte +application. It can be adapted for other consumers, including by compiling +components as custom elements, but Svelte remains the authoring model and +compiler contract behind those elements. Hyperkernel wants its UI primitives to +be usable from plain HTML and JavaScript and from applications built with +different frameworks without requiring each consumer to adopt Svelte-specific +tooling or semantics. + +SvelteKit also brings an application model and a collection of integrated and +adjacent tools. The current repository separately configures npm, Vite, +Prettier, ESLint, Svelte checks, Vitest, Playwright, and a Node adapter. Each +tool is individually reasonable, but together they create more configuration, +dependency upgrades, and integration boundaries than Hyperkernel wants to own. + +Remote functions were the main SvelteKit-specific capability that justified this +cost. At the time of this decision they remain experimental and may change +without notice. They present a framework-specific programming model that is +compiled into generated HTTP endpoints and `fetch` wrappers. That can be +productive inside a SvelteKit application, but it does not provide the explicit, +framework-independent `Request`, `Response`, command, and query contracts +Hyperkernel wants at its public boundaries. + +The deeper issue is not that Svelte or SvelteKit are defective. They solve a +broader frontend problem by introducing a framework model for components, +reactivity, routing, data loading, rendering, and client-server communication. +Hyperkernel instead wants its primary vocabulary to remain close to the Web +Platform so that packages are portable, contracts remain inspectable, and the +cost of replacing a tool does not become the cost of rewriting the product. + +Deno is a better fit for that direction. It executes TypeScript directly, +implements web-standard server APIs, denies sensitive I/O by default, supports +workspaces, and includes the formatter, linter, type checker, test runner, and +task runner required for the initial project. Consolidating those concerns in +one runtime reduces configuration and dependency management so that the +repository can spend more of its complexity budget on Hyperkernel itself. + +Web Components create a similar opportunity in the browser. Custom elements, +Shadow DOM, templates, slots, DOM events, CSS custom properties, and CSS parts +provide a framework-independent component boundary. Their missing integrated +rendering and state model is a cost, but it is also an opportunity to choose a +smaller and more explicit internal model than the general-purpose reactivity +systems supplied by Svelte, React, and similar frameworks. + +## Invariants + +1. Public UI contracts are expressed through HTML element names, attributes, + properties, methods, events, slots, CSS custom properties, CSS parts, and + TypeScript declarations. They do not require a Svelte or Preact runtime in a + consuming application's source model. +2. Svelte, Preact, Deno, router, build, or transport APIs never define durable + domain state or bypass Hyperkernel's command, authorization, event, and + projection contracts. +3. Framework transports remain adapters around explicit, framework-independent + command and query contracts based on Web Platform data types where those + types satisfy the contract. +4. Preact is an internal rendering mechanism, not Hyperkernel's component, + state-management, or public application model. +5. UI state changes are explicit. Hyperkernel will not introduce a general + reactive graph, hooks, signals, or another implicit dependency-tracking model + without a new design decision supported by concrete failure cases. +6. A custom element owns its local ephemeral state. Durable user-visible state + still changes through commands and events; using Web Components does not + create a second persistence model. +7. A server-rendered custom element produces valid HTML that is useful before + client upgrade. Client initialization preserves the semantic content and + state represented by that HTML rather than blindly replacing it. +8. Declarative Shadow DOM is used only where shadow encapsulation is part of the + component contract. A component may deliberately use light DOM when document + semantics, forms, styling, accessibility, or composition make it the better + boundary. +9. Deno permissions follow least privilege in development, tests, production, + and package scripts. `--allow-all` is not the default execution contract. +10. Deno's process permissions do not replace Hyperkernel actor identities, + capabilities, command authorization, database boundaries, or extension + isolation. +11. The runtime migration preserves every kernel persistence, transaction, + ordering, replay, recovery, and compatibility invariant. A change of runtime + is not permission to change kernel semantics. +12. Packages intended for npm consumers are tested from their published artifact + and supported consumer runtimes. Passing inside the Deno workspace is + necessary but not sufficient evidence of npm compatibility. +13. The repository does not maintain permanent parallel Svelte and Web + Components implementations of the same interface. Temporary coexistence + requires an explicit migration boundary and removal condition. +14. Hyperkernel uses semantic HTML, native controls, CSS, ECMAScript, and + browser APIs directly when they satisfy the required contract. It does not + adopt a headless UI library without a new design record. +15. Platform-first implementation does not weaken accessibility. Custom + interactions preserve correct semantics, keyboard operation, focus + behavior, and assistive-technology support. +16. Zod remains the runtime schema-validation mechanism. Its internal + representation is not a durable wire or storage format, and upgrades must + preserve the accepted meaning of versioned schemas. + +## Decision + +### Carry forward the platform, accessibility, and validation boundaries + +Hyperkernel continues to use semantic HTML, native controls, CSS, ECMAScript, +and browser APIs directly when they satisfy the product contract. It continues +to own the observable behavior and styling of its core interface primitives and +will not introduce a headless UI library without a new design record that +defines the missing platform capability, accessibility behavior, compatibility +contract, dependency impact, and exit path. + +Accessibility remains a product contract rather than a benefit assumed from a +framework or component library. Custom elements and intentional deviations from +native behavior must preserve semantics, keyboard operation, focus behavior, +forms, and assistive-technology support through documented tests. + +Zod remains the runtime validation mechanism for untrusted structured data. +Schemas may be implemented with regular Zod or Zod Mini when their APIs satisfy +the boundary, but their internal representation does not identify a durable +contract. Event and storage compatibility remain governed by explicit types and +schema versions. + +Future dependency exceptions require the same evidence established by record +0003: the missing platform capability, observable behavior owned by the +dependency, compatibility contract, transitive and build impact, migration +path, and why local ownership would be riskier. + +### Use Deno as the primary runtime and toolchain + +The monorepo will be defined by a root `deno.json` workspace. Deno will run +TypeScript, workspace tasks, formatting, linting, type checking, unit tests, and +the server application. Repository commands should prefer built-in Deno +capabilities before adding an overlapping package or configuration layer. + +The server boundary will use `Deno.serve`, `Request`, `Response`, `URL`, +`Headers`, `FormData`, Web Streams, and other standards-based APIs where they +provide the required contract. Routing and transport code remain thin adapters +around kernel commands and queries. Deno request objects, permission objects, +and runtime-specific APIs do not enter public kernel contracts. + +Deno is selected as a mature, actively maintained runtime whose security model, +Web Platform alignment, and integrated tooling match Hyperkernel's direction. +Confidence in the project's engineering practices supports the choice, but it is +not a compatibility guarantee. Hyperkernel will pin supported versions, inspect +relevant changes, and verify upgrades against its own contracts. + +Deno's Node and npm compatibility may support migration or a dependency that has +no adequate alternative. Compatibility is a boundary, not the new default +vocabulary. Each retained Node or npm dependency must have an identified owner +and a reason to remain. + +This decision replaces npm as the primary local package manager and task +orchestrator. It does not remove npm as a distribution target. Deno workspaces +can contain npm packages, while `deno publish` itself targets JSR. Any +Hyperkernel package published to npm will therefore retain the metadata and +build or packaging step required by npm consumers. That pipeline must be +explicit, reproducible, and verified from the produced tarball. + +JSR may later become an additional distribution target, but adopting Deno does +not make JSR publication part of this decision. + +### Use Web Components as the public UI boundary + +Reusable Hyperkernel interface primitives will be autonomous custom elements. +Their observable contract will use the standard browser component vocabulary: + +- attributes for serializable declarative input; +- properties and methods where values or behavior cannot be represented + faithfully as attributes; +- DOM events for observable output and interaction; +- light DOM and named slots for caller-owned content; +- Shadow DOM only for intentional encapsulation; +- CSS custom properties and parts for supported styling boundaries; +- TypeScript declarations for typed consumers. + +This allows the same element to be used from plain HTML, a Hyperkernel +application, or another framework. A consumer may wrap an element in its own +framework, but Hyperkernel will not require that wrapper as the canonical API. + +The first implementation must define registration behavior, duplicate-version +behavior, error reporting, teardown, focus, forms, accessibility, and browser +support. Custom-element registration is global and effectively permanent for a +document, so package loading and development reload cannot assume that an +existing element name can be redefined. + +### Use Preact only for rendering + +Hand-written DOM updates are explicit but become verbose and error-prone for +nontrivial conditional and repeated markup. Hyperkernel will use Preact as a +small internal renderer so component implementations can describe output with +JSX and render that description into a DOM root. + +The accepted boundary includes the Preact VNode type, JSX transformation, client +rendering, and the server renderer needed to produce HTML. It does not include +hooks, signals, context, compatibility APIs, a Preact application root, or +Preact components as public Hyperkernel component contracts. + +A custom element remains the lifecycle and ownership boundary. It receives input +through platform contracts, stores explicit local state, decides when a render +is necessary, and invokes the renderer. Rendering does not discover state +dependencies or decide when state changed. + +Preact is replaceable only if that claim is kept true in the source. Shared code +must not gradually depend on hooks or Preact-specific context and then continue +describing Preact as an implementation detail. If concrete components show that +the narrow renderer boundary is insufficient, the project must revisit this +decision explicitly. + +### Keep state transitions explicit + +Hyperkernel will not begin by creating a general frontend state-management +abstraction. A component may keep local ephemeral state in fields or a small +component-owned controller. An input, DOM event, command result, or explicit +method changes that state and requests a render. + +This model may repeat some update code that a reactive framework could infer. +That is an accepted initial cost. For Hyperkernel, visible ownership and +predictable control flow are more important than minimizing every state-to-view +binding. + +Shared durable state remains in projections and enters the interface through +queries or subscriptions with explicit ownership and cleanup. State shared only +between a small set of components should first use DOM composition, properties, +and events. A broader client-side store requires concrete evidence that these +boundaries are inadequate. + +### Support server rendering through Declarative Shadow DOM + +The Hyperkernel shell does not require server-side rendering as its primary +delivery model. Published UI primitives must still be usable by applications +that do. + +When a component uses Shadow DOM, its server renderer will emit the custom +element host and a declarative shadow root through +`