136 lines
7.1 KiB
Markdown
136 lines
7.1 KiB
Markdown
|
|
# A lane is a view
|
||
|
|
|
||
|
|
Plan, 2026-10-01, written at `2dc5735`. It undoes the lane model as a thing in
|
||
|
|
the document and keeps what it was for.
|
||
|
|
|
||
|
|
## The decision
|
||
|
|
|
||
|
|
> A lane is a view over a symbol with sequential, non-overlapping children.
|
||
|
|
|
||
|
|
Nothing in the document is a lane. There is no lane type, no lane group, no
|
||
|
|
`:layout :sequence`, no lane commands and no lane validation. The word
|
||
|
|
survives in exactly two places: the UI, where a symbol can be DRAWN as a lane,
|
||
|
|
and the drag handling that re-spans a symbol's children while it is being
|
||
|
|
drawn that way.
|
||
|
|
|
||
|
|
The display model goes back to a row per symbol. A symbol in lane mode draws
|
||
|
|
its children as blocks on its own single row; expanded, they are rows like
|
||
|
|
anything else. Everything else is an ordinary row that expands into what it
|
||
|
|
places.
|
||
|
|
|
||
|
|
## What a lane was, and what each part becomes
|
||
|
|
|
||
|
|
| was | becomes |
|
||
|
|
| --- | --- |
|
||
|
|
| a group node with `:layout :sequence` | nothing — the symbol is the container |
|
||
|
|
| `node/lane?` | a view question: is this symbol drawn in lane mode |
|
||
|
|
| `symbol/lane-clips nodes lane-id` | the children of a symbol, sorted by `node/placed-span` |
|
||
|
|
| `symbol/lane-problems` | gone. Nothing enforces non-overlap; the drag that claims time produces it |
|
||
|
|
| `lane/lane-frame` | `clip/source-time` — one clock instead of two |
|
||
|
|
| `domain/lane.cljs` | re-based onto `domain/span.cljs`: re-spanning a symbol's children |
|
||
|
|
| `clip/lane-node`, the born-with lane | gone; a symbol is born empty again |
|
||
|
|
| `::ui/new-lane`, `::ui/adopt-in-lane`, lane renaming | gone, gone, and ordinary node renaming |
|
||
|
|
|
||
|
|
`lane.cljs`'s fourteen commands are not deleted — they are what "endpoint drag
|
||
|
|
overlap handling" means, and they already do the right arithmetic. What
|
||
|
|
changes is their subject: every one of them currently takes a host symbol AND
|
||
|
|
a lane id and asks `lane-clips nodes lane-id`; each takes a symbol and asks
|
||
|
|
for its children. `extend-hold`, `resize-out`, `resize-in`, `roll`, `blank`,
|
||
|
|
`place-symbol`, `adopt`, `append-drawing`, `reuse-drawing`,
|
||
|
|
`duplicate-drawing`, `overwrite-drawing`, `make-unique`. `span/finish` is
|
||
|
|
already the one commit path and stays exactly as it is.
|
||
|
|
|
||
|
|
Put them in `span.cljs`, which already owns "one write to one node's span" and
|
||
|
|
`finish`. The sequence operations are the same subject — re-spanning children
|
||
|
|
— and keeping them apart was a consequence of lanes existing.
|
||
|
|
|
||
|
|
## Where lane mode lives
|
||
|
|
|
||
|
|
A symbol is drawn as a lane because somebody said so, not because of what its
|
||
|
|
children happen to look like at this moment. Deriving it from "the children do
|
||
|
|
not currently overlap" means a symbol stops being a lane the moment a drag
|
||
|
|
makes two children overlap, and the rules that were maintaining non-overlap
|
||
|
|
switch off exactly when they are needed.
|
||
|
|
|
||
|
|
Two options:
|
||
|
|
|
||
|
|
1. **Editor state**, `[:ui :lane-mode #{sid}]`. Purest reading of "a lane is a
|
||
|
|
view". But the drag rules follow the mode, so an unsaved, per-person toggle
|
||
|
|
would decide whether dropping a symbol trims its neighbour or composites
|
||
|
|
over it — the same gesture doing two different things to the document
|
||
|
|
depending on something the document does not record.
|
||
|
|
2. **A display hint on the symbol**, e.g. `:display :lane`, saved like any
|
||
|
|
other field (`clip-keys`, `leaf/leaves`, `leaf/clip` in the same commit).
|
||
|
|
Still not a type: nothing in evaluation reads it, `symbol/problems` does
|
||
|
|
not check it, and a symbol with it set behaves identically on the stage.
|
||
|
|
|
||
|
|
Recommended: 2. It is one field, it keeps editing rules reproducible between
|
||
|
|
people, and it does not make the symbol a different kind of thing. The thing
|
||
|
|
to hold the line on is that NOTHING outside the timeline and its drag handling
|
||
|
|
is allowed to read it.
|
||
|
|
|
||
|
|
## Order of work
|
||
|
|
|
||
|
|
Each step compiles, passes `npm test`, and leaves the editor usable.
|
||
|
|
|
||
|
|
1. **Row per symbol.** In `ui/timeline.cljs`, delete `portal`, the `::portal`
|
||
|
|
hint row, the `under?` lineage predicate and the `chosen` argument; emit a
|
||
|
|
row for a clip whose parent is a lane instead of skipping it. Keep the
|
||
|
|
`:cels` blocks for the collapsed row, and keep `inside-rows` — including
|
||
|
|
its `:unmapped?` branch, which is what makes a held drawing's contents
|
||
|
|
reachable at all.
|
||
|
|
2. **Lane mode as a hint.** Add the field, draw a symbol's children as blocks
|
||
|
|
when it is set and as rows when it is not, and move the lane-row drag
|
||
|
|
handling onto it. Now both display paths exist and nothing in the domain
|
||
|
|
has changed yet.
|
||
|
|
3. **Re-base the commands.** Move `lane.cljs` into `span.cljs`, replacing
|
||
|
|
`(lane-clips nodes lane-id)` with the symbol's children and dropping the
|
||
|
|
`lane-id` argument. The tests in `frontend/test/arthur/domain/lane_test.cljs`
|
||
|
|
are the proof of this step: they should need their fixtures changed and
|
||
|
|
their assertions kept, and any assertion that has to change is a behaviour
|
||
|
|
change worth noticing.
|
||
|
|
4. **Delete the rest.** `node/lane?`, `symbol/lane-clips`,
|
||
|
|
`symbol/lane-problems`, `clip/lane-node`, `::ui/new-lane`,
|
||
|
|
`::ui/adopt-in-lane`, the lane branch of `::ui/new-symbol`, lane renaming,
|
||
|
|
`aimed-lane`, and the `:lane?`/`sound-lane?` row flags. Rename what is left
|
||
|
|
so the word does not appear outside the timeline.
|
||
|
|
5. **Audio falls out.** An audio node is already a parent-less child of a
|
||
|
|
symbol, which is exactly the new shape — so the audio-in-lane rules added
|
||
|
|
in `2dc5735` (`holds-other?`, the mixed-lane refusal, the `in-lane` filter
|
||
|
|
in `sound-rows`) delete rather than migrate. A symbol drawn as a lane whose
|
||
|
|
children are sounds is an audio lane, and that is the whole of it.
|
||
|
|
6. **Shift-to-reparent stays** as it is: `nest/move-node` and
|
||
|
|
`nest/move-refusal` never knew about lanes.
|
||
|
|
|
||
|
|
## What must not be lost
|
||
|
|
|
||
|
|
All of this was broken at some point today and is now proved; each has a test
|
||
|
|
to keep.
|
||
|
|
|
||
|
|
- A held clip's contents are reachable from the root timeline, with no keys
|
||
|
|
and no draggable edges — `source-time` is nil for a hold, and the walk used
|
||
|
|
to stop there.
|
||
|
|
- Double-clicking a clip opens its symbol as a tab, and the editor survives
|
||
|
|
it: `symbol/lineage` must not report a cycle for an id the symbol does not
|
||
|
|
hold, and opening a symbol must drop a selection pointing into the one being
|
||
|
|
left.
|
||
|
|
- Selection waits for pointer-up, so a press does not re-draw the timeline out
|
||
|
|
from under the gesture it is starting.
|
||
|
|
- A drop never silently deletes what it lands on.
|
||
|
|
- A sound is drawn once, not twice.
|
||
|
|
|
||
|
|
## Open questions
|
||
|
|
|
||
|
|
1. **What happens to a symbol in lane mode whose children overlap anyway** —
|
||
|
|
through a reparent, a paste, or a document written before the mode existed?
|
||
|
|
Drawing it as rows is honest and loses the mode silently; drawing
|
||
|
|
overlapping blocks is a lie. Suggest: draw the blocks, and report it the
|
||
|
|
way `clip/conflicts` reports a correction nobody has resolved — a decision
|
||
|
|
waiting for a person, not a `problem`.
|
||
|
|
2. **Is lane mode a property of the symbol or of the instance placing it?** A
|
||
|
|
symbol placed twice would be drawn the same way in both places under the
|
||
|
|
first reading. That is probably right, and worth saying out loud.
|
||
|
|
3. **Does a lane row's edge drag trim the placing instance's span, or ripple
|
||
|
|
the children?** Same handle, two commands; the row is now an instance, so
|
||
|
|
it has a span of its own for the first time.
|