Skip to content

fix(board): let the iPad regular-width Minesweeper board viewport fill its column - #1106

Merged
wei18 merged 2 commits into
mainfrom
session/cryptic-starling-hh3q
Sep 24, 2026
Merged

wei18 merged 2 commits into
mainfrom
session/cryptic-starling-hh3q

Conversation

@wei18

@wei18 wei18 commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Closes #1101

What

MinesweeperBoardView.macLayout is selected by horizontalSizeClass == .regular, so iPad inherited the Mac detail-pane caps (macOuterMaxWidth 900 centring the layout, macBoardMaxSide 600 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 cellSizing ladder; 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) + \.regularBoardContainer environment key (same shape as MinesweeperCellTokens.swift).
  • MinesweeperBoardView.swift: macLayout outer maxWidth and macBoardColumn maxWidth/maxHeight are 900 / 600 under .cappedDetailPane and .infinity under .fillsColumn. compactLayout and cellSizing untouched.
  • Round 2 (f73289d, PM ruling on the review BLOCKER): a width-bound board (Beginner 9×9, always the .fitted branch) 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 .fitted branch frame; fittedAlignment(for:) maps .fillsColumn → .top, .cappedDetailPane → .center; compact calls boardGrid() (default .center). Pinned by RegularBoardContainerTests; mutating the default to .top turns 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 .fillsColumn branch to .center turns the unit test red.
  • Sudoku's identical 960 / 640 cap is tracked as fix(board): Sudoku regular-width board inherits the 960/640 Mac detail-pane caps on iPad #1105 (out of scope by PM ruling).

iPhone and macOS did not move (mechanical proof)

  • Full MinesweeperKit suite 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 status after the run: no iPhone or Mac PNG modified. The only baseline changes are the iPad .fillsColumn re-record and the rename below.
  • snapshotBeginnerCovered_regularCapped_light (new) hosts the same view at the same 1032×1376 / .regular size 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.
  • RegularBoardContainerTests pins the platform default; mutating the #else branch to .fillsColumn goes red (Expectation failed: (RegularBoardContainer.platformDefault → .fillsColumn) == .cappedDetailPane), green after revert.

iPad baseline (rendered vs rendered, base 34c3b03 → head)

Board-iPad-light-beginner-covered at 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-ascspec regenerated 168 slots, 0 failures; exactly the 7 minesweeper/ipad-13/*/03-board.png frames 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, no frame-drift-verified label needed. The Minesweeper 03-board frames stay #1054-affected (Liquid Glass G4 not renderable by the snapshot pipeline).

Simulator re-measurement (#1101 method: grid = a11y-tree union of MinesweeperCellButton frames, viewport = pixel-scanned visible cell extent; iPad Pro 11-inch (M5), iOS 26.4, private sim; base and head measured on the same device)

Configuration Base cell Base viewport Head cell Head grid Head viewport Hidden R / B Leftover column below viewport
portrait Beginner 9×9 52 484×484 at y 106–590 (frame 486×600) 52 484×484 at y 48–532 (flush with the column top; round 1 had it centred at y 369–853) 484×484 0 / 0 642 below (width-bound board, cannot grow)
portrait Intermediate 16×16 44 481.5×595.5 68 1118×1118 481.5×1117.5 636.5 / 0.5 ≈4
portrait Expert 16×30 44 481.5×595.5 68 2098×1118 481.5×1117.5 1616.5 / 0.5 ≈4
landscape Beginner 64 592×592 (frame 600) 81 745×745 at y 48–793 745×745 0 / 0 ≈5
landscape Intermediate 44 595.5×595.5 45 750×750 749.5×749.5 (no scrolling) 0 / 0 ≈0
landscape Expert 44 595.5×595.5 45 1408×750 857.5×749.5 550.5 / 0.5 ≈0
iPhone 17 Pro Expert (control) 44 397.7×615.7 44 1378×734 397.7×615.7 980.3 / 118.3 ≈0 (unchanged)

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

wei18 and others added 2 commits September 24, 2026 10:58
…#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>
@wei18

wei18 commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

Code review — round 1 (a137f69), two independent reviewers

haiku → 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 cappedDetailPane PNG share sha1 dd773643f6a36922bbd447ddd292ce1f356cc4a2; issue refs in comments exist; commit stat matches (14 files, +140/−5).

sonnet → REQUEST-CHANGES, 1 BLOCKER (evidence: rendered git show of the ipad-13 en/ja 03-board frames, base vs head): the ASC frame renders Beginner, which is width-bound in every regular layout, so under .fillsColumn the same board was centred in the full column — the blank moved from below the board to above and below, and the store frame gained a large HUD-to-board gap (board top ≈ y 760 → ≈ 1400 of 2752 px). Also verified as correct: capped path byte-identical, no leakage into compact / cellSizing / Sudoku / shared modules, RegularBoardContainerTests mutation reproduced red in a throwaway tree, scan:store_frame_drift --base 34c3b032 PASS, targeted suites 8/8 green. MINOR: #if os(iOS) branch of platformDefault is not exercised by the macOS swift test host (known repo limitation, not introduced here); comment grammar "it had" → "they had".

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 .fillsColumn only → implemented in round 2 (f73289d). Sonnet re-review of f73289d and the fresh-context T6 acceptance follow as separate comments.

@wei18

wei18 commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

Code review — round 2 (f73289d), sonnet re-review → APPROVE

Verified by the reviewer from the ref (not from the commit message):

  • boardGrid(fittedAlignment: = .center) only touches the .fitted branch frame (MinesweeperBoardView.swift:1041, :1072); both scroll branches untouched. Compact call site :515 is boardGrid() (default .center); capped call site :608 goes through fittedAlignment(for:) (:911-916), .cappedDetailPane → .center. Only two call sites exist.
  • Store frame regression resolved, by pixel bbox on ipad-13/en/03-board.png: board top y = 786 (34c3b03) → 1406 (a137f69, the BLOCKER) → 784 (f73289d); the extra height from lifting the 900 cap now lands below the board. en / ja / the plain snapshot inspected.
  • Capped baseline PNG sha1 dd773643f6a36922bbd447ddd292ce1f356cc4a2 in f73289d, and not in the commit's change list; change list = MinesweeperBoardView.swift, RegularBoardContainerTests.swift, iPad .fillsColumn snapshot, 7 store frames.
  • Mutations reproduced in a throwaway tree by the reviewer: default .center → .top turns the iPhone board snapshots red (snapshotBeginnerCovered_iPhone_light/dark, snapshotAdsEnabledUnloadedSlot_iPhone_light/dark in the filtered file; the two explicit-parameter iPad/capped tests stay green as expected); fittedAlignment(for: .fillsColumn) → .center turns fittedAlignmentMapsPerContainer red. Clean run of the filtered suites 9/9 green.
  • mise run scan:store_frame_drift --base 34c3b032 PASS on f73289d.

MINOR (cosmetic, carried from round 1, not blocking): MinesweeperBoardView.swift:591 "so both keep the margin it had" → "they had".

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).

@wei18

wei18 commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

T6 驗收(fresh-context)

repo wei18/Sudoku,PR #1106,branch session/cryptic-starling-hh3q,base 34c3b032 → head f73289d8。以下逐條用 head/base 內容與 throwaway worktree 測試結果驗證,不採信 PR body 的宣稱。

# 項目 判定 證據
1 PR 標題 ^[a-z0-9] + body 含 Closes #1101 + 無「does not fix」措辭 PASS 標題 fix(board): let the iPad regular-width Minesweeper board viewport fill its column;body 開頭 Closes #1101;grep `does not (fix
2 diff 檔案集合僅限預期範圍 PASS git diff 34c3b032...f73289d8 --stat:14 files = 2 Sources (MinesweeperBoardView.swift, RegularBoardContainer.swift) + 3 Tests (ASCScreenshotEmitTests.swift, MinesweeperBoardSnapshotTests.swift, RegularBoardContainerTests.swift) + 2 __Snapshots__ PNG + 7 ipad-13/*/03-board.png。無 Sudoku / GameShellKit / GameAppKit / 其他 PNG
3 capped 新 baseline = 舊 iPad PNG 位元全同 PASS git show 34c3b032:…snapshotBeginnerCovered_iPad_light….png | shasum 與 git show f73289d8:…snapshotBeginnerCovered_regularCapped_light….png | shasum 均為 dd773643f6a36922bbd447ddd292ce1f356cc4a2(與 PR body 宣稱值逐字相符)
4 throwaway tree 全量 swift test 綠 + 無 PNG 被重錄 PASS Test run with 416 tests in 71 suites passed(與 PR round2 數字相符);跑完後 git status --short 空白,無 flake,無需重跑
5 突變 A:platformDefault #else → .fillsColumn PASS --filter RegularBoardContainerTests 紅:Expectation failed: (RegularBoardContainer.platformDefault → .fillsColumn) == .cappedDetailPane;已還原並確認與 head 零差異
6 突變 B:boardGrid 預設 fittedAlignment → .top PASS --filter 'MinesweeperBoardSnapshotTests|MinesweeperBoardRevealedSnapshotTests':Test run with 14 tests in 2 suites failed with 10 issues,10 個唯一 iPhone 測試轉紅(snapshotBeginnerCovered_iPhone_{light,dark}、snapshotMidReveal_iPhone_{light,dark}、snapshotFlagged_iPhone_{light,dark}、snapshotMineHit_iPhone_{light,dark}、snapshotAdsEnabledUnloadedSlot_iPhone_{light,dark});iPad/regularCapped 兩測試仍綠(不受影響,符合預期,因它們顯式傳入 fittedAlignment(for:));git status --short 只有原始碼一行髒,無 PNG 被動;已還原。小落差:PR body 說「11 個 iPhone/iPad 快照轉紅」,實測是 10 個(全部 iPhone,iPad 兩者不受影響)——數字對不上但方向與機制正確,屬 body 誤植
7 突變 C:fittedAlignment(for:) 的 .fillsColumn 分支 → .center PASS --filter RegularBoardContainerTests 紅:Expectation failed: (MinesweeperBoardView.fittedAlignment(for: .fillsColumn) → …) == (.top → …);已還原並確認與 head 零差異
8 capped 路徑逐字等價 + compact 路徑零改動 PASS diff base↔head 顯示:.cappedDetailPane 下 macOuterFrameMaxWidth 恆等於 Self.macOuterMaxWidth(900)、macBoardFrameMaxSide 恆等於 Self.macBoardMaxSide(600)、fittedAlignment(for: .cappedDetailPane) 恆為 .center — 與 base 硬編碼值逐字相同;compactLayout 唯一改動是呼叫語法 boardGrid → boardGrid()(新增註解),無語意變化
9 mise run scan:store_frame_drift --base 34c3b032 PASS mise trust 後執行:PASS [store_frame_drift]: no mapped baseline drifted…(mapped baseline 有變但 screenshots-ascspec/ 同批變動,視為已涵蓋)
10 目視 ipad-13/en/03-board.png base vs head PASS(符合 round2 描述) 兩張圖皆棋盤貼齊卡片頂部 HUD 下方,無大片空白夾在棋盤上方;head 棋盤略大(對應 PR 敘述 592pt→682pt),視覺上與「Option A + top-align」一致,非 round1 的置中版本
11 可證偽數字抽驗 PASS round1 git diff 34c3b032...a137f69b --stat = 14 files(與 body「14 files」相符);round2 git show --stat f73289d8 = 10 files(round2 自身增量,疊加 round1 後累計仍是 14 files,與第 2 項一致);416/71 見第 4 項

