perfect target area thing

This commit is contained in:
Your Name 2026-10-03 00:39:07 -04:00
parent 664252e0fc
commit 5bcf22e458
23 changed files with 668 additions and 432 deletions

View file

@ -30,10 +30,11 @@ stage and timeline. A stage marquee replaces, or with Shift adds to, the same
set. Timeline rows, bars, and cel blocks render membership from that same set;
the primary member gets the inspector/focus treatment.
Targeting remains separate. Clicking a timeline label both selects it and aims
it. Shift-click changes the selection set and makes the clicked row the one
target. Clicking a bar/cel or the stage changes selection without silently
changing the target. A target is singular even when selection is plural.
Creation targeting is derived rather than stored. The primary (last-selected)
occurrence is the preferred row; the playhead validates it and, when necessary,
walks outward to the nearest valid containing occurrence. A target is singular
even when selection is plural. See `docs/creating-in.md` for the resolver
contract.
## Clipboard value
@ -50,19 +51,13 @@ cross-project identity collision policy hidden inside it.
## Paste
Paste always uses the explicit insertion target and the playhead:
- no target: paste at the top of the open symbol;
- aimed ordinary node: paste beside it in the symbol that owns it;
- aimed instance, including an instance whose source is drawn as a lane: paste
inside the symbol it places;
- the open lane's own row: paste directly into that lane.
The destination is resolved structurally from the target. It does not require
the target instance to be visible at the playhead. Being outside the target
symbol's authored window is legal: content can exist there and be cropped by
the window. Lane paste may grow the lane/window because a sequence cannot store
an overlapping or unreachable tail and pretend the edit succeeded.
Paste resolves one destination from the primary selection and playhead. An
ordinary occurrence is usable only while the playhead maps through every
enclosing occurrence and lies within its extent. Otherwise resolution walks
outward, with the open symbol as the total fallback. A selected lane is an
insertion surface only while all occurrences enclosing its parent symbol are
valid. Multi-selection supplies one ordered payload, not several destinations;
only its primary member anchors this resolution.
The earliest finite start among the copied roots is aligned with the playhead
in the destination. All other root starts retain their offset from it. Roots
@ -118,13 +113,14 @@ After either duplicate command, the new roots replace the selection.
Cut, paste, duplicate, and duplicate unique each call one domain command and
commit through one `edit/transaction`; each is exactly one undo/redo step no
matter how many nodes or symbols it touches. Copy, aiming, and selection do not
matter how many nodes or symbols it touches. Copy and selection do not
touch history. A refusal changes no document leaves and creates no history step.
Undo/redo filters the complete selection set against the restored document and
repairs the primary selection. Clipboard payloads remain snapshots. Paste
validates the destination and all remapped references at commit time, so a stale
target or a newly impossible symbol cycle refuses rather than partially editing.
validates the resolved destination and all remapped references at commit time,
so a stale selection or a newly impossible symbol cycle refuses rather than
partially editing.
## Required tests
@ -133,7 +129,7 @@ normal shared references, deep unique graph remaps, multi-root relative timing,
composition overlap, lane overwrite, lane forward duplication and ripple,
mixed-owner duplication, cycle refusal, stale targets, and all-or-nothing
failure. Event tests cover copy without history, atomic cut/paste/duplicates,
resulting multi-selection, target resolution, pasting beyond a symbol window,
resulting multi-selection, target fallback at the playhead,
and one undo plus redo of each mutation. Browser tests cover mirrored stage and
timeline selection, Shift-toggle on labels/bars/cels, singular aiming, and the
keyboard commands.
timeline selection, Shift-toggle on labels/bars/cels, singular derived creation
targeting, and the keyboard commands.

149
docs/creating-in.md Normal file
View file

