Files
ujsx/AGENTS.md
glm-5.1 743265c34a docs: add README, dual license files, AGENTS.md; fix tsup DTS build
- Add README.md with full API reference and usage examples for npm publishing
- Add LICENSE-MIT and LICENSE-APACHE for dual licensing (MIT OR Apache-2.0)
- Update AGENTS.md with project instructions for AI agents
- Fix tsconfig.json: remove declaration, declarationMap, outDir, and sourceMap
  which conflicted with tsup's own DTS generation (TS5055 overwrite errors)
2026-05-19 07:06:56 +00:00

129 lines
7.0 KiB
Markdown

# AGENTS.md
Instructions for AI agents working with the @alkdev/ujsx codebase.
## Project Overview
`@alkdev/ujsx` is a runtime-agnostic reactive tree library. JSX serves as an intermediate representation — the same declarative tree can target different hosts (markdown, graph structures, DOM, workflow engines) through a `HostConfig` adapter. The core has zero platform-specific assumptions: no `onClick`, no `className`, no `style`.
## Build & Test Commands
```bash
npm run build # tsup production build (ESM + CJS) → dist/
npm run build:tsc # Type checking only (tsc --noEmit)
npm run lint # Same as build:tsc — tsc --noEmit
npm run test # vitest run
npm run test:watch # vitest in watch mode
npm run test:coverage # vitest run --coverage
```
Always run `npm run lint` and `npm run test` after making changes.
## Architecture
### Source Layout
```
src/
mod.ts # Barrel export (all public API)
core/
schema.ts # TypeBox Module (UJSX), UNode/UElement/URoot/UPrimitive types, type guards
h.ts # Element factory: h(), createRoot(), createComponent(), Fragment, jsx/jsxs/jsxDEV
reactive.ts # ReactiveRoot, reactiveComponent, reactiveElement; re-exports signal/computed/effect/batch
context.ts # Context class (signal-backed), Density, Direction, RenderContext
events.ts # EventEnvelope, PubSubLike, UjsxEventMap, createPubSubEmitter, proxyEventEmitter
pointer.ts # ValuePointer<T>, selectNode(), setNode()
jsx-runtime.ts # Re-exports jsx/jsxs/jsxDEV/Fragment for jsxImportSource
host/
config.ts # HostConfig<TTag,Instance,RootCtx>, Root, createRoot(), mount/reconcile/unmount pipeline
fiber.ts # Fiber<I> type, Effect<I> type, disposeFiber(), HostLike
reconcile.ts # reconcileProps, reconcileChildren, commitMutations, scheduleUpdate, flushUpdates, LIS
transform/
registry.ts # TransformRegistry, TransformRule, TransformContext, childCtx, matchesSchema, ctx
```
### Core Data Model
The tree is the truth. All nodes are `UNode`:
```typescript
type UNode = UPrimitive | UElement | URoot;
// UPrimitive = string | number | boolean | null
// UElement = { type: string; props: UniversalProps; children: UNode[]; key?: string }
// URoot = { type: "root"; props: UniversalProps; children: UNode[] }
```
- `key` on `UElement` is extracted from props by `h()` and promoted to the element level — it is never in `props`
- `URoot` is a transparent container: never has a `key`, its children mount directly into the parent
- Function components are transparent: called with `{ ...props, children }`, their output mounts in place
### Rendering Pipeline
1. `h()` / JSX creates `UElement` trees (pure data)
2. `createHostRoot(host, container)` creates a `Root` with a `HostConfig`
3. `root.render(node)` mounts the tree via `HostConfig` methods
4. Re-renders use `reconcileChildren` (key-based, LIS-optimized) and `reconcileProps` (TypeBox `Value.Diff`/`Value.Equal`/`Value.Hash`)
5. `wireSignalToFiber` binds Preact signals to fibers for automatic `scheduleUpdate``flushUpdates`
### Key Subsystems
- **Reactivity**: Built on `@preact/signals-core`. `Signal<UNode>` flows through `computed``effect` → reconciler. Do not introduce alternative reactive systems.
- **HostConfig**: The sole integration point for platform-specific logic. Three type params: `TTag` (allowed tags), `Instance` (host instance type), `RootCtx` (root context type). Required methods: `createRootContext`, `createInstance`, `createTextInstance`, `appendChild`. Optional: `insertBefore`, `removeChild`, `prepareUpdate`, `commitUpdate`, `finalizeRoot`, `finalizeInstance`, `emit`.
- **Transforms**: `TransformRegistry` with priority-sorted `TransformRule`s. Rules match by `direction` and `match()` predicate. The `next()` callback enables recursive transformation. Directions use Unicode arrows (e.g. `"ujsx→mdast"`).
- **Pointers**: `ValuePointer<T>` wraps `signal<T>` with a path. `selectNode`/`setNode` provide immutable tree navigation and updates.
### TypeBox Usage
`UJSX` is a `Type.Module` from `@alkdev/typebox`. Schema keys: `UPrimitive`, `PropValue`, `UniversalProps`, `UElement`, `URoot`, `UNode`. Runtime validation via `Value.Check(UJSX.Import("UElement"), node)`. The reconciler uses `Value.Diff`, `Value.Equal`, and `Value.Hash` for prop diffing with function-prop stripping.
## Code Conventions
- **No comments in code** unless explicitly requested
- **ESM primary**: `"type": "module"`, CJS is a distribution compatibility layer
- **No Node-specific APIs in core**: `src/core/` and `src/transform/` must not import `fs`, `path`, `child_process`, etc.
- **Dual format via tsup**: Same source produces ESM and CJS; exports map always has matching `import` and `require` entries
- **TypeBox is non-negotiable**: Not optional, not replaceable
- **`@preact/signals-core` is the only reactive primitive**: No RxJS, Solid signals, or Vue reactivity
- **Platform agnostic**: No DOM APIs, Three.js APIs, or any platform-specific API calls in core
## Sub-path Exports
Each sub-path maps to a single source file — no barrel re-exports within sub-paths:
| Import | Source |
|--------|--------|
| `@alkdev/ujsx` | `src/mod.ts` |
| `@alkdev/ujsx/schema` | `src/core/schema.ts` |
| `@alkdev/ujsx/h` | `src/core/h.ts` |
| `@alkdev/ujsx/reactive` | `src/core/reactive.ts` |
| `@alkdev/ujsx/context` | `src/core/context.ts` |
| `@alkdev/ujsx/events` | `src/core/events.ts` |
| `@alkdev/ujsx/pointer` | `src/core/pointer.ts` |
| `@alkdev/ujsx/host` | `src/host/config.ts` |
| `@alkdev/ujsx/transform` | `src/transform/registry.ts` |
| `@alkdev/ujsx/jsx-runtime` | `src/core/jsx-runtime.ts` |
## Testing
Tests are in `test/` using Vitest. Each test file maps to a specific feature area. Tests import directly from `src/` (not from `dist/`). When adding features, add or update corresponding test files.
## Architecture Documentation
Detailed architecture docs live in `docs/architecture/`:
- `schema.md` — TypeBox Module, type definitions, type guards
- `element-factory.md` — h(), createRoot(), createComponent(), Fragment
- `reactive-layer.md` — ReactiveRoot, reactiveComponent, reactiveElement, signal integration
- `host-config.md` — HostConfig interface, createRoot(), mount pipeline, reconciler gap
- `reconciler.md` — Fiber tree, reconciliation algorithm, update scheduling, TypeBox optimizations
- `lifecycle.md` — Mount, update, unmount/dispose lifecycle, signal cleanup
- `transforms.md` — TransformRegistry, TransformRule, TransformContext, bi-directional transforms
- `events.md` — EventEnvelope, PubSubLike, UjsxEventMap
- `pointers.md` — ValuePointer, selectNode, setNode, tree navigation
- `build-distribution.md` — Package structure, exports map, dependencies, runtime targets
Design decisions (ADRs) are in `docs/architecture/decisions/`:
- 001: HTML-agnostic core
- 002: TypeBox Module as type registry
- 003: Preact signals-core for reactivity
- 004: `key` as first-class field on UElement
- 005: Signal-driven updates for props, reconciliation for structure