Skip to content

Commit 033bc12

Browse files
committed
spec(renderer): pointer shape output gated on the pointerShape capability
Adds renderer-spec §7.9 and wires it into the existing capability sections instead of a standalone feature spec: pointer shape is one more consumer of RuntimeCapabilities, gated the same way as the rest. - §7.9: open() gains pointerShape (kitty/CSS shape names; anything else is ignored and never reaches output). While capabilities.pointerShape is true, the last declaring element in hit-test order wins (innermost; capture-mode floats hide what is beneath, passthrough floats defer to it). Changes are appended to result.output as OSC 22 ; <shape> ST, so the host keeps writing one buffer (cowboyd on #101, replacing #101's separate result.cursor). There is no createTerm opt-in: the capability is the only gate. - Restore (dreyfus92 on #101): set-only OSC 22 cannot pop, so the Term sets "default" when the pointer leaves or is omitted, and update() returns the reset when the capability is withdrawn. Resize keeps the emitted shape since the terminal pointer is unaffected. - §7.3: output "may be" a view, since a frame carrying OSC 22 is assembled in TypeScript. - §7.7, §7.8, §11.2, §12.2 and terminfo-spec §6.3/§9.3 point at §7.9 instead of a "focused feature specification". terminfo-spec §6.3 documents the escape hatch for set-only terminals that never answer the query: term.update([{ type: "capability", key: "pointer-shape", value: true }]). "pointerShape" rather than "cursor": cursor already names the hardware text cursor (§7.6), and pointerShape matches the capability key and RuntimeCapabilities field.
1 parent 2c6215c commit 033bc12

2 files changed

Lines changed: 74 additions & 22 deletions

File tree

‎specs/renderer-spec.md‎

Lines changed: 67 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -287,10 +287,10 @@ The render transaction produces ANSI bytes as a `Uint8Array`. These bytes:
287287
The output reflects the complete visual state of the frame. The caller SHOULD
288288
write the output to the terminal without modification.
289289

290-
The output `Uint8Array` is a view over renderer-owned memory. It is valid until
291-
the next `render()` or `update()` call on the same Term instance, at which point
292-
the buffer may be reused or detached (§7.7). Callers who need to retain the
293-
output beyond that point MUST copy it.
290+
The output `Uint8Array` may be a view over renderer-owned memory. It is valid
291+
until the next `render()` or `update()` call on the same Term instance, at which
292+
point the buffer may be reused or detached (§7.7). Callers who need to retain
293+
the output beyond that point MUST copy it.
294294

295295
### 7.4 Lifecycle
296296

@@ -449,9 +449,9 @@ be a no-op for that step.
449449

450450
**Capability semantics.** A `CapabilityEvent` step folds the event's `key` and
451451
`value` into the Term's private `RuntimeCapabilities`. The foundation update
452-
transaction does not emit bytes or alter renderer output as a consequence of a
453-
capability event. A focused capability specification MUST define any output
454-
invalidation or immediate bytes required by its consumer.
452+
transaction emits bytes or alters renderer output as a consequence of a
453+
capability event only where a capability section of this specification says so
454+
(§7.9).
455455

456456
**Return value.** `update()` returns a `Uint8Array` of bytes to write to the
457457
terminal immediately. An empty array is valid when the update changes no
@@ -469,16 +469,61 @@ is valid until the next `render()` **or** `update()` call).
469469

470470
### 7.8 Capability consumption
471471

