Skip to content

Commit 32deb34

Browse files
cowboydnatemoo-re
authored andcommitted
📝 Update render spec to include clipping behavior
1 parent b47c1ed commit 32deb34

1 file changed

Lines changed: 70 additions & 2 deletions

File tree

specs/renderer-spec.md

Lines changed: 70 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -294,6 +294,43 @@ Creation of a Term is asynchronous because it may involve WASM module
294294
preparation. A Term instance MAY be used for any number of render transactions.
295295
The Term retains its cell buffers across frames for diffing purposes.
296296

297+
### 7.5 Clip semantics
298+
299+
An element whose `props` include a `clip` group declares a **clip region**: a
300+
rectangular bound on the cells its descendants are permitted to write. Cells
301+
produced by descendants that fall outside this region MUST be suppressed from
302+
the output. The clip region is determined by the element's computed layout box
303+
and the axes selected by the `clip` group (`horizontal`, `vertical`, or both).
304+
305+
Clip regions stack. When clip elements nest:
306+
307+
- The effective clip region of an element MUST be the intersection of its own
308+
declared region with the effective clip region of its nearest clipping
309+
ancestor, if any.
310+
- When the renderer finishes processing a clip element's subtree, it MUST
311+
restore the effective clip region of that element's clipping ancestor. Later
312+
siblings drawn within an ancestor clip MUST therefore remain bounded by that
313+
ancestor.
314+
- A `clip` element whose declared region is fully outside its ancestor's
315+
effective region produces an empty effective region; descendants of that
316+
element MUST NOT contribute any cells to the output.
317+
318+
The renderer MAY impose an implementation-defined limit on the depth of clip
319+
regions it can track. The limit itself is not normatively bounded. When a frame
320+
nests clip regions more deeply than the renderer can track:
321+
322+
- All clip regions whose entry the renderer successfully tracked MUST continue
323+
to be honored for the remainder of the frame, including for siblings drawn
324+
after the over-deep subtree closes. The renderer MUST maintain push/pop
325+
symmetry so that exiting an untracked clip does not disturb any ancestor's
326+
effective region.
327+
- Content drawn inside an untracked clip region MUST remain bounded by the
328+
deepest successfully-tracked ancestor clip region. The untracked region's own
329+
additional restriction MAY be lost.
330+
- The renderer MUST surface the condition via the render result's error channel
331+
(see §12.3) before returning, so the caller can detect that some clipping was
332+
not applied.
333+
297334
---
298335

299336
## 8. Public Rendering API
@@ -648,7 +685,10 @@ The `open()` constructor currently accepts the following property groups in its
648685
color
649686
- **`cornerRadius`** — per-corner radius values, producing rounded box-drawing
650687
characters
651-
- **`clip`** — clip region configuration for scroll containers
688+
- **`clip`** — Declares the element as a clip region (see §7.5). Currently
689+
accepts `horizontal: boolean` and `vertical: boolean` axis selectors.
690+
Originally added for scroll containers; nesting and standalone use are
691+
supported.
652692
- **`floating`** — floating-element configuration (offset, expansion, parent
653693
reference, attach target, structured attach points, pointer capture mode, clip
654694
target, z-index)
@@ -770,7 +810,8 @@ The `errors` field contains any errors reported by the Clay layout engine during
770810
the most recent `render()` call. Each error is a `ClayError` object with:
771811

772812
- `type`: a string identifying the error category. The following types are
773-
defined, matching Clay's error taxonomy:
813+
defined. Most mirror Clay's error taxonomy; `"CLIP_DEPTH_EXCEEDED"` is
814+
Clayterm-specific.
774815
- `"TEXT_MEASUREMENT_FUNCTION_NOT_PROVIDED"`
775816
- `"ARENA_CAPACITY_EXCEEDED"`
776817
- `"ELEMENTS_CAPACITY_EXCEEDED"`
@@ -780,6 +821,9 @@ the most recent `render()` call. Each error is a `ClayError` object with:
780821
- `"PERCENTAGE_OVER_1"`
781822
- `"INTERNAL_ERROR"`
782823
- `"UNBALANCED_OPEN_CLOSE"`
824+
- `"CLIP_DEPTH_EXCEEDED"` — A frame nested clip regions more deeply than the
825+
renderer could track. See §7.5 for the guarantees that still hold in this
826+
case. The `message` SHOULD identify the renderer's tracking limit.
783827
- `message`: a human-readable string describing the error in detail.
784828

785829
Errors are collected per-render; each call to `render()` returns only the errors
@@ -852,6 +896,30 @@ background color.
852896
accumulates per-cell direction bitmasks and resolves them to correct box-drawing
853897
junction glyphs in a post-render pass.
854898

899+
**Clip stack.** Section 7.5 requires the effective clip region of a nested
900+
`clip` element to be the intersection of its declared region with its clipping
901+
ancestor's effective region. The underlying layout engine (Clay) emits
902+
per-clip-element bounding boxes that are not pre-intersected with any ancestor's
903+
clip, so the renderer maintains an internal stack of effective clip rectangles:
904+
it pushes the intersected rect on each clip-region entry and pops on exit. The
905+
stack capacity is a small fixed value sufficient for realistic UIs; depth beyond
906+
that is handled per §7.5 (prior clips honored, the over-deep level coalesced
907+
into its deepest tracked ancestor, and a `"CLIP_DEPTH_EXCEEDED"` error
908+
surfaced).
909+
910+
Upstream Clay may eventually flatten nested clip emission so renderers only need
911+
single-rect handling; see
912+
[nicbarker/clay#466](https://github.com/nicbarker/clay/issues/466) (the
913+
underlying issue),
914+
[nicbarker/clay#485](https://github.com/nicbarker/clay/pull/485) (in-flight
915+
Clay-side fix), and
916+
[nicbarker/clay#87](https://github.com/nicbarker/clay/issues/87) (renderer
917+
guidance). When upgrading Clay, check whether a single clip element now produces
918+
multiple `SCISSOR_START`/`SCISSOR_END` pairs across its lifetime (one per
919+
nesting transition rather than just an outer pair); if so, the renderer-side
920+
stack can be removed and replaced with a single rect storing Clay's bounding box
921+
directly.
922+
855923
---
856924

857925
## 14. Deferred / Future Areas

0 commit comments

Comments
 (0)