import SwiftUI #if DEBUG /// **Render-cost counters for the board strip — DEBUG only, and the only way a test can tell "one /// card repainted" from "the whole board rebuilt".** /// /// Ported from the pathfinder (`Kanban/Views/MasonryLayout.swift`'s `BoardRenderMetrics`), which is /// where the shape comes from: a flat bag of counters, a `reset()`, and one `count…` call at the top /// of each body being watched. What is new here is the masonry's **column** vocabulary and the /// trash column's own counter — Lanework's strip has three body levels, not two. /// /// ### What it is for /// /// The 2026-07-31 drag-performance work shipped four gates — `LaneView.==` / `CardFaceView.==` /// applied through `.equatable()` (84f909a), the resting-layout cache (a51ad75), the marquee's /// de-observation (f6105d4) and the resize hold (409f430) — and every one of them is a claim about /// *how many bodies run*. A claim like that is unfalsifiable from the outside: the board looks the /// same either way, only the frame rate differs, and a frame rate is a machine fact rather than a /// test. These counters are what turn each gate into an assertion (`BoardRenderPerformanceTests`). /// /// The reading half is RENDER-INSTRUMENTATION.md at the repo root, beside DRAG-REORDER.md: the /// Instruments 26 SwiftUI template, the Hitches thresholds, and how the drag signposts /// (`DragSignposts`) line up against these numbers. /// /// ### Why the storage is what it is /// /// `nonisolated(unsafe) static var`, exactly as the pathfinder has it. In practice every write /// happens on the main actor — SwiftUI runs body evaluation *and* `Layout` callbacks there — but the /// `Layout` conformance itself is `nonisolated`, so a main-actor-isolated counter would not be /// reachable from `MasonryLayout.sizeThatFits`. The unsafety is real and deliberately accepted: this /// type does not exist in a release build, and the alternative is a lock on the layout hot path of a /// board the whole point is to keep fast. /// /// **Nothing in the app reads these.** They are written by the bodies below and read by tests. A /// counter that branched would be instrumentation that changed the thing it measures. enum BoardRenderMetrics { /// Every `subview.sizeThatFits(…)` `MasonryLayout` actually performed — the number its /// measurement cache exists to drive down. nonisolated(unsafe) static var masonryMeasurements = 0 /// Measurement requests served from that cache instead. nonisolated(unsafe) static var masonryCacheHits = 0 /// `MasonryLayout.sizeThatFits` calls — SwiftUI probes a layout more than once per pass, which /// is the multiplier the cache absorbs. nonisolated(unsafe) static var masonrySizeThatFitsCalls = 0 /// `MasonryLayout.placeSubviews` calls. nonisolated(unsafe) static var masonryPlaceCalls = 0 /// `CardFaceView.body` evaluations — both homes, board and trash, since it is one view. nonisolated(unsafe) static var cardBodyEvaluations = 0 /// `BoardView.body` evaluations — the strip itself. /// /// Not one of the pathfinder's counters, and it earns its place by being the **discriminator**: /// "a lane re-ran" means one of two completely different things depending on whether the strip /// re-ran with it. Strip **and** lanes is a parent pass whose `.equatable()` gate did not /// suppress; lanes with the strip *still* is a direct Observation invalidation, which no gate has /// any say over (`LaneView.==`'s own note). Without this number the two are indistinguishable /// from a test, and the first diagnosis this instrumentation produced turned on exactly that /// distinction (RENDER-INSTRUMENTATION.md ▸ What the first run found). nonisolated(unsafe) static var stripBodyEvaluations = 0 /// `LaneView.body` evaluations (the pathfinder's `columnBodyEvaluations`, renamed to Lanework's /// vocabulary: a lane is the kanban column, and a *column* is an interior masonry track). nonisolated(unsafe) static var laneBodyEvaluations = 0 /// `TrashLaneView.body` evaluations — counted separately from the lanes because the column is /// not one: it renders only while shown, and it re-runs for reasons the lanes do not have. nonisolated(unsafe) static var trashLaneBodyEvaluations = 0 /// Every container body the strip draws — what "≤ N container bodies" is asserted against, so a /// regression cannot hide by moving from one counter to the other. static var containerBodyEvaluations: Int { laneBodyEvaluations + trashLaneBodyEvaluations } static func reset() { masonryMeasurements = 0 masonryCacheHits = 0 masonrySizeThatFitsCalls = 0 masonryPlaceCalls = 0 cardBodyEvaluations = 0 stripBodyEvaluations = 0 laneBodyEvaluations = 0 trashLaneBodyEvaluations = 0 } static func countStripBody() { stripBodyEvaluations += 1 } static func countCardBody() { cardBodyEvaluations += 1 } static func countLaneBody() { laneBodyEvaluations += 1 } static func countTrashLaneBody() { trashLaneBodyEvaluations += 1 } } #endif