總判定:ACCEPT-with-findings

Findings(不影響 ACCEPT,但值得記錄):

  • 第 6 項:PR body 敘述「mutating the default to .top turns 11 iPhone/iPad board snapshots red」,實測轉紅的是 10 個 test,且全部是 iPhone(iPad 兩個測試都因為顯式傳參而不受影響,這本身是正確行為)。機制驗證通過,純粹是 body 裡的計數誤植,建議作者更正措辭裡的「11」。

我沒驗的部分(依 PR body,未重跑,標示信心度):

  • 模擬器實測表格(iPad Pro 11" (M5) 各難度/方向的 cell/grid/viewport 數字、pinch-zoom 68→34pt 驗證):無法在此環境重跑 idb/XCUITest 對已合併 sim 的即時量測,未驗,依 PR body 陳述,信心度中等(機制與程式碼改動方向一致,但精確數字未獨立複現)。
  • mise run store:screenshots build-ascspec 「168 slots, 0 failures,只有 7 個 03-board 變」:只驗證了最終 drift-scan 結果(PASS)與最終 7 張 PNG 的檔案集合(第 2 項),未重跑該 mise task 本身重新產生全部 168 張並逐一比對,信心度高(drift scan 是獨立於此宣稱的第二重證據)。
  • Code Reviewer round-1/round-2 對話紀錄本身(sonnet REQUEST-CHANGES → round2 修正 → sonnet re-review pending):未在此驗收中重新審視 review thread 內容,只確認了 round2 commit 的程式碼確實對應 body 描述的修正(第 8 項)。

🤖 Generated with Claude Code

@wei18

wei18 commented Sep 24, 2026

Copy link
Copy Markdown
Owner Author

PM merge record, head f73289d8. Gate satisfied: two code-review rounds plus a fresh-context acceptance (ACCEPT-with-findings, the single finding being a count correction already applied to the PR body).

Owner ruling: Option A — the iPad board fills the column. PM rulings: seam S2 (an environment value, not #if os(iOS)), and a fitted board is top-aligned under .fillsColumn.

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 03-board, the one frame the store shows, renders Beginner. Measured board top: 786 px on base → 1406 px centred → 784 px top-aligned. The PM compared the three frames directly: round 1 is worse than what ships today; round 2 restores the board under the HUD, larger than before.

Verified independently:

  • The Mac detail pane is untouched, by blob identity: the new …cappedDetailPane.png baseline and origin/main's Board-iPad-light-beginner-covered.png are the same git object 678fe533…. Not an equal-hash claim — the same object.
  • 0 iPhone or macOS baselines moved. The only snapshot changes are the iPad Beginner baseline (modified) and the capped baseline (added).
  • Mutations reproduced by reviewers, not only by the author: flipping the alignment default to .top turns 10 iPhone snapshots red while the two explicit-parameter tests stay green; flipping platformDefault's #else branch to .fillsColumn turns RegularBoardContainerTests red on the macOS host.
  • Simulator measurements: portrait Intermediate/Expert viewport 481.5×595.5 → 481.5×1117.5 with about 4 pt of leftover column (was ~522 pt); landscape Intermediate 749.5² with no scrolling at all; portrait Beginner y 48–532, flush under the HUD; iPhone Expert control identical to base. feat(minesweeper): board pinch-to-zoom + scroll-indicator clearance from board edge #815 pinch-zoom still reaches the 0.5× floor on the new portrait layout (68 → 34 pt cells, ~13 columns in view).
  • Full MinesweeperKit 416 tests / 71 suites green with a clean tree afterwards; 7 ipad-13 03-board frames regenerated out of 168 slots; scan:store_frame_drift passes without the escape-hatch label. CI 14/14.
  • No close, fix or resolve keyword appears before any #NNN in the branch's commit messages, so nothing is auto-closed on squash beyond Closes #1101.

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.

@wei18
wei18 merged commit 93bdc18 into main Sep 24, 2026
14 checks passed
@wei18
wei18 deleted the session/cryptic-starling-hh3q branch September 24, 2026 04:45
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

fix(board): Minesweeper regular-width board viewport is capped to a 600 pt square and leaves the iPad board column empty in both orientations

1 participant