Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions Shared/Periscope/TODOs.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,7 @@ here.
- feat(PeriscopeTools) [needs-design]: Inspect-by-object is scope-granular, not instance-granular. `.logInspectable(_:)` keys the badge/inspector to a `Log`'s *scope*, so tagging a list row (Where tags `EvidenceRow` with `WhereLog.evidence`, `LocationStatusRow` with `WhereLog.session`) surfaces the whole scope's recent events, not that one row's. Events already carry `externalID` for object correlation, but the inspector can't filter by it — a per-instance child scope (blocked on the `LogContextProviding` parent-hierarchy P0) or an `externalID`-scoped inspect entry would make true row-/object-level inspection work. (pr#94 review)
- design(PeriscopeCore) [needs-design]: No eager store handle — `PeriscopeStore.make` being `async` forces an "optional store, observe until it lands" dance on consumers. Where exposes an `Optional<PeriscopeStore>` on `WhereModel` that stays `nil` until the bootstrap `Task` completes, and `RootView` has to watch the transition (`.onChange` of the store identity) to wire the viewer/inspector/alerter. A synchronous pending-store handle (usable immediately, resolves in the background) or an `await`-readiness accessor would remove the optional-and-observe boilerplate every app repeats. (agent)
- test(PeriscopeTools) [quick-win]: Add the missing 1:1 tests for the small `*+Display` extensions. (audit 2026-07-26)
- test(PeriscopeTools) [needs-design]: broken-snapshots — replace the hosting smoke tests with image snapshots. Eighteen tests across nine files assert nothing but "the hosted view reached a window": `#expect(await waitUntil { host.view.window != nil })` in `LogEventListTests.swift:30`, `:41`, `LogHierarchyViewHostingTests.swift:23`, `:34`, `PeriscopeViewerHostingTests.swift:29`, `:42`, `ScopeEventsViewHostingTests.swift:25`, `:38`, `SpanHistoryViewHostingTests.swift:23`, `:34`, `SpanTreeViewHostingTests.swift:26`, `:37`, and the `try waitFor { host.view.window != nil }` spelling in `LogInspectableHostingTests.swift:25`, `:38`, `:50`, `LogTraceViewHostingTests.swift:23`, `OpenSpansViewHostingTests.swift:27`, `:38`. The predicate restates what `show`/`showHosted` already guarantee, so each test proves only that construction didn't crash — never what rendered, which is the part the elaborate seeding sets up (`LogHierarchyView`'s outline, the comfortable density `PeriscopeViewerHostingTests` injects, the "No Events" state `ScopeEventsViewHostingTests` documents at `:29`). The repo convention is now that an image bundle, not a hosting smoke test, owns "does this screen render" (see [`Where/WhereUI/AGENTS.md`](../../Where/WhereUI/AGENTS.md#testing) and the WhereUI suite that replaced its own smoke tests). Convert them to a `PeriscopeToolsSnapshotTests` bundle over the same seeded stores, keeping any assertion that isn't the window check and deleting the files left empty. The bundle is the prerequisite tracked in the root [`TODOs.md`](../../TODOs.md) broken-snapshots item, which also wants `PeriscopeViewer`'s snapshot moved out of WhereUI — land them together. (Note the two `window != nil` checks in `Shared/LifecycleKit/Tests/` are *not* in scope: they assert the hosting helper's own lifecycle contract, which is the one place the check is the point.) (pr#101 review)

# Completed issues

Expand Down
1 change: 1 addition & 0 deletions Shared/SwiftDataInspector/TODOs.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,5 +9,6 @@ here.
## P1s (Should do)
- test [quick-win]: The bare-`PersistentIdentifier` branch of relationship resolution (`SwiftDataReflection.swift:132`) is untested — the existing relationship test faults a materialized model, which takes the other path. Add a fixture test over an unmaterialized slot. (audit 2026-07-26)
- test [needs-design]: `Tests/SwiftDataInspectorTests.swift` is one 759-line file covering 13 sources, against the 1:1 convention. Split it by concern, optionally adding hosted UI smoke tests. (audit 2026-07-26)
- test [needs-design]: broken-snapshots — don't add those hosting smoke tests; take image snapshots instead. The repo convention is now that a `*SnapshotTests` bundle owns "does this screen render", and a "hosts without crashing" test asserts nothing about the result (see the conversion item in [`Shared/Periscope/TODOs.md`](../Periscope/TODOs.md)). This module has no snapshot bundle yet — adding one is tracked in the root [`TODOs.md`](../../TODOs.md) broken-snapshots item, which also wants `SwiftDataInspectorSnapshotTests` moved here out of `WhereUI`. Do the split against that bundle rather than seeding smoke tests it would immediately replace. (pr#101 review)

# Completed issues
3 changes: 3 additions & 0 deletions TODOs.md
Original file line number Diff line number Diff line change
Expand Up @@ -98,6 +98,9 @@ inbox rather than here.
## P1s (Should do)

## P2s (Nice to have)
- test(WhereUI) [needs-design]: broken-snapshots — two `WhereUISnapshotTests` suites pin views WhereUI doesn't own, flagged `[Fix later]` on PR #101. Each belongs to the module that owns the view, so both span WhereUI and a shared module and sit here rather than in either file. Both are blocked on the same prerequisite: neither shared module has a snapshot bundle yet, and adding one means a `*SnapshotTests` target plus its own standalone scheme and a `snapshot` CI job, following the `WhereUISnapshotTests` pattern in `Project.swift` — image bundles are deliberately kept out of `Stuff-iOS-Tests`. (pr#101 review)
- test(PeriscopeTools) [needs-design]: Move the `PeriscopeViewer` snapshot into Periscope. `Where/WhereUI/SnapshotTests/PeriscopeViewerSnapshotTests.swift` captures a PeriscopeTools view, and its own doc comment (`:8`–`:11`) admits it lives in Where only because that's where the app reaches it; it already builds its own frozen store (`:34`), so nothing but the `whereBroadwayRoot()` styling ties it here. Pairs with the hosting-test conversion in [`Shared/Periscope/TODOs.md`](Shared/Periscope/TODOs.md), which needs the same bundle — do them together. (pr#101 review)
- test(SwiftDataInspector) [needs-design]: Move the `SwiftDataInspector` snapshot into SwiftDataInspector. `Where/WhereUI/SnapshotTests/SwiftDataInspectorSnapshotTests.swift` reaches through `PreviewSupport.previewServices()`, a seeded manual day, and a real `WhereSession` just to obtain a configuration (`:12`–`:21`) — so an app-agnostic module is snapshotted through Where's store. The module's own in-memory `@Model` fixtures would render it without the app, matching how its unit tests already work. (pr#101 review)
- perf(StuffTestHost) [needs-design]: Two loose ends in the shared test host, both reaching the root Tuist manifest, which is why they sit here rather than in a StuffTestHost file. The WhereCore-always-embedded build trade-off is documented and verified load-bearing at `Project.swift:256` — decide whether to keep documenting it or split the host so unrelated bundles don't pay for it. Separately, the scene configuration name is spelled twice, in `Shared/StuffTestHost/Sources/AppDelegate.swift:11` and `Project.swift:244`, so the two can drift silently. (audit 2026-07-26)

# Completed issues
9 changes: 9 additions & 0 deletions Where/TODOs.md
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,15 @@ The item format and the placement rule live in the root
- refactor(WhereCore): Persist a **minReaderVersion** per entity, not just a version. Additive (expand/contract) changes leave it low so old builds keep reading via the retained old field (tolerant reader); only a genuinely forward-incompatible change bumps it. Readers exclude entities whose `minReaderVersion > appVersion` and surface a "some data needs a newer app" warning — the only case that actually needs exclusion. (agent)
- refactor(WhereCore): Durable write-back is **read-repair**, decoupled from read correctness: opportunistically (batched, on `.NSPersistentStoreRemoteChange` + launch) rewrite stale records to the current version and stamp it, so old builds can honor exclusion. Transforms must be deterministic + commutative so two devices healing the same record via CloudKit converge (LWW-safe). (agent)
- design(WhereCore): Open question — the exclusion UX, where an older device progressively hides days a newer device has touched, needs a deliberate warning surface, not a silent drop. (agent)
- fix(WhereUI) [needs-design]: broken-snapshots — the snapshot suite pinned genuinely broken renderings as references, flagged with `[Fix later]` review comments on PR #101 and merged anyway to land the suite. These are not flaky captures (those have their own ledger below) — each is a faithful, reproducible image of something actually wrong, so re-recording is never the fix. Fix the view, the capture frame, or the pipeline as each item says, then re-record just that reference under `Where/WhereUI/SnapshotTests/__Snapshots__/`. Most cluster on the accessibility axes `.screenDefaults` added — the ax5 Dynamic Type and VoiceOver-annotated configurations that nothing rendered before this suite existed. (pr#101 review)
- fix(WhereUI) [needs-design]: broken-snapshots: the VoiceOver-annotated calendar captures are blank. `CalendarContentViewSnapshotTests/calendarContent.WithData_iPhone_accessibility.png` (66 KB) and `..._iPad_accessibility.png` (171 KB) are solid white inside their border, against 205 KB–2.2 MB for every other screen-sized `_accessibility` reference — so it's specific to `CalendarContentView`, not the annotation pipeline. `AccessibilitySnapshotViewController` renders the wrapper with `viewRenderingMode: .drawHierarchyInRect` (`Shared/SnapshotKitTesting/Sources/AccessibilitySnapshotViewController.swift:36`), and `parseAccessibility()` claims failures "surface loudly rather than producing a blank image" (`:44`) — whatever this is slips past every `ImageRenderingError` guard there. Two configurations' worth of accessibility coverage currently assert a blank image, so a real regression in them can't fail. (pr#101 review)
- fix(WhereUI) [quick-win]: broken-snapshots: the calendar day grid breaks at accessibility Dynamic Type. Every two-digit date truncates to its first digit — the 10th–31st render as "1", "2", or "3" — because the day number is clamped to a fixed square (`CalendarContentView.swift:436`, `.frame(width: calendar.day.numberSize, height:)`), and the weekday header row wraps mid-word ("Sun" over two lines, "Wed" over three) because each symbol is a plain `Text` in an equal-width grid column (`:282`). Both show in `calendarContent.WithData_iPhone_ax5.png`; the digit truncation also hits `..._iPad_ax5.png`, where the extra width goes to inter-column gaps instead of the numbers. Showing "1" where the date is 10 is wrong content, not merely tight layout. (pr#101 review)
- fix(WhereUI) [quick-win]: broken-snapshots: the presence timeline's leading accent bar doesn't scale with Dynamic Type. `StintRow` sizes its `Capsule` from the fixed stylesheet tokens `timeline.accentWidth`/`accentHeight` — 4×34 (`Sources/Shared/WhereStylesheet.swift:719`, `:720`) applied at `PresenceTimelineList.swift:54`–`:57` — so at ax5 it stays a 4pt stub beside ~40pt text (`presenceTimeline.WithData_iPhone_ax5.png`, `..._iPad_ax5.png`). Scale it with `@ScaledMetric`, or derive it in `WhereStylesheet.init(context:)` the way the day-grid tap targets already do. (pr#101 review)
- fix(WhereUI) [needs-design]: broken-snapshots: the presence timeline row squishes horizontally at ax5 instead of restacking. `StintRow` keeps accent bar, emoji, name/date stack, and day count on one `HStack` line at every type size (`PresenceTimelineList.swift:51`), so `presenceTimeline.WithData_iPhone_ax5.png` renders "California" hyphenated over three lines beside a two-line "148 days". Switch to a `ViewThatFits` / `AnyLayout` that stacks vertically at accessibility sizes. (pr#101 review)
- fix(WhereUI) [needs-design]: broken-snapshots: `YearView` overflows horizontally at ax5. In `year.Loaded_iPhone_ax5.png` the month title reads "nuary", the day grid is clipped on both edges, and the Calendar/Timeline pill runs off the trailing edge. The suspect is `YearModePicker`, a fixed-width `HStack` of two labelled segments pinned as a bottom `safeAreaInset` (`YearView.swift:36`–`:38`, `:79`), which is wider than the screen at ax5. Confirm the oversized inset is what widens the layout beneath it, then make the picker fit at accessibility sizes (icon-only, wrapped, or scrollable). (pr#101 review)
- fix(WhereUI) [quick-win]: broken-snapshots: the Resolve toolbar badge sits awkwardly on the iOS 26 glass toolbar button. `ResolveToolbarLabel` hand-rolls the badge as a red `Capsule` overlaid on the `checklist` symbol and pushes it out with a fixed `.offset(x: spacing.small, y: -spacing.small)` (`LocationsView.swift:210`–`:217`), landing it half outside the button's own glass capsule — visible in `root.LoggedIn_iPhone.png`. Use SwiftUI's `.badge()` on the toolbar item, or offset against the resolved chrome rather than a fixed spacing token. (pr#101 review)
- test(WhereUI) [quick-win]: broken-snapshots: the Resolve sheet's ax5 reference is cut off mid-content. `resolution.WithIssues_iPhone_ax5.png` slices the second issue card's subtitle at the frame's bottom edge, because the case captures at the fixed `.iPhone` device frame via `.screenDefaults` (`ResolutionView.swift:221`), which clips rather than growing. `SnapshotConfiguration.Frame.fullContent` exists for exactly this (see `CalendarContentView.swift:589`) — add a full-content ax5 case so the whole sheet is pinned instead of its first screenful. (pr#101 review)
- fix(WhereUI): broken-snapshots: `locations.Loaded_iPad.png` bakes in raw inflection markup — the Elsewhere card's subtitle renders literally as `^[3 region](inflect: true)`. This is the `locations.elsewhere.subtitle` P1 filed above, now pinned as a reference; recorded here so the image isn't mistaken for correct output, and so that reference is re-recorded when the fix lands. (pr#101 review)

## P2s (Nice to have)
- feat(WhereUI) [needs-design]: Give the app a branded launch screen. `UILaunchScreen` is an empty dictionary (`Project.swift`), so the pre-main frame is plain white. Measured from a fresh-install simulator recording, a first run reads as ~1.7s of white → ~0.25s of the dark `LaunchSplashView` → the light onboarding screen, so the splash registers as a quarter-second dark blip between two light screens rather than as the app opening. A launch screen matching the splash's background + icon would make that continuous. Note this is the right layer to fix it at: the splash's own `minimumSplashDuration` hold deliberately gates only the `.ready` reveal, not a gate transition like onboarding, so lengthening the hold would just delay interactive UI. (agent)
Expand Down
Loading