Skip to content

tabs: rebuild sidebar around native tab surfaces - #180

Open
khoi wants to merge 32 commits into
mainfrom
tabbar
Open

tabs: rebuild sidebar around native tab surfaces#180
khoi wants to merge 32 commits into
mainfrom
tabbar

Conversation

@khoi

@khoi khoi commented Aug 7, 2026

Copy link
Copy Markdown
Contributor

Why

The sidebar stored tab order, group membership, selection, and drag state across nested view-facing models. A move that crossed a group, space, or window had to rebuild state in several places, which made identity, live terminal ownership, and drop behavior hard to keep in sync. The SwiftUI window shell also left native window, panel, and drag behavior split across unrelated layers.

This replaces that base with one normalized tab topology and a native macOS shell. Views derive their rows from snapshots, while moves validate topology revisions and commit the same tab, split tree, terminal surfaces, notification state, and agent state as one operation.

What changed

Tabs and groups now share stable identities in one collection model. The same move rules handle reorder, regroup, cross-space transfer, cross-window transfer, split creation, and detach-to-window without cloning live terminal state.

The window now owns native docked and floating sidebar controllers, a native space strip, a key command panel, shared drag previews, split targets, and desktop drop handling. Space controls keep their AppKit identity across updates, expose configured shortcuts, support reorder and tab drops, and leave blank titlebar areas available for window dragging.

Note

This changes only macOS app tab and sidebar behavior. It does not change socket, CLI, documentation, website, release, or integration-skill contracts. UI automation remains CI-only.

Demo

Floating sidebar Command panel Split drop target
Floating sidebar Command panel Split drop target

Verification

make mac-test passes the full unit suite, including normalized topology, atomic live transfer, shell geometry, native hit testing, menu routing, and drag payload coverage. make mac-test-snapshots passes all 136 light and dark references, including the new split target and updated sidebar, space strip, floating shell, and command panel surfaces.

make mac-check passes formatting and lint. make mac-scan-dead-code reports no unused code. Local UI tests were not run because this repository runs them only in CI.

@tuist

tuist Bot commented Aug 8, 2026

Copy link
Copy Markdown

🛠️ Tuist Run Report 🛠️

Builds 🔨

Scheme Status Duration Commit
supatermUITests 1m 26s ecd33849e

khoi added 28 commits August 9, 2026 23:49
Cross-window moves need source and destination validation before either
collection changes. Project both revisions into a transfer plan, commit the
pair only while both inputs still match, and publish an immutable snapshot
for sidebar controllers.
A direct drag on an unselected tab must carry that tab while leaving the
prior tab as the live split host. The old split fallback inferred a host
from stored history, and the drag could start before its compositor image
was ready.

Resolve the source image before native activation, restore the one-use
selection handoff at the final start boundary, and accept only the exact
live split target. This keeps capture, selection, and drop state in one
ordered gesture.
A sidebar row drag selected the prior tab before the pointer left the
sidebar, so local reorders made the lifted row jump away.

Keep the dragged tab selected for local movement. A valid one-tab content
destination now consumes the prior selection once before it resolves the
split host.
The source surface frame was frozen before capture completed. Moving or
resizing the window during a drag could switch previews at the old edge.

Read the source view in screen coordinates for each drag move and pass
that frame straight to the presentation registry.
The test host may be inactive, so a non-activating panel cannot become the global key window during the full suite. Validate the configured action through the intended first responder without relying on process activation.
Pane updates can bind a fresh sidebar row while the original host is lifted. Restoring the old host then reuses stale pointer state and cancels the next drag.

Give each lift ownership of its exact host so new bindings and later lifts invalidate old restores.
The docked and floating branches mounted one cached AppKit controller
through two SwiftUI representable sites. Teardown from the old site could
remove the controller after the new site had mounted it, leaving the
current space empty until another space remounted it.

Keep one sidebar subtree and vary only its shell values so the collection
controller retains one owner across hide and show transitions.
khoi added 4 commits August 9, 2026 23:49
Local reorder waited for screen capture, while external success shared
cancellation cleanup. That could restore stale rows after a transfer and
let destination topology move under an active drop.

Start the native session at the gesture threshold, update its preview
when capture resolves, and bind each external result to its source state
and hovered topology. Restore captured rows only when the drag cancels.
Whole-group detach already uses the shared drag and transfer path, but no
test crossed both boundaries. Lock the group payload and empty-window
transfer semantics so later single-tab gates cannot break it.
After the native shell cutover, SwiftUI still declared the old spring but
no longer owned the child frames. The shell used a generic easing curve,
so the toggle lost its prior motion.

Drive position and bounds from the native frame owner, seed interrupted
toggles from presentation layers, and keep resize and Reduce Motion
immediate. Share the Core Animation builders with split-drop motion.
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