fix(ui): slide the detail tab indicator instead of morphing glass - #386
Merged
Conversation
The detail tab bar played an exaggerated stretch on every tab switch, worst on the four-tab container view, and the first switch after a cold launch was worse still. GlassEffectContainer's spacing is a blend threshold, not a layout gap: the higher it is, the sooner shapes start merging as they approach. It was set to tabCount * detailTabSegment -- 320pt for containers, 480pt for sandboxes -- against an HStack spacing of 2pt, so the blend field spanned the whole bar. Apple documents that a container spacing larger than the interior stack's spacing blends the effects together at rest. The indicator also toggled glassEffect between .regular and .identity per segment under one shared glassEffectID. That ID stays constant while the segment beneath it changes, which is the documented trigger for GlassEffectTransition.matchedGeometry to apply "additional scale and offset effects to content" -- on top of the oversized blend. Render one persistent glass capsule instead and position it with matchedGeometryEffect against the selected segment. The pill now moves rather than appearing and disappearing, so there is no morph to overshoot and no appearance transition to misfire on first mount. GlassEffectContainer is no longer needed for a single effect.
Greptile SummaryThe PR replaces the per-segment Liquid Glass morph with one persistent capsule that follows the selected detail tab using matched geometry, while preserving reduced-motion and accessibility behavior.
Confidence Score: 5/5The PR appears safe to merge. No blocking failure remains.
|
| Filename | Overview |
|---|---|
| ArcBox/Components/DetailTabPicker.swift | Reworks the shared detail-tab indicator into a single matched-geometry glass capsule without an accepted blocking issue. |
| CHANGELOG.md | Adds release highlights without affecting application behavior. |
Reviews (2): Last reviewed commit: "chore(release): curate the 1.35.0 highli..." | Re-trigger Greptile
The 1.35.0 release PR merged with an empty `### Highlights` section, so `cargo xtask release notes` now fails on every PR against master. That step runs before Lint, Build and Test, so no PR gets as far as compiling. The release DMG extracts the same section to feed Sparkle's update dialog and refuses to build without it, so 1.35.0 cannot be packaged either. Write the section from what 1.35.0 actually shipped: the Fleet device auth, enrollment and runner management in #320, the authoritative sandbox port reconciliation in #374, and the telemetry identification and Privacy toggle ordering in #367.
AprilNEA
added a commit
that referenced
this pull request
Aug 13, 2026
…er (#393) * ci: fail the highlights gate only where it protects a release 581a33c added this check to catch a missing `### Highlights` while it was still an edit, because v1.34.2 through v1.34.4 each cut a tag and a GitHub release with no DMG behind them. That part works and stays. What it did not account for is that release-please never writes that section, so every release PR fails this by construction — and the version comes from the manifest, so once one merged red, every other PR failed the same way, at a step ahead of Lint, Build and Test. Nothing reached the compiler for 22 hours after 1.35.0, and #386 had to smuggle the changelog fix into a UI PR to verify itself. Keep it fatal on the release PR and on master, where it guards the tag and says master is unshippable. Everywhere else report the same finding as a warning: still visible, no longer everyone's outage. Both paths now spell out what to add and why. * feat(release): build notes from highlights and the change list together Highlights were the *only* source of release notes, so a release without them had nothing to ship and `release notes` refused to build — which is why every release PR failed by construction and why v1.34.2 through v1.34.4 each cut a tag with no DMG behind it. The prose and the list answer different questions. Curated Highlights say why to update; the Features and Bug Fixes entries say what changed. Emit both, in that order, and let either be absent. Only a release with neither now fails, which is a real signal rather than a standing tax: a bare engine bump lands under Miscellaneous and produces no user-facing entry, so a human still has to say what it means. Section titles reuse ChangelogParser's user-facing set, so the About window and the update dialog cannot disagree about what counts. The `([#387](…)) ([abc1234](…))` trailers are dropped — right for a changelog, noise in an update dialog.
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.
Problem
The detail tab bar in the toolbar played an exaggerated stretch on every tab switch — most visible on the four-tab Containers view — and the first switch after a cold launch was worse still. The Runners detail bar was unaffected because it uses a native segmented
Picker, where AppKit owns the indicator.Affected views: Containers, Volumes, Images (Docker), Sandboxes, Machines — everything going through
DetailTabPicker.Root cause
Two independent departures from the documented Liquid Glass contract, compounding:
1.
GlassEffectContainer(spacing:)was being used as a layout gap. It is a blend threshold — Apple: "As shapes near one another, their paths start to blend into one another. The higher the spacing, the sooner blending begins." It was set totabCount * AppMetrics.detailTabSegment:Applying Liquid Glass to custom views: "A spacing value on the container that's larger than the spacing of an interior HStack … causes Liquid Glass effects to blend together at rest." Apple's own example pairs
GlassEffectContainer(spacing: 40)withHStack(spacing: 40). The blend field here covered the entire bar, so the indicator blended against segments it was nowhere near.2. One shared
glassEffectIDover a per-segment.regular↔.identitytoggle.GlassEffectTransition.matchedGeometry: "this transition applies additional scale and offset effects to content when the identity of the shape does not change but its content does.""selected-detail-tab"stayed constant while the segment beneath it changed — precisely that trigger.Fix
Render one persistent glass capsule and position it with
matchedGeometryEffectagainst the selected segment: each segment is a geometry source, and a singleColor.clear.glassEffect(.regular.interactive())background followsid: selection.The pill now moves instead of appearing and disappearing, so there is no morph to overshoot and no appearance transition to misfire on first mount.
GlassEffectContaineris dropped — it exists to blend multiple effects, and there is now one.Animation also tightened from
0.3sto0.2s, closer to the native indicator's pace.reduceMotionhandling and theaccessibilityRepresentationsegmentedPickerare unchanged.Verification
make build,make lint(0 violations),make test(91 tests / 10 suites) all pass on this branch.Note for reviewers
The pill reads as a cleaner slide than before — the liquid squish is gone. That is intentional, and tunable independently of the bug. If a glassier feel is wanted back, the lever is the animation curve, not the container spacing.
Also included: curate the 1.35.0 highlights
This PR carries a second, unrelated commit because CI on
masteris currently red for every PR and this one cannot be verified without it.Release PR #382 (
chore(master): release 1.35.0,4e034ec) merged with its own check already red, leaving## [1.35.0]inCHANGELOG.mdwithout a### Highlightssection while.release-please-manifest.jsonnames1.35.0. pr.yml:90 therefore fails withrelease 1.35.0 has no non-empty ### Highlights section, and that step runs beforeLint,Build (Debug)andRun Tests— so no PR reaches the compiler. The release DMG extracts the same section to feed Sparkle's update dialog and refuses to build without it, so 1.35.0 cannot be packaged either.The added section is written from what 1.35.0 actually shipped — Fleet device auth and runner management (#320), authoritative sandbox port reconciliation (#374), and telemetry identification with the Privacy toggle ordering (#367). Build, docs and CI-only entries are left out; this text is what users see in the update dialog.
cargo xtask release notes --version "$(jq -r '."."' .release-please-manifest.json)"— the exact CI invocation — now succeeds. Verified green on its own branch before consolidation:Build & Testpassed in 8m48s withVerify curated release highlights,Lint,Build (Debug)andRun Testsall ✓.Reviewers: the changelog wording is the part worth reviewing, since it ships verbatim to users. One sentence is a deliberate judgment call — it states plainly that a signed-in account is identified by email and name while analytics is on. #367 made that change and also corrected the Privacy toggle's caption, which had claimed no personal data was collected. Reword or drop it if you'd rather it not appear there.
Happy to split this back out if you'd rather land the unblock on its own — it was originally #389.