22 KiB
Ebox current implementation reference
This is the maintainer entry point for the standalone Ebox repository. It describes the package boundary, active files, runtime model, invariants, and verification commands. The historical Ebox checkout is a separate legacy source tree; it is not a dependency of this package. ETAF is the sibling higher-level package.
Reading order
- Read
AGENTS.mdfor repository rules. - Read
README.mdfor installation and the public boundary. - Read
docs/user/ebox-user-guide.en.mdfor public construction patterns. - Read
docs/user/ebox-api-reference.en.mdfor the complete public function/property/configuration inventory. - Read this document for ownership and verification.
- Read
docs/maintainer/ebox-incremental-update-contract.en.mdbefore changing publication or patch planning. - Read
docs/maintainer/ebox-performance-architecture-analysis.en.mdbefore changing performance, retained surfaces, or incremental data flow.
Active source map
| File | Owns |
|---|---|
ebox.el |
Public facade, construction helpers, rendering, TP-backed buffer entry points, scrolling, commit, and byte compilation. |
ebox-cache.el |
Measurement/render cache records, invalidation, and cache reports. |
ebox-source.el |
Opaque source handles, immutable author source records, and candidate source indexes. |
ebox-runtime-index.el |
Compressed persistent radix maps and legacy hash-table adapters. |
ebox-state-contract.el |
Closed retained-state ownership inventory and read-only compatibility-mirror rebuild probes. |
ebox-style.el |
ECSS property schemas, declarations and cascade, shorthand expansion, computed style, colors, borders, and dirty effects. |
ebox-interaction.el |
Native help and keymap adapters with target-buffer context, validation and owned copies of the four explicit node capabilities, restricted hover paint compilation, and native surface-property projection with nested explicit-nil precedence. |
ebox-size.el |
Pure explicit-unit validation, dimensional arithmetic, dependency discovery, and fractional-pixel resolution from injected measurements. |
ebox-tree.el |
Node traversal, logical child access, ECSS subject adaptation, identity, parent paths, keys, and tree snapshots. |
ebox-child-range.el |
Immutable weighted segment trie, sparse persistent key trie, Range replacement, and Gate A metrics. |
ebox-measure.el |
Display-sensitive character/face/pixel measurement and measurement caches. |
ebox-fragment.el |
Layout fragments, signatures, snapshots, spans, and dirty-kind facts. |
ebox-render-context.el |
Render-local values and the injected candidate/materialization input port. |
ebox-layout.el |
Box, row, column, stack, concatenation, spacer, wrapping, and common formatting context. |
ebox-flex.el |
Flex normalization, lines, free-space distribution, and flex rendering. |
ebox-grid.el |
Tracks, implicit tracks, fractions, minmax/repeat, gap, placement, span, and alignment. |
ebox-surface.el |
Candidate identity, projection to TP surface plans, retained mount/update, and the rollback-capable Ebox runtime-state participant. |
ebox-buffer-backend.el |
Propertized render-string construction, display spaces/borders, and existing-slot shaping. |
ebox-patch-plan.el |
Pure tentative-operation antichain planning from immutable parent facts. |
ebox-incremental.el |
Runtime state, snapshots, dirty planning, owner escalation, pure commit preparation, and reports. |
ebox-dsl.el |
Data-oriented .ebox forms and lowering to public nodes. |
ebox-selector.el |
CSS-like string parsing to ECSS structured selector ASTs, indexed candidate lookup, and tree/runtime query handles. |
ebox-spi.el |
Additive immutable framework SPI v2 provider and initial/update operation descriptors. |
ebox-native-reflow.el |
Optional native module loading/build commands, ABI checks, bounded sessions, and Elisp fallback. |
The package intentionally does not include application Components, UI controls, reactive data, or a playground implementation. Those are sibling-package responsibilities.
The active contract also covers Makefile, .github/workflows/ci.yml, tests/ebox-core-render-tests.el, tests/ebox-state-contract-tests.el, tests/ebox-layout-boundary-tests.el, tests/ebox-patch-plan-tests.el, tests/ebox-style-schema-tests.el, tests/ebox-spi-tests.el, tests/ebox-child-range-tests.el, tests/ebox-grid-tests.el, tests/ebox-commit-tests.el, tests/ebox-surface-tests.el, tests/ebox-dsl-tests.el, tests/ebox-flex-tests.el, tests/ebox-selector-tests.el, tests/ebox-package-tests.el, tests/ebox-visual-check-tests.el, tests/ebox-docs-contract-tests.el, tests/ebox-ci-contract-tests.el, native/Cargo.toml, native/Cargo.lock, native/build.rs, native/vendor/emacs-30/emacs-module.h, native/src/lib.rs, native/src/layout.rs, native/c/ebox_module.c, scripts/ebox-package-lint.el, scripts/ebox-visual-check.el, and scripts/ebox-performance-evaluator.el.
Runtime index behavior is covered by tests/ebox-runtime-index-tests.el.
tests/ebox-display-tests.el covers render-before-display ordering, optional
native display actions, and preservation of unrelated windows and selection.
make display-tests runs it without operating a user's GUI instance.
The source distribution boundary includes LICENSE, release-dependencies.json,
scripts/ebox-release.py, scripts/ebox-release-smoke.el, and
scripts/test_ebox_release.py. The first tool owns locked dependency retrieval,
deterministic archives, and isolated installation; the Lisp smoke checks run
inside that installation rather than loading sibling checkouts.
Native node capabilities are covered by tests/ebox-interaction-tests.el through
make interaction-tests. The interactive reference and its command regressions
live in the sibling Playground package.
CSS sizing is covered by tests/ebox-size-tests.el,
tests/ebox-size-style-tests.el, tests/ebox-css-layout-tests.el,
tests/ebox-css-flex-grid-tests.el, tests/ebox-css-viewport-tests.el, and
tests/ebox-size-native-tests.el.
tests/ebox-zero-height-tests.el covers empty automatic geometry and explicit
blank-line sizing through the same make size-tests entry point.
The supported literal migration tool is scripts/migrate-css-sizes.py;
scripts/test_migrate_css_sizes.py verifies its source-preserving rewrites and
ambiguous-input reports.
Runtime model
The normal data flow is:
Caller-owned Source Tree
-> Surface-owned Runtime Copy
-> TP Candidate Objects
-> Element Tree
-> Computed Style
-> Box/Formatting Context
-> Measurement + Render Context
-> Layout Fragment/Snapshot
-> Ebox Dirty/Patch Plan
-> TP Surface Plan
-> TP Atomic Publication
-> Ebox Runtime-State Participant
| Model | Owner | Must not own |
|---|---|---|
| Source/Element Tree | ebox-child-range.el, ebox-tree.el, ebox-dsl.el |
Published buffer mutation. |
| State Ownership Contract | ebox-state-contract.el |
Live publication or mutable authority. |
| Computed Style | ebox-style.el |
Layout identity or patch execution. |
| Length Values | ebox-size.el |
Window/node reads, property defaults, choosing display boundaries, or arbitrary Lisp evaluation. |
| Measurement | ebox-measure.el |
Application state or dirty policy. |
| Formatting Context | ebox-layout.el, ebox-flex.el, ebox-grid.el |
Buffer edits. |
| Fragment/Snapshot | ebox-fragment.el, ebox-incremental.el |
Source parsing or identity allocation. |
| Surface Projection and Runtime Publication | ebox-surface.el plus public TP surface APIs |
Ebox layout decisions or generic diff execution. |
| Dirty/Patch Semantics | ebox-incremental.el |
Raw measurement or TP buffer writes. |
| Pure Patch Artifact Plan | ebox-patch-plan.el |
Buffer/surface reads or publication. |
| Generic Surface Diff/Commit | TP | Ebox geometry, dirty policy, or application state. |
| Render-String Backend | ebox-buffer-backend.el |
Live buffer writes, retained markers, style semantics, or application state. |
Invariants
- Public Ebox nodes are data;
ebox--*names are private. - Box geometry uses explicit
(unit number)data withpx,%,vw,vh,ch, orlh;calc,min,max, andclampcompose values. Bare geometry numbers, singleton pixel lists, viewport/contain keywords, and parameterizedfit-contentare rejected. - The author unit-constraint table is authoritative. The schema and construction boundary validate every nested branch against its inline, block, parent-main-axis, or border-paint domain before any display work. Empty automatic content has zero height.
- Property schemas own allowed keywords and ranges; layout supplies reference measurements. Length arithmetic retains fractions and unresolved dependencies until the appropriate layout/display boundary. Vertical buffer content remains quantized to complete lines.
ebox-renderuses an ephemeral TP surface and does not publish to a buffer;ebox-render-to-buffermounts a retained TP surface;ebox-display-bufferdisplays that same retained-surface path; andebox-commitprepares Ebox semantics and updates the mount through TP. Ebox exposes no parallel public macro that erases a live buffer and evaluates arbitrary body forms.- Declarative input remains caller-owned. Live mount/commit assigns identity only on a surface-owned copy, so the same source can back multiple buffers.
- Logical
:idvalues resolve throughebox-region-resolveto opaque surface-scoped handles. Handles, not source-tree numeric ids, distinguish the same logical region mounted in different buffers. - A failed candidate leaves the previous buffer, runtime identity, and report intact.
- Keys are local to siblings; visible strings are never used as identity.
- Ebox owns logical node-to-subject adaptation and id/class/type candidate indexes; ECSS's public structured matcher is the only selector truth source. Subjects expose built-in id/key plus explicit
:selector-attributes, never visible content, layout state, or runtime containers. ebox-style--property-definitionsis the single Ebox author-property source. Load time derives Ebox's read-only lookup index and one immutable ECSS package schema from it; the surface schema is composed once, and node construction or updates never re-register or copy the whole schema domain.- E1 freezes the closed M2a target classification for retained state: immutable generation facts; generation-bound scroll/native authority; opaque TP client-state custody; one-way compatibility mirrors; and disposable caches. Every inventory row separately records the M2a-era storage shape plus its target owner, mutation API, generation binding, rollback, rebuild proof, and cleanup. TP client state holds the whole Ebox state plist. The E1 mirror probes only rebuild and compare projections; they never mutate live state.
ebox-layout.elhas no load or call edge toebox-surface.elor TP. Candidate isolation, inherited-cascade detection, and one-shot materialization enter layout only through the validated port owned byebox-render-context.el;ebox.elwires the surface implementation after both sides load.ebox-incremental.ellikewise contains noebox-surfacesymbol or publication call.ebox-surface.elinstalls one immutable read-only context port for live client state, region mounts, cascade proof, and inline-style preparation; batch observation remains in the higherebox.elorchestrator.ebox-patch-plan.elconsumes only tentative operation plists plus the immutable generation parent table and returns a deterministic plan artifact. The incremental adapter owns all live-fact preparation. E3 defaults to the pure route while retaining explicitlegacyand equality-checkingshadowroutes; none of the three routes publishes.ebox-style--property-definitionsis also the single used-value projection truth. Non-default engine lowering lives in each property's:engine-projection; ECSS metadata exposes the derived:engine-targets, and only non-public engine fields may use the small internal exception table.ebox-framework-spi-capabilitiesreturns a fresh immutable SPI v2 provider record declaring paired initial/update stage+rollback, combined participant ordering, same-object reports, initial observation replay, and the actual accepted TP protocol.ebox-framework-spi-required-tp-versionexposes the minimum TP 2.0.0 dependency, whileebox-framework-spi-supported-tp-protocolslists accepted structured protocol identities; protocol membership alone does not satisfy the minimum package version. Initial TP/Ebox timing is measured during publication, stored as defensive snapshots in the completed report, and replayed without mutation authority; no synthetic zero-duration timing is emitted. Ebox provides no selected port or consumer bootstrap.- Ebox registers every rollback-capable runtime mirror through TP's public
tp-transaction-participate-v2API. The loaded manifest must advertise that callable API with an accepted structured protocol; missing or malformed capability fails closed during load. - SPI initial publication widens under provider ownership and wraps the TP transaction in an editor change group. On any unsuccessful or nonlocal exit, it restores original overlay identities and bounds together with buffer text, marker identities, point, mark, narrowing, undo history, and modified state.
- Framework report finalization is postaccept and no-throw. Initial, full-update, and scoped-update completion faults are retained in the same legacy report as
framework-report-finalizationdiagnostics; they cannot invoke framework rollback or escape into an ETAF semantic rollback window. owner-rerenderis broader thanspan-patch, which is broader thanpaint-patch.- Buffer coordinates belong to the generation that produced them and must be refreshed after mutation.
- Grid uses the normal measurement and rendering pipeline. Native reflow may reject an ineligible tree and must fall back to Elisp without changing correctness.
- Native sizing accepts fixed values and static size arithmetic within the public axis constraints when the resolved geometry is integral in the native representation. Percentage/viewport dependencies, fractional geometry, and vertical intrinsic sizing use the normal Elisp fallback. Accepting these values at the public boundary does not imply native acceleration.
- A confirmed retained-native frame owns an immutable layout document together with its exact document revision. Candidate-session forks share that document. A synchronous frame may omit the document only when its base and target document revisions are equal and match the confirmed document, and its TP runtime revision matches the confirmed frame. A changed IR sends a complete replacement document with the next document revision; confirmation promotes the candidate document atomically, while failure or cancellation leaves the parent session unchanged.
- A full declarative update continuing a committed native frame computes
selector-local styles for every candidate node before stable native
eligibility. The
:styles-prepared-pcertificate prevents replacement runtime nodes from losing computed styles while ordinary full/local updates retain O(changed) style work. Topology-changing full-frame bootstrap remains eligible. - Loading Ebox never builds or installs the optional Rust module.
- Phase 9 is complete: first mount, declarative commit, handle/selector update, viewport/theme update, batch flush, and scroll update all publish through TP surfaces. Phase 10 has unified selector parsing, matching, and cascade under ECSS; Ebox computes dirty/layout ownership and joins its opaque runtime state to the TP rollback-capable transaction, with no second live buffer executor.
- Successful reports preserve the Ebox semantic strategy and planned publication scope, then add
:publication-scope tp-surface, TP physical operation counts, surface revision, scoped/full-root facts, and retained-object reconciliation counts.
Grid contract
Grid currently covers explicit-unit and percentage tracks, auto, fractional tracks, minmax, repeat, implicit rows and columns, row/column gap, auto-flow, one-based placement, positive spans, item/content alignment, and ordinary buffer rendering and updates. Ebox supports canonical font inputs (:font-family, :font-size, :font-weight, and :font-style) plus text-decoration paint; full browser typography is outside the contract. Absolute positioning, z-index, border radius, shadows, and browser-level bidi are also outside the contract.
Active examples and tests
The standalone package has no bundled playground fixtures. The migrated .ebox references and their file runner live in the sibling ebox-playground package; this repository tests the DSL and layout primitives directly. The regression boundary is the ERT files under tests/ listed in the repository's documentation contract. No test in this package should load the historical full-stack source tree or an application framework.
Verification matrix
make load
make compile
make check
make size-tests
make size-migration-tests
make interaction-tests
make display-tests
make release-tool-tests
make release-deps
make release-build
make release-check
make native-integration
make core-tests
make runtime-index-tests
make child-range-tests
make grid-tests
make ebox-commit-tests
make surface-tests
make visual-check-tests
make package-tests
make selector-tests
make dsl-tests
make flex-tests
make state-contract-tests
make layout-boundary-tests
make layout-boundary-performance
make patch-plan-tests
make patch-plan-performance
make style-schema-tests
make style-schema-performance
make spi-tests
make spi-performance
make c1b-contract-tests
make docs-contract-tests
make ci-contract-tests
make performance-evaluator
make visual-check
make native-rust-tests
make native-build
make package-lint
make diff-check
Run focused tests first, then make check after changes to shared rendering, Grid, public constructors, or documentation. Native changes require the Rust checks and a native build.
make size-tests includes make size-migration-tests, so the migration tool's
Python regression suite also runs through make check.
Source package distribution
Use scripts/ebox-release.py through the Make targets above rather than writing
another installation probe. release-dependencies.json pins public HTTPS URLs
and full commit IDs for mandatory TP/ECSS and optional EKP. It is a reproducible
build input, not a replacement for each package's minimum version contract.
EKP is included only with --with-ekp; ordinary archives do not make it an
Ebox package dependency.
Recommended complete verification:
make release-check EMACS=emacs RELEASE_DIR=/tmp/ebox-release-3.0.0
make release-check EMACS=emacs RELEASE_DIR=/tmp/ebox-release-3.0.0-kp RELEASE_OPTIONS=--with-ekp
Both commands fetch locked dependencies and build from the current Ebox source
files, excluding compiled output and temporary files. The output contains
deterministic tar archives, archive-contents, and a manifest with SHA-256
checksums, dependency commits, and Ebox source revision/dirty status. Modified
source is allowed for development verification and is recorded as such; a
clean release checkout should be used for a formal release artifact. Neither
command uploads files or creates a tag.
release-check builds a new directory and then verifies it. To verify an
existing archive without rebuilding or fetching dependencies:
python3 scripts/ebox-release.py verify --directory /tmp/ebox-release-3.0.0 --emacs emacs
verify checks archive checksums, uses emacs -Q --batch, and lets package.el
resolve dependencies from that local archive into a disposable package profile.
It checks autoloads, required package provenance, standalone loading,
render/update/selector behavior, and the real EKP provider when included.
No sibling load-path or user's installed packages supply missing dependencies.
The ordinary archive check does not build or require the Rust module.
To reuse already fetched dependencies, run make release-deps with a fresh
RELEASE_DEPS_DIR, then pass RELEASE_OPTIONS="--dependencies /path/to/deps"
to release-check or release-build. Those checkouts must be clean at the
locked commits. Include --with-ekp consistently when fetching and building
the KP variant. release-build stops after producing archives.
make native-integration RELEASE_DIR=/path/to/archive EMACS=emacs NATIVE_MODULE=/absolute/path/to/module verifies an existing archive and requires
that exact module to load and produce a native frame. It does not build the
module. Supply the artifact for the host OS and architecture.
Existing output directories are rejected rather than overwritten. Temporary
checkouts and package profiles are removed on success, failure, interruption,
or subprocess timeout; completed archive/dependency outputs remain at the
explicit paths. Nonzero exit means verification is unsuccessful. Use
make release-tool-tests for tool regressions. This entry point supersedes
ad hoc clean-install probes.
CI runs the Elisp suite and clean installation on Linux with Emacs 29.1 and 30.2, including optional EKP installation. The native jobs build and test on Linux, macOS, and Windows, then load and execute the built module through a clean package installation on Emacs 30.2. Rust MSRV build/test jobs remain separate. Configuration is not evidence of a successful remote run: inspect the results for the specific revision, and do not infer GUI visual parity from batch or Rust tests.
Migrate literal author sizes
For old Ebox author literals, use the supported source-aware migration tool instead of another search-and-replace script. Its positional inputs are explicit files or directories; no inputs are inferred from the machine's workspace. Start with a dry run:
python3 scripts/migrate-css-sizes.py ../ebox-playground/examples
Review the proposed changes and ambiguity reports, then add --write for the
same explicit paths. The tool preserves strings, comments, and formatting and
does not evaluate Elisp. Dynamic values and removed/ambiguous constructs need
an intentional source edit. Target author data only: internal measured structs
can use the same property names with already-resolved numeric engine values.
This entry point supersedes temporary literal-rewrite probes.
Validate the migration tool with:
python3 -m unittest discover -s scripts -p 'test_migrate_css_sizes.py'