Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
15 commits
Select commit Hold shift + click to select a range
1601310
feat(s10.8 PR-A): zero-risk polish batch — deviations §1/§3/§4 quick …
ramith Jul 18, 2026
31fcf94
docs(s10.8): land the Realigned Target package — the visual truth for…
ramith Jul 19, 2026
7ecc170
feat(s10.8 PR-B): capsule tab strip — the realigned toolbar identity …
ramith Jul 19, 2026
838da19
fix(s10.8 PR-B): R4-TAB suite to its own file — ContrastAuditTests hi…
ramith Jul 19, 2026
e1d3eb7
feat(s10.8 PR-C): queue header collapses to ONE 32pt row; grips are h…
ramith Jul 19, 2026
a5799c9
fix(s10.8 PR-C): header controls to their own file — PlaylistView hit…
ramith Jul 19, 2026
a590b50
feat(s10.8 PR-D): the playing row — subtle tinted card + 3-bar mini e…
ramith Jul 19, 2026
4918ae3
feat(s10.8 PR-E1): the inspector becomes a floating card; slim carved…
ramith Jul 19, 2026
2378f30
feat(s10.8 PR-E2): TRUE peak in the meter path — kernel extracted, la…
ramith Jul 19, 2026
c52deab
feat(s10.8 PR-F): hero badges consolidate to two chips — teal path ch…
ramith Jul 19, 2026
6968a1b
feat(s10.8 PR-G): styled-glass bands + glossy play + pulsing footer dot
ramith Jul 19, 2026
138282a
docs(s10.8): realign ledger — PR-A..G record, 14 deliberate deviation…
ramith Jul 19, 2026
3623af1
fix(s10.8): break-it round — hit targets, DT headroom, meter gating, …
ramith Jul 19, 2026
006d259
fix(s10.8): founder round 1 — tab strip to the RIGHT edge; pill absor…
ramith Jul 19, 2026
96b5cba
fix(s10.8): queue keyboard cursor decoupled from now-playing; retire …
ramith Jul 19, 2026
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
2 changes: 1 addition & 1 deletion Sources/AdaptiveSound/AudioEngineBridge.swift
Original file line number Diff line number Diff line change
Expand Up @@ -295,7 +295,7 @@ final class AudioEngineBridge: AudioPlaybackEngine, @unchecked Sendable {
return LoudnessSnapshot(
integratedLufs: readout.integratedLufs,
shortTermLufs: readout.shortTermLufs,
peakDb: readout.peakDb
truePeakDb: readout.truePeakDb
)
}

Expand Down
100 changes: 93 additions & 7 deletions Sources/AdaptiveSound/DesignSystem.swift
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,11 @@ enum DesignSystem {
static let blue = from(Palette.blue)

/// Surfaces (elevation stack) — dark = pre-S9-T (#1E1E1E window, pre-D10); light =
/// first pass (gray base, white raised cards, darker inset).
/// first pass (gray base, white raised cards, darker inset). (`panel` had one
/// consumer, the footer band background, which S10.8 PR G replaced with the
/// styled-glass strata; the Kit `Palette.panel` token stays for the R4 audit.)
static let window = from(Palette.window)
static let card = from(Palette.card)
static let panel = from(Palette.panel)
static let hairline = from(Palette.hairline)

// Labels — secondary + tertiary lifted so BOTH clear WCAG AA (≥4.5:1) on the
Expand All @@ -76,12 +77,13 @@ enum DesignSystem {
static let labelTertiary = from(Palette.labelTertiary)
static let labelDisabled = from(Palette.labelDisabled)

/// Status FILL/indicator colors (non-text 3:1): `statusWarning` #FF9F0A vibrant orange,
/// `statusError` the macOS-26 palette red (#FF383C/#FF4245). PR 6 (founder "split text
/// vs fill"): TEXT/glyph sites use the darker AA-legible variants below instead — the
/// vivid values are for the meter hot-bar and status dots.
/// Status FILL/indicator color (non-text 3:1): `statusWarning` #FF9F0A vibrant orange
/// (the Pure-fallback / interrupted dots). PR 6 (founder "split text vs fill"):
/// TEXT/glyph sites use the darker AA-legible variants below. (The `statusError` red
/// FILL lost its consumer when S10.8 PR E replaced the sample-peak CLIP bar with the
/// true-peak amber meter — `statusErrorText` still carries error TEXT in the sidebar;
/// the Kit `Palette.statusError` stays for the R4 audit.)
static let statusWarning = from(Palette.statusWarning)
static let statusError = from(Palette.statusError)
/// Status TEXT/glyph variants (AA 4.5:1). Differ from the fill values only in LIGHT
/// (on the deep dark base the vivid values already clear text AA).
static let statusWarningText = from(Palette.statusWarningText)
Expand All @@ -92,6 +94,28 @@ enum DesignSystem {
/// TOK-04), so appearance-independent like `accent`.
static let rowNowPlaying = from(Palette.rowNowPlaying)
static let rowSelected = from(Palette.rowSelected)

/// The small-control hover wash (S10.8 PR B): re-exports the badge fill — the same
/// white-8%/dark-6% wash the realigned mock uses for hovered chrome controls, so
/// hover states never mint a second near-identical value.
static let hoverWash = from(Palette.badgeFill)

// S10.8 PR C — queue-header control chips (realigned `png/03`); audited R4-CHIP-01.
static let accentText = from(Palette.accentText)
static let controlHover = from(Palette.controlHover)
static let controlActiveFill = from(Palette.controlActiveFill)
static let segmentSelected = from(Palette.segmentSelected)

/// S10.8 PR D — the playing row (realigned `png/04`); audited R4-ROW-01.
static let accentTitle = from(Palette.accentTitle)
/// The mini equalizer's bar teal — the iconFill gradient's bright stop, re-exported
/// (never a second #3FD0BA).
static let accentBright = from(Palette.iconFillTop)

/// S10.8 PR E — the true-peak hot VALUE text (realigned `png/05`); audited
/// R4-METER-01. The hot FILL is `Gradient.meterHotFill` (which composes the Kit
/// `Palette.meterHot` stop inline), so there is no solid `meterHot` re-export.
static let meterHotText = from(Palette.meterHotText)
}

// MARK: Typography (semantic scale mapped to Dynamic Type text styles)
Expand All @@ -118,6 +142,8 @@ enum DesignSystem {
/// Uppercase section labels — pair with `.tracking(0.5).textCase(.uppercase)`.
static let micro = SwiftUI.Font.system(.subheadline, weight: .semibold) // ~11
static let monoSmall = SwiftUI.Font.system(.subheadline, design: .monospaced) // ~11 mono
/// Micro mono captions (8a slider end-labels) — Dynamic-Type-mapped, never a fixed 9.5.
static let monoMicro = SwiftUI.Font.system(.caption2, design: .monospaced) // ~10 mono
/// Compact now-playing pairing (footer transport / mini-player): title over subtitle.
static let trackTitle = SwiftUI.Font.system(.headline) // ~13 semibold
static let trackSubtitle = SwiftUI.Font.system(.subheadline) // ~11
Expand Down Expand Up @@ -153,6 +179,29 @@ enum DesignSystem {
startPoint: .top,
endPoint: .bottom
)

/// The realigned horizontal teal fill (S10.8 PR E — `asTealMeter`): mid → bright,
/// leading → trailing. Shared by the loudness meters, the carved sliders, and the
/// footer scrubber's playing state — the same two iconFill stops, re-composed.
static let meterFill = LinearGradient(
gradient: SwiftUI.Gradient(colors: [
Color.from(Palette.iconFillMid),
Color.from(Palette.iconFillTop),
]),
startPoint: .leading,
endPoint: .trailing
)

/// The true-peak meter's HOT fill (> −1 dBTP): teal running into the amber tail
/// (realigned `png/05`). Stops: the bright teal → `meterHot`.
static let meterHotFill = LinearGradient(
gradient: SwiftUI.Gradient(colors: [
Color.from(Palette.iconFillTop),
Color.from(Palette.meterHot),
]),
startPoint: .leading,
endPoint: .trailing
)
}

// MARK: Shell metrics (app-owned window chrome bands)
Expand Down Expand Up @@ -259,5 +308,42 @@ enum DesignSystem {
static let gripSymbol: CGFloat = 13 // drag-handle glyph point size
static let gripHitWidth: CGFloat = 22 // grip hover/drag hit target
static let gripHitHeight: CGFloat = 26
/// Hover-revealed grip opacity (S10.8 PR C — realigned: no handles at rest).
static let gripHoverOpacity: Double = 0.45
}

// MARK: Loudness meter mapping (S10.8 PR E — the realigned meter rows, `png/05`)

/// Display mapping for the inspector's loudness meter bars. Floors reproduce the mock's
/// fractions exactly: −15.9 LUFS → 62%, −24.3 → 41% (floor −42); −0.8 dBTP → 93%,
/// −3.2 → 74% (floor −12). The hot threshold equals the limiter's default −1 dBTP
/// ceiling — the meter warns exactly where the safety layer clamps.
enum Meters {
static let lufsFloor: Double = -42
static let truePeakFloorDb: Double = -12
static let hotThresholdDbtp: Double = -1.0
/// "The true-peak row has a live measurement" — the bridge's −120 dB sentinel, with
/// margin. Independent of the LUFS `hasSignal` gate (a start-of-track transient
/// must never wait 400 ms of gated blocks to warn).
static let peakMeterFloorDb: Double = -110
static let labelColumnWidth: CGFloat = 82
}

// MARK: Queue header metrics (S10.8 PR C — the realigned single-row header, `png/03`)

/// Everything above the queue list collapses into ONE row of this height: title + count +
/// icon chips + the Up Next/Recent capsule pair + the right-aligned compact filter pill.
enum QueueHeader {
static let height: CGFloat = 32
static let iconButton: CGFloat = 28 // square chip, Radius.control corners
static let iconSymbol: CGFloat = 12
static let segmentHeight: CGFloat = 20 // + 2×segmentPadding = the 24pt pair
static let segmentPadding: CGFloat = 2
/// The filter pill: 190pt ideal (the mock), compressing to min so the header row
/// survives the LAY-01 minimum queue width (90: leaves the count subtitle ~50pt at
/// the 880pt window's worst case — the subtitle is the designated truncation victim).
static let filterIdealWidth: CGFloat = 190
static let filterMinWidth: CGFloat = 90
static let filterHeight: CGFloat = 28
}
}
154 changes: 148 additions & 6 deletions Sources/AdaptiveSound/DesignSystemGlass.swift
Original file line number Diff line number Diff line change
Expand Up @@ -85,11 +85,13 @@ struct CarvedGroove: View {
let fillFraction: Double
/// Track (groove) thickness.
var height: CGFloat = .init(GlassDecor.carvedTrackHeight)
/// The progress-fill color (accent for the sliders; the scrubber passes its play/pause/
/// interrupted state color).
var fillColor: SwiftUI.Color = DesignSystem.Color.accent
/// Whether the fill carries the dark-only teal glow (off when the fill isn't the accent —
/// e.g. the scrubber paused/interrupted — so a teal glow never sits under a grey fill).
/// The progress fill (S10.8 PR E: the realigned mid→bright teal gradient by default —
/// sliders, meters, and the playing scrubber share it; the scrubber's paused/
/// interrupted states pass their solid state colors instead).
var fillStyle: AnyShapeStyle = .init(DesignSystem.Gradient.meterFill)
/// Whether the fill carries the dark-only teal glow (off when the fill isn't the accent
/// family — e.g. the scrubber paused/interrupted — so a teal glow never sits under a
/// grey fill).
var glow: Bool = true

@Environment(\.colorScheme) private var colorScheme
Expand All @@ -116,7 +118,7 @@ struct CarvedGroove: View {
.frame(height: height)

Capsule()
.fill(fillColor)
.fill(fillStyle)
.frame(width: geo.size.width * clamped, height: height)
.shadow(color: (dark && glow) ? SwiftUI.Color(token: GlassDecor.sliderGlowDark) : .clear,
radius: 5)
Expand Down Expand Up @@ -173,6 +175,146 @@ struct CarvedTrack: View {
}
}

// MARK: - Inspector card glow (S10.8 PR E — realigned `png/05`)

/// The teal radial glow behind/below the floating inspector card: rendered only when the
/// glow field itself is visible (dark + no Reduce Transparency — `GlowFieldGate`), and
/// extending past the card's bottom so the empty area under the hugged card reads
/// intentional. Appearance-gated, so it lives in this sanctioned file.
struct InspectorCardGlow: View {
var body: some View {
GlowFieldGate {
RadialGradient(
colors: [SwiftUI.Color(token: GlassDecor.inspectorGlowDark), .clear],
center: .center,
startRadius: 0,
endRadius: CGFloat(GlassDecor.inspectorGlowRadius)
)
.padding(.bottom, -CGFloat(GlassDecor.inspectorGlowBleed))
.blur(radius: CGFloat(GlassDecor.inspectorGlowBlur))
}
}
}

// MARK: - Capsule tab strip visuals (S10.8 PR B — the realigned 8a tab selector)

/// The tab strip's carved track: the `tabTrack` token fill + the shared carved top inner
/// shade (the same inset-shadow cue as `CarvedGroove` — one carved grammar, two consumers).
/// Appearance-aware, so it lives in this sanctioned file.
struct TabTrackCapsule: View {
@Environment(\.colorScheme) private var colorScheme

var body: some View {
let dark = colorScheme == .dark
Capsule()
.fill(SwiftUI.Color(token: Palette.tabTrack.value(for: dark ? .dark : .light)))
.overlay(alignment: .top) {
LinearGradient(
colors: [SwiftUI.Color(token: dark ? GlassDecor.carvedShadeDark
: GlassDecor.carvedShadeLight), .clear],
startPoint: .top, endPoint: .bottom
)
.frame(height: 2)
}
.clipShape(Capsule())
}
}

/// A teal "glossy button" fill for any insettable shape: the brand `iconFill` gradient
/// (the realigned "teal button" IS this gradient — byte-identical stops), the shared
/// specular top rim, and a dark-only teal glow (grammar rule 6: light drops emissive
/// cues). Consumers: the active tab capsule (PR B) and the footer play circle (PR G).
struct TealGloss<GlossShape: InsettableShape>: View {
var shape: GlossShape

@Environment(\.colorScheme) private var colorScheme

var body: some View {
let appearance: TokenAppearance = colorScheme == .dark ? .dark : .light
shape
.fill(DesignSystem.Gradient.iconFill)
.overlay {
shape.strokeBorder(
LinearGradient(
colors: [SwiftUI.Color(token: GlassDecor.rim.value(for: appearance)), .clear],
startPoint: .top, endPoint: .center
),
lineWidth: 1
)
}
.shadow(color: appearance == .dark ? SwiftUI.Color(token: GlassDecor.tabActiveGlowDark) : .clear,
radius: CGFloat(GlassDecor.tabActiveGlowRadius),
x: 0,
y: CGFloat(GlassDecor.tabActiveGlowOffsetY))
}
}

/// The active tab's teal capsule (S10.8 PR B) — the capsule instance of `TealGloss`.
struct ActiveTabCapsule: View {
var body: some View {
TealGloss(shape: Capsule())
}
}

// MARK: - Chrome band styled glass (S10.8 PR G — realigned `png/01` + `png/06`)

/// The app-owned chrome bands' "styled glass" (founder decision 2: fill strata, NOT real
/// blur — nothing scrolls behind these bands). Dark: the window base + a sheen gradient
/// lit from `lightFrom`, a 1px specular line on the band's TOP edge, and (header only) a
/// dark seam on its bottom edge. Light: the plain window + hairline seams — the grammar
/// drops emissive cues. Owns what AppShell's `.background` + `Hairline` overlays did.
struct ChromeBandModifier: ViewModifier {
/// `.top` for the header (lit from its top edge), `.bottom` for the footer.
var lightFrom: UnitPoint = .top

@Environment(\.colorScheme) private var colorScheme

func body(content: Content) -> some View {
let dark = colorScheme == .dark
content
.background {
ZStack {
DesignSystem.Color.window
if dark {
LinearGradient(
colors: [SwiftUI.Color(token: GlassDecor.bandSheenStrong),
SwiftUI.Color(token: GlassDecor.bandSheenWeak)],
startPoint: lightFrom,
endPoint: lightFrom == .top ? .bottom : .top
)
}
}
}
// The specular line ALWAYS sits on the band's top edge (the edge that catches
// light — the realign guide's StyledGlassBar contract). Light keeps only the
// SEPARATING hairlines the bands had before (footer top; header top is the
// window edge — nothing to separate).
.overlay(alignment: .top) {
if dark || lightFrom == .bottom {
Rectangle()
.fill(dark ? SwiftUI.Color(token: GlassDecor.bandSpecularDark)
: DesignSystem.Color.hairline)
.frame(height: 1)
}
}
.overlay(alignment: .bottom) {
if lightFrom == .top { // header: dark seam against the content below
Rectangle()
.fill(dark ? SwiftUI.Color(token: GlassDecor.bandSeamDark)
: DesignSystem.Color.hairline)
.frame(height: 1)
}
}
}
}

extension View {
/// Apply the realigned chrome-band treatment (see `ChromeBandModifier`).
func chromeBand(lightFrom: UnitPoint) -> some View {
modifier(ChromeBandModifier(lightFrom: lightFrom))
}
}

/// The sanctioned environment→resolver wiring for the glow field (this file is the one
/// place appearance may be read — semgrep rule 4). Renders `content` only when the pure
/// `glowFieldIsVisible` resolver says so (dark + no reduced-transparency request).
Expand Down
8 changes: 5 additions & 3 deletions Sources/AdaptiveSound/Loudness/LoudnessSnapshot.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,17 @@ import Foundation

/// A point-in-time loudness readout for the UI meters, produced off the audio
/// thread by the BS.1770-5 `LufsMeter` (via the C bridge) and polled by the view
/// model. All values are LUFS except `peakDb` (sample-peak dBFS).
/// model. All values are LUFS except `truePeakDb` — inter-sample TRUE peak in
/// dBTP (8× polyphase ISP, the shared `TruePeakKernel`; sample-peak before
/// S10.8 PR E — the "True peak" meter label is honest because of this field).
struct LoudnessSnapshot: Equatable {
var integratedLufs: Double
var shortTermLufs: Double
var peakDb: Double
var truePeakDb: Double

/// Sentinel before any measurement exists (engine stopped / silence).
static let unmeasured = LoudnessSnapshot(
integratedLufs: -200, shortTermLufs: -200, peakDb: -120
integratedLufs: -200, shortTermLufs: -200, truePeakDb: -120
)

/// True once the meter has produced a real integrated value.
Expand Down
13 changes: 3 additions & 10 deletions Sources/AdaptiveSound/TabSelection.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,14 +12,7 @@ enum TabSelection: String, CaseIterable {
var id: String {
rawValue
}

var icon: String {
switch self {
case .nowPlaying: "play.circle.fill"
case .library: "square.grid.2x2"
case .eq: "slider.horizontal.3"
case .monitoring: "waveform.and.magnifyingglass"
case .settings: "gearshape.fill"
}
}
// No `icon`: the realigned tab strip (S10.8 PR B) is text-only capsules — the old
// per-tab SF Symbols were for the retired segmented Picker. Re-add with a consumer if
// a future surface (e.g. a menu) needs tab glyphs.
}
15 changes: 11 additions & 4 deletions Sources/AdaptiveSound/UI/FormatBadgeView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,13 +8,20 @@ struct FormatBadgeView: View {
let format: String
var isSelected: Bool = false

/// 8a metric (18pt chip) — @ScaledMetric so larger text sizes grow the chip instead of
/// clipping (break-it finding 3: the fixed frame regressed the old padding-based chip).
@ScaledMetric(relativeTo: .subheadline) private var chipHeight: CGFloat = 18

var body: some View {
// 8a metrics (deviations §3): 18pt capsule-ish chip, radius = height/2.
// Selected/current tint (S10.8 PR D, realigned `png/04`): the audited chip pair —
// accentText on the accent-derived controlActiveFill (R4-CHIP-01).
Text(format)
.font(DesignSystem.Font.micro)
.padding(.horizontal, 6)
.padding(.vertical, 3)
.background(isSelected ? Color.asAccent.opacity(0.2) : Color.asCard)
.foregroundStyle(isSelected ? Color.asAccent : Color.asLabelSecond)
.clipShape(.rect(cornerRadius: 4, style: .continuous))
.frame(height: chipHeight)
.background(isSelected ? DesignSystem.Color.controlActiveFill : Color.asCard)
.foregroundStyle(isSelected ? DesignSystem.Color.accentText : Color.asLabelSecond)
.clipShape(.rect(cornerRadius: chipHeight / 2, style: .continuous))
}
}
Loading
Loading