472-
The renderer may consume the read-only capability snapshot maintained by
473-
`Term.update()`, but this foundation specification does not define any
474-
capability-specific output. Protocols that change emitted bytes MUST add their
475-
own renderer section, capability evidence, invalidation rules, and tests in a
476-
focused feature specification.
477-
478-
In particular, color encoding, synchronized-output wrapping, pointer-shape
479-
output, Kitty keyboard mode setup, and Kitty graphics emission are deferred to
480-
their respective follow-up PRs. The renderer continues to emit its existing
481-
hardcoded ANSI output until one of those specifications is adopted.
472+
The renderer consumes the read-only capability snapshot maintained by
473+
`Term.update()`. A protocol that changes emitted bytes MUST define its gating
474+
capability, output, and invalidation rules in its own section of this
475+
specification. Pointer shape (§7.9) is the first.
476+
477+
Color encoding, synchronized-output wrapping, Kitty keyboard mode setup, and
478+
Kitty graphics emission are deferred to their respective follow-up PRs. The
479+
renderer continues to emit its existing hardcoded ANSI output until each is
480+
specified.
481+
482+
### 7.9 Pointer shape
483+
484+
An `open()` directive MAY declare a `pointerShape` property naming the mouse
485+
pointer shape to show while the pointer is over the element. Values are the
486+
kitty pointer shape names (the CSS `cursor` keywords): `default`, `none`,
487+
`context-menu`, `help`, `pointer`, `progress`, `wait`, `cell`, `crosshair`,
488+
`text`, `vertical-text`, `alias`, `copy`, `move`, `no-drop`, `not-allowed`,
489+
`grab`, `grabbing`, `e-resize`, `n-resize`, `ne-resize`, `nw-resize`,
490+
`s-resize`, `se-resize`, `sw-resize`, `w-resize`, `ew-resize`, `ns-resize`,
491+
`nesw-resize`, `nwse-resize`, `zoom-in`, `zoom-out`. Any other value MUST be
492+
treated as absent and MUST NOT reach the output. The property does not affect
493+
layout or cell output.
494+
495+
The gate is `RuntimeCapabilities.pointerShape` (see
496+
[Terminfo Specification](terminfo-spec.md) §6.3). While it is `false`, the
497+
renderer MUST NOT read `pointerShape` properties and MUST NOT emit OSC 22.
498+
499+
**Resolved shape.** While the capability is `true`, each render resolves one
500+
shape:
501+
502+
- `default` when the render options carry no `pointer`.
503+
- Otherwise, the `pointerShape` of the last declaring element in hit-test order
504+
(the order behind `pointerenter` events, §12.4), or `default` when none
505+
declares one. Ancestors precede descendants, so the innermost element wins. A
506+
floating element in `"capture"` mode hides the elements beneath it; one in
507+
`"passthrough"` mode lets them decide.
508+
509+
Elements inside a `snapshot()` participate as direct directives would.
510+
511+
**Output.** The Term tracks the shape it last emitted, starting at `default`.
512+
When the resolved shape differs, the render MUST append `ESC ] 22 ; <shape> ST`
513+
to `output` after the frame's cell bytes and record the new shape. Otherwise it
514+
MUST NOT emit OSC 22.
515+
516+
**Restore.** OSC 22 set cannot pop, so the Term restores by setting `default`:
517+
518+
- A render that resolves `default` emits it per **Output** above.
519+
- An update step that changes `pointerShape` from `true` to `false` while the
520+
emitted shape is not `default` MUST return `ESC ] 22 ; default ST` and record
521+
`default`.
522+
- A resize step leaves the emitted shape unchanged.
523+
524+
To restore before exit, the caller renders a frame without `pointer`, or passes
525+
`{ type: "capability", key: "pointer-shape", value: false }` to `update()`, and
526+
writes the result.
482527

483528
---
484529

@@ -816,8 +861,9 @@ declaration is present, the cursor-positioning and cursor-visibility sequences
816861
specified in §7.6.
817862

818863
Capability-specific output modes are not terminal-state management owned by the
819-
foundation renderer. A focused feature specification must define their state
820-
boundaries separately.
864+
foundation renderer. Each capability section of §7 defines its own state
865+
boundary. The mouse pointer shape (§7.9) is distinct from the cursor shape
866+
above, which remains caller-managed.
821867

822868
### 11.3 The renderer does not own application lifecycle
823869

@@ -903,6 +949,8 @@ The `open()` constructor currently accepts the following property groups in its
903949
reference, attach target, structured attach points, pointer capture mode, clip
904950
target, z-index)
905951
- **`scroll`** — scroll container configuration
952+
- **`pointerShape`** — mouse pointer shape while hovered (§7.9); not transferred
953+
to the WASM module
906954

907955
The `floating` object shape is:
908956

‎specs/terminfo-spec.md‎

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -284,7 +284,11 @@ per field the reply carries. OSC 21 arriving alongside OSC 10/11/12 replies does
284284
not merge; each response yields its own event.
285285

286286
**OSC 22.** An OSC 22 reply sets `key: "pointer-shape", value: true`. The shape
287-
name in the reply is discarded in v1; only protocol support is recorded.
287+
name in the reply is discarded in v1; only protocol support is recorded. The
288+
renderer emits pointer shapes only while `pointerShape` is `true` (Renderer
289+
Specification §7.9). Terminals that implement OSC 22 set but not its query never
290+
reply; a caller with outside knowledge supplies the evidence itself with
291+
`term.update([{ type: "capability", key: "pointer-shape", value: true }])`.
288292

289293
**DA1.** The DA1 reply is recognized internally as the probe fence and MUST NOT
290294
surface as a `CapabilityEvent`.
@@ -422,8 +426,8 @@ and mouse events in arrival order. The host loop routes them to `term.update()`.
422426
423427
A capability event can arrive after the renderer has already emitted frames.
424428
`term.update()` folds it into the runtime snapshot. Any renderer-side output
425-
invalidation or immediate bytes are defined by the focused feature specification
426-
that consumes that capability.
429+
invalidation or immediate bytes are defined by the renderer section that
430+
consumes that capability (Renderer Specification §7.8).
427431
428432
---
429433

0 commit comments

Comments
 (0)