etaf/docs/proposals/module-boundaries.en.md
2026-08-26 15:14:27 +08:00

338 lines
13 KiB
Markdown

# ETAF Module Responsibilities and Target Architecture (Unimplemented Proposal)
> Status: design proposal. This is not the delivered API. Current behavior remains
> defined by [`architecture.en.md`](../architecture.en.md),
> [`user-guide.en.md`](../user-guide.en.md), and passing tests.
This document defines only the target model, module owners, dependency direction, and
completion criteria. Delivery sequencing belongs in a separate implementation plan;
historical names and compatibility do not shape the target architecture.
User mental cost is a hard constraint: one capability has one canonical name and one
recommended spelling. Internal algorithms, backend nodes, wrappers, and compatibility
aliases never become peer public concepts. Ordinary applications learn Text, Box, and
Component first; Fragment, layout detail, and lower packages are progressively disclosed.
## 1. Final model
```text
Component semantics and ownership
View: Text / Box / Fragment
Ebox: measurement, layout, geometry patches
TP + Emacs adapter: paint and final submission
```
The main public concepts are `Text`, `Box`, and `Component`. `Fragment` is advanced
structure, `Host` is an internal Renderer term, and an Ebox Node is a backend object.
The complete normalized View shape is:
```text
View = Text
| Box
| Fragment
| ComponentCall
```
`expr` and `slot` are computation/projection mechanisms, not visual nodes. The target
public View does not accept raw Ebox Nodes. Missing rendering capability must become a
typed View/Ebox feature with explicit identity and impact contracts rather than an
opaque escape.
## 2. Text, Box, and Fragment
### 2.1 Text
Text is a leaf that owns strings and inline Text runs, typography/paint, text
measurement and wrapping input, and optional identity/semantic/event properties.
Text defaults to `:outer 'inline`. It cannot own Box/Component children or establish
row, column, flex, or grid layout. Use an outer Box for padding, border, dimensions, or
child layout.
### 2.2 Box axes
```text
:outer = inline | block
:layout = normal | row | column | flex | grid
```
`:outer` controls participation in a parent normal layout. `:layout` controls child
layout. Defaults are `Box: block/normal` and `Text: inline`.
```elisp
(box :outer 'inline :layout 'flex ...)
```
This is inline-flex: an orthogonal combination, not another node type. ETAF exposes
`:outer` and `:layout`, not `:inner` or Ebox's raw display-pair representation.
### 2.3 Bounded normal layout
Normal layout supports only:
- consecutive inline Text/Box nodes in one wrapping line flow;
- block Boxes starting a new independent block;
- stable child order and block boundaries ending adjacent inline lines.
It does not implement full browser CSS: no floats, tables, absolute/fixed positioning,
run-in, list-item, or arbitrary anonymous-box rules. Ebox may map `normal` to an
internal flow algorithm; `flow` is not ETAF vocabulary.
For row, column, flex, or grid parents, the parent algorithm owns item placement and a
child's `:outer` does not change ordering. `:outer` is consumed only by a normal parent.
### 2.4 Other layout modes
| Mode | Responsibility |
| --- | --- |
| `row` | simple horizontal order without Flex distribution |
| `column` | simple vertical order without Flex distribution |
| `flex` | Flex sizing, direction, wrapping, alignment, and gaps |
| `grid` | two-dimensional tracks, placement, spans, and gaps |
They are Box modes, not Components or alternate Runtime node names. The only canonical
surface is `(box :layout 'MODE ...)`; core provides no layout-name aliases.
### 2.5 Ebox DSL primitives and sugar
ETAF View and Ebox DSL are different layers: ETAF has Text/Box/Fragment/Component,
while Ebox receives lowered content and layout. The target Ebox DSL has one structural
tag:
```elisp
(box :layout MODE ...)
```
An Ebox box may be a content leaf or own children and a layout context. A Range
descriptor is an incremental backend protocol, not an authoring node. A bare string is
content-literal shorthand, not a node type.
Current tags classify as follows:
| Current tag | Classification | Target expression |
| --- | --- | --- |
| `ebox` | compatibility alias for `box` | remove; use `box` |
| `spacer` | empty-Box convenience sugar | remove; use a childless `box` |
| `row` | layout-tag sugar | `(box :layout 'row ...)` |
| `column` | layout-tag sugar | `(box :layout 'column ...)` |
| `flex` | layout-tag sugar | `(box :layout 'flex ...)` |
| `grid` | layout-tag sugar | `(box :layout 'grid ...)` |
| `item` | Flex-child participation sugar | put flex/order/align-self properties on the child Box |
| `grid-item` | Grid-child placement sugar | put grid placement properties on the child Box |
Item wrappers are therefore unnecessary. Parent-participation properties belong to
the child Box and are validated by the parent layout mode:
```elisp
(box :layout 'flex
(box :flex-grow 1 :content "A"))
(box :layout 'grid
(box :grid-column '(1 :span 2) :content "Header"))
```
These are low-level Ebox examples, so Ebox `:content` is valid. ETAF View continues to
use Text/children and does not expose `:content`.
The target Ebox DSL retains none of those aliases or sugar tags. Ebox still implements
the layout algorithms independently, but algorithm kinds are not authoring node kinds.
### 2.6 Fragment
Fragment is a nonvisual structural Range. It may contain zero, one, or many sibling
Views, creates no Box or layout context, and may carry only a stable `:key` plus
children. Runtime may retain and replace its Range independently.
A Component call has no fixed outer/layout properties. Its root Text/Box determines
participation. A transparent Component returning a Fragment may have multiple roots.
### 2.7 Strings, content, and empty Boxes
Strings under a Box normalize to Text, so `(box "text")` equals
`(box (text "text"))`. ETAF does not expose `(box :content ...)`; `:content` remains an
Ebox backend field. ETAF also has no spacer type: a childless Box is an empty Box.
## 3. Property contract
Properties form closed, owner-specific schemas. Unknown or inapplicable combinations
fail explicitly.
| Owner | Category | Representative properties |
| --- | --- | --- |
| Text | outer participation | `:outer` |
| Text | text/paint | `:face`, `:color`, `:bgcolor`, `:wrap-mode`, `:text-align` |
| Box | outer/inner layout | `:outer`, `:layout` |
| Box | geometry/surface | width/height/min/max, margin, padding, border, box sizing, overflow |
| Flex container/item | layout/participation | direction, wrap, alignment, gap, grow/shrink/basis/order |
| Grid container/item | layout/participation | tracks, auto flow, placement, spans, alignment, gap |
| Text/Box | identity/semantics | `:key`, `:ref`, class/id/role/ARIA/events/Behaviors |
Key rules:
- `:key` is identity metadata, not paint;
- outer/layout/layout-specific properties have structure/geometry impact;
- color/background/typography have paint impact;
- ECSS preserves that impact classification;
- TP consumes resolved paint contributions only;
- structural changes transactionally replace the required layout context.
## 4. Module owners
### ETAF Core
`etaf-view` owns normalized View shapes, string-to-Text normalization, closed property
schemas, slots, keys, and expression boundaries. It does not measure, lay out, or write
buffers.
`etaf-component` owns Component props and `:view`/`:setup`/`:styles` definitions. It
does not schedule Runtime or call Ebox.
`etaf-runtime` owns Component/Fragment/Range identity, reactive dependencies, Context,
Theme, Actions, Behaviors, Data, Resources, lifecycle, candidate generations, commit
authority, rollback, and disposal. It does not compute pixels.
`etaf-renderer` is the only ETAF-to-Ebox lowering adapter. It consumes normalized View
and computed properties without accessing Ebox private state.
Theme token meaning and inheritance belong to ETAF Context, not TP.
### Ebox
Ebox owns Text measurement/wrapping/line layout, normal/row/column/flex/grid geometry,
the box model, overflow/scroll/viewport behavior, stable Ebox node identity, geometry
snapshots, and structural patch plans. It does not know Component semantics or paint
contribution priority.
Logical boundaries:
```text
ebox-core pure measurement, layout, snapshots, structural patches
ebox-emacs font/window capabilities, buffer positions, structural submission
```
These need not become separate distribution packages immediately, but must remain
independently testable.
### ECSS
ECSS owns selectors, cascade, inheritance, and computed properties. Its output retains
structure/geometry/paint impact. ECSS does not run layout or write buffers.
### TP and commit boundary
TP owns paint-contribution priority, paint-operation merging, Emacs text-property
journaling, application, and rollback.
```text
ETAF Runtime creates a candidate and owns commit authority
Ebox stages structural/geometry patches
TP stages paint patches
Emacs adapter atomically applies both
ETAF Runtime promotes the generation; failure rolls participants back
```
Ebox does not own TP priority, TP does not own layout, and neither participant may
promote a Runtime generation.
### Upper packages
| Package | Owns | Does not own |
| --- | --- | --- |
| `etaf-ui` | ordinary reusable Components | another Runtime or layout engine |
| `etaf-sqlite` | SQLite Data Source and its transactions/disposal | Data Controller or UI |
| `etaf-performance` | application-neutral operations/stages/statistics/reports | example semantics or scheduling authority |
| `ebox-playground` | public Ebox examples/verification | ETAF |
| `etaf-playground` | ETAF/UI/SQLite composition examples and tooling | core protocols |
`etaf-performance` is a separate optional package. No measured package depends on it;
it observes only public boundaries. A Playground `.etaf` manifest belongs to the
Playground. Any future general compiler must lower to the same View/Component contract.
## 5. Dependency direction
```text
etaf-ui ─────────────▶ ETAF Core ─────────────▶ Ebox public port
etaf-sqlite ─────────▶ ETAF Data contract
etaf-playground ─────▶ ETAF + optional UI/SQLite
ebox-playground ─────▶ Ebox only
etaf-performance ────▶ public observation boundaries only
ETAF Renderer ───────▶ ECSS computed properties
ETAF Runtime ───────▶ Ebox/TP transaction participants
```
Reverse dependencies are forbidden: Ebox never depends on ETAF, TP never parses View,
ECSS never writes buffers, UI never calls Ebox private APIs, SQLite never knows UI, and
Playgrounds never inject protocols into core.
## 6. Rust and Elisp
Boundaries precede implementation language. Deterministic kernels may move to Rust only
after contracts and equivalence tests are stable:
```text
Rust candidates:
normalized View validation and structural diff over opaque stable IDs
ECSS cascade
Text-measurement input processing and Ebox layout
pure geometry/paint patch computation
Elisp / Emacs:
user Components, refs, Context, Actions, Data, Resources, lifecycle
database and external I/O
Emacs events, font/window capabilities, and final submission
```
Component identity, dependency ownership, TP priority, and generation authority do not
move merely because a kernel is written in Rust. Elisp must not repeat Rust diff,
cascade, layout, or text scans.
## 7. Correctness and performance invariants
- View, computed properties, geometry snapshots, and paint contributions materialize
once per transaction;
- Text paint changes do not execute unrelated Components or layout;
- outer/layout/geometry changes affect only the required layout owner;
- Fragment/slot/list changes replace only their Range;
- Ebox/TP/Emacs participant failure preserves the last committed generation;
- resize uses current window facts immediately, without hidden debouncing semantics;
- instrumentation does not change the measured path.
Final gates cover structural equivalence, legal/illegal outer-layout combinations,
normal inline/block behavior, row/column/flex/grid GUI behavior, Component roots and
Fragments, style/Theme/TP priority and rollback, continuous Research Shelf/Flex
reference resize, and p95/max at or below 50ms in fixed real scenarios.
## 8. Current gap and non-goals
Current code still registers `text`, `fragment`, `container`, `row`, `column`, `stack`,
`flex`, `grid`, and `spacer`, and exposes `raw-ebox`. Target `box`, `:outer`, and
`:layout 'normal` are not implemented, and the target public View does not retain
`raw-ebox`.
Current Ebox DSL still accepts `box`, `ebox`, `row`, `column`, `flex`, `item`, `grid`,
`grid-item`, and `spacer`. The target Ebox DSL keeps only `box`; all other tags are
removed or represented as Box properties according to the table above.
This is a clean redesign: old Host names and old `.etaf` sources need not continue to
run, and no long-lived compatibility layer is added. Formal architecture/user docs must
not present target syntax as delivered before implementation is complete.
Non-goals:
- full browser CSS;
- spacer, public flow, or `(box :content ...)`;
- injecting raw Ebox Nodes into View;
- layout modes as Components or alternate Runtime nodes;
- old Host aliases or optional layout shorthand in core;
- core protocols owned by Playground/UI/database packages;
- App precompilation without measured compile cost and runtime benefit.