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))) (list (apply #'ebox-spacer props)))
('fragment ('fragment
(etaf--render-value-list children (append path (list :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) (let ((nodes nil)
(index 0) (index 0)
(range-child-p nil) (range-child-p nil)
@ -624,6 +624,8 @@ the ordinary layout lowering path instead."
RANGE-CHILD-P records that NODES include nonvisual Range segments." RANGE-CHILD-P records that NODES include nonvisual Range segments."
(let ((node (let ((node
(cond (cond
((eq name 'box)
(ebox-build (append (list 'box) props nodes)))
((eq name 'row) ((eq name 'row)
(if (null props) (if (null props)
(apply #'ebox-row nodes) (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) (etaf--layout-node 'row props children range-child-p)
(apply #'ebox-create :content content props))) (apply #'ebox-create :content content props)))
('spacer (apply #'ebox-spacer 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)) (etaf--layout-node name props children range-child-p))
(_ (signal 'etaf-renderer-error (_ (signal 'etaf-renderer-error
(list (format "Semantic Host requires Step4b lowering: %S" (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) (or (plist-get env :slots)
(and component (etaf--semantic-component-slots component))))) (and component (etaf--semantic-component-slots component)))))
(if (null component-id) (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-identity nil)
(etaf--current-component-semantic-id nil) (etaf--current-component-semantic-id nil)
(etaf--current-component-props 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) (etaf-runtime-candidate-inline-host-ids runtime)
:test #'eql) :test #'eql)
(etaf--runtime-invalidate-semantic-ancestors runtime host-id) (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--pvec-get
(etaf-generation-semantic-nodes generation) (etaf-generation-semantic-nodes generation)
(etaf--semantic-inline-range-component-id old)))) component-id)))
(cl-pushnew (etaf--semantic-component-identity component) (cl-pushnew (etaf--semantic-component-identity component)
(etaf-runtime-candidate-updated-component-identities (etaf-runtime-candidate-updated-component-identities
runtime) runtime)
@ -4456,43 +4459,23 @@ Generation, including its effects and Host contributions."
(defun etaf--runtime-render-dirty-inline-range (runtime effect inline) (defun etaf--runtime-render-dirty-inline-range (runtime effect inline)
"Evaluate RUNTIME dirty INLINE EFFECT and stage its semantic output." "Evaluate RUNTIME dirty INLINE EFFECT and stage its semantic output."
(let* ((generation (etaf-runtime-current-generation runtime)) (let (deps context-deps output)
(base-component (etaf--runtime-call-with-component-env
(etaf--pvec-get (etaf-generation-semantic-nodes generation) runtime (etaf--semantic-inline-range-component-id inline)
(etaf--semantic-inline-range-component-id inline))) (lambda ()
(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 (let ((etaf--runtime-dependency-collector
(lambda (source) (cl-pushnew source deps :test #'eq))) (lambda (source) (cl-pushnew source deps :test #'eq)))
(etaf--context-inject-recorder (etaf--context-inject-recorder
(lambda (frame key) (lambda (frame key)
(cl-pushnew (cons (etaf-context-owner-id frame) key) (cl-pushnew (cons (etaf-context-owner-id frame) key)
context-deps :test #'equal))) 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--active-effect nil)
(etaf--render-phase-p t)) (etaf--render-phase-p t))
(setq output (setq output
(etaf--runtime-inline-value-string (etaf--runtime-inline-value-string
(funcall (funcall
(etaf--expr-thunk (etaf--generation-effect-target effect))) (etaf--expr-thunk (etaf--generation-effect-target effect)))
(etaf--semantic-inline-range-surface-properties inline)))) (etaf--semantic-inline-range-surface-properties inline))))))
(let ((candidate (copy-sequence inline))) (let ((candidate (copy-sequence inline)))
(setf (etaf--semantic-inline-range-output candidate) (setf (etaf--semantic-inline-range-output candidate)
(copy-sequence output) (copy-sequence output)
@ -4518,11 +4501,13 @@ Generation, including its effects and Host contributions."
:test #'eql) :test #'eql)
(etaf--runtime-invalidate-semantic-ancestors (etaf--runtime-invalidate-semantic-ancestors
runtime (etaf--semantic-inline-range-parent-id inline)) 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--pvec-get
(etaf-generation-semantic-nodes (etaf-generation-semantic-nodes
(etaf-runtime-current-generation runtime)) (etaf-runtime-current-generation runtime))
(etaf--semantic-inline-range-component-id inline)))) component-id)))
(cl-pushnew (etaf--semantic-component-identity component) (cl-pushnew (etaf--semantic-component-identity component)
(etaf-runtime-candidate-updated-component-identities runtime) (etaf-runtime-candidate-updated-component-identities runtime)
:test #'equal))) :test #'equal)))
@ -4675,11 +4660,13 @@ RENDERED-IDENTITIES names the Component render participants."
(etaf--pvec-get (etaf--pvec-get
(etaf-generation-semantic-nodes generation) (etaf-generation-semantic-nodes generation)
inline-id)))) inline-id))))
(component-id
(and inline (etaf--semantic-inline-range-component-id inline)))
(component (component
(and inline (and component-id
(etaf--pvec-get (etaf--pvec-get
(etaf-generation-semantic-nodes generation) (etaf-generation-semantic-nodes generation)
(etaf--semantic-inline-range-component-id inline))))) component-id))))
(and component (and component
(member (etaf--semantic-component-identity component) (member (etaf--semantic-component-identity component)
rendered-identities)))) rendered-identities))))

View File

@ -73,7 +73,7 @@
key-thunk) key-thunk)
(defconst etaf--host-names (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. "Minimal unstyled Hosts implemented by ETAF core.
Product Components such as Button belong to `etaf-ui'; they are not added to 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-test--render-text
(etaf-view (column (text "A") (text "B"))))))) (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 () (ert-deftest etaf-view-grid-lowers-to-ebox-grid ()
"Lower the Grid Host through Ebox's two-dimensional layout node." "Lower the Grid Host through Ebox's two-dimensional layout node."
(let ((node (etaf-render (let ((node (etaf-render