@ -0,0 +1,149 @@
# Creating in
Revised 2026-10-02.
Arthur's creation model is a layer list plus a playhead. The primary active row
is the preferred place for a new thing. The playhead decides whether that row is
present in the current occurrence and supplies the frame at which the thing is
created.
This is editor behavior, not document structure. A saved clip has symbols,
instances and spans; it does not have timeline rows or a creation target.
## Selection and the active row
The selection set names the objects affected by copy, delete and transform. Its
primary member is also the active row, analogous to the active layer in a paint
program. There is no second targeting gesture for a user to maintain.
- Clicking a row label, its timeline body, or the same occurrence on the stage
makes that occurrence primary.
- Shift/marquee selection may retain several objects, but only the primary row
anchors creation.
- Clicking blank stage or timeline space returns the active row to the symbol in
the current tab.
- Row disclosure is independent. Selecting or creating never implicitly expands
a row.
The selection does not change merely because the playhead moves. An off-frame
object remains available for copying, deletion and inspection.
## Resolving the active row at the playhead
Resolution starts with the structural destination implied by the primary row:
- A lane row means the lane symbol itself.
- An ordinary symbol-instance row means the symbol placed by that occurrence.
- A non-instance row means the symbol containing that node.
- No row means the symbol in the current tab.
For an ordinary symbol occurrence, the playhead must be within that occurrence's
extent in the current nested context. Extents are tested after walking all parent
time maps and use the half-open interval `[in, out)`. If the preferred occurrence
is not present, resolution walks outward to the nearest parent whose occurrence
is present. A containing lane is therefore the natural fallback from an inactive
cel. If no nested occurrence is present, the current tab is the destination.
The preferred row is retained during fallback. Scrubbing back into its extent
makes it the effective destination again.
A lane differs only in what its row means. It is an insertion surface across its
containing timeline and does not require an existing cel under the playhead. A
cel within the lane is still an ordinary symbol occurrence with an extent.
For `A -> B -> lane C -> cel D`:
- over D, with D primary, creation happens inside D;
- past D but while C is available, creation inserts a new cel in C;
- outside C's containing occurrence but inside B, creation happens inside B;
- outside every nested occurrence, creation happens in A, the current tab.
## What creation does
Once resolved, every creation command follows the destination kind.
### Lane destination
- A new or dropped symbol is instantiated directly in the lane at the playhead.
- Its interval claims that time. Existing cels under the interval are removed or
trimmed by the lane's ordinary claim-time command.
- Beginning a drawing creates a new one-frame drawing symbol in the lane and the
finished shape is a child of that drawing.
- Selecting an existing cel changes the destination from the lane to the symbol
placed by that cel; subsequent symbols and shapes become children there.
Thus no separate "new cel" versus "add inside" mode is needed. Selecting the
lane header says new cel; selecting a cel says add inside.
### Ordinary symbol destination
- A new symbol is instantiated as a child at the mapped playhead frame.
- A new shape is authored directly in the symbol at that frame.
- Stage coordinates are transformed through the occurrence into the destination
symbol's local coordinates.
### Explicit timeline drop
A timeline drop uses the row and frame under the pointer, not the stored active
row and playhead. Dropping onto a lane therefore always instantiates in that lane
and claims the pointer's interval. A stage drop uses the resolved active row and
the playhead.
Paste, imported symbols and converted footage obey the same resolver as direct
creation. They must not each reconstruct nesting or extent fallback separately.
## Multi-selection and paste
Multi-selection does not create multiple insertion targets. Copy and cut take
the canonical forest of selected roots as one ordered payload; the primary
(last-selected) occurrence alone supplies the preferred row for a later paste.
At paste time that row and the current playhead resolve one effective
destination, and every root in the payload is inserted there in one transaction.
The earliest finite root start is aligned to the destination frame. Other roots
keep their timing offsets, hierarchy, and clipboard order. In an ordinary symbol
the roots may overlap. In a lane every root must have a finite span and the
payload's root spans must not overlap one another; valid spans claim their times
and trim or remove existing cels as a batch. An invalid member refuses the whole
paste rather than inserting a partial payload.
After paste, all new roots form the selection and the final root is primary, so
it becomes the preferred row for the next creation. Pasting one payload into
several selected destinations is intentionally not implicit: that would be a
separate distribute command. Duplicate is also distinct from paste—it stays
beside each source in its original owner and does not consult the playhead or
creation target.
## Palette lanes
Palette lanes use the same row-and-playhead resolution. Their content filter and
transition command remain palette-specific: only palettes and palette
transitions can be inserted there. This is a type restriction, not a second
targeting model.
## Implementation boundary
One pure resolver returns the effective destination:
```clojure
{:kind :lane | :symbol
:sid destination-symbol
:path effective-occurrence-path
:frame destination-local-frame
:matrix destination-to-current-tab-transform}
```
Callers may add the unchanged document as `:clip` or rename `:frame` to `:at`,
but they must not reinterpret the active row. Polygon creation, symbol creation,
stage drops, paste, import and footage conversion all consume this answer.
Explicit timeline drops use the same structural row rule with the row and frame
under the pointer; unlike playhead resolution, an invalid pointer destination is
refused rather than allowed to fall outward.
The invariants are:
1. The primary row is the preferred structural destination.
2. The playhead validates occurrences and supplies creation time.
3. Inactive targets fall outward; selection does not follow them.
4. A lane row inserts a cel, while a cel row enters its symbol.
5. Stage creation uses the playhead; timeline drops use pointer time.

View file

@ -94,12 +94,9 @@ decision, not a cleanup.
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.
- **Every symbol is born with a lane,** `clip/lane-node`, id `:lane`. A symbol
with none had nowhere to drop a thing, which made the first drop into any
symbol a special case. An unaimed drop fills an EMPTY lane that is already
there and otherwise makes a new one; it never takes an occupied lane nobody
pointed at, because placement claims time and would trim or delete what was
in it.
- **Lanes are explicit.** A symbol may contain ordinary overlapping children
without a lane. Selecting a lane row opts creation into its claim-time
behavior; selecting a cel enters that cel's source symbol instead.
- **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.
@ -122,12 +119,12 @@ that is mostly refused today.
## Current timeline interaction
- 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.
- Creation follows the primary active row and the playhead; the complete rule is
in `docs/creating-in.md`. A lane row creates a new cel and claims its interval,
while selecting a cel creates inside the symbol that cel places.
- Drawing with a lane row active creates a new one-frame drawing cel. Dropping a
library symbol there creates a natural-duration playing cel. Explicit timeline
drops use the row and frame under the pointer.
- 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

View file

@ -65,8 +65,7 @@ code. `npm test` is green at each step either way.
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`, aims it for immediate drawing or dropping, and
always adds it at the top of the open symbol rather than nesting it in the
previously aimed lane.
symbol with `:display :lane` in the effective creation target derived from
selection and playhead; the new lane then becomes the primary selection.
## Notes

View file

@ -384,9 +384,10 @@ content identity, and cel context when applicable. Show local time and
its project context where a useful mapping exists. Holds and loops need an honest
description instead of a fictitious unique global frame.
Creation controls next to the breadcrumb act in that explicit location. Selection
does not secretly change where a new symbol goes. A shared drawing indicates its
reuse and offers Make this cel unique. Names help identify content;
Creation follows the primary active row, resolved at the playhead as specified in
[`creating-in.md`](creating-in.md). The wider selection set still names what copy,
delete and transform affect; it is not a second list of creation destinations. A
shared drawing indicates its reuse and offers Make this cel unique. Names help identify content;
linked-use indicators must rely on IDs, because different drawings can share names.
| Surface | Primary scope and controls |