Skip to content

fix: animate the workspace row-click expand/collapse - #413

Merged
umputun merged 5 commits into
masterfrom
fix/sidebar-row-expand-animation
Aug 9, 2026
Merged

fix: animate the workspace row-click expand/collapse#413
umputun merged 5 commits into
masterfrom
fix/sidebar-row-expand-animation

Conversation

@umputun

@umputun umputun commented Aug 9, 2026

Copy link
Copy Markdown
Owner

a workspace row has two hit targets for one toggle and they rendered differently. The disclosure triangle goes through AppKit's native path and animates; clicking anywhere else on the row called expandItem/collapseItem directly, which do not animate, so the sessions just appeared and disappeared.

routing the row-click toggle through outline.animator() makes both paths render the same.

the animator proxy is not documented for expand/collapse, so I checked it before committing:

  • it does animate. Row frames are mid-flight right after the call, settling to their final positions ~0.25s later, with a live CA animation on the parent row. The plain call jumps straight to the end state.
  • outlineViewItemDidExpand/DidCollapse still fire synchronously and isItemExpanded/numberOfRows update immediately, which is what keeps the suppressExpansionPersist bracketing at the other call sites intact. testWorkspaceCollapsePersistsAcrossRelaunch and testActiveWorkspaceCollapsePersistsDespiteReveal both collapse from a row click and assert the state reaches disk, so that leg is pinned.

no Reduce Motion gate here, unlike the app's own pulses. Both hit targets reach the same AppKit animation, so gating this one would split them again.

scope is the row-click path only. The other expand/collapse sites stay unanimated, being either bulk (Expand/Collapse Workspaces, rebuild re-apply) or programmatic (selection reveal, drag spring-load).

what this does not fix: the row click still waits out NSEvent.doubleClickInterval before toggling, so it reads as a pause and then motion. Getting rid of that means dropping double-click rename on workspace rows, or toggling optimistically and inverting on a double-click, or adding press feedback during the wait. None of the three is worth its cost for a defect this size, so the delay stays.

it is written up in docs/backlog/workspace-row-toggle-deferral-drops-clicks.md with the rejected approaches, along with two real defects in the same deferral: a second workspace-row click inside the interval cancels the first one's pending toggle, and a session-row click returns before the cancel so a pending workspace toggle still fires under it.

Fix #407

umputun added 4 commits August 9, 2026 12:32
A workspace row carries two hit targets for one toggle. The disclosure triangle goes through AppKit's
native path and animates; the row-click path called expandItem/collapseItem directly, which do not
animate, so the same action on the same row rendered two different ways.

Route that toggle through outline.animator(). Checked against AppKit that the proxy animates only the
row insert/remove while flipping expansion and firing didExpand/didCollapse synchronously, so the
suppressExpansionPersist protocol and the persist write-back are unaffected;
testWorkspaceCollapsePersistsAcrossRelaunch and testActiveWorkspaceCollapsePersistsDespiteReveal both
drive the collapse from a row click and pin that.

Not gated on Reduce Motion, unlike the app's own pulses: both hit targets reach the same AppKit
animation, so a gate here would re-split them.

Scope is the row-click path. The other call sites stay unanimated, being either bulk (expand/collapse
all, rebuild re-apply) or programmatic (selection reveal, drag spring-load). The delay half of the
report is a separate behavior decision and is untouched.

Related to #407
…clicks

Surfaced while fixing the animation half of #407. The single pendingRowToggle slot and the guard
ordering in handleSingleClick mean a second workspace-row click cancels the first with no feedback, and
a session-row click returns before the cancel so a pending workspace toggle still fires under it.

Deferred because settling them is the same behavior decision as the report's delay half, not a
rendering fix.
The doc comment on toggleExpansion restated three facts the same change wrote into
.claude/rules/sidebar.md, which already owns the click-routing contract. Keep only what is code-local -
that the animator proxy fires didExpand/didCollapse synchronously, so the persist write-back and the
suppressExpansionPersist bracketing around the other call sites still hold - and cross-reference the
rest.

Also correct the backlog item: workspace rename sits in the File menu's Workspace section, not a
Workspace menu (there is none; the only CommandMenu is Navigate), and shouldSelectItem is at
WorkspaceSidebar+RowRendering.swift:15-18.
… delay

Keeps the next reader from re-proposing any of them: dropping double-click rename, the optimistic
toggle with an inverted undo, and press feedback during the deferral were each weighed and judged
either a worse trade or disproportionate to the defect.
Copilot AI lite review requested due to automatic review settings August 9, 2026 18:04

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR aligns workspace expand/collapse behavior in the AppKit sidebar so that clicking anywhere on a workspace row animates the same way as clicking the native disclosure triangle, addressing the UX inconsistency described in #407.

Changes:

  • Route workspace row-click expand/collapse through NSOutlineView.animator() to match native disclosure animation.
  • Document the decision and its scope in the sidebar rules.
  • Add a backlog note describing known issues with the current single pending-toggle deferral behavior (out of scope for this fix).

Reviewed changes

Copilot reviewed 3 out of 3 changed files in this pull request and generated 1 comment.

File Description
agterm/Views/WorkspaceSidebar+ContextMenu.swift Uses the outline view animator proxy for row-click expand/collapse so the transition animates like the disclosure triangle.
.claude/rules/sidebar.md Records the animation decision (including why it’s not Reduce Motion–gated) and clarifies which other expansion paths remain unanimated.
docs/backlog/workspace-row-toggle-deferral-drops-clicks.md Captures deferred-toggle edge cases and rejected approaches for future follow-up work.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment on lines +50 to +53
/// Animated to match the disclosure triangle; `.claude/rules/sidebar.md` owns why, why it is not gated on
/// Reduce Motion, and which sites stay unanimated. The proxy flips expansion and fires
/// didExpand/didCollapse synchronously, so the persist write-back is unaffected and the
/// `suppressExpansionPersist` bracketing the other sites wrap their calls in still holds.
"the suppressExpansionPersist bracketing the other sites wrap their calls in" stranded a preposition off
a gerund and read as noise. Same three facts, one fewer line.
@cloudflare-workers-and-pages

cloudflare-workers-and-pages Bot commented Aug 9, 2026

Copy link
Copy Markdown

Deploying agterm with  Cloudflare Pages  Cloudflare Pages

Latest commit: 58e0aed
Status: ✅  Deploy successful!
Preview URL: https://4cb473ee.agterm.pages.dev
Branch Preview URL: https://fix-sidebar-row-expand-anima.agterm.pages.dev

View logs

@umputun
umputun merged commit f595631 into master Aug 9, 2026
8 checks passed
@umputun
umputun deleted the fix/sidebar-row-expand-animation branch August 9, 2026 18:27
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.

Sidebar: workspace row click is delayed and unanimated, unlike the disclosure triangle

2 participants