From 542dd925d0a606bad2aeb99f603d13e9508d13db Mon Sep 17 00:00:00 2001 From: Tomas Uvira Date: Mon, 17 Aug 2026 01:04:24 +0200 Subject: [PATCH] Record being wrong about which change broke a test MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit A UI test went red in the same run that adopted three new APIs. The one touching that screen was reverted; the test still failed. A modifier order was corrected; still failed. Simulator data was wiped; still failed. Running it against a clean checkout — which should have been the first move, not the fourth — it failed there too, with none of the work present. The scrolling entry had already been rewritten to explain the fault in confident detail. That write-up was fiction, so it now says what actually happened: the API was convicted on circumstance and remains unproven here, and the one claim that survives is about its two-way binding costing invalidations. xcode-practice gets the general lesson, since it will outlive this particular test: stash and rerun before reasoning about which edit did it, and a note recording a false cause is worse than no note, because the next person believes it. Co-Authored-By: Claude Opus 5 (1M context) --- .claude-plugin/marketplace.json | 2 +- .../.claude-plugin/plugin.json | 2 +- .../apple-findings/references/scrolling.md | 26 ++++++++++++++++--- .../skills/xcode-practice/SKILL.md | 21 +++++++++++++++ 4 files changed, 46 insertions(+), 5 deletions(-) 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