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.