A lane is a generic row of symbol clips
A lane was a drawing lane: the only thing that could go in one was a one-frame held cel, and every other symbol instance stayed a permanent root row of its own. Those are not two kinds of timing, they are one kind with two creation policies. `lane/place-symbol` drops any library symbol in as a clip that plays naturally at speed one, `lane/adopt` moves an instance that is already in the document into a lane keeping its source, span, playback and corrections, and `append-drawing`/`overwrite-drawing` keep being the policy that makes a new empty symbol a one-frame hold. The child shape they produce is the same. Both new commands claim their interval through `blank` before they write, so the partition rule is unchanged and unduplicated: placing into occupied lane time trims, removes or splits the incumbents, and a lane still never stores an overlap. Real compositing overlap is another lane, where the order is explicit. Creating a symbol with nothing aimed now makes a lane and a clip in it instead of a loose root instance, and a pool drop prefers an explicitly targeted lane, then the selected one, and makes a lane only when there is neither. That is what stops the row-per-symbol growth coming back in through the drop path, and it is why `add-lane` now takes a z in front of the existing root nodes and calls what it makes a "lane" rather than "drawings". The timeline learned the two gestures that a generic lane needs. A clip body dragged over another lane's track previews there as a dashed block and lands through `::adopt-in-lane`; the track is found with `elementsFromPoint` and its selection read back off the element, because a pointer capture does not retarget. A pool drop over an existing lane previews as a dashed clip inside that lane instead of a temporary new row that appears and then vanishes -- which also needed the drag-leave check to be geometric, since inserting the preview changes the element under the pointer and Chromium then reports a leave with no related target. Lanes are renameable from their label, by double-click, F2, or the pencil, through `::rename-node`. `symbol/lane-cels` is `symbol/lane-clips`, and the vocabulary table in the handoff now separates the two words it had merged: a clip is an instance in a lane, and a cel is specifically the one-frame held source that drawing creation makes. Keeping `cel` for the policy is what lets the lane stop being about drawings at all. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
This commit is contained in:
parent
26ada03591
commit
5ebe776ce4
12 changed files with 590 additions and 174 deletions
|
|
@ -1,12 +1,14 @@
|
|||
# Lane and cel handoff
|
||||
# Lane and symbol-clip handoff
|
||||
|
||||
Status (2026-10-01): the timeline is the one timing interface. Cels are ordinary
|
||||
nodes with their own playback clock and appear as blocks on one row per drawing
|
||||
lane. Blocks move by mouse; edge drags trim without overlap; Shift-right-edge
|
||||
drags ripple every later cel; and the center of a shared cut composes the two
|
||||
edge edits into a rolling edit. Linked audio follows picture moves while its
|
||||
edges remain independently trimmable. Rotation and position corrections survive
|
||||
regeneration and expose conflicts for removal or retry.
|
||||
Status (2026-10-01): the timeline is the one timing interface. A lane is a
|
||||
generic non-overlapping row of symbol clips; it is not a special drawing type.
|
||||
Dropping a library symbol makes a naturally playing clip, while creating a new
|
||||
empty symbol makes a one-frame held clip at the playhead. With no destination
|
||||
lane, either operation creates one. Existing legacy root symbol rows can be
|
||||
dragged into a lane. Blocks move by mouse; edge drags claim time by trimming
|
||||
neighbors; Shift-edge drags ripple every later clip; and the center of a shared
|
||||
cut composes the two edge edits into a rolling edit. Linked audio follows picture
|
||||
moves while its edges remain independently trimmable.
|
||||
|
||||
The commits beginning at `3d3c1bb` are the argument for the model and are worth
|
||||
reading before touching what they did — they are the design record, more than
|
||||
|
|
@ -39,9 +41,10 @@ and do not reintroduce the others.
|
|||
| word | means |
|
||||
| --- | --- |
|
||||
| instance | the `:kind`. The general thing, anywhere in a document |
|
||||
| cel | an instance in a lane. One drawing, held for some duration |
|
||||
| clip | an instance in a lane. It can hold one source frame or play a symbol naturally |
|
||||
| cel | specifically a one-frame source held over a clip's duration; the empty-symbol/drawing creation policy |
|
||||
| lane | a group with `:layout :sequence` |
|
||||
| drawing | the content a cel names — an ordinary symbol |
|
||||
| drawing | content authored into a symbol; not a different timeline node type |
|
||||
| placement | ONLY where a node sits: `nest/placement`, and the transform that puts a face on the stage. Never the node itself |
|
||||
|
||||
`occurrence` and `exposure` are not words for a cel. **`exposure` means something
|
||||
|
|
@ -82,19 +85,29 @@ decision, not a cleanup.
|
|||
- **Refuse rather than guess.** Every command returns `{:clip :selection}` or
|
||||
`{:refused why}`, never a half-applied edit. Where the model needs a choice
|
||||
nobody has made, refusing and saying why is the behaviour, not a placeholder.
|
||||
- **A cel is not a row.** Rows, expansion and selection are editor state. The
|
||||
- **A clip is not a row.** Rows, expansion and selection are editor state. The
|
||||
document has never known about rows and must not learn.
|
||||
- **A lane is generic.** Drawing creation, library placement, and adopting an
|
||||
existing root instance all produce the same child instance shape. The only
|
||||
difference is playback policy: a new empty drawing holds source frame zero;
|
||||
a dropped library symbol plays at speed one.
|
||||
- **Placement claims time.** Lanes never store overlaps. A new or extended clip
|
||||
trims, removes, or splits whatever previously owned the claimed interval.
|
||||
Real compositing overlap uses another lane, where ordering remains explicit.
|
||||
|
||||
## Current timeline interaction
|
||||
|
||||
- Creating a symbol inside an aimed drawing lane creates a one-frame cel at the
|
||||
playhead. Drawing a polygon uses the existing cel there or creates the same
|
||||
one-frame cel when the frame is empty.
|
||||
- Dragging a cel body moves it. A linked audio node follows a picture move;
|
||||
- Creating a symbol inside an aimed lane creates a one-frame held clip at the
|
||||
playhead. With no aimed lane it first creates a lane. Drawing a polygon uses
|
||||
the existing clip there or creates the same one-frame clip when the frame is
|
||||
empty.
|
||||
- Dropping any library symbol into a lane creates a natural-duration playing
|
||||
clip. Dropping it on unclaimed timeline or stage space first creates a lane.
|
||||
- Dragging a clip body moves it. A linked audio node follows a picture move;
|
||||
moving or trimming the audio itself remains independent.
|
||||
- Dragging a right edge changes its endpoint. Growth consumes adjacent spans
|
||||
instead of overlapping them. Shift-drag inserts or removes lane time by moving
|
||||
every later cel by the same delta.
|
||||
every later clip by the same delta.
|
||||
- At a shared boundary, the left and right hit zones trim one side. The center
|
||||
is a rolling edit: right-edge resize followed by left-edge resize at one frame.
|
||||
- Split, trim-in, and trim-out are direct buttons and are disabled without an
|
||||
|
|
@ -156,9 +169,10 @@ The implemented correction slice and its remaining UI limits are recorded in
|
|||
|
||||
## Known gaps and traps
|
||||
|
||||
- **Audio lanes do not work.** `symbol/lane-problems` requires `:instance`
|
||||
children, so an audio node in a lane is rejected outright. `lane-model.md`
|
||||
says a lane may hold visual OR audio cels and should reject only a mixture.
|
||||
- **Audio remains outside visual lanes.** `symbol/lane-problems` deliberately
|
||||
requires symbol instances. Audio is still an independent root node that can
|
||||
link to picture; making audio itself lane-based would need an explicit lane
|
||||
capability rather than a mixed child rule.
|
||||
- **`:z` is required on cels and means nothing there.** A lane never has two
|
||||
cels on one frame, so draw order between them cannot matter. `node/problems`
|
||||
requires `:z` on every node uniformly, which is its own kind of simplicity —
|
||||
|
|
@ -172,9 +186,9 @@ The implemented correction slice and its remaining UI limits are recorded in
|
|||
pose selection and plate drawings/tracing, instance-specific picture-rate
|
||||
requests, `pose/put-cut` addressing only `:main`. It predates the lane model
|
||||
and nobody has squared the two.
|
||||
- **The button row in the timeline pane is a test harness, not a design.** It is
|
||||
how the commands were made reachable and provable. `lane-model.md` describes
|
||||
the real cel action strip, the breadcrumb and the location bar; none exist.
|
||||
- **Slip and retime are still absent.** The timeline action strip now applies
|
||||
split/trim uniformly to a selected root or lane clip, but source-time slip and
|
||||
retime still need their own proved semantics before they become controls.
|
||||
- **`shadow-cljs release app` clobbers the dev bundle.** Both builds write
|
||||
`../static/arthur/js`, which Django serves, and the optimized build does not
|
||||
export the `arthur` global — so after a release the browser tests fail with
|
||||
|
|
@ -186,14 +200,14 @@ The implemented correction slice and its remaining UI limits are recorded in
|
|||
|
||||
From `frontend/`:
|
||||
|
||||
npx shadow-cljs compile test && node out/node-tests.js # 437 tests, 5,804 assertions
|
||||
npx shadow-cljs compile test && node out/node-tests.js # 469 tests, 9,592 assertions
|
||||
npx shadow-cljs compile app # the bundle Django serves
|
||||
npx shadow-cljs release app # then `compile app` again — see above
|
||||
|
||||
The browser tests need the Django dev server up (`mise exec -- python manage.py
|
||||
runserver 8778` from the repo root) and a compiled dev bundle:
|
||||
|
||||
node --experimental-websocket test/browser/lane.mjs # the lane/cel flow
|
||||
node --experimental-websocket test/browser/lane.mjs # generic symbol-lane flow
|
||||
CHROME=/usr/bin/chromium node --experimental-websocket test/browser/take.mjs
|
||||
|
||||
`take.mjs` defaults to a macOS Chrome path, hence `CHROME=`. It writes a real
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue