Skip to content

feat(SnapshotKit): support measured Duo capture geometry - #377

Merged
kyleve merged 12 commits into
mainfrom
codex/duo-snapshot-foundation
Nov 11, 2026
Merged

kyleve merged 12 commits into
mainfrom
codex/duo-snapshot-foundation

Conversation

@kyleve

@kyleve kyleve commented Oct 16, 2026 •

Copy link
Copy Markdown
Owner

Posted by an AI agent on kve's behalf.

Summary

Add explicit frame layout traits and recorded Duo profiles to the Xcode 27.2 baseline from main. Apply requested content clearance after UIKit finalizes containment and native geometry.

This layer retains the existing Where navigation. Its image references establish the new capture baseline before the adaptive app changes. Calendar month sections, day rows, and Timeline rows measure eagerly so full-content captures include the final rows; the selection and tabletop changes remain in the app PR. Eager Timeline measurement removes blank tails caused by lazy height estimates in a bottom-anchored scroll view. Settings waits for native search chrome to settle before measuring its full height. Existing scrolling onboarding and Devices fixtures use full-content frames. Theme choices also fall back to a vertical layout when narrow, and large-text Logged Days rows retain their dates, notes, and labels.

Screenshots

Developer Overlay, light iPhone full-content capture. Retaining the scene restores uniform native glass without changing the menu's styling, dimensions, or content.

Before After
Detached-scene menu with bright bands and a hard material cutoff Scene-backed menu with uniform glass and complete content

Before: 9e759fc8. After: f05ca6240; all six overlay captures reproduced byte for byte.

Primary APIs

  • SnapshotConfiguration.Frame.LayoutTraits owns horizontal and vertical size classes and interface idiom. Configuration-level layout traits remain supported and take precedence over frame defaults. Phone and iPad presets carry truthful traits without changing their filenames.
  • Frame.scenePolicy controls capture scene retention without renaming references. Both configured assertions and hosted PNG export honor it.
  • Frame.viewport captures bounded shell composition. It must be paired with full-content captures of scrolling panes.
  • The four Duo profiles use measured native geometry. The module README retains the measurement date, runtime/build, collection method, and validation limits; the unused raw JSON dump is removed. .narrowWindow covers a synthetic multitasking container.
let configuration = SnapshotConfiguration(device: .duoInnerPortrait)
configuration.applyTraits(to: host)

Previews and UIKit capture use the same mapping. Conversion from configuration traits to frame traits uses exhaustive switches for both size classes and idiom, so new enum cases require an explicit mapping. Custom frames can supply layoutTraits; component frames inherit the host's traits.

Capture and tooling boundaries

All capture entry points acquire one lock before host discovery, validation, and preparation. Fixed, regular-width phone captures retain their scene so native bar geometry stays stable. Other synthetic captures detach the marked window by default, preserving compact title space and anchoring full-content search to expanded content. Capture and measurement hosts reconcile their effective insets after containment and layout, with another reconciliation after settling. The predicate uses content-controller traits, not pixel width or simulator identity. Frames can explicitly retain the scene when native chrome requires it. Periscope and Developer Overlay use this option without changing their configured traits, insets, or sizing. Developer Overlay's native glass otherwise develops bright bands and a hard material cutoff. Retention can expose physical reserved regions, so general presets keep the automatic policy. Both paths restore scene and presentation state on success or failure. A nil inset override retains native geometry.

Only the captured root overrides its safe-area getter, during final pixel rendering. Native getters remain active during containment, measurement, and settling so inset changes propagate to children. Root pinning protects translated tiles. Overriding ancestors doubled asymmetric insets after measurement rehosting. Annotation padding has zero safe-area insets to avoid moving centered content. Tall tiled Year annotations retain the pre-existing limitation listed below. Regressions cover these boundaries, already-mounted inputs, compact and regular title readability, native bar stability, tiling, and failure cleanup. Full-content captures reset restored scroll offsets after readiness; viewport captures preserve them.

Accessibility subjects stay mounted during readiness and settling. Ordinary captures retain their controller parent during parsing; only upstream tiling temporarily detaches it. Annotation wrappers grow only after the final accessibility parse. Synthetic viewports recover native navigation-bar metadata synchronously, restore window geometry before parsing, and restore the original getters on every exit.

