190 lines
8.2 KiB
Markdown
190 lines
8.2 KiB
Markdown
# ETAF
|
|
|
|
ETAF builds text applications from reusable Components above the independent
|
|
[Ebox](../ebox) layout and rendering engine.
|
|
|
|
Start with `etaf-view` and `etaf-mount`. Properties precede children in
|
|
`(name :property value ... child ...)`; property values are ordinary Elisp.
|
|
Evaluate the complete example, switch to `*etaf-hello*`, and activate “Say hello”:
|
|
|
|
<!-- etaf-example: hello -->
|
|
```elisp
|
|
;;; -*- lexical-binding: t; -*-
|
|
(require 'etaf)
|
|
|
|
(etaf-mount
|
|
"*etaf-hello*"
|
|
(etaf-view
|
|
(column
|
|
(text :font-weight 'bold "Hello")
|
|
(box :ref 'hello :role 'button :tab-index 0
|
|
:on-press (lambda () (message "Hello ETAF"))
|
|
"Say hello"))))
|
|
```
|
|
|
|
A Component receives declared props and optional content through slots. Use the
|
|
exact name passed to `etaf-define-component`; the registry creates no aliases.
|
|
`(expr FORM)` evaluates one child expression. In a structural child position it
|
|
may return nil, text, a typed Host or Component View, or a proper sequence of
|
|
those values. Inside `text`, an expression must return a string.
|
|
|
|
<!-- etaf-example: card -->
|
|
```elisp
|
|
;;; -*- lexical-binding: t; -*-
|
|
(require 'etaf)
|
|
|
|
(etaf-define-component demo-card (&key title)
|
|
:view
|
|
(column
|
|
(text :font-weight 'bold (expr title))
|
|
(slot)
|
|
(slot :name 'footer)))
|
|
|
|
(etaf-mount
|
|
"*etaf-card*"
|
|
(etaf-view
|
|
(demo-card :title "Account"
|
|
(text "Connected")
|
|
(slot :name 'footer (text "Footer")))))
|
|
```
|
|
|
|
Add `:setup` when a Component owns state. It runs once per retained instance;
|
|
`:render` uses ordinary Elisp to capture handles before returning `etaf-view`.
|
|
The shorter `:view` form compiles the same View model.
|
|
|
|
<!-- etaf-example: counter -->
|
|
```elisp
|
|
;;; -*- lexical-binding: t; -*-
|
|
(require 'etaf)
|
|
|
|
(etaf-define-component demo-counter ()
|
|
:setup (etaf-ref 0)
|
|
:render
|
|
(let ((count (etaf-state)))
|
|
(etaf-view
|
|
(column
|
|
(text (expr (format "Count: %d" (etaf-value count))))
|
|
(box :ref 'increment :role 'button :tab-index 0
|
|
:on-press (lambda () (cl-incf (etaf-value count)))
|
|
"Increment")))))
|
|
|
|
(etaf-mount "*etaf-counter*" (etaf-view (demo-counter)))
|
|
```
|
|
|
|
Keep `etaf-value` reads inside the property or `expr` that should update. Event
|
|
callbacks capture ordinary lexical locals; call `etaf-state` during rendering.
|
|
Use a lexical-binding `.el` file for reusable application code. `etaf-node` is
|
|
available for programmatic View builders. Context, Data, Behavior, and named
|
|
Actions are optional capabilities; simple callbacks need no Action registration.
|
|
|
|
Use exact catalog names such as `etaf-button` after `(require 'etaf-ui)`.
|
|
Core does not load `.etaf` files: Playground treats them as inert structure,
|
|
with its explicit companion registration handling executable Elisp.
|
|
|
|
## Performance records
|
|
|
|
ETAF provides an independent, opt-in, application-neutral timing recorder. It
|
|
consumes public Runtime observer reports without advice or private cross-package
|
|
probes. Runtime operations such as Event, Action, mount, flush, and unmount are
|
|
recorded automatically; Ebox, TP, Data, Resource, and SQLite provider stages
|
|
inside the same operation are correlated by sequence.
|
|
|
|
```elisp
|
|
(require 'etaf-performance)
|
|
;; In a buffer with a mounted ETAF Runtime:
|
|
(etaf-performance-mode 1)
|
|
;; Use any mounted ETAF application normally.
|
|
(etaf-performance-show)
|
|
```
|
|
|
|
For an interactive capture, run `M-x etaf-performance-clear` first. After
|
|
reproducing the operations, press `c` in the panel (or run
|
|
`M-x etaf-performance-copy-report`) to copy a complete report. Press `w` (or
|
|
run `M-x etaf-performance-export`) to save the same report as an `.eld` file.
|
|
The portable report includes the Emacs/display environment, power source,
|
|
low-power mode, native-JIT state, system load, grouped p50/p95/max, individual
|
|
operations, GC deltas, and ordered provider stages. The panel header exposes
|
|
the same environment context so a machine-wide slowdown is not mistaken for
|
|
one package hotspot.
|
|
|
|
The `*ETAF Performance*` panel shows operation IDs, generation changes, total
|
|
latency, GC deltas, and each flat provider stage in sequence. Provider stages
|
|
may overlap, so they are not presented as exclusive/self time. Records are
|
|
bounded by `etaf-performance-max-records`; disabling the mode only detaches the
|
|
Runtime observer and never rewrites functions. `etaf-performance-summary`
|
|
computes operation p50/p95/max statistics on demand, while
|
|
`etaf-performance-operation-stage-summary` groups one operation's flat stages
|
|
by provider category. `etaf-performance-records` returns defensive operation
|
|
and stage snapshots; caller mutation cannot rewrite retained history.
|
|
Pass a numeric observer runtime ID to `etaf-performance-records` to select one
|
|
mount's history even when a buffer name is reused. Summary and report functions
|
|
use all retained records when called without an argument; an explicit `nil`
|
|
means an empty selection. The exported environment describes report generation,
|
|
not each historical operation. These synchronous operation durations do not
|
|
measure physical input-to-presentation latency; use the GUI measurement entry
|
|
in [scripts/README.md](scripts/README.md) for per-action condition checks.
|
|
|
|
Use `etaf-performance-call-operation` or
|
|
`etaf-performance-with-operation` to trace an arbitrary operation that has no
|
|
built-in public boundary. Both delegate to the same Runtime operation boundary;
|
|
they do not create a second timer.
|
|
|
|
## Executable examples
|
|
|
|
The [`examples/`](examples/README.md) directory contains three core-only best-practice applications: retained state and Actions, Data Controller ownership, and Resource error/cleanup lifecycle. They are byte-compiled and driven through mounted public event paths by `make check`.
|
|
|
|
```elisp
|
|
(add-to-list 'load-path "/path/to/github/etaf/examples")
|
|
(require 'etaf-counter-example)
|
|
(etaf-counter-example-open)
|
|
```
|
|
|
|
## Documentation
|
|
|
|
- [Architecture](docs/architecture.en.md) · [中文架构](docs/architecture.zh.md)
|
|
- [User guide](docs/user-guide.en.md) · [中文用户指南](docs/user-guide.zh.md)
|
|
- [Implementation plan](docs/implementation-plan.en.md) · [中文实施计划](docs/implementation-plan.zh.md)
|
|
- [Module-boundary proposal (unimplemented)](docs/proposals/module-boundaries.en.md) · [模块边界提案(未实现)](docs/proposals/module-boundaries.zh.md)
|
|
- [Best-practice examples](examples/README.md) · [中文示例](examples/README.zh-CN.md)
|
|
|
|
## Independent packages
|
|
|
|
| Package | Role |
|
|
| --- | --- |
|
|
| [`etaf-ui`](../etaf-ui/README.md) | Official Component catalog: Button, Checkbox, Label, Panel, and DataGrid. |
|
|
| [`etaf-sqlite`](../etaf-sqlite/README.md) | Concrete SQLite Data Source; the Data Controller remains in ETAF core. |
|
|
| [`etaf-playground`](../etaf-playground/README.md) | ETAF examples, with the UI catalog loaded only when requested. |
|
|
| [`ebox-playground`](../ebox-playground/README.md) | Ebox-only layout examples, independent from ETAF. |
|
|
|
|
There is no separate `etaf-data` install: Data is a core ETAF capability. There is no generic `etaf-adapters` package: other databases, services, files, or ORMs should provide concrete Data Source packages with explicit names.
|
|
|
|
## Load and verify
|
|
|
|
Install ECSS 0.1.0 and TP 2.0.0 before Ebox 3.0.0, then install ETAF 0.2.1.
|
|
ETAF declares TP directly because Host final-accept authority uses the TP
|
|
transaction contract. Rendering requires Ebox framework SPI v2; a missing,
|
|
malformed, or incompatible provider fails during ETAF bootstrap.
|
|
|
|
ETAF snapshots one immutable v2 render port for the Emacs process. During an
|
|
ordered upgrade it also accepts Ebox's transitional dual-capability TP manifest
|
|
because that manifest contains the required v2 protocol. ETAF never dispatches
|
|
through the retired v1 capability.
|
|
|
|
During development, load the sibling Ebox checkout before ETAF:
|
|
|
|
```elisp
|
|
(add-to-list 'load-path "/path/to/github/ecss")
|
|
(add-to-list 'load-path "/path/to/github/tp")
|
|
(add-to-list 'load-path "/path/to/github/ebox")
|
|
(add-to-list 'load-path "/path/to/github/etaf")
|
|
(require 'etaf)
|
|
```
|
|
|
|
Run the complete local gate:
|
|
|
|
```sh
|
|
make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
|
```
|
|
|
|
The core gate byte-compiles the implementation, runs the core/Data/Resource tests, and checks documentation/API boundaries. Run `make check` in the sibling `etaf-ui`, `etaf-sqlite`, `etaf-playground`, and `ebox-playground` repositories for their independent gates; none is loaded by the core facade.
|