refactor: publish framework changes through structured TP participants
Some checks are pending
CI / test (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
Some checks are pending
CI / test (push) Waiting to run
CI / native-build (macos-latest) (push) Waiting to run
CI / native-build (ubuntu-latest) (push) Waiting to run
CI / native-build (windows-latest) (push) Waiting to run
CI / native-msrv (macos-latest) (push) Waiting to run
CI / native-msrv (ubuntu-latest) (push) Waiting to run
CI / native-msrv (windows-latest) (push) Waiting to run
This commit is contained in:
parent
f1f91468aa
commit
32b4834322
23
CHANGELOG.md
23
CHANGELOG.md
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
All notable changes to Ebox are documented here.
|
All notable changes to Ebox are documented here.
|
||||||
|
|
||||||
## 2.0.1 (Unreleased)
|
## 3.0.0 (Unreleased)
|
||||||
|
|
||||||
### Added
|
### Added
|
||||||
|
|
||||||
@ -11,15 +11,19 @@ All notable changes to Ebox are documented here.
|
|||||||
- Surface-scoped region handles through `ebox-region-resolve`, retained TP publication through `ebox-render-to-buffer` and `ebox-display-buffer`, and atomic handle/selector/viewport/scroll updates.
|
- Surface-scoped region handles through `ebox-region-resolve`, retained TP publication through `ebox-render-to-buffer` and `ebox-display-buffer`, and atomic handle/selector/viewport/scroll updates.
|
||||||
- Typography longhands and inherited foreground projection that keep text readable when child boxes override background colors.
|
- Typography longhands and inherited foreground projection that keep text readable when child boxes override background colors.
|
||||||
- A public, nestable framework render-burst boundary that covers pre-commit allocation, shares interactive deferred GC ownership with Ebox commits, and restores batch GC settings without collecting at the boundary.
|
- A public, nestable framework render-burst boundary that covers pre-commit allocation, shares interactive deferred GC ownership with Ebox commits, and restores batch GC settings without collecting at the boundary.
|
||||||
- A supported `ebox-transaction-participant-route` switch: v2 structured TP
|
- Structured TP participant registration for every retained publication.
|
||||||
registration is the default, while the complete v1 adapter remains available
|
|
||||||
explicitly and as the compatibility fallback for older TP manifests.
|
|
||||||
|
|
||||||
### Changed
|
### Changed
|
||||||
|
|
||||||
- Package metadata now distinguishes builds with the public unmount/revision
|
- Package metadata now distinguishes builds with the public unmount/revision
|
||||||
boundary and SPI v2 compatibility surface from older 2.0.0 development
|
boundary and SPI v2 compatibility surface from older 2.x development
|
||||||
snapshots.
|
snapshots.
|
||||||
|
- TP 1.0.1 is the minimum consumer-first dependency. Ebox accepts its structured
|
||||||
|
transition protocol and TP 2.0.0's final v2-only protocol, while always using
|
||||||
|
the public structured participant API.
|
||||||
|
- SPI initial publication owns widening and an outer editor change group. A
|
||||||
|
failed stage or nonlocal rollback exit restores text, markers, overlays,
|
||||||
|
point, mark, narrowing, undo history, and modified state before it escapes.
|
||||||
- ECSS is the only selector matcher and cascade winner. Ebox adapts node trees to ECSS subjects, interprets computed values for measurement/layout/paint, and classifies their layout impact.
|
- ECSS is the only selector matcher and cascade winner. Ebox adapts node trees to ECSS subjects, interprets computed values for measurement/layout/paint, and classifies their layout impact.
|
||||||
- TP is the only live buffer writer. Ebox produces retained surface candidates and semantic dirty-owner plans; TP owns diff execution, marker-backed mounts, revisions, transactions, and rollback.
|
- TP is the only live buffer writer. Ebox produces retained surface candidates and semantic dirty-owner plans; TP owns diff execution, marker-backed mounts, revisions, transactions, and rollback.
|
||||||
- Flex and Grid container/item declarations enter the same ECSS pipeline as ordinary boxes instead of maintaining a parallel style path.
|
- Flex and Grid container/item declarations enter the same ECSS pipeline as ordinary boxes instead of maintaining a parallel style path.
|
||||||
@ -35,10 +39,15 @@ All notable changes to Ebox are documented here.
|
|||||||
- `ebox-pop-to-buffer`, `ebox-switch-to-buffer`, and the private erase-and-evaluate buffer writer they exposed.
|
- `ebox-pop-to-buffer`, `ebox-switch-to-buffer`, and the private erase-and-evaluate buffer writer they exposed.
|
||||||
- Ebox's duplicate selector parser/matcher and duplicate cascade winner.
|
- Ebox's duplicate selector parser/matcher and duplicate cascade winner.
|
||||||
- Bundled playground fixtures. Executable `.ebox` references live in the independent `ebox-playground` package.
|
- Bundled playground fixtures. Executable `.ebox` references live in the independent `ebox-playground` package.
|
||||||
|
- The Ebox transaction route switch, v1 TP participant adapter call, and
|
||||||
|
missing-capability fallback.
|
||||||
|
|
||||||
### Migration
|
### Migration
|
||||||
|
|
||||||
- Install ECSS 0.1.0 and TP 1.0.1, in either order, before Ebox 2.0.1.
|
- Install ECSS 0.1.0 and TP 1.0.1 or newer, in either order, before Ebox 3.0.0.
|
||||||
|
- Remove any customization of `ebox-transaction-participant-route`; Ebox 3.0.0
|
||||||
|
requires TP's structured participant capability and fails closed when it is
|
||||||
|
missing or malformed.
|
||||||
- Replace numeric region-id mutation with `ebox-region-resolve` followed by `ebox-region-update` on the returned handle.
|
- Replace numeric region-id mutation with `ebox-region-resolve` followed by `ebox-region-update` on the returned handle.
|
||||||
- Replace `ebox-pop-to-buffer` or `ebox-switch-to-buffer` with `ebox-display-buffer`, or call `ebox-render-to-buffer` when display is controlled by the caller.
|
- Replace `ebox-pop-to-buffer` or `ebox-switch-to-buffer` with `ebox-display-buffer`, or call `ebox-render-to-buffer` when display is controlled by the caller.
|
||||||
- Ebox does not scan previously rendered text to reconstruct runtime identity. Remount the declarative source through Ebox 2.0.
|
- Ebox does not scan previously rendered text to reconstruct runtime identity. Remount the declarative source through Ebox 3.0.
|
||||||
|
|||||||
@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Ebox 的重要变化记录在这里。
|
Ebox 的重要变化记录在这里。
|
||||||
|
|
||||||
## 2.0.1(未发布)
|
## 3.0.0(未发布)
|
||||||
|
|
||||||
### 新增
|
### 新增
|
||||||
|
|
||||||
@ -11,14 +11,17 @@ Ebox 的重要变化记录在这里。
|
|||||||
- 通过 `ebox-region-resolve` 提供 surface-scoped region handle;`ebox-render-to-buffer` 与 `ebox-display-buffer` 统一使用 retained TP publication;handle、selector、viewport 与 scroll 更新加入同一原子事务。
|
- 通过 `ebox-region-resolve` 提供 surface-scoped region handle;`ebox-render-to-buffer` 与 `ebox-display-buffer` 统一使用 retained TP publication;handle、selector、viewport 与 scroll 更新加入同一原子事务。
|
||||||
- 增加 typography longhand 与继承 foreground 的投影;子盒子覆盖背景色时仍能保持文字可读。
|
- 增加 typography longhand 与继承 foreground 的投影;子盒子覆盖背景色时仍能保持文字可读。
|
||||||
- 增加公开、可嵌套的 framework render-burst 边界,覆盖 commit 前分配,与 Ebox commit 共用交互 deferred GC ownership,并在 batch 边界不主动 GC 的情况下恢复原设置。
|
- 增加公开、可嵌套的 framework render-burst 边界,覆盖 commit 前分配,与 Ebox commit 共用交互 deferred GC ownership,并在 batch 边界不主动 GC 的情况下恢复原设置。
|
||||||
- 增加受支持的 `ebox-transaction-participant-route` 开关:默认使用 v2
|
- 所有 retained publication 统一使用 structured TP participant registration。
|
||||||
structured TP registration;显式旧 route 与旧 TP manifest 的完整 v1 adapter
|
|
||||||
fallback 继续保留。
|
|
||||||
|
|
||||||
### 变更
|
### 变更
|
||||||
|
|
||||||
- 包 metadata 现在会区分具备 public unmount/revision 边界与 SPI v2
|
- 包 metadata 现在会区分具备 public unmount/revision 边界与 SPI v2
|
||||||
compatibility surface 的构建,旧的 2.0.0 development snapshot 不会再被误接受。
|
compatibility surface 的构建,旧的 2.x development snapshot 不会再被误接受。
|
||||||
|
- TP 1.0.1 是 consumer-first 迁移的最低依赖。Ebox 接受其 structured 过渡协议与
|
||||||
|
TP 2.0.0 的最终 v2-only 协议,但始终只调用公开 structured participant API。
|
||||||
|
- SPI initial publication 自己负责 widening 与外层 editor change group。stage
|
||||||
|
失败或 rollback 非局部退出向外传播前,会恢复 text、marker、overlay、point、
|
||||||
|
mark、narrowing、undo history 与 modified state。
|
||||||
- ECSS 是唯一 selector matcher 与 cascade winner。Ebox 只把节点树适配为 ECSS subject,解释 computed value 的测量、布局与绘制后果,并分类 layout impact。
|
- ECSS 是唯一 selector matcher 与 cascade winner。Ebox 只把节点树适配为 ECSS subject,解释 computed value 的测量、布局与绘制后果,并分类 layout impact。
|
||||||
- TP 是唯一 live buffer writer。Ebox 只生成 retained surface candidate 与语义 dirty-owner plan;diff execution、marker-backed mount、revision、transaction 与 rollback 都属于 TP。
|
- TP 是唯一 live buffer writer。Ebox 只生成 retained surface candidate 与语义 dirty-owner plan;diff execution、marker-backed mount、revision、transaction 与 rollback 都属于 TP。
|
||||||
- Flex/Grid container 与 item declaration 和普通 box 一样进入同一条 ECSS pipeline,不再维护平行 style path。
|
- Flex/Grid container 与 item declaration 和普通 box 一样进入同一条 ECSS pipeline,不再维护平行 style path。
|
||||||
@ -33,10 +36,15 @@ Ebox 的重要变化记录在这里。
|
|||||||
- `ebox-pop-to-buffer`、`ebox-switch-to-buffer`,以及它们暴露的私有 erase-and-evaluate buffer writer。
|
- `ebox-pop-to-buffer`、`ebox-switch-to-buffer`,以及它们暴露的私有 erase-and-evaluate buffer writer。
|
||||||
- Ebox 内重复的 selector parser/matcher 与 cascade winner。
|
- Ebox 内重复的 selector parser/matcher 与 cascade winner。
|
||||||
- 仓库内置 playground fixture;可执行 `.ebox` reference 已迁到独立 `ebox-playground` 包。
|
- 仓库内置 playground fixture;可执行 `.ebox` reference 已迁到独立 `ebox-playground` 包。
|
||||||
|
- Ebox transaction route 开关、v1 TP participant adapter 调用,以及 capability
|
||||||
|
缺失时的 fallback。
|
||||||
|
|
||||||
### 迁移
|
### 迁移
|
||||||
|
|
||||||
- ECSS 0.1.0 与 TP 1.0.1 互相独立,安装顺序任意;两者都安装后再安装 Ebox 2.0.1。
|
- ECSS 0.1.0 与 TP 1.0.1 或更高版本互相独立,安装顺序任意;两者都安装后再安装
|
||||||
|
Ebox 3.0.0。
|
||||||
|
- 删除 `ebox-transaction-participant-route` 的自定义设置;Ebox 3.0.0 要求 TP
|
||||||
|
提供 structured participant capability,缺失或格式错误时会 fail closed。
|
||||||
- 数字 region id 更新改为先调用 `ebox-region-resolve`,再把返回的 handle 交给 `ebox-region-update`。
|
- 数字 region id 更新改为先调用 `ebox-region-resolve`,再把返回的 handle 交给 `ebox-region-update`。
|
||||||
- `ebox-pop-to-buffer` 或 `ebox-switch-to-buffer` 改为 `ebox-display-buffer`;如果显示行为由调用方控制,则使用 `ebox-render-to-buffer`。
|
- `ebox-pop-to-buffer` 或 `ebox-switch-to-buffer` 改为 `ebox-display-buffer`;如果显示行为由调用方控制,则使用 `ebox-render-to-buffer`。
|
||||||
- Ebox 不扫描历史渲染文本来重建 runtime identity;请通过 Ebox 2.0 重新 mount 声明式 source。
|
- Ebox 不扫描历史渲染文本来重建 runtime identity;请通过 Ebox 3.0 重新 mount 声明式 source。
|
||||||
|
|||||||
4
Makefile
4
Makefile
@ -10,7 +10,7 @@ NATIVE_MANIFEST = native/Cargo.toml
|
|||||||
NATIVE_TARGET ?= $(shell $(EMACS_BATCH) -l ebox-native-reflow.el --eval '(princ (ebox-native-reflow--rust-target))')
|
NATIVE_TARGET ?= $(shell $(EMACS_BATCH) -l ebox-native-reflow.el --eval '(princ (ebox-native-reflow--rust-target))')
|
||||||
NATIVE_RELEASE_DIR = native/target/$(NATIVE_TARGET)/release
|
NATIVE_RELEASE_DIR = native/target/$(NATIVE_TARGET)/release
|
||||||
|
|
||||||
.PHONY: all check ci load compile test checkdoc source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests m0a-tests state-contract-tests layout-boundary-tests layout-boundary-performance patch-plan-tests patch-plan-performance style-schema-tests style-schema-performance spi-tests spi-performance docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
.PHONY: all check ci load compile test checkdoc source-tests font-tests core-tests child-range-tests grid-tests ebox-commit-tests surface-tests visual-check-tests package-tests selector-tests dsl-tests flex-tests m0a-tests state-contract-tests layout-boundary-tests layout-boundary-performance patch-plan-tests patch-plan-performance style-schema-tests style-schema-performance spi-tests spi-performance c1b-contract-tests docs-contract-tests ci-contract-tests performance-evaluator visual-check native-rust-tests native-build diff-check clean package-lint package-lint-install
|
||||||
|
|
||||||
all: check
|
all: check
|
||||||
|
|
||||||
@ -90,6 +90,8 @@ style-schema-performance:
|
|||||||
spi-tests:
|
spi-tests:
|
||||||
$(EMACS_TEST) -l tests/ebox-spi-tests.el -f ert-run-tests-batch-and-exit
|
$(EMACS_TEST) -l tests/ebox-spi-tests.el -f ert-run-tests-batch-and-exit
|
||||||
|
|
||||||
|
c1b-contract-tests: ebox-commit-tests spi-tests package-tests docs-contract-tests
|
||||||
|
|
||||||
spi-performance:
|
spi-performance:
|
||||||
$(EMACS_TEST) -l tests/ebox-spi-tests.el --eval "(ert-run-tests-batch-and-exit 'ebox-spi-performance-builds-bounded-provider-snapshots)"
|
$(EMACS_TEST) -l tests/ebox-spi-tests.el --eval "(ert-run-tests-batch-and-exit 'ebox-spi-performance-builds-bounded-provider-snapshots)"
|
||||||
|
|
||||||
|
|||||||
14
README.md
14
README.md
@ -7,7 +7,8 @@ application also needs Components, reactive state, behaviors, or lifecycle.
|
|||||||
|
|
||||||
## Install
|
## Install
|
||||||
|
|
||||||
Ebox requires Emacs 29.1 or newer, ECSS, and TP. A package manager should
|
Ebox requires Emacs 29.1 or newer, ECSS 0.1.0 or newer, and TP 1.0.1 or newer.
|
||||||
|
A package manager should
|
||||||
install the declared dependencies. For sibling source checkouts, add the three
|
install the declared dependencies. For sibling source checkouts, add the three
|
||||||
directories to `load-path`, then load Ebox:
|
directories to `load-path`, then load Ebox:
|
||||||
|
|
||||||
@ -67,11 +68,11 @@ do not require a wrapper node.
|
|||||||
Ebox copies canonical input before assigning runtime identity, so one built
|
Ebox copies canonical input before assigning runtime identity, so one built
|
||||||
value may be mounted in multiple buffers without sharing live ownership.
|
value may be mounted in multiple buffers without sharing live ownership.
|
||||||
|
|
||||||
Ebox registers its rollback-capable TP state through the public structured v2
|
Ebox registers rollback-capable state only through TP's public structured
|
||||||
participant API by default. Set `ebox-transaction-participant-route` to `v1`
|
participant API. TP 1.0.1 supports the consumer-first migration protocol;
|
||||||
before the next operation for an immediate old-route rollback. If the loaded TP
|
TP 2.0.0 publishes the final v2-only protocol. Missing or malformed structured
|
||||||
manifest predates the structured API, the default v2 policy selects the same
|
participant capability stops Ebox loading instead of selecting a compatibility
|
||||||
complete v1 adapter automatically; the two registrations are never run together.
|
writer.
|
||||||
|
|
||||||
## Optional native module
|
## Optional native module
|
||||||
|
|
||||||
@ -89,6 +90,7 @@ exact Elisp fallback. Ebox never builds it while loading.
|
|||||||
make load EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
make load EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
make compile EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
make compile EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
make docs-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
make docs-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
|
make c1b-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
make native-rust-tests
|
make native-rust-tests
|
||||||
```
|
```
|
||||||
|
|||||||
@ -6,8 +6,9 @@ Component、响应式状态、behavior 或生命周期时,使用同级 ETAF
|
|||||||
|
|
||||||
## 安装
|
## 安装
|
||||||
|
|
||||||
Ebox 需要 Emacs 29.1 或更高版本,并依赖 ECSS 与 TP。包管理器应自动安装声明的
|
Ebox 需要 Emacs 29.1 或更高版本、ECSS 0.1.0 或更高版本,以及 TP 1.0.1 或
|
||||||
依赖。使用同级源码 checkout 时,把三个目录加入 `load-path` 后加载 Ebox:
|
更高版本。包管理器应自动安装声明的依赖。使用同级源码 checkout 时,把三个目录
|
||||||
|
加入 `load-path` 后加载 Ebox:
|
||||||
|
|
||||||
```elisp
|
```elisp
|
||||||
(add-to-list 'load-path "/path/to/ecss")
|
(add-to-list 'load-path "/path/to/ecss")
|
||||||
@ -62,10 +63,9 @@ Flex/Grid participation property 直接属于子 `box`,不需要额外 wrapper
|
|||||||
Ebox 会在分配 runtime identity 前复制 canonical input,因此同一个 built value 可以
|
Ebox 会在分配 runtime identity 前复制 canonical input,因此同一个 built value 可以
|
||||||
挂载到多个 buffer,而不会共享 live ownership。
|
挂载到多个 buffer,而不会共享 live ownership。
|
||||||
|
|
||||||
Ebox 默认通过 TP 的公开 structured v2 participant API 注册可回滚状态。下一次
|
Ebox 只通过 TP 的公开 structured participant API 注册可回滚状态。TP 1.0.1
|
||||||
operation 前把 `ebox-transaction-participant-route` 设为 `v1`,即可立即回到旧
|
支持 consumer-first 迁移协议;TP 2.0.0 发布最终的 v2-only 协议。structured
|
||||||
route。若加载的 TP manifest 尚未提供 structured API,默认 v2 policy 会自动选择
|
participant capability 缺失或格式错误时,Ebox 会停止加载,不会选择兼容 writer。
|
||||||
同一条完整 v1 adapter;两种 registration 绝不会同时运行。
|
|
||||||
|
|
||||||
## 可选 native 模块
|
## 可选 native 模块
|
||||||
|
|
||||||
@ -83,6 +83,7 @@ fallback。Ebox 加载时不会自动构建它。
|
|||||||
make load EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
make load EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
make compile EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
make compile EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
make docs-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
make docs-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
|
make c1b-contract-tests EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
make check EMACS=/Applications/Emacs.app/Contents/MacOS/Emacs
|
||||||
make native-rust-tests
|
make native-rust-tests
|
||||||
```
|
```
|
||||||
|
|||||||
@ -86,17 +86,20 @@ Caller-owned Source Tree
|
|||||||
- Keys are local to siblings; visible strings are never used as identity.
|
- 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 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-definitions` is 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.
|
- `ebox-style--property-definitions` is 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 current v1 storage shape plus its target owner, mutation API, generation binding, rollback, rebuild proof, and cleanup. Current v1 TP client state still holds the whole Ebox state plist until a later checkpoint changes that storage shape. The E1 mirror probes only rebuild and compare projections; they never mutate live state.
|
- 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.el` has no load or call edge to `ebox-surface.el` or TP. Candidate isolation, inherited-cascade detection, and one-shot materialization enter layout only through the validated port owned by `ebox-render-context.el`; `ebox.el` wires the surface implementation after both sides load.
|
- `ebox-layout.el` has no load or call edge to `ebox-surface.el` or TP. Candidate isolation, inherited-cascade detection, and one-shot materialization enter layout only through the validated port owned by `ebox-render-context.el`; `ebox.el` wires the surface implementation after both sides load.
|
||||||
- `ebox-incremental.el` likewise contains no `ebox-surface` symbol or publication call. `ebox-surface.el` installs one immutable read-only context port for live client state, region mounts, cascade proof, and inline-style preparation; batch observation remains in the higher `ebox.el` orchestrator.
|
- `ebox-incremental.el` likewise contains no `ebox-surface` symbol or publication call. `ebox-surface.el` installs one immutable read-only context port for live client state, region mounts, cascade proof, and inline-style preparation; batch observation remains in the higher `ebox.el` orchestrator.
|
||||||
- `ebox-patch-plan.el` consumes 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 explicit `legacy` and equality-checking `shadow` routes; none of the three routes publishes.
|
- `ebox-patch-plan.el` consumes 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 explicit `legacy` and equality-checking `shadow` routes; none of the three routes publishes.
|
||||||
- `ebox-style--property-definitions` is 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-style--property-definitions` is 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-capabilities` returns a fresh immutable v2 provider record declaring paired initial/update stage+rollback, combined participant ordering, same-object legacy reports, initial observation replay, and `tp-transaction-protocol-v1+v2`. 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; the existing v1 callback APIs remain available.
|
- `ebox-framework-spi-capabilities` returns 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-version` exposes the minimum TP 1.0.1 dependency, while `ebox-framework-spi-supported-tp-protocols` lists the consumer-first and final v2 protocol identities. 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's TP adapter defaults to the manifest-advertised public
|
- Ebox registers every rollback-capable runtime mirror through TP's public
|
||||||
`tp-transaction-participate-v2` API. `ebox-transaction-participant-route=v1`
|
`tp-transaction-participate-v2` API. The loaded manifest must advertise that
|
||||||
is the immediate kill switch, and a TP manifest without the structured API
|
callable API with an accepted structured protocol; missing or malformed
|
||||||
selects the same v1 adapter as a compatibility fallback. One helper owns this
|
capability fails closed during load.
|
||||||
selection and registers exactly one participant per transaction.
|
- 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-finalization` diagnostics; they cannot invoke framework rollback or escape into an ETAF semantic rollback window.
|
- 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-finalization` diagnostics; they cannot invoke framework rollback or escape into an ETAF semantic rollback window.
|
||||||
- `owner-rerender` is broader than `span-patch`, which is broader than `paint-patch`.
|
- `owner-rerender` is broader than `span-patch`, which is broader than `paint-patch`.
|
||||||
- Buffer coordinates belong to the generation that produced them and must be refreshed after mutation.
|
- Buffer coordinates belong to the generation that produced them and must be refreshed after mutation.
|
||||||
@ -144,6 +147,7 @@ make style-schema-tests
|
|||||||
make style-schema-performance
|
make style-schema-performance
|
||||||
make spi-tests
|
make spi-tests
|
||||||
make spi-performance
|
make spi-performance
|
||||||
|
make c1b-contract-tests
|
||||||
make docs-contract-tests
|
make docs-contract-tests
|
||||||
make ci-contract-tests
|
make ci-contract-tests
|
||||||
make performance-evaluator
|
make performance-evaluator
|
||||||
|
|||||||
@ -86,16 +86,19 @@ active 合同还覆盖 `Makefile`、`.github/workflows/ci.yml`、`tests/ebox-cor
|
|||||||
- Key 只在兄弟节点中有效;不能用可见字符串作为 identity。
|
- Key 只在兄弟节点中有效;不能用可见字符串作为 identity。
|
||||||
- Ebox 只拥有逻辑 node-to-subject 适配与 id/class/type 候选索引;ECSS 的公共结构化 matcher 是唯一 selector 真相源。subject 只暴露内建 id/key 和显式 `:selector-attributes`,绝不暴露可见 content、布局状态或 runtime 容器。
|
- Ebox 只拥有逻辑 node-to-subject 适配与 id/class/type 候选索引;ECSS 的公共结构化 matcher 是唯一 selector 真相源。subject 只暴露内建 id/key 和显式 `:selector-attributes`,绝不暴露可见 content、布局状态或 runtime 容器。
|
||||||
- `ebox-style--property-definitions` 是 Ebox 作者属性的唯一来源:加载时一次生成 Ebox 自己的只读查询索引与一个不可变 ECSS package schema;surface schema 只组合一次,node 构造和更新不重复注册或复制整份 schema。
|
- `ebox-style--property-definitions` 是 Ebox 作者属性的唯一来源:加载时一次生成 Ebox 自己的只读查询索引与一个不可变 ECSS package schema;surface schema 只组合一次,node 构造和更新不重复注册或复制整份 schema。
|
||||||
- E1 固化封闭的 M2a 目标分类:不可变 generation fact、绑定 generation 的 scroll/native authority、opaque TP client-state custody、单向 compatibility mirror,以及 disposable cache。每一行分别记录当前 v1 存储形态,以及目标 owner、mutation API、generation binding、rollback、rebuild proof 与 cleanup。在后续 checkpoint 改变存储形态前,当前 v1 TP client state 仍保存完整 Ebox state plist。E1 mirror 探针只重建并比较投影,不修改 live state。
|
- E1 固化封闭的 M2a 目标分类:不可变 generation fact、绑定 generation 的 scroll/native authority、opaque TP client-state custody、单向 compatibility mirror,以及 disposable cache。每一行分别记录 M2a 阶段的存储形态,以及目标 owner、mutation API、generation binding、rollback、rebuild proof 与 cleanup。TP client state 保存完整 Ebox state plist。E1 mirror 探针只重建并比较投影,不修改 live state。
|
||||||
- `ebox-layout.el` 不再 load 或调用 `ebox-surface.el`/TP。candidate isolation、继承 cascade 判定和一次性 materialization 只能通过 `ebox-render-context.el` 拥有的已校验 port 进入 layout;`ebox.el` 在两侧加载后接线 surface 实现。
|
- `ebox-layout.el` 不再 load 或调用 `ebox-surface.el`/TP。candidate isolation、继承 cascade 判定和一次性 materialization 只能通过 `ebox-render-context.el` 拥有的已校验 port 进入 layout;`ebox.el` 在两侧加载后接线 surface 实现。
|
||||||
- `ebox-incremental.el` 同样不再包含任何 `ebox-surface` symbol 或 publication call。`ebox-surface.el` 只安装一个不可变只读 context port,提供 live client state、region mount、cascade proof 与 inline-style preparation;batch observation 留在更高层的 `ebox.el` orchestrator。
|
- `ebox-incremental.el` 同样不再包含任何 `ebox-surface` symbol 或 publication call。`ebox-surface.el` 只安装一个不可变只读 context port,提供 live client state、region mount、cascade proof 与 inline-style preparation;batch observation 留在更高层的 `ebox.el` orchestrator。
|
||||||
- `ebox-patch-plan.el` 只消费 tentative operation plist 与不可变 generation parent table,并返回 deterministic plan artifact;所有 live fact 准备仍由 incremental adapter 拥有。E3 默认使用 pure route,同时保留显式 `legacy` 与执行等价检查的 `shadow` route;三条 route 都不能发布。
|
- `ebox-patch-plan.el` 只消费 tentative operation plist 与不可变 generation parent table,并返回 deterministic plan artifact;所有 live fact 准备仍由 incremental adapter 拥有。E3 默认使用 pure route,同时保留显式 `legacy` 与执行等价检查的 `shadow` route;三条 route 都不能发布。
|
||||||
- `ebox-style--property-definitions` 同时也是 used-value projection 的唯一真相源。非默认 engine lowering 写在每个 property 的 `:engine-projection` 中;ECSS metadata 暴露派生的 `:engine-targets`,只有非公共 engine 字段可以进入小型 internal exception table。
|
- `ebox-style--property-definitions` 同时也是 used-value projection 的唯一真相源。非默认 engine lowering 写在每个 property 的 `:engine-projection` 中;ECSS metadata 暴露派生的 `:engine-targets`,只有非公共 engine 字段可以进入小型 internal exception table。
|
||||||
- `ebox-framework-spi-capabilities` 返回 fresh immutable v2 provider record,声明 paired initial/update stage+rollback、combined participant order、same-object legacy report、initial observation replay 以及 `tp-transaction-protocol-v1+v2`。initial TP/Ebox timing 在 publication 期间真实测量,以 defensive snapshot 写入 completed report 后再 replay;不生成虚假的零耗时数据,也不携带 mutate authority。Ebox 不创建 selected port 或 consumer bootstrap;现有 v1 callback API 保留。
|
- `ebox-framework-spi-capabilities` 返回 fresh immutable SPI v2 provider record,声明 paired initial/update stage+rollback、combined participant order、same-object report、initial observation replay,以及实际接受的 TP protocol。`ebox-framework-spi-required-tp-version` 公开最低 TP 1.0.1 依赖,`ebox-framework-spi-supported-tp-protocols` 列出 consumer-first 与最终 v2 protocol identity。initial TP/Ebox timing 在 publication 期间真实测量,以 defensive snapshot 写入 completed report 后再 replay;不生成虚假的零耗时数据,也不携带 mutate authority。Ebox 不创建 selected port 或 consumer bootstrap。
|
||||||
- Ebox 的 TP adapter 默认使用 manifest 声明的公开
|
- Ebox 始终通过 TP 的公开 `tp-transaction-participate-v2` API 注册可 rollback 的
|
||||||
`tp-transaction-participate-v2` API;`ebox-transaction-participant-route=v1`
|
runtime mirror。加载的 manifest 必须用 accepted structured protocol 声明该可调用
|
||||||
是立即 kill switch。TP manifest 未提供 structured API 时,同一个 selection
|
API;capability 缺失或格式错误会在加载时 fail closed。
|
||||||
owner 会选择完整 v1 adapter;每个 transaction 只注册一个 participant。
|
- SPI initial publication 由 provider 负责 widen,并在 TP transaction 外包一层
|
||||||
|
editor change group。任何失败或非局部退出都会恢复原 overlay identity/bounds,
|
||||||
|
以及 buffer text、marker identity、point、mark、narrowing、undo history 与
|
||||||
|
modified state。
|
||||||
- Framework report finalization 属于 postaccept 且保证不向外抛出。initial、full update 与 scoped update 的 completion fault 都以 `framework-report-finalization` diagnostics 留在同一个 legacy report 中,不能触发 framework rollback,也不能逃逸进 ETAF semantic rollback window。
|
- Framework report finalization 属于 postaccept 且保证不向外抛出。initial、full update 与 scoped update 的 completion fault 都以 `framework-report-finalization` diagnostics 留在同一个 legacy report 中,不能触发 framework rollback,也不能逃逸进 ETAF semantic rollback window。
|
||||||
- `owner-rerender` 范围大于 `span-patch`,`span-patch` 大于 `paint-patch`。
|
- `owner-rerender` 范围大于 `span-patch`,`span-patch` 大于 `paint-patch`。
|
||||||
- Buffer 坐标属于生成它的 generation,变更后必须重新获取。
|
- Buffer 坐标属于生成它的 generation,变更后必须重新获取。
|
||||||
@ -140,6 +143,7 @@ make style-schema-tests
|
|||||||
make style-schema-performance
|
make style-schema-performance
|
||||||
make spi-tests
|
make spi-tests
|
||||||
make spi-performance
|
make spi-performance
|
||||||
|
make c1b-contract-tests
|
||||||
make docs-contract-tests
|
make docs-contract-tests
|
||||||
make ci-contract-tests
|
make ci-contract-tests
|
||||||
make performance-evaluator
|
make performance-evaluator
|
||||||
|
|||||||
129
ebox-spi.el
129
ebox-spi.el
@ -4,21 +4,24 @@
|
|||||||
|
|
||||||
;;; Commentary:
|
;;; Commentary:
|
||||||
|
|
||||||
;; Additive provider-side Ebox framework SPI v2. This module publishes an
|
;; Provider-side Ebox framework SPI v2. This module publishes an
|
||||||
;; immutable capability snapshot and delegates initial/update execution to the
|
;; immutable capability snapshot and delegates initial/update execution to the
|
||||||
;; existing combined Ebox participant. It does not select a consumer port,
|
;; existing combined Ebox participant. It does not select a consumer port,
|
||||||
;; probe a framework package, or remove the v1 callback surface.
|
;; or probe a framework package.
|
||||||
|
|
||||||
;;; Code:
|
;;; Code:
|
||||||
|
|
||||||
(require 'cl-lib)
|
(require 'cl-lib)
|
||||||
(require 'ebox-canonical)
|
(require 'ebox-canonical)
|
||||||
(require 'ebox-surface)
|
(require 'ebox-surface)
|
||||||
(defvar tp-transaction-protocol)
|
(require 'tp-transaction)
|
||||||
|
|
||||||
(defconst ebox-framework-spi--tp-v2-available-p
|
(defconst ebox-framework-spi-required-tp-version "1.0.1"
|
||||||
(require 'tp-transaction nil t)
|
"Minimum TP package version required by Ebox framework SPI v2.")
|
||||||
"Non-nil when TP exposes the additive transaction protocol module.")
|
|
||||||
|
(defconst ebox-framework-spi-supported-tp-protocols
|
||||||
|
'(tp-transaction-protocol-v1+v2 tp-transaction-protocol-v2)
|
||||||
|
"TP protocols that provide the structured participant API Ebox requires.")
|
||||||
|
|
||||||
(declare-function ebox--render-to-buffer-internal
|
(declare-function ebox--render-to-buffer-internal
|
||||||
"ebox" (buffer-or-name input options &optional participant))
|
"ebox" (buffer-or-name input options &optional participant))
|
||||||
@ -144,6 +147,58 @@
|
|||||||
(signal 'wrong-type-argument (list 'functionp framework-rollback)))
|
(signal 'wrong-type-argument (list 'functionp framework-rollback)))
|
||||||
t)
|
t)
|
||||||
|
|
||||||
|
(defun ebox-framework-spi--buffer-overlays (buffer)
|
||||||
|
"Return every overlay currently attached to BUFFER."
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(let ((lists (overlay-lists)))
|
||||||
|
(delete-dups (append (car lists) (cdr lists))))))
|
||||||
|
|
||||||
|
(defun ebox-framework-spi--snapshot-editor-custody (buffer)
|
||||||
|
"Capture BUFFER state that an unsuccessful initial mount must preserve."
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(let ((restriction-start (point-min))
|
||||||
|
(restriction-end (point-max)))
|
||||||
|
(save-restriction
|
||||||
|
(widen)
|
||||||
|
(list
|
||||||
|
:point (point)
|
||||||
|
:mark-marker (mark-marker)
|
||||||
|
:mark-position (mark t)
|
||||||
|
:mark-insertion-type (marker-insertion-type (mark-marker))
|
||||||
|
:mark-active mark-active
|
||||||
|
:restriction-start restriction-start
|
||||||
|
:restriction-end restriction-end
|
||||||
|
:overlays
|
||||||
|
(mapcar (lambda (overlay)
|
||||||
|
(list overlay (overlay-start overlay) (overlay-end overlay)))
|
||||||
|
(ebox-framework-spi--buffer-overlays buffer))
|
||||||
|
:undo-list (copy-tree buffer-undo-list)
|
||||||
|
:modified-p (buffer-modified-p))))))
|
||||||
|
|
||||||
|
(defun ebox-framework-spi--restore-editor-custody (buffer snapshot)
|
||||||
|
"Restore BUFFER editor state from SNAPSHOT after mount rollback."
|
||||||
|
(when (buffer-live-p buffer)
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(widen)
|
||||||
|
(let* ((overlay-snapshots (plist-get snapshot :overlays))
|
||||||
|
(original-overlays (mapcar #'car overlay-snapshots)))
|
||||||
|
(dolist (overlay (ebox-framework-spi--buffer-overlays buffer))
|
||||||
|
(unless (memq overlay original-overlays)
|
||||||
|
(delete-overlay overlay)))
|
||||||
|
(dolist (entry overlay-snapshots)
|
||||||
|
(when (overlayp (car entry))
|
||||||
|
(move-overlay (car entry) (nth 1 entry) (nth 2 entry) buffer))))
|
||||||
|
(let ((mark-marker (plist-get snapshot :mark-marker)))
|
||||||
|
(set-marker mark-marker (plist-get snapshot :mark-position) buffer)
|
||||||
|
(set-marker-insertion-type
|
||||||
|
mark-marker (plist-get snapshot :mark-insertion-type)))
|
||||||
|
(goto-char (plist-get snapshot :point))
|
||||||
|
(setq mark-active (plist-get snapshot :mark-active)
|
||||||
|
buffer-undo-list (copy-tree (plist-get snapshot :undo-list)))
|
||||||
|
(narrow-to-region (plist-get snapshot :restriction-start)
|
||||||
|
(plist-get snapshot :restriction-end))
|
||||||
|
(set-buffer-modified-p (plist-get snapshot :modified-p)))))
|
||||||
|
|
||||||
(defun ebox-framework-spi-initial
|
(defun ebox-framework-spi-initial
|
||||||
(buffer-or-name input framework-stage framework-rollback)
|
(buffer-or-name input framework-stage framework-rollback)
|
||||||
"Initially mount INPUT and run the paired framework callbacks.
|
"Initially mount INPUT and run the paired framework callbacks.
|
||||||
@ -160,10 +215,10 @@ transaction failure. Return that same report object after completion."
|
|||||||
(let ((buffer (get-buffer-create buffer-or-name)))
|
(let ((buffer (get-buffer-create buffer-or-name)))
|
||||||
(when (ebox-surface-buffer-mounted-p buffer)
|
(when (ebox-surface-buffer-mounted-p buffer)
|
||||||
(error "Ebox SPI initial operation requires an unmounted buffer"))
|
(error "Ebox SPI initial operation requires an unmounted buffer"))
|
||||||
(let* ((existing-bridge
|
(let* ((custody (ebox-framework-spi--snapshot-editor-custody buffer))
|
||||||
|
(change-group (with-current-buffer buffer (prepare-change-group)))
|
||||||
|
(existing-bridge
|
||||||
(with-current-buffer buffer ebox-surface--tp-observer))
|
(with-current-buffer buffer ebox-surface--tp-observer))
|
||||||
(_bridge (or existing-bridge
|
|
||||||
(ebox-surface--ensure-tp-observer buffer)))
|
|
||||||
(started (float-time))
|
(started (float-time))
|
||||||
(participant
|
(participant
|
||||||
(ebox-surface--make-framework-participant
|
(ebox-surface--make-framework-participant
|
||||||
@ -177,11 +232,29 @@ transaction failure. Return that same report object after completion."
|
|||||||
(cl-remf report :framework-initial-observation-started)
|
(cl-remf report :framework-initial-observation-started)
|
||||||
(funcall framework-rollback report))
|
(funcall framework-rollback report))
|
||||||
:state 'unpublished
|
:state 'unpublished
|
||||||
:diagnostics nil)))
|
:diagnostics nil))
|
||||||
|
activated success)
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
(ebox--render-to-buffer-internal buffer input nil participant)
|
(progn
|
||||||
(unless existing-bridge
|
(unless existing-bridge
|
||||||
(ebox-surface-cleanup-buffer-observer buffer))))))
|
(ebox-surface--ensure-tp-observer buffer))
|
||||||
|
(with-current-buffer buffer (activate-change-group change-group))
|
||||||
|
(setq activated t)
|
||||||
|
(prog1 (with-current-buffer buffer
|
||||||
|
(save-restriction
|
||||||
|
(widen)
|
||||||
|
(ebox--render-to-buffer-internal
|
||||||
|
buffer input nil participant)))
|
||||||
|
(with-current-buffer buffer (accept-change-group change-group))
|
||||||
|
(setq success t)))
|
||||||
|
(unwind-protect
|
||||||
|
(unless success
|
||||||
|
(when (and activated (buffer-live-p buffer))
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(cancel-change-group change-group))
|
||||||
|
(ebox-framework-spi--restore-editor-custody buffer custody)))
|
||||||
|
(unless existing-bridge
|
||||||
|
(ebox-surface-cleanup-buffer-observer buffer)))))))
|
||||||
|
|
||||||
(defun ebox-framework-spi-update
|
(defun ebox-framework-spi-update
|
||||||
(buffer-or-name input framework-stage framework-rollback)
|
(buffer-or-name input framework-stage framework-rollback)
|
||||||
@ -264,8 +337,8 @@ or promotion capability."
|
|||||||
ebox-framework-spi-schema-version)
|
ebox-framework-spi-schema-version)
|
||||||
(equal (ebox-framework-spi--provider-capabilities provider)
|
(equal (ebox-framework-spi--provider-capabilities provider)
|
||||||
ebox-framework-spi--required-capabilities)
|
ebox-framework-spi--required-capabilities)
|
||||||
(eq (ebox-framework-spi--provider-tp-protocol provider)
|
(memq (ebox-framework-spi--provider-tp-protocol provider)
|
||||||
tp-transaction-protocol)
|
ebox-framework-spi-supported-tp-protocols)
|
||||||
(equal (ebox-framework-spi--provider-stage-order provider)
|
(equal (ebox-framework-spi--provider-stage-order provider)
|
||||||
ebox-framework-spi--stage-order)
|
ebox-framework-spi--stage-order)
|
||||||
(equal (ebox-framework-spi--provider-rollback-order provider)
|
(equal (ebox-framework-spi--provider-rollback-order provider)
|
||||||
@ -308,31 +381,7 @@ or promotion capability."
|
|||||||
(ebox-framework-spi-validate-provider provider)
|
(ebox-framework-spi-validate-provider provider)
|
||||||
provider))
|
provider))
|
||||||
|
|
||||||
(if ebox-framework-spi--tp-v2-available-p
|
(provide 'ebox-framework-spi-v2)
|
||||||
(provide 'ebox-framework-spi-v2)
|
|
||||||
(dolist (function
|
|
||||||
'(ebox-framework-spi-operation-kind
|
|
||||||
ebox-framework-spi-operation-function
|
|
||||||
ebox-framework-spi-operation-argument-schema
|
|
||||||
ebox-framework-spi-operation-result-schema
|
|
||||||
ebox-framework-spi-operation-paired-stage-rollback-p
|
|
||||||
ebox-framework-spi-provider-spi-version
|
|
||||||
ebox-framework-spi-provider-schema-version
|
|
||||||
ebox-framework-spi-provider-capabilities
|
|
||||||
ebox-framework-spi-provider-tp-protocol
|
|
||||||
ebox-framework-spi-provider-stage-order
|
|
||||||
ebox-framework-spi-provider-rollback-order
|
|
||||||
ebox-framework-spi-provider-report-semantics
|
|
||||||
ebox-framework-spi-provider-initial-operation
|
|
||||||
ebox-framework-spi-provider-update-operation
|
|
||||||
ebox-framework-spi-initial
|
|
||||||
ebox-framework-spi-update
|
|
||||||
ebox-framework-spi-initial-observation-reports
|
|
||||||
ebox-framework-spi-validate-provider
|
|
||||||
ebox-framework-spi-capabilities))
|
|
||||||
(when (symbol-function function)
|
|
||||||
(fmakunbound function)))
|
|
||||||
(setq features (delq 'ebox-framework-spi-v2 features)))
|
|
||||||
(provide 'ebox-spi)
|
(provide 'ebox-spi)
|
||||||
|
|
||||||
;;; ebox-spi.el ends here
|
;;; ebox-spi.el ends here
|
||||||
|
|||||||
@ -47,50 +47,36 @@
|
|||||||
(defvar ebox--render-display-signature)
|
(defvar ebox--render-display-signature)
|
||||||
(defvar ebox-incremental--allocated-slot-proof-cache)
|
(defvar ebox-incremental--allocated-slot-proof-cache)
|
||||||
(defvar ebox-incremental--buffer-render-state-override)
|
(defvar ebox-incremental--buffer-render-state-override)
|
||||||
(defvar ebox-transaction-participant-route 'v2
|
|
||||||
"Early default for the Ebox-owned TP participant route.")
|
|
||||||
(declare-function tp-runtime-manifest "tp" ())
|
(declare-function tp-runtime-manifest "tp" ())
|
||||||
(declare-function tp-transaction-participate-v2 "tp-reactive" (&rest args))
|
(declare-function tp-transaction-participate-v2 "tp-reactive" (&rest args))
|
||||||
|
|
||||||
(define-error 'ebox-surface-participant-route-error
|
(define-error 'ebox-surface-tp-protocol-error
|
||||||
"Invalid Ebox TP participant route")
|
"Incompatible TP transaction protocol")
|
||||||
|
|
||||||
(defun ebox-surface--tp-participant-v2-capability-p (manifest)
|
(defun ebox-surface--validate-tp-v2-capability (manifest)
|
||||||
"Return non-nil when MANIFEST provides the valid public TP v2 API.
|
"Validate MANIFEST as the required public TP v2 participant capability."
|
||||||
An absent capability denotes an older compatible TP and returns nil. A
|
(let ((api (plist-get manifest :structured-participant-api))
|
||||||
present but malformed capability fails closed instead of masking corruption."
|
(protocol (plist-get manifest :transaction-protocol)))
|
||||||
(let ((api (plist-get manifest :structured-participant-api)))
|
(unless (and (memq protocol
|
||||||
(cond
|
'(tp-transaction-protocol-v1+v2
|
||||||
((null api) nil)
|
tp-transaction-protocol-v2))
|
||||||
((and (eq (plist-get manifest :transaction-protocol)
|
(eq api 'tp-transaction-participate-v2)
|
||||||
'tp-transaction-protocol-v1+v2)
|
(fboundp api))
|
||||||
(eq api 'tp-transaction-participate-v2)
|
(signal 'ebox-surface-tp-protocol-error
|
||||||
(fboundp api))
|
(list :accepted-protocols
|
||||||
t)
|
'(tp-transaction-protocol-v1+v2
|
||||||
(t
|
tp-transaction-protocol-v2)
|
||||||
(signal 'ebox-surface-participant-route-error
|
:manifest manifest)))
|
||||||
(list :malformed-tp-structured-participant manifest))))))
|
t))
|
||||||
|
|
||||||
(defconst ebox-surface--tp-participant-v2-available-p
|
(defconst ebox-surface--tp-v2-capability-valid-p
|
||||||
(ebox-surface--tp-participant-v2-capability-p (tp-runtime-manifest))
|
(ebox-surface--validate-tp-v2-capability (tp-runtime-manifest))
|
||||||
"Non-nil when the loaded TP manifest advertises its public v2 API.")
|
"Non-nil after the loaded TP manifest passes the required v2 contract.")
|
||||||
|
|
||||||
(defun ebox-surface--effective-participant-route ()
|
|
||||||
"Return the selected TP participant route for the next registration."
|
|
||||||
(pcase ebox-transaction-participant-route
|
|
||||||
('v2 (if ebox-surface--tp-participant-v2-available-p 'v2 'v1))
|
|
||||||
('v1 'v1)
|
|
||||||
(_ (signal 'ebox-surface-participant-route-error
|
|
||||||
(list :participant-route ebox-transaction-participant-route)))))
|
|
||||||
|
|
||||||
(defun ebox-surface--transaction-participate (key stage rollback)
|
(defun ebox-surface--transaction-participate (key stage rollback)
|
||||||
"Register KEY with paired STAGE and ROLLBACK through one selected TP route."
|
"Register KEY with paired STAGE and ROLLBACK through TP v2."
|
||||||
(pcase (ebox-surface--effective-participant-route)
|
(tp-transaction-participate-v2
|
||||||
('v2
|
:key key :stage stage :rollback rollback))
|
||||||
(tp-transaction-participate-v2
|
|
||||||
:key key :stage stage :rollback rollback))
|
|
||||||
('v1
|
|
||||||
(tp-transaction-participate key stage rollback))))
|
|
||||||
|
|
||||||
(defvar ebox-surface--inline-style-value-cache nil
|
(defvar ebox-surface--inline-style-value-cache nil
|
||||||
"Per-projection cache for selector-free computed inline styles.")
|
"Per-projection cache for selector-free computed inline styles.")
|
||||||
|
|||||||
11
ebox.el
11
ebox.el
@ -1,7 +1,7 @@
|
|||||||
;;; ebox.el --- Pixel-perfect box model renderer -*- lexical-binding: t -*-
|
;;; ebox.el --- Pixel-perfect box model renderer -*- lexical-binding: t -*-
|
||||||
|
|
||||||
;; Author: ebox contributors
|
;; Author: ebox contributors
|
||||||
;; Version: 2.0.1
|
;; Version: 3.0.0
|
||||||
;; Package-Requires: ((emacs "29.1") (ecss "0.1.0") (tp "1.0.1"))
|
;; Package-Requires: ((emacs "29.1") (ecss "0.1.0") (tp "1.0.1"))
|
||||||
;; Keywords: ui, graphics, convenience
|
;; Keywords: ui, graphics, convenience
|
||||||
;; URL: https://github.com/ginqi7/ebox
|
;; URL: https://github.com/ginqi7/ebox
|
||||||
@ -100,15 +100,6 @@
|
|||||||
:group 'applications
|
:group 'applications
|
||||||
:prefix "ebox-")
|
:prefix "ebox-")
|
||||||
|
|
||||||
(defcustom ebox-transaction-participant-route 'v2
|
|
||||||
"TP participant registration route used by new Ebox transactions.
|
|
||||||
`v2' uses TP's public structured participant capability when advertised and
|
|
||||||
falls back to the complete v1 adapter when that capability is absent. `v1'
|
|
||||||
is the immediate rollback switch and always uses the retained adapter."
|
|
||||||
:type '(choice (const :tag "Structured v2 participant" v2)
|
|
||||||
(const :tag "Legacy v1 adapter" v1))
|
|
||||||
:group 'ebox)
|
|
||||||
|
|
||||||
;;;###autoload
|
;;;###autoload
|
||||||
(defun ebox-native-build (&optional clean)
|
(defun ebox-native-build (&optional clean)
|
||||||
"Build and install Ebox's optional native module asynchronously.
|
"Build and install Ebox's optional native module asynchronously.
|
||||||
|
|||||||
@ -74,29 +74,16 @@
|
|||||||
:scroll-region-ids (copy-sequence
|
:scroll-region-ids (copy-sequence
|
||||||
(plist-get state :scroll-region-ids)))))
|
(plist-get state :scroll-region-ids)))))
|
||||||
|
|
||||||
(defvar ebox-surface--tp-participant-v2-available-p)
|
(defun ebox-commit-test--participant-v2-count ()
|
||||||
|
"Return the structured participant registration count for mount and commit."
|
||||||
(defun ebox-commit-test--participant-route-counts
|
(let ((buffer (generate-new-buffer " *ebox-participant-v2*"))
|
||||||
(ebox-route tp-route v2-available-p)
|
|
||||||
"Return v2/v1 registration counts for EBOX-ROUTE and TP-ROUTE.
|
|
||||||
V2-AVAILABLE-P controls the frozen TP manifest capability used by the fixture."
|
|
||||||
(let ((buffer (generate-new-buffer " *ebox-participant-route*"))
|
|
||||||
(ebox-transaction-participant-route ebox-route)
|
|
||||||
(tp-transaction-execution-route tp-route)
|
|
||||||
(ebox-surface--tp-participant-v2-available-p v2-available-p)
|
|
||||||
(v2-register (symbol-function 'tp-transaction-participate-v2))
|
(v2-register (symbol-function 'tp-transaction-participate-v2))
|
||||||
(v1-register (symbol-function 'tp-transaction-participate))
|
(v2-count 0))
|
||||||
(v2-count 0)
|
|
||||||
(v1-count 0))
|
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
(cl-letf (((symbol-function 'tp-transaction-participate-v2)
|
(cl-letf (((symbol-function 'tp-transaction-participate-v2)
|
||||||
(lambda (&rest arguments)
|
(lambda (&rest arguments)
|
||||||
(cl-incf v2-count)
|
(cl-incf v2-count)
|
||||||
(apply v2-register arguments)))
|
(apply v2-register arguments))))
|
||||||
((symbol-function 'tp-transaction-participate)
|
|
||||||
(lambda (&rest arguments)
|
|
||||||
(cl-incf v1-count)
|
|
||||||
(apply v1-register arguments))))
|
|
||||||
(ebox-render-to-buffer
|
(ebox-render-to-buffer
|
||||||
buffer (ebox-test-box :key 'root (ebox-test-text "old")
|
buffer (ebox-test-box :key 'root (ebox-test-text "old")
|
||||||
:width '(80)))
|
:width '(80)))
|
||||||
@ -105,7 +92,7 @@ V2-AVAILABLE-P controls the frozen TP manifest capability used by the fixture."
|
|||||||
:width '(80)))
|
:width '(80)))
|
||||||
(with-current-buffer buffer
|
(with-current-buffer buffer
|
||||||
(should (string-match-p "new" (buffer-string))))
|
(should (string-match-p "new" (buffer-string))))
|
||||||
(list :v2 v2-count :v1 v1-count))
|
v2-count)
|
||||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||||
|
|
||||||
(ert-deftest ebox-source-index-promotes-and-rolls-back-atomically ()
|
(ert-deftest ebox-source-index-promotes-and-rolls-back-atomically ()
|
||||||
@ -954,59 +941,36 @@ V2-AVAILABLE-P controls the frozen TP manifest capability used by the fixture."
|
|||||||
1))))
|
1))))
|
||||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||||
|
|
||||||
(ert-deftest ebox-transaction-participant-route-defaults-v2-across-tp-routes ()
|
(ert-deftest ebox-transaction-participant-is-v2-only ()
|
||||||
"Ebox uses only public v2 registration under either TP live writer."
|
"Mount and commit register only through TP's structured participant API."
|
||||||
(should
|
(should (= (ebox-commit-test--participant-v2-count) 2)))
|
||||||
(eq (eval (car (get 'ebox-transaction-participant-route 'standard-value)) t)
|
|
||||||
'v2))
|
|
||||||
(let ((ebox-transaction-participant-route 'v2))
|
|
||||||
(should (eq (ebox-surface--effective-participant-route) 'v2)))
|
|
||||||
(dolist (tp-route '(structured v1))
|
|
||||||
(let ((counts
|
|
||||||
(ebox-commit-test--participant-route-counts 'v2 tp-route t)))
|
|
||||||
(should (= (plist-get counts :v2) 2))
|
|
||||||
(should (= (plist-get counts :v1) 0)))))
|
|
||||||
|
|
||||||
(ert-deftest ebox-transaction-participant-capability-is-absent-or-valid ()
|
(ert-deftest ebox-transaction-participant-accepts-structured-protocols ()
|
||||||
"Old TP manifests fall back, while advertised corruption fails closed."
|
"Both consumer-first and final TP manifests satisfy Ebox's v2 contract."
|
||||||
(should-not
|
(dolist (protocol '(tp-transaction-protocol-v1+v2
|
||||||
(ebox-surface--tp-participant-v2-capability-p
|
tp-transaction-protocol-v2))
|
||||||
'(:transaction-protocol tp-transaction-protocol-v1+v2)))
|
(should
|
||||||
(should
|
(ebox-surface--validate-tp-v2-capability
|
||||||
(ebox-surface--tp-participant-v2-capability-p (tp-runtime-manifest)))
|
(list :transaction-protocol protocol
|
||||||
|
:structured-participant-api
|
||||||
|
'tp-transaction-participate-v2)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-transaction-participant-rejects-missing-or-malformed-v2 ()
|
||||||
|
"Missing or incompatible structured capabilities fail closed."
|
||||||
(should-error
|
(should-error
|
||||||
(ebox-surface--tp-participant-v2-capability-p
|
(ebox-surface--validate-tp-v2-capability
|
||||||
|
'(:transaction-protocol tp-transaction-protocol-v1+v2))
|
||||||
|
:type 'ebox-surface-tp-protocol-error)
|
||||||
|
(should-error
|
||||||
|
(ebox-surface--validate-tp-v2-capability
|
||||||
'(:transaction-protocol tp-transaction-protocol-v1+v2
|
'(:transaction-protocol tp-transaction-protocol-v1+v2
|
||||||
:structured-participant-api ignore))
|
:structured-participant-api ignore))
|
||||||
:type 'ebox-surface-participant-route-error)
|
:type 'ebox-surface-tp-protocol-error)
|
||||||
(should-error
|
(should-error
|
||||||
(ebox-surface--tp-participant-v2-capability-p
|
(ebox-surface--validate-tp-v2-capability
|
||||||
'(:transaction-protocol incompatible
|
'(:transaction-protocol incompatible
|
||||||
:structured-participant-api tp-transaction-participate-v2))
|
:structured-participant-api tp-transaction-participate-v2))
|
||||||
:type 'ebox-surface-participant-route-error))
|
:type 'ebox-surface-tp-protocol-error))
|
||||||
|
|
||||||
(ert-deftest ebox-transaction-participant-route-retains-v1-fallback ()
|
|
||||||
"Missing TP v2 capability and the kill switch each select only v1."
|
|
||||||
(dolist (case '((v2 structured nil) (v1 structured t) (v1 v1 t)))
|
|
||||||
(let ((counts
|
|
||||||
(ebox-commit-test--participant-route-counts
|
|
||||||
(nth 0 case) (nth 1 case) (nth 2 case))))
|
|
||||||
(should (= (plist-get counts :v2) 0))
|
|
||||||
(should (= (plist-get counts :v1) 2)))))
|
|
||||||
|
|
||||||
(ert-deftest ebox-transaction-participant-route-rejects-invalid-before-live-write ()
|
|
||||||
"An invalid Ebox route fails inside TP and leaves buffer/runtime unpublished."
|
|
||||||
(let ((buffer (generate-new-buffer " *ebox-invalid-participant-route*"))
|
|
||||||
(ebox-transaction-participant-route 'invalid))
|
|
||||||
(unwind-protect
|
|
||||||
(progn
|
|
||||||
(should-error
|
|
||||||
(ebox-render-to-buffer
|
|
||||||
buffer (ebox-test-box :key 'root (ebox-test-text "rejected")))
|
|
||||||
:type 'ebox-surface-participant-route-error)
|
|
||||||
(should-not (ebox--buffer-render-state buffer))
|
|
||||||
(with-current-buffer buffer (should (equal (buffer-string) ""))))
|
|
||||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
|
||||||
|
|
||||||
(ert-deftest ebox-commit-framework-publish-failure-rolls-back-full-and-scoped ()
|
(ert-deftest ebox-commit-framework-publish-failure-rolls-back-full-and-scoped ()
|
||||||
"A framework publish failure invokes its pair once on both commit paths."
|
"A framework publish failure invokes its pair once on both commit paths."
|
||||||
|
|||||||
@ -56,7 +56,7 @@
|
|||||||
"layout-boundary-tests" "layout-boundary-performance"
|
"layout-boundary-tests" "layout-boundary-performance"
|
||||||
"patch-plan-tests" "patch-plan-performance"
|
"patch-plan-tests" "patch-plan-performance"
|
||||||
"style-schema-tests" "style-schema-performance"
|
"style-schema-tests" "style-schema-performance"
|
||||||
"spi-tests" "spi-performance"
|
"spi-tests" "spi-performance" "c1b-contract-tests"
|
||||||
"docs-contract-tests" "ci-contract-tests" "performance-evaluator"
|
"docs-contract-tests" "ci-contract-tests" "performance-evaluator"
|
||||||
"visual-check"
|
"visual-check"
|
||||||
"native-rust-tests" "native-build" "package-lint" "diff-check")
|
"native-rust-tests" "native-build" "package-lint" "diff-check")
|
||||||
|
|||||||
@ -30,6 +30,30 @@
|
|||||||
(insert "\n"))
|
(insert "\n"))
|
||||||
(buffer-string)))
|
(buffer-string)))
|
||||||
|
|
||||||
|
(defun ebox-test--production-lisp-text ()
|
||||||
|
"Return all package-root Ebox Lisp production source as text."
|
||||||
|
(with-temp-buffer
|
||||||
|
(dolist (file (directory-files ebox-test--root t "\\`ebox.*\\.el\\'"))
|
||||||
|
(insert-file-contents file)
|
||||||
|
(insert "\n"))
|
||||||
|
(buffer-string)))
|
||||||
|
|
||||||
|
(ert-deftest ebox-package-c1b-is-v2-only ()
|
||||||
|
"Ebox 3 requires structured TP and contains no executable v1 bridge."
|
||||||
|
(let ((facade
|
||||||
|
(with-temp-buffer
|
||||||
|
(insert-file-contents (expand-file-name "ebox.el" ebox-test--root))
|
||||||
|
(buffer-string)))
|
||||||
|
(source (ebox-test--production-lisp-text)))
|
||||||
|
(should (string-match-p "Version: 3.0.0" facade))
|
||||||
|
(should (string-match-p (regexp-quote "(tp \"1.0.1\")") facade))
|
||||||
|
(should-not (string-match-p "ebox-transaction-participant-route" source))
|
||||||
|
(should-not (string-match-p "(tp-transaction-participate\\_>" source))
|
||||||
|
(should (string-match-p "(tp-transaction-participate-v2\\_>" source))
|
||||||
|
(should (equal ebox-framework-spi-required-tp-version "1.0.1"))
|
||||||
|
(should (memq tp-transaction-protocol
|
||||||
|
ebox-framework-spi-supported-tp-protocols))))
|
||||||
|
|
||||||
(ert-deftest ebox-package-defines-custom-group-and-scroll-option ()
|
(ert-deftest ebox-package-defines-custom-group-and-scroll-option ()
|
||||||
"The package exposes a real customization group and default scroll option."
|
"The package exposes a real customization group and default scroll option."
|
||||||
(should (get 'ebox 'custom-group))
|
(should (get 'ebox 'custom-group))
|
||||||
|
|||||||
@ -38,10 +38,14 @@
|
|||||||
(should (ebox-framework-spi-validate-provider first))
|
(should (ebox-framework-spi-validate-provider first))
|
||||||
(should-not (eq first second))
|
(should-not (eq first second))
|
||||||
(should (= (ebox-framework-spi-provider-spi-version first) 2))
|
(should (= (ebox-framework-spi-provider-spi-version first) 2))
|
||||||
|
(should (equal ebox-framework-spi-required-tp-version "1.0.1"))
|
||||||
|
(should (equal ebox-framework-spi-supported-tp-protocols
|
||||||
|
'(tp-transaction-protocol-v1+v2
|
||||||
|
tp-transaction-protocol-v2)))
|
||||||
(should (eq (ebox-framework-spi-provider-schema-version first)
|
(should (eq (ebox-framework-spi-provider-schema-version first)
|
||||||
'ebox-framework-spi-schema/v2))
|
'ebox-framework-spi-schema/v2))
|
||||||
(should (eq (ebox-framework-spi-provider-tp-protocol first)
|
(should (eq (ebox-framework-spi-provider-tp-protocol first)
|
||||||
'tp-transaction-protocol-v1+v2))
|
tp-transaction-protocol))
|
||||||
(should
|
(should
|
||||||
(equal (ebox-framework-spi-provider-capabilities first)
|
(equal (ebox-framework-spi-provider-capabilities first)
|
||||||
'(initial-paired-stage-rollback
|
'(initial-paired-stage-rollback
|
||||||
@ -108,63 +112,19 @@
|
|||||||
(ebox-framework-spi-provider-initial-operation fresh)))
|
(ebox-framework-spi-provider-initial-operation fresh)))
|
||||||
'buffer)))))
|
'buffer)))))
|
||||||
|
|
||||||
(ert-deftest ebox-spi-provider-is-additive-and-does-not-bootstrap-consumers ()
|
(ert-deftest ebox-spi-provider-does-not-bootstrap-consumers ()
|
||||||
"E5 publishes no selected port, consumer probe, or v1 removal."
|
"The provider publishes no selected port or consumer probe."
|
||||||
(let ((source (ebox-spi-test--source "ebox-spi.el"))
|
(let ((source (ebox-spi-test--source "ebox-spi.el"))
|
||||||
(facade (ebox-spi-test--source "ebox.el")))
|
(facade (ebox-spi-test--source "ebox.el")))
|
||||||
(dolist (pattern '("featurep" "fboundp" "selected-port"
|
(dolist (pattern '("featurep" "fboundp" "selected-port"
|
||||||
"etaf-renderer-port" "etaf-spi-bootstrap"))
|
"etaf-renderer-port" "etaf-spi-bootstrap"))
|
||||||
(should-not (string-match-p (regexp-quote pattern) source)))
|
(should-not (string-match-p (regexp-quote pattern) source)))
|
||||||
(should (string-match-p "Version: 2.0.1" facade))
|
(should (string-match-p "Version: 3.0.0" facade))
|
||||||
(should (string-match-p "(tp \"1.0.1\")" facade))
|
(should (string-match-p "(tp \"1.0.1\")" facade))
|
||||||
(should (functionp #'ebox-render-to-buffer))
|
(should (functionp #'ebox-render-to-buffer))
|
||||||
(should (functionp #'ebox-commit))
|
(should (functionp #'ebox-commit))
|
||||||
(should (functionp #'ebox-framework-spi-capabilities))))
|
(should (functionp #'ebox-framework-spi-capabilities))))
|
||||||
|
|
||||||
(ert-deftest ebox-spi-absent-tp-v2-keeps-the-v1-facade-loadable ()
|
|
||||||
"An absent TP transaction feature leaves the optional v2 provider absent."
|
|
||||||
(let* ((emacs (expand-file-name invocation-name invocation-directory))
|
|
||||||
(parent (file-name-directory
|
|
||||||
(directory-file-name ebox-spi-test--root)))
|
|
||||||
(ecss (expand-file-name "ecss" parent))
|
|
||||||
(tp (expand-file-name "tp" parent))
|
|
||||||
(provider-source
|
|
||||||
(expand-file-name "ebox-spi.el" ebox-spi-test--root))
|
|
||||||
(probe
|
|
||||||
(prin1-to-string
|
|
||||||
`(progn
|
|
||||||
(require 'cl-lib)
|
|
||||||
(require 'ebox-canonical)
|
|
||||||
(require 'ebox-surface)
|
|
||||||
(let ((original-require (symbol-function 'require)))
|
|
||||||
(cl-letf (((symbol-function 'require)
|
|
||||||
(lambda (feature &optional filename noerror)
|
|
||||||
(if (eq feature 'tp-transaction)
|
|
||||||
nil
|
|
||||||
(funcall original-require feature filename
|
|
||||||
noerror)))))
|
|
||||||
(load ,provider-source nil t)))
|
|
||||||
(require 'ebox)
|
|
||||||
(unless (featurep 'ebox-spi)
|
|
||||||
(error "SPI loader feature is absent"))
|
|
||||||
(when (featurep 'ebox-framework-spi-v2)
|
|
||||||
(error "v2 provider feature leaked without TP v2"))
|
|
||||||
(when (fboundp 'ebox-framework-spi-capabilities)
|
|
||||||
(error "v2 provider predicate leaked without TP v2"))
|
|
||||||
(unless (and (fboundp 'ebox-render-to-buffer)
|
|
||||||
(fboundp 'ebox-commit))
|
|
||||||
(error "v1 Ebox facade failed to load"))))))
|
|
||||||
(with-temp-buffer
|
|
||||||
(let ((status
|
|
||||||
(process-file
|
|
||||||
emacs nil (current-buffer) nil
|
|
||||||
"-Q" "--batch"
|
|
||||||
"-L" ebox-spi-test--root "-L" ecss "-L" tp
|
|
||||||
"--eval" "(setq load-prefer-newer t)"
|
|
||||||
"--eval" probe)))
|
|
||||||
(unless (zerop status)
|
|
||||||
(ert-fail (buffer-string)))))))
|
|
||||||
|
|
||||||
(ert-deftest ebox-spi-initial-and-update-share-their-legacy-report ()
|
(ert-deftest ebox-spi-initial-and-update-share-their-legacy-report ()
|
||||||
"Both operation descriptors preserve paired callbacks and report identity."
|
"Both operation descriptors preserve paired callbacks and report identity."
|
||||||
(ebox-spi-test--reset-runtime-state)
|
(ebox-spi-test--reset-runtime-state)
|
||||||
@ -447,6 +407,161 @@
|
|||||||
(should (equal (with-current-buffer buffer (buffer-string)) "")))
|
(should (equal (with-current-buffer buffer (buffer-string)) "")))
|
||||||
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-spi-initial-activation-failure-cleans-only-owned-observer ()
|
||||||
|
"Activation errors and throws preserve text and pre-existing observers."
|
||||||
|
(dolist (failure '(error throw))
|
||||||
|
(dolist (existing '(nil t))
|
||||||
|
(ert-info ((format "failure=%s existing-observer=%s" failure existing))
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-spi-activation-fault*"))
|
||||||
|
(input (ebox-test-box :key 'root (ebox-test-text "new")))
|
||||||
|
prior-observer cancelled result)
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(insert (propertize "sentinel" 'face 'bold)))
|
||||||
|
(when existing
|
||||||
|
(setq prior-observer
|
||||||
|
(ebox-surface--ensure-tp-observer buffer)))
|
||||||
|
(cl-letf (((symbol-function 'activate-change-group)
|
||||||
|
(lambda (_group)
|
||||||
|
(if (eq failure 'throw)
|
||||||
|
(throw 'ebox-spi-activation 'activation-throw)
|
||||||
|
(error "activation-error"))))
|
||||||
|
((symbol-function 'cancel-change-group)
|
||||||
|
(lambda (_group) (setq cancelled t))))
|
||||||
|
(setq result
|
||||||
|
(catch 'ebox-spi-activation
|
||||||
|
(condition-case condition
|
||||||
|
(ebox-framework-spi-initial
|
||||||
|
buffer input #'ignore #'ignore)
|
||||||
|
(error condition)))))
|
||||||
|
(should (equal result
|
||||||
|
(if (eq failure 'throw) 'activation-throw
|
||||||
|
'(error "activation-error"))))
|
||||||
|
(should-not cancelled)
|
||||||
|
(should-not (ebox-surface-buffer-mounted-p buffer))
|
||||||
|
(should-not (gethash buffer ebox--buffer-render-state-table))
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(should (eq ebox-surface--tp-observer prior-observer))
|
||||||
|
(should (equal-including-properties
|
||||||
|
(buffer-string)
|
||||||
|
(propertize "sentinel" 'face 'bold)))))
|
||||||
|
(when (buffer-live-p buffer) (kill-buffer buffer))))))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-spi-initial-failure-restores-exact-editor-custody ()
|
||||||
|
"A failed initial mount preserves editor identities, bounds, and history."
|
||||||
|
(ebox-spi-test--reset-runtime-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-spi-editor-custody*"))
|
||||||
|
(input (ebox-test-box :key 'root (ebox-test-text "committed")))
|
||||||
|
overlay left-marker right-marker snapshot captured)
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(buffer-enable-undo)
|
||||||
|
(insert (propertize "sentinel" 'face 'bold))
|
||||||
|
(undo-boundary)
|
||||||
|
(goto-char 4)
|
||||||
|
(set-mark 2)
|
||||||
|
(setq mark-active t
|
||||||
|
overlay (make-overlay 2 6 buffer t t)
|
||||||
|
left-marker (copy-marker 3 nil)
|
||||||
|
right-marker (copy-marker 5 t))
|
||||||
|
(overlay-put overlay 'ebox-test-property '(owned value))
|
||||||
|
(narrow-to-region 2 7)
|
||||||
|
(set-buffer-modified-p nil)
|
||||||
|
(setq snapshot
|
||||||
|
(list
|
||||||
|
:contents
|
||||||
|
(save-restriction
|
||||||
|
(widen)
|
||||||
|
(buffer-substring (point-min) (point-max)))
|
||||||
|
:point (point)
|
||||||
|
:mark-marker (mark-marker)
|
||||||
|
:mark-position (mark t)
|
||||||
|
:mark-insertion-type
|
||||||
|
(marker-insertion-type (mark-marker))
|
||||||
|
:mark-active mark-active
|
||||||
|
:narrow-start (point-min)
|
||||||
|
:narrow-end (point-max)
|
||||||
|
:overlay-start (overlay-start overlay)
|
||||||
|
:overlay-end (overlay-end overlay)
|
||||||
|
:overlay-properties (overlay-properties overlay)
|
||||||
|
:left-position (marker-position left-marker)
|
||||||
|
:left-insertion-type (marker-insertion-type left-marker)
|
||||||
|
:right-position (marker-position right-marker)
|
||||||
|
:right-insertion-type (marker-insertion-type right-marker)
|
||||||
|
:undo-list (copy-tree buffer-undo-list)
|
||||||
|
:modified-p (buffer-modified-p))))
|
||||||
|
(condition-case condition
|
||||||
|
(ebox-framework-spi-initial
|
||||||
|
buffer input
|
||||||
|
(lambda (_report) (error "editor custody primary"))
|
||||||
|
#'ignore)
|
||||||
|
(error (setq captured condition)))
|
||||||
|
(should (equal captured '(error "editor custody primary")))
|
||||||
|
(should-not (ebox-surface-buffer-mounted-p buffer))
|
||||||
|
(should-not (ebox-surface-buffer-observer buffer))
|
||||||
|
(with-current-buffer buffer
|
||||||
|
(should
|
||||||
|
(equal (save-restriction
|
||||||
|
(widen)
|
||||||
|
(buffer-substring (point-min) (point-max)))
|
||||||
|
(plist-get snapshot :contents)))
|
||||||
|
(should (= (point) (plist-get snapshot :point)))
|
||||||
|
(should (eq (mark-marker) (plist-get snapshot :mark-marker)))
|
||||||
|
(should (= (mark t) (plist-get snapshot :mark-position)))
|
||||||
|
(should (eq (marker-insertion-type (mark-marker))
|
||||||
|
(plist-get snapshot :mark-insertion-type)))
|
||||||
|
(should (eq mark-active (plist-get snapshot :mark-active)))
|
||||||
|
(should (= (point-min) (plist-get snapshot :narrow-start)))
|
||||||
|
(should (= (point-max) (plist-get snapshot :narrow-end)))
|
||||||
|
(should (eq (overlay-buffer overlay) buffer))
|
||||||
|
(should (= (overlay-start overlay)
|
||||||
|
(plist-get snapshot :overlay-start)))
|
||||||
|
(should (= (overlay-end overlay)
|
||||||
|
(plist-get snapshot :overlay-end)))
|
||||||
|
(should (equal (overlay-properties overlay)
|
||||||
|
(plist-get snapshot :overlay-properties)))
|
||||||
|
(should (= (marker-position left-marker)
|
||||||
|
(plist-get snapshot :left-position)))
|
||||||
|
(should (eq (marker-insertion-type left-marker)
|
||||||
|
(plist-get snapshot :left-insertion-type)))
|
||||||
|
(should (= (marker-position right-marker)
|
||||||
|
(plist-get snapshot :right-position)))
|
||||||
|
(should (eq (marker-insertion-type right-marker)
|
||||||
|
(plist-get snapshot :right-insertion-type)))
|
||||||
|
(should (equal buffer-undo-list (plist-get snapshot :undo-list)))
|
||||||
|
(should (eq (buffer-modified-p)
|
||||||
|
(plist-get snapshot :modified-p)))))
|
||||||
|
(when (overlayp overlay) (delete-overlay overlay))
|
||||||
|
(when (markerp left-marker) (set-marker left-marker nil))
|
||||||
|
(when (markerp right-marker) (set-marker right-marker nil))
|
||||||
|
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||||
|
|
||||||
|
(ert-deftest ebox-spi-rollback-throw-cannot-stop-initial-cleanup ()
|
||||||
|
"A framework rollback throw escapes only after editor cleanup completes."
|
||||||
|
(ebox-spi-test--reset-runtime-state)
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-spi-rollback-throw*"))
|
||||||
|
(input (ebox-test-box :key 'root (ebox-test-text "committed"))))
|
||||||
|
(unwind-protect
|
||||||
|
(progn
|
||||||
|
(with-current-buffer buffer (insert "before"))
|
||||||
|
(should
|
||||||
|
(eq
|
||||||
|
(catch 'ebox-spi-rollback-escape
|
||||||
|
(ebox-framework-spi-initial
|
||||||
|
buffer input
|
||||||
|
(lambda (_report) (error "primary before rollback throw"))
|
||||||
|
(lambda (_report)
|
||||||
|
(throw 'ebox-spi-rollback-escape 'rollback-escaped)))
|
||||||
|
'not-escaped)
|
||||||
|
'rollback-escaped))
|
||||||
|
(should-not (ebox-surface-buffer-mounted-p buffer))
|
||||||
|
(should-not (ebox-surface-buffer-observer buffer))
|
||||||
|
(should (equal "before"
|
||||||
|
(with-current-buffer buffer (buffer-string)))))
|
||||||
|
(when (buffer-live-p buffer) (kill-buffer buffer)))))
|
||||||
|
|
||||||
(ert-deftest ebox-spi-update-rolls-the-same-report-back ()
|
(ert-deftest ebox-spi-update-rolls-the-same-report-back ()
|
||||||
"A later TP failure restores update state through the v2 operation."
|
"A later TP failure restores update state through the v2 operation."
|
||||||
(ebox-spi-test--reset-runtime-state)
|
(ebox-spi-test--reset-runtime-state)
|
||||||
|
|||||||
@ -16,6 +16,13 @@
|
|||||||
(require 'tp-surface)
|
(require 'tp-surface)
|
||||||
(require 'ebox-native-reflow)
|
(require 'ebox-native-reflow)
|
||||||
|
|
||||||
|
(defmacro ebox-surface-test--with-elisp-backend (&rest body)
|
||||||
|
"Run BODY with the explicit Elisp rendering backend."
|
||||||
|
(declare (indent 0) (debug t))
|
||||||
|
`(cl-letf (((symbol-function 'ebox-native-reflow-layout-ready-p)
|
||||||
|
(lambda () nil)))
|
||||||
|
,@body))
|
||||||
|
|
||||||
(defun ebox-surface-test--reset-render-state ()
|
(defun ebox-surface-test--reset-render-state ()
|
||||||
"Reset render identities and side tables used by projection tests."
|
"Reset render identities and side tables used by projection tests."
|
||||||
(setq ebox--region-id-counter 0
|
(setq ebox--region-id-counter 0
|
||||||
@ -1265,7 +1272,8 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
(ert-deftest ebox-viewport-reflow-reuses-retained-node-subtree ()
|
(ert-deftest ebox-viewport-reflow-reuses-retained-node-subtree ()
|
||||||
"A safe viewport reflow should reuse the retained TP node topology."
|
"A safe viewport reflow should reuse the retained TP node topology."
|
||||||
(ebox-surface-test--reset-render-state)
|
(ebox-surface-test--reset-render-state)
|
||||||
(let ((buffer (generate-new-buffer " *ebox-surface-viewport-reflow*"))
|
(ebox-surface-test--with-elisp-backend
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-surface-viewport-reflow*"))
|
||||||
(ensured-node-count 0)
|
(ensured-node-count 0)
|
||||||
(runtime-index-count 0)
|
(runtime-index-count 0)
|
||||||
(original-ensure (symbol-function 'ebox-surface--ensure-node-tree))
|
(original-ensure (symbol-function 'ebox-surface--ensure-node-tree))
|
||||||
@ -1332,7 +1340,7 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
:surface-object)))
|
:surface-object)))
|
||||||
(should (equal-including-properties actual expected)))))
|
(should (equal-including-properties actual expected)))))
|
||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer)))))
|
(kill-buffer buffer))))))
|
||||||
|
|
||||||
(ert-deftest ebox-viewport-resize-retains-native-frame-continuity ()
|
(ert-deftest ebox-viewport-resize-retains-native-frame-continuity ()
|
||||||
"A topology-stable resize should keep the committed native frame session."
|
"A topology-stable resize should keep the committed native frame session."
|
||||||
@ -1814,7 +1822,8 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
(ert-deftest ebox-viewport-reflow-reuses-stable-active-stylesheet ()
|
(ert-deftest ebox-viewport-reflow-reuses-stable-active-stylesheet ()
|
||||||
"A viewport change should retain styles only while their signature is stable."
|
"A viewport change should retain styles only while their signature is stable."
|
||||||
(ebox-surface-test--reset-render-state)
|
(ebox-surface-test--reset-render-state)
|
||||||
(let ((ebox-style-stylesheet (ecss-stylesheet-create))
|
(ebox-surface-test--with-elisp-backend
|
||||||
|
(let ((ebox-style-stylesheet (ecss-stylesheet-create))
|
||||||
(buffer (generate-new-buffer " *ebox-viewport-cascade*"))
|
(buffer (generate-new-buffer " *ebox-viewport-cascade*"))
|
||||||
(ensured-node-count 0)
|
(ensured-node-count 0)
|
||||||
(original-ensure (symbol-function 'ebox-surface--ensure-node-tree)))
|
(original-ensure (symbol-function 'ebox-surface--ensure-node-tree)))
|
||||||
@ -1855,12 +1864,13 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
(should-not (plist-get report :projection-kind))
|
(should-not (plist-get report :projection-kind))
|
||||||
(should (plist-get report :runtime-published))))
|
(should (plist-get report :runtime-published))))
|
||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer)))))
|
(kill-buffer buffer))))))
|
||||||
|
|
||||||
(ert-deftest ebox-viewport-reflow-retains-stable-inline-inheritance ()
|
(ert-deftest ebox-viewport-reflow-retains-stable-inline-inheritance ()
|
||||||
"Stable inherited Text style should retain its viewport projection exactly."
|
"Stable inherited Text style should retain its viewport projection exactly."
|
||||||
(ebox-surface-test--reset-render-state)
|
(ebox-surface-test--reset-render-state)
|
||||||
(let ((buffer (generate-new-buffer " *ebox-viewport-inheritance*"))
|
(ebox-surface-test--with-elisp-backend
|
||||||
|
(let ((buffer (generate-new-buffer " *ebox-viewport-inheritance*"))
|
||||||
(ensured-node-count 0)
|
(ensured-node-count 0)
|
||||||
(original-ensure (symbol-function 'ebox-surface--ensure-node-tree)))
|
(original-ensure (symbol-function 'ebox-surface--ensure-node-tree)))
|
||||||
(unwind-protect
|
(unwind-protect
|
||||||
@ -1894,7 +1904,7 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
(should (plist-get state :cascade-required-p))
|
(should (plist-get state :cascade-required-p))
|
||||||
(should (equal-including-properties actual expected))))
|
(should (equal-including-properties actual expected))))
|
||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer)))))
|
(kill-buffer buffer))))))
|
||||||
|
|
||||||
(ert-deftest ebox-text-style-owns-local-cascade-state ()
|
(ert-deftest ebox-text-style-owns-local-cascade-state ()
|
||||||
"Persist an independent style owner state for canonical Text."
|
"Persist an independent style owner state for canonical Text."
|
||||||
@ -1955,7 +1965,8 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
|
|
||||||
(ert-deftest ebox-viewport-reflow-falls-back-for-scroll-and-visible-overflow ()
|
(ert-deftest ebox-viewport-reflow-falls-back-for-scroll-and-visible-overflow ()
|
||||||
"Nested scroll state and visible overflow must not enter retained reflow."
|
"Nested scroll state and visible overflow must not enter retained reflow."
|
||||||
(dolist (fixture
|
(ebox-surface-test--with-elisp-backend
|
||||||
|
(dolist (fixture
|
||||||
(list
|
(list
|
||||||
(cons 'nested-scroll
|
(cons 'nested-scroll
|
||||||
(lambda ()
|
(lambda ()
|
||||||
@ -1992,7 +2003,7 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
(should-not (plist-get report :projection-kind))
|
(should-not (plist-get report :projection-kind))
|
||||||
(should (plist-get report :runtime-published))))
|
(should (plist-get report :runtime-published))))
|
||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer))))))
|
(kill-buffer buffer)))))))
|
||||||
|
|
||||||
(ert-deftest ebox-viewport-reflow-retains-viewport-dependent-root-scroll ()
|
(ert-deftest ebox-viewport-reflow-retains-viewport-dependent-root-scroll ()
|
||||||
"A sole root scroll owner may reflow its own viewport-dependent content."
|
"A sole root scroll owner may reflow its own viewport-dependent content."
|
||||||
@ -2037,7 +2048,8 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
|
|
||||||
(ert-deftest ebox-viewport-reflow-supports-height-and-both-axis-resize ()
|
(ert-deftest ebox-viewport-reflow-supports-height-and-both-axis-resize ()
|
||||||
"Retained viewport reflow should cover height-only and two-axis changes."
|
"Retained viewport reflow should cover height-only and two-axis changes."
|
||||||
(dolist (case
|
(ebox-surface-test--with-elisp-backend
|
||||||
|
(dolist (case
|
||||||
'((height 120 3 120 5 height)
|
'((height 120 3 120 5 height)
|
||||||
(both 160 3 240 5 both)))
|
(both 160 3 240 5 both)))
|
||||||
(pcase-let ((`(,name ,old-width ,old-height ,new-width ,new-height ,axes)
|
(pcase-let ((`(,name ,old-width ,old-height ,new-width ,new-height ,axes)
|
||||||
@ -2077,7 +2089,7 @@ candidate cannot hide mutations by restoring the old hash-table pointer."
|
|||||||
(line-end-position)))
|
(line-end-position)))
|
||||||
new-width)))))
|
new-width)))))
|
||||||
(when (buffer-live-p buffer)
|
(when (buffer-live-p buffer)
|
||||||
(kill-buffer buffer)))))))
|
(kill-buffer buffer))))))))
|
||||||
|
|
||||||
(ert-deftest ebox-viewport-reflow-rolls-back-after-publication-failure ()
|
(ert-deftest ebox-viewport-reflow-rolls-back-after-publication-failure ()
|
||||||
"A failed retained viewport publication must restore its old generation."
|
"A failed retained viewport publication must restore its old generation."
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user