stabilize architecture docs: address review findings and advance to stable
Critical fixes: - Restructure pointers.md: move setNode prop-key writes section under its own heading (was incorrectly nested under selectNode) - Add Context/Density/Direction/RenderContext documentation section to host-config.md (was only a brief constraint bullet) - Advance all 5 ADRs from Status: Proposed → Accepted and frontmatter from status: draft → status: stable (decisions are driving implementation) - Add error handling philosophy section to README Warning/suggestion fixes: - Add isUElement null check (node !== null) to schema.md discriminator table - Add UjsxEnvelope convenience type documentation to events.md - Add Direction Unicode arrow naming note to transforms.md - Standardize all cross-references from absolute docs/research/ paths to relative ../research/ paths across all architecture docs - Fix schema.md ADR references to use relative paths - Reduce redundancy between transforms.md and host-config.md Direction notes - Update all architecture doc frontmatter from draft → stable Deferred: - Performance model section (reconciler not yet built) - Concepts/glossary document (low ROI at current scale) - Line counts in source references (would date quickly)
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
---
|
||||
status: draft
|
||||
status: stable
|
||||
last_updated: 2026-05-18
|
||||
---
|
||||
|
||||
@@ -82,6 +82,85 @@ interface Root<TTag extends string, Instance, RootCtx> {
|
||||
|
||||
The `Context` connection is intentional: UJSX wants hosts to make density-aware and target-aware decisions without the host needing to know about signal internals. `Context` provides a reactive `ContextValue` that hosts can read during `createInstance` or `commitUpdate`.
|
||||
|
||||
## Context, Density, Direction & RenderContext
|
||||
|
||||
The `context.ts` module exports a set of related types and a class that support adaptive rendering and directional transforms:
|
||||
|
||||
### Context (class)
|
||||
|
||||
```typescript
|
||||
class Context {
|
||||
constructor(initial?: Partial<ContextValue>)
|
||||
get(): ContextValue
|
||||
get signal(): ReadonlySignal<ContextValue>
|
||||
set(partial: Partial<ContextValue>): void
|
||||
subscribe(fn: (value: ContextValue) => void): () => void
|
||||
fork(overrides: Partial<ContextValue>): Context
|
||||
}
|
||||
```
|
||||
|
||||
`Context` wraps a Preact `signal<ContextValue>` and provides reactive access to context data. Hosts read context during `createInstance` or `commitUpdate` to adapt rendering (e.g., switching layouts based on `density`). The `signal` getter exposes the underlying `ReadonlySignal` for composition with other reactive primitives.
|
||||
|
||||
- **`get()`** — returns the current `ContextValue` (non-reactive read).
|
||||
- **`set(partial)`** — shallow-merges `partial` into the current value inside a `batch()`, triggering any subscriptions.
|
||||
- **`subscribe(fn)`** — calls `fn` on every change via `effect()`. Returns a dispose function.
|
||||
- **`fork(overrides)`** — creates a new `Context` with the current values shallow-merged with `overrides`. Forked contexts are independent — changes to the fork do not propagate to the parent.
|
||||
|
||||
### ContextValue
|
||||
|
||||
```typescript
|
||||
interface ContextValue {
|
||||
density: Density;
|
||||
target: string;
|
||||
metadata: Record<string, unknown>;
|
||||
}
|
||||
```
|
||||
|
||||
The shape of a context's value. Defaults:
|
||||
|
||||
- `density`: `"full"`
|
||||
- `target`: `"markdown"`
|
||||
- `metadata`: `{}`
|
||||
|
||||
### Density
|
||||
|
||||
```typescript
|
||||
type Density = "full" | "compact" | "minimal"
|
||||
```
|
||||
|
||||
Controls rendering granularity for hosts that support adaptive output. `full` means render everything; `compact` and `minimal` are host-defined — UJSX passes the value through, it does not interpret it. A desktop UI host might use `compact` to hide labels and `minimal` to render only essential controls.
|
||||
|
||||
### Direction
|
||||
|
||||
```typescript
|
||||
type Direction = "ujsx→mdast" | "mdast→ujsx" | "ujsx→jpath" | "jpath→ujsx" | "ujsx→hast" | "hast→ujsx"
|
||||
```
|
||||
|
||||
Six directional strings pairing into three bi-directional channels (markdown, JSON path, HTML). `Direction` is defined in `context.ts` because it governs both transform rules and render context — it's not transform-specific.
|
||||
|
||||
The `→` character in direction strings is a Unicode right arrow (U+2192). This was chosen for readability over alternatives like `ujsx-to-mdast` or `ujsx2mdast`. Consumers should be aware of the non-ASCII characters in IDE autocompletion and linting contexts.
|
||||
|
||||
### RenderContext
|
||||
|
||||
```typescript
|
||||
interface RenderContext extends ContextValue {
|
||||
direction: Direction;
|
||||
}
|
||||
```
|
||||
|
||||
A convenience type that adds `direction` to `ContextValue`. Used primarily by the transform system to carry conversion direction alongside context data. The transform `ctx` factory function accepts a `Direction` and returns a `TransformContext` (which includes `direction`), not a `RenderContext` — `RenderContext` exists for consumers that want to type-narrow the full context shape.
|
||||
|
||||
### Exports
|
||||
|
||||
All context types are available from the barrel export (`@alkdev/ujsx`) and the `context` sub-path:
|
||||
|
||||
```typescript
|
||||
import { Context } from "@alkdev/ujsx/context"; // class
|
||||
import type { Density, Direction, RenderContext } from "@alkdev/ujsx/context"; // types
|
||||
```
|
||||
|
||||
`Context` is a runtime export; `Density`, `Direction`, and `RenderContext` are type-only exports.
|
||||
|
||||
## createRoot()
|
||||
|
||||
```typescript
|
||||
@@ -170,7 +249,7 @@ unmount() {
|
||||
|
||||
This means calling `unmount()` followed by creating a new root on the same container will likely result in leaked instances and stale signal effects.
|
||||
|
||||
The reconciler architecture ([reconciler.md](reconciler.md)) and lifecycle management ([lifecycle.md](lifecycle.md)) address both gaps. The research documents (`docs/research/reconciler/01-reactive-host-bridge.md` and `03-unmount-dispose-support.md`) provide the detailed implementation plans.
|
||||
The reconciler architecture ([reconciler.md](reconciler.md)) and lifecycle management ([lifecycle.md](lifecycle.md)) address both gaps. The research documents (`../research/reconciler/01-reactive-host-bridge.md` and `../research/reconciler/03-unmount-dispose-support.md`) provide the detailed implementation plans.
|
||||
|
||||
### Event IDs Use `Date.now()`
|
||||
|
||||
@@ -193,7 +272,7 @@ The reconciler solves this by maintaining a fiber tree alongside the instance tr
|
||||
- **`render()` accepts any `UNode`** — if the node is a `URoot`, its children are mounted directly. If the node is any other `UNode` (element or primitive), it is wrapped in an array and mounted as a single top-level element without a root container.
|
||||
- **Function components are synchronous and transparent** — they receive props and children, return a `UNode`, and produce no host instance. The reconciler research discusses how to handle components that return different tree shapes across renders.
|
||||
- **`Context` is always present** — `createRoot()` guarantees a `Context` exists on the `Root`. If none is provided, a default is created with `density: "full"`, `target: "markdown"`, and empty `metadata`.
|
||||
- **`RenderContext` extends `ContextValue`** — the `Direction` type (`"ujsx→mdast" | "mdast→ujsx" | "ujsx→jpath" | "jpath→ujsx" | "ujsx→hast" | "hast→ujsx"`) and `RenderContext` interface (which adds `direction` to `ContextValue`) are exported from the `context` sub-path. `RenderContext` is primarily used by the transform system to specify conversion direction. `Density` (`"full" | "compact" | "minimal"`) controls rendering granularity for hosts that support adaptive output.
|
||||
- **`RenderContext` extends `ContextValue`** — adds `direction` to the context value. See [Context, Density, Direction & RenderContext](#context-density-direction--rendercontext) for full documentation of these types.
|
||||
- **`container` is opaque** — UJSX passes it to `createRootContext` and stores it on `Root`, but never inspects it. The host defines what it means.
|
||||
- **Mount is depth-first, post-order** — children are fully constructed before being appended to their parent. Hosts can rely on this ordering invariant.
|
||||
|
||||
@@ -216,4 +295,4 @@ The reconciler solves this by maintaining a fiber tree alongside the instance tr
|
||||
- Context: `src/core/context.ts` — `Context` class with signal-based values
|
||||
- Reconciler architecture: [reconciler.md](reconciler.md)
|
||||
- Lifecycle management: [lifecycle.md](lifecycle.md)
|
||||
- Reconciler research: `docs/research/reconciler/01-reactive-host-bridge.md` and `03-unmount-dispose-support.md`
|
||||
- Reconciler research: `../research/reconciler/01-reactive-host-bridge.md` and `../research/reconciler/03-unmount-dispose-support.md`
|
||||
Reference in New Issue
Block a user