From d9b6232dea0cbb8960be38b8e0445861a7046e8c Mon Sep 17 00:00:00 2001 From: rzen Date: Thu, 6 Aug 2026 20:54:08 -0400 Subject: [PATCH] =?UTF-8?q?The=20lane=20plate's=20strays=20return=20to=20t?= =?UTF-8?q?heir=20anchors=20=E2=80=94=20the=20replica's=20wash=20leaves=20?= =?UTF-8?q?the=20height=20arithmetic=20it=20broke?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The pathfinder surface stack landed in e1ffe71 entangled with staged-index placement drift: the replica's wash block sat inside replicaHeight (a CGFloat has no .background — HEAD did not build), the body plate rode outside the cut dim, the lanePlate property and the DESIGN bullet sat away from their kin. Pure relocation, no new lines: every block moves to the anchor it was written for. Verified by an isolated build of this tree. Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY --- DESIGN/03-board-ui.md | 2 +- Kanban/UI/Board/LaneView.swift | 36 +++++++++++++++++----------------- 2 files changed, 19 insertions(+), 19 deletions(-) diff --git a/DESIGN/03-board-ui.md b/DESIGN/03-board-ui.md index 6005553..527a883 100644 --- a/DESIGN/03-board-ui.md +++ b/DESIGN/03-board-ui.md @@ -33,8 +33,8 @@ Toolbars are **pure enhancement**: every function they host already has a menu i ### Capabilities (settled) - **`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`). - **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. +- **`icon`**: SF Symbol per item with per-level defaults (board `rectangle.split.3x1`, lane `square.stack`, card `doc.text`). - **`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. diff --git a/Kanban/UI/Board/LaneView.swift b/Kanban/UI/Board/LaneView.swift index 202fed9..b836235 100644 --- a/Kanban/UI/Board/LaneView.swift +++ b/Kanban/UI/Board/LaneView.swift @@ -166,15 +166,15 @@ struct LaneView: View, Equatable { .padding(BoardMetrics.lanePlatePadding(bodyPointSize: pointSize)) } .background(selectionBackground) + // 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) .overlay(selectionStroke) // The deferred cut's dim (04-interactions.md ▸ Clipboard) — on the whole lane, because a cut // 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 @@ -657,6 +657,10 @@ struct LaneView: View, Equatable { .padding(BoardMetrics.lanePlatePadding(bodyPointSize: pointSize)) } .frame(width: replicaWidth, height: replicaHeight, alignment: .topLeading) + // 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) .background(RoundedRectangle(cornerRadius: cornerRadius).fill(.background)) .clipShape(RoundedRectangle(cornerRadius: cornerRadius)) .dragReplicaShadow() @@ -672,10 +676,6 @@ 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 @@ -978,6 +978,16 @@ struct LaneView: View, Equatable { store.selection.container == .board && store.selection.ids.contains(lane.id) } + /// 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) + } + /// The selection treatment: a subtle whole-lane accent wash and stroke. Deliberately quiet — /// 03-board-ui.md gives lane *colour* to the top-edge accent band, so selection must not read as /// a fill that would compete with it once that lands. @@ -997,16 +1007,6 @@ struct LaneView: View, Equatable { .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)