Give card windows their own undo stacks and coarsen the close
Phase B of the two-level undo card: every card-window gesture — comment
post/delete/edit, body Edit sessions, style and details changes —
registers fine-grained on the window's own stack (window.undoManager
answers with it; board ⌘Z never sees mid-session card steps; an empty
window stack beeps, never falls through). Window close folds the stack
into one coarse values-based board step ("Edit card 'X'") — per-target
per-field later-wins merge, so foreign mid-session writes stay out by
construction, a no-net-change session registers nothing, and any stale
component skips the whole step. The comments/.trash purge defers with
the coarse step via a step-retirement seam on the providers: it runs
when the step leaves the board stack or the board session ends; the git
provider retires dropped steps on register, which keeps Pro's
purge-at-close-flush structural with no tier check. Interim on git
boards: gestures still auto-commit per debounce until phase C's
close-flush commit.
2432 tests in 418 suites green.
Claude-Session: https://claude.ai/code/session_01CqjXB7ASoWtbyoGod68k97
This commit is contained in:
@@ -30,14 +30,18 @@ import SwiftUI
|
||||
/// colour to remember; only `.set` reaches `StyleRecents.record`.
|
||||
@MainActor
|
||||
enum StyleCommand {
|
||||
/// - Parameter undo: the issuing window's own stack, for the one anchor that has one — the card
|
||||
/// window's sidebar (13-native-undo.md ▸ Rules ▸ two levels). `nil`, which every board-side
|
||||
/// anchor passes, is the board's stack.
|
||||
static func apply(
|
||||
background: StyleChange = .keep,
|
||||
icon: StyleChange = .keep,
|
||||
to target: StyleTarget,
|
||||
in store: BoardStore,
|
||||
recents: StyleRecents
|
||||
recents: StyleRecents,
|
||||
on undo: CardWindowUndo? = nil
|
||||
) {
|
||||
store.applyStyle(to: target, background: background, icon: icon)
|
||||
store.applyStyle(to: target, background: background, icon: icon, on: undo)
|
||||
if case let .set(value) = background {
|
||||
recents.record(value)
|
||||
}
|
||||
@@ -239,6 +243,13 @@ struct StyleEditorView: View {
|
||||
/// size and a default argument cannot read one.
|
||||
var layout: StyleEditorLayout?
|
||||
|
||||
/// **Which stack this anchor's writes register on** — the card window's own when the editor is
|
||||
/// mounted in one (`CardStyleSection`), and `nil`, the board's, everywhere else
|
||||
/// (13-native-undo.md ▸ Rules ▸ two levels). It sits beside `layout` and arrives the same way —
|
||||
/// the anchor telling the shared component about itself — but unlike `layout` it is not geometry:
|
||||
/// a colour chosen in a card window is one of that window's session gestures.
|
||||
var undo: CardWindowUndo?
|
||||
|
||||
/// The live body metric, read here rather than passed in — `CardStyleSection`'s pattern, so
|
||||
/// every anchor derives its geometry the same way (10-accessibility.md's full-relative-scaling
|
||||
/// rule).
|
||||
@@ -293,7 +304,7 @@ struct StyleEditorView: View {
|
||||
columns: layout.backgroundColumns,
|
||||
layout: layout,
|
||||
apply: { change in
|
||||
StyleCommand.apply(background: change, to: target, in: store, recents: recents)
|
||||
StyleCommand.apply(background: change, to: target, in: store, recents: recents, on: undo)
|
||||
}
|
||||
)
|
||||
}
|
||||
@@ -346,7 +357,7 @@ struct StyleEditorView: View {
|
||||
columns: layout.symbolColumns,
|
||||
layout: layout,
|
||||
apply: { change in
|
||||
StyleCommand.apply(icon: change, to: target, in: store, recents: recents)
|
||||
StyleCommand.apply(icon: change, to: target, in: store, recents: recents, on: undo)
|
||||
}
|
||||
)
|
||||
if let maximumHeight = layout.symbolGridMaximumHeight {
|
||||
|
||||
Reference in New Issue
Block a user