diff --git a/.claude-plugin/marketplace.json b/.claude-plugin/marketplace.json index 0e9f783..a9a7e84 100644 --- a/.claude-plugin/marketplace.json +++ b/.claude-plugin/marketplace.json @@ -12,7 +12,7 @@ "name": "pdat-swift-practice", "source": "./pdat-swift-practice", "description": "Curated Apple platform findings with sources, plus practices learned from production incidents.", - "version": "0.2.0", + "version": "0.3.0", "author": { "name": "PDAT s.r.o." }, diff --git a/pdat-swift-practice/.claude-plugin/plugin.json b/pdat-swift-practice/.claude-plugin/plugin.json index bcfd7a6..7b2d5ac 100644 --- a/pdat-swift-practice/.claude-plugin/plugin.json +++ b/pdat-swift-practice/.claude-plugin/plugin.json @@ -1,6 +1,6 @@ { "name": "pdat-swift-practice", - "version": "0.2.0", + "version": "0.3.0", "description": "Curated Apple platform findings with sources, plus practices learned from production incidents.", "author": { "name": "PDAT s.r.o.", diff --git a/pdat-swift-practice/skills/apple-findings/references/scrolling.md b/pdat-swift-practice/skills/apple-findings/references/scrolling.md index e25df13..ffdfa95 100644 --- a/pdat-swift-practice/skills/apple-findings/references/scrolling.md +++ b/pdat-swift-practice/skills/apple-findings/references/scrolling.md @@ -25,9 +25,29 @@ following the offset while the user drags. to reach `proxy.scrollTo` from a `.task`. In our card list that is the filter chip strip, which scrolls the active chip into view. -**Step forward?** Modest but real. One layer of nesting disappears and the -scroll position stops being write-only. Not urgent — the existing code -works — so worth doing when that view is touched for another reason. +**Step forward? Unproven — we backed it out.** Swapping `ScrollViewReader` +for `.scrollPosition(id:)` coincided with the UI test for that strip +failing, so it looked like the culprit and was reverted. It was not: +the same test fails on a completely clean tree, with none of the change +present. Something in the environment broke that test, and the swap was +convicted on circumstance. + +The revert stands anyway, because it was never worth much here — this +strip only ever *writes* a scroll target, which is exactly what the proxy +already does. But the reasoning above is worth keeping, because being +wrong about it taught the real lesson: **when a change and a failure +arrive together, check the failure against an unchanged tree before +concluding.** Two rounds of edits went into explaining a fault that was +not there. + +One thing that does hold, independent of all that: + +- **`scrollPosition` is two-way, so it costs invalidations.** It writes to + its binding on every scroll, where the proxy only takes commands. It + therefore belongs in a small view. Ours would have sat in a screen whose + body derives a sorted, grouped card list — every drag of the strip would + have recomputed all of it. Extracting the strip into its own view was + worth keeping regardless of which scrolling API ends up inside it. **Available since.** iOS 17, so nothing blocks it at our deployment target. `onScrollGeometryChange` needs iOS 18. diff --git a/pdat-swift-practice/skills/xcode-practice/SKILL.md b/pdat-swift-practice/skills/xcode-practice/SKILL.md index 28a560a..fdedc3f 100644 --- a/pdat-swift-practice/skills/xcode-practice/SKILL.md +++ b/pdat-swift-practice/skills/xcode-practice/SKILL.md @@ -136,6 +136,27 @@ dead when it is passing. Also: do not pipe a test run through `tail` or a narrow `grep` and then diagnose from what survived — filter the saved log afterwards instead. +## Check a failure against an unchanged tree before blaming the change + +A UI test started failing in the same run that introduced three API +changes. The obvious suspect — the one touching that exact screen — was +reverted, and the test still failed; a modifier order was corrected, still +failed; the simulator's app data was wiped, still failed. Only then was +the test run against a clean checkout with none of the work present, and +it failed there too. The change had been innocent all along, and two +rounds of edits went into explaining a fault that did not exist. + +`git stash` and one run is cheaper than any amount of reasoning about +which edit did it. Do it first, not fourth. The same goes for what gets +written down afterwards: a note recording a false cause is worse than no +note, because the next person believes it. + +Corollary for UI tests: when one goes red without a code change, suspect +what the test depends on but does not control — accumulated seed data, +`@AppStorage` surviving between launches, a simulator location and the +"nearby" sort order it drives, or an element scrolled out of a lazy +container and therefore missing from the accessibility tree entirely. + ## Core Data in tests Build objects through the context's own model rather than