feat: add dynamic right-panel shell tabs#110
Conversation
|
Warning Rate limit exceeded
Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 10 minutes and 33 seconds. ⌛ How to resolve this issue?After the wait time has elapsed, a review can be triggered using the 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 configurationConfiguration used: Organization UI Review profile: ASSERTIVE Plan: Pro Plus Run ID: 📒 Files selected for processing (32)
📝 WalkthroughWalkthroughAdds a shell-tab system for the right-side panel: new "context" tab, sortable shell-tab components, shell-tab state primitives and migration logic, updated side-panel APIs (openTab/closeTab/moveTab/toggleTab), and call-site updates plus tests and i18n for an "Add tab" UI. Changes
Sequence DiagramsequenceDiagram
actor User
participant SessionSidePanel as Session<br/>Side Panel
participant DropdownMenu as Dropdown<br/>Menu
participant View as View<br/>API
participant ShellTabs as Shell Tabs<br/>State
participant ShellTab as ShellTab<br/>Component
rect rgba(100, 150, 255, 0.5)
Note over User,ShellTabs: Open existing shell tab (review)
User->>SessionSidePanel: Click "review" tab
SessionSidePanel->>View: openTab("review")
View->>ShellTabs: openShellTab(state, "review")
ShellTabs-->>View: updated state with "review"
View-->>SessionSidePanel: tab updated
SessionSidePanel-->>User: "review" tab shown
end
rect rgba(150, 200, 100, 0.5)
Note over User,DropdownMenu: Add new tab via dropdown
User->>SessionSidePanel: Click "Add tab"
SessionSidePanel->>DropdownMenu: Render available tabs
User->>DropdownMenu: Select "context"
DropdownMenu->>View: openTab("context")
View->>ShellTabs: openShellTab(state, "context")
ShellTabs-->>View: "context" added
end
rect rgba(200, 100, 150, 0.5)
Note over User,ShellTab: Reorder tabs via drag
User->>ShellTab: Drag "files"
ShellTab->>View: moveTab("files", 2)
View->>ShellTabs: moveShellTab(state, "files", 2)
ShellTabs-->>View: reordered tabs
View-->>ShellTab: order updated
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested labels
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Code Review
This pull request introduces a new shell tab system for the right utility panel, allowing for multiple manageable tabs including Status, Files, Review, Terminal, and Context. The implementation includes new UI components for sortable tabs, a dropdown menu for adding tabs, and comprehensive migration logic for legacy session states. Feedback identifies a logic error in the toggleShellTab function that prevents the panel from closing when an active tab is toggled, and a regression in openReviewPanel where the 'review' tab is not explicitly activated.
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/src/context/layout.tsx`:
- Around line 223-227: The current migratedRightPanel falls through to undefined
for older layouts because it returns non-record rightPanel values as-is; change
the logic so when rightPanel is null/undefined you return an object that
preserves the legacy open state by calling legacyRightPanelOpened(review,
fileTree) (i.e., set opened to legacyRightPanelOpened(review, fileTree) and
merge with the default/rightPanel shape), while still keeping the existing
branches: if isRecord(rightPanel) migrate its opened field, and if rightPanel is
a boolean return it unchanged.
In `@packages/app/src/pages/session/migrate-session-view.ts`:
- Around line 90-95: The nested ternary that computes activeCandidate is hard to
read; extract this logic into a small helper (e.g., computeActiveTab or
resolveActiveCandidate) that takes the entry (or entry.sessionTabsRaw,
entry.migratedSidePanelTab, entry.reviewInSessionTabs) and returns "context" |
"review" | undefined using clear if/return branches. Replace the inline ternary
with a call to that helper (e.g., const activeCandidate =
resolveActiveCandidate(entry)) and keep the same precedence: prefer "context" if
sessionTabsRaw.active === "context", then map "changes" or "review" to "review",
otherwise fall back to migratedSidePanelTab or reviewInSessionTabs -> "review".
In `@packages/app/src/pages/session/right-panel-tabs.ts`:
- Around line 134-142: The moveShellTab function doesn't validate the
destination index and can move tabs before the pinned "status" tab; update
moveShellTab (and its use of ShellTabState.openShellTabs and normalizeShellTabs)
to clamp the incoming to index into the valid range so the tab never lands
before the pinned tab: compute a clampedTo = Math.min(Math.max(to, 1),
state.openShellTabs.length - 1) (or equivalent) before performing the splice and
use clampedTo in place of to; leave the early returns for target === "status"
and from === -1 unchanged.
In `@packages/app/src/pages/session/session-side-panel.tsx`:
- Around line 119-121: openReviewPanel currently only opens the side panel but
doesn't activate the "review" tab, so newly opened files are hidden under the
Tabs.Content value="review" branch; modify openReviewPanel to first select the
"review" panel/tab via the sidePanel selector (e.g. call
view().sidePanel.select("review") or the equivalent select method on sidePanel)
and then ensure the panel is opened (open it only if not already opened) so the
review shell is active before file tabs are opened.
In `@packages/app/src/pages/session/terminal-shell-tab.ts`:
- Around line 16-21: The toggleDesktopTerminal function currently calls
view.sidePanel.toggleTab("terminal") and then directly calls
terminal.open()/terminal.close(), which duplicates the state update performed by
the createEffect in session-side-panel (which also syncs terminal state based on
the side panel tab); if the immediate-call is intentional for UI responsiveness,
add a brief clarifying comment above toggleDesktopTerminal explaining that
terminal.open()/close() are called immediately to provide instant feedback while
the createEffect in session-side-panel.tsx ensures global synchronization and is
idempotent, otherwise remove the direct terminal.open()/close() calls and rely
solely on the createEffect to manage terminal state.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: abd5611a-9c1a-4868-b2ec-5917342c569b
📒 Files selected for processing (27)
packages/app/src/components/dialog-select-file.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/components/session-context-usage.tsxpackages/app/src/components/session/index.tspackages/app/src/components/session/session-sortable-shell-tab.tsxpackages/app/src/context/layout.shell-tabs.test.tspackages/app/src/context/layout.test.tspackages/app/src/context/layout.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/parity.test.tspackages/app/src/i18n/zh.tspackages/app/src/pages/session.tsxpackages/app/src/pages/session/close-session-tab.test.tspackages/app/src/pages/session/close-session-tab.tspackages/app/src/pages/session/file-tab-scroll.test.tspackages/app/src/pages/session/file-tab-scroll.tspackages/app/src/pages/session/helpers.test.tspackages/app/src/pages/session/helpers.tspackages/app/src/pages/session/migrate-session-view.test.tspackages/app/src/pages/session/migrate-session-view.tspackages/app/src/pages/session/right-panel-tabs.test.tspackages/app/src/pages/session/right-panel-tabs.tspackages/app/src/pages/session/session-side-panel.test.tsxpackages/app/src/pages/session/session-side-panel.tsxpackages/app/src/pages/session/terminal-shell-tab.tspackages/app/src/pages/session/use-session-commands.test.tspackages/app/src/pages/session/use-session-commands.tsx
💤 Files with no reviewable changes (1)
- packages/app/src/pages/session/file-tab-scroll.test.ts
There was a problem hiding this comment.
Nitpick Review
All CI checks pass. I addressed the low-risk consistency items that were worth folding into this PR:
migrate-session-view.tsnow reusessame()instead of keeping a localarraysEqualhelper.right-panel-tabs.tsnow documents whycoerceLegacySidePanelTabcan returnundefinedwhilemigrateLegacyRightPanelTabreturns a concrete fallback.
Remaining observations are intentionally left as follow-up-level cleanup, not blockers:
isPlainObjectcould eventually be consolidated with other localisRecordhelpers, but introducing a shared utility would broaden this PR.RightPanelShellIconNameis a small convenience alias. Keeping it is clearer than inlining the union everywhere.resolveActiveCandidateremains private and is covered throughmigrateSessionViewbehavior tests. Exporting it only for direct tests would expand the module API without much value.
The implementation is ready to merge.
|
@coderabbitai review |
✅ Actions performedReview triggered.
|
c74b11f to
64c8806
Compare
64c8806 to
e0170da
Compare
There was a problem hiding this comment.
Actionable comments posted: 5
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/e2e/files/file-tree.spec.ts`:
- Around line 16-18: The test currently uses page.getByRole("tablist").first()
(stored in shellTabList) which can grab the wrong tablist; instead scope the
lookup to the right utility panel before selecting the tablist: locate the
right-utility panel via a stable semantic selector (e.g., a
data-component/data-action attribute or a named landmark/role) and call
getByRole("tablist") or locator("role=tablist") on that panel element, then
click the last button and select the "Review" menu item; update references to
shellTabList to use this scoped panel.getByRole("tablist") to avoid ordering
flakiness.
In `@packages/app/src/components/prompt-input.tsx`:
- Around line 206-216: The side-panel openTab("review") call is duplicated;
hoist view().sidePanel.openTab("review") before the wantsReview branch so it's
executed once, then inside the branch only call
view().sidePanel.explorer.setTab("changes"), tabs().setActive("review"), and
queueCommentFocus(), and in the else branch only call
view().sidePanel.explorer.setTab("all") to avoid repeating openTab; use the
existing wantsReview variable to control which explorer tab and follow-up
actions run.
In `@packages/app/src/context/layout.tsx`:
- Around line 209-216: The code currently returns a bare boolean for legacy
rightPanel values which loses the stored open state; change the boolean branch
in migratedRightPanel to return an object instead (e.g., return { width:
DEFAULT_RIGHT_PANEL_WIDTH, opened: rightPanel }) so consumers expecting { width,
opened } see the correct opened value; keep the other branches using
legacyRightPanelOpened(rightPanel, review, fileTree), isRecord, and the existing
opened merge logic intact.
In `@packages/app/src/pages/session/migrate-session-view.ts`:
- Around line 99-114: The current validity check (activeIsValidInnerTab) allows
any file:// active value even if that file isn't present in rawAll, preventing
cleanup; update activeIsValidInnerTab to require that a file:// active also
exists in the rawAll list (i.e., rawActive === undefined || rawActive ===
"review" || (rawActive.startsWith("file://") && rawAll.includes(rawActive))).
Keep the later normalization using fileTabsOnly(rawAll) and the computed active
assignment (which already guards with all.includes(rawActive)), but change the
initial guard so entries with orphaned rawActive values fall into the cleanup
branch that sets sessionTabs[key] and toggles tabsShapeChanged.
In `@packages/app/src/pages/session/session-side-panel.tsx`:
- Around line 217-227: The handler handleShellDragOver computes the drop index
incorrectly for shell tabs: get fromIndex =
view().sidePanel.openTabs().indexOf(from) and toIndex =
view().sidePanel.openTabs().indexOf(to), then if fromIndex < toIndex decrement
toIndex by 1 (i.e. toIndex = toIndex - 1) before calling
view().sidePanel.moveTab(from, toIndex) so the splice-based moveTab inserts at
the hovered position the same way file tabs do; keep the existing guards
(isRightPanelTab checks and -1 checks).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: b14a9608-b435-43ca-81f5-50f23a39f4b6
📒 Files selected for processing (32)
packages/app/e2e/app/home.spec.tspackages/app/e2e/files/file-tree.spec.tspackages/app/src/components/dialog-select-file.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/components/session-context-usage.tsxpackages/app/src/components/session/index.tspackages/app/src/components/session/session-sortable-shell-tab.tsxpackages/app/src/context/global-sync.tsxpackages/app/src/context/global-sync/bootstrap.tspackages/app/src/context/layout.shell-tabs.test.tspackages/app/src/context/layout.test.tspackages/app/src/context/layout.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/parity.test.tspackages/app/src/i18n/zh.tspackages/app/src/pages/session.tsxpackages/app/src/pages/session/close-session-tab.test.tspackages/app/src/pages/session/close-session-tab.tspackages/app/src/pages/session/file-tab-scroll.test.tspackages/app/src/pages/session/file-tab-scroll.tspackages/app/src/pages/session/helpers.test.tspackages/app/src/pages/session/helpers.tspackages/app/src/pages/session/migrate-session-view.test.tspackages/app/src/pages/session/migrate-session-view.tspackages/app/src/pages/session/right-panel-tabs.test.tspackages/app/src/pages/session/right-panel-tabs.tspackages/app/src/pages/session/session-side-panel.test.tsxpackages/app/src/pages/session/session-side-panel.tsxpackages/app/src/pages/session/terminal-shell-tab.tspackages/app/src/pages/session/use-session-commands.test.tspackages/app/src/pages/session/use-session-commands.tsxpackages/app/src/utils/is-record.ts
💤 Files with no reviewable changes (1)
- packages/app/src/pages/session/file-tab-scroll.test.ts
e0170da to
7123ba4
Compare
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@packages/app/src/pages/session/close-session-tab.ts`:
- Around line 11-29: Replace the hardcoded "status" checks with the canonical
closability metadata from RIGHT_PANEL_TAB_META: import RIGHT_PANEL_TAB_META and
use RIGHT_PANEL_TAB_META[tab].closable (or equivalent) in both
canCloseSessionTab and closeSessionTab instead of comparing sidePanelTab() ===
"status"; when closing a shell tab call input.closeShellTab(shellTab) only if
RIGHT_PANEL_TAB_META[shellTab].closable is true, and return true after closing;
ensure you still prefer closing file tabs via input.closeFileTab(fileTab) first
and reference the same RIGHT_PANEL_TAB_META lookup in both functions so
enablement and behavior stay in sync.
In `@packages/app/src/pages/session/helpers.ts`:
- Around line 60-66: The normalized active tab (from input.normalizeTab(active))
is returned as long as it looks like a file tab, but it may be stale; update the
logic in the helper that computes the active/closable tab so that after
computing normalizedActive and verifying input.pathFromTab(normalizedActive),
you also check openedTabs().includes(normalizedActive) (or equivalent
membership) before returning it; this ensures input.tabs().active() entries are
guarded against orphaned/stale tabs the same way activeTab()/openedTabs() are
used elsewhere.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: ASSERTIVE
Plan: Pro Plus
Run ID: 2ca61dbf-d0f6-4ea9-b7db-5c8e9dded31d
📒 Files selected for processing (32)
packages/app/e2e/app/home.spec.tspackages/app/e2e/files/file-tree.spec.tspackages/app/src/components/dialog-select-file.tsxpackages/app/src/components/prompt-input.tsxpackages/app/src/components/session-context-usage.tsxpackages/app/src/components/session/index.tspackages/app/src/components/session/session-sortable-shell-tab.tsxpackages/app/src/context/global-sync.tsxpackages/app/src/context/global-sync/bootstrap.tspackages/app/src/context/layout.shell-tabs.test.tspackages/app/src/context/layout.test.tspackages/app/src/context/layout.tsxpackages/app/src/i18n/en.tspackages/app/src/i18n/parity.test.tspackages/app/src/i18n/zh.tspackages/app/src/pages/session.tsxpackages/app/src/pages/session/close-session-tab.test.tspackages/app/src/pages/session/close-session-tab.tspackages/app/src/pages/session/file-tab-scroll.test.tspackages/app/src/pages/session/file-tab-scroll.tspackages/app/src/pages/session/helpers.test.tspackages/app/src/pages/session/helpers.tspackages/app/src/pages/session/migrate-session-view.test.tspackages/app/src/pages/session/migrate-session-view.tspackages/app/src/pages/session/right-panel-tabs.test.tspackages/app/src/pages/session/right-panel-tabs.tspackages/app/src/pages/session/session-side-panel.test.tsxpackages/app/src/pages/session/session-side-panel.tsxpackages/app/src/pages/session/terminal-shell-tab.tspackages/app/src/pages/session/use-session-commands.test.tspackages/app/src/pages/session/use-session-commands.tsxpackages/app/src/utils/is-record.ts
💤 Files with no reviewable changes (1)
- packages/app/src/pages/session/file-tab-scroll.test.ts
7123ba4 to
0c47281
Compare
Summary
Why
The right panel previously showed fixed shell tabs even when a user only needed one surface. Dynamic shell tabs make the panel match the active workflow while keeping old persisted layouts compatible.
Related Issue
None.
How To Verify
cd packages/app bun run typecheck bun run test:unitLocal screenshot smoke was also run with a temporary E2E spec. The captured screenshot is available in the Codex session at
/tmp/pawwork-dynamic-shell-tabs-e2e.png.Screenshots or Recordings
Local E2E screenshot reviewed in Codex session:
/tmp/pawwork-dynamic-shell-tabs-e2e.png.Checklist
devbranchSummary by CodeRabbit
New Features
Bug Fixes
Refactor
Tests