Canonical taxonomy of PhotoTux commands: scopes, mutation classes, execution classes, undo policies, and representative ID families. This appendix organizes the contracts in 08 — Command System for implementers, reviewers, and extension authors. Normative keywords follow Requirement Keywords.
Commands are the sole semantic mutation spine for document-authoritative commits (08 Accepted v1). Presentations emit actions or QML slots; host adapters resolve to SessionState::invoke; commands produce transactions or typed failures. Widgets MUST NOT mutate the authoritative document graph outside this taxonomy.
Registered in phototux_engine::command_id and exercised via SessionState::invoke:
| Family | IDs |
|---|---|
| History | history.undo, history.redo, history.jump |
| Layer | layer.create, layer.create-fill, layer.set-fill-color, layer.delete, layer.set-active, layer.set-visibility, layer.set-opacity, layer.set-blend, layer.reorder, layer.group, layer.ungroup, layer.set-clip, layer.set-locks, layer.align, layer.set-blend-if |
| View | view.zoom-to, view.zoom-to-fit, view.pan-to, view.pan-by, view.zoom-at, view.set-tool |
| Document | document.new-preset, document.new-size, document.assign-profile, document.convert-profile, document.set-icc, document.set-soft-proof, document.crop, document.rotate-90 |
| Selection | selection.replace, selection.deselect, selection.invert, selection.select-all, selection.modify, selection.color-select, selection.to-mask |
| Mask | mask.create, mask.delete, mask.set-enabled, mask.set-attributes, mask.create-vector, mask.apply, mask.to-selection |
| Text / Shape | text.create, text.set-content, text.bake, shape.create, shape.rasterize, shape.boolean |
| Filter / style | filter.add-adjustment, filter.set-parameters, filter.add-effect, filter.set-gaussian-radius, filter.preview, filter.set-preview-params, filter.commit, filter.cancel-preview, effect.reorder, effect.set-enabled, style.add, style.set-params, style.set-color, style.set-enabled, style.remove |
| Clipboard / path | clipboard.paste-layer, path.stroke-to-layer, path.set-closed, path.move-anchor, path.add-anchor, path.delete-anchor |
| Raster | raster.transform-commit, raster.flip, raster.fill, raster.gradient, raster.paint-stroke |
| App / workspace | app.show-preferences, app.show-filter-gallery, workspace.reset, workspace.toggle-panel, workspace.apply-preset |
See Host-only exemption catalog below. High level: ephemeral previews; paint dab stream until raster.paint-stroke; tool chrome; file I/O adapters; telemetry. Prefs/workspace chrome is now registered as application/workspace commands that return HostFollowUp (host applies UI; document dirty unchanged).
Every registered command descriptor MUST declare all of the following axes:
| Axis | Values | Meaning |
|---|---|---|
| Scope | application, window, workspace, document, view, resource, extension |
Authority boundary and scheduler key |
| Mutation class | none, ephemeral, document, history-meta, workspace, preference, resource, destructive |
What truth changes |
| Execution class | sync, async-prepare, async-job, streaming |
Scheduling and progress |
| Undo policy | none, transaction, groupable, mergeable, checkpoint-assist |
History behavior |
| Conflict policy | exact-version, rebase-safe, latest-wins-view, exclusive-op |
Version vector handling |
| Cancellation | immediate, cooperative-phase, after-commit-via-undo |
Cancel semantics |
| Capability class | built-in core, host file, clipboard, extension-* | Authority required |
Affects process-wide registries that are not a single document.
Representative IDs:
app.quitapp.open-filesapp.show-preferencesapp.toggle-fullscreen(window/host mediated)resource.reload-catalogextension.enableextension.disable
Rules:
- MUST NOT write document pixels or layer graphs directly.
- Document opens create documents through lifecycle + import commands, then switch focus.
- Preference mutations use preference schema versions, not document history.
Affect layout, panels, docking, tools presentation, and view arrangement.
Representative IDs:
workspace.apply-presetworkspace.reset-layoutworkspace.save-presetpanel.showpanel.hidepanel.pin-targetdock.splitdock.mergewindow.newwindow.close
Rules:
- Workspace changes MUST NOT mark document modified unless an explicitly persisted document property changes.
- Missing panels/extensions degrade with tombstones or unavailable placeholders per 05 and 23.
- Layout restore is reconciliation, not widget replay (02, 03).
Mutates authoritative document state. This is the primary editing surface.
Sub-families:
| Sub-family | Examples | Notes |
|---|---|---|
| Document properties | document.set-size, document.assign-profile, document.convert-profile, document.set-icc |
Assign ≠ convert; optional ICC bytes (16) |
| Persistence | document.save, document.save-as, document.revert |
Staged writes (27) |
| Layer structure | layer.create, layer.delete, layer.reorder, layer.group, layer.ungroup |
Stable object IDs |
| Layer placement | layer.align |
One entry per gesture; a group moves as one object |
| Layer blend ranges | layer.set-blend-if |
Mergeable: a handle drag is one entry |
| Layer attributes | layer.set-opacity, layer.set-blend, layer.set-visibility, layer.set-lock |
Compositing inputs |
| Raster edit | raster.paint-stroke, raster.fill, raster.clear, raster.transform |
Tile-aware transactions |
| Selection | selection.replace, selection.union, selection.invert, selection.deselect |
Object vs pixel selection distinct |
| Mask | mask.create, mask.paint, mask.disable, mask.apply |
Active edit target switches |
| Filter | filter.apply-destructive, filter.add-adjustment, filter.set-parameters |
Preview then commit |
| Text/shape | text.create, text.set-content, shape.create, shape.set-path |
Rasterize is explicit |
| History | history.undo, history.redo, history.clear (destructive policy) |
New monotonic versions |
| Clipboard | clipboard.copy, clipboard.cut, clipboard.paste |
Validated payloads |
| Import into doc | document.import-layers, document.place-file |
Untrusted input limits |
Affects canvas view state for a document projection.
Representative IDs:
view.zoom-inview.zoom-toview.pan-byview.rotate-toview.toggle-proofview.toggle-guidesview.set-overlay
Rules:
- View-only commands SHOULD use undo policy
noneunless the product explicitly records view bookmarks as user data. - Multiple views MAY reference one document; view commands target a
ViewId.
Mutates application or document-embedded resources (brushes, gradients, profiles references, presets).
Representative IDs:
brush.create-presetbrush.update-presetpalette.importprofile.install-local
Rules:
- Embedding into a document is a document-scoped command.
- Missing resources produce typed unavailable states, not silent substitution.
Commands contributed by plugins through 23 — Plugin SDK.
Rules:
- IDs MUST be namespaced:
ext.<publisher>.<name>. - Schemas are bounded and versioned.
- Execution occurs under capability grants and budgets.
- Removal MUST NOT corrupt core history; opaque records or checkpoints apply.
Read-only or navigational. Examples: command search preview queries, enablement probes. Prefer non-command queries when no shared semantic state changes.
Transient tool preview, hover sample, rubber-band rectangle. MAY update presentation caches. MUST NOT publish document versions. Commit converts ephemeral state into a document command.
Ordinary editable mutation producing a history transaction (unless NoChange).
Undo/redo/clear and coalescing control. Undo/redo still publish new document versions.
Non-document persistence domains with separate schemas and migration (24, 03).
Discards editability or exceeds ordinary undo guarantees (flatten, rasterize with source drop, clear history, overwrite unique file without backup policy). Destructive commands MUST:
- use precise naming;
- disclose consequences in UI and accessibility descriptions;
- default to non-destructive alternatives when available;
- never be the initial default focus in dialogs (26, 29).
flowchart TB
Submit[Command submit] --> Validate[Validation pipeline]
Validate -->|Reject| Rejected[Rejected typed error]
Validate -->|Sync path| Build[Build transaction]
Validate -->|Async prepare| Prepare[Worker prepare]
Prepare --> Applicability[Applicability check]
Applicability --> Build
Build --> Commit[Atomic commit and history]
Commit --> Publish[Publish snapshot delta]
Validate -->|Long job| Job[Async job manager]
Job --> Progress[Progress and cancel]
Progress --> Prepare
| Class | Use when | Progress | Cancel |
|---|---|---|---|
sync |
Validation + commit fit interactive budget | Optional busy | Before commit |
async-prepare |
Heavy preparation, short commit | Preview/progress | Cooperative; stale results discarded |
async-job |
Import, export, full-document filter | Required after 250 ms | Phase-bounded |
streaming |
Encode/decode chunk streams | Chunk/tile | Boundary cancel |
Brush strokes typically use mergeable sync or short prepare batches. Full-document filters and exports MUST be jobs.
| Policy | Behavior | Typical commands |
|---|---|---|
none |
No history entry | View zoom, panel show |
transaction |
One undo step | Set opacity, create layer |
groupable |
Explicit begin/end group | Multi-layer reorder dialog |
mergeable |
Coalesce continuous gesture | Paint stroke segments |
checkpoint-assist |
May request checkpoint for traversal | Huge filter, structural replace |
Merge MUST preserve deterministic undo boundaries. Merge changes history presentation, not semantic ordering (20).
Commands fail by stage; error categories map to Error Taxonomy:
- Registry / schema →
malformed/unsupported - Authority →
permission - Lifecycle →
lifecycle - Target resolution →
unavailable-target - Version →
version-conflict - Semantic invariants →
semantic/invariant - Resources →
resource-pressure - Commit race →
version-conflictor abandon
Enablement is advisory. Execution ALWAYS revalidates.
The following catalog is normative for naming patterns and taxonomy placement. Exact parameter schemas live with subsystem descriptors; this list is the handbook index.
| ID | Scope | Mutation | Exec | Undo |
|---|---|---|---|---|
document.new |
application/document | document | sync | none (creation) |
document.open |
application | document | async-job | none |
document.close |
document | history-meta/lifecycle | sync | none |
document.save |
document | document meta | async-job | none |
document.save-as |
document | document meta | async-job | none |
document.export |
document | none (delivery) | async-job/streaming | none |
document.revert |
document | document | async-job | transaction or replace |
| ID | Notes |
|---|---|
layer.create-raster |
Inserts with stable ID |
layer.create-adjustment |
Nondestructive node |
layer.create-fill |
|
layer.create-text |
Text engine |
layer.create-shape |
Shape engine |
layer.duplicate |
New IDs, copy resources by policy |
layer.delete |
Inverse restores graph |
layer.reorder |
Atomic multi-target |
layer.set-name |
Accessibility name source |
layer.set-opacity |
|
layer.set-blend-mode |
Color/precision aware |
layer.set-visibility |
|
layer.set-lock-flags |
|
layer.rasterize |
Destructive disclosure |
layer.merge-down |
Destructive disclosure |
layer.merge-group |
Destructive disclosure |
layer.flatten-visible |
Destructive disclosure |
| ID | Notes |
|---|---|
selection.set-pixel |
Replace coverage |
selection.modify |
Expand/contract/feather |
selection.select-object |
Object selection distinct |
selection.clear |
|
edit-target.set |
Layer pixels vs mask |
mask.create |
Attached coverage |
mask.from-selection |
|
mask.disable |
|
mask.apply-to-layer |
Often destructive to mask editability |
| ID | Notes |
|---|---|
brush.begin-stroke |
Optional explicit group |
brush.append-stroke |
Mergeable |
brush.end-stroke |
Finalize merge |
raster.fill |
Selection-aware |
raster.erase |
|
transform.preview |
Ephemeral |
transform.commit |
Transaction |
transform.nudge |
Keyboard alternative |
| ID | Notes |
|---|---|
filter.preview |
Ephemeral / job prepare |
filter.commit |
Destructive or adjustment |
adjustment.create |
Nondestructive |
adjustment.set-parameters |
Live preview + commit |
color.assign-profile |
Interpretation only |
color.convert-profile |
Pixel mutation |
color.set-proofing |
View or document policy per descriptor |
document.assign-profile |
Tag-only; shipped |
document.convert-profile |
Approx pixel path for named tags |
document.set-icc |
Embed/clear validated ICC bytes on DocumentColorState; history label; .ptx via graph JSON |
document.set-soft-proof |
View-like; no dirty / no generation bump |
| ID | Notes |
|---|---|
history.undo |
New version |
history.redo |
New version |
history.jump |
If exposed; budget-checked |
history.clear |
Destructive |
| ID | Notes |
|---|---|
clipboard.copy |
Internal schema + host MIME |
clipboard.cut |
Copy + delete transaction group |
clipboard.paste |
Validate like import |
clipboard.paste-special |
Explicit conversion choices |
From 01 — Information Architecture and 08:
- One action normally maps to one command.
- Many presentations MAY share one command ID with different parameter presets.
- Display labels localize; command IDs do not.
- Context menus, toolbars, shortcuts, and accessibility actions MUST resolve to the same IDs for the same semantics.
- View-only actions that change only ephemeral UI MAY omit commands if no cross-view shared state persists; if state persists in workspace/preferences, a command is REQUIRED.
Extension commands MUST declare:
- publisher namespace and semantic ID;
- schema and behavior versions;
- required capabilities;
- memory/time/queue budgets;
- undo policy compatible with opaque history rules;
- thread class (never UI thread for untrusted work);
- determinism claims if replay is offered.
Host MUST reject descriptors that:
- duplicate built-in IDs;
- request ambient authority;
- omit bounds on parameters;
- claim destructive effects without disclosure metadata;
- require network, accounts, or generative services.
Command IDs MUST:
- use
domain.verb-objectkebab-case segments; - avoid toolkit, menu path, or vendor product terms;
- remain stable across UI rearrangements;
- change ID or schema version when semantic outcome changes.
Good: layer.set-opacity, mask.paint, document.convert-profile.
Bad: LayersPanel/OpacitySliderChanged, photoshopFlatten, aiSelectSubject.
Every invocation SHOULD correlate:
action_id(optional)command_idinvocation_idoperation_id(async)transaction_id(if committed)document_id/view_id/workspace_idcorrelation_id
Diagnostics MUST exclude pixel payloads and private paths by default (08).
- Every user-visible mutation has a command ID in this taxonomy’s families.
- Descriptors declare all axes.
- Destructive commands disclose and are test-covered.
- Mergeable stroke commands produce one undo step per gesture policy.
- Export does not clear document modified state.
- Extension commands are capability-scoped and budgeted.
- Headless tests invoke commands without UI toolkits (engine
commandsmodule tests).
Source of truth: CommandMeta / COMMAND_META_ALL in engine (must cover every command_id::ALL). Axes: scope · mutation · undo · conflict.
| Family | Scope | Mutation | Undo | Conflict |
|---|---|---|---|---|
history.undo / history.redo |
document | history-meta | none | exclusive-op |
layer.* (except set-active) |
document | document | transaction / mergeable (opacity) | exact-version |
layer.set-active |
document | ephemeral | none | latest-wins-view |
view.* |
view | ephemeral | none | latest-wins-view |
document.new-* |
document | document | none | exclusive-op |
document.assign/convert/crop/rotate |
document | document | transaction | exact-version |
selection.* |
selection | document | transaction | exact-version |
mask.* / text.* / shape.* / filter.* / style.* |
document | document | transaction / mergeable (params) | exact-version |
clipboard.paste-layer / path.stroke-to-layer / raster.* |
document | document | transaction / groupable (paint-stroke) | exact-version |
app.show-preferences |
application | preference | none | latest-wins-view |
workspace.reset / workspace.toggle-panel |
workspace | workspace | none | latest-wins-view |
Paint dabs remain EngineCommand until stroke-end raster.paint-stroke.
Operations that MUST NOT go through document-authoritative SessionState::invoke as graph mutations. Chrome that is registered above still uses HostFollowUp for the actual UI side effect.
| Class | Owner | Examples | Why exempt / host |
|---|---|---|---|
| Paint stream | phototux_canvas / paint worker |
Dab EngineCommand traffic |
Streaming; commits via raster.paint-stroke |
| Ephemeral previews | UI + GPU | Selection rubber-band, crop/transform drafts | Not committed until replace/commit command |
| Tool chrome | AppSession |
Brush size/hardness, FG/BG, eyedropper sample | Presentation state; not document graph |
| File I/O adapters | phototux_io + file worker |
Open/save/export/PSD/.ptx |
Host I/O; dirty/receipt after success |
| Destructive file chrome | QML + host_op |
document.new/open/close, app.quit |
Dialog + unsaved gate before session replace |
| Dialog chrome | QML | About, save-as, export, command palette open | Presentation; palette invokes actions by id |
| Telemetry | UI | FPS, status text, startup ms | Non-authoritative |
HostFollowUp::ConvertPixels |
UI GPU path | After document.convert-profile |
Pixel rewrite after command commits metadata |
| GPU recover chrome | action.app.recover-gpu → app.recover_gpu |
Rebuild GPU resources from engine graph; does not mutate document generation | Device/surface loss UX (P6) |
| Clipboard mask/selection | clipboard.copy_selection_mask, clipboard.copy_layer_mask, clipboard.paste_selection, clipboard.paste_mask |
App-local R8 payloads (+ OS grayscale preview); paste restores selection or layer mask | Handbook §21 Met spine |
Remaining host_op |
actions.rs → dispatch_host_op |
Selection modify GPU path, shape create wrappers, guides toggles, clipboard copy, mask paint helpers | Bridge until fully routed; still must not bypass history for document pixels without a command |