Add --panel flag to new-split command - #10
Conversation
Allows splitting a specific panel without changing focus first. Usage: cmuxterm new-split <direction> [--panel <id|index>] Example: cmuxterm new-split down --panel 1
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 07b95df910
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| let directionArg = parts[0] | ||
| let panelArg = parts.count > 1 ? parts[1] : "" | ||
|
|
||
| guard let direction = parseSplitDirection(directionArg) else { | ||
| return "ERROR: Invalid direction. Use left, right, up, or down." | ||
| } | ||
|
|
||
| var result = "ERROR: Failed to create split" | ||
| DispatchQueue.main.sync { | ||
| guard let tabId = tabManager.selectedTabId, | ||
| let tab = tabManager.tabs.first(where: { $0.id == tabId }), | ||
| let surfaceId = tab.focusedSurfaceId else { | ||
| let tab = tabManager.tabs.first(where: { $0.id == tabId }) else { | ||
| return | ||
| } | ||
| success = tabManager.newSplit(tabId: tabId, surfaceId: surfaceId, direction: direction) | ||
|
|
||
| // If panel arg provided, resolve it; otherwise use focused surface | ||
| let surfaceId: UUID? | ||
| if !panelArg.isEmpty { | ||
| surfaceId = resolveSurfaceId(from: panelArg, tab: tab) | ||
| if surfaceId == nil { |
There was a problem hiding this comment.
Trim panel arg before resolving surface id
When new_split is sent over the socket with multiple spaces (e.g., new_split right 1), parts[1] retains the leading whitespace because split(maxSplits: 1) preserves the remainder as-is. resolveSurfaceId expects a clean UUID or integer, so the lookup fails and returns ERROR: Panel not found even though the panel is valid. Trimming panelArg before resolving would make the new --panel feature robust to common whitespace formatting in socket clients.
Useful? React with 👍 / 👎.
new-split now returns the UUID of the newly created panel, enabling
reliable chaining of split operations without index drift issues.
Before: OK
After: OK F2675177-3838-49AF-A1A0-1744C0048E99
Example workflow to create left + 2x2 grid on right:
RIGHT=$(cmuxterm new-split right | awk '{print $2}')
BOTTOM=$(cmuxterm new-split down --panel $RIGHT | awk '{print $2}')
cmuxterm new-split right --panel $RIGHT
cmuxterm new-split right --panel $BOTTOM
* Add --panel flag to new-split command
Allows splitting a specific panel without changing focus first.
Usage: cmuxterm new-split <direction> [--panel <id|index>]
Example: cmuxterm new-split down --panel 1
* Return new panel ID from new-split command
new-split now returns the UUID of the newly created panel, enabling
reliable chaining of split operations without index drift issues.
Before: OK
After: OK F2675177-3838-49AF-A1A0-1744C0048E99
Example workflow to create left + 2x2 grid on right:
RIGHT=$(cmuxterm new-split right | awk '{print $2}')
BOTTOM=$(cmuxterm new-split down --panel $RIGHT | awk '{print $2}')
cmuxterm new-split right --panel $RIGHT
cmuxterm new-split right --panel $BOTTOM
Bumps the ghostty submodule to c6302d022 which splits Termio.resize into visual and pty legs and adds an IO-thread xev.Timer to coalesce SIGWINCH during a live sidebar/divider drag. Resolves the starship-prompt re-render that ran on every drag frame; the visual grid still reflows per frame. Also documents the new fork patches in docs/ghostty-fork.md (manaflow-ai#10 resize debounce, manaflow-ai#11 per-RunStep DEVELOPER_DIR helper, manaflow-ai#12 lazy iOS xcframework slices). Note: c6302d022 lives on local branch cmux-prompt-flicker-pty-coalesce and has not yet been pushed to manaflow-ai/ghostty main (heyitaki lacks push access). The xcframework pin reference at the bottom of docs/ghostty-fork.md still points at 22fa801 — regenerate the xcframework release + ghosttykit-checksums.txt entry once the new pin is upstreamed. Claude-Session: 3604552a-5f21-4c2d-90f6-e1c51667d079 Co-Authored-By: Claude <noreply@anthropic.com>
…-ai#2 manaflow-ai#3 + tests) Three real bugs from the review of bc76ffe, all fixed in one go since they touch the same per-binding state machine: manaflow-ai#1 — HerdrTabRegistry.remove now calls HerdrInboundLayoutSync.forgetBinding (and HerdrDividerSync.reset). The throttle/suppress commit added three static maps keyed by binding.rootCmuxPaneId (lastDividerApplyAt, pendingDividerSpec, dividerApplyScheduled, plus the new pendingResizeRetries / per- binding suppression state) — none of them were cleared on teardown. A future binding reusing the same UUID inherited stale throttle timestamps and could have applied dividers from a tree that no longer existed. manaflow-ai#3 — Suppression is now per-binding instead of one global static. inboundApplyActiveByBinding + inboundApplySuppressUntilByBinding are keyed by binding.rootCmuxPaneId; shouldSuppressOutboundResize takes an optional binding key (nil falls back to the global aggregate, used by the legacy getter and the unresolved-binding defensive path in forwardPanelSize). HerdrPanelOpener. forwardPanelSize resolves the binding key for the panel via HerdrTabRegistry.binding(forCmuxPaneId:) and consults the per-binding state. A drag in workspace A no longer suppresses legitimate user-driven cmux window resizes in workspace B. manaflow-ai#2 — When forwardPanelSize gets suppressed, it now hands a retry closure to markPendingResize. After the trailing window closes we re-fire the latest pending retry per panel so the user-driven resize is not silently dropped. Each markPendingResize schedules a flush Task that waits inboundApplySuppressTrailingMs + 50ms; if the suppression window has been re-armed by another apply, the flush no-ops and the next apply schedules another. forgetBinding also drains the pendingResizeRetries dict so a torn-down panel doesn't leak its closure. Tests: - testPerBindingSuppressionDoesNotBleedToOtherBindings — locks contract that workspace A's drag doesn't suppress workspace B. - testForgetBindingClearsSuppressionState — locks the teardown cleanup that manaflow-ai#1 was missing. - testPendingResizeFiresAfterSuppressionReleases — locks that a suppressed user resize is re-fired exactly once after the trailing window expires. Test seam `_withInboundApplyActiveForTesting` now accepts an optional bindingKey so the per-binding-scoping test can drive both the global and per-binding state from one helper. Open items from the review NOT addressed in this commit: - Throttle still has no test (reviewer manaflow-ai#7). Next. - 33ms/250ms hardcoded constants undocumented (reviewer manaflow-ai#10). - The trailing window deadline can shrink across re-entrant apply boundaries on wall-clock backwards step (reviewer manaflow-ai#5). Use a monotonic clock; deferred. - Bandwidth premise still unproven (reviewer A). Need instrumentation pass to confirm SSH-master is actually the bottleneck before claiming this commit "fixes" the symptom.
…oop checkpoint Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…low-ai#1, manaflow-ai#4, manaflow-ai#8, manaflow-ai#9, manaflow-ai#10, manaflow-ai#12, manaflow-ai#14) Several linked defects in the mirror sizing transaction: - manaflow-ai#1: applyAssignedGrids re-pinned a stale grid during a WINDOW live-resize (or interactive geometry drag), painting past the shrinking pane. The divider-drag early return does not cover a window resize, so gate the stale re-pin on the same suppression the view path uses. - manaflow-ai#8: under zoom the visible tree is the single zoomed leaf, so hidden but live base panes were unpinned and rendered on a stale grid. Pin each pane from the visible tree or the base tree; clear only panes in neither. - manaflow-ai#9: the pin-grow repaint went through the attach-only redraw kick (armed only at .enter), so late-granted cells stayed blank mid-session. Extract the shrink/restore SIGWINCH body into forceRedrawKick(windowIds:) and call it directly on a pin grow. - manaflow-ai#10: the stale-repin else-if and gridParityMismatch tested only the under direction, so an over-render (rendered > assigned) was an invisible no-op. Compare with != on both axes; reapplyAssignedGrid clamps either way. - manaflow-ai#12: gridParityMismatch read only the ledger, which goes stale because a same-size re-apply returns early before reporting. Read the surface's live grid first, falling back to the ledger. - manaflow-ai#4: the parked-container consumer clamped an oversized parked reading to the bound and banked it, overwriting a correct size. Reject it (as the sibling oversized consumer does) and keep the last good container. - manaflow-ai#14: rearmIfOutputMissedPlan gated on the plain isVisibleForSizing, which goes stale-true when a hidden tab's view is dismantled; gate on isEffectivelyVisibleForSizing so an offscreen mirror cannot spin re-arms. Tests: parkedHiddenReadingOverTheBoundIsRejectedNotClamped (manaflow-ai#4), gridParityFlagsAnOverRenderedPane (manaflow-ai#10, manaflow-ai#12), and the reworked grid-lag test now pins that an offscreen mirror does not re-arm (manaflow-ai#14).
Summary
--panelflag tonew-splitcommand to split a specific panel without changing focus firstfocus-panel --panel 1thennew-split downnew-split down --panel 1Usage
Test plan
--panel 1to split only the right panel downward