"The button's job ends the instant it describes what you want. It never gets to do it."
A Hard Architectural Wall
In most apps, a button handler that moves a file just... moves the file, right there in the callback. Waygate forbids it. Views, menu items, keyboard handlers, and drag/drop handlers may do exactly one thing when a mutation is requested: snapshot the intent into an immutable OperationPlan and submit it to the engine. A UI callback that calls a mutating FileManager method is not a bug to fix later — it is an architecture violation, the kind a code review rejects on sight.
Why the Wall Exists
Not because the UI is untrusted, but because the UI is the wrong place. The engine is where journaling, apply-time revalidation, off-main-thread execution, and crash recovery live. A mutation started from a view callback runs on the main thread, skips the journal, and has no recovery story. The wall guarantees that every change — no matter how it was triggered — inherits all of those protections, because there is only one place a change can actually happen.
Snapshot, Then You Can Let Go
There's a second reason, subtle but vital. A command resolves against an immutable snapshot of the active pane, tab, selection, and destination taken at submission time. So if focus shifts a heartbeat after you press the key — you click into the other pane, a tab switches — the operation still targets exactly what you meant. The plan froze the intent; later focus changes cannot retarget it onto the wrong folder.
UI submits plans; only the engine mutates. Every view, menu, shortcut, and drag/drop handler snapshots intent into an immutable plan and hands it off. Direct filesystem mutation from UI code is an architecture violation — because the engine is the single place that journals, revalidates, runs off-main, and recovers.
Focus is not a safe target. If a 'Move to Other Pane' command read the active pane at EXECUTION time instead of submission time, a focus change in the gap could send your files to the wrong destination. Waygate resolves the target against a snapshot taken when you invoked the command — so what you saw when you pressed the key is what gets acted on.
Code
Wrong vs right: the view's only move·swift
// WRONG -- a mutation from a UI callback. No journal, main-thread, no recovery.
@objc func moveButtonClicked() {
try? FileManager.default.moveItem(at: selectedURL, to: otherPaneURL) // VIOLATION
}
// RIGHT -- snapshot intent, submit, let go.
@objc func moveButtonClicked() {
let plan = OperationPlan(
kind: .move,
sources: activePane.selectionSnapshot(), // frozen NOW
destination: otherPane.locationSnapshot(), // frozen NOW
collision: .ask,
expected: activePane.identitySnapshot()
)
Task { await engine.submit(plan) } // the engine does the rest
}
The command resolves against a snapshot, not live focus·swift
struct CommandContext { // captured at INVOCATION, immutable thereafter
let activePaneID: PaneID
let activeTabID: TabID
let selection: [LocationReference]
let destination: LocationReference?
}
// If the user clicks into the other pane a moment later, `ctx` is unchanged,
// so the operation still targets what they saw when they invoked it.
func perform(_ command: Command, _ ctx: CommandContext) async {
let plan = command.plan(from: ctx)
await engine.submit(plan)
}
In an app you've built, find a UI callback that directly performs a risky side effect (writes a file, hits an API, deletes a record). Rewrite it, on paper, into two parts: a snapshot of intent, and a submission to a single actor that owns the side effect. What did you have to capture at snapshot time so that a later state change couldn't corrupt the action?
Hint
You need to freeze everything the action depends on — the target, the selection, the destination — at the moment of intent, because any of them can change before the action runs. Once the intent is a frozen value handed to one actor, the callback can return immediately and later UI changes can't retarget it. That's 'UI describes, engine acts' in miniature.
Progress
Progress is local-only — sign in to sync across devices.