Nest sidebar worktrees by branch with onboarding card#324
Merged
Conversation
Adds a View menu toggle ("Nest Worktrees by Branch", default on) that
visually nests sidebar worktrees by the / components of their branch
names. Headers collapse/expand with animation; collapsed headers
aggregate notification, running-script, and agent indicators from
their descendants. Collapse state persists per repo and per bucket in
sidebar.json. While nesting is on, rows sort alphabetically and drag
reorder is suppressed for affected buckets; toggling off restores the
custom order and collapse state survives.
Extracts the shared sidebar ping-dot views into their own file so the
leaf row and the group header share one source of truth, and fixes a
pre-existing hardcoded lineWidth in those types to use pixelLength.
…onboarding Introduces a generic SidebarCard primitive plus a SidebarBottomCardView coordinator that hosts the pinned bottom-of-sidebar slot one card at a time. Refactors the existing coding-agents card onto the framework and exposes a static resolveMode(...) so the coordinator can probe it without rendering. Adds a new low-priority onboarding card teaching the new branch-nesting default: it shows when nesting is on, points at the menu location for the toggle (no inline disable button, the friction is intentional), and treats both the dismiss X and toggling nesting off via the View menu as permanent dismissals so re-enabling nesting later doesn't bring the prompt back.
Sidebar branch-nesting - Scope branchName fan-out per-leaf in SidebarItemGroupView so trie rebuilds no longer re-fire on unrelated leaf ticks (notification, agent storm, running-script update). - Auto-uncollapse ancestor prefixes on Reveal in Sidebar / deeplink selection so the row never lands behind a collapsed group header. - Prune collapsedBranchPrefixes against the live branch set during sidebar reconcile so dead entries don't accumulate in sidebar.json across worktree rename / removal. - Reject .archived bucket and unknown-repo IDs in branchNestExpansionChanged so stale UI / deeplinks can't write phantom collapse state. - Wrap collapsedBranchPrefixes decode in try? so a malformed value drops just the field instead of nuking the whole sidebar layout. - Add ancestorPrefixes(of:) helper plus tests covering case-sensitive grouping (Feature/x and feature/x stay distinct), reveal expansion, reducer guards, and malformed-payload decode. Branch-nesting code organization - Move buildRows / aggregateIndicators / Row / GroupIndicators into a caseless enum SidebarBranchNesting namespace. - Aggregator view collects scoped leaf reads into LeafIndicatorSnapshot values and delegates to the pure aggregateIndicators(from:), so the tested algorithm is the one production runs. - Move sidebarNestIndentStep into SidebarNestLayout.indentStep. Bottom-card framework - Drop SidebarCard.actions slot; unify dismiss API as onDismiss: (() -> Void)? so a non-functional X can't ship. - Share cardRelevantSinceDate gating via SidebarCardRelevance.isDismissed. - Nest ResolvedCard inside SidebarBottomCardView.Slot. Build transitionToken off case names instead of SkillAgent.rawValue and preconditionFailure on the unreachable .agent(.hidden) arm. - Drop dead @Environment(\.backgroundProminence) on the indicator view. Onboarding card - Move the toggle-off permadismiss off SidebarBottomCardView.onChange and onto the SidebarCommands menu Toggle binding so it still fires when the sidebar column is hidden. - Mention "toggle off to restore custom ordering" in the description. Docs - Add a Code Guidelines bullet to AGENTS.md banning top-level free functions in favor of static members on caseless enums / extensions.
orderedSidebarItemIDs (which feeds worktreeID(byOffset:), select-next / select-previous, the worktree shortcut hints, and the menu-bar Select Worktree submenu) was reading the raw drag-order out of sidebarGrouping.bucketsByRepository. With branch nesting on, that order diverges from what the sidebar actually renders: the visible list is alphabetical and rows inside collapsed group headers are hidden, but hotkeys still walked the underlying custom order including hidden rows. Add @shared(.appStorage("sidebarNestWorktreesByBranch")) to RepositoriesFeature.State so the reducer reads the same toggle the View menu binds to, then rework orderedSidebarItemIDs to: - Match the rendered visual order: main, pinned-tail, pending, unpinned-tail. - When nesting is on for a git repo, run the pinned- and unpinned-tail runs through SidebarBranchNesting.buildRows and keep only the visible .leaf IDs, so hotkeys land on the same row the user sees and skip anything inside a collapsed group. - Fall back to the raw custom drag order when nesting is off. Add a reducer test that pins all three branches (nesting-on alphabetical order, collapsed-group skip, nesting-off restores custom order).
Arrow navigation - When the selected worktree is hidden behind a collapsed group, worktreeID(byOffset:) now anchors off the unfiltered ordered list and walks toward the nearest visible neighbor in the direction of travel instead of jumping to the top / bottom of the list. Adds a private ignoreCollapsedGroups: Bool flavor on orderedSidebarItemIDs that keeps the visual partition but skips the trie's collapse filtering. - New test pins both forward and backward nearest-visible-neighbor resolution. Hotkey path hardening - visibleBranchNestingRowIDs (renamed branchNestingRowIDs to drop the redundant "visible") now builds the branchName lookup with Dictionary(_, uniquingKeysWith:) instead of uniqueKeysWithValues: so a transient duplicate row id during state transitions can't trap the arrow-key path. Slot.transitionToken - The unreachable .agent(.hidden) arm now returns a stable "agent:hidden" string instead of preconditionFailure. A future debug surface that constructs the variant directly no longer crashes the render path; identity stays distinct so .animation(_:value:) still fires correctly. Code organization - Move sidebarItemGroups(in:repositoryID:) onto SidebarItemGroup.slots and shortcutIndex(for:) onto SidebarShortcutIndex.build per the new AGENTS.md "no top-level free functions" rule. The file that adds the rule no longer ships its own offenders. - Introduce a typed SharedReaderKey.sidebarNestWorktreesByBranch extension; all four read sites (State, View menu binding, sidebar view, bottom-card host) now go through the typed handle so the key string + default value can't drift. - Inline the SidebarBranchNestingRowView construction at the three ForEach branches; drop the @ViewBuilder helper method per the project rule that SwiftUI subviews must be dedicated View structs. Docstrings - orderedSidebarItems vs orderedSidebarItemIDs now cross-reference each other and explain why the heavy flavor intentionally surfaces the raw curated order regardless of UI collapse state (command palette / multi-select consumers want that, not the visible alphabetical projection). - pruneCollapsedBranchPrefixes docstring now narrows its claim and explains why a chain-collapsed single-link prefix is intentionally preserved (so a future sibling-branch addition pre-seeds the saved collapse state). Tests - Split the previous omnibus orderedSidebarItemIDs test into three focused tests (alphabetizes, skips collapsed groups, restores custom order when nesting is off) sharing a single setup helper. - Add coverage for pending worktrees rendering between pinned-tail and unpinned-tail when nesting is on.
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.
Summary
/-separated branch components into collapsible nested headers (e.g.feature/tools/apiandfeature/tools/webland under a singlefeature/toolsgroup). Default ON, togglable from View → Nest Worktrees by Branch. Per-prefix collapse state persists insidebar.json. Drag-to-reorder is suppressed while nesting is on; the original custom order is restored verbatim when nesting is toggled off.SidebarCard+SidebarBottomCardView.Slotcoordinator) and a low-priorityNestedWorktreesOnboardingCardViewthat teaches the toggle location. The card auto-permadismisses when the user toggles nesting off via the View menu, regardless of whether the sidebar column is visible. Refactors the existing coding-agents card onto the new framework.Other changes
AGENTS.mdcodifying the rule.Reveal in Sidebar/ deeplink selection now expands any collapsed ancestor prefix so the row never lands on an invisible target.SharedReaderKey.sidebarNestWorktreesByBranchextension; new AGENTS.md rule banning top-level free functions in favor of static members on caseless enums.Test plan