A refused trash hover withdraws the trash's own promise — the fall-through holds only keepable ones

Gap #12 ruled into 04 ▸ the trash-drop bullet: the refusal fall-through's
honest reading (the column declines to be a target, it does not cancel
the held drag) covers only promises the refusal leaves keepable — lane
shadows keep drawing and an ⌥-copy lands where they are. A standing
proposal naming the trash itself is the promise the refusal just broke,
so it withdraws: the drag goes proposal-less over the column, release
with no proposal cancels in agreement with the empty picture, and
lifting ⌥ re-asks at the same address. Implementation filed on the
Implementation board.

Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
2026-08-06 20:48:45 -04:00
parent 531ca2c595
commit e1ffe71e5b
8 changed files with 76 additions and 21 deletions
+1
View File
@@ -34,6 +34,7 @@ Toolbars are **pure enhancement**: every function they host already has a menu i
- **`background`** on board / lane / card: palette name (kebab-case, hand-editable) or `#RRGGBB[AA]` hex. Board color paints the board window's content background (the surface behind and between lanes). Lane and card color are **edge accents, not fills** (settled in the pathfinder's treatment shootout — its settings matrix of C-series lane / K-series card variants landed on **C7 · full-column top edge** and **K1 · left edge stripe**): a lane's color paints a full-width band along its top edge, a card's a stripe along its left edge; the surfaces themselves keep the standard chrome, so colored title text never sits on a colored fill. - **`background`** on board / lane / card: palette name (kebab-case, hand-editable) or `#RRGGBB[AA]` hex. Board color paints the board window's content background (the surface behind and between lanes). Lane and card color are **edge accents, not fills** (settled in the pathfinder's treatment shootout — its settings matrix of C-series lane / K-series card variants landed on **C7 · full-column top edge** and **K1 · left edge stripe**): a lane's color paints a full-width band along its top edge, a card's a stripe along its left edge; the surfaces themselves keep the standard chrome, so colored title text never sits on a colored fill.
- **`icon`**: SF Symbol per item with per-level defaults (board `rectangle.split.3x1`, lane `square.stack`, card `doc.text`). - **`icon`**: SF Symbol per item with per-level defaults (board `rectangle.split.3x1`, lane `square.stack`, card `doc.text`).
- **The standard chrome is the pathfinder's surface stack** (settled 2026-08-06): the window keeps the neutral system background; every lane wears a quiet quaternary-wash plate (the trash plate's own figure — translucent, so a board-chosen color shows through and the board-level ink rule keeps its premise; opaque under Reduce Transparency, the trash precedent); every card sits on an opaque `controlBackgroundColor` plate — white over the washed lane in light appearance, a step *darker* than the window in dark. One plate value for every face a card draws (resting, replicas, placeholder, arriving), so a card is the same object wherever it renders.
- **`iconColor`**: resolved — **schema yes, control no**. The field renders when hand-written (tint palette name or hex); the app offers no control for it (Controls below). - **`iconColor`**: resolved — **schema yes, control no**. The field renders when hand-written (tint palette name or hex); the app offers no control for it (Controls below).
- The pathfinder's palettes (12 icon tints, 12 backgrounds) carry over as the starting point. **The 12+12 split is a picker split, not a name split** (settled): color resolution searches foregrounds then backgrounds, so a hand-written `background: carnation` (an icon-tint name) resolves and paints — the split governs what the grids offer, never what a name means. - The pathfinder's palettes (12 icon tints, 12 backgrounds) carry over as the starting point. **The 12+12 split is a picker split, not a name split** (settled): color resolution searches foregrounds then backgrounds, so a hand-written `background: carnation` (an icon-tint name) resolves and paints — the split governs what the grids offer, never what a name means.
+1 -1
View File
@@ -62,7 +62,7 @@ Every command is a menu item. The full inventory — every command and action, i
The trash lane (03-board-ui.md ▸ Trash — cards and lanes moved into `<root>/.trash/`; lanes rejoined 2026-07-29 as opaque-unit rows) speaks the board's ordinary keyboard language when shown; hidden, it is invisible to every gesture — and **hiding it clears a trash selection** (nothing invisible stays selected, so the toggle-off drops the selection rather than leave commands enabled against rows nobody can see). Trash cards are ordinary cards; a trashed lane is one opaque row (title + card count) — the old liveness machinery stays retired: no ancestor walks, no entry-vs-universe split, one container boundary plus the board's own kind rule. Rules: The trash lane (03-board-ui.md ▸ Trash — cards and lanes moved into `<root>/.trash/`; lanes rejoined 2026-07-29 as opaque-unit rows) speaks the board's ordinary keyboard language when shown; hidden, it is invisible to every gesture — and **hiding it clears a trash selection** (nothing invisible stays selected, so the toggle-off drops the selection rather than leave commands enabled against rows nobody can see). Trash cards are ordinary cards; a trashed lane is one opaque row (title + card count) — the old liveness machinery stays retired: no ancestor walks, no entry-vs-universe split, one container boundary plus the board's own kind rule. Rules:
- **Navigation**: the shown trash is the **last container for card navigation** — arrows walk into and out of it, and ⌥→ jumps to it; inside, plain arrows walk every row, card and lane row alike (navigation crosses kinds). The trash lane itself is never selectable *as a lane* (no lane op applies to it): with a lane selected, ←/→ and ⌥→ stop at the last real lane. - **Navigation**: the shown trash is the **last container for card navigation** — arrows walk into and out of it, and ⌥→ jumps to it; inside, plain arrows walk every row, card and lane row alike (navigation crosses kinds). The trash lane itself is never selectable *as a lane* (no lane op applies to it): with a lane selected, ←/→ and ⌥→ stop at the last real lane.
- **Dropping a live card — or lane — on the shown trash deletes it** (lanes extended 2026-07-29): the drag is the pointer's delete gesture — release moves the dragged item(s) into `.trash/`; a lane drag over the shown trash proposes the delete alongside its strip slots. The drop diverges from positional drops in one way: **the shadow always takes the topmost position** — honest, not arbitrary: every trash arrival stamps `modified` and the trash sorts newest-first by that stamp (03 ▸ Trash), so a fresh delete genuinely lands on top. The trash takes no drops while hidden, like every gesture. Cross-board arrivals and ⌥-copies refuse too (a transfer-and-delete compound and a copy-into-the-trash are operations the design doesn't name), and a refusal falls through to the strip retarget rather than cancelling the held drag. - **Dropping a live card — or lane — on the shown trash deletes it** (lanes extended 2026-07-29): the drag is the pointer's delete gesture — release moves the dragged item(s) into `.trash/`; a lane drag over the shown trash proposes the delete alongside its strip slots. The drop diverges from positional drops in one way: **the shadow always takes the topmost position** — honest, not arbitrary: every trash arrival stamps `modified` and the trash sorts newest-first by that stamp (03 ▸ Trash), so a fresh delete genuinely lands on top. The trash takes no drops while hidden, like every gesture. Cross-board arrivals and ⌥-copies refuse too (a transfer-and-delete compound and a copy-into-the-trash are operations the design doesn't name), and a refusal falls through to the strip retarget rather than cancelling the held drag. **A refusal arriving over a standing trash proposal withdraws it** (ruled 2026-08-06 — the stationary ⌥ flip made the state reachable without a mouse move): the fall-through's honest reading — the column declines to be a target, it does not cancel the drag the user is still holding — covers only promises the refusal leaves keepable: shadows standing in a lane keep drawing, and an ⌥-copy released over the column lands where they are. A standing proposal naming the trash itself is the one promise the refusal just broke — tombstone rows promising a delete the release will refuse — so it withdraws rather than holds: the drag goes proposal-less over the column (the foreign-reload rule's shape — the shadow withdraws, no proposal stands until the pointer reaches a live target, and a release with no valid proposal cancels, which now agrees with the empty picture). Lifting ⌥ re-asks at the same address (the stationary flip's re-read) and the trash's proposal returns.
- **Selection keeps one container boundary — and goes kind-blind inside the trash** (re-ruled 2026-07-31, superseding the lanes-rejoin pass's kind-homogeneous trash grammar): a selection never mixes trash items with board items, but *within* the trash cards and lane rows select together — clicks, ⇧-click ranges, ⇧-arrow extension, and the rubber band all sweep every row (the band's full-height backdrop covers both kinds), and Select All with a non-empty trash selection selects **all visible trash rows**. The live board keeps cards XOR lanes, and its Select All stays card-scoped, as everywhere. The guard moves to the exits (the mixed-payload drop refusal and ⌘C/⌘X validation below) — inside the trash the only verbs are Delete and the restore paths, so upstream homogeneity bought nothing the exits don't. ⇧-arrow extension still stops at the container boundary. Menu validation stays binary by container: Delete = move to trash on board selections, Delete = permanent on trash selections (03 ▸ Trash) — and Delete works on a mixed selection, the alert counting both kinds. An external move observed by reload re-resolves the selection by presence, as everywhere (02-architecture.md). - **Selection keeps one container boundary — and goes kind-blind inside the trash** (re-ruled 2026-07-31, superseding the lanes-rejoin pass's kind-homogeneous trash grammar): a selection never mixes trash items with board items, but *within* the trash cards and lane rows select together — clicks, ⇧-click ranges, ⇧-arrow extension, and the rubber band all sweep every row (the band's full-height backdrop covers both kinds), and Select All with a non-empty trash selection selects **all visible trash rows**. The live board keeps cards XOR lanes, and its Select All stays card-scoped, as everywhere. The guard moves to the exits (the mixed-payload drop refusal and ⌘C/⌘X validation below) — inside the trash the only verbs are Delete and the restore paths, so upstream homogeneity bought nothing the exits don't. ⇧-arrow extension still stops at the container boundary. Menu validation stays binary by container: Delete = move to trash on board selections, Delete = permanent on trash selections (03 ▸ Trash) — and Delete works on a mixed selection, the alert counting both kinds. An external move observed by reload re-resolves the selection by presence, as everywhere (02-architecture.md).
- **Within-trash moves are inert**: no move or paste ever targets the trash (deleting is ⌫/⌘⌫ or the drag above), and ⌥⌘↑/⌥⌘↓ are inert on trash rows — the trash's order is its arrival order, not a workspace to arrange. - **Within-trash moves are inert**: no move or paste ever targets the trash (deleting is ⌫/⌘⌫ or the drag above), and ⌥⌘↑/⌥⌘↓ are inert on trash rows — the trash's order is its arrival order, not a workspace to arrange.
- **Clipboard: the restore path.** ⌘C copies a trash card (a live copy lands wherever pasted — like copying out of Finder's Trash); **⌘X works** (resettled — it was disabled under the tombstone model): cut in the trash, paste is the keyboard-native restore, an ordinary folder move (10-accessibility.md's drag-free contract) — a card pastes into a lane, a trashed lane pastes after the anchor lane (the lane-paste rule above, verbatim). **⌘C and ⌘X validate against mixed selections** (ruled 2026-07-31, with kind-blind selection): the pasteboard's payload types are per-kind, so Cut and Copy grey out via ordinary menu validation while a trash selection mixes kinds — no failed gesture, no beep; the drag path's drop-time explanation (Drag-to-restore below) is where the rule teaches itself. **An item entering the trash voids its pending cut** (the deliberate-removal rule): a cut card — or lane — that gets deleted drops out of the pending cut, as under the old model. - **Clipboard: the restore path.** ⌘C copies a trash card (a live copy lands wherever pasted — like copying out of Finder's Trash); **⌘X works** (resettled — it was disabled under the tombstone model): cut in the trash, paste is the keyboard-native restore, an ordinary folder move (10-accessibility.md's drag-free contract) — a card pastes into a lane, a trashed lane pastes after the anchor lane (the lane-paste rule above, verbatim). **⌘C and ⌘X validate against mixed selections** (ruled 2026-07-31, with kind-blind selection): the pasteboard's payload types are per-kind, so Cut and Copy grey out via ordinary menu validation while a trash selection mixes kinds — no failed gesture, no beep; the drag path's drop-time explanation (Drag-to-restore below) is where the rule teaches itself. **An item entering the trash voids its pending cut** (the deliberate-removal rule): a cut card — or lane — that gets deleted drops out of the pending cut, as under the old model.
+13 -4
View File
@@ -113,8 +113,8 @@ enum Accommodations {
} }
/// A **translucent wash** a tint laid over whatever happens to be behind it, and the shape /// A **translucent wash** a tint laid over whatever happens to be behind it, and the shape
/// every non-material translucency on the board takes: the trash column's plate and hatched /// every non-material translucency on the board takes: every lane's plate, the trash column's
/// header, and the drag shadow's fill. /// plate and hatched header, and the drag shadow's fill.
/// ///
/// These are not glass, and the distinction matters enough to keep two types: a material samples /// These are not glass, and the distinction matters enough to keep two types: a material samples
/// and blurs its backdrop, a wash simply composites at an alpha. But they fail the same way for /// and blurs its backdrop, a wash simply composites at an alpha. But they fail the same way for
@@ -136,8 +136,17 @@ enum Accommodations {
} }
} }
/// The trash column's plate the quietest of the three, since the header above it carries the /// Every lane's resting plate (03-board-ui.md § Styling Capabilities the standard chrome's
/// column's identity. /// middle step, the pathfinder's lane surface carried over). Translucent so a board-chosen
/// colour shows through it, which is what keeps `BoardTextInk`'s premise true for the header
/// text sitting on it.
static func lanePlateWash(reduceTransparency: Bool) -> Wash {
reduceTransparency ? .opaque : .translucent(opacity: 0.35)
}
/// The trash column's plate the same figure as an ordinary lane's, kept as its own knob
/// because the trash answers to its own rendering spec (03-board-ui.md § Trash Rendering),
/// and the quietest of its treatments: the hatched header above carries the column's identity.
static func trashPlateWash(reduceTransparency: Bool) -> Wash { static func trashPlateWash(reduceTransparency: Bool) -> Wash {
reduceTransparency ? .opaque : .translucent(opacity: 0.35) reduceTransparency ? .opaque : .translucent(opacity: 0.35)
} }
+21
View File
@@ -0,0 +1,21 @@
import SwiftUI
/// The board's **standard chrome** the neutral surface stack under every lane and card,
/// carried over from the pathfinder (03-board-ui.md § Styling: user colour is an edge accent;
/// these are the fills underneath, and they never carry the user's colour).
///
/// The stack is three steps deep, and each step has its own home: the window keeps the standard
/// background (`BoardView.boardBackground` paints over it only when the board *chooses* a colour),
/// every lane lays a quiet quaternary wash over that (`Accommodations.lanePlateWash`), and every
/// card sits on the opaque plate below which is what makes a card pop against the washed lane in
/// both appearances: white on grey in light, a step *darker* than the window in dark.
enum BoardSurface {
/// A card's opaque plate `controlBackgroundColor`, the pathfinder's card surface. Every face
/// on the board fills with this one value (the resting face, the drag replica, the lane
/// replica's miniatures, the placeholder editor and the arriving face), so a card is the same
/// object wherever it is drawn. Opaque deliberately: a card's title never sees the board
/// colour, which is why the 4.5:1 rule binds to the board level and not here
/// (10-accessibility.md; `BoardView.boardBackground`).
static var cardPlate: Color { Color(nsColor: .controlBackgroundColor) }
}
+3 -2
View File
@@ -423,8 +423,9 @@ struct BoardView: View {
/// hand-written hex, they reach the same place has its text colour computed against the /// hand-written hex, they reach the same place has its text colour computed against the
/// threshold by `BoardTextInk`, composited over the window background in the active appearance /// threshold by `BoardTextInk`, composited over the window background in the active appearance
/// and recomputed on an appearance flip; the two subtrees that sit on this fill /// and recomputed on an appearance flip; the two subtrees that sit on this fill
/// (`LaneView.header` and `TrashLaneView.header` every other surface on the board carries its /// (`LaneView.header` and `TrashLaneView.header` their plates are translucent washes the
/// own opaque plate) take the answer as a `\.colorScheme` override. /// colour shows through, where every card carries its own opaque plate,
/// `BoardSurface.cardPlate`) take the answer as a `\.colorScheme` override.
/// ///
/// **One path, two verification stories** (`ContrastMath`): the twelve palette pairs are checked /// **One path, two verification stories** (`ContrastMath`): the twelve palette pairs are checked
/// statically, by a test over the ink this seam chooses for each of them (03-board-ui.md § /// statically, by a test over the ink this seam chooses for each of them (03-board-ui.md §
+2 -2
View File
@@ -249,7 +249,7 @@ struct CardFaceView: View, Equatable {
// Constant, whether or not a stripe paints: every card's text sits on the same grid, so // Constant, whether or not a stripe paints: every card's text sits on the same grid, so
// colouring a card never shifts its title relative to its uncoloured neighbours. // colouring a card never shifts its title relative to its uncoloured neighbours.
.padding(.leading, stripeWidth) .padding(.leading, stripeWidth)
.background(RoundedRectangle(cornerRadius: cornerRadius).fill(.background.secondary)) .background(RoundedRectangle(cornerRadius: cornerRadius).fill(BoardSurface.cardPlate))
.overlay(alignment: .leading) { accentStripe } .overlay(alignment: .leading) { accentStripe }
// The selection treatment, which a hovering Finder file drag borrows outright: "the card // The selection treatment, which a hovering Finder file drag borrows outright: "the card
// highlights while hovered" (04-interactions.md Drag and drop), and the accent stroke is // highlights while hovered" (04-interactions.md Drag and drop), and the accent stroke is
@@ -510,7 +510,7 @@ struct CardFaceView: View, Equatable {
width: BoardMetrics.cardReplicaWidth(measured: measuredWidth, bodyPointSize: pointSize), width: BoardMetrics.cardReplicaWidth(measured: measuredWidth, bodyPointSize: pointSize),
alignment: .leading alignment: .leading
) )
.background(RoundedRectangle(cornerRadius: cornerRadius).fill(.background.secondary)) .background(RoundedRectangle(cornerRadius: cornerRadius).fill(BoardSurface.cardPlate))
.overlay(alignment: .leading) { accentStripe } .overlay(alignment: .leading) { accentStripe }
.dragReplicaShadow() .dragReplicaShadow()
} }
+34 -11
View File
@@ -82,6 +82,10 @@ struct LaneView: View, Equatable {
/// what "increased" does to a stroke. /// what "increased" does to a stroke.
@Environment(\.colorSchemeContrast) private var contrast @Environment(\.colorSchemeContrast) private var contrast
/// Reduce Transparency, for the lane plate's wash (`Accommodations.lanePlateWash` translucent
/// normally, the standard secondary background under the setting; the trash plate's rule).
@Environment(\.accessibilityReduceTransparency) private var reduceTransparency
/// The window's appearance the input to 10-accessibility.md's runtime-contrast rule, and the /// The window's appearance the input to 10-accessibility.md's runtime-contrast rule, and the
/// answer for every board whose background is not a hand-written hex (`headerInk`). /// answer for every board whose background is not a hand-written hex (`headerInk`).
/// ///
@@ -167,6 +171,10 @@ struct LaneView: View, Equatable {
// lane is cut cards and all. // lane is cut cards and all.
.cutTreatment(of: lane.id, in: store) .cutTreatment(of: lane.id, in: store)
.onGeometryChange(for: CGFloat.self) { $0.size.height } action: { measuredHeight = $0 } .onGeometryChange(for: CGFloat.self) { $0.size.height } action: { measuredHeight = $0 }
// Behind the selection wash, not composed into it: the plate is the lane's resting
// surface, the wash above it is the selection's own layer, and stacked `.background`s
// put the later one further back.
.background(lanePlate)
// **This lane's drop target**, on the whole body. It accepts *every* session type and routes // **This lane's drop target**, on the whole body. It accepts *every* session type and routes
// internally card sessions against this lane's masonry zones, lane sessions forwarded to // internally card sessions against this lane's masonry zones, lane sessions forwarded to
// the strip's logic, external Finder file sessions against those same zones because // the strip's logic, external Finder file sessions against those same zones because
@@ -248,12 +256,13 @@ struct LaneView: View, Equatable {
// since the ink they need is the same question and only the *verification* differs // since the ink they need is the same question and only the *verification* differs
// (`ContrastMath`). // (`ContrastMath`).
// //
// A lane draws *no plate*: its background is the selection wash, which is `.clear` at // A lane's plate is a *translucent wash* (`lanePlate`), so the title, the icon, the
// rest (`selectionBackground`), so the title, the icon, the count badge and the rename // count badge and the rename field still land, effectively, on the board's `background`
// field all land directly on the board's `background` the surface the design binds the // the surface the design binds the threshold to ("text does sit on it",
// threshold to ("text does sit on it", `BoardView.boardBackground`). The card faces below // `BoardView.boardBackground`); the wash shifts it too little to change the answer. The
// are a different matter and deliberately untouched: they carry their own opaque plate // card faces below are a different matter and deliberately untouched: they carry their
// (`CardFaceView`'s `.background.secondary`), so their titles never see the board colour. // own opaque plate (`BoardSurface.cardPlate`), so their titles never see the board
// colour.
// //
// **Placed here, not at the end of the chain**, which is the modifier order doing real // **Placed here, not at the end of the chain**, which is the modifier order doing real
// work: everything above including the new-card button in the overlay is text on the // work: everything above including the new-card button in the overlay is text on the
@@ -639,7 +648,7 @@ struct LaneView: View, Equatable {
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
.background( .background(
RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize)) RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize))
.fill(.background.secondary) .fill(BoardSurface.cardPlate)
) )
} }
Spacer(minLength: 0) Spacer(minLength: 0)
@@ -663,6 +672,10 @@ struct LaneView: View, Equatable {
private var replicaHeight: CGFloat { private var replicaHeight: CGFloat {
max(measuredHeight, BoardMetrics.laneReplicaMinimumHeight(bodyPointSize: pointSize)) max(measuredHeight, BoardMetrics.laneReplicaMinimumHeight(bodyPointSize: pointSize))
// The lane's own wash over an opaque base the replica floats over whatever the cursor is
// above, so unlike the resting lane it cannot let the wash composite against the board; the
// opaque `.background` behind it stands in for the window's neutral surface.
.background(lanePlate)
} }
/// Where the title bar sits inside the replica its **centre**, measured down from the replica's /// Where the title bar sits inside the replica its **centre**, measured down from the replica's
@@ -977,13 +990,23 @@ struct LaneView: View, Equatable {
/// (10-accessibility.md: "Increase Contrast strengthens borders and the selection indicator"; /// (10-accessibility.md: "Increase Contrast strengthens borders and the selection indicator";
/// `Accommodations`, and `CardFaceView.plateStroke` for the same three-way branch on a card). /// `Accommodations`, and `CardFaceView.plateStroke` for the same three-way branch on a card).
/// ///
/// A lane is otherwise bounded by nothing but the gap between it and its neighbour, which is the /// A lane is otherwise bounded by its quiet wash and the gap between it and its neighbour,
/// distinction the setting most needs to restore here. /// which is the distinction the setting most needs to restore here.
private var selectionStroke: some View { private var selectionStroke: some View {
RoundedRectangle(cornerRadius: cornerRadius) RoundedRectangle(cornerRadius: cornerRadius)
.strokeBorder(plateStroke, lineWidth: plateStrokeWidth) .strokeBorder(plateStroke, lineWidth: plateStrokeWidth)
} }
/// The lane's resting plate the standard chrome's middle step (03-board-ui.md § Styling
/// Capabilities: window, washed lane, opaque card), the same quaternary wash the trash column
/// wears (`Accommodations.lanePlateWash`). Translucent deliberately: a board-chosen colour
/// shows through, so the header ink computed against the board's background (`headerInk`)
/// keeps its premise on a painted board.
private var lanePlate: some View {
RoundedRectangle(cornerRadius: cornerRadius)
.fill(Accommodations.lanePlateWash(reduceTransparency: reduceTransparency).style)
}
private var plateStroke: AnyShapeStyle { private var plateStroke: AnyShapeStyle {
if isSelected { if isSelected {
AnyShapeStyle(Color.accentColor) AnyShapeStyle(Color.accentColor)
@@ -1171,7 +1194,7 @@ private struct NewCardStubView: View {
.padding(BoardMetrics.cardContentPadding(bodyPointSize: pointSize)) .padding(BoardMetrics.cardContentPadding(bodyPointSize: pointSize))
.background( .background(
RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize)) RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize))
.fill(.background.secondary) .fill(BoardSurface.cardPlate)
) )
.overlay( .overlay(
RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize)) RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize))
@@ -1207,7 +1230,7 @@ private struct NewCardStubView: View {
.padding(.leading, BoardMetrics.cardStripeWidth(bodyPointSize: pointSize)) .padding(.leading, BoardMetrics.cardStripeWidth(bodyPointSize: pointSize))
.background( .background(
RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize)) RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize))
.fill(.background.secondary) .fill(BoardSurface.cardPlate)
) )
} }
+1 -1
View File
@@ -105,7 +105,7 @@ struct TrashLaneRowView: View {
.padding(BoardMetrics.cardContentPadding(bodyPointSize: pointSize)) .padding(BoardMetrics.cardContentPadding(bodyPointSize: pointSize))
.frame(maxWidth: .infinity, alignment: .leading) .frame(maxWidth: .infinity, alignment: .leading)
// Quieter than a card's plate, which is what "dimmed" is here: the cards beside it keep the // Quieter than a card's plate, which is what "dimmed" is here: the cards beside it keep the
// ordinary secondary background, and this row sits a step further back. // ordinary card plate (`BoardSurface.cardPlate`), and this row sits a step further back.
.background(RoundedRectangle(cornerRadius: cornerRadius).fill(.background.tertiary)) .background(RoundedRectangle(cornerRadius: cornerRadius).fill(.background.tertiary))
.overlay( .overlay(
RoundedRectangle(cornerRadius: cornerRadius) RoundedRectangle(cornerRadius: cornerRadius)