Refuse a symbol placed inside itself, and label instances by what they place
A paste put an instance of "bg" inside "bg" itself. It saved, loaded, and then threw "symbol cycle in audio" out of nest/audio-tracks, because a container that contains itself has no finite expansion. place-symbol and ui/drag already refused that, each by asking clip/contains-symbol?. Paste asks clip/problems instead, and problems did not encode the invariant at all -- it checked missing symbols, pose tracks and audio links, but never the placement graph. So the rule goes where every command is already checked: paste, cut, duplicate, correction and the span ops all gate on problems, so one rule covers them all. Why it looked like a reasonable thing to do is the other half. place-symbol copied the symbol's name onto the instance it made, and that copy went stale on the next rename: the symbol read "bg" in its tab while an instance of it still read "symbol-18", which is its id from before it was named. One object under two names, with nothing on screen to connect them. So instances are no longer given a name at creation, and clip/node-label reads the symbol's name through on every render. :name on an instance now means only what a person typed, which is what tells two instances of one symbol apart -- "8625 left" and "8625 right" of one "face" -- so an authored name still wins and read-through is the fallback. The label logic was duplicated across four call sites with three different fallback orders; location.cljs already read through and the others did not. They now share one function. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
0d49db793b
commit
2460dce0a5
7 changed files with 130 additions and 27 deletions
|
|
@ -39,6 +39,27 @@
|
|||
"an ordinary symbol may contain cropped content beyond its window")
|
||||
(is (empty? (clip/problems made)))))
|
||||
|
||||
(deftest pasting-a-symbol-into-itself-is-refused
|
||||
;; What happened to a real document: an instance of "bg" was copied from the
|
||||
;; symbol holding it, and pasted while "bg" itself was the open symbol. Nothing
|
||||
;; on the paste path looked at the placement graph, so it saved, loaded, and
|
||||
;; then threw "symbol cycle in audio" out of nest/audio-tracks.
|
||||
(let [doc (-> (clip/blank)
|
||||
(assoc-in [:symbols :outer] {:id :outer :frames 200 :nodes {}})
|
||||
(assoc-in [:symbols :inner] {:id :inner :frames 10 :nodes {}})
|
||||
(clip/place-symbol nil :outer :inner 5 (random-uuid) nil))
|
||||
id (first (keys (get-in doc [:symbols :outer :nodes])))
|
||||
payload (:clipboard (clipboard/snapshot doc [(address :outer id [id])]))]
|
||||
(testing "into the symbol it places"
|
||||
(let [r (clipboard/paste doc payload :inner 0 {:fresh-id (ids)})]
|
||||
(is (= "symbol :inner instance :copy-1 places :inner, which is itself"
|
||||
(:refused r)))
|
||||
(is (nil? (:clip r)) "and the document is not handed back changed")))
|
||||
(testing "while pasting it somewhere harmless still works"
|
||||
(let [r (clipboard/paste doc payload :outer 0 {:fresh-id (ids)})]
|
||||
(is (nil? (:refused r)))
|
||||
(is (empty? (clip/problems (:clip r))))))))
|
||||
|
||||
(deftest lane-paste-claims-time-as-one-command
|
||||
(let [doc (fixture/document)
|
||||
payload (:clipboard (clipboard/snapshot doc [(address :main :a [:a])]))
|
||||
|
|
|
|||
|
|
@ -235,6 +235,51 @@
|
|||
(is (= (get-in c [:symbols :outer :nodes])
|
||||
(get-in (leaf/clip "c" (leaf/leaves "c" c)) [:symbols :outer :nodes]))))))
|
||||
|
||||
(deftest an-instance-is-labelled-by-what-it-places
|
||||
(let [c (-> (nested)
|
||||
(assoc-in [:symbols :inner :name] "mouth"))
|
||||
[id n] (first (get-in c [:symbols :outer :nodes]))]
|
||||
(testing "placing does not copy the symbol's name onto the node"
|
||||
(is (nil? (:name n))
|
||||
"a cached name is what goes stale; there is nothing to go stale"))
|
||||
(testing "so the label follows the symbol, including a later rename"
|
||||
(is (= "mouth" (clip/node-label c id n)))
|
||||
(is (= "jaw" (clip/node-label (assoc-in c [:symbols :inner :name] "jaw") id n))
|
||||
"renaming the symbol renames every row that shows an instance of it"))
|
||||
(testing "an unnamed symbol falls back to its id, not to the node's uuid"
|
||||
(is (= "inner" (clip/node-label (update-in c [:symbols :inner] dissoc :name)
|
||||
id n))))
|
||||
(testing "but a name somebody typed on the instance still wins"
|
||||
;; Two instances of one symbol are told apart only by this.
|
||||
(is (= "left eye" (clip/node-label c id (assoc n :name "left eye")))))
|
||||
(testing "and a node that places nothing is labelled by its own name or id"
|
||||
(is (= "lid" (clip/node-label c :lid {:id :lid :kind :rect :name "lid"})))
|
||||
(is (= "lid" (clip/node-label c :lid {:id :lid :kind :rect}))))))
|
||||
|
||||
(deftest a-cycle-is-a-problem-and-not-only-a-refusal
|
||||
;; `place-symbol` and `ui/drag` refuse to MAKE one; this is the document being
|
||||
;; asked whether it already has one, which is the question every other command
|
||||
;; — paste above all — gets to ask by calling `clip/problems`.
|
||||
(let [c (nested)
|
||||
instance (fn [c host src]
|
||||
(assoc-in c [:symbols host :nodes :loop]
|
||||
{:id :loop :kind :instance :z "z" :parent nil
|
||||
:span [0 10] :time {:mode :map :at 0 :rate 1}
|
||||
:source {:symbol src}}))]
|
||||
(testing "a symbol placed inside itself"
|
||||
(is (= ["symbol :inner instance :loop places :inner, which is itself"]
|
||||
(clip/problems (instance c :inner :inner)))))
|
||||
(testing "a symbol placed inside something it already places"
|
||||
;; Both edges of :outer -> :inner -> :outer close the loop, so both are
|
||||
;; named: either one is a fair thing to undo.
|
||||
(let [ps (clip/problems (instance c :inner :outer))]
|
||||
(is (= 2 (count ps)))
|
||||
(is (some #{"symbol :inner instance :loop places :outer, which contains it"} ps))
|
||||
(is (some #(re-find #"^symbol :outer instance .* places :inner, which contains it$" %)
|
||||
ps))))
|
||||
(testing "and an instance that closes no loop is still fine"
|
||||
(is (empty? (clip/problems (instance c :loose :inner)))))))
|
||||
|
||||
(deftest a-new-symbol-is-empty-and-placed-where-it-was-asked-for
|
||||
(let [c (nested)
|
||||
u #uuid "00000000-0000-4000-8000-000000000001"
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue