Repository navigation
fix(board): let the iPad regular-width Minesweeper board viewport fill its column - #1106
Conversation
…#1101) MinesweeperBoardView.macLayout was written for the Mac detail pane and is selected by horizontalSizeClass == .regular, so iPad inherited the Mac caps: macOuterMaxWidth 900 centred the layout and macBoardMaxSide 600 pinned the board viewport to a 600 pt square in both orientations, leaving ~540 pt of the board column empty in portrait (#1101 measurements). New `regularBoardContainer` environment value (RegularBoardContainer.swift): - .fillsColumn (iOS default): the board frame takes the column's full width AND height; the #764 cellSizing ladder picks the cell side from that rect. - .cappedDetailPane (macOS default): the locked 900 / 600 Mac rule, unchanged. Platform default pinned by RegularBoardContainerTests (mutating the #else branch turns it red). An environment value rather than `#if os(iOS)` so the macOS test host can render the iPad rule: the iPad snapshot and the emit_iPad_board ASC frame inject .fillsColumn explicitly. Baselines: the old iPad PNG is kept byte-for-byte under a new `snapshotBeginnerCovered_regularCapped_light` test that injects .cappedDetailPane at the same 1032x1376 size (passes without re-record, the mechanical proof the Mac rule did not move); the iPad .fillsColumn baseline is re-recorded. All iPhone and Mac baselines untouched (415 tests / 71 suites green). The 7 Minesweeper ipad-13 03-board store frames are regenerated (scan:store_frame_drift PASS); they stay #1054-affected. Simulator (iPad Pro 11-inch M5, iOS 26.4; #1101 method): portrait Intermediate/Expert viewport 481.5x595.5 -> 481.5x1117.5 (leftover column ~4 pt), landscape Intermediate 595.5x595.5 -> 749.5x749.5 (no scrolling), landscape Expert 595.5x595.5 -> 857.5x749.5; iPhone Expert control unchanged at 397.7x615.7. Sudoku's identical 960/640 cap is tracked as #1105. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
…round 2) Code review on a137f69: Beginner (9x9) is width-bound in every regular layout, so under .fillsColumn the fitted branch's `.center` alignment put the same 484 pt board in the middle of the whole column, moving the blank from below the board to above AND below it, and the ASC Beginner store frame gained a large gap between the HUD and the board. PM ruling (2026-09-24): "fill the column" means the board starts where the column starts, so the fitted board is top-aligned under .fillsColumn only. - `boardGrid` takes `fittedAlignment: Alignment = .center`, applied only to the `.fitted` branch frame; compact (iPhone) keeps the default, capped (Mac) passes `.center` via the new pure `fittedAlignment(for:)` helper (.fillsColumn -> .top, .cappedDetailPane -> .center), pinned by RegularBoardContainerTests. - Mutations: default flipped to `.top` turns 11 iPhone/iPad board snapshots red; helper's .fillsColumn branch flipped to .center turns the unit test red. Full suite 416 tests / 71 suites green; no iPhone or Mac PNG moved; the capped iPad baseline still sha1 dd773643f6a36922bbd447ddd292ce1f356cc4a2. - iPad .fillsColumn baseline re-recorded (board flush with the column top); the 7 Minesweeper ipad-13 03-board store frames regenerated (168 slots, only those 7 changed). They stay #1054-affected. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Code review — round 1 (a137f69), two independent reviewershaiku → APPROVE, 0 findings. Verified: capped path modifier-by-modifier equal to base (900 / 600); file set = 2 sources + 3 tests + 2 snapshot PNGs + 7 ipad-13 03-board frames, nothing else; old iPad PNG and the new sonnet → REQUEST-CHANGES, 1 BLOCKER (evidence: rendered Leader adjudication: the BLOCKER is factually right and was escalated to the PM as a design ruling (contact point 2). PM ruling: top-align the fitted board under |
Code review — round 2 (f73289d), sonnet re-review → APPROVEVerified by the reviewer from the ref (not from the commit message):
MINOR (cosmetic, carried from round 1, not blocking): Not verified by the reviewer: full 416 / 71 run (filtered suites only), the 5 non-en/ja locale frames (same code path), simulator numbers and pinch-zoom (Leader-measured, see PR body). |
T6 驗收(fresh-context)repo
總判定:ACCEPT-with-findings Findings(不影響 ACCEPT,但值得記錄):
我沒驗的部分(依 PR body,未重跑,標示信心度):
🤖 Generated with Claude Code |
|
PM merge record, head Owner ruling: Option A — the iPad board fills the column. PM rulings: seam S2 (an environment value, not The round-1 blocker and its fix. A reviewer found that Beginner is width-bound, so filling the column merely moved the empty space from below the board to above and below — and Verified independently:
Note: the round-2 commit body still says 11 red snapshots; the correct figure is 10, as recorded here and in the PR body. History was not rewritten for a count. Out of scope and tracked: #1105, Sudoku's identical 960/640 regular-width cap. |
Closes #1101
What
MinesweeperBoardView.macLayoutis selected byhorizontalSizeClass == .regular, so iPad inherited the Mac detail-pane caps (macOuterMaxWidth900 centring the layout,macBoardMaxSide600 pinning the board viewport to a 600 pt square in both orientations). #1101 measured ~540 pt of empty board column in portrait.PM ruling (2026-09-24, on the two-option numbers): Option A — the iPad board viewport fills the column (width and height), cell size left to the #764
cellSizingladder; seam S2 — an environment value, not#if os(iOS), so the macOS test host can render the iPad rule for the iPad snapshot and the ASC store frame.RegularBoardContainer.swift(new):.fillsColumn(iOS default) /.cappedDetailPane(macOS default) +\.regularBoardContainerenvironment key (same shape asMinesweeperCellTokens.swift).MinesweeperBoardView.swift:macLayoutoutermaxWidthandmacBoardColumnmaxWidth/maxHeightare 900 / 600 under.cappedDetailPaneand.infinityunder.fillsColumn.compactLayoutandcellSizinguntouched..fittedbranch) cannot grow into the column, and centring it in the full column height moved the blank from below the board to above and below — visible in the ASC Beginner store frame.boardGrid(fittedAlignment: Alignment = .center)now applies the alignment only to the.fittedbranch frame;fittedAlignment(for:)maps.fillsColumn → .top,.cappedDetailPane → .center; compact callsboardGrid()(default.center). Pinned byRegularBoardContainerTests; mutating the default to.topturns the 10 iPhone board snapshots red (T6-verified count; the two iPad/capped tests pass the alignment explicitly and stay green, as intended — the round-2 commit body's "11" counted a first-record iPad run), mutating the.fillsColumnbranch to.centerturns the unit test red.iPhone and macOS did not move (mechanical proof)
MinesweeperKitsuite on head (f73289d), my own run:Test run with 416 tests in 71 suites passed(exit 0); round 1 was 415 / 71. Base (34c3b03) board suites: 23 tests / 4 suites green.git statusafter the run: no iPhone or Mac PNG modified. The only baseline changes are the iPad.fillsColumnre-record and the rename below.snapshotBeginnerCovered_regularCapped_light(new) hosts the same view at the same 1032×1376 /.regularsize but injects.cappedDetailPane; its baseline is the old iPad PNG moved byte-for-byte (221020 bytes, same blob) and it passes without re-recording — the Mac rule renders pixel-identically at a size where both caps bind.RegularBoardContainerTestspins the platform default; mutating the#elsebranch to.fillsColumngoes red (Expectation failed: (RegularBoardContainer.platformDefault → .fillsColumn) == .cappedDetailPane), green after revert.iPad baseline (rendered vs rendered, base 34c3b03 → head)
Board-iPad-light-beginner-coveredat 1032×1376: base = 600 pt cap inside a 900 pt centred column (board 592, top-aligned); round 1 = column 684 × 1344, board 682 square, centred vertically (the review BLOCKER); round 2 = same board flush with the column top, level with the HUD. Three-way composite:<scratchpad>/ev/ipad-baseline-3way.png.Store frames
mise run store:screenshots build-ascspecregenerated 168 slots, 0 failures; exactly the 7minesweeper/ipad-13/*/03-board.pngframes changed, every other frame byte-identical. Regenerated again after round 2 (168 slots, again only those 7 changed). Three-way side by side — pre-#1101 / round-1 centred / round-2 top-aligned:<scratchpad>/ev/store-frame-3way.png(round 2 puts the board back at the card top, under the HUD).mise run scan:store_frame_drift→PASS, noframe-drift-verifiedlabel needed. The Minesweeper03-boardframes stay #1054-affected (Liquid Glass G4 not renderable by the snapshot pipeline).Simulator re-measurement (#1101 method: grid = a11y-tree union of
MinesweeperCellButtonframes, viewport = pixel-scanned visible cell extent; iPad Pro 11-inch (M5), iOS 26.4, private sim; base and head measured on the same device)Base rows reproduce #1101's table exactly (same numbers on my device). Landscape cells are 45 (not the 44 I predicted) because the column top is y = 48, giving 750 pt of height; the portrait scroll branches start at y = 52 because of the 4 pt scroll-content margin. Round-2 numbers were measured after f73289d; Intermediate, Expert and all landscape rows are unchanged from round 1 except Beginner landscape moving 2.5 pt up (top-aligned). Three-way composites per configuration:
<scratchpad>/ev/compare-<difficulty>-<orientation>.png.Pinch-zoom (#815) on the new portrait Intermediate layout: works. XCUITest
pinch(withScale: 0.4)on a cell → a11y cell side 68 → 34 pt (the #815 0.5× floor), grid 1118² → 574² centred in the viewport at (36,324)–(610,898); ~13 of 16 columns visible at the overview instead of ~7. Evidence:<scratchpad>/ev/head-ipad-zoom/intermediate-portrait{,-zoomed}.png+-ax.json.Evidence dir (local, gitignored):
<scratchpad>/ev/—base-ipad/,head-ipad/,base-iphone/,head-iphone/(*.png,*-ax.json,probe-xcuitest.log),Probe1101.swift.archive(throwaway XCUITest probe, never committed),tools/.Review
Code Reviewer mandatory (>50 LOC): two independent reviews (sonnet + haiku) — round 1 (a137f69): haiku APPROVE, sonnet REQUEST-CHANGES with the Beginner-centring BLOCKER above (ruled and fixed in round 2); sonnet re-review of f73289d pending, result posted as a comment. Fresh-context T6 acceptance: posted as a PR comment.
🤖 Generated with Claude Code