Non-destructive editing
First enclose a range with Select, then apply it via the ✎ Edit tool rail.
| Operation | What it does | Shortcut |
|---|---|---|
| Hide | Removes selected points from view. Still included in exports and tallies | ⌘⇧H / Ctrl+⇧H |
| Delete | Removes selected points from deliverables. Excluded from exports and tallies | Del / ⌫ |
| Reclassify | Changes classification (0–255) | — |
| Color | Overrides color | — |
The difference between hide and delete is the key point. Hide means "I don't want to see this right now"; delete means "don't include this in the deliverable". Neither ever changes the .copc.laz, and both can be undone with ⌘Z.
How the source stays unchanged
The "truth" of an edit is neither the source nor the GPU attributes — it's the Command log. hide / delete / reclassify / colorOverride are appended one after another, each paired with the selection's predicate. The attributes that reach the GPU are rebuilt from this log every time.
This works because of LOD. As the camera pulls away, points are dropped; move closer and they're reloaded. If edits were written straight into the GPU attributes, an edit would vanish from a point that got dropped and came back. Rebuilding from the log means it comes back too ("rebuild after eviction is identical" is pinned by a regression test).
Because it's a log, a later edit wins. hide and delete have a priority order — if both apply to the same point, delete wins.
Undo / Redo
⌘Z undoes, ⌘⇧Z (also Ctrl+Y on Windows) redoes.
Undo is per user. Even while collaborating, ⌘Z only undoes edits you made yourself — other people's edits stay. Only your one entry, wherever it sits in the log, is removed, and Redo restores it to its original position, so later-wins relationships aren't disturbed either. The same behavior as Google Docs or Figma. ⌘Z and the button are disabled when you have no edits of your own.
"Clear edits" is the one exception — it clears everyone's. (If someone else could ⌘Z away another person's clear, the state would become unreadable, so it's outside Undo.) While collaborating, a confirmation dialog with the count shows up.
Reverting a whole batch
⌘Z undoes one operation at a time, but the edits from one AI turn are grouped into a single batch, revertible as a unit too. If the same instruction spans multiple turns, they're grouped further under a runId (human operations aren't batched for now — they stay one-at-a-time ⌘Z).
await oniyanma.execute('listBatches')
// → [{ id, kind: 'aiTurn', status: 'applied', counts: { ops, entries }, … }, …]
await oniyanma.execute('revertBatch', { id })
// a confirmation gate applies when a machine calls this (returns whether the batch is already
// applied, and the count that would revert, first)Only the edit log (ops) reverts, in general. If findings were filed or updated within that batch, those don't revert (no intermediate version of them is stored). The count that wasn't reverted comes back in the response, so you can confirm whether everything actually reverted. → Confirmation gate (dry run)
The exception is a detection import batch (kind:'import'). Since that kind of batch only ever creates, revertBatch can safely withdraw every imported finding along with it (batches that update existing findings — human / ai / macro — remain excluded, since the pre-update values aren't stored).
A batch someone else made can't be reverted. If the batch's provenance (the client that issued it) doesn't match the client currently operating, it's not-owner — the same discipline as per-user undo.
Actor (provenance)
Every edit records who issued it.
| Actor | Where from |
|---|---|
human | UI buttons, shortcuts, the ⌘K palette |
ai | The in-app AI console (the model name and the start of the instruction are also recorded) |
api | Via window.oniyanma.execute / MCP |
The edit panel shows something like "Edits: 12 (3 by AI/API)". Also available from getState().editsByActor.
This is what lets you later separate "what I left to the AI" from "what a human judged".
The confirmation gate when called from a machine
hideSelection / deleteSelection / reclassifySelection / colorSelection / clearEdits: when a machine (ai / api) calls one without confirm, it doesn't execute — only the affected count comes back.
await oniyanma.execute('deleteSelection')
// → { preview: true, command: 'deleteSelection',
// wouldAffect: { points: 128340, note: 'resident (currently on-screen LOD sample) basis' },
// note: 'Not executed. …call again with confirm:true' }
await oniyanma.execute('deleteSelection', { confirm: true }) // this executesA human's UI action sees its own result, so it doesn't pass through this gate. → Confirmation gate (dry run)
Saving
Edits autosave to localStorage per dataset and survive a reload. To take them to a different device, or hand them over as a deliverable, export a sidecar (<dataset>.oniyanma.json). → Saving and exporting
Commands
hideSelection · deleteSelection · reclassifySelection · colorSelection · undo · redo · clearEdits · listBatches · getBatch · revertBatch