diff --git a/ops.ts b/ops.ts index 8f952af..3344eea 100644 --- a/ops.ts +++ b/ops.ts @@ -11,8 +11,6 @@ const PROP_BORDER = 0x08; const PROP_CLIP = 0x10; const PROP_FLOATING = 0x20; -/* ── Packing ──────────────────────────────────────────────────────── */ - const encoder = new TextEncoder(); function packAxis(view: DataView, offset: number, axis: SizingAxis): number { @@ -73,9 +71,9 @@ export function pack( let o = offset; for (let op of ops) { - switch (op.id) { + switch (op.directive) { case OP_CLOSE_ELEMENT: - view.setUint32(o, op.id, true); + view.setUint32(o, op.directive, true); o += 4; break; @@ -83,8 +81,8 @@ export function pack( view.setUint32(o, OP_OPEN_ELEMENT, true); o += 4; - let id = encoder.encode(op.name); - o = packString(view, id, o); + let bytes = encoder.encode(op.id); + o = packString(view, bytes, o); let mask = 0; if (op.layout) mask |= PROP_LAYOUT; @@ -210,15 +208,11 @@ export function pack( return (o - offset) / 4; } -/* ── Color ────────────────────────────────────────────────────────── */ - export function rgba(r: number, g: number, b: number, a = 255): number { return ((a & 0xFF) << 24) | ((r & 0xFF) << 16) | ((g & 0xFF) << 8) | (b & 0xFF); } -/* ── Sizing axis types ────────────────────────────────────────────── */ - export type SizingAxis = | { type: "fit"; min?: number; max?: number } | { type: "grow"; min?: number; max?: number } @@ -241,15 +235,13 @@ export const percent = (value: number): SizingAxis => ({ }); export const fixed = (value: number): SizingAxis => ({ type: "fixed", value }); -/* ── Op descriptors ───────────────────────────────────────────────── */ - export interface CloseElement { - id: typeof OP_CLOSE_ELEMENT; + directive: typeof OP_CLOSE_ELEMENT; } export interface OpenElement { - id: typeof OP_OPEN_ELEMENT; - name: string; + directive: typeof OP_OPEN_ELEMENT; + id: string; layout?: { width?: SizingAxis; height?: SizingAxis; @@ -280,7 +272,7 @@ export interface OpenElement { } export interface Text { - id: typeof OP_TEXT; + directive: typeof OP_TEXT; content: string; color?: number; fontSize?: number; @@ -291,22 +283,20 @@ export interface Text { export type Op = OpenElement | Text | CloseElement; -/* ── Descriptor constructors ──────────────────────────────────────── */ - export function open( - name: string, - props: Omit = {}, + id: string, + props: Omit = {}, ): OpenElement { - return { id: OP_OPEN_ELEMENT, name, ...props }; + return { directive: OP_OPEN_ELEMENT, id, ...props }; } export function text( content: string, - props: Omit = {}, + props: Omit = {}, ): Text { - return { id: OP_TEXT, content, ...props }; + return { directive: OP_TEXT, content, ...props }; } export function close(): CloseElement { - return { id: OP_CLOSE_ELEMENT }; + return { directive: OP_CLOSE_ELEMENT }; } diff --git a/specs/renderer-spec.md b/specs/renderer-spec.md index 74a6d49..e0efa55 100644 --- a/specs/renderer-spec.md +++ b/specs/renderer-spec.md @@ -225,13 +225,7 @@ stages. The caller MUST NOT need to perform any of these operations. symmetric: both calls occur within the same render transaction, in the same function scope. -**INV-7. Element identity disambiguation.** When multiple elements within a -frame share the same id, the renderer MUST disambiguate their identities so that -the layout engine does not conflate them. The disambiguation mechanism is an -implementation detail, but the guarantee is normative: identical ids MUST NOT -cause layout corruption or element conflation. - -**INV-8. Separation of concerns.** The rendering concern and the input-parsing +**INV-7. Separation of concerns.** The rendering concern and the input-parsing concern MUST remain independent. Neither MUST depend on the other's state, types, or API surface. They MAY share a compiled WASM binary for loading efficiency, but this is an implementation convenience, not an architectural @@ -374,7 +368,8 @@ open(id: string, props?): OpenElement Creates an element-open directive. The `id` parameter provides an identity for the element within the frame, used by the underlying layout engine for element -tracking and hit-testing. The optional `props` parameter carries configuration +tracking and hit-testing. IDs MUST be unique within a frame; passing duplicate +IDs is undefined behavior. The optional `props` parameter carries configuration for layout, styling, and behavior. Elements opened with `open()` MUST be closed with a corresponding `close()` @@ -473,12 +468,10 @@ transfer mechanism is an implementation detail described in Section 12.1. ### 9.3 Directive identity -Each element directive is assigned an identity within the frame for use by the -underlying layout engine. When multiple elements share the same id (the `id` -parameter to `open()`), the renderer MUST disambiguate their identities -automatically. The disambiguation mechanism is an implementation detail. The -normative requirement is that the caller MUST NOT need to provide globally -unique ids; the renderer handles uniqueness internally. +Each element directive carries an `id` provided by the caller via `open()`. +Element IDs MUST be unique within a frame. The renderer uses the ID directly as +the element's identity for the layout engine. Passing duplicate IDs within a +single frame is undefined behavior. --- @@ -501,10 +494,9 @@ renderer processes directives in the order they appear in the array. ### 10.3 Element identity within a frame -Within a single frame, each element MUST have an unambiguous identity for the -layout engine. As specified in Section 9.3, the renderer handles disambiguation. -Two elements with the same id in the same frame MUST NOT cause layout -corruption, hash collision, or identity conflation. +Within a single frame, each element MUST have a unique identity for the layout +engine. As specified in Section 9.3, element IDs MUST be unique within a frame. +Passing duplicate IDs is undefined behavior. ### 10.4 No cross-frame identity diff --git a/src/clayterm.c b/src/clayterm.c index a9bedf6..81f793c 100644 --- a/src/clayterm.c +++ b/src/clayterm.c @@ -437,7 +437,6 @@ struct Clayterm *init(void *mem, int w, int h) { void reduce(struct Clayterm *ct, uint32_t *buf, int len, int mode, int row) { int i = 0; - uint32_t idx = 0; Clay_BeginLayout(); @@ -454,7 +453,7 @@ void reduce(struct Clayterm *ct, uint32_t *buf, int len, int mode, int row) { if (id_len > 0) { Clay_String str = {.length = (int32_t)id_len, .chars = id_chars}; - Clay_ElementId eid = Clay__HashString(str, idx++); + Clay_ElementId eid = Clay__HashString(str, 0); Clay__OpenElementWithId(eid); } else { Clay__OpenElement(); diff --git a/test/validate.test.ts b/test/validate.test.ts index cb98cf2..8db4af9 100644 --- a/test/validate.test.ts +++ b/test/validate.test.ts @@ -19,16 +19,16 @@ describe("validate", () => { expect(validate([])).toBe(true); }); - it("rejects ops with wrong id", () => { - expect(validate([{ id: 0xff }])).toBe(false); + it("rejects ops with wrong directive", () => { + expect(validate([{ directive: 0xff }])).toBe(false); }); - it("rejects open element missing name", () => { - expect(validate([{ id: 0x02 }])).toBe(false); + it("rejects open element missing id", () => { + expect(validate([{ directive: 0x02 }])).toBe(false); }); it("rejects text missing content", () => { - expect(validate([{ id: 0x03 }])).toBe(false); + expect(validate([{ directive: 0x03 }])).toBe(false); }); it("rejects non-array", () => { @@ -40,7 +40,7 @@ describe("validate", () => { }); it("assert throws TypeError on bad input", () => { - expect(() => assert([{ id: 0x02 }])).toThrow(TypeError); + expect(() => assert([{ directive: 0x02 }])).toThrow(TypeError); }); it("rejects padding > 255 (u8 overflow)", () => { @@ -106,6 +106,6 @@ describe("validated", () => { it("throws on invalid ops", () => { // deno-lint-ignore no-explicit-any - expect(() => term.render([{ id: 0xff }] as any)).toThrow(TypeError); + expect(() => term.render([{ directive: 0xff }] as any)).toThrow(TypeError); }); }); diff --git a/validate.ts b/validate.ts index 1481aa4..248ea48 100644 --- a/validate.ts +++ b/validate.ts @@ -89,13 +89,13 @@ const Floating = Type.Object({ zIndex: Type.Optional(u16), }); -/* ── Op types (discriminated on `id`) ─────────────────────────────── */ +/* ── Op types (discriminated on `directive`) ──────────────────────── */ -const CloseElement = Type.Object({ id: Type.Literal(0x04) }); +const CloseElement = Type.Object({ directive: Type.Literal(0x04) }); const OpenElement = Type.Object({ - id: Type.Literal(0x02), - name: Type.String(), + directive: Type.Literal(0x02), + id: Type.String(), layout: Type.Optional(Layout), bg: Type.Optional(rgba), cornerRadius: Type.Optional(CornerRadius), @@ -105,7 +105,7 @@ const OpenElement = Type.Object({ }); const TextOp = Type.Object({ - id: Type.Literal(0x03), + directive: Type.Literal(0x03), content: Type.String(), color: Type.Optional(rgba), fontSize: Type.Optional(u8),