Pixel settling retains main’s normalized pixel comparison and retry policy. Final reference-comparison tolerances remain unchanged. Hosted resize, restoration, full-content, PNG export, and cancellation checks cover the shared pipeline.

Snapshot shard discovery accepts literal multiline source arrays with shared support files. Unsupported declarations fail without borrowing a following target's sources. The existing shard assignments stay unchanged.

Compatibility and review focus

  • The stack requires iOS 27.1. Development and CI use Xcode and simulator 27.2. The native macOS deployment target is unchanged.
  • Review the capture isolation and frame-trait API changes before the references. The fix addresses stale inset compensation: requested top/bottom values of 5/9 previously reached native children as 0/0.
  • The app integration in feat(WhereUI): adapt Where for iPhone Duo #372 fixes a separate stale developer-overlay inset delivery. Native accessibility capture corrections belong to this foundation layer; later app layers own their navigation and search behavior.
  • The Duo profiles retain their recorded 27.1 measurements. Re-measurement on the native 27.2 Duo runtime remains an explicit release-validation item.
  • Theme descriptions retain three lines of minimum space and grow for large text. The shared picker no longer truncates the Flat description at AX5.

Testing

  • At 42ab5b40e, ./test --review --only SnapshotKitTests --only WhereUISnapshotTests/DeveloperOverlaySnapshotTests passed all 56 SnapshotKit tests and the Developer Overlay image suite (all six captures matched byte for byte). Architecture and SwiftFormat passed. Review fixes preserve the existing mappings and presets; no references changed.

  • Full gates on the current head 42ab5b40e: GitHub Actions and CircleCI pipeline 2195.

  • At f05ca6240, the full run passed 2,922 iOS unit tests and 28 host macro tests. All 71 snapshot tests are covered by the full run plus focused assertions after review; 679 affected WhereUI tests also passed after the eager Timeline change.

  • ./test --no-generate --review --timings --only WhereUITests with the seven affected snapshot suites verified the refreshed references. A final ./test --no-generate --no-build --review --only WhereUISnapshotTests/DeveloperOverlaySnapshotTests repeated all six overlay captures byte for byte. Architecture checks and SwiftFormat passed.

  • Full gates for the capture-repair baseline (before the exhaustive-conversion refactor and documentation cleanup): GitHub Actions and CircleCI pipeline 2188.

The capture fixes and reference migration are verified at this PR's own layer. A green stack tip cannot establish that an earlier PR's renderer and references agree. Layer-specific Timeline, Settings, and Year references retain the behavior present here rather than borrowing later adaptive UI semantics.

  • Independent source review covered capture containment, final annotation sizing, native-bar getter restoration, Settings measurement, and eager Timeline layout. Visual review covered the refreshed references and preserved comparison-sheet metadata; comparison tolerances are unchanged.
  • Two tall Year accessibility images retain a pre-existing marker offset and incomplete native-toolbar legends (STU-325).
  • Native Duo 27.2 geometry re-measurement and interactive device checks remain pending. Synthetic regular-simulator coverage does not establish native system-bar geometry or spoken VoiceOver behavior.

Stack

First active layer, based on main after #376 merged. This supplies the capture foundation for #378 and #372. Capture corrections and their references land here; adaptive corrections and references land with the app integration.

Backlog reconciliation

The 13 preserved Duo records are tracked in Linear. STU-318 owns adoption and its six implementation sub-issues. This slice relates to STU-319, STU-320, STU-327. Unmerged implementation remains In Review.

Remaining Backlog work: native Duo release validation (STU-316), intermittent Locations settling diagnostics (STU-317), and tall Year accessibility annotations (STU-325). Existing STU-114 and STU-252 now distinguish the partial fixes from their remaining requirements; both retain their priorities, labels, and open state.

@kyleve
kyleve added this pull request to stack #379 October 16, 2026 14:07
@kyleve kyleve changed the title Support Duo runtime and adaptive snapshot hosting feat(SnapshotKit): support Duo captures on iOS 27.1 Oct 16, 2026
@kyleve
kyleve force-pushed the codex/duo-snapshot-foundation branch from 61518f9 to 8d09084 Compare October 30, 2026 22:50
@kyleve
kyleve force-pushed the codex/duo-snapshot-foundation branch from 02f3c09 to ca5dede Compare November 9, 2026 22:30
@kyleve kyleve changed the title feat(SnapshotKit): support Duo captures on iOS 27.1 feat(SnapshotKit): support measured Duo capture geometry Nov 9, 2026
@kyleve
kyleve force-pushed the codex/duo-snapshot-foundation branch from ca5dede to b4f5f88 Compare November 11, 2026 17:04

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Do we need to keep this committed, or can we remove it now that we have the measurements?

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Posted by an AI agent on kve's behalf.

