From 51cf994cb94927c1527f862657f2c78809f81d9e Mon Sep 17 00:00:00 2001 From: rzen Date: Fri, 7 Aug 2026 11:22:36 -0400 Subject: [PATCH] =?UTF-8?q?Lane=20selection=20sheds=20its=20slab=20?= =?UTF-8?q?=E2=80=94=20a=20hairline=20edge=20with=20a=20soft=20halo,=20int?= =?UTF-8?q?ensity=20inverse=20to=20area?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The whole-lane wash-and-solid-ring scaled its emphasis with the lane's area: what read as a ring on a card read as a slab on a column. Resettled as the system focus ring's vocabulary at selection strength — a hairline accent edge with a soft accent glow bleeding outward, riding the stroke shape rather than the translucent plate (a shadow behind it would bleed through as murk), and no wash at all: nothing tints the cards' backdrop, and lane colour stays the accent band's alone. Under Increase Contrast the halo yields to the solid full-alpha ring — "strengthens borders" means crisper, and a blur is the one thing a border cannot become — so the setting sees exactly the indicator it always has; cards and trash rows are card-scale and keep the solid ring at every contrast. One selection vocabulary, pinned by Accommodations.drawsSelectionHalo and its test. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy --- Kanban/UI/Accommodations.swift | 14 +++++++ Kanban/UI/Board/LaneView.swift | 44 ++++++++++++--------- KanbanTests/VisualAccommodationsTests.swift | 9 +++++ 3 files changed, 48 insertions(+), 19 deletions(-) diff --git a/Kanban/UI/Accommodations.swift b/Kanban/UI/Accommodations.swift index ffdbbe7..7c27171 100644 --- a/Kanban/UI/Accommodations.swift +++ b/Kanban/UI/Accommodations.swift @@ -75,6 +75,20 @@ enum Accommodations { contrast == .increased ? 1 : base } + /// Whether a selected lane draws its **halo** — the soft accent glow bleeding outward from the + /// plate's hairline edge (`LaneView.selectionStroke`), the system focus ring's vocabulary at + /// selection strength. + /// + /// Under Increase Contrast the answer is no, and that is the setting's own logic rather than a + /// retreat: "strengthens borders and the selection indicator" means *crisper*, and a blur is + /// the one thing a border cannot become. The edge instead goes full-alpha and heavier + /// (`accentOpacity`, `borderWidth` at the solid ring's base), so the setting sees the same + /// unambiguous solid ring it always has — the halo is the resting treatment's softness, not + /// the indicator itself. + static func drawsSelectionHalo(contrast: ColorSchemeContrast) -> Bool { + contrast != .increased + } + /// Increase Contrast, asked of AppKit rather than of the SwiftUI environment — for callers built /// outside a rendered hierarchy, where the environment's accessibility values are not reliably /// populated (`Motion.prefersReducedMotion`'s constituency). diff --git a/Kanban/UI/Board/LaneView.swift b/Kanban/UI/Board/LaneView.swift index 310efa3..d5933ba 100644 --- a/Kanban/UI/Board/LaneView.swift +++ b/Kanban/UI/Board/LaneView.swift @@ -175,10 +175,6 @@ 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 @@ -1081,28 +1077,35 @@ struct LaneView: View, Equatable { .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. - private var selectionBackground: some View { - RoundedRectangle(cornerRadius: cornerRadius) - .fill(isSelected ? AnyShapeStyle(Color.accentColor.opacity(0.08)) : AnyShapeStyle(.clear)) - } - - /// The selection ring — and, under Increase Contrast, the plate's resting edge as well - /// (10-accessibility.md: "Increase Contrast strengthens borders and the selection indicator"; - /// `Accommodations`, and `CardFaceView.plateStroke` for the same three-way branch on a card). + /// The selection treatment: a hairline accent edge with a soft accent glow bleeding outward — + /// the system focus ring's vocabulary at selection strength (resettled 2026-08-06, replacing + /// the whole-lane wash-and-solid-ring, whose emphasis scaled with the lane's area: what read as + /// a ring on a card read as a slab on a column). No wash at all now — nothing tints the cards' + /// backdrop, and lane *colour* stays the accent band's alone (03-board-ui.md § Styling). /// - /// 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. + /// The glow rides the stroke shape, never the plate: the plate is translucent + /// (`Accommodations.lanePlateWash`), and a shadow drawn behind it would bleed through as murk. + /// Under Increase Contrast the halo yields to the solid full-alpha ring — the two width bases + /// below — so the setting's selection indicator is exactly what it was before the resettlement + /// (`Accommodations.drawsSelectionHalo`; the trash's lane rows and `CardFaceView` are + /// card-scale and keep the solid ring at every contrast: one selection vocabulary, intensity + /// inverse to area). private var selectionStroke: some View { RoundedRectangle(cornerRadius: cornerRadius) .strokeBorder(plateStroke, lineWidth: plateStrokeWidth) + .shadow(color: haloColor, radius: 5) + } + + /// The halo's glow — clear whenever the halo doesn't draw, because a conditional modifier + /// would change the overlay's identity where a clear shadow just paints nothing. + private var haloColor: Color { + guard isSelected, Accommodations.drawsSelectionHalo(contrast: contrast) else { return .clear } + return Color.accentColor.opacity(0.3) } private var plateStroke: AnyShapeStyle { if isSelected { - AnyShapeStyle(Color.accentColor) + AnyShapeStyle(Color.accentColor.opacity(Accommodations.accentOpacity(0.55, contrast: contrast))) } else if Accommodations.drawsRestingBorder(contrast: contrast) { AnyShapeStyle(.separator) } else { @@ -1111,7 +1114,10 @@ struct LaneView: View, Equatable { } private var plateStrokeWidth: CGFloat { - Accommodations.borderWidth(isSelected ? 1.5 : 1, contrast: contrast) + // The halo's edge is a hairline; with the halo off (Increase Contrast) the selected edge + // returns to the solid ring's 1.5 base, which `borderWidth` then strengthens as ever. + let base: CGFloat = isSelected && !Accommodations.drawsSelectionHalo(contrast: contrast) ? 1.5 : 1 + return Accommodations.borderWidth(base, contrast: contrast) } // MARK: - Rename plumbing diff --git a/KanbanTests/VisualAccommodationsTests.swift b/KanbanTests/VisualAccommodationsTests.swift index 14bfbfc..869d878 100644 --- a/KanbanTests/VisualAccommodationsTests.swift +++ b/KanbanTests/VisualAccommodationsTests.swift @@ -255,6 +255,15 @@ struct IncreaseContrastTests { #expect(Accommodations.accentOpacity(base, contrast: .increased) == 1) } } + + /// The selected lane's glow is the resting treatment's softness, not the indicator itself: + /// under Increase Contrast it yields to the solid full-alpha ring, because "strengthens + /// borders" means crisper and a blur is the one thing a border cannot become. + @Test("The selection halo yields to Increase Contrast") + func theHaloYieldsToIncreaseContrast() { + #expect(Accommodations.drawsSelectionHalo(contrast: .standard)) + #expect(!Accommodations.drawsSelectionHalo(contrast: .increased)) + } } // MARK: - Reduce Transparency