KJDRAW / COMMANDS AND TRANSACTIONS
Commands and transactions
Make every UI, automation and Agent edit observable through the same revisioned transactional boundary.
Execute a command#
const line = await sdk.executeCommand('CREATE', {
type: 'LINE',
payload: { start: [0, 0, 0], end: [100, 0, 0] },
}, { document: drawing })
await sdk.executeCommand(
'MOVE',
{ id: line.id, dx: 10, dy: 5 },
{ document: drawing, expectedRevision: drawing.revision },
)
expectedRevision closes stale-write races. A mismatch fails before mutation. Successful commits advance the revision and emit typed events; undo/redo enters the same history.
When using a mounted editor, prefer editor.execute(command, args): it always targets that editor's drawing. With a shared SDK, pass { document: drawing } explicitly for background tasks. A bare sdk.executeCommand() uses the SDK's single active drawing, which follows editor focus and can change when another view becomes active.
Atomicity and rollback#
A command runs against a transaction draft. Validation and any configured authority backend complete before publication. If execution or validation fails, the live document does not expose the partial draft.
Built-in editing includes declared combinations for creation, properties, move/copy/rotate/scale/mirror, arrays, offset, break/explode, trim/extend, chamfer/fillet and grips. Consult Capabilities for exact boundaries rather than inferring support from a command name.
Select geometry#
The packaged workbench includes click selection, directional box selection, an open fence and editable grips. For your own toolbar or automation, query the drawing in model coordinates:
import { selectEntitiesInBox, selectEntitiesByFence } from '@kanjieteam/kjdraw/selection'
const ids = selectEntitiesInBox(drawing, [0, 0], [100, 80], 'window')
await sdk.executeCommand('SELECT', { ids: [...ids], operation: 'replace' }, { document: drawing })
// Find objects touched by an open line across the drawing.
const crossed = selectEntitiesByFence(drawing, [[0, 20], [100, 20]])
| API / option | Purpose |
|---|---|
selectEntitiesInBox(drawing, first, second, mode, options?) | window requires the whole object inside; crossing also accepts geometry touching the frame |
selectEntitiesByFence(drawing, points, options?) | At least two points; the last point is not automatically joined to the first |
spaceId | Query a particular drawing space; defaults to model space |
includeLocked: true | Include visible locked-layer entities for inspection; does not make them editable |
tolerance | Non-negative distance in model units; defaults to 1e-8 |
Both functions return object IDs without changing selection or undo history. Hidden and frozen layers are excluded. Circles, arcs, ellipses and bulged polyline segments use curve intersections; splines follow the displayed curve and text uses approximate label extents.
For custom Canvas UI, editor.workbench.renderer.selectBox(first, second) and selectFence(points) accept canvas-relative CSS pixels, not model coordinates. selectBox automatically chooses window for left-to-right dragging and crossing for right-to-left. Apply the returned IDs with editor.setSelection(ids).
Protected layers#
Editing commands reject writes to locked, frozen or hidden layers, including moving an object into a protected layer. A multi-object command fails as a whole; it does not move the writable objects and leave the others behind. Read-only measurements and queries still work. Use the Layers panel to unlock, thaw or show a layer, or update it explicitly:
await editor.execute('LAYERUPDATE', {
id: layerId,
patch: { locked: false, frozen: false, visible: true },
})
This editing policy applies to transactional commands, including registered extensions. Direct drawing.transact(...) remains available for importers and data migrations that reconstruct protected drawings; it is a low-level data API, not a user permission system.
Use command envelopes#
const envelope = sdk.createCommandEnvelope('MOVE', {
ids: selectedIds,
dx: 3,
dy: 0,
}, {
document: drawing,
origin: 'automation',
expectedRevision: drawing.revision,
})
const receipt = await sdk.executeCommandEnvelope(envelope)
console.log(receipt.command, receipt.afterRevision)
Envelopes make command identity, arguments, origin, target document and expected revision explicit. AI-origin envelopes add the Agent plan review protocol described in Agent workflows.
Observe changes#
Subscribe through sdk.events.on('command:committed', listener) and dispose the returned function when its owning UI scope unmounts. Renderers should derive their display state from the committed document rather than mutate canonical geometry directly.