The popover/sheet split reverses — settings fold into the Git tab, the widget stacks name over branch

The titlebar widget becomes a two-line identity block: the board glyph at
22pt spanning both lines, the title over the branch (git-mode only, smaller
and secondary), the em-dash retired. New Branch… returns to the switch menu
behind a divider, revealing an inline name field — the pre-split shape. The
board settings sheet retires whole: add-git and commit identity render
inline in the Git tab's postures (BoardGitSetup.swift), the availability
rule collapses into BoardGitSetupSection.resolve, and Board ▸ Board
Settings… leaves the menu bar. Where 07's remote/credential setup surfaces
land is deliberately left open — filed on the Redesign board.

Claude-Session: https://claude.ai/code/session_01SR4XGjmBE16ZUYWpfFHXwY
This commit is contained in:
2026-08-07 21:48:02 -04:00
parent 99ebb69a1d
commit 7414fc8400
22 changed files with 884 additions and 1259 deletions
+17 -48
View File
@@ -7,20 +7,28 @@ import XCTest
/// > runs in UI tests over every surface board (trash shown and hidden), card window (Preview, Edit,
/// > raw source), welcome, template chooser, board popover, board settings sheet.
///
/// The last of those is **one surface shorter than the design's sentence** since 2026-08-07: the
/// board settings sheet retired that day (03-board-ui.md Board settings sheet, marked retired
/// the 2026-07-31 popover/sheet split reversed), and everything it held now renders inside the board
/// popover's Git tab. So the popover's own audit is where those controls are looked at, and the
/// every-surface claim is satisfied by there being one surface fewer rather than by a test skipping
/// one.
///
/// One test per surface, one audit call each. `performAccessibilityAudit` audits **the app's
/// currently displayed UI** rather than a subtree, so each test's job is entirely navigation: get the
/// surface on screen, then let the audit look at whatever is there.
///
/// ### The board settings sheet is reachable since the 2026-08-07 pivot
/// ### The settings sheet's audit, and where it went
///
/// The sheet was Pro-only, and this suite could not reach it: the fixture launch had no tier
/// control, and a `--ui-test-pro` launch argument was rejected as a subscription bypass anyone could
/// type into Terminal (`UITestLaunch` ships in the app binary on purpose). Since the pivot
/// (12-editions.md PIVOT 2026-08-07 git left the paywall), `BoardSettingsAvailability` asks only
/// the board's mode, the fixture board is mode `none`, and the sheet opens for the audit like any
/// other surface the deferral to the manual VoiceOver pass is retired with the gate. The
/// no-bypass objection stands as precedent for whatever the next split gates. See
/// `testBoardSettingsSheetOpensFromTheMenuAndAudits`.
/// The sheet was Pro-only and this suite could not reach it: the fixture launch had no tier control,
/// and a `--ui-test-pro` launch argument was rejected as a subscription bypass anyone could type
/// into Terminal (`UITestLaunch` ships in the app binary on purpose). The 2026-08-07 pivot
/// (12-editions.md PIVOT 2026-08-07 git left the paywall) made it reachable and it gained a test
/// here; the **reversal later the same day retired the sheet outright**, and the test with it. What
/// remains is the precedent the no-bypass objection stands for whatever the next split gates and
/// one live consequence for this file: the surfaces that sheet held are now the board popover's Git
/// tab, which `testBoardInfoPopover` opens onto its Info tab. Auditing the Git tab specifically wants
/// a click on the popover's segmented strip and is a card of its own, filed rather than faked here.
///
/// ### No waiving
///
@@ -199,43 +207,4 @@ final class AccessibilityAuditTests: XCTestCase {
)
try app.performAccessibilityAudit()
}
// MARK: - The board settings sheet
/// **The settings sheet's audit**, reachable since the 2026-08-07 pivot (this file's header):
/// the fixture board is mode `none`, `BoardSettingsAvailability` asks only the mode, so the menu
/// row opens the sheet add-git's home (03-board-ui.md Board settings sheet) and the audit
/// looks at the sheet itself rather than deferring to the manual VoiceOver pass.
@MainActor
func testBoardSettingsSheetOpensFromTheMenuAndAudits() throws {
let app = XCUIApplication.launchedWithFixtureBoard()
let bar = app.menuBars.firstMatch
let boardMenu = bar.menuBarItems["Board"]
XCTAssertTrue(
boardMenu.waitForExistence(timeout: XCUIApplication.uiTimeout),
"the Board menu is missing from the menu bar"
)
boardMenu.click()
let row = bar.menuItems["Board Settings…"]
XCTAssertTrue(
row.waitForExistence(timeout: XCUIApplication.uiTimeout),
"Board ▸ Board Settings… is missing — the row ships in every mode that can host the sheet"
)
XCTAssertTrue(
row.isEnabled,
"a mode-none board hosts the sheet (add-git's home) — the pivot retired the tier gate that disabled this row"
)
row.click()
let sheet = app.sheets.firstMatch
XCTAssertTrue(
sheet.waitForExistence(timeout: XCUIApplication.uiTimeout),
"the Board Settings sheet did not present from its menu row"
)
try app.performAccessibilityAudit()
app.typeKey(.escape, modifierFlags: [])
}
}
+9 -10
View File
@@ -30,7 +30,7 @@ Have a scratch board to hand for Part 2 — a new one from File ▸ New Board…
## Part 1 — run the audit suite
`KanbanUITests/AccessibilityAuditTests.swift` runs Xcode's accessibility audit over the surfaces the design names — nine of the ten automatically, the tenth (the **board settings sheet**) by hand in Part 3, because it is Pro-only and the fixture launch has no tier control. **Violations are test failures, not warnings**, and nothing is waived: the audits pass no issue handler at all.
`KanbanUITests/AccessibilityAuditTests.swift` runs Xcode's accessibility audit over the surfaces the design names — all of them automatically since 2026-08-07, when the **board settings sheet** retired and its controls rehomed into the board popover's Git tab (03-board-ui.md ▸ Board settings sheet, marked retired). The by-hand pass below is now about that tab, not about a surface the suite cannot reach. **Violations are test failures, not warnings**, and nothing is waived: the audits pass no issue handler at all.
```
xcodebuild test -project Kanban.xcodeproj -scheme Kanban \
@@ -38,7 +38,7 @@ xcodebuild test -project Kanban.xcodeproj -scheme Kanban \
-only-testing:KanbanUITests/AccessibilityAuditTests
```
The nine surfaces, and how each test gets there:
The surfaces, and how each test gets there:
| Test | Surface | Navigation |
| --- | --- | --- |
@@ -51,22 +51,21 @@ The nine surfaces, and how each test gets there:
| `testWelcomeWindow` | Welcome, with a recents row | Window ▸ Welcome to Lanework |
| `testTemplateChooser` | Template chooser | File ▸ New Board… |
| `testBoardInfoPopover` | Board popover | File ▸ Board Info |
| `testBoardSettingsRowIsPresentAndDisabledOnTheFreeFixture` | Board window, with Board ▸ Board Settings… checked | Opens the Board menu, asserts the row is present and disabled, closes it |
Every test launches the app with `--ui-test-fixture-board`, which makes the app build a known board inside its own container and open it — three lanes ("To Do", "Doing", "Done"), six cards, one card with a rich Markdown body, an attachment and a three-comment thread (one unattributed, one edited), one card already in the trash. That is the `standard` fixture variant; the bare flag means it, and the other two shapes (`large`, `malformed`) belong to the end-to-end pass. The board and the registry both live in a scratch directory that is wiped on every launch, so an audit run never touches your real boards or your recents list. See `Kanban/App/UITestLaunch.swift` for why the board cannot simply be handed to the app on the command line (the sandbox).
If a test fails, read the issue's `compactDescription` and fix the app. Adding a waiver is a design change and needs an entry on the Redesign board first.
### The board settings sheet, by hand
### The popover's Git tab, by hand
The sheet (03-board-ui.md ▸ Board settings sheet) needs **Pro on a board with app-managed git**, and there is deliberately no launch argument that grants Pro: `UITestLaunch` is compiled into the shipping binary, so a tier flag would be a subscription bypass anyone could type into Terminal. Until a fixture can reach Pro honestly, run this by hand once per release on a Pro build, with a git-mode board open:
The automated audit opens the popover on its **Info** tab, and switching tabs is a click the suite doesn't make yet — so the git surface's own keyboard and VoiceOver behaviour is a by-hand pass, once per release, with a git-mode board open. (This section was the **board settings sheet's** until 2026-08-07; the sheet retired and its controls came here, so the checks moved with them rather than being dropped.)
- [ ] **Open both doors.** Board ▸ Board Settings…, and the popover's **Board Settings…** row (File ▸ Board Info ▸ Git). The popover dismisses as the sheet appears — never both at once.
- [ ] **Sectioned, and navigable by heading.** With VoiceOver on, the rotor's heading list holds "Board Settings" and each section's title ("Branch", "Commit Identity"; "Git" on a board with no repository yet).
- [ ] **Tab reaches every control** with VoiceOver off and Full Keyboard Access on — the branch name field, Create, both identity fields, Done.
- [ ] **One door.** File ▸ Board Info (⌘I) ▸ **Git**. There is no second configuration surface and no Board ▸ Board Settings… row — both retired with the sheet.
- [ ] **Navigable by heading.** With VoiceOver on, the rotor's heading list holds "Commit Identity" on a git-mode board.
- [ ] **Tab reaches every control** with VoiceOver off and Full Keyboard Access on — the branch menu, the New Branch… field and Create when revealed, both identity fields.
- [ ] **⌘Z inside a field is the field's**, not the board's: type into Commit Identity ▸ Name, press ⌘Z, and the *typing* reverts — no tree checkout, no board step consumed (06-history-undo.md ▸ Undo routing).
- [ ] **Escape and Done both dismiss**; Escape in a dirty branch-name field clears the field first (one layer per press).
- [ ] **The read-only lock disables in place**: the sheet stays open and its controls grey out, with the banner naming why.
- [ ] **Escape steps outward one layer per press**: in a dirty New Branch… field it clears the field; in an empty one it closes the reveal; again, it dismisses the popover.
- [ ] **The read-only lock disables in place**: the popover stays open and its controls grey out, with the banner naming why.
## Part 2 — the VoiceOver smoke script