Skip to content

Add Clayterm current-state specification#5

Open
taras wants to merge 1 commit intomainfrom
docs/add-clayterm-spec
Open

Add Clayterm current-state specification#5
taras wants to merge 1 commit intomainfrom
docs/add-clayterm-spec

Conversation

@taras
Copy link
Copy Markdown
Member

@taras taras commented Apr 4, 2026

Summary

  • Adds a v0.1 draft specification (specs/clayterm-spec.md) documenting Clayterm's rendering contract, architectural model, core invariants, and public API surface
  • Normative sections cover the rendering pipeline, frame-snapshot model, descriptor pattern, identity semantics, and boundary responsibilities (zero IO, no terminal state management, no application lifecycle)
  • Non-normative sections describe settling surfaces (transfer encoding, descriptor property groups, input parsing, pointer events) separately from the stable core
  • Includes appendix on confidence levels and a list of open decisions intentionally left unresolved

Test plan

  • Review spec content for accuracy against current codebase
  • Verify normative sections (4–11) align with existing API behavior
  • Confirm non-normative sections (12–14) accurately describe current implementation surfaces
  • Check that open decisions list is complete and current

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new bot commented Apr 4, 2026

Open in StackBlitz

npm i https://pkg.pr.new/clayterm@5

commit: 30b0eef

@taras taras force-pushed the docs/add-clayterm-spec branch from 4158ad1 to e31bd93 Compare April 10, 2026 20:41
Copy link
Copy Markdown
Member

@cowboyd cowboyd left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is good. Left the feedback, I think we need to use "directive" instead of "descriptor" because they are command-like open() / close()

Comment on lines +50 to +52
**Descriptor (op).** A plain object that declares one element of the UI tree for a single frame. Descriptors are typed by an identifier field and carry layout, styling, and content properties. The set of descriptors for a frame is ordered and forms an implicit tree via open/close pairing.

**Descriptor array.** An ordered array of descriptors constituting a complete frame description. The array is the input to the rendering transaction.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

not sure these are really things. we don't mention descriptors, the core primitive is the Op. There are three: open(), close(), and text()

Comment on lines +63 to +65

**Term.** An instance of the Clayterm renderer, bound to specific terminal dimensions. A Term is the object through which the caller performs render transactions.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thinking of changing this to Screen, Region


### 4.4 Double-buffered diffing

The renderer maintains two cell buffers: a front buffer (the previously rendered frame) and a back buffer (the frame being rendered). After populating the back buffer from the current frame's render commands, the renderer compares it against the front buffer and emits ANSI bytes only for cells that differ. The buffers are then swapped. This mechanism is internal to the renderer and not directly observable to the caller.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The buffers are then swapped.

implementation detail (as the spec mentions), but this is not true. cells where a diff is detected is copied from back to front so that at the end, they are the same.

open(name: string, props?): OpenElement
```

Creates an element-open descriptor. The `name` parameter provides a tag name for the element. The optional `props` parameter carries configuration for layout, styling, and behavior.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

we should probably rename name to id so that it is clear that this should be unique, or call the parameter "unique name" or some such

Comment on lines +341 to +346
### 11.4 The renderer does not own input parsing

Input parsing (keyboard events, mouse events, escape sequence decoding) is an independent concern. It is not part of the rendering contract defined by this specification. The renderer MUST NOT depend on input-parsing state, types, or API.

Clayterm currently provides input-parsing functionality alongside the renderer in the same package. This co-location is an implementation detail, not an architectural coupling. Section 12.4 describes the current input surface.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

worth noting that this is handled by the clay term input interface.

Also, hit detection (pointerenter, pointerleave) requires the render loop to participate. You can pass the current x,y position of the pointer and it will tell you the ids of every element that it is over. This is how the PointerEvents[] array is populated in the render output.


The `output` field is the ANSI byte output specified normatively in Section 7.3 and Section 8.2.

The `events` field contains pointer events (enter, leave, click) derived from the underlying layout engine's element hit-testing. This field was added during a pointer-events feature implementation. The pointer event model is functional but has acknowledged gaps (no modifier keys on click events) and its interaction protocol (calling `setPointer(x, y, down)` before rendering, then reading events from the return value) was arrived at through iteration rather than upfront design.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is how Clay says that you're supposed to do things.

Comment on lines +458 to +460
**WASM loading.** The current implementation loads the WASM binary relative to the module's location, compiles it once, and instantiates it per Term or Input with fresh memory. The loading mechanism has changed and may change again.

**WASM co-location.** The WASM binary file is expected to be co-located with the JavaScript module files. Both JSR and npm package builds include the artifact.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This has changed.


**Memory layout.** WASM linear memory is initialized with 256 pages (16MB). The renderer state struct and the transfer buffer are allocated in WASM linear memory. The specific layout is an implementation detail.

**Output buffer lifetime.** The ANSI byte output resides in WASM linear memory. The `Uint8Array` returned by `render()` is a view over this memory. The output is valid until the next `render()` call on the same Term instance, at which point the buffer may be reused. Callers who need to retain the output beyond the next render SHOULD copy it.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can't imagine this ever changing, so it can be added to the normative section.


3. **Is the input API part of the Clayterm specification?** This specification describes it in Section 12.4 but does not specify it normatively. The input API may become a separate package or specification.

4. **Are `pack()` and `validate()` public API?** Both are currently exported. Neither is specified normatively here.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

pack is not, validate is.

Adds the v0.1 draft specification covering the architectural model,
core invariants, public rendering API, descriptor model, and boundary
responsibilities. Non-normative sections document settling surfaces
(transfer encoding, input parsing, pointer events) separately from
the stable rendering core.
@taras taras force-pushed the docs/add-clayterm-spec branch from e31bd93 to 30b0eef Compare April 11, 2026 10:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants