Non-overlap is an invariant, not a report
The plan asked what to draw when a symbol in lane mode holds overlapping children, and offered to report it as a decision waiting for a person. Wrong question: they never overlap. Placement claims time — anything placed, moved or grown over occupied time trims, removes or splits what it lands on — so the operation that could have made an overlap did not, and `span/finish`, which is already the single commit path and already refuses rather than half-applying, is where that is enforced. An overlap is then a bug in a command and not a state to design around. The check stays, named `symbol/overlaps` and used three ways: the commit path refuses one, a property test asserts no command can produce one, and a document that somehow holds one still LOADS and is drawn visibly wrong with the status saying so. Not `problems`, which stops a document loading, and not `conflicts`, which means somebody has a decision to make — a display hint must never be able to keep a document from opening. The one place a person can ask for the impossible is toggling lane mode on over children that already overlap. That refuses and offers to trim them into a sequence, through the `:required-frames` retry the model already uses. Also written down, because it is the pair the modifier exists to separate: a plain body drag is temporal and replaces, trimming extents as needed; shift is structural and goes through `nest/move-node` into the symbol under the pointer, which has to keep working for symbols held in a lane. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
10b96761fa
commit
6e7827e03f
1 changed files with 84 additions and 30 deletions
|
|
@ -1,7 +1,8 @@
|
||||||
# A lane is a view
|
# A lane is a view
|
||||||
|
|
||||||
Plan, 2026-10-01, written at `2dc5735`. It undoes the lane model as a thing in
|
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 document and keeps what it was for. Build on what is there and tear out
|
||||||
|
half of it.
|
||||||
|
|
||||||
## The decision
|
## The decision
|
||||||
|
|
||||||
|
|
@ -25,7 +26,7 @@ places.
|
||||||
| a group node with `:layout :sequence` | nothing — the symbol is the container |
|
| a group node with `:layout :sequence` | nothing — the symbol is the container |
|
||||||
| `node/lane?` | a view question: is this symbol drawn in lane mode |
|
| `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-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 |
|
| `symbol/lane-problems` | `symbol/overlaps`, a diagnostic the write path calls |
|
||||||
| `lane/lane-frame` | `clip/source-time` — one clock instead of two |
|
| `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 |
|
| `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 |
|
| `clip/lane-node`, the born-with lane | gone; a symbol is born empty again |
|
||||||
|
|
@ -44,13 +45,66 @@ 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
|
`finish`. The sequence operations are the same subject — re-spanning children
|
||||||
— and keeping them apart was a consequence of lanes existing.
|
— and keeping them apart was a consequence of lanes existing.
|
||||||
|
|
||||||
|
## Children in lane mode never overlap
|
||||||
|
|
||||||
|
This is an invariant, not a condition to check for and report. Placement
|
||||||
|
claims time: anything placed, moved or grown over occupied time TRIMS the
|
||||||
|
extents it lands on — trimming the incumbent, removing one wholly covered, or
|
||||||
|
splitting one it lands inside — so the result has no overlap because the
|
||||||
|
operation that could have made one did not. That is `blank` followed by a
|
||||||
|
non-rippling placement, which is what `overwrite-drawing` already composes.
|
||||||
|
|
||||||
|
Enforced at the boundary, which already exists: `span/finish` is the single
|
||||||
|
commit path for every one of these commands, it validates before it returns,
|
||||||
|
and it refuses rather than half-applying. So `finish` gains the overlap check
|
||||||
|
for a symbol in lane mode, and no command can commit one. An overlap that
|
||||||
|
appears anyway is a bug in a command, not a state to design around.
|
||||||
|
|
||||||
|
Keep the check as a named diagnostic — `symbol/overlaps`, taking a symbol and
|
||||||
|
returning the pairs — used three ways:
|
||||||
|
|
||||||
|
1. `span/finish` refuses when it would commit one.
|
||||||
|
2. The test suite asserts no command can produce one: a property over the
|
||||||
|
commands in the style of `drawn` in `lane_test`, which samples rather than
|
||||||
|
computing expected numbers by hand.
|
||||||
|
3. A document that somehow arrives holding one still LOADS — a display hint
|
||||||
|
must never be able to stop a document loading — and the timeline draws it
|
||||||
|
visibly wrong with the status line saying so. Not `clip/problems`, which
|
||||||
|
means the document will not load, and not `clip/conflicts`, which means a
|
||||||
|
person has a decision to make. This is neither: it is a bug report.
|
||||||
|
|
||||||
|
Toggling lane mode ON for a symbol whose children already overlap is the one
|
||||||
|
place a person can ask for the impossible. Refuse it and say why, with the
|
||||||
|
`:required-frames` retry pattern offering to trim them into a sequence — the
|
||||||
|
domain reports what it would need, the UI offers one button.
|
||||||
|
|
||||||
|
Outside lane mode nothing is enforced, because overlapping children are what
|
||||||
|
compositing IS. An endpoint drag there is an ordinary span edit that may
|
||||||
|
overlap; the claim-time rule follows the mode.
|
||||||
|
|
||||||
|
## The two drag intentions
|
||||||
|
|
||||||
|
Unchanged from `docs/lane-nesting-notes.md`, and both kept:
|
||||||
|
|
||||||
|
- **Plain drag** of a clip body is temporal: it moves in time, within its
|
||||||
|
symbol or into another symbol drawn as a lane, and it REPLACES — trimming,
|
||||||
|
removing and splitting extents as needed so nothing overlaps.
|
||||||
|
- **Shift-drag** is structural: the dragged node goes INSIDE the symbol the
|
||||||
|
clip under the pointer places, through `nest/move-node`, which preserves the
|
||||||
|
world transform and the root timing. This must keep working for symbols
|
||||||
|
contained in a lane, which is the case it exists for.
|
||||||
|
|
||||||
|
Overlap cannot distinguish them — dropping on occupied time already means
|
||||||
|
claiming it — so the modifier says which, and the label by the pointer says it
|
||||||
|
back. `nest/move-refusal` already answers before the drop.
|
||||||
|
|
||||||
## Where lane mode lives
|
## Where lane mode lives
|
||||||
|
|
||||||
A symbol is drawn as a lane because somebody said so, not because of what its
|
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
|
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
|
not currently overlap" means a symbol stops being a lane the moment anything
|
||||||
makes two children overlap, and the rules that were maintaining non-overlap
|
overlaps, and the rules that maintain non-overlap switch off exactly when they
|
||||||
switch off exactly when they are needed.
|
are needed.
|
||||||
|
|
||||||
Two options:
|
Two options:
|
||||||
|
|
||||||
|
|
@ -66,8 +120,8 @@ Two options:
|
||||||
|
|
||||||
Recommended: 2. It is one field, it keeps editing rules reproducible between
|
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
|
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
|
to hold the line on is that nothing outside the timeline, and the commit
|
||||||
is allowed to read it.
|
path's overlap check, is allowed to read it.
|
||||||
|
|
||||||
## Order of work
|
## Order of work
|
||||||
|
|
||||||
|
|
@ -79,27 +133,29 @@ Each step compiles, passes `npm test`, and leaves the editor usable.
|
||||||
`:cels` blocks for the collapsed row, and keep `inside-rows` — including
|
`:cels` blocks for the collapsed row, and keep `inside-rows` — including
|
||||||
its `:unmapped?` branch, which is what makes a held drawing's contents
|
its `:unmapped?` branch, which is what makes a held drawing's contents
|
||||||
reachable at all.
|
reachable at all.
|
||||||
2. **Lane mode as a hint.** Add the field, draw a symbol's children as blocks
|
2. **Lane mode as a hint.** Add the field and the toggle, draw a symbol's
|
||||||
when it is set and as rows when it is not, and move the lane-row drag
|
children as blocks when it is set and as rows when it is not, and move the
|
||||||
handling onto it. Now both display paths exist and nothing in the domain
|
lane-row drag handling onto it. Both display paths now exist and nothing in
|
||||||
has changed yet.
|
the domain has changed.
|
||||||
3. **Re-base the commands.** Move `lane.cljs` into `span.cljs`, replacing
|
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-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`
|
`lane-id` argument. `frontend/test/arthur/domain/lane_test.cljs` is the
|
||||||
are the proof of this step: they should need their fixtures changed and
|
proof: its fixtures should change and its assertions should not, and any
|
||||||
their assertions kept, and any assertion that has to change is a behaviour
|
assertion that has to change is a behaviour change worth noticing.
|
||||||
change worth noticing.
|
4. **Move the invariant.** `symbol/lane-problems` becomes `symbol/overlaps`,
|
||||||
4. **Delete the rest.** `node/lane?`, `symbol/lane-clips`,
|
called by `span/finish` for a symbol in lane mode, plus the property test
|
||||||
`symbol/lane-problems`, `clip/lane-node`, `::ui/new-lane`,
|
that no command can produce an overlap.
|
||||||
`::ui/adopt-in-lane`, the lane branch of `::ui/new-symbol`, lane renaming,
|
5. **Delete the rest.** `node/lane?`, `symbol/lane-clips`, `clip/lane-node`,
|
||||||
`aimed-lane`, and the `:lane?`/`sound-lane?` row flags. Rename what is left
|
`::ui/new-lane`, `::ui/adopt-in-lane`, the lane branch of
|
||||||
so the word does not appear outside the timeline.
|
`::ui/new-symbol`, lane renaming, `aimed-lane`, and the
|
||||||
5. **Audio falls out.** An audio node is already a parent-less child of a
|
`:lane?`/`sound-lane?` row flags. Rename what is left so the word does not
|
||||||
|
appear outside the timeline.
|
||||||
|
6. **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
|
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 `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
|
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.
|
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
|
7. **Shift-to-reparent stays** as it is: `nest/move-node` and
|
||||||
`nest/move-refusal` never knew about lanes.
|
`nest/move-refusal` never knew about lanes.
|
||||||
|
|
||||||
## What must not be lost
|
## What must not be lost
|
||||||
|
|
@ -121,15 +177,13 @@ to keep.
|
||||||
|
|
||||||
## Open questions
|
## Open questions
|
||||||
|
|
||||||
1. **What happens to a symbol in lane mode whose children overlap anyway** —
|
1. **Is lane mode a property of the symbol or of the instance placing it?** A
|
||||||
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
|
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.
|
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
|
2. **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
|
the children?** Same handle, two commands; the row is now an instance, so
|
||||||
it has a span of its own for the first time.
|
it has a span of its own for the first time.
|
||||||
|
3. **The held destination for shift-to-reparent** is still refused —
|
||||||
|
`nest/inside` has no invertible clock for a hold, which is most of what
|
||||||
|
anybody would try to nest into. `docs/lane-nesting-notes.md` argues the
|
||||||
|
refusal is stronger than the facts require and says what would settle it.
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue