fix: rebuild ordinary projection state after local output declines
This commit is contained in:
parent
6ac8ff6e04
commit
e1f4821e6d
@ -35,6 +35,8 @@ The candidate is either fully published or discarded. A failed render, TP write,
|
||||
|
||||
The planner prefers `paint-patch`, then `span-patch`, then `owner-rerender`, and finally `root-rerender` when geometry or identity makes a smaller operation unsafe. The incremental adapter derives tentative owners from live generation facts; the pure patch port then receives only those artifacts and the immutable parent table. These names describe Ebox semantic owner scope; TP alone computes and executes the physical text/property diff. A patch must not silently widen its semantic scope. Reports preserve the Ebox strategy and planned scope while separately recording TP's actual surface operations and revision.
|
||||
|
||||
If span, owner-scoped, formatting-context, or mixed local output declines, the producer rebuilds ordinary candidate state before rendering the complete root. Temporary coordinates, fragments, and paint layers from the declined attempt cannot authorize publication. The report retains the planned strategy and scope; `:projection-kind` is `nil` for this ordinary fallback. A planned mixed strategy therefore does not prove that computation stayed local.
|
||||
|
||||
## Strict retained viewport reflow
|
||||
|
||||
`viewport-reflow` is an explicit retained projection kind for a narrow, proof-driven viewport resize path. The planner may select it only when all of the following hold: the plan is one root-owned geometry `owner-rerender`; the old and candidate roots are the same retained root; no dirty entry changes children; the node-key set, region-id set, and parent table are unchanged; neither the old surface nor the current style environment requires cascade or inline-inheritance recomputation; there is no scroll state; the root has no visible-overflow scope; and the old surface has a non-empty retained node-object table. Width-only, height-only, and both-axis changes use the same proof.
|
||||
|
||||
@ -35,6 +35,8 @@
|
||||
|
||||
规划器优先使用 `paint-patch`,再使用 `span-patch`、`owner-rerender`,最后在几何或 identity 使小操作不安全时使用 `root-rerender`。incremental adapter 从 live generation fact 推导 tentative owner;pure patch port 随后只接收这些 artifact 与不可变 parent table。这些名称描述的是 Ebox 的语义 owner 范围;只有 TP 负责计算并执行物理文本/属性 diff。Patch 不得静默扩大语义范围。报告保留 Ebox strategy 与 planned scope,同时单独记录 TP 的实际 surface operation 和 revision。
|
||||
|
||||
span、owner-scoped、formatting-context 或 mixed 局部输出被拒绝时,producer 先重建普通 candidate state,再渲染完整 root。被拒绝的尝试留下的临时坐标、fragment 和 paint layer 不能授权发布。报告保留 planned strategy 与 scope;普通 fallback 的 `:projection-kind` 为 `nil`。因此,planned mixed strategy 不能证明计算始终局限在局部。
|
||||
|
||||
## 严格 retained viewport reflow
|
||||
|
||||
`viewport-reflow` 是一个明确的 retained projection kind,只用于范围很窄、由 proof 驱动的 viewport resize 路径。只有以下条件全部满足时,规划器才能选择它:plan 是一个 root-owned geometry `owner-rerender`;旧 root 与 candidate root 是同一个 retained root;没有 dirty entry 修改 children;node-key set、region-id set 和 parent table 都不变;旧 surface 和当前 style environment 都不需要 cascade 或 inline-inheritance 重新计算;没有 scroll state;root 没有 visible-overflow scope;旧 surface 存在非空 retained node-object table。仅改变 width、仅改变 height 以及同时改变两个轴使用同一套 proof。
|
||||
|
||||
@ -1741,11 +1741,20 @@ PREVIOUS-STATE's node-object table."
|
||||
(defun ebox-surface--complete-projection
|
||||
(context root source-index style-required-p projection)
|
||||
"Complete a retained PROJECTION for ROOT after a local proof miss.
|
||||
The initial span attempt already owns the surface and node-root identities in
|
||||
The local attempt already owns the surface and node-root identities in
|
||||
CONTEXT, so full fallback must populate those same objects instead of creating
|
||||
the projection roots a second time."
|
||||
(when (and style-required-p
|
||||
(ebox-source--index-derived-stale-p source-index))
|
||||
;; Local subject deltas can retain a child's old parent object. The full
|
||||
;; style walk requires one coherent ancestry for its eq-keyed bindings.
|
||||
(setq source-index
|
||||
(ebox-tree-source-index
|
||||
root t ebox--render-root-parent-kind source-index)))
|
||||
(let* ((node-root (plist-get projection :node-root))
|
||||
(objects-by-node (plist-get projection :objects-by-node))
|
||||
;; The retained projection may share the published node-id table.
|
||||
;; Full projection visits every node and writes node-keyed entries.
|
||||
(objects-by-node (make-hash-table :test 'eq))
|
||||
(style-state-table
|
||||
(plist-get projection :style-binding-states))
|
||||
(stylesheet-signature
|
||||
@ -1762,6 +1771,8 @@ the projection roots a second time."
|
||||
(states-by-subject (make-hash-table :test 'eq))
|
||||
(subjects (when style-required-p
|
||||
(ebox-tree-subject-index root source-index))))
|
||||
(plist-put projection :source-index source-index)
|
||||
(plist-put projection :objects-by-node objects-by-node)
|
||||
(tp-object-retain context node-root)
|
||||
(ebox-surface--ensure-node-tree
|
||||
context node-root root source-index objects-by-node
|
||||
@ -6080,26 +6091,37 @@ published runtime and must be consumed without clearing those shared nodes."
|
||||
(or (ebox-surface--scroll-patch-output
|
||||
(ebox-surface--signals-buffer signals) state)
|
||||
(ebox-surface--render-candidate state)))
|
||||
((eq projection-kind 'formatting-context-reflow)
|
||||
(or (ebox-surface--formatting-context-reflow-output
|
||||
(ebox-surface--signals-buffer signals) state)
|
||||
(ebox-surface--render-candidate state)))
|
||||
((eq projection-kind 'mixed-owner-reflow)
|
||||
(or (ebox-surface--mixed-owner-output
|
||||
(ebox-surface--signals-buffer signals)
|
||||
previous-state state)
|
||||
(ebox-surface--render-candidate state)))
|
||||
((memq projection-kind '(span-patch owner-scoped))
|
||||
(if-let* ((span-output
|
||||
(ebox-surface--span-patch-output
|
||||
(ebox-surface--signals-buffer signals)
|
||||
previous-state state)))
|
||||
span-output
|
||||
(setq projection
|
||||
(ebox-surface--complete-projection
|
||||
context root source-index
|
||||
style-required-p projection))
|
||||
(setq state
|
||||
((memq projection-kind
|
||||
'(span-patch owner-scoped formatting-context-reflow
|
||||
mixed-owner-reflow))
|
||||
(if-let* ((local-output
|
||||
(pcase projection-kind
|
||||
('formatting-context-reflow
|
||||
(ebox-surface--formatting-context-reflow-output
|
||||
(ebox-surface--signals-buffer signals) state))
|
||||
('mixed-owner-reflow
|
||||
(ebox-surface--mixed-owner-output
|
||||
(ebox-surface--signals-buffer signals)
|
||||
previous-state state))
|
||||
(_
|
||||
(ebox-surface--span-patch-output
|
||||
(ebox-surface--signals-buffer signals)
|
||||
previous-state state)))))
|
||||
local-output
|
||||
;; A declined output may already have staged coordinates,
|
||||
;; fragments or paint layers. Rebuild from the original
|
||||
;; candidate inputs before producing ordinary full output.
|
||||
;; Range deltas already projected their complete object
|
||||
;; map, including newly registered keys in this context.
|
||||
(unless (and (eq projection-kind 'mixed-owner-reflow)
|
||||
(ebox-surface--mixed-range-splice-p state))
|
||||
(setq projection
|
||||
(ebox-surface--complete-projection
|
||||
context root source-index
|
||||
style-required-p projection)))
|
||||
(setq projection-kind nil)
|
||||
(setq source-index (plist-get projection :source-index))
|
||||
(setq state
|
||||
(ebox-surface--projection-state
|
||||
root previous-state state-overrides nil
|
||||
stylesheet-active-p cascade-required-p axes
|
||||
|
||||
@ -2996,6 +2996,370 @@ remain retained identities."
|
||||
(ebox-test-text "paint-c") :color paint-c))
|
||||
candidate))
|
||||
|
||||
(defun ebox-commit-test--mixed-formatting-ownership (scenario &optional inherited-p)
|
||||
"Check public ownership for specialized or declined formatting SCENARIO.
|
||||
When INHERITED-P is non-nil, keep ancestor color across an untouched sibling."
|
||||
(cl-labels
|
||||
((fixture (left right paint-a paint-b paint-c)
|
||||
(let ((tree (ebox-commit-test--mixed-owner-root
|
||||
left right paint-a paint-b paint-c)))
|
||||
(if inherited-p
|
||||
(ebox-test-box :key 'inherited-parent :color "#345678" tree)
|
||||
tree)))
|
||||
(nodes (state)
|
||||
(let (result)
|
||||
(cl-labels ((walk (node path)
|
||||
(push (cons path node) result)
|
||||
(cl-loop for child in (ebox-tree-node-children node)
|
||||
for index from 0
|
||||
do (walk child (append path (list index))))))
|
||||
(walk (plist-get state :root-node) '(root)))
|
||||
(nreverse result)))
|
||||
(region-paths (state)
|
||||
(let ((paths (make-hash-table :test #'eql))
|
||||
(regions (make-hash-table :test #'eql)))
|
||||
(dolist (entry (nodes state))
|
||||
(puthash (plist-get (cdr entry) :node-id) (car entry) paths))
|
||||
(maphash (lambda (region node-id)
|
||||
(puthash region (gethash node-id paths) regions))
|
||||
(plist-get state :region-node-table))
|
||||
regions))
|
||||
(region-path (regions id)
|
||||
(or (gethash id regions)
|
||||
(ert-fail (list :unknown-region id))))
|
||||
(source-ancestry (state)
|
||||
(let* ((index (plist-get state :source-index))
|
||||
(subjects (ebox-source--index-node-subjects index)))
|
||||
(mapcar
|
||||
(lambda (entry)
|
||||
(let ((subject
|
||||
(ebox-source--subject-table-get
|
||||
subjects (ebox-tree-node-source-handle (cdr entry)))))
|
||||
(list subject (ecss-subject-parent subject)
|
||||
(copy-sequence (ecss-subject-children subject)))))
|
||||
(nodes state))))
|
||||
(same-source-ancestry-p (before after)
|
||||
(and (= (length before) (length after))
|
||||
(cl-every
|
||||
(lambda (old new)
|
||||
(and (eq (car old) (car new))
|
||||
(eq (cadr old) (cadr new))
|
||||
(= (length (nth 2 old)) (length (nth 2 new)))
|
||||
(cl-every #'eq (nth 2 old) (nth 2 new))))
|
||||
before after)))
|
||||
(contents (buffer)
|
||||
;; Preserve every property, renaming only buffer-local region ids.
|
||||
(let* ((text (ebox-commit-test--buffer-string buffer))
|
||||
(regions (region-paths (ebox--buffer-render-state buffer)))
|
||||
(properties
|
||||
(delete-dups
|
||||
(append (mapcar #'cdr ebox-region-types)
|
||||
'(ebox-scroll-window
|
||||
ebox-overflow-foreground-source))))
|
||||
(position 0))
|
||||
(while (< position (length text))
|
||||
(let ((end (or (next-property-change position text) (length text)))
|
||||
(props (text-properties-at position text)))
|
||||
(dolist (property properties)
|
||||
(when-let* ((id (plist-get props property)))
|
||||
(setq props
|
||||
(plist-put props property (region-path regions id)))))
|
||||
(when-let* ((owners (plist-get props 'ebox-content-owners)))
|
||||
(setq props
|
||||
(plist-put props 'ebox-content-owners
|
||||
(mapcar (lambda (id) (region-path regions id))
|
||||
owners))))
|
||||
(set-text-properties position end props text)
|
||||
(setq position end)))
|
||||
text))
|
||||
(mounts (state node)
|
||||
(let ((regions (region-paths state))
|
||||
(object (gethash (plist-get node :node-id)
|
||||
(plist-get state :surface-node-object-table))))
|
||||
;; TP does not require a particular ordering of an owner's mounts.
|
||||
(sort
|
||||
(mapcar
|
||||
(lambda (mount)
|
||||
(let ((tags (plist-get mount :tags)))
|
||||
(when-let* ((region (plist-get tags :ebox/region-id)))
|
||||
(plist-put tags :ebox/region-id (region-path regions region)))
|
||||
mount))
|
||||
(tp-object-mounts object))
|
||||
(lambda (a b)
|
||||
(string< (prin1-to-string a) (prin1-to-string b)))))))
|
||||
(let* ((buffer
|
||||
(ebox-render-to-buffer
|
||||
(generate-new-buffer-name " *ebox-mixed-formatting-fallback*")
|
||||
(fixture
|
||||
"left-old" "right-old" "#111111" "#222222" "#333333")))
|
||||
(fresh nil)
|
||||
(left "left changed\nleft again")
|
||||
(right "right changed\nright again")
|
||||
(before (ebox--buffer-render-state buffer))
|
||||
(surface (with-current-buffer buffer ebox-surface--buffer-surface))
|
||||
(revision (tp-surface-revision surface))
|
||||
(runtime-revision (plist-get before :runtime-revision))
|
||||
(old-objects (plist-get before :surface-node-object-table))
|
||||
(old-object-facts
|
||||
(let (facts)
|
||||
(maphash (lambda (id object)
|
||||
(push (list id object (tp-object-mounts object)) facts))
|
||||
old-objects)
|
||||
facts))
|
||||
(old-text (ebox-commit-test--buffer-string buffer))
|
||||
(old-report (tp-surface-report surface))
|
||||
(old-mounts (tp--surface-mounts surface))
|
||||
(old-mount-ids (mapcar #'tp--surface-mount-id old-mounts))
|
||||
(old-mount-index (tp--surface-mount-index surface))
|
||||
(old-index (tp--surface-index surface))
|
||||
(old-ranges (plist-get before :surface-owned-ranges))
|
||||
(old-range-facts (ebox-surface--snapshot-owned-ranges old-ranges))
|
||||
(old-fragments (plist-get before :surface-fragments))
|
||||
(old-fragment-facts (copy-tree old-fragments))
|
||||
(old-source-index (plist-get before :source-index))
|
||||
(old-source-ancestry (and inherited-p (source-ancestry before)))
|
||||
(root-render (symbol-function 'ebox-surface--render-candidate))
|
||||
(formatting-output
|
||||
(symbol-function 'ebox-surface--formatting-context-reflow-output))
|
||||
(mixed-output (symbol-function 'ebox-surface--mixed-owner-output))
|
||||
(formatting-calls 0)
|
||||
(mixed-calls 0)
|
||||
(publication-failures 0)
|
||||
(root-renders 0)
|
||||
geometry-owner-id
|
||||
report)
|
||||
(unwind-protect
|
||||
(progn
|
||||
;; Only the specialized output boundary declines. The real
|
||||
;; public candidate, planner, mixed projector, and full renderer
|
||||
;; run normally; no candidate facts or reports are fabricated.
|
||||
(cl-letf
|
||||
(((symbol-function
|
||||
'ebox-surface--formatting-context-reflow-output)
|
||||
(lambda (target state)
|
||||
(cl-incf formatting-calls)
|
||||
(should (eq (plist-get state :projection-kind)
|
||||
'mixed-owner-reflow))
|
||||
(let ((proof (plist-get state :mixed-owner-proof)))
|
||||
(should (eq (plist-get proof :geometry-kind)
|
||||
'formatting-context-reflow))
|
||||
(setq geometry-owner-id
|
||||
(plist-get (plist-get proof :geometry-proof)
|
||||
:owner-id)))
|
||||
(when inherited-p
|
||||
(let* ((index (plist-get state :source-index))
|
||||
(subjects (ebox-source--index-node-subjects index))
|
||||
(peer (ebox--host-ref-node buffer 'untouched))
|
||||
(subject
|
||||
(ebox-source--subject-table-get
|
||||
subjects (ebox-tree-node-source-handle peer)))
|
||||
(parent-id
|
||||
(gethash (plist-get peer :node-id)
|
||||
(plist-get state :parent-table)))
|
||||
(parent
|
||||
(gethash parent-id (plist-get state :node-table)))
|
||||
(parent-subject
|
||||
(ebox-source--subject-table-get
|
||||
subjects (ebox-tree-node-source-handle parent))))
|
||||
(message "Inherited fallback candidate: derived-stale=%S untouched-parent-coherent=%S"
|
||||
(ebox-source--index-derived-stale-p index)
|
||||
(eq parent-subject (ecss-subject-parent subject)))))
|
||||
(unless (memq scenario '(decline rollback))
|
||||
(let ((output (funcall formatting-output target state)))
|
||||
(should (stringp output))
|
||||
output))))
|
||||
((symbol-function 'ebox-surface--mixed-owner-output)
|
||||
(lambda (target previous state)
|
||||
(cl-incf mixed-calls)
|
||||
(let ((output (funcall mixed-output target previous state)))
|
||||
(if (eq scenario 'late-decline)
|
||||
(progn
|
||||
;; Decline only after the actual projector has
|
||||
;; produced all three kinds of temporary state.
|
||||
(should (stringp output))
|
||||
(should (plist-get state :content-coordinate-patches))
|
||||
(should (plist-get state :mixed-owner-fragment-data))
|
||||
(should (plist-get state :paint-property-contributions))
|
||||
nil)
|
||||
output))))
|
||||
((symbol-function 'ebox-surface--render-candidate)
|
||||
(lambda (state)
|
||||
(cl-incf root-renders)
|
||||
(funcall root-render state))))
|
||||
(when (eq scenario 'rollback)
|
||||
(let ((tp--surface-publication-step-function
|
||||
(lambda (step target)
|
||||
(when (eq step 'client-state)
|
||||
(should (eq target surface))
|
||||
(cl-incf publication-failures)
|
||||
(should (= root-renders 1))
|
||||
(should (= (tp-surface-revision surface)
|
||||
(1+ revision)))
|
||||
(should-not (eq before (tp-surface-client-state surface)))
|
||||
(should-not
|
||||
(equal-including-properties
|
||||
old-text (ebox-commit-test--buffer-string buffer)))
|
||||
(error "Reject formatting fallback publication")))))
|
||||
(should
|
||||
(equal
|
||||
(should-error
|
||||
(ebox-commit
|
||||
buffer
|
||||
(ebox-commit-test--mixed-owner-candidate
|
||||
buffer left right "#AAAAAA" "#BBBBBB" "#CCCCCC")))
|
||||
'(error "Reject formatting fallback publication"))))
|
||||
(should (= publication-failures 1))
|
||||
(should (eq before (ebox--buffer-render-state buffer)))
|
||||
(should (eq before (tp-surface-client-state surface)))
|
||||
(should (= revision (tp-surface-revision surface)))
|
||||
(should (= runtime-revision
|
||||
(plist-get before :runtime-revision)))
|
||||
(should (equal old-report (tp-surface-report surface)))
|
||||
(should (equal-including-properties
|
||||
old-text (ebox-commit-test--buffer-string buffer)))
|
||||
(should (eq old-mounts (tp--surface-mounts surface)))
|
||||
(should (eq old-mount-index (tp--surface-mount-index surface)))
|
||||
(should (eq old-index (tp--surface-index surface)))
|
||||
(should (equal old-mount-ids
|
||||
(mapcar #'tp--surface-mount-id old-mounts)))
|
||||
(should (eq old-objects
|
||||
(plist-get before :surface-node-object-table)))
|
||||
(should (= (length old-object-facts) (hash-table-count old-objects)))
|
||||
(dolist (fact old-object-facts)
|
||||
(should (eq (nth 1 fact) (gethash (car fact) old-objects)))
|
||||
(should (equal (nth 2 fact)
|
||||
(tp-object-mounts (nth 1 fact)))))
|
||||
(should (eq old-ranges (plist-get before :surface-owned-ranges)))
|
||||
(should (equal old-range-facts
|
||||
(ebox-surface--snapshot-owned-ranges old-ranges)))
|
||||
(should (eq old-fragments (plist-get before :surface-fragments)))
|
||||
(should (equal-including-properties
|
||||
old-fragment-facts old-fragments))
|
||||
(when inherited-p
|
||||
(should (eq old-source-index (plist-get before :source-index)))
|
||||
(should (same-source-ancestry-p
|
||||
old-source-ancestry (source-ancestry before)))))
|
||||
;; Retry builds a new public candidate from the restored state.
|
||||
(setq report
|
||||
(ebox-commit
|
||||
buffer
|
||||
(ebox-commit-test--mixed-owner-candidate
|
||||
buffer left right "#AAAAAA" "#BBBBBB" "#CCCCCC"))))
|
||||
(should (= formatting-calls (if (eq scenario 'rollback) 2 1)))
|
||||
(should (= mixed-calls formatting-calls))
|
||||
(should (= root-renders
|
||||
(if (eq scenario 'specialized) 0 formatting-calls)))
|
||||
(should geometry-owner-id)
|
||||
(message "Formatting ownership scenario=%S specialized-calls=%d root-renders=%d publication-failures=%d"
|
||||
scenario formatting-calls root-renders publication-failures)
|
||||
(when (eq scenario 'specialized)
|
||||
(should (eq (plist-get report :projection-kind) 'mixed-owner-reflow))
|
||||
(should-not (plist-get report :tp-full-root))
|
||||
(should-not (plist-get report :tp-scope-fallback)))
|
||||
(should (= (tp-surface-revision surface) (1+ revision)))
|
||||
(should (zerop (plist-get report :created-objects)))
|
||||
(should (zerop (plist-get report :removed-objects)))
|
||||
(let* ((after (ebox--buffer-render-state buffer))
|
||||
(objects (plist-get after :surface-node-object-table)))
|
||||
(should (= (plist-get after :runtime-revision)
|
||||
(1+ runtime-revision)))
|
||||
(should (= (length old-object-facts) (hash-table-count old-objects)))
|
||||
(should (= (hash-table-count old-objects)
|
||||
(hash-table-count objects)))
|
||||
(dolist (fact old-object-facts)
|
||||
(should (eq (nth 1 fact) (gethash (car fact) old-objects)))
|
||||
(should (eq (nth 1 fact) (gethash (car fact) objects))))
|
||||
(when (eq scenario 'specialized)
|
||||
(let ((owner-mounts
|
||||
(tp-object-mounts (gethash geometry-owner-id objects))))
|
||||
(should (= (length owner-mounts) 1))
|
||||
(should (equal (plist-get (plist-get (car owner-mounts) :tags)
|
||||
:ebox/roles)
|
||||
'(content-owner content)))))
|
||||
(setq fresh
|
||||
(ebox-render-to-buffer
|
||||
(generate-new-buffer-name " *ebox-mixed-formatting-full*")
|
||||
(fixture
|
||||
left right "#AAAAAA" "#BBBBBB" "#CCCCCC")))
|
||||
(when inherited-p
|
||||
(should (eq old-source-index (plist-get before :source-index)))
|
||||
(should (same-source-ancestry-p
|
||||
old-source-ancestry (source-ancestry before)))
|
||||
(let ((actual-style
|
||||
(plist-get (ebox--host-ref-node buffer 'untouched)
|
||||
:ebox-computed-style))
|
||||
(expected-style
|
||||
(plist-get (ebox--host-ref-node fresh 'untouched)
|
||||
:ebox-computed-style)))
|
||||
(message "Untouched inherited color: actual=%S expected=%S"
|
||||
(ecss-computed-style-value actual-style :color)
|
||||
(ecss-computed-style-value expected-style :color))))
|
||||
(should (equal-including-properties
|
||||
(contents buffer) (contents fresh)))
|
||||
(when inherited-p
|
||||
(let ((actual-style
|
||||
(plist-get (ebox--host-ref-node buffer 'untouched)
|
||||
:ebox-computed-style))
|
||||
(expected-style
|
||||
(plist-get (ebox--host-ref-node fresh 'untouched)
|
||||
:ebox-computed-style)))
|
||||
(should (equal "#345678"
|
||||
(ecss-computed-style-value expected-style :color)))
|
||||
(should (equal (ecss-computed-style-values actual-style)
|
||||
(ecss-computed-style-values expected-style)))))
|
||||
(let* ((expected (ebox--buffer-render-state fresh))
|
||||
(actual-nodes (nodes after))
|
||||
(expected-nodes (nodes expected)))
|
||||
(should (equal (mapcar #'car actual-nodes)
|
||||
(mapcar #'car expected-nodes)))
|
||||
(cl-mapc
|
||||
(lambda (actual wanted)
|
||||
(ert-info ((format "public owner mounts at %S" (car actual)))
|
||||
(let ((actual-mounts (mounts after (cdr actual)))
|
||||
(expected-mounts (mounts expected (cdr wanted))))
|
||||
(when (eq scenario 'specialized)
|
||||
;; Local context ownership deliberately has explicit
|
||||
;; content-role tags; full output uses descendant tags.
|
||||
;; Both must publish the same complete coordinates.
|
||||
(setq actual-mounts
|
||||
(mapcar (lambda (mount)
|
||||
(list (plist-get mount :start)
|
||||
(plist-get mount :end)))
|
||||
actual-mounts)
|
||||
expected-mounts
|
||||
(mapcar (lambda (mount)
|
||||
(list (plist-get mount :start)
|
||||
(plist-get mount :end)))
|
||||
expected-mounts)))
|
||||
(should (equal actual-mounts expected-mounts)))))
|
||||
actual-nodes expected-nodes))))
|
||||
(when (buffer-live-p buffer) (kill-buffer buffer))
|
||||
(when (buffer-live-p fresh) (kill-buffer fresh))))))
|
||||
|
||||
(ert-deftest ebox-commit-mixed-formatting-specialized-preserves-owner-coverage ()
|
||||
"Successful formatting specialization stays local and owns its full output."
|
||||
(ebox-commit-test--mixed-formatting-ownership 'specialized))
|
||||
|
||||
(ert-deftest ebox-commit-mixed-formatting-fallback-preserves-full-ownership ()
|
||||
"A declined mixed formatting output must publish full-render ownership."
|
||||
(ebox-commit-test--mixed-formatting-ownership 'decline))
|
||||
|
||||
(ert-deftest ebox-commit-mixed-formatting-fallback-rolls-back-and-retries ()
|
||||
"Fallback publication failure restores exact ownership before a fresh retry."
|
||||
(ebox-commit-test--mixed-formatting-ownership 'rollback))
|
||||
|
||||
(ert-deftest ebox-commit-mixed-formatting-late-fallback-preserves-full-ownership ()
|
||||
"A late decline cannot reuse the successful projector's temporary results."
|
||||
(ebox-commit-test--mixed-formatting-ownership 'late-decline))
|
||||
|
||||
(ert-deftest ebox-commit-mixed-formatting-fallback-preserves-untouched-inheritance ()
|
||||
"Full fallback preserves inherited color through copied ancestor subjects."
|
||||
(ebox-commit-test--mixed-formatting-ownership 'decline t))
|
||||
|
||||
(ert-deftest ebox-commit-mixed-formatting-inheritance-rolls-back-and-retries ()
|
||||
"Inherited fallback preserves the old source ancestry on rollback and retry."
|
||||
(ebox-commit-test--mixed-formatting-ownership 'rollback t))
|
||||
|
||||
|
||||
|
||||
|
||||
@ -3138,7 +3502,8 @@ remain retained identities."
|
||||
(should (ebox-surface--mixed-range-splice-p candidate-state))
|
||||
(funcall root-render candidate-state))))
|
||||
(setq report (ebox-commit (current-buffer) candidate)))
|
||||
(should (eq (plist-get report :projection-kind) 'mixed-owner-reflow))
|
||||
(should (eq (plist-get report :strategy) 'mixed-owner-reflow))
|
||||
(should-not (plist-get report :projection-kind))
|
||||
(should (= 1 root-renders))
|
||||
(should-not (plist-get report :tp-retained-content))
|
||||
(should (> (plist-get report :created-objects) 0))
|
||||
|
||||
Loading…
Reference in New Issue
Block a user