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
+34 -11
View File
@@ -82,6 +82,10 @@ struct LaneView: View, Equatable {
/// what "increased" does to a stroke.
@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
/// 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.
.cutTreatment(of: lane.id, in: store)
.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
// 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
@@ -248,12 +256,13 @@ struct LaneView: View, Equatable {
// since the ink they need is the same question and only the *verification* differs
// (`ContrastMath`).
//
// A lane draws *no plate*: its background is the selection wash, which is `.clear` at
// rest (`selectionBackground`), so the title, the icon, the count badge and the rename
// field all land directly on the board's `background` the surface the design binds the
// threshold to ("text does sit on it", `BoardView.boardBackground`). The card faces below
// are a different matter and deliberately untouched: they carry their own opaque plate
// (`CardFaceView`'s `.background.secondary`), so their titles never see the board colour.
// A lane's plate is a *translucent wash* (`lanePlate`), so the title, the icon, the
// count badge and the rename field still land, effectively, on the board's `background`
// the surface the design binds the threshold to ("text does sit on it",
// `BoardView.boardBackground`); the wash shifts it too little to change the answer. The
// card faces below are a different matter and deliberately untouched: they carry their
// 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
// 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)
.background(
RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize))
.fill(.background.secondary)
.fill(BoardSurface.cardPlate)
)
}
Spacer(minLength: 0)
@@ -663,6 +672,10 @@ struct LaneView: View, Equatable {
private var replicaHeight: CGFloat {
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
@@ -977,13 +990,23 @@ struct LaneView: View, Equatable {
/// (10-accessibility.md: "Increase Contrast strengthens borders and the selection indicator";
/// `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
/// distinction the setting most needs to restore here.
/// A lane is otherwise bounded by its quiet wash and the gap between it and its neighbour,
/// which is the distinction the setting most needs to restore here.
private var selectionStroke: some View {
RoundedRectangle(cornerRadius: cornerRadius)
.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 {
if isSelected {
AnyShapeStyle(Color.accentColor)
@@ -1171,7 +1194,7 @@ private struct NewCardStubView: View {
.padding(BoardMetrics.cardContentPadding(bodyPointSize: pointSize))
.background(
RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize))
.fill(.background.secondary)
.fill(BoardSurface.cardPlate)
)
.overlay(
RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize))
@@ -1207,7 +1230,7 @@ private struct NewCardStubView: View {
.padding(.leading, BoardMetrics.cardStripeWidth(bodyPointSize: pointSize))
.background(
RoundedRectangle(cornerRadius: BoardMetrics.cardCornerRadius(bodyPointSize: pointSize))
.fill(.background.secondary)
.fill(BoardSurface.cardPlate)
)
}