# Ebox user guide [中文](ebox-user-guide.zh.md) Ebox is the low-level Text/Box layout and buffer-rendering package. This guide uses one public author grammar and keeps framework-integration details separate. See the [API reference](ebox-api-reference.en.md) for the complete function inventory. ## 1. Load Ebox ```elisp (require 'ebox) ``` Loading Ebox does not create a buffer, enable a mode in the current buffer, or build Rust. ## 2. Learn one author model An Ebox document contains Text and Box nodes. The author grammar has exactly seven entries: | Entry | Meaning | | --- | --- | | `"text"` | Short form for one Text node. | | `(text ... "text")` | Text with explicit text properties. | | `(box ... CHILD...)` | A normal visual Box. | | `(row ... CHILD...)` | A Box with simple horizontal child layout. | | `(column ... CHILD...)` | A Box with simple vertical child layout. | | `(flex ... CHILD...)` | A Box with Flex child layout. | | `(grid ... CHILD...)` | A Box with Grid child layout. | Children are always nested directly. A layout form is a Box with a selected child-layout algorithm, not a different kind of visual object. ```elisp (defvar ebox-guide-input (ebox-build '(column :padding (1 2) :border (1 solid "#8A93A6") (text :color "#263244" "Research notes") (row :item-gap 1 (box :id "status" :background-color "#F4F6FB" "Inbox") (box :background-color "#EEF2FF" "Archive"))))) ``` `ebox-build` returns an opaque `CanonicalEboxInput`. It keeps the canonical forest and its source generation together. Pass that value unchanged to the render and publication functions; ordinary author code does not extract or reassemble its internal nodes. Use `box` with geometry but no child when an empty rectangular area is needed. No extra node type is necessary. ## 3. Choose the layout that states the intent Use `box` for ordinary content, `row` or `column` for direct one-axis composition, `flex` when free space or wrapping matters, and `grid` for two-dimensional tracks or explicit placement. ### Row and column `row` and `column` accept `:item-gap` and `:cross-align`. Their children remain ordinary Text or Box nodes. ```elisp (ebox-build '(row :item-gap 2 :cross-align center (box :width 12 "Left") (box :width 12 "Right"))) ``` ### Flex Flex container properties belong to `flex`. Participation properties belong directly to a child `box` because they describe the parent-child relationship. ```elisp (ebox-build '(flex :width (480) :flex-flow (row wrap) :gap (1 (12)) (box :flex (1 1 auto) "Primary") (box :flex-grow 2 "Secondary"))) ``` ### Grid Grid placement is one-based. Tracks may be fixed, `auto`, fractional, `minmax`, or `repeat` values. Placement properties also belong directly to a child `box`. ```elisp (ebox-build '(grid :width (640) :grid-template-columns ((200) (fr 1) (fr 1)) :grid-template-rows (1 1) :gap (1 (12)) (box :grid-column (1 :span 3) "Header") (box :grid-column 1 :grid-row 2 "Navigation") (box :grid-column 2 :grid-row 2 "Main") (box :grid-column 3 :grid-row 2 "Aside"))) ``` ## 4. Use geometry and paint properties Horizontal numbers are character columns; a one-element list such as `(240)` is a pixel width. Vertical numbers are lines. Text accepts only font, foreground/background, and text-decoration properties. Padding, margin, border, size, `:outer`, overflow, visibility, and wrapping policy belong only to Box. Font and color on Box may feed inherited Text facts, but never give Text Box geometry. ```elisp (ebox-build '(box :width (420) :padding (1 2) :margin (0 1) :border (1 solid "#8A93A6") :color "#263244" :background-color "#FFFFFF" "A readable panel")) ``` Use `:outer inline` or `:outer block` to state how a Box participates in its parent. The child-layout algorithm still comes from the form name. ## 5. Render and publish `ebox-render` returns propertized text without changing a live buffer. `ebox-render-to-buffer` mounts a retained TP surface and returns its buffer. Both functions, and `ebox-display-buffer`, accept the opaque value returned by `ebox-build`. ```elisp (ebox-render ebox-guide-input) (ebox-render-to-buffer "*Ebox Guide*" ebox-guide-input) ``` Build a fresh canonical input and use `ebox-commit` for an atomic update: ```elisp (ebox-commit "*Ebox Guide*" (ebox-build '(column :padding (1 2) (text "Updated notes") (box :key body "The new input is caller-owned.")))) ``` Validation, rendering, or publication failure leaves the previous buffer and runtime state intact. `ebox-buffer-update-report` returns a defensive copy of the last successful update report. ## 6. Query and update a mounted surface `:id`, `:class`, and `:key` are author metadata. Selectors use ECSS semantics. Resolve an `:id` to an opaque, surface-scoped handle before a direct update: ```elisp (let ((handle (ebox-region-resolve "*Ebox Guide*" "status"))) (ebox-region-update handle :color "#166534")) ``` `ebox-selector-query-buffer` returns document-ordered matches from a mounted buffer. `ebox-selector-update-buffer` applies one style update to all editable matches. Numeric region ids are diagnostic render metadata, not stable update handles. ## 7. Resize and scroll A finite height plus `:overflow scroll` creates a scroll window. Ebox buffer mode installs keyboard and wheel commands that route through the innermost scroll owner before falling back to ordinary Emacs scrolling. ```elisp (ebox-build '(box :id log :width (420) :height 8 :overflow scroll "line 1\nline 2\nline 3\nline 4\nline 5\nline 6\nline 7\nline 8\nline 9")) ``` Visible mounted buffers follow their display window. Integrations may apply an explicit viewport with: ```elisp (ebox-rerender-buffer-with-context (get-buffer "*Ebox Guide*") 800 30) ``` ## 8. Standalone `.ebox` files A `.ebox` file contains one unquoted structural form: ```elisp (column :padding '(1 2) (text :color "#263244" "Title") (box :width '(240) "Body")) ``` The sibling `ebox-playground` evaluates property expressions before calling `ebox-build`. Quote list and symbol constants in a `.ebox` file, as shown above. When calling `ebox-build` directly, pass inert data as in the earlier examples. ## 9. Typed integration API Frameworks that already normalize author input may bypass the list DSL. This is an integration API, not a second author grammar. One source builder owns the complete source generation. Each TextNode or BoxNode receives an opaque handle from that builder and node-owned facts projected from the same normalized declarations. The framework then seals the builder and transports the forest and source index together as one `CanonicalEboxInput`. ```elisp (let* ((builder (ebox-source-builder-create)) (root-declarations nil) (left-declarations nil) (right-declarations nil) (root-handle (ebox-source-builder-bind builder :declarations root-declarations)) (left-handle (ebox-source-builder-bind builder :declarations left-declarations)) (right-handle (ebox-source-builder-bind builder :declarations right-declarations)) (left (ebox-text-create :value "Left" :source-handle left-handle :owned-facts (ebox-canonical-facts-from-declarations 'text left-declarations))) (right (ebox-text-create :value "Right" :source-handle right-handle :owned-facts (ebox-canonical-facts-from-declarations 'text right-declarations))) (root (ebox-box-create :layout (ebox-row-layout-create :item-gap 1 :cross-align 'center) :children (list left right) :source-handle root-handle :owned-facts (ebox-canonical-facts-from-declarations 'row root-declarations)))) (ebox-canonical-input-create (list root) (ebox-source-builder-finish builder))) ``` `ebox-source-builder-create`, `ebox-source-builder-bind`, `ebox-source-builder-finish`, `ebox-canonical-facts-from-declarations`, the typed node/layout constructors, and `ebox-canonical-input-create` belong to this integration boundary. Here `:layout`, `:children`, `:source-handle`, and `:owned-facts` are evaluated constructor fields. They are not author properties and do not extend the seven-entry grammar. ## 10. Optional native reflow and verification The Rust module is optional and Ebox never builds it while loading: ```elisp (ebox-native-status) (ebox-native-build) ``` From the repository root: ```sh make docs-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs make dsl-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs ```