S10.7 — Liquid Glass release UX (Now Playing + shell)#61
Merged
Conversation
Design-reference snapshots (docs/design/*/NowPlayingView.swift, from the S10.7 handoff) are artifacts, never compiled — committing one tripped the pre-commit SwiftLint pass. Root fix is scope, not bypass: docs/ is declared out of scope in .swiftlint.yml (excluded), .swiftformat (--exclude), and the hook's staged-file collection (explicit paths don't honor config excludes).
The founder-approved release design for the Now Playing screen + shell: 8a = 7a Liquid Glass material layer over the 5a layout. INTEGRATION.md maps it onto this repo's governed UI architecture (AppShell/ChromeBar/ NowPlayingBar/DesignSystem) and carries the 6-PR incremental plan the S10.7 sprint is built around. NowPlayingView.swift + the HTML files are visual references only, NOT drop-ins.
…an + rig seeds Defines the Liquid Glass polish sprint per the founder-locked scope (Now Playing + shell; trailing 260pt inspector; transport stays in footer; chrome stays a band). Core architecture from the 2026-07-17 online research pass (Apple docs/HIG/WWDC25-26 + SDK-verified API inventory): a three- regime layer policy — system glass only where macOS provides it; app glass-look surfaces are token-governed content-layer fills + edge decoration (nothing renders beneath our flow panels, so backdrop-sampling material/glassEffect would be cited 'ghost glass'); plain content styling elsewhere. Zero hand-applied glassEffect/Material this sprint, enforced by a planned strict-gate tripwire. Also updates sprint-plan: S10.3 fully shipped (#59+#60), S10.5 annotated CUT, S10.7 row + refreshed §Status. Pending: SME panel (swiftui-pro + macos-design + qa-expert rig redesign) + architect/the-fool gate; founder decisions D5-D9 open.
…ided rig, R0 Kit Panel round 1 (swiftui-pro + macos-design + qa-expert) integrated: - Reframe (founder): this IS the release UX (R1 re-gated on S10.7+S10.8; 'nothing elementary ships'); entire GUI eventually redesigns around Liquid Glass — Glass tokens are that foundation. Incremental delivery with founder screenshots at every milestone. - macos-design: D10 dark-base re-tune decision (8a is tuned vs #0e1013, app ships #1E1E1E); mandated light-appearance translation grammar (never invert); transport-less hero recomposed (154pt, centered vs lens); designed first-launch/empty state; footer non-text 3:1 rules; firm D6 (hero-right lens, flex 400-560) + D9 (native segmented). - swiftui-pro: placement-test inventory corrected (3 existing Material overlays legitimately answer 'yes' -> .glassPanel(.overlay) role); PR-5 scrolling architecture mandated (no outer ScrollView - queue virtualization); CarvedSliderTrack split (geometry vs commit policy) with keyboard-parity acceptance; signal-path badge state mapping before the widget retires; re-chunk (grid->PR5, scrubber re-plumb->PR6); conditional phaseAnimator pattern; @ScaledMetric badges; blur-free GlowField construction. - qa-expert: §7 DECIDED — R0 DesignTokenKit extraction (executable target untestable otherwise); four semgrep tripwires (dry-run: 6 pre-existing sites, migration plan); R3 snapshots REJECTED w/ entry criteria; R4 upgraded to permanent contrast unit test (composite enumeration); R5 manual Instruments recipe; R6 matrix A-H bound to the founder screenshot loop; SlotFitTests; the-fool attack brief §7.4.
… fool PASS-W/-AMENDMENTS All gate amendments landed (verdicts recorded in the doc header): Architect: staged token declarations (hostile-Periphery / SongsList lesson — role charter reserved, code lands with first consumer); single- source invariant extended to the LEGACY palette (D10's audit must not audit a hand-mirror; R4 gains window/card/panel x label pairs); 'variable content' defined so the placement test survives D8 (smooth per-track- tinted fields stay Regime B; sampled glows clamp into token ranges); PR 1 split at the code/governance seam (1a Kit+overlay+migrations, 1b semgrep rules+guards+template); Kit admission charter + purity guard; role growth pre-authorized for S10.8; footer band keeps "panel" (AppShell byte-untouched). The-fool (all claims grep-verified before folding): dry-run inventory CORRECTED (4 overlay migrations incl. PlaylistDetailView:290, 2 Library TEMPs, per-site named-color dispositions incl. a new statusError token for the hand-painted meter red); matrix re-aimed (forced-visible overlay cells in PR 1a, single-run A/B rule, H at PR 5/6, deferred-cell ledger); .overlay role tightly specified (Material + call-site shape + native RT/IC, no strata) + one-off pixel-diff evidence; lens 0dB-label exposure added to R4 (palette max-luminance backdrop); lens a11y position REPLACED (exposed frame + accessibilityAction, not sighted-only); hero height derived-not-fixed with a max-type layout test; pre-mortem mitigations: PR-2 tuning stopping rule, D10 interim story + base-compat micro-pass, rule-2 never-suppressed (edit-the-rule governance), legal- token-recomposition named as the top evasion class.
…r + D8 art-sampled glows IN Founder resolved every open decision: D5 sample-rate readout YES; D6 8a hero-right lens; D9 native segmented control; D10 app-wide dark-base re-tune YES; and OVERRODE the defer recommendations on D7 (queue filter field -> PR 5, view-local, space-suppressing, jump-clears-filter) and D8 (album-art-sampled glow colors -> new PR 7: dominant-color extraction clamped into token chroma/alpha ranges, per-track cache, RM-gated recolor, brand-color fallback; R4 audits the clamp BOUNDS so contrast holds by construction). Section 9 rewritten as RESOLVED; PR order is now 1a->1b->2..7; sprint-plan S10.7 row updated (SP 6->7).
…ver, .overlay role The testability spine of the Liquid Glass release UX (design §3.2 R0), zero visual change: - Sources/DesignTokenKit (library target, ZERO SwiftUI/AppKit imports): RGBAColor + AppearancePair (with defaulted increased-contrast variants), sRGB alpha-over compositing + WCAG luminance/contrast math, Palette (every app color as data, byte-identical to the former DesignSystem literals, + a registry), SlotWidths, and the pure resolveSurface(role:appearance:flags:) contract with the one Material-backed role: .overlay(.ultraThin/.bar). - App side: DesignSystemGlass.swift bridges Kit data -> SwiftUI (explicit-sRGB, one path) and ships .glassPanel(_:in:) as the env->resolver->paint shim; DesignSystem.Color re-exports Kit values (single-source invariant — no RGBA literal remains on the app side); dynamic() gains the two accessibilityHighContrast appearance names with call-site-compatible defaults. - Migrations (all pixel-identical): the four floating overlays (ErrorBanner, QueueToast, EQ recall banner, PlaylistDetail restore pill — the .bar substrate preserved) now route through .glassPanel(.overlay); the loudness meters' hand-painted Color.red becomes the new statusError token (same per-appearance values). - Rig (design §7): RES resolver cube, TOK palette invariants, SLOT fixed-slot fit net, and the PERMANENT R4 contrast audit — whose first run caught three genuine PRE-EXISTING shortfalls the S9 audit missed (it measured labels against the window, not the card/panel composites): labelTertiary dark on panel⊕window = 4.46:1 (pinned, resolves with the PR-2 D10 re-tune) and statusError off-window / light surfaces 3.0–4.3:1 (pinned, resolves with the PR-6 meters restyle). All pinned via withKnownIssue so the fix flips them loud. swift build green; swift test: 168 tests / 29 suites pass, 6 known issues (the pinned shortfalls above). Founder acceptance next: matrix A,B in a single run + the three forced-visible overlay cells + the one-off before/after pixel diff.
…e explicit-sRGB path)
…riphery-clean resolver Review verdict was approve-with-fixes; all applied: - BLOCKER-1: statusError encoded the PRE-macOS-26 palette red (#FF3B30/#FF453A); macOS 26 refreshed it — Color.red actually resolves to #FF383C light / #FF4245 dark (probed three ways: Color.resolve(in:), NSColor(Color.red), NSColor.systemRed). Values corrected as exact fractions, so the meters swap is now genuinely pixel-identical. R4 ratios re-verified: hard dark-window assertion 4.85 (green); all five pins still pinned (4.28/4.09/3.05/3.57/3.57); known-issue count 6. - BLOCKER-2: periphery (hostile, strict) flagged AccessibilityFlags' never-read fields. Adopted the design doc's literal resolver signature (role:appearance:reduceTransparency:increasedContrast:) with the same `_ =` staging idiom; struct dropped until the fill roles read flags. `periphery scan`: No unused code detected (run this time, stated here). - MAJOR-1: R4-LEG-03's five shortfalls now pinned PER (appearance, surface) pair — a partial fix (e.g. PR-2's dark-composite shift) flips its own pair loud instead of hiding in a shared block. - MINOR-1: SlotFit measures regular weight (the slot-constrained time label's real config; semibold is the unconstrained tooltip). - MINOR-2: LibrarySidebar rename-error caption `.red` -> statusError (the inventory miss; pixel-identical post-BLOCKER-1). - MINOR-3: iconFill gradient stops moved into Palette (registry rows) — "no RGBA literal on the app side" now holds file-wide. - NIT-2: TOK-04 asserts rowSelected appearance-independence too. swift build green; swift test 168/29 pass w/ 6 known issues; periphery clean. Founder A/B + overlay-cell screenshots are valid to capture from THIS commit (evidence against f4ad777 would have caught the red shift).
… guards, matrix template The rig that makes the PR-1a token architecture self-enforcing (design §7 R1/R6): - .semgrep.yml: ui-no-adhoc-material (materials incl. .background(.bar) outside the 4 definition files), no-liquid-glass-api (FROZEN — never suppressed; adoption edits the rule inside a design review), ui-no-color-literal (constructor forms + implicit/explicit palette members in paint contexts + shadow colors; Color.clear and hierarchical styles deliberately allowed), ui-no-appearance-branching (\.colorScheme reads / preferredColorScheme / NSApp.appearance= — S9-T now enforced). - strict-gate.sh: Kit-purity guard (DesignTokenKit must never import SwiftUI/AppKit) + fixed-band Dynamic-Type-clamp guard (NowPlayingBar; ChromeBar joins in PR 6). - Dispositions from the pinned dry-run: ChromeBar app-mark glyph Color.white -> onAccent (pixel-identical; the token exists for exactly this); 4 TEMP suppressions with the house grammar — 2x LibrarySidebar .background(.bar) (S10.8 sweep, expiry 2026-08-15), PlaylistDetailView dead-file triangle .orange (unify onto statusWarning in S10.8 — a NEW dry-run find, same class as the meters red), NowPlayingBar scrubber shadow literal (becomes a Glass token in PR 6, expiry 2026-08-01). - docs/sprints/s10-7-visual-matrix.md: the committed R6 template — states A-H w/ toggle instructions, per-PR required cells, the R5 Instruments recipe, the PR-5 VO/keyboard script, and the ledger with PR 1a recorded as ACCEPTED (captured cells + 5 deferred cells under TEMP grammar, expiry = sprint end). Verified: semgrep green over the tree with suppressions accountable (check-suppressions.sh green); plant-a-violation test fired all three new nets (glassEffect, .red paint, Kit UI-import) and the tree is clean after revert; swift build + 168 tests green.
…le milestone - D10 (founder-locked): Palette.window dark #1E1E1E -> the 8a deep base #0e1013, app-wide via the token (every tab inherits; S10.8 sweeps their surface tints). Light untouched. - The R4 flip mechanism fired exactly as designed on the FIRST run against the deep base: three dark pins went "unexpectedly passed" and were PROMOTED to hard assertions (labelTertiary on panel⊕window — the PR-1a find, now fixed by the base itself; statusError on dark card/panel). Known issues 6 -> 3 (only the light statusError pins remain, scheduled PR 6). - Ambient glow field (design §3.3): Kit gains the three 8a glow color pairs (dark = spec alphas .28/.12/.10; light = grammar-rule-5 thirds), GlowFieldSpec geometry + falloff data, and the pure glowFieldIsVisible resolver (RES-04: suppressed under RT AND under IC alone). App side: GlowField renders three stop-authored radial gradients (no .blur — zero filter passes), mounted via .background on NowPlayingTabView, hit-transparent + a11y-hidden; other tabs keep the plain deep base until S10.8 (D1). - R4 glow pairs landed and CAUGHT A REAL DESIGN PROBLEM before any pixel shipped: labelTertiary on the teal core fails AA at spec alpha (3.98:1). Resolution is a PLACEMENT RULE, not an alpha dilution or a waiver — the 8a mock itself puts only the hero (large text, 3:1) on that core. Model refined to geometry: single cores at max alpha for label/secondary, pairwise overlaps at mid-stop attenuation (cores are disjoint), tertiary audited everywhere its rule allows (R4-GLOW-01/02/ 04); rule recorded in the design doc §3.3. - New tests: RES-04 visibility cube, GLOW-01/02 grammar+falloff invariants, the three R4-GLOW suites. Verified: swift build green; 174 tests / 30 suites pass w/ 3 known issues; semgrep green; periphery clean. Founder milestone next: matrix cells A,B,C,D,H vs the 8a mock + dark-mode glance at the other four tabs (the D10 interim story: contrast provably improves — R4 legacy pairs — cohesion intentionally lags until S10.8).
…y, light suppressed, seams feathered Review verdict was "not ready for founder round 1"; all findings applied so the two tuning rounds are spent on taste, not defects: - BLOCKER 1: the circular RadialGradient clipped by a non-circular Ellipse left a hard cyan rim on the minor axis (alpha 0.048 step at the boundary). Now a CIRCULAR gradient scaled into the spec ellipse (Circle + scaleEffect) — every isoline is the ellipse; clear at every edge direction. - BLOCKER 2: glow centers were guesses; now DERIVED FROM THE MOCK'S CSS (teal 0.214/0.067, lime 0.786/0.973, blue 0.634/0.621 — blue is an interior midfield glow, was ~430pt off). PR-6 window-space equivalents recorded for the shell-mount migration. - MAJOR 3 (Option B): the mock's falloff is an exact-LINEAR radial ramp (its blur only rounds the apex) — profile now 0.45 @ 0.55 via a shared falloffFraction(at:) that BOTH the render gradient and the audit read. The abstract cores+overlaps audit model is replaced by a GEOMETRIC grid audit sampling the real three-glow composite from GlowFieldSpec at the reference AND min-window geometries — founder center-tuning re-verifies automatically; the impossible worst-cases are gone. - MAJOR 4: light-mode glows mathematically DARKEN the near-white window (teal composite ΔL −7.2% = a stain; no alpha fixes it). The resolver is now appearance-aware: glows render in DARK only; light ships pixel-identical to today. The grammar thirds stay recorded as the S10.8 re-derivation starting point (luminance-positive pastels). - MAJOR 5: the interim card⊕glow composite (right pane over the lime core) fails tertiary AA (4.23) — audited as R4-GLOW-05 with label/ secondary asserted hard and the one tertiary defect pinned to the PR-5 pane restyle. - MAJOR 6: 24pt top/bottom seam feather (LinearGradient mask, zero filter passes) so the flat chrome/footer bands never meet a lit field across a 0.5pt hairline (ΔRGB +39 unfeathered). STOPGAP — removed in PR 6 when the bands go glass. - GlowFieldGate lives in DesignSystemGlass.swift — the sanctioned appearance reader (our own rule 4 would have fired on the view). Verified: 176 tests / 30 suites green with 4 known issues (3× statusError light -> PR 6; 1× card⊕glow tertiary -> PR 5); semgrep green; periphery clean.
Founder decision (round 1 of 2): glow geometry goes FULLY proportional — ellipse sizes become fractions of the tab (teal 0.643x0.940, lime 0.679x1.007, blue 0.375x0.638, derived from the mock's 1120x720 card translated to tab space), reproducing the mock's coverage at ANY window size. Fixed point-sizes read as three subtle pools on a large window (~40% width coverage where the mock has ~64%). The R4 geometric audit re-ran against the larger real overlaps and stays green: 176 tests / 30 suites, 4 known issues (unchanged set).
…fill role - Kit: PeakHoldTracker (pure, TIME-FED — no wall clock, so freeze-on- pause is structural; hold 0.6s then linear decay 1.25/s; hostile-input clamps), GlassDecor (the 8a edge-decoration strata as data: specular rim, glass hairline — the FIRST token with real Increase-Contrast variants per §3.2 — dark-only bottom bleed, appearance-tuned shadow, lens radius 20), Palette.lensFill (8a rgba(16,18,21,.42) dark; white- glass light), and the resolver's first FILL path: .lens resolves translucent normally, OPAQUE (fill⊕window, derived) under RT — and under IC even without RT (RES-02 doctrine). - App: .glassPanel fill branch composes the strata (fill + bleed under content; top-weighted rim + hairline strokes above; soft deep shadow, light = half/tighter). SpectrumAnalyzerView: bars + per-cell peak-hold caps (2pt, bar color @50%, 4pt above; drawn only once separated from the bar; RM-gated animation) inside the lens; heights still sourced from spectrumBars; bar field stays 50pt (outer frame 66 = +2x8 lens padding). AudioViewModel publishes peakCaps from the SAME 20 Hz tick (one @observable invalidation), isPlaying-gated belt-and-braces. - Tests: PH-01..09 (derived expectations from the config), RES-01/02 now real (fill role), R4-LENS-01 (lens⊕glow grid dark + light lens + RT/IC opaque fallbacks x label/labelSecondary), TOK-03 gains its first DELIBERATE opt-in (glassHairline must be STRONGER under IC). - Matrix ledger: PR 2 recorded as ACCEPTED (glow tokens frozen; C/D/H deferred with reasons). 188 tests / 31 suites green (4 known issues, unchanged set); semgrep green; periphery clean. Founder cells next: A, B, C, G + the Instruments row (the first perf checkpoint — caps animate over the 20 Hz field).
Founder cells A/B/C/D/G all captured and verified — including the RT cell showing the resolver suppress the glow field and opacify the lens LIVE (the headless contract, confirmed on screen). Review finding from the light cells: the first-pass light shadow (literal half-of-dark, 0.30 @ 18) read as a gray smudge on the light window — tuned to 0.15 @ 12 / y 5 (native light panels sit near 0.15); dark untouched. Instruments row parked by the founder (ledger TEMP; any track works when resumed). PR-4's B cell re-verifies the shadow tune.
…ate machine, empty state - HeroBand replaces the compact NowPlayingWidget card (retired; periphery enforces full deletion): heroTitle token (.largeTitle + heavy — Dynamic- Type-mapped, fixed 28pt banned) with the 8a teal halo applied by a .heroTitle() modifier in the sanctioned appearance-reader file (dark-only, grammar rule 6); artist line; and the FULL signal-path badge mapping as .glassPanel(.badge) capsules — PURE (accent dot, monochrome) / ENHANCED + pulsing dot / rate / bits / decoder(pure) / intensity% / XF / "(Pure unavailable)" warning / interrupted. The pulse is the mandated conditional-phaseAnimator (unmount = stop); badge heights are @ScaledMetric off the 22pt token; track changes crossfade + badge-state flips animate, all RM-gated; the empty state is designed (quiet placeholder, no halo, badge row space-reserved so first play never reflows). - Kit: badgeFill pair, hero halo/pulse tokens, the .badge fill role (same derived RT/IC-opaque contract as .lens), pulseIsActive predicate. - The audit's THIRD real catch on new work: labelSecondary on badge⊕teal-core = 4.26:1 (22 sampled points) — the white badge fill brightens the backdrop like the card⊕lime case. Encoded as a rule: badge text is the primary label or a status color, never the dimmed hierarchy (chips carry hierarchy in the capsule, not dimmed text). statusWarning-on-light-badge pinned (pre-existing orange class, scheduled with PR-6/S10.8 unification). - Tests: RES-01/02 now table-driven over both fill roles; PG-01..04 pulse-gate table; R4-BADGE-01 (badge⊕glow grid + light + RT/IC). 190 tests / 31 suites green (5 known issues, all pinned + scheduled); semgrep green; periphery clean.
…founder crash 2x today Both 2026-07-17 crashes (17:32, 19:16 — EXC_BREAKPOINT on MediaPlayer's "accessQueue" thread) share one signature: closure #1 in NowPlayingController.push(_:artwork:). The artwork request handler, defined inside the @mainactor controller, INHERITED MainActor isolation; MediaPlayer invokes it on its own serial queue when serializing artwork (jpegDataWithSize:, e.g. a Control Center render after a device switch), and Swift 6's dynamic isolation check (dispatch_assert_queue) traps. Fix: an explicit @sendable handler capturing only the immutable image value — severs the isolation inference, touches no controller state. Latent since S10.4 (#58); surfaced by today's Bluetooth device-switch sessions. Cherry-pickable to main independently of S10.7.
…60pt inspector, queue filter The one structural PR (design §5), founder decisions D2/D6/D7: - NowPlayingTabView: the 50/50 containerRelativeFrame split dissolves into VStack(HeroRow / HStack(queue-flex | InspectorColumn)) over the glow field. MANDATED scrolling architecture: no outer ScrollView (queue virtualization + jump-to-now-playing live), hero hugs intrinsic height, inspector scrolls INSIDE its panel chrome. LeftPanelView / RightPanelView / NowPlayingInfoView deleted (periphery-verified — contents redistributed, nothing orphaned). - HeroRow: HeroBand centered against the LENS (D6: flex 400-560 x 122). The lens gains its instrument dressing now that the frame exists: "0 dB" reference + 20Hz-20kHz axis strips OUTSIDE the bar field (the §7-R4 pair-9 placement resolution — labels never overlay near-white lime bars), four gridline hairlines behind the bars. The double- click→Monitoring affordance moves onto the lens with hover rim brighten + keyboard focus/Return + an exposed a11y element carrying the named action (the fool's accessibility replacement). - InspectorColumn (D2): master gain + Reimagine intensity (both now on the shared CarvedSlider — 8a carved track/knob visuals in the sanctioned appearance file, LIVE-commit interaction with full keyboard/VO parity: focusable + arrow-step + adjustable), the decoder/bit-depth detail line (relocated from the hero badges), loudness meters, crossfeed. Pad-inside-then-frame (the S9 lesson's PR-5 form). The .panel fill role lands (8a rgba(30,33,38,.5)). - Queue filter (D7, founder override): view-local, reuses the S9.6 FacetTextFilter primitive (title + relative path — AudioFile carries no artist; documented delta from §5), transport-Space suppressed while focused, Escape clears, jump-to-now-playing clears the filter first, No-Matches state, and reorder drag is DISABLED while filtered (moving relative to hidden rows is incoherent; menu moves stay). - Rig: RES table + .panel row; TOK-01 radii chain (panel ≥ lens); R4-PANEL-01 (panel⊕glow right-region grid incl. tertiary — its designed home) ; R4-GLOW-05 RETIRED by construction (the card fill it audited no longer exists; rows-on-glow is R4-GLOW-02's job); LAY-01/02 layout-arithmetic suite (the §5 width/height budget as assertions, max-type headroom documented). 193 tests / 32 suites green (4 known issues, all light-palette pins scheduled for PR 6); semgrep green; periphery clean.
…ation, slider drag math
swiftui-pro chunk review (approve-with-fixes) applied in full:
- BLOCKER: Jump-to-Now-Playing under an active filter now SEQUENCES —
the owner clears the filter this transaction (full list mounts, all
row ids registered), then bumps the scroll request-ID next main-actor
turn. The old same-transaction bump scrolled against the filtered
tree: a filtered-out target no-op'd, No-Matches never fired onChange.
Header/controls take a callback, not the request-ID binding.
- HeroBand: bits/decoder capsules DELETED (the §5 relocation had added
the inspector copy without the move); spoken summary keeps them
deliberately (one VO stop for the whole path).
- CarvedSlider: pointer maps onto the KNOB'S inset travel (knob/2 …
width−knob/2) so mouse-down at the extremes is value-neutral (live
gain no longer jumps ~0.6 dB on grab); knob size shared via
GlassDecor.sliderKnobSize (Kit) so math and visuals cannot drift;
track fills the 20pt hit target (GR top-aligned it at 14pt); a
focused slider consumes ←/→ even at the bounds (NSSlider parity).
- Filter Escape: `.onExitCommand` added (the field editor's
cancelOperation can eat Escape before onKeyPress); Escape now hands
key focus to the queue — queueFocused lifted to PlaylistView and
passed as FocusState.Binding, so ↑/↓ work immediately after clear.
- ShellMetrics now FORWARDS NowPlayingLayout (single-source): the
shell lays out from the same Kit values LAY-01/02 assert against.
- InspectorColumn scroll content clips to the radius-22 panel shape
(rows no longer render square into the corner cutouts).
- Queue rows: ForEach keyed by stable QueueItem.id again (VisibleRow
wrapper carries the real index); grip hidden while filtered
(dragPayload nil — the row API's own non-reorderable state).
- FacetTextFilter: localizedStandardContains — case- AND diacritic-
insensitive per the §5 contract ("beyonce" matches "Beyoncé");
benefits all four consumers; diacritic test added.
- Lens: default accessibility action added (VO-press works directly).
Space deliberately NOT added: the transport's menu key-equivalent
matches first (same audit that removed the queue's dead handler).
- Hygiene: knob fill is a Kit token (knobFill pair, PR-6 light-mode
hook); carvedTrack + knobFill join the Palette.all registry; the
resolver names every fill role explicitly (no default: inheritance);
stale NowPlayingInfoView MARKs trimmed; INTEGRATION.md 50/50 line
annotated as superseded.
Gate: 194 tests / 32 suites green (4 known-issue pins, PR-6 scheduled);
strict-gate PASSED (semgrep, swiftlint --strict, periphery, clang-tidy,
leaks + plant-a-leak).
…ification riders The traversal script now states the Keyboard-navigation-ON prerequisite (custom .focusable views join Tab only with it on) and the Return-not- Space lens activation rationale; a rider list ties the bbf36cb review fixes to the founder round so each fix gets eyeballed, not assumed.
…r row Founder screenshot-round request: the strength dropdown sat on its own line under the toggle; it now trails the toggle in a single HStack (picker joins the row only while crossfeed is on).
…ault+42pt + full keyboard/VO walk) Founder screenshot round green across the board: C proves the RT→opaque resolver swap live, E proves the stronger-hairline HC variant, 42pt proves the badge row scales without clipping, and the keyboard walk confirms Return-on-lens → Monitoring (the one behaviour code review couldn't verify). -- kHz-under-Enhanced routed to PR 6 (owns the D5 sample-rate readout).
…-- kHz") The Enhanced path stored SignalPathInfo without achievedSampleRate, so it stayed 0 and both the hero badge and footer signal slot showed "-- kHz" while playing (founder finding, PR-5 round). The Pure path already carried the rate via makeSignalPathInfo; Enhanced now does the same. - startEnhancedPlayback captures engine.outputNode.outputFormat rate at store time (engine settled + playing on the engine queue — the Pure store-time discipline, never a MainActor engine read). - reestablishEnhancedAfterConfigChange refreshes the one field after an engine restart so a switch to a different-rate device can't leave it stale (mutate, not store, so fallback/interrupt flags stand). Enhanced shows the DEVICE output rate (honest per the field doc + the D5 device-pill context): a 48 kHz device reads "48 kHz". The PR-6 device-pill readout (D5) then surfaces the same value with a numericText transition.
…ht AA pins
Founder decision ("split text vs fill"): the vivid status* tokens become the
FILL/indicator colors (meter hot-bar, status dots — WCAG 3:1 non-text); new
status*Text variants carry the darker AA-legible shade for text/glyph sites
(4.5:1). On the deep dark base the vivid value already clears text AA, so the
text variants differ from the fills only in LIGHT — where vivid orange/red on
near-white was illegible.
- Palette: statusErrorText (#A3000F light) + statusWarningText (#6E4400 light);
dark = the vivid value unchanged. Both join Palette.all.
- Text/glyph sites → *Text variant: CLIP label, sidebar rename error, the
Pure-fallback + disconnected warnings (settings/hero/footer/error banner).
- Fills/dots stay vivid: the meter hot-bar and the signal-path dots (the dots
reinforce adjacent text → decorative, not audited at 3:1).
- ContrastAudit: R4-LEG-03 now asserts statusErrorText clears text AA AND the
vivid statusError fill clears non-text 3:1 on every surface, both appearances;
R4-BADGE-01 audits statusWarningText on the light badge. The 4 withKnownIssue
pins are RETIRED — `swift test` now reports 0 known issues (was 4).
Dark mode is visually unchanged (text variants == vivid in dark); only light
text/glyphs darken. 194 tests / 32 suites green, 0 known issues.
…cText) - Pill restyled from the flat card + hand-drawn hairline to the .badge glass role (the white-8% "small-control fill" — the same recipe the 8a mock's device pill uses); Capsule shape, rim + hairline + RT/IC from the resolver. - D5: the device's live sample rate reads on the pill, trailing the name, with a numericText digit transition when it changes (track/device switch). Now that Enhanced publishes its rate (b78b3ad), it's populated on both paths. - Reserved fixed slot (SlotWidths.chromeSampleRate = 66pt, SLOT-02 asserts the widest real string "176.4 kHz" fits) shown only while a rate is known, so the pill's fixed width — and the tabs' x-origin (the founder's "fixed top-left") — never move. Pill widened 200→252 to keep the name legible beside the rate. - Pill a11y value now includes the rate; the readout Text is a11y-hidden (folded into the pill value, not double-spoken). 195 tests / 32 suites green, 0 known issues.
…eral retired The footer scrubber (the app's only scrubber) now shares the 8a carved surface with the inspector sliders — deferred-commit seek + hover thumb + tooltip intact (the design's semantics-preserved requirement), only the VISUAL unified. - Extracted two shared primitives from PR 5's CarvedTrack (behaviour identical by construction — the slider's fill still reaches the knob centre at every position): CarvedGroove (token base + inset shade + teal fill w/ dark-only glow, parameterised by fill fraction / height / fill color / glow) and CarvedKnob (token circle + bottom shade). CarvedTrack now composes them. - FooterScrubber's hand-drawn track → CarvedGroove; fill follows the play/ pause/interrupted state color, and the dark-only teal glow shows ONLY while actually playing (no teal glow under a grey/dim fill). Thumb → CarvedKnob. - The NowPlayingBar:303 shadow literal (.black.opacity(0.3), the last PR-1 TEMP nosemgrep) is RETIRED by adoption — the carved knob's bottom-shade cue replaces the drop shadow; no shadow token needed. One fewer suppression. - carvedTrackHeight (5pt) added to the Kit (shared by both carved surfaces); footer scrubberTrackHeight removed, thumbSize 10→14 to match the carved knob. Gate: 195 tests / 32 suites green, 0 known issues; strict-gate PASSED (semgrep/swiftlint/periphery/clang-tidy/leaks+plant).
The chrome is a fixed 60pt band like the footer; the device pill + segmented tabs grow with text size, so accessibility sizes could overflow it. Add the .dynamicTypeSize(.small ... .xLarge) clamp and enroll ChromeBar in the strict-gate clamp guard (design §8 — "ChromeBar joins this list in PR 6").
…comment accuracy
swiftui-pro review verdict was approve/ship (no blocker/major); applied the
three worthwhile non-blocking items:
- MINOR-1: R4-LEG-03 now audits statusWarningText (text AA) on window/card/
panel too, not just the error variant — closes the "forever-green" hole
for the warning glyph's non-badge surfaces (banner/footer/settings). Passes
by math (light ≥7:1). The vivid statusWarning is deliberately NOT asserted
at 3:1 (it's only the decorative status dot, which reinforces adjacent text).
- MINOR-2: the chrome rate readout gets minimumScaleFactor(0.7) so a 9-char
hi-res rate ("176.4 kHz") at the clamped .xLarge max shrinks to fit rather
than tail-truncating away the "kHz" unit (SLOT-02 proves default-size fit).
- NIT: PlaylistDetailView's pre-existing .orange TEMP now points at
statusWarningText (the AA text variant, not the vivid fill) for the S10.8
unification.
MINOR-3 (pill re-evals at 20 Hz while playing) left as-is — correctly scoped
to only the pill, cheap leaf re-eval, no visual thrash (review: not blocking).
195 tests / 32 suites / 0 known issues; strict-gate PASSED.
… "48 k…") Founder screenshot: the footer read "Enhanced · 48 k…" — the 120pt slot was sized for the "--" placeholder era and truncated the moment the Enhanced path started publishing a real rate (b78b3ad). The promised-with-that-fix slot widening had only been delivered for the chrome pill (SLOT-02), not the footer. - SlotWidths.footerSignalSlot = 150 (Kit), sized for the widest legitimate composition: 6pt status dot + 5pt gap + "Enhanced · 176.4 kHz". - Footer.signalSlotWidth re-exports it (same pattern as the time-label slot). - SLOT-03 asserts the full dot+text composition fits with margin. - Footer min-width budget at the 880pt window: 766pt total — still fits. 196 tests / 32 suites / 0 known issues.
…ver rendered Founder round: four screenshots showed the pill with NO rate while the hero badge and footer (plain views on the same signalPath property) updated live — and the digits changed only on a device switch. Cause: macOS does not reliably re-render a Menu's custom LABEL when observed data changes; the label refreshes when the menu itself is rebuilt (device switch), which matches every observation. The readout is now a sibling Text beside the Menu inside the shared glass capsule (Menu = icon + name + system chevron; then spacer; then the fixed 66pt rate slot). Updates like any view; numericText roll preserved; the Menu keeps the pill's a11y label/value/hint (value still folds the rate in); the capsule + fixed 252pt frame moved to the outer HStack — same look, same tabs-x-origin invariance. Long names compress before the spacer or slot give. 196 tests / 32 suites / 0 known issues; file lint/format clean.
…er rolls to the wrong song Founder repro: "every time I change the device mid play, sometimes the app starts playing a different song, and (sometimes) the selected track and the played track differ." Root cause: player.stop() FIRES any pending .dataPlayedBack completion — indistinguishable from a real end-of-track. seekEnhancedBestEffort (both the device-switch re-establish AND user seek on a 48 kHz passthrough file) stops the player while the gapless hook is armed, so the stale completion invoked the seam handler: it consumed the on-deck track and scheduled it against the current track's re-scheduled remainder — wrong audio, and a transition-count bump that desyncs the queue selection from what's audible. "Sometimes" = the race (completion drain order) x whether a next track was armed. A second latent defect rode along: a stale fire consumed the ONE-SHOT seam hook even when it aborted, silently killing gapless at the track's real ending. Fix — the passthrough twin of the resampler's proven generation idiom: - passthroughGeneration (owned by resampleQueue like all seam state). - Every passthrough schedule (playFile initial, seam chain, best-effort segment) captures the epoch it was installed under; the completion abandons at fire time on a mismatch — upstream of the one-shot hook, so the hook survives for the legitimate EOF. - Bumps: stopEnhancedResamplerLocked (covers stop/track-change/reconfigure/ shutdown and the resampler→passthrough seam) and seekEnhancedBestEffort (bump BEFORE the stop, new segment under the new epoch). Queue discipline unchanged: sync from engineQueue/configChangeQueue only (C-1 direction). 196 tests / 32 suites / 0 known issues; strict-gate PASSED.
… hardenings swift-expert review of 92aef6b: APPROVE (epoch guard proven correct across every interleaving it covers); applied its findings: - MAJOR-1: the converter-failure seam fallback left resampleSession pointing at the OLD track's exhausted session while a passthrough schedule + armed hook were live — a later seek then misrouted to seekEnhancedResampler, whose stop() bumped only resampleGeneration → the live passthrough completion fired under an unbumped epoch and rolled the seam: the same wrong-song bug escaping through stale state. Fixed both ends: the fallback now clears resampleSession (the P1-2 rule, mirroring the empty-prime branch), AND seekEnhancedResampler bumps passthroughGeneration too (defense-in-depth — stale routing can never fire a live passthrough completion again). - MINOR-3: pauseForDeviceLoss now stopEnhancedResampler()s before its player.stop() — a queued device-loss callback racing a just-started Enhanced session could stop a live-epoch player with an armed hook. - NIT-4: stopEnhancedResamplerLocked + scheduleFileChainingPassthrough get dispatchPrecondition(.onQueue(resampleQueue)) — the contract was comment-only while primeEnhancedResamplerLocked already asserted it. Not applied (recorded for the break-it round): MINOR-2 capture-vs-schedule sub-ms window (pre-existing transport-vs-config-change family, strictly improved by the epoch guard); NIT-5 headless guard-predicate test; NIT-6 pre-existing end-of-queue sentinel question (runtime log check). 196 tests / 32 suites / 0 known issues; strict-gate PASSED.
…break-it list seeded
Founder-verified: pill rate readout, carved scrubber, H (teal play button by
design), and the wrong-song fix ("device switching works"). 42pt chrome-clamp
cell deferred (content scaling proven in the PR-5 round; clamp gate-asserted;
founder's Settings stuck in per-app Custom). Five break-it items filed.
The glow field now tints itself from the current album art (founder decision D8), inside the §3.1 pre-binding: every sampled color is CLAMPED into token-defined bounds, so the R4 audit keeps auditing an enumerable worst case and no cover can break the contrast budget. Kit (pure, headless): - SampledGlow: per-slot sRGB channel ceilings (teal .62 — tightest, alpha .28 under the hero/queue-head; lime/blue .95 at their lower alphas), hue- preserving proportional scale-down (never per-channel truncation), token DARK alpha forced (alphas are the audited quantity, never sampled), aesthetic floors (near-black / near-gray → brand fallback). Dominant-color selection: 12 hue buckets, weight = saturation × value (a vivid accent out-votes a dull field), achromatic pixels don't vote, deterministic. - GlowFieldSpec.compositeBackdrop gains a per-slot override palette — the render fold and the audit fold share the convention. - RGBAColor.fromPixel(UInt8) — the extractor's entry into the Kit color space. App: - ArtworkGlowSampler (@observable @mainactor): 24×24 sRGB downsample → pixel votes → Kit selection+clamp; per-track FIFO cache (64); synchronous (576 pixels — no Sendable laundering). nil artwork — including the token-guarded resolve gap right after a track change — resets to brand. - GlowField renders the per-slot overrides; recolor is a DISCRETE restyle: one crossfade, Reduce-Motion → a cut (§3.3, never a continuous animation). - NowPlayingTabView wires artwork changes → sampler (initial: true covers mounting with a track already playing). Rig: - R4-GLOW-D8 (own suite; the base audit is at the type-length limit): folds the grid with the clamp-box CORNER (ceiling-gray per slot at token alpha — provably ≥ any admissible palette by channel monotonicity) and asserts the SAME pairs as R4-GLOW-01/02/04 plus the lens/badge composites. Passing it proves the ENTIRE sampled color space. - SampledGlowTests: box membership, hue preservation, floors, idempotence, selection determinism/achromatic behavior, constants sanity. 204 tests / 34 suites / 0 known issues; strict-gate PASSED.
…amp/decode hardening
swiftui-pro verdict was request-changes; applied everything, plus one
strengthening beyond the review:
- BLOCKER: the palette cache key was AudioFile.relativePath — hard-coded ""
by the queue adapter for EVERY track, so the first album's palette was
served for the whole session (recolored once, never again). Key is now the
file's absolute URL (AudioFile.id).
- MAJOR-2 + stronger: the "corner dominates everything" claim missed mixed
palettes with BRAND fallbacks (brand teal's green/blue exceed the 0.62 teal
ceiling — and, beyond the review's proposed single extra fold, brand blue's
1.0 channel exceeds ITS 0.95 corner too). R4-GLOW-D8 now folds the full 2³
per-slot {corner, brand} lattice — exact coverage of the reachable union by
per-slot monotonicity, no accidental-pass margins. Comments re-scoped.
- MAJOR-3: the D8 fold now audits the panel⊕field pairs too (all three label
rungs, right side — tertiary's designed home), matching the §7 R4 table.
- MINOR-4: clamp floors evaluated on the SCALED color (what would render) —
barely-chromatic brights whose spread collapses under scale-down reject to
brand instead of rendering as sub-floor gray; clamp genuinely idempotent
now; regression case added.
- MINOR-5: loose-file embedded art decodes via CGImageSource thumbnail capped
at 512px (library-path parity) — no more full-res main-actor decode of a
3000²+ cover on track change.
- NITs: premultiplied-alpha vote semantics documented both sides;
compositeBackdrop guards a short override array instead of trapping.
204 tests / 34 suites / 0 known issues; strict-gate PASSED.
…t enters close-out
Founder confirmed per-track glow recolor on a probe-picked 6-album demo queue
("ok I see what you meant it changes") after the first test track turned out
to be a teal-graded cover sampling ≈ brand teal (pipeline verified against the
real DB artwork; 41/43 library covers sample, 2 brand-fallback by design).
B/H/missing-art/crossfade-feel ride the sprint-end A–H pass. sprint-plan
§Status updated (the one prose status surface).
…ery, stop-race, error copy
qa-expert sprint-end break-it findings, engine chunk:
- BLOCKER-1 (empirically proven): playback NEVER stopped at end of queue
(repeat off) — setNextTrack(nil) at the final seam cleared the EOF hooks,
so the last real completion was swallowed: isPlaying stayed true forever,
the footer counted past the duration, the engine rendered silence. The
three passthrough fire-sites and the resampler seam now route through
fire*EOFOrEnd: a nil hook under a LIVE epoch is provably a genuine EOF
(every non-EOF stop bumps the epoch first) → the shared ended-state
surface fires (gaplessPlaybackEnded + intent cleared) and the VM stops
cleanly. Single-track queues had the same hole (hooks never armed).
- MAJOR-1: engine target dying (mid-play disconnect / PIN re-assert failure)
left currentDeviceID pointing at the corpse — picker and engine diverged,
and every Pure viability check failed against the dead id ("Pure
unavailable" until a manual re-pick). refreshDevices now re-asserts the
surviving selection to the engine when its target is no longer listed.
- MAJOR-2: a user Stop racing a config-change re-establish could restart
audio under a stopped UI (zombie playback) — the re-establish now
re-validates the play intent immediately before touching the player and
undoes its engine restart when the intent was cleared mid-flight.
- error-0 class: the described BT-disconnect→play repro is NOT reachable on
current code (qa trace); the one reachable window — play from the menu
bar/Control Center during app-quit teardown — is closed by gating
isEngineReady FIRST in shutdown. AudioBridgeError gains LocalizedError
(the banner rendered "…error 0."), and startPlayback self-heals
engineNotInitialized via retryInitialization.
- Pure-mode lens (fool): the spectrum FROZE at the last Enhanced frame at
full opacity during Pure playback (no tap, bars never zeroed) — tickSpectrum
zeroes bars/caps once when the active path is Pure.
- MINOR-3: 20 Hz same-value writes suppressed (pollEngineReadouts with
equality guards) — signalPath/loudness observers no longer re-evaluate
20x/s while idle; makes SignalPathInfo's Equatable doc-claim true.
205 tests / 34 suites green; strict-gate PASSED.
…re hardening
The rig/UI chunk of the sprint-end break-it round (fool + qa findings):
- R4 domain drift (fool): the row-tint folds still sampled x >= 0.5 ("the
50/50 pane") — PR 5 moved the queue to the LEFT flex region, where the teal
core reaches. Row tints now audit at EVERY grid point in both the concrete
test and the D8 lattice (over-auditing is free; under-auditing was the hole).
Row tints PASS everywhere — the hole was in the model, not the pixels.
- Bleed stratum (fool→REAL defect): folding the panel's dark bottom light-
bleed measured tertiary at 4.18 < 4.5 where the inspector's last rows could
REST on it. Constraint encoded, look untouched (house doctrine): the
inspector's bottom content inset now IS GlassDecor.bleedHeight (same token,
cannot drift) — text never rests on the bleed; transient scroll crossings
accepted (seam-feather class). The lattice documents the rule.
- R4-CONTROL-01 (fool): the §7 pair-4 audit the design owed since PR 6 —
pill label + rate readout on badgeFill over the chrome band, both
appearances + the RT/IC composite. Passes.
- Queue guards (qa): Delete no longer removes a row the filter is hiding;
the drop-target highlight no longer latches when a filter is typed
mid-drag; the filter's dead relativePath candidate dropped (empty for
every library track — title-only, honestly documented).
- Tripwire hardening (fool): semgrep excludes PATH-ANCHORED (bare basenames
exempted ANY same-named file); colorspace prefix generalized (displayP3
evaded the sRGB-only group); .overlay/.border join the paint-context
alternation; NSColor.systemX statics matched; the Kit purity guard now
catches `import struct SwiftUI.Color`-style scoped imports.
- seamFeather comment corrected (the "removed in PR 6" plan didn't happen —
D4 kept the bands quiet; permanent until a shell-glass wave).
205 tests / 34 suites green; strict-gate PASSED (semgrep re-verified against
the hardened rules).
…R-5 Instruments correction)
ramith
marked this pull request as ready for review
July 18, 2026 14:10
… identical-gate promise PR #61 run 1 failed on 74 files this sprint never touched: the runner brewed SwiftFormat 0.62.1 (wrapIfStatementBodies behavior changed) against the local 0.61.1, and SwiftLint was one minor ahead too (0.65.0 vs 0.64.1) — it just never got a turn. The strict gate is only meaningful if local and hosted CI run IDENTICAL style tools (design §7.2), so: - strict-gate.sh asserts SWIFTFORMAT_PIN=0.61.1 / SWIFTLINT_PIN=0.64.1 at the tool check — any skew now fails loudly with instructions (bump the pin + reformat in the same commit) instead of red-herring style errors. - strict-ci.yml installs both tools as pinned release binaries (brew no longer supplies them), prepended on PATH ahead of the runner image's copies. Ledger: founder verified the end-of-queue fix live ("queue ended"); founder DECIDED the queue-arrows behavior stands by design ("arrows behaviour is ok").
…polish wave DEVIATIONS.md (founder-commissioned external audit vs the 8a card) moved to its referenced home under docs/design/now-playing-7a/. The companion triage (docs/sprints/s10-8-deviations-plan.md) reconciles it with the S10.7 decision record: 6 report items are stale/by-design (solid bars + caps, lens recipe, carved sliders, carved scrubber all SHIPPED; the "wrong hue" glow is D8 art-sampling working), the rest become the S10.8 part-1 PR ladder (A–G) with three founder decisions surfaced (D9 reopen for the capsule tabs — recommend yes; band strata vs real blur — recommend strata; True-peak rename only with a true-peak measurement — recommend enabling it in libebur128).
…ons recorded, §Status → S10.8 Founder 2026-07-19: capsule tabs IN (D9 reopened + decided), "perf test is ok" (Instruments R5 recipe — closes the PR-3/PR-5 deferred rows), decisions 2-3 proceeding per recommendation. Retro captures the sprint's process lessons (runtime bugs founder-caught vs static rig-caught → R3 snapshot decision moves BEFORE S10.8; tool-pin lesson; ledger-discipline leak; day-one branch push). sprint-plan §Status is the one prose surface: S10.7 closed, S10.8 two-part scope current.
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.
The S10.7 sprint branch: the 8a Liquid Glass design adopted on Now Playing + the global shell, on a permanent token foundation (founder decision: the entire GUI eventually redesigns around these tokens; R1 gates on this sprint + the S10.8 tab sweep).
What's inside (8 founder-accepted milestones + close-out)
.glassPanel(_:in:)— the ONE sanctioned surface painter (system Material for overlays; token'd translucent fills + edge strata for app glass; derived opaque fallbacks under Reduce Transparency / Increase Contrast).swift testreports 0 known issues.docs/sprints/s10-7-visual-matrix.md(the ledger).Verification
Every commit passed the local strict gate (semgrep / swiftlint --strict / periphery / clang-tidy / leaks + plant-a-leak) + 205 headless tests. Founder-accepted per milestone via the screenshot loop. Remaining before un-drafting: the founder's final full A–H matrix pass, the Instruments/FPS run, and the ledger's cell debt (expiry 2026-08-15).
Draft: opened for CI coverage + review visibility while the sprint close-out finishes.
🤖 Generated with Claude Code