From c831ca11ca30a721f7678c4ee1aa0c8ac977deff Mon Sep 17 00:00:00 2001 From: Kinneyzhang Date: Wed, 26 Aug 2026 16:16:57 +0800 Subject: [PATCH] feat: lower canonical Box through ETAF runtime --- etaf-renderer.el | 6 ++-- etaf-runtime.el | 75 +++++++++++++++++++-------------------------- etaf-view.el | 2 +- tests/etaf-tests.el | 45 +++++++++++++++++++++++++++ 4 files changed, 81 insertions(+), 47 deletions(-) diff --git a/etaf-renderer.el b/etaf-renderer.el index a612de9..912c110 100644 --- a/etaf-renderer.el +++ b/etaf-renderer.el @@ -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" diff --git a/etaf-runtime.el b/etaf-runtime.el index 703bbbe..7f63bd1 100644 --- a/etaf-runtime.el +++ b/etaf-runtime.el @@ -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)))) diff --git a/etaf-view.el b/etaf-view.el index 2087c4b..3327951 100644 --- a/etaf-view.el +++ b/etaf-view.el @@ -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 diff --git a/tests/etaf-tests.el b/tests/etaf-tests.el index 73a9f81..b9e607b 100644 --- a/tests/etaf-tests.el +++ b/tests/etaf-tests.el @@ -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