arthur/docs/lane-is-a-view-notes.md
2026-10-03 00:39:07 -04:00

3.8 KiB

Implementation notes — "A lane is a view"

Running log for docs/lane-is-a-view-plan.md. [ ] not started, [~] in progress, [x] done with npm test green.

Baseline at fb38990: 475 tests, 9621 assertions, 0 failures.

Order of work

The plan's seven steps, re-grouped — see Deviation from the plan's order below.

  • A. Domain: symbol/children, symbol/lane?, symbol/overlaps, lane.cljs → span.cljs, the overlap check in span/finish (plan steps 3, 4, and the domain half of 5)
  • B. Events: re-base callers and remove lane-node-specific commands; keep explicit ::new-lane and cross-lane adoption (plan step 5)
  • C. UI: row per symbol, explicit lane creation, and drag handling (plan steps 1, 2, and the UI half of 5)
  • D. Audio: delete holds-other?, the mixed-lane refusal, the in-lane filter in sound-rows (plan step 6)
  • E. Tests: domain/sequence_test, events/lane_test, browser/lane.mjs
  • F. Shift-to-reparent still works, untouched (plan step 7)

Not in this pass — see Left for a second pass: tearing out the held cel, the instance-playback control, drawing a loop's repeats, the audio period guard.

Deviation from the plan's order

The plan's steps 1 and 2 are display work that keys off "a symbol's children", and step 3 is what MAKES the cels a symbol's children. Until then a cel's :parent is the lane node, so there is nothing for the display to read: step 2 cannot draw "a symbol's children as blocks" while the children belong to a group. So the data model moves first (A) and the display follows (C). The content of each step is unchanged; only the order is.

The one thing this gives up is the plan's promise that every step leaves the editor usable — between A and C the timeline draws the new shape with the old code. npm test is green at each step either way.

Decisions taken

  1. Lane mode is :display :lane on the SYMBOL — plan's recommendation 2, and open question 1 answered "the symbol, not the instance". A symbol placed twice is drawn as a lane in both places. Added to symbol/symbol-keys and to leaf/leaves' select-keys so it saves like :frames.
  2. A symbol's children are its parent-less nodes that have a placed span. The plan's step 6 settles it: "an audio node is already a parent-less child of a symbol, which is exactly the new shape". Span-less nodes — a shape on screen for the whole shot — are not in the sequence and are skipped, which is also what stops the commands destructuring a nil span.
  3. A symbol holds at most one sequence. It follows from 1 and 2: the container is the symbol. Two lanes of picture is now two symbols placed in a third, which is what compositing already was.
  4. The open symbol gets a row of its own in lane mode, and only then. The blocks have to sit on a row and the open symbol had none; expanding it turns its children into ordinary rows. Not a row always, which would shift every row in the pane for no gain.
  5. Open question 2 — a lane row's edge drag trims the PLACING INSTANCE's span, via span/resize-out, like the handle on every other row. Rippling the children is what the cel blocks' own edges already do, and giving one handle two meanings is what the plan refuses elsewhere.
  6. Open question 3 — the lane work first, the held cel after. The plan says they are independent, and the held cel is joined to a loop control that does not exist yet; doing it second costs one more pass over lane_test's fixtures and risks nothing.
  7. Lane creation stays explicit. A blank document and new symbol create ordinary symbols. The separate new → lane command creates and places a symbol with :display :lane in the effective creation target derived from selection and playhead; the new lane then becomes the primary selection.

Notes