Removed in 9f9fa8457. No production code or tests consumed the raw JSON. The README retains the measurement date, runtime and Xcode builds, collection method, hardware-verification caveat, and pending native 27.2 validation. Preset values are unchanged, and the original measurements remain in Git history. Updated the source pointer and measurement guidance, and propagated the cleanup through the stack.

Base automatically changed from codex/duo-broadway to main November 11, 2026 17:17
@kyleve
kyleve force-pushed the codex/duo-snapshot-foundation branch from b4f5f88 to 1a7d8ae Compare November 11, 2026 17:17
public var resolvedLayoutTraits: Frame.LayoutTraits? {
guard let layoutTraits else { return device.layoutTraits }
return Frame.LayoutTraits(
horizontalSizeClass: layoutTraits.horizontalSizeClass == .compact ? .compact : .regular,

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Please change these (and others) to actual switch statements so they fail if we add new cases.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

(Either that, or add an extension to layoutTraits.verticalSizeClass that switches internally to convert to the value in question, and then call that.)

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Posted by an AI agent on kve's behalf.

Fixed in 42ab5b40e. Both axes now use one conversion property with an exhaustive switch; idiom conversion also switches explicitly over every case. No default or ternary fallback remains in these mappings, so adding a case requires an explicit conversion.

All 56 SnapshotKit tests passed. All six Developer Overlay captures matched their existing references byte for byte; architecture and formatting checks passed. Propagated through #378 and #372.

@kyleve
kyleve force-pushed the codex/duo-snapshot-foundation branch from 1a7d8ae to 9e759fc Compare November 11, 2026 17:39

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

The color changes here seem somewhat suspect

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Posted by an AI agent on kve's behalf.

Fixed in f05ca6240. The color shift was a capture defect: detaching the window scene produced bright bands, ripples, and a hard cutoff in the native glass. These Developer Overlay fixtures now opt into scene retention; the product's colors and material styling are unchanged.

The three menu references were refreshed after visual review. All six overlay captures then repeated byte for byte at this PR's layer. The adaptive layer also passed all six after accounting for its additional Duo Layout Lab row and content bounds.

Developer Overlay after scene retention, light iPhone full-content capture at f05ca6240

Full-resolution before/after images are also in the updated PR description. Full CI is running on the published head.

Move generic accessibility capture fixes and reviewed references into the renderer layer. Measure Timeline rows eagerly, settle Settings sizing, and retain the scene for Developer Overlay glass. Preserve layer-specific navigation and accessibility semantics.

Validation: 2,922 iOS unit tests, then 679 affected WhereUI tests; 71 snapshot tests covered by the full run and focused assertions; all six overlay captures repeat byte-for-byte. Architecture, host checks, and SwiftFormat pass.
@kyleve
kyleve removed this pull request from stack #379 November 11, 2026 18:38
@kyleve
kyleve added this pull request to stack #493 November 11, 2026 18:39

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

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

Posted by an AI agent on kve's behalf.

Developer Overlay — light iPhone full-content fixture at f05ca6240. The snapshot retains its window scene so native glass stays uniform; menu styling is unchanged. All six overlay captures repeated byte for byte. Capture defect and fix discussion.

Developer Overlay with uniform native glass and complete menu content

Remove the unused raw simulator record. Keep the measurement date, toolchain, collection method, and outstanding native validation in the module README; preserve all preset values. Update the measurement rule and source pointer.
Share a switch-based size-class conversion between both axes and switch explicitly over the configuration idiom. Adding an enum case now requires a deliberate frame mapping instead of falling through a ternary. Existing mappings and identifiers are unchanged.

Validation: all 56 SnapshotKit tests, architecture checks, and SwiftFormat pass.
@kyleve
kyleve merged commit 2aedf67 into main Nov 11, 2026
22 checks passed
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