feat: lower canonical Box through ETAF runtime

This commit is contained in:
Kinneyzhang 2026-08-26 16:16:57 +08:00
parent c3ea8aa8ea
commit c831ca11ca
4 changed files with 81 additions and 47 deletions

View File

@ -552,7 +552,7 @@ the ordinary layout lowering path instead."
(list (apply #'ebox-spacer props)))
('fragment
(etaf--render-value-list children (append path (list :fragment))))
((or 'row 'column 'container 'stack 'flex 'grid)
((or 'box 'row 'column 'container 'stack 'flex 'grid)
(let ((nodes nil)
(index 0)
(range-child-p nil)
@ -624,6 +624,8 @@ the ordinary layout lowering path instead."
RANGE-CHILD-P records that NODES include nonvisual Range segments."
(let ((node
(cond
((eq name 'box)
(ebox-build (append (list 'box) props nodes)))
((eq name 'row)
(if (null props)
(apply #'ebox-row nodes)
@ -653,7 +655,7 @@ RANGE-CHILD-P preserves the direct material Range parent."
(etaf--layout-node 'row props children range-child-p)
(apply #'ebox-create :content content props)))
('spacer (apply #'ebox-spacer props))
((or 'row 'column 'container 'stack 'flex 'grid)
((or 'box 'row 'column 'container 'stack 'flex 'grid)
(etaf--layout-node name props children range-child-p))
(_ (signal 'etaf-renderer-error
(list (format "Semantic Host requires Step4b lowering: %S"

View File

@ -1122,7 +1122,8 @@ when the requested boundary is no longer mounted."
(or (plist-get env :slots)
(and component (etaf--semantic-component-slots component)))))
(if (null component-id)
(let ((etaf--current-component-instance nil)
(let ((etaf--current-runtime runtime)
(etaf--current-component-instance nil)
(etaf--current-component-identity nil)
(etaf--current-component-semantic-id nil)
(etaf--current-component-props nil)
@ -2991,10 +2992,12 @@ The candidate uses resolved VALUE, DEPS, and NODES."
(etaf-runtime-candidate-inline-host-ids runtime)
:test #'eql)
(etaf--runtime-invalidate-semantic-ancestors runtime host-id)
(when-let* ((component
(when-let* ((component-id
(etaf--semantic-inline-range-component-id old))
(component
(etaf--pvec-get
(etaf-generation-semantic-nodes generation)
(etaf--semantic-inline-range-component-id old))))
component-id)))
(cl-pushnew (etaf--semantic-component-identity component)
(etaf-runtime-candidate-updated-component-identities
runtime)
@ -4456,43 +4459,23 @@ Generation, including its effects and Host contributions."
(defun etaf--runtime-render-dirty-inline-range (runtime effect inline)
"Evaluate RUNTIME dirty INLINE EFFECT and stage its semantic output."
(let* ((generation (etaf-runtime-current-generation runtime))
(base-component
(etaf--pvec-get (etaf-generation-semantic-nodes generation)
(etaf--semantic-inline-range-component-id inline)))
(component
(or (gethash (etaf--semantic-component-identity base-component)
(etaf-runtime-candidate-semantic-nodes runtime))
base-component))
(instance
(gethash (etaf--semantic-component-resource-key component)
(etaf-runtime-resource-registry runtime)))
deps context-deps output)
(let ((etaf--runtime-dependency-collector
(lambda (source) (cl-pushnew source deps :test #'eq)))
(etaf--context-inject-recorder
(lambda (frame key)
(cl-pushnew (cons (etaf-context-owner-id frame) key)
context-deps :test #'equal)))
(etaf--current-runtime runtime)
(etaf--current-component-instance instance)
(etaf--current-component-identity
(etaf--semantic-component-identity component))
(etaf--current-component-semantic-id
(etaf--semantic-component-semantic-id component))
(etaf--current-component-props
(etaf--semantic-component-props component))
(etaf--current-component-slots
(etaf--semantic-component-slots component))
(etaf--current-context
(etaf--semantic-component-context-frame component))
(etaf--active-effect nil)
(etaf--render-phase-p t))
(setq output
(etaf--runtime-inline-value-string
(funcall
(etaf--expr-thunk (etaf--generation-effect-target effect)))
(etaf--semantic-inline-range-surface-properties inline))))
(let (deps context-deps output)
(etaf--runtime-call-with-component-env
runtime (etaf--semantic-inline-range-component-id inline)
(lambda ()
(let ((etaf--runtime-dependency-collector
(lambda (source) (cl-pushnew source deps :test #'eq)))
(etaf--context-inject-recorder
(lambda (frame key)
(cl-pushnew (cons (etaf-context-owner-id frame) key)
context-deps :test #'equal)))
(etaf--active-effect nil)
(etaf--render-phase-p t))
(setq output
(etaf--runtime-inline-value-string
(funcall
(etaf--expr-thunk (etaf--generation-effect-target effect)))
(etaf--semantic-inline-range-surface-properties inline))))))
(let ((candidate (copy-sequence inline)))
(setf (etaf--semantic-inline-range-output candidate)
(copy-sequence output)
@ -4518,11 +4501,13 @@ Generation, including its effects and Host contributions."
:test #'eql)
(etaf--runtime-invalidate-semantic-ancestors
runtime (etaf--semantic-inline-range-parent-id inline))
(when-let* ((component
(when-let* ((component-id
(etaf--semantic-inline-range-component-id inline))
(component
(etaf--pvec-get
(etaf-generation-semantic-nodes
(etaf-runtime-current-generation runtime))
(etaf--semantic-inline-range-component-id inline))))
component-id)))
(cl-pushnew (etaf--semantic-component-identity component)
(etaf-runtime-candidate-updated-component-identities runtime)
:test #'equal)))
@ -4675,11 +4660,13 @@ RENDERED-IDENTITIES names the Component render participants."
(etaf--pvec-get
(etaf-generation-semantic-nodes generation)
inline-id))))
(component-id
(and inline (etaf--semantic-inline-range-component-id inline)))
(component
(and inline
(and component-id
(etaf--pvec-get
(etaf-generation-semantic-nodes generation)
(etaf--semantic-inline-range-component-id inline)))))
component-id))))
(and component
(member (etaf--semantic-component-identity component)
rendered-identities))))

View File

@ -73,7 +73,7 @@
key-thunk)
(defconst etaf--host-names
'(text fragment container row column stack flex grid spacer)
'(text box fragment container row column stack flex grid spacer)
"Minimal unstyled Hosts implemented by ETAF core.
Product Components such as Button belong to `etaf-ui'; they are not added to

View File

@ -1294,6 +1294,51 @@
(etaf-test--render-text
(etaf-view (column (text "A") (text "B")))))))
(ert-deftest etaf-view-box-lowers-outer-and-layout-to-ebox ()
"Lower canonical Box axes through Ebox's public Box DSL."
(let ((row
(etaf-render
(etaf-view
(box :outer 'inline :layout 'row
(text "A")
(text "B")))))
(column
(etaf-render
(etaf-view
(box :outer 'block :layout 'column
(text "A")
(text "B"))))))
(should (equal (ebox--computed-display row) '(inline row)))
(should (equal (ebox--computed-display column) '(block column)))
(should (equal (substring-no-properties (ebox-render row)) "AB"))
(should (equal (substring-no-properties (ebox-render column)) "A\nB"))))
(ert-deftest etaf-view-box-rejects-invalid-layout ()
"Reject an invalid canonical Box layout before publication."
(should-error
(etaf-render (etaf-view (box :layout 'masonry "A")))
:type 'error))
(ert-deftest etaf-runtime-box-retains-child-text-update ()
"Mounted canonical Box should retain its layout while Text updates."
(let ((buffer-name " *etaf-box-runtime-test*")
(source (etaf-ref "A")))
(unwind-protect
(progn
(etaf-mount
buffer-name
(etaf-view
(box :layout 'row
(text (expr :value (etaf-value source)))
(text "B"))))
(should (string-match-p "AB" (etaf-test--buffer-text buffer-name)))
(setf (etaf-value source) "C")
(should (string-match-p "CB" (etaf-test--buffer-text buffer-name))))
(when-let* ((runtime (etaf-runtime-for-buffer buffer-name)))
(etaf-unmount runtime))
(when-let* ((buffer (get-buffer buffer-name)))
(kill-buffer buffer)))))
(ert-deftest etaf-view-grid-lowers-to-ebox-grid ()
"Lower the Grid Host through Ebox's two-dimensional layout node."
(let ((node (etaf-render