Skip to content

feat(sidebar): group bulk actions, empty-area quick menu, sort#40

Merged
AThraen merged 4 commits into
mainfrom
feat/group-bulk-actions
May 14, 2026
Merged

feat(sidebar): group bulk actions, empty-area quick menu, sort#40
AThraen merged 4 commits into
mainfrom
feat/group-bulk-actions

Conversation

@mortenaslo
Copy link
Copy Markdown
Contributor

Summary

  • Per-group bulk actions — right-click a group (strip tab or inline header) now offers Remote control all, Sleep all, Wake all, Close all… The items disable based on live vs. dormant counts.
  • Empty-area quick menu — right-click on empty sidebar header, session list, group strip, or terminal grid surfaces a shared menu with New session, New group…, Bulk actions submenu (Wake all dormant / Sleep all / Close all), Group display mode, Expand/Collapse all groups, Session row icons mode, Show git branch / worktree clusters toggles, and All settings…. The legacy footer "+" group button is gone; group creation flows through this menu.
  • Sort sessions button — new button in the SESSIONS header (left of +). Direction-aware sort menu with Name / Folder / Last active / Git ▸ (Branch, Dirty, Repo). Re-clicking the active field flips direction; switching fields picks each field's natural starting direction. Direction glyph lives in the MenuItem icon slot so it column-aligns; the active row renders SemiBold + 100% opacity, inactive rows show their default direction as a 45%-opacity preview.
  • Visual polish — vertical filter strip hairline divider between All/Ungrouped and user groups; Ungrouped glyph "∅" → "□" so it reads as the hollow companion to "▦".

Implementation notes

  • ShellSession.LastActivityAt (defaults to CreatedAt) is updated whenever the active session changes, persisted via the existing state.json flow.
  • SessionManager.SortSessions(Comparison<ShellSession>) is a one-shot in-place sort; drag-reorder still works afterwards.
  • Git-based sorts build a one-time Dictionary<id, SessionViewModel> lookup; dormant sessions (no live VM) sink to the bottom of every git ordering.

Test plan

  • Right-click a group in both the filter strip and an inline header: verify bulk actions enable/disable per group population and that confirm dialog fires on Close all.
  • Right-click empty sidebar / terminal grid: verify menu opens with all entries, and item-level menus on session rows / group tabs still shadow it.
  • Toggle Group display modes: None / FilterStrip / InlineHeaders all rebuild cleanly.
  • InlineHeaders mode + multiple groups: Expand all / Collapse all flips every section and persists across restart.
  • Sort by each field: arrow flips on re-click, switches to natural default on new field; verify Git fields keep dormant rows at the bottom and worktrees adjacent within the same branch/repo.
  • Restart with sessions active in different states: LastActivityAt updates make "Last active" reflect recent navigation.

🤖 Generated with Claude Code

mortenaslo and others added 3 commits May 13, 2026 22:36
Adds Remote control all, Sleep all, Start all, and Close all entries to
both the group-strip tab and the inline group-header context menus.
Items enable/disable based on live vs dormant member counts.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Sidebar right-click on empty space (header, session list, group strip,
empty terminal grid) now shows a shared quick menu with:
- New session / New group
- Bulk actions submenu — Wake all dormant, Sleep all, Close all
- Group display mode toggle (None / FilterStrip / InlineHeaders)
- Expand / collapse all group sections
- Session row icons mode (OnHover / Always / Hidden)
- Show git branch / worktree clusters toggles
- All settings…

The legacy + group footer button is removed; group creation flows
through "New group…" in the same menu.

New sort button in the SESSIONS header (left of +). Opens a
direction-aware menu: Name, Folder, Last active, Git ▸ (Branch, Dirty,
Repo). Re-clicking the active field flips direction; switching fields
picks the field's natural starting direction. Direction glyph lives in
the MenuItem icon slot so it column-aligns. Sessions without live git
info sink to the bottom of git-based orderings.

ShellSession gains a LastActivityAt timestamp updated when a session
becomes the active one, persisted via the existing state.json path.
SessionManager.SortSessions exposes a one-shot Sort(Comparison).

Visual polish: vertical filter strip now has a hairline divider
between All/Ungrouped and the user groups; Ungrouped glyph changed
from "∅" to "□" so it reads as the hollow companion to the "▦" All
glyph.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@AThraen
Copy link
Copy Markdown
Contributor

AThraen commented May 14, 2026

Code review

Found 1 issue:

  1. LastActivityAt property initializer is DateTime.UtcNow, but its own XML doc promises it defaults to CreatedAt for sessions that predate the field. System.Text.Json fires the property initializer at deserialization time when the JSON key is missing, so every legacy session loaded from a pre-PR state.json gets LastActivityAt = <load time> — not its CreatedAt. "Sort by Last active" then ranks all legacy sessions as if they were just touched (in load order) until each is activated once. Fix is either a sentinel + getter coalesce, or a one-time backfill from CreatedAt during state load.

public DateTime CreatedAt { get; set; } = DateTime.UtcNow;
/// <summary>
/// Last time the user gave this session focus (clicked it in the sidebar, woke it,
/// or it was selected via Ctrl+Tab). Persisted so "Sort by last active" survives
/// restarts. Defaults to <see cref="CreatedAt"/> for sessions that have never been
/// activated since the field was introduced.
/// </summary>
public DateTime LastActivityAt { get; set; } = DateTime.UtcNow;

🤖 Generated with Claude Code

- If this code review was useful, please react with 👍. Otherwise, react with 👎.

LastActivityAt was initialized to DateTime.UtcNow, which fires when
System.Text.Json deserializes a pre-PR state.json that doesn't carry
the key — every legacy session loaded as "active right now" and sorted
to the top of "Sort by Last active" in load order rather than reflecting
real activity.

Initialize to default (DateTime.MinValue) as a sentinel; SessionManager.
LoadFromState then backfills from CreatedAt for any session that comes
back from disk without the field. SessionManager.CreateSession sets it
explicitly on the new-session path so new sessions still get a real
timestamp.
@AThraen AThraen merged commit 00524e0 into main May 14, 2026
1 check passed
@AThraen AThraen deleted the feat/group-bulk-actions branch May 14, 2026 19:34
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.

2 participants