Skip to content
Shapemetry

@huukhanhnguyen/model / commandRegistry/laneEdit

commandRegistry/laneEdit

EDITABLE_LANES

const EDITABLE_LANES: readonly ["materials", "layers", "patterns", "styles", "textStyles", "annotationStyles", "cameras", "animations", "components", "textures", "tables", "views", "sheets"] = LANE_NAMES


applyLaneEdit()

applyLaneEdit(target, call): LaneEditCall | null

Replay one lane edit (undo/redo, and D2's command log). Returns the inverse of the applied edit, or null when the helper was a no-op (blocked remove, no-op move, refused rename).

Parameters

target

ModelScope | Model

call

LaneEditCall

Returns

LaneEditCall | null


laneEntry()

laneEntry(target, lane, key): LaneEntry | undefined

The entry itself, or undefined — the read half every section needs after an insert (laneInsert already returns it) or before an edit.

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

key

string

Returns

LaneEntry | undefined


laneEntryKey()

laneEntryKey(lane, entry): string | undefined

A lane entry's identity value — its key, or the literal its name arg evaluates to. Undefined when the lane keys by name and the name is a real expression (not statically addressable, same limit the validator accepts).

Parameters

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

entry

LaneEntry

Returns

string | undefined


laneInsert()

laneInsert(target, lane, entry, index?): { entry: LaneEntry; inverse: LaneEditCall; key: string; }

Insert an entry, minting/uniquing its identity. index defaults to the end.

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

entry

LaneEntry

index?

number

Returns

{ entry: LaneEntry; inverse: LaneEditCall; key: string; }

entry

entry: LaneEntry

inverse

inverse: LaneEditCall

key

key: string


laneKeys()

laneKeys(target, lane): string[]

Every entry's identity in lane order.

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

Returns

string[]


laneMove()

laneMove(target, lane, key, index): { inverse: LaneEditCall | null; moved: boolean; }

Move an entry to index (lane order is meaningful — CollectionNode.keys() dirties its readers on a reorder).

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

key

string

index

number

Returns

{ inverse: LaneEditCall | null; moved: boolean; }

inverse

inverse: LaneEditCall | null

moved

moved: boolean


laneReferencePaths()

laneReferencePaths(target, lane, name): string[]

Paths of every arg statically referencing name — the delete-block report.

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

name

string

Returns

string[]


laneRemove()

laneRemove(target, lane, key): { inverse: LaneEditCall | null; refs: string[]; removed: boolean; }

Remove an entry — REFUSED (with the referencing paths) while a by-name consumer still points at it, on every lane that has by-name consumers. Unlike the other doors this is TOLERANT of an unknown key (a no-op): the callers are ✕ glyphs holding a captured entry, and a double click must not throw out of a click handler.

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

key

string

Returns

{ inverse: LaneEditCall | null; refs: string[]; removed: boolean; }

inverse

inverse: LaneEditCall | null

refs

refs: string[]

removed

removed: boolean


laneReplace()

laneReplace(target, lane, key, entry): { inverse: LaneEditCall; }

Replace a lane entry WHOLE, keeping its position and its identity.

The door an entry-level diff needs, and the one this module was missing: swap an entry outright rather than reaching inside it for a minimal edit (root CLAUDE.md, "The object is the unit of change").

Remove-then-insert cannot do this and must not be used for it. laneRemove REFUSES to delete an entry another node references by name — a material named "Oak" that some faceMaterial('Oak') still points at stays put — and laneInsert uniques the identity it mints, so the pair would silently leave the old entry in place beside a renamed copy. Editing in place sidesteps both: nothing is ever unreferenced, and the identity never moves.

The incoming entry's identity must match key; a rename is a different operation with different consequences (it rewrites every reference).

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

key

string

entry

LaneEntry

Returns

{ inverse: LaneEditCall; }

inverse

inverse: LaneEditCall


laneSetArg()

laneSetArg(target, lane, key, argKey, input): { changed: boolean; inverse: LaneEditCall; }

Write one arg on the lane ENTRY's own node (materials/layers/styles/ cameras are single nodes; a chain entry's own args, where it has them). input is an expression STRING — never evaluated here; undefined REMOVES the arg (the old clearMaterialArg/clearLayerArg/ clearStyleArg door, and what an inverse restores to when there was no arg).

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

key

string

argKey

string

input

string | undefined

Returns

{ changed: boolean; inverse: LaneEditCall; }

changed

changed: boolean

inverse

inverse: LaneEditCall


laneSetField()

laneSetField(target, lane, key, field, value): { changed: boolean; inverse: LaneEditCall | null; }

Write one header field (key/title/label/enabled/flat). A key rename routes through the lane's rename semantics: a by-name lane rewrites every reference doc-wide, and a renamed view carries settings.activeView with it. Returns changed=false on an empty or colliding key (the caller reverts the field, matching what every section already does).

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

key

string

field

string

value

unknown

Returns

{ changed: boolean; inverse: LaneEditCall | null; }

changed

changed: boolean

inverse

inverse: LaneEditCall | null


laneSetOps()

laneSetOps(target, lane, key, operations, field?): { inverse: LaneEditCall; }

Replace a chain entry's whole body (the views/components/ sheets subtree editors' write-back). field selects which body — default operations; a components[] entry's opening is the other one.

Parameters

target

ModelScope | Model

lane

"patterns" | "animations" | "materials" | "layers" | "styles" | "textStyles" | "annotationStyles" | "cameras" | "views" | "components" | "textures" | "tables" | "sheets"

key

string

operations

(Operation | Group)[]

field?

LaneEditField = "operations"

Returns

{ inverse: LaneEditCall; }

inverse

inverse: LaneEditCall


laneTouch()

laneTouch(target): void

Fire the root collection's "change" — the SAME signal touch(owner) fires in packages/ui, which render sync, the lane nodes' hasChanged pass and the builder's reactive blocks all subscribe to. Owned here so a lane edit can no longer forget it.

Parameters

target

ModelScope | Model

Returns

void


settingsSet()

settingsSet(target, patch, replace?): { inverse: LaneEditCall; }

The one settings door. patch merges; a key set to undefined is removed. replace swaps the whole block (how the inverse restores it).

Parameters

target

ModelScope | Model

patch

ModelSettings | undefined

replace?

boolean = false

Returns

{ inverse: LaneEditCall; }

inverse

inverse: LaneEditCall


clearNodeArg

Re-exports clearNodeArg


nodeArgInput

Re-exports nodeArgInput


setNodeArg

Re-exports setNodeArg

Last updated: 📖 8 min readEdit on GitHub