Skip to content

feat: draggable terminal groups with insertion-line drop indicator - #44

Merged
bgultekin merged 2 commits into
mainfrom
feat/draggable-group-reordering
May 7, 2026
Merged

bgultekin merged 2 commits into
mainfrom
feat/draggable-group-reordering

Conversation

@bgultekin

@bgultekin bgultekin commented May 7, 2026 •

Copy link
Copy Markdown
Owner

Summary

  • Groups can be reordered by dragging their header row — drag initiates from the full-width header HStack (excluding buttons)
  • Animated insertion line appears centred in the gap between groups to show the drop position before releasing
  • Drop is handled by the unified GroupDropTargetDelegate on each group card, which now handles both session drops and group reordering in a single delegate
  • Drag preview shows a card-style mockup with chevron + group name
  • Drag state is cleaned up via a mouse-button watchdog task (same pattern as terminal drags)
  • Group rename focus is restored after a drop via a delayed retry (asyncAfter 120ms) to survive AppKit drag session teardown reclaiming first responder
  • .onDrag is absent from the header when renaming is active, preventing gesture competition with the NSTextField

Test plan

  • Drag a group name row — insertion line appears between groups as you move
  • Drop in the top half of a card → group inserts before it; bottom half → inserts after
  • Dropping a group onto its own position shows forbidden cursor and is a no-op
  • After reorder, quit and relaunch — new order persists
  • Drag a terminal (session) — group reorder indicator does NOT appear
  • Double-click to rename a group after a drag-and-drop — text field stays focused and accepts input
  • Chevron, ellipsis, VS Code, and + buttons still work normally (not consumed by drag)

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Improved stability and responsiveness of group renaming functionality with enhanced text field interaction handling and more reliable activation during editing sessions.

@coderabbitai

coderabbitai Bot commented May 7, 2026 •

Copy link
Copy Markdown

Review Change Stack

Warning

Rate limit exceeded

@bgultekin has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 44 minutes and 40 seconds before requesting another review.

To continue reviewing without waiting, purchase usage credits in the billing tab.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9431d7e7-ff6c-42ba-850e-6a14cf550c8b

📥 Commits

Reviewing files that changed from the base of the PR and between cee62c5 and 9c9ecf8.

📒 Files selected for processing (1)
  • macos/Sources/Ocak/Views/SessionListView.swift
📝 Walkthrough

Walkthrough

SessionGroupListView improves group renaming UX with two coordinated fixes: lifecycle-driven NSEvent monitor management that installs on rename start and removes on state change or view disappearance, plus enhanced GroupNameTextField activation logic that retries with state checks (first responder, key window, app active) rather than a single fixed delay.

Changes

Group Renaming Improvements

Layer / File(s) Summary
Event Monitor Lifecycle
macos/Sources/Ocak/Views/SessionListView.swift
SessionGroupListView installs the outside-click NSEvent monitor on .onChange(isRenamingGroup) and removes it both when isRenamingGroup becomes false and on .onDisappear.
Field Activation with Retry Logic
macos/Sources/Ocak/Views/SessionListView.swift
GroupNameTextField reactivation replaces single fixed-delay retry with multi-delay loop that checks first-responder, key-window, and app-active conditions before re-activating and re-targeting the text field.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Possibly related PRs

  • bgultekin/ocak#45: Modifies the same file with related GroupNameTextField reactivation and session/group UI event handling logic.

Poem

🐰 A rename so smooth, with monitors in place,
No clicks slip past, no field out of grace—
We retry with patience, till keys align true,
When responder and window both whisper "it's you!"

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The PR title 'draggable terminal groups with insertion-line drop indicator' accurately summarizes the main feature added in this PR, which enables dragging groups with visual feedback.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/draggable-group-reordering

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

A SwiftUI .onDrag in this accessory app returns frontmost-app focus to
the previously active process. The DrawerPanel still claims key status
internally, but key events route to whichever app is frontmost — so the
rename field appeared focused yet received no input. Reactivate the app,
clear any stuck editing session, and extend the retry window to survive
slower drag teardown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bgultekin
bgultekin force-pushed the feat/draggable-group-reordering branch from f1dbe57 to cee62c5 Compare May 7, 2026 07:55
@bgultekin
bgultekin marked this pull request as ready for review May 7, 2026 08:05

@coderabbitai coderabbitai Bot 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.

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
macos/Sources/Ocak/Views/SessionListView.swift (1)

1148-1176: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Cancel or gate these focus retries when rename ends.

The delayed retries (0.15s, 0.35s, 0.6s) are scheduled during makeNSView() and cannot be cancelled—they will fire regardless of whether the user later commits or cancels the rename. If the user presses Escape to cancel, then immediately switches apps, the queued retry can still execute NSApp.activate(ignoringOtherApps: true) and pull Ocak back to the front, unexpectedly interrupting their work.

Add a check in the retry closure to bail out if the rename session is no longer active (e.g., guard isRenamingGroup else { return }), or store and invalidate the retry tokens if using a cancellation mechanism. Test by: start rename → press Escape → immediately switch to another app → Ocak should not regain focus during the retry window.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@macos/Sources/Ocak/Views/SessionListView.swift` around lines 1148 - 1176, The
delayed focus-retry closures scheduled in makeNSView (the
DispatchQueue.main.asyncAfter loop calling activate(tf)) must be gated or
cancellable so they don't re-activate the app after a rename has ended; update
the retry closure to early-return if the rename session is no longer active
(e.g., guard isRenamingGroup else { return }) or instead schedule the retries as
DispatchWorkItem instances stored on the view/session and cancel them from the
rename-completion/cancel path (the method that ends renaming). Locate the
activate(_:) closure and the loop that iterates delays [0.15,0.35,0.6] and
either add the guard check referencing isRenamingGroup (or equivalent
state/property) or switch to DispatchWorkItem + cancel logic and ensure
cancellation is invoked when the rename finishes or is aborted.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Outside diff comments:
In `@macos/Sources/Ocak/Views/SessionListView.swift`:
- Around line 1148-1176: The delayed focus-retry closures scheduled in
makeNSView (the DispatchQueue.main.asyncAfter loop calling activate(tf)) must be
gated or cancellable so they don't re-activate the app after a rename has ended;
update the retry closure to early-return if the rename session is no longer
active (e.g., guard isRenamingGroup else { return }) or instead schedule the
retries as DispatchWorkItem instances stored on the view/session and cancel them
from the rename-completion/cancel path (the method that ends renaming). Locate
the activate(_:) closure and the loop that iterates delays [0.15,0.35,0.6] and
either add the guard check referencing isRenamingGroup (or equivalent
state/property) or switch to DispatchWorkItem + cancel logic and ensure
cancellation is invoked when the rename finishes or is aborted.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 9df76815-903f-4516-a666-4d47b0ee969d

📥 Commits

Reviewing files that changed from the base of the PR and between 88223f8 and cee62c5.

📒 Files selected for processing (1)
  • macos/Sources/Ocak/Views/SessionListView.swift

@bgultekin
bgultekin force-pushed the feat/draggable-group-reordering branch from 434fdb5 to cee62c5 Compare May 7, 2026 08:11
…ery paths

Reserve the heavy NSApp/window reactivation for the post-drag retry path so
the common no-drag rename only does a plain makeFirstResponder. Document why
endEditing is recovery-only and why the retry delays were chosen.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
@bgultekin
bgultekin merged commit cf5112c into main May 7, 2026
2 checks passed
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