Remove the lane header's "+" new-card control
The lane header no longer draws a new-card button. Every other creation path is untouched: File > New Card (Cmd-N), Return on a selected lane, and double-click on lane empty space. - LaneView: drop the button and its overlay slot, leaving the collapse chevron as the header's one piece of trailing chrome. - NewCardTarget: drop the button's click-names-target carve-out over the Cmd-N target rule (Return on a selected lane is now the only direct-target path left). - AccessibilityPhrases: drop the button's spoken label. - BoardMetrics: drop its width reserve; laneHeaderTrailingReserve now reduces to the collapse chevron's own reserve. - DESIGN/03, 04, 10, 11: update the lane title-bar inventory, the Cmd-N override clause and search-clearing mechanism list, the accessibility tree-shape sentence, and the pointer-grammar row that described the button. - Tests: drop the two pinned accessibility-label tests and the newCardButtonReserve assertions; retarget the truncation-headroom test at the now-solo laneHeaderTrailingReserve; fix stale doc-quote comments and the manual accessibility-verification checklist. Full KanbanTests suite: 2807 tests, 482 suites, all passing. Claude-Session: https://claude.ai/code/session_014PtZdPwqZuqEDLc6wZMtEy
This commit is contained in:
@@ -9,13 +9,15 @@ import SwiftUI
|
||||
///
|
||||
/// Leading SF Symbol from `icon` — lenient, an unknown name renders the `square.stack` default
|
||||
/// (`ItemSymbol`) — then the title or its quiet "Untitled" placeholder, a quiet secondary
|
||||
/// card-count badge, and a trailing quiet new-card button. **The whole bar is the drag surface**:
|
||||
/// card-count badge, and a trailing quiet collapse chevron. **The whole bar is the drag surface**:
|
||||
/// a click selects the lane — toggling off on a repeat, exactly as empty space does
|
||||
/// (04-interactions.md § Selection, settled) — and movement begins a **system drag session**
|
||||
/// carrying the lane (`DragSession`, DRAG-REORDER.md). The click-versus-drag split is the system's
|
||||
/// own now: `.onTapGesture` and `.onDrag` coexist, so a hesitant click can never start a drag and a
|
||||
/// drag can never also select. The one thing carved out of the drag region is the new-card button,
|
||||
/// which sits in an overlay outside it.
|
||||
/// drag can never also select. The one thing carved out of the drag region is the collapse chevron,
|
||||
/// which sits in an overlay outside it. **Card creation has no header control**: the "+" that once
|
||||
/// lived beside the chevron is gone (m-collapse's own follow-up), and every creation path — ⌘N,
|
||||
/// Return on a selected lane, double-click on lane empty space — is untouched by its absence.
|
||||
///
|
||||
/// ### The lane's one context menu
|
||||
///
|
||||
@@ -37,8 +39,9 @@ import SwiftUI
|
||||
/// invisibility works and why this needs so little code elsewhere: no card face means no marquee
|
||||
/// registration and no navigation frame, no grid registration means the card zones have nothing to
|
||||
/// resolve against (`BoardDropContext.retargetCards` answers end-of-lane instead), and no header means
|
||||
/// no rename target and no new-card button. The strip keeps exactly three gestures: a plain click
|
||||
/// expands, a modified click selects the lane, and a drag reorders it.
|
||||
/// no rename target and no collapse chevron — the strip's own plain click is its reversal instead. The
|
||||
/// strip keeps exactly three gestures: a plain click expands, a modified click selects the lane, and a
|
||||
/// drag reorders it.
|
||||
///
|
||||
/// ### The card face
|
||||
///
|
||||
@@ -223,7 +226,7 @@ struct LaneView: View, Equatable {
|
||||
.onDrop(of: boardDropTypes, delegate: LaneDropDelegate(context: drops, laneID: lane.id))
|
||||
// **The lane is an accessibility container** — "window → lanes (accessibility containers, in
|
||||
// lane `order`) → cards (leaf elements, in card `order`)" (10-accessibility.md ▸ The board
|
||||
// through VoiceOver). `.contain` rather than `.combine`: the header, the new-card button and
|
||||
// through VoiceOver). `.contain` rather than `.combine`: the header, the collapse chevron and
|
||||
// every card must stay individually reachable, which is the whole point of a container the
|
||||
// VoiceOver cursor enters (`TrashLaneView` states the same rule from the trash's side).
|
||||
.accessibilityElement(children: .contain)
|
||||
@@ -456,13 +459,11 @@ struct LaneView: View, Equatable {
|
||||
}
|
||||
.onDisappear { drops.registry.removeHeader(lane.id) }
|
||||
// **The header's trailing chrome, outside the drag region** — the overlay is attached
|
||||
// after `.onDrag` deliberately, which is what makes both controls clickable rather than
|
||||
// grabbable (the new-card button's long-standing arrangement, now shared).
|
||||
// after `.onDrag` deliberately, which is what makes the chevron clickable rather than
|
||||
// grabbable (the new-card button's old arrangement, now the chevron's alone — the button
|
||||
// itself is gone).
|
||||
.overlay(alignment: .trailing) {
|
||||
HStack(spacing: 0) {
|
||||
collapseButton
|
||||
newCardButton
|
||||
}
|
||||
collapseButton
|
||||
}
|
||||
// **10-accessibility.md's ≥ 4.5:1 rule, at the one place on the board where text sits on
|
||||
// a colour the user chose** (`BoardTextInk`) — palette name and hand-written hex alike,
|
||||
@@ -478,7 +479,7 @@ struct LaneView: View, Equatable {
|
||||
// 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
|
||||
// work: everything above — including the collapse chevron in the overlay — is text on the
|
||||
// board background and takes the computed ink, while the context menu and the Style…
|
||||
// popover attached below stay in the window's own appearance, because a menu is system
|
||||
// chrome drawn on its own surface and not on this board's colour.
|
||||
@@ -687,11 +688,11 @@ struct LaneView: View, Equatable {
|
||||
Spacer(minLength: 0)
|
||||
}
|
||||
// Reserves the trailing chrome's width so a long title truncates before it collides, and keeps
|
||||
// those controls out of the gestured region. **Font-derived** rather than a fixed figure: both
|
||||
// are `Image`s at a relative image scale, so a fixed reserve would be overrun by the glyphs
|
||||
// themselves at a large system text size and 03-board-ui.md's graceful-truncation rule would
|
||||
// quietly stop holding (`BoardMetrics.laneHeaderTrailingReserve`, which is the collapse
|
||||
// chevron's room plus the new-card button's).
|
||||
// the chevron out of the gestured region. **Font-derived** rather than a fixed figure: it is an
|
||||
// `Image` at a relative image scale, so a fixed reserve would be overrun by the glyph itself at
|
||||
// a large system text size and 03-board-ui.md's graceful-truncation rule would quietly stop
|
||||
// holding (`BoardMetrics.laneHeaderTrailingReserve`, the collapse chevron's own room now that
|
||||
// the new-card button that once shared it is gone).
|
||||
.padding(.trailing, BoardMetrics.laneHeaderTrailingReserve(bodyPointSize: pointSize))
|
||||
.padding(.horizontal, BoardMetrics.laneHeaderInset(bodyPointSize: pointSize))
|
||||
}
|
||||
@@ -757,13 +758,13 @@ struct LaneView: View, Equatable {
|
||||
}
|
||||
|
||||
/// **The collapse chevron** (03-board-ui.md § Lane ▸ Collapsed lanes) — the pointer's way into the
|
||||
/// fold, beside the new-card button at the header's trailing edge.
|
||||
/// fold, at the header's trailing edge, and its **only** trailing control since the new-card button
|
||||
/// that once sat beside it was removed (this milestone).
|
||||
///
|
||||
/// **Always visible, not hover-revealed**, because the header has no hover chrome to match: the
|
||||
/// new-card button beside it has been unconditional since it existed, and one control that appears
|
||||
/// on hover next to one that does not reads as a glitch rather than as a convention. The cost is
|
||||
/// paid in the header's trailing reserve, which is the figure that keeps the title truncating before
|
||||
/// either glyph (`headerContent`).
|
||||
/// **Always visible, not hover-revealed**, because the header has no hover chrome at all: an
|
||||
/// always-on glyph that only sometimes appeared would read as a glitch rather than as a convention.
|
||||
/// The cost is paid in the header's trailing reserve, which is the figure that keeps the title
|
||||
/// truncating before it (`headerContent`).
|
||||
///
|
||||
/// `chevron.left` because the fold travels that way — the lane's content collapses toward its
|
||||
/// leading edge and leaves a spine — and the strip's reversal is a click on the strip itself, which
|
||||
@@ -785,30 +786,6 @@ struct LaneView: View, Equatable {
|
||||
.disabled(!store.acceptsBoardMutations)
|
||||
}
|
||||
|
||||
/// The new-card button — a **pointer twin** of File ▸ New Card whose click *names its target*:
|
||||
/// "the lane header's new-card button overrides [the ⌘N target] rule — the click names its
|
||||
/// target lane, selection notwithstanding" (11-command-nexus.md ▸ Pointer grammar, settled), so
|
||||
/// it passes this lane and no anchor rather than consulting `NewCardTarget`.
|
||||
private var newCardButton: some View {
|
||||
Button {
|
||||
store.transient.beginPlaceholder(inLane: lane.id)
|
||||
} label: {
|
||||
Image(systemName: "plus")
|
||||
.imageScale(.small)
|
||||
.foregroundStyle(.secondary)
|
||||
.contentShape(Rectangle())
|
||||
}
|
||||
.buttonStyle(.plain)
|
||||
// "The lane header's new-card button is a labeled child ('New card in ⟨lane⟩')"
|
||||
// (10-accessibility.md ▸ The board through VoiceOver) — the header's one child element, which
|
||||
// is why it lives in an overlay outside the flattened bar rather than inside it.
|
||||
.accessibilityLabel(AccessibilityPhrases.newCardLabel(lane: lane.title.value))
|
||||
// Mutating, so the read-only lock disables it like every other write path
|
||||
// (02-architecture.md § The lock's scope), and the focused-editor rule closes it while an
|
||||
// inline editor is open (04 ▸ Grammar) — the pointer twin of a disabled menu item.
|
||||
.disabled(store.isReadOnly || store.isEditingInline)
|
||||
}
|
||||
|
||||
// MARK: - The lane drag
|
||||
|
||||
/// Begins the lane's system drag session (DRAG-REORDER.md; 04-interactions.md ▸ Drag and drop).
|
||||
|
||||
Reference in New Issue
Block a user