Skip to content

Stop re-rendering unchanged board views on every move - #72

Merged
austin-smith merged 4 commits into
mainfrom
prune-board-views-on-unrelated-moves
Jul 15, 2026
Merged

austin-smith merged 4 commits into
mainfrom
prune-board-views-on-unrelated-moves

Conversation

@austin-smith

Copy link
Copy Markdown
Owner

What Changed

  • New BoardSnapshots.swift: SelectionSnapshot and TopRowSnapshot — value projections of the observable session, captured once per ContentView body pass — plus FoundationPlaceholder for the empty-foundation art.
  • Every board view (tableau piles and row, stock, waste, tableau stock, foundations, free cells, Pyramid discard and board, TriPeaks board, Canfield reserve and board row, the top-row dispatcher, and all ten per-variant top rows) now renders from those slices instead of reading the observable session, and prunes through a manual Equatable that covers every rendered input. The session reference stays for tap/drag wiring only and participates in == by identity.
  • New pure GameRules.canSelectTableauCards(_:within:variant:) so pile rendering never calls the session's rule; Canfield's whole-pile transfer check reduces exactly to a count comparison for suffix runs.
  • WasteView captures its top card's tilt at init and compares it in == — the invalid-drop return rerolls that tilt without touching pile contents, and a pruned waste would otherwise visibly re-tilt on reveal.
  • Interaction value types (DropTarget et al.) marked nonisolated so the nonisolated == implementations can compare them.
  • New tests: snapshot derivation parity against the live session, pickup-rule parity across every suffix of every pile over seeded deals of all ten variants, and Equatable regression guards (prune on fresh gesture closures; re-render on each perturbed rendered input, including the waste tilt reroll).

Why

Follow-up to #70/#71 and the last planned piece of the lag work: the session is @Observable with one coarse state property, so every board view that read viewModel.state.* re-evaluated on every move — a TriPeaks draw re-ran all 28 peak slots, a Klondike move re-ran every pile and foundation. Only CardView pruned. With value slices and per-view Equatable, a move now re-evaluates only the views whose rendered data actually changed.

Validation

  • macOS and iOS builds clean; full unit suite green after each commit's stage, including the three new test files.
  • Simulator interaction sweep across every converted view's event wiring: Klondike stock draw, waste tap, tableau tap, invalid-drag return, and hint ring with destination highlight; TriPeaks peak play; Pyramid draw with discard; Canfield reserve tap; FreeCell drag-into-cell and tap-out-of-cell; Spider ten-card deal.
  • Structural asserts: no @Bindable/session reads remain under Views/ except DragOverlayView (renders the live drag by design) and the StatisticsView sheet.
  • Zero visible behavior change: layout, drop-target frames, accessibility labels/traits, and all animations are byte-identical in the diff apart from the data plumbing.

selectionsnapshot and toprowsnapshot are value projections of the
observable session, captured once per body pass so board views can
render from equatable slices instead of subscribing to the whole
state. the pure canselecttableaucards mirrors the session rule for
suffix runs — canfield's whole-pile transfer check reduces to a count
comparison there — and parity tests pin the two together for every
suffix of every pile across seeded deals of all ten variants.
tableaupileview and tableaurowview take the pile arrays, variant, and
selection snapshot by value and prune through manual equatable — the
session reference stays for tap wiring only and participates in == by
identity. the interaction value types become nonisolated so the
nonisolated == can compare drop targets. equatable regression tests
assert pruning survives fresh gesture closures and breaks on every
rendered input.
stock, waste, tableau stock, foundation, free cell, discard, the
pyramid and tripeaks boards, canfield's reserve and board row, the
top-row dispatcher, and all ten per-variant top rows now render from
toprowsnapshot and selectionsnapshot slices, so a move that leaves a
view's slice unchanged skips its body entirely. wasteview captures its
top card's tilt at init and compares it in == because the invalid-drop
return rerolls that tilt without touching the pile's contents. the
per-variant rows carry no equatable of their own — the dispatcher's ==
gates the whole row.
@cursor

cursor Bot commented Jul 15, 2026

Copy link
Copy Markdown

Bugbot is not enabled for your account, so this pull request was not reviewed.

Enable Bugbot in the Cursor dashboard to get automatic reviews on future PRs.

@austin-smith austin-smith changed the title Prune board views on unrelated moves Stop re-rendering unchanged board views on every move Jul 15, 2026

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: 5d66450e2d

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment thread ComputerSolitaire/Views/Shared/BoardViews.swift
codex review of #72 reasonably worried that toprowview's pruning could
swallow the waste-return tilt reroll before wasteview's topcardtilt
check runs. it cannot: cardview reads the tilt binding in its own
body, and binding reads register direct per-reader dependencies that
survive ancestor pruning — verified on the exact repro. record that
contract where the next reader will look for it.
@austin-smith

Copy link
Copy Markdown
Owner Author

@codex review

@chatgpt-codex-connector

Copy link
Copy Markdown

Codex Review: Didn't find any major issues. 👍

Reviewed commit: 605c64f0a0

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

@austin-smith
austin-smith merged commit 51b09ad into main Jul 15, 2026
2 checks passed
@austin-smith
austin-smith deleted the prune-board-views-on-unrelated-moves branch July 15, 2026 05:30
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.

1 participant