Redesign developer overlay as a Liquid Glass HUD - #115
Merged
Conversation
Rework the DEBUG-only developer overlay into a HUD-style Liquid Glass surface, and make the floating window movable, resizable, and aware of the app content behind it. - Hover button: circular adaptive `.glassEffect` disc with a `.primary` glyph (reads in light/dark) instead of the flat stroked ring. - Chrome: one glass panel surface with a slim HUD control strip (close / drag grabber / full-screen); the tools list clears its scroll background so the glass shows through. - Full screen: an inset (~12px) rounded modal over a dimmed, touch-blocking scrim, preserving the `.isModal` / `.screenChanged` accessibility behavior. - Floating window: draggable by its grabber, resizable from a bottom-trailing grip (top-leading pinned), clamped on-screen with a min size. Center + size (and resting corner) persist across launches via an injected KeyValueStore, kept local to this DEBUG surface. In-flight translation lives in the view so the store is written once per gesture. - Safe area: the window publishes the footprint it occupies (DeveloperOverlayInsetKey), which RootView applies as safeAreaPadding to the app content only, so screens behind the non-modal HUD scroll clear of it; the HUD's own list reserves room for the resize grip. - Add developer.dragHandle / developer.resizeHandle a11y strings; extend DeveloperOverlayModelTests for the layout math, persistence round-trip, and layout survival across a full-screen toggle.
- Measure the drag and resize gestures in the global coordinate space. They were attached to the moving/growing window, so a `.local` translation was relative to a frame moving under the finger, which oscillated and read as the window briefly resetting to its prior size/position. - Apply `.dynamicTypeSize(.small)` to the HUD surface so the control bar and tools list shrink, reclaiming space in the small window.
Address review findings on the developer HUD: - Clamp the persisted floating layout to the *current* container before use (shared `currentBase`, used by both display and the gesture-end commit). Persisted geometry was only clamped for the container active when written, so after a rotation, size-class change, or launch on a different device the window could open off-screen or oversized. - Extract the footprint math into a pure, testable `DeveloperOverlayModel.contentInsets(for:in:edgeTolerance:)`. An edge now docks only when its near edge is within tolerance *and* the opposite edge is free, so a window spanning an axis (e.g. the near full-width default) no longer wrongly claims a large inset, and a window docks at most one edge per axis. Each inset is capped at `Layout.maxContentInsetFraction` (0.8) of the container so a large window can't collapse the content behind it. - Add DeveloperOverlayModelTests for the inset directions, spanning, the cap, the empty-container guard, and the clamp-on-restore path.
kyleve
enabled auto-merge (squash)
July 22, 2026 16:48
kyleve
pushed a commit
that referenced
this pull request
Jul 26, 2026
Weekly read-only audit refresh. Every open July 19 finding was re-verified against current source, and the week's new surface was reviewed: the Periscope migration (#94), Settings drill-in (#111), developer HUD (#115), navigation restructure (#119), log-viewer tooling (#107), String Catalog symbols (#124), the Gregorian-calendar pass (`fe99dde`), preview coverage (`52f0136`), Bumper Bowling (#127), and catalog serialization (#135). No source changed. ## Structural changes to the audit - **`LogKit` and `LogViewerUI` are gone** — Periscope replaced them, so their sections are removed and the inventory drops to **14 SPM library targets**. Counts refreshed to ~359 source / ~198 test files (WhereUI 84 → 113, WhereCore 70 → 87, PeriscopeTools 15 → 24, RegionKit 9 → 13). - New section for **Bumper Bowling**, the repo-owned architecture lint that landed this week. ## Two new high findings 1. **`where.gregorian_calendar` enforces nothing.** The rule filters `MemberAccessExprSyntax` on `base == "Calendar"`, so it matches a spelled-out `Calendar.current` but not the implicit-member form (`calendar: Calendar = .current`, `in: .current`) — which, after the Gregorian call-site pass, is the only form left in the tree. CI hard-gates `bumper lint` at `severity: .error` and is green, which confirms it finds nothing while seven production sites drift. `.bumper/RULES.md` also claims three calendar violations and some preview-coverage violations are "left visible during this bootstrap"; both claims are stale. 2. **`CalendarDay.displayDate` resolves through `Calendar.current`.** `startOfDay(in:)` interprets the day's Gregorian Y-M-D as *that* calendar's components, so on a Buddhist-era device every day label flowing through the helper — relabel, logged days, resolution details, the region drill-in — renders a date ~543 years off. `DateRangeFormatting.abbreviated` and `PresenceTimeline.stints` also default to `.current`. The three highs carried from July 19 are all still open: daily-summary staleness, the WhereUI tracking-toggle race, and the LifecycleKit terminal-phase race (now described precisely — `runSteps`' cancellation check sits at the *top* of the loop, so a cancel during the **final** step's `minVisible` hold is never observed before it returns `.completed`). ## Retired as verified fixed The Where app `README.md`; the cold-launch reason misclassification (`.undetermined` + `completedStepIDs`); `#Preview` coverage across WhereUI/WhereWidgets; the hand-maintained string-key facades; the in-app SwiftData browser; the widget post-midnight snapshot policy (now documented as intentional degradation); and `SharedItemLoader` warning logs. ## TODOs.md **`Where/TODOs.md`** — retires the `WhereModel` break-up (now 185 lines of process-lifetime state), the SwiftData browser, the `@_spi(Testing)` migration, and two items whose subject no longer exists (the `WhereModel` "controller" guard, hoisting `Calendar.current` onto the controller). Files this week's findings, pins the exact remaining `waitForOneRunloop` call sites, and promotes soft-deleting untracked regions now that the shipped picker reaches the hard delete. **`Shared/Periscope/TODOs.md`** — adds a P1 (the orphan sweep closing spans whose `survivesRelaunch` policy couldn't be decoded, silently) and three P2s (`LogInspectorModel` re-querying full subtrees per commit, `SpanHistoryModel` swallowing an `SpanEnded` decode failure, and a test pinning open-span containment). Sharpens the two density items with the user-visible consequence. **Root `AGENTS.md`** — its description of the audit cited the LogKit/LogViewerUI sections as an example of stale content surviving a refresh; this refresh removed them, so the caveat is rewritten to keep the useful half without an example that goes stale again. ## Verification Static analysis only — the Cloud agent runs Linux, so no `tuist test`, `bumper lint`, or simulator runs. CI status on `main` was read via `gh` (green) which is what lets the "the Gregorian rule finds nothing" conclusion stand. Changes are markdown-only, so `./swiftformat --lint` and `./xcstrings --lint` are unaffected.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Reworks the DEBUG-only developer overlay into a HUD-style Liquid Glass surface, and makes the floating window movable, resizable, and aware of the app content behind it. All changes are
#if DEBUGand compiled out of release.What changed
.glassEffectdisc with a.primaryglyph (reads in light and dark) instead of the flat stroked ring..isModal/.screenChangedaccessibility behavior.KeyValueStore, kept local to this DEBUG surface rather than routed through the shippingWherePreferences. In-flight translation lives in the view so the store is written once per gesture (not per frame). The persisted layout is re-clamped to the current container on open, so a window saved in one orientation / size class / device can't reopen off-screen or oversized.DeveloperOverlayInsetKey), whichRootViewapplies assafeAreaPaddingto the app content only, so screens behind the non-modal HUD scroll clear of it; the HUD's own list reserves room for the resize grip. The footprint math is a pure, unit-testedDeveloperOverlayModel.contentInsets(...): an edge docks only when its near edge is within tolerance and the opposite edge is free (so a window spanning an axis — e.g. the near full-width default — claims nothing), and each inset is capped at 80% of the container so a large window can't collapse the content behind it.developer.dragHandle/developer.resizeHandleaccessibility labels.Notable decisions / trade-offs
safeAreaPaddingis scoped to theLifecycleContaineronly (never the sibling overlay/toast layers) so the overlay's geometry can't feed back on itself. A bottom-docked HUD lifts the tab bar clear of it — intended..localtranslation oscillated and read as the window resetting to its prior size/position..dynamicTypeSize(.small)to reclaim space in the small HUD.Testing
DeveloperOverlayModelTests: default/clamp/move/resize geometry, persistence round-trip viaInMemoryKeyValueStore, layout survival across a full-screen toggle, the content-inset footprint (each docking direction, corner, axis-spanning, the cap, the empty-container guard), and the clamp-on-restore path.WhereUITestspasses locally (tuist test WhereUITests, iPhone 17 / iOS 26.2) andswiftformat --lintis clean.