ebox-playground/README.md

360 lines
20 KiB
Markdown

# ebox-playground
`ebox-playground` is the independent, generic file runner for Ebox DSL examples. It uses only public Ebox APIs; concrete layouts live in readable `.ebox` fixtures under `examples/`. `flex-reference.ebox`, `grid-reference.ebox`, and `size-reference.ebox` demonstrate layout and sizing with a restrained terracotta, sage, blue, violet, ochre, and teal visual language. `interaction-reference.ebox` demonstrates native Ebox interaction and retained updates. `layer-reference.ebox` combines retained overlapping panels with a native anchored menu.
ECSS 0.1.0 and TP 1.0.1 are independent packages and may be installed in either order. Install both before Ebox 2.0.1, then install Ebox Playground 0.1.1. `ebox-playground` never reaches into those packages' private APIs.
Standalone gallery entry points default to a definite 720 px canvas so nested examples have stable space in batch renders and one-window demos. Split-window `.ebox` previews instead resolve `(vw 100)` to the preview window's display-safe width. Section separators use their containing Column's available width, so they also fit a narrower root. `etaf-playground` remains the separate higher-level Component and Runtime showcase.
Standalone and batch fixture renders use the same compact 720 px default viewport unless the caller dynamically binds `ebox-viewport-width`; viewport-based references therefore stay inside the example canvas. When `C-c C-c` renders an `.ebox` source buffer, the command keeps the source on the left, opens the preview on the right, and resolves `(vw 100)` to Ebox's shared display-safe window width. Ebox's single serialized viewport controller publishes every window change immediately through its retained path; the Playground owns no second resize hook or timer.
```elisp
(require 'ebox-playground)
(ebox-playground-open)
```
Open `examples/size-reference.ebox` and press `C-c C-c` for **Ebox Size
Reference**. It demonstrates the six length units on their permitted axes, the supported keyword values
for all six size constraints, `calc/min/max/clamp`, and combinations with fonts,
spacing, Flex, and Grid. Resize the preview to compare percentages with viewport
units and see fluid sizes change. Precision and buffer-renderer limitations are
stated in the example rather than presented as browser-equivalent behavior.
Constrained long-text samples separate intrinsic widths; Row samples separate
`auto` from `stretch`. Height content keywords are explicitly marked equivalent
in the current renderer. Shared low/middle/high inputs show how `min`, `max`,
and `clamp` respond at their boundaries.
Solid rectangles mark the measured Box's full extent, including empty space;
pale rulers show available parent width, while white cards contain the labels.
Run `make size-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs` to check
the core size contracts and the actual example declarations, known arithmetic
results, painted sizing differences, complete rendering, and retained viewport updates. `make check` also
includes the example's regression tests.
## ORBIT spatial desktop and layer lab
`examples/layer-minimal.ebox` is the smallest static overlap example. The complete
lab opens with **ORBIT / CREATIVE OS**, a dark spatial desktop with violet,
ice-blue, and acid-green cards, offset shadow slabs, nested geometric artwork,
and floating badges. It uses the same generic runner and a companion for
application state:
```elisp
(ebox-playground-open-file
(expand-file-name "examples/layer-reference.ebox"
ebox-playground-directory))
```
Visit the `.ebox` file and press `C-c C-c` for the source/preview split. Every
control supports click, `RET`, and `SPC`, with native help and hover paint.
All text uses one font size.
- **EXPLODE STACK** spreads the three cards apart and returns them to their
original positions without moving the following sections.
- **CYCLE FOCUS** brings the studio, task card, and player to the front in turn.
- **SWITCH MOOD** changes the card palette between lunar violet/ice/lime and
solar pink/apricot/mint. Each preview has independent state.
- **UPDATE COVERED NOTE** changes a note fully covered by Panel A. Update it
twice, then **TOGGLE PANEL A** to reveal revision 02. The revealed note has
its own native action and live help; covering it never removes its logical
data or bindings.
- **MOVE PANEL A** changes `:left` and `:top` without adding flow rows.
**RAISE / LOWER A** changes `:z-index` between 2 and 4 while Panel B stays at
3. Opaque panel content and padding replace the lower layer's visible paint.
- The relative marker reserves its original flow row and paints one row lower.
Each parent forms a local stacking group automatically; no isolation
declaration is needed.
- **OPEN / CLOSE ROOT MENU** shows a menu whose logical owner clips to one
row. The nested absolute menu declares `:layer root` and anchors to the
trigger's semantic ID, so it can paint beyond that local clip.
**CYCLE PLACEMENT** visits `bottom-start`, `bottom-end`, `top-start`, and
`top-end`; **MOVE ANCHOR** moves the trigger and its open menu together.
Choosing sage or amber updates the result and closes the menu.
`layer-reference.ebox` contains layout data and state references;
`layer-reference.el` owns state and business actions. It uses only public
`ebox-keymap-create`, `ebox-help-create`, and `ebox-region-update` APIs.
Offsets use inline `ch` and block `lh` units. The text renderer composes whole
rows, and positioned content must share its host's line height.
Run the batch checks without opening GUI windows:
```sh
make layer-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
```
This target runs core layer contracts and the actual demo's native commands,
covered updates, movement, stacking, all four anchor placements, cross-buffer
mouse dispatch, and independent previews. After each command, the tests compare
the mounted output with a fresh render of the committed snapshot, including
paint and native capabilities. `make check` includes the Playground tests.
For repeated command timing, use the existing interaction evaluator with
`"examples/layer-reference.ebox"` and a toolbar label such as
`"UPDATE COVERED NOTE"`; see the invocation below.
<a id="native-interaction-lab"></a>
## Native interaction lab
Open the interaction reference with the existing file runner:
```elisp
(require 'ebox-playground)
(ebox-playground-open-file
(expand-file-name "examples/interaction-reference.ebox"
ebox-playground-directory))
```
You can also visit `examples/interaction-reference.ebox` and press `C-c C-c`
for a source/preview split. **Ebox Native Interaction Lab** first separates
help text and pointer shapes from hover paint, then combines `:help-echo`,
`:pointer`, `:hover-style`, and `:keymap` in a live counter and event log.
- Hover the bordered cards, including their padding, to compare static help,
dynamic help, native pointers, and color/text-decoration changes.
- Click **ADD / [+]** to add or the separate **[-]** Text to subtract. Move
point into an action to use `RET` / `SPC`, or use its `+` / `-` shortcuts.
**SWITCH STEP 1 / 5** replaces its keymap with commands for the chosen step.
- **SWITCH HOVER PALETTE** replaces hover paint. **REMOVE / RESTORE** clears
all four capabilities with explicit `nil`, then restores them. The Text
reset action resets the count.
- **UPDATE STATUS GROUP** updates the three `.demo-status` Boxes together
through one public `ebox-update-selector` call.
- The nested surface demonstrates parent coverage, a Text child with its own
commands, and an explicit-`nil` child that blocks enclosing capabilities.
The event log records which command ran.
Box help, pointer, and keymap include content, padding, and border, excluding
that Box's margin and structural newlines. A hover owner's text and padding
share one native `mouse-face` within a rendered line; unspecified attributes
use that owner's base style, including when child text normally differs.
Physical left/right borders are excluded from hover; horizontal border strokes
keep their paint. Native pointer appearance and help display depend on Emacs
settings and the window system. See the
[public capability contract](../ebox/docs/user/ebox-api-reference.en.md#native-node-capabilities).
`interaction-reference.el` owns the business functions and state;
`interaction-reference.ebox` owns the layout. The runner reloads the matching
`.el` before each preview. Ebox's `ebox-help-create` supplies the native help
adapter; `ebox-keymap-create` binds
zero-argument callbacks and updates named regions with
`ebox-region-update`. Mouse callbacks automatically run in the event window's
buffer. Ebox adds no application state or focus navigation system; use ordinary
point motion for keyboard bindings. Keymaps are snapshotted, so publishing new
bindings requires replacing `:keymap`. Each evaluation creates a fresh demo.
Run the interaction checks from this directory:
```sh
make interaction-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
```
The target verifies the core interaction contracts and this fixture's rendered
capabilities, command effects, replacements, and removal/restoration.
`make check` also includes the Playground interaction regression tests.
For repeated-key behavior and command timing, use the shared interaction
evaluator:
```sh
emacs -Q --batch -L . -L ../ebox -L ../ecss -L ../tp \
-l scripts/ebox-playground-flex-resize-evaluator.el \
--eval '(pp (ebox-playground-interaction-evaluator-run "examples/interaction-reference.ebox" "ADD " "RET" 30 720 200))'
```
The signature is `(FILE LABEL &optional KEY STEPS WIDTH HEIGHT)`. It opens a
fresh temporary preview, finds the visible label once, and repeats native key
lookup at the resulting point without repositioning after updates. Losing the
node's binding fails the run. `KEY` accepts a key-description string or event
vector; defaults are `RET`, 30 commands, 720 px width, and 200 lines of height.
Sample count and dimensions must be positive integers.
The returned plist reports mean/p95/max command milliseconds, GC count/time,
allocated cons cells (`:cons-cells`, to track allocation pressure),
publication count, final plain text, and parity with an independent render of
the committed snapshot. Work counters `:layout-calls`, `:surface-plans`,
`:fragment-scans`, and `:node-registrations` count calls to layout rendering,
surface planning, fragment extraction (including local slices), and node-tree
registration. `:fragment-characters` sums the characters passed to fragment
extraction. Read the call counts alongside this character total to distinguish
local work from full-surface scanning; call counts are not affected-node counts.
A parity mismatch fails.
Timing includes synchronous command execution, publication, and GC. Timings,
GC, publication counts, and work counters cover only the command loop;
initial mounting and the final parity render are excluded. The evaluator
excludes GUI redisplay, OS key-repeat
latency, and idle prewarming. The sampling loop checks a 30-second deadline
between commands. Temporary preview and owned timer cleanup runs on success,
error, timeout, or interruption, preserving existing previews and windows.
Reuse this entry point for interaction regressions instead of ad hoc key-repeat
timing scripts.
For native click dispatch in an already visible preview, load the same evaluator
and call `(ebox-playground-interaction-evaluator-click BUFFER LABEL &optional OFFSET)`.
`BUFFER` is a live buffer or its name; the first matching label must be visible
in a displaying window. `OFFSET` is a zero-based integer within the label and
defaults to zero. For example, evaluate from the preview buffer:
```elisp
(load (expand-file-name "scripts/ebox-playground-flex-resize-evaluator.el"
ebox-playground-directory) nil t t)
(ebox-playground-interaction-evaluator-click (current-buffer) "ADD " 0)
(ebox-playground-interaction-evaluator-click (current-buffer) "[-]" 1)
```
This constructs an Emacs mouse event, resolves the native binding, and uses
`call-interactively`; it does not inject an OS mouse click. The helper does not
select a window or move point itself. It returns the clicked position; check
the counter or event log separately to verify the business result.
## Author and render `.ebox` files
The package also owns the `.ebox` file boundary. Visiting an Ebox DSL file selects `ebox-dsl-mode`; its header line shows `C-c C-c Render preview`. Press `C-c C-c` to evaluate the file's single Elisp expression, build its resulting layout data through `ebox-build`, and automatically display the source and preview side by side.
```elisp
(add-to-list 'auto-mode-alist '("\\.ebox\\'" . ebox-dsl-mode))
```
Write the same expression in `.ebox` that you would pass as the argument to
`(ebox-build ...)` in Elisp. Quote a whole static layout; its list and symbol
properties do not need additional quotes:
```elisp
'(column :padding ((lh 1) (ch 2))
:cross-align center
(box :width (px 240) "Hello"))
```
Keep necessary business functions and variables in an optional same-basename
`.el` file beside the `.ebox`. Before each preview evaluation, the runner loads
that exact `.el` source if it exists, then evaluates the `.ebox` expression.
This applies to `C-c C-c`, `ebox-playground-open-file`, and file rendering;
saved `.el` edits are loaded on the next preview without `require` caching or
selecting stale bytecode. A missing companion is allowed; a companion load error
stops evaluation. Ordinary Elisp variable definitions control state retention
or reset on reload.
For example, put the data and action in `notes.el`:
```elisp
;;; notes.el --- Notes example behavior -*- lexical-binding: t; -*-
(defvar notes-body "A long article.")
(defun notes-help ()
"Return business help for the article."
(format "Article: %s" notes-body))
(defun notes-activate ()
"Mark the article in the current Ebox buffer."
(ebox-region-update "article" :color "#166534"))
```
Keep `notes.ebox` focused on the layout:
```elisp
`(column :padding ((lh 1) (ch 2))
(box :id "article" :width (px 480)
:help-echo ,(ebox-help-create #'notes-help)
:pointer hand
:keymap ,(ebox-keymap-create :activate #'notes-activate)
,notes-body))
```
`ebox-help-create` adapts a zero-argument business function returning a string
or `nil` to native help with the hovered buffer as context. `ebox-keymap-create`
handles `RET`, `[return]`, `SPC`, and `[mouse-1]` through
`:activate`. Its optional `:bindings` alist pairs key-description strings or
event vectors with zero-argument callbacks. Raw native keymaps are also accepted.
See the [public helper contract](../ebox/docs/user/ebox-api-reference.en.md#native-node-capabilities).
The runner evaluates one `.ebox` expression with lexical bindings on each
render. Backquote preserves layout data, `,` inserts a value, and `,@` inserts a
list of children. Properties and children in the result are data; the runner
does not evaluate them separately. Keep business definitions in the companion;
use Ebox's public adapters for native help and command plumbing, and do not add
helpers merely to hide repeated DSL property lists.
Prefer concise DSL: omit redundant defaults, put common inheritable text
styles on an existing parent, and write child overrides only. Keep explicit
values in panels that teach those properties. The [authoring rules](../ebox/docs/user/ebox-user-guide.en.md#prefer-defaults-and-inheritance)
explain which properties inherit. Empty Boxes have zero automatic content
height; give one-line separators an explicit `:height (lh 1)`.
For example simplifications, compare a saved baseline directory with the
current `examples` directory using the existing evaluator:
```sh
emacs -Q --batch -L . -L ../ebox -L ../ecss -L ../tp \
-l scripts/ebox-playground-flex-resize-evaluator.el \
--eval '(ebox-playground-compare-example-directories "/path/to/baseline" "examples")'
```
The optional third and fourth arguments select viewport widths and file names.
Defaults cover both references at 720 and 1000 px, including their complete
documents. The comparison checks text, effective faces, display and interaction
properties, and line widths; a mismatch fails the batch command. It creates no
files or GUI windows and supersedes one-off comparison probes for this task.
For scrolling regressions across examples, use the same evaluator's generic
scroll entry point:
```sh
emacs -Q --batch -L . -L ../ebox -L ../ecss -L ../tp \
-l scripts/ebox-playground-flex-resize-evaluator.el \
--eval '(ebox-playground-scroll-evaluator-run (directory-files "examples" t "\\.ebox$") 721 20 45)'
```
The arguments are one file path or a list of paths, viewport width in pixels,
sample count, and viewport height in lines. Each file needs a scrollable root.
Content is fully cached before alternating one-line motions at the top and
midpoint. Results report mean/p95/max milliseconds, full root renders, node style
visits, style computations, and fresh-render parity; incomplete motion or changed
output fails the run. The independent render comparison ignores runtime identity
and zero-pixel spacers beside identical font faces; height carriers are retained.
This measures retained TP scroll publication, excluding
native window scrolling, idle prewarming, and redisplay. To compare GUI font
metrics, load this evaluator and call the same function in the existing GUI
Emacs. It uses undisplayed temporary buffers, preserves live previews and windows,
and cleans its mounts, advice, and timers even on errors or interruption. This
entry point supersedes temporary per-example scroll timing probes.
Size data uses `(unit number)` with `px`, `%`, `vw`, `vh`, `ch`, and `lh`, plus
`calc`, `min`, `max`, and `clamp`. The [shared unit-constraint
table](../ebox/docs/user/ebox-user-guide.en.md#size-unit-constraints) defines which
units each property accepts; the axes cannot be mixed, including in nested
function branches. Ebox rejects invalid combinations when it builds the tree.
For example, the preview can stay one line
shorter than the viewport with `:height (calc (- (vh 100) (lh 1)))`; the
expression is resolved by Ebox on each layout. `fit-content` stays a bare
keyword and does not take an argument. Border thickness has its own paint-unit
rule in the same table. Ebox's buffer backend quantizes
vertical content to complete lines. See the [Ebox sizing
contract](../ebox/docs/user/ebox-user-guide.en.md#4-use-geometry-and-paint-properties).
To migrate an old unquoted `.ebox` layout, quote the whole static list and
remove the quotes around its list and symbol properties, for example
`:padding '(1 2)` becomes `:padding ((lh 1) (ch 2))`. For dynamic values, use
backquote and an explicit comma: a computed pixel width becomes
`:width (px ,(+ 200 40))`. Bare lengths, singleton pixel lists, and the old
viewport keywords must also be migrated. The runner does not auto-detect the
legacy format.
The reference fixtures live directly under `examples/`. For example:
```elisp
(ebox-playground-open-file
(expand-file-name "examples/grid-reference.ebox"
ebox-playground-directory))
```
Run `make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs` from this directory.
Run `make performance` for the committed Flex resize gate. It prepares ECSS,
TP, Ebox, Playground bytecode, and the Rust module, then renders a read-only
`HEAD:examples/flex-reference.ebox` snapshot without touching the working-tree
fixture. The GUI driver performs five warmups followed by 30 accepted viewport
updates; requested and published widths must match exactly, and both p95 and max
must remain at or below 50ms. For uncontaminated repeated samples, run
`make performance-prepare`, let compilation activity settle, then run
`make performance-evaluator` without rebuilding.