Repository navigation
feat(desktop): replace sidebar activity strip with port/agent chips and bulk-close hover cards - #5887
Conversation
…nd bulk-close hover cards Drop the per-workspace morph strip (staggered port badges, unfold machinery) in favor of compact chips on a slim activity line under each workspace row: - Ports chip (radio tower + count) and agents chip (most-relevant agent avatar + count, 2+ agents only), styled like the old count pill - Hovering a chip swaps its count for an x in place; clicking bulk-kills every port / stops every agent with a success toast - Each chip opens a hover card listing items with click-to-open and per-item close, plus a close-all footer - Chip hover suppresses the workspace hover card via a ref-counted hold on DashboardSidebarHoverProvider, so the two cards never fight - Port open logic extracted to usePortOpenActions, shared with the bottom-panel port badge; agent kill hook gains a bulk killAgents - Removes the details-expanded custom variant and the group/item scope Both experiment flags (inline-workspace-ports, workspace-agents-row) keep their existing gating; the bottom PORTS panel is unchanged. Claude-Session: https://claude.ai/code/session_01HGmkhuu5WVFn83PhDC7Yoz
📝 WalkthroughWalkthroughThe dashboard sidebar replaces the workspace details strip with agent and port chips. New hooks centralize port actions, normalize running-agent data, support bulk agent termination, and suppress parent hover cards while chip hover cards are active. ChangesSidebar activity chips
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant WorkspaceRow as DashboardSidebarExpandedWorkspaceRow
participant Chips as DashboardSidebarWorkspaceChips
participant AgentChip as DashboardSidebarAgentsChip
participant PortChip as DashboardSidebarPortsChip
participant KillAgents as useDashboardSidebarAgentKill
participant PortActions as usePortOpenActions
WorkspaceRow->>Chips: render workspace activity
Chips->>AgentChip: pass running agents
Chips->>PortChip: pass active ports
AgentChip->>KillAgents: killAgents(terminalIds)
PortChip->>PortActions: open or close port actions
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
Actionable comments posted: 3
🧹 Nitpick comments (2)
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarAgentKill/useDashboardSidebarAgentKill.ts (1)
73-83: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win
killAgentsswallows individual rejection reasons.Unlike
killAgent, which logsconsole.error("[sidebar-agent] Failed to kill agent:", error)on failure,killAgentsonly counts rejections for the aggregate toast — the actual error objects fromPromise.allSettledare discarded. This makes debugging a partial bulk-kill failure harder (no trace in the console/log).♻️ Proposed fix to log individual failures
const results = await Promise.allSettled( terminalIds.map((terminalId) => getHostServiceClientByUrl(hostUrl).terminal.killSession.mutate({ terminalId, workspaceId, }), ), ); - const failedCount = results.filter( - (result) => result.status === "rejected", - ).length; + for (const result of results) { + if (result.status === "rejected") { + console.error("[sidebar-agent] Failed to kill agent:", result.reason); + } + } + const failedCount = results.filter( + (result) => result.status === "rejected", + ).length;🤖 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 `@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarAgentKill/useDashboardSidebarAgentKill.ts` around lines 73 - 83, Update the bulk failure handling in killAgents to inspect each rejected result from Promise.allSettled and log its reason with the same “[sidebar-agent] Failed to kill agent:” context used by killAgent, while preserving the existing failedCount toast and successful-result count.apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/DashboardSidebarAgentsChip.tsx (1)
38-45: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value
reducewithout an initial value will throw ifagentsis ever empty.Currently safe because
DashboardSidebarWorkspaceChipsonly renders this component whenagents.length > 1, but that invariant lives in a different file. A one-line initial value removes the coupling risk entirely.- const primaryAgent = agents.reduce((best, agent) => { + const primaryAgent = agents.reduce((best, agent) => { if (STATUS_PRIORITY[agent.status] !== STATUS_PRIORITY[best.status]) { return STATUS_PRIORITY[agent.status] > STATUS_PRIORITY[best.status] ? agent : best; } return agent.startedAt > best.startedAt ? agent : best; - }); + }, agents[0]);🤖 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 `@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/DashboardSidebarAgentsChip.tsx` around lines 38 - 45, Add an initial value to the agents.reduce call used to compute primaryAgent in DashboardSidebarAgentsChip, using the existing empty-agent fallback expected by the component, so the reduction remains safe when agents is empty while preserving the current priority and startedAt selection logic.
🤖 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.
Inline comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/DashboardSidebarPortsChip.tsx`:
- Around line 116-124: Port-hover actions currently allow clicks to reach the
parent workspace item. In DashboardSidebarPortsChip.tsx at lines 116-124, stop
propagation for the footer “Close all ports” button; in
DashboardSidebarPortHoverRow.tsx at lines 19-46, add the same click-propagation
guard to both hovered-row action buttons so opening or closing ports only
performs the intended action.
- Around line 26-41: Share the port-kill pending state across bulk and per-port
actions by keeping the single useDashboardSidebarPortKill call in
DashboardSidebarPortsChip and passing isPending plus killPort/onClose to each
DashboardSidebarPortHoverRow. In DashboardSidebarPortHoverRow, remove its local
hook usage and consume the parent-provided props so both close paths disable
consistently. Apply this to DashboardSidebarPortsChip.tsx lines 26-41 and
DashboardSidebarPortHoverRow.tsx lines 14-46.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarAgentKill/useDashboardSidebarAgentKill.ts`:
- Around line 54-94: The useDashboardSidebarAgentKill hook currently keeps
isPending local to each caller, allowing bulk and per-row kill actions to race
on the same terminal IDs. Share in-flight terminal IDs across
DashboardSidebarAgentsChip and DashboardSidebarAgentHoverRow within the
workspace, or disable row actions for IDs included in an active bulk kill, and
ensure killAgents and killAgent consult that shared state before invoking
terminal.killSession.
---
Nitpick comments:
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/DashboardSidebarAgentsChip.tsx`:
- Around line 38-45: Add an initial value to the agents.reduce call used to
compute primaryAgent in DashboardSidebarAgentsChip, using the existing
empty-agent fallback expected by the component, so the reduction remains safe
when agents is empty while preserving the current priority and startedAt
selection logic.
In
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarAgentKill/useDashboardSidebarAgentKill.ts`:
- Around line 73-83: Update the bulk failure handling in killAgents to inspect
each rejected result from Promise.allSettled and log its reason with the same
“[sidebar-agent] Failed to kill agent:” context used by killAgent, while
preserving the existing failedCount toast and successful-result count.
🪄 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: defaults
Review profile: CHILL
Plan: Pro
Run ID: a1882a32-82d9-4d8b-bce7-46c9e6dfee5e
📒 Files selected for processing (35)
apps/desktop/src/renderer/globals.cssapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHoverCardOverlay/DashboardSidebarHoverCardOverlay.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarPortsList/components/DashboardSidebarPortBadge/DashboardSidebarPortBadge.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/DashboardSidebarWorkspaceItem.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/DashboardSidebarExpandedWorkspaceRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/DashboardSidebarWorkspaceChips.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/DashboardSidebarAgentsChip.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/components/DashboardSidebarAgentAvatar/DashboardSidebarAgentAvatar.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/components/DashboardSidebarAgentAvatar/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/components/DashboardSidebarAgentHoverRow/DashboardSidebarAgentHoverRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/components/DashboardSidebarAgentHoverRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/DashboardSidebarPortsChip.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/components/DashboardSidebarPortHoverRow/DashboardSidebarPortHoverRow.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/components/DashboardSidebarPortHoverRow/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarAgentKill/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarAgentKill/useDashboardSidebarAgentKill.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarChipHoverSuppression/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarChipHoverSuppression/useDashboardSidebarChipHoverSuppression.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarWorkspaceRunningAgents/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarWorkspaceRunningAgents/useDashboardSidebarWorkspaceRunningAgents.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceAgentBadge/DashboardSidebarWorkspaceAgentBadge.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceAgentBadge/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceDetailsAction/DashboardSidebarWorkspaceDetailsAction.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceDetailsAction/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDiffStats/DashboardSidebarWorkspaceDiffStats.tsxapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/usePortOpenActions/index.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/hooks/usePortOpenActions/usePortOpenActions.tsapps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/providers/DashboardSidebarHoverProvider/DashboardSidebarHoverProvider.tsxapps/desktop/src/renderer/stores/inline-workspace-ports.tsapps/desktop/src/renderer/stores/workspace-agents-row.ts
💤 Files with no reviewable changes (7)
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceDetailsAction/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceAgentBadge/index.ts
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceAgentBadge/DashboardSidebarWorkspaceAgentBadge.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/components/DashboardSidebarWorkspaceDetailsAction/DashboardSidebarWorkspaceDetailsAction.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/DashboardSidebarWorkspaceDetails.tsx
- apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceDetails/index.ts
- apps/desktop/src/renderer/globals.css
| export function DashboardSidebarPortsChip({ | ||
| ports, | ||
| }: DashboardSidebarPortsChipProps) { | ||
| const { isPending, killPorts } = useDashboardSidebarPortKill(); | ||
| const { hold, release } = useDashboardSidebarChipHoverSuppression(); | ||
|
|
||
| const handleCloseAll = async () => { | ||
| if (isPending) return; | ||
| const results = await killPorts(ports); | ||
| const closedCount = results.filter((result) => result.success).length; | ||
| if (closedCount > 0) { | ||
| toast.success( | ||
| closedCount === 1 ? "Closed 1 port" : `Closed ${closedCount} ports`, | ||
| ); | ||
| } | ||
| }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | ⚡ Quick win
Bulk "close all" and per-port "close" don't share kill-pending state. usePortKillActions (via useDashboardSidebarPortKill) tracks pendingCount in local useState, so calling it separately in the chip and in each hover row gives each caller its own disjoint isPending. Nothing prevents a user from triggering a per-port close while "close all" is already killing that same port (or vice versa), since neither button is disabled by the other's in-flight state.
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/DashboardSidebarPortsChip.tsx#L26-L41: calluseDashboardSidebarPortKill()once here and passisPending/killPortdown toDashboardSidebarPortHoverRowas props instead of the row calling the hook itself.apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/components/DashboardSidebarPortHoverRow/DashboardSidebarPortHoverRow.tsx#L14-L46: drop the localuseDashboardSidebarPortKill()call, acceptisPending/onClose(orkillPort) as props from the parent chip instead.
🔧 Proposed fix sketch
interface DashboardSidebarPortHoverRowProps {
port: DashboardSidebarPort;
+ isPending: boolean;
+ onClose: (port: DashboardSidebarPort) => void;
}
export function DashboardSidebarPortHoverRow({
port,
+ isPending,
+ onClose,
}: DashboardSidebarPortHoverRowProps) {
- const { isPending, killPort } = useDashboardSidebarPortKill();
const { openPrimary } = usePortOpenActions(port);
...
- onClick={() => {
- if (isPending) return;
- void killPort(port);
- }}
+ onClick={() => {
+ if (isPending) return;
+ onClose(port);
+ }}-const { isPending, killPorts } = useDashboardSidebarPortKill();
+const { isPending, killPort, killPorts } = useDashboardSidebarPortKill();
...
-<DashboardSidebarPortHoverRow key={...} port={port} />
+<DashboardSidebarPortHoverRow
+ key={...}
+ port={port}
+ isPending={isPending}
+ onClose={(p) => void killPort(p)}
+/>📝 Committable suggestion
‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.
| export function DashboardSidebarPortsChip({ | |
| ports, | |
| }: DashboardSidebarPortsChipProps) { | |
| const { isPending, killPorts } = useDashboardSidebarPortKill(); | |
| const { hold, release } = useDashboardSidebarChipHoverSuppression(); | |
| const handleCloseAll = async () => { | |
| if (isPending) return; | |
| const results = await killPorts(ports); | |
| const closedCount = results.filter((result) => result.success).length; | |
| if (closedCount > 0) { | |
| toast.success( | |
| closedCount === 1 ? "Closed 1 port" : `Closed ${closedCount} ports`, | |
| ); | |
| } | |
| }; | |
| interface DashboardSidebarPortHoverRowProps { | |
| port: DashboardSidebarPort; | |
| isPending: boolean; | |
| onClose: (port: DashboardSidebarPort) => void; | |
| } | |
| export function DashboardSidebarPortHoverRow({ | |
| port, | |
| isPending, | |
| onClose, | |
| }: DashboardSidebarPortHoverRowProps) { | |
| const { openPrimary } = usePortOpenActions(port); | |
| return ( | |
| <div className="group/row flex items-center gap-1.5 rounded-sm px-2 py-1 hover:bg-muted"> | |
| <button | |
| type="button" | |
| onClick={openPrimary} | |
| disabled={isPending} | |
| className="flex min-w-0 flex-1 items-center gap-1.5 text-left focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring" | |
| > | |
| <span className="size-1.5 shrink-0 rounded-full bg-green-500" /> | |
| {port.label && ( | |
| <span className="min-w-0 truncate text-xs">{port.label}</span> | |
| )} | |
| <span className="shrink-0 font-mono text-[11px] tabular-nums text-muted-foreground"> | |
| localhost:{port.port} | |
| </span> | |
| </button> | |
| {/* Always in layout, shown via visibility so the row never changes | |
| size on hover. */} | |
| <button | |
| type="button" | |
| onClick={() => { | |
| if (isPending) return; | |
| onClose(port); | |
| }} | |
| disabled={isPending} | |
| aria-label={`Close port ${port.port}`} | |
| className="invisible flex size-5 shrink-0 items-center justify-center rounded text-muted-foreground hover:bg-destructive/10 hover:text-destructive focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring group-focus-within/row:visible group-hover/row:visible" | |
| > | |
| <LuX className="size-3" strokeWidth={STROKE_WIDTH} /> | |
| </button> |
| export function DashboardSidebarPortsChip({ | |
| ports, | |
| }: DashboardSidebarPortsChipProps) { | |
| const { isPending, killPorts } = useDashboardSidebarPortKill(); | |
| const { hold, release } = useDashboardSidebarChipHoverSuppression(); | |
| const handleCloseAll = async () => { | |
| if (isPending) return; | |
| const results = await killPorts(ports); | |
| const closedCount = results.filter((result) => result.success).length; | |
| if (closedCount > 0) { | |
| toast.success( | |
| closedCount === 1 ? "Closed 1 port" : `Closed ${closedCount} ports`, | |
| ); | |
| } | |
| }; | |
| export function DashboardSidebarPortsChip({ | |
| ports, | |
| }: DashboardSidebarPortsChipProps) { | |
| const { isPending, killPort, killPorts } = useDashboardSidebarPortKill(); | |
| const { hold, release } = useDashboardSidebarChipHoverSuppression(); | |
| const handleCloseAll = async () => { | |
| if (isPending) return; | |
| const results = await killPorts(ports); | |
| const closedCount = results.filter((result) => result.success).length; | |
| if (closedCount > 0) { | |
| toast.success( | |
| closedCount === 1 ? "Closed 1 port" : `Closed ${closedCount} ports`, | |
| ); | |
| } | |
| }; |
📍 Affects 2 files
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/DashboardSidebarPortsChip.tsx#L26-L41(this comment)apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/components/DashboardSidebarPortHoverRow/DashboardSidebarPortHoverRow.tsx#L14-L46
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/DashboardSidebarPortsChip.tsx`
around lines 26 - 41, Share the port-kill pending state across bulk and per-port
actions by keeping the single useDashboardSidebarPortKill call in
DashboardSidebarPortsChip and passing isPending plus killPort/onClose to each
DashboardSidebarPortHoverRow. In DashboardSidebarPortHoverRow, remove its local
hook usage and consume the parent-provided props so both close paths disable
consistently. Apply this to DashboardSidebarPortsChip.tsx lines 26-41 and
DashboardSidebarPortHoverRow.tsx lines 14-46.
| <button | ||
| type="button" | ||
| onClick={() => void handleCloseAll()} | ||
| disabled={isPending} | ||
| className="flex w-full items-center gap-1.5 rounded-sm px-2 py-1 text-xs hover:bg-muted focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-ring disabled:opacity-70" | ||
| > | ||
| <LuX className="size-3" strokeWidth={STROKE_WIDTH} /> | ||
| Close all ports | ||
| </button> |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Locate the relevant files and inspect the component hierarchy/events around PortsChip and hover row.
fd -a 'DashboardSidebarPortsChip\.tsx|DashboardSidebarPortHoverRow\.tsx|DashboardSidebarWorkspaceItem\.tsx|HoverCardContent|HoverCard' . | sed 's#^\./##' | head -100
echo '--- PortsChip outline ---'
PortsChip="$(fd 'DashboardSidebarPortsChip\.tsx' . | head -1)"
if [ -n "${PortsChip:-}" ]; then
wc -l "$PortsChip"
ast-grep outline "$PortsChip" || true
echo '--- PortsChip relevant lines ---'
sed -n '1,180p' "$PortsChip" | nl -ba
fi
echo '--- HoverRow relevant lines ---'
HoverRow="$(fd 'DashboardSidebarPortHoverRow\.tsx' . | head -1)"
if [ -n "${HoverRow:-}" ]; then
wc -l "$HoverRow"
sed -n '1,120p' "$HoverRow" | nl -ba
fi
echo '--- Search workspace row click handlers / hover card placement ---'
rg -n "DashboardSidebarWorkspaceItem|DashboardSidebarExpandedWorkspaceRow|HoverCardContent|onMouseEnter|onMouseLeave|onClick|type=\"button\"|Close all ports|open primary|killPort" apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem -S || trueRepository: superset-sh/superset
Length of output: 3347
🏁 Script executed:
#!/bin/bash
set -euo pipefail
# Programmatically inspect the relevant source structure without executing repository code.
# This reads the candidate files and builds a small map of hook/handler references.
python3 - <<'PY'
from pathlib import Path
import re
files = [
Path('apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/DashboardSidebarPortsChip.tsx'),
Path('apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/components/DashboardSidebarPortHoverRow/DashboardSidebarPortHoverRow.tsx'),
]
for p in files:
print(f'=== {p} exists={p.exists()} ===')
if not p.exists():
continue
s = p.read_text()
for i,line in enumerate(s.splitlines(),1):
if any(tok in line for tok in ['onClick=', 'onMouseEnter=', 'onMouseLeave=', 'HoverCardContent', 'HoverCardTrigger', 'Close all ports', 'openPrimary', 'killPort', 'stopPropagation']):
print(f'{i}: {line}')
PYRepository: superset-sh/superset
Length of output: 1471
🏁 Script executed:
#!/bin/bash
set -euo pipefail
PortsChip="$(fd 'DashboardSidebarPortsChip\.tsx' . | head -1)"
HoverRow="$(fd 'DashboardSidebarPortHoverRow\.tsx' . | head -1)"
WorkspaceItem="$(fd 'DashboardSidebarWorkspaceItem\.tsx' . | head -1)"
echo '--- PortsChip relevant lines ---'
sed -n '28,130p' "$PortsChip"
echo '--- HoverRow relevant lines ---'
sed -n '1,90p' "$HoverRow"
echo '--- Workspace item outline/relevant event handlers ---'
sed -n '1,220p' "$WorkspaceItem"
echo '--- HoverCardContent implementation ---'
echo "$WorkspaceItem" | sed 's#DashboardSidebarWorkspaceItem\.tsx$#../../DashboardSidebarWorkspaceHoverCardContent.tsx#' | xargs -r sed -n '1,180p'Repository: superset-sh/superset
Length of output: 13394
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo '--- expanded row relevant content ---'
ExpandedRow="$(fd 'DashboardSidebarExpandedWorkspaceRow\.tsx' . | head -1)"
sed -n '1,220p' "$ExpandedRow"
echo '--- hover content implementation ---'
sed -n '1,180p' apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarWorkspaceHoverCardContent/DashboardSidebarWorkspaceHoverCardContent.tsx
echo '--- Overlay implementation ---'
sed -n '1,220p' apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarHoverCardOverlay/DashboardSidebarHoverCardOverlay.tsx
echo '--- locate hover card trigger usage ---'
rg -n "DashboardSidebarWorkspaceHoverCardContent|HoverCardContent|HoverCardTrigger|DashboardSidebarWorkspaceItem\(" apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar -S
echo '--- parent click handlers/row clickable behavior ---'
rg -n "onClick|handleClick|createWorkspace|openWorkspace|Navigate|useNavigate|type=\"button\"|DashboardSidebarWorkspaceItemProps" apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarCollapsedWorkspaceButton apps/desktop/src/renderer/hooks -S | head -200Repository: superset-sh/superset
Length of output: 37717
Add click-propagation guards to port-hover actions. The ports chip trigger intentionally stops clicks from reaching expanded row handlers, but the hover-card actions inside DashboardSidebarPortHoverRow and the footer “Close all ports” button still do not; closing/opening from the hover UI can then also activate the parent workspace item. Stop propagation in both hovered row buttons and the footer “Close all ports” button.
📍 Affects 2 files
apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/DashboardSidebarPortsChip.tsx#L116-L124(this comment)apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/components/DashboardSidebarPortHoverRow/DashboardSidebarPortHoverRow.tsx#L19-L46
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarPortsChip/DashboardSidebarPortsChip.tsx`
around lines 116 - 124, Port-hover actions currently allow clicks to reach the
parent workspace item. In DashboardSidebarPortsChip.tsx at lines 116-124, stop
propagation for the footer “Close all ports” button; in
DashboardSidebarPortHoverRow.tsx at lines 19-46, add the same click-propagation
guard to both hovered-row action buttons so opening or closing ports only
performs the intended action.
| const killAgents = useCallback( | ||
| async (terminalIds: string[]): Promise<number> => { | ||
| if (terminalIds.length === 0) return 0; | ||
| if (!hostUrl) { | ||
| toast.error("Failed to stop agents", { | ||
| description: "No host is available for this workspace", | ||
| }); | ||
| return 0; | ||
| } | ||
| setIsPending(true); | ||
| try { | ||
| const results = await Promise.allSettled( | ||
| terminalIds.map((terminalId) => | ||
| getHostServiceClientByUrl(hostUrl).terminal.killSession.mutate({ | ||
| terminalId, | ||
| workspaceId, | ||
| }), | ||
| ), | ||
| ); | ||
| const failedCount = results.filter( | ||
| (result) => result.status === "rejected", | ||
| ).length; | ||
| if (failedCount > 0) { | ||
| toast.error( | ||
| failedCount === 1 | ||
| ? "Failed to stop 1 agent" | ||
| : `Failed to stop ${failedCount} agents`, | ||
| ); | ||
| } | ||
| return results.length - failedCount; | ||
| } finally { | ||
| void queryClient.invalidateQueries({ | ||
| queryKey: ["terminal-agent-bindings", hostUrl, workspaceId], | ||
| }); | ||
| setIsPending(false); | ||
| } | ||
| }, | ||
| [hostUrl, workspaceId, queryClient], | ||
| ); | ||
|
|
||
| return { isPending, killAgent, killAgents }; |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== locate files =="
fd -a 'useDashboardSidebarAgentKill|DashboardSidebarAgentsChip|DashboardSidebarAgentHoverRow' .
echo
echo "== candidate files =="
for f in $(fd 'useDashboardSidebarAgentKill|DashboardSidebarAgentsChip|DashboardSidebarAgentHoverRow' .); do
echo "--- $f"
wc -l "$f"
done
echo
echo "== hook outline/read =="
for f in $(fd 'useDashboardSidebarAgentKill' .); do
echo "--- OUTLINE $f"
ast-grep outline "$f" || true
echo "--- CONTENT $f"
cat -n "$f"
done
echo
echo "== callers =="
rg -n "useDashboardSidebarAgentKill|killAgent|killAgents" . -g '*.ts' -g '*.tsx'Repository: superset-sh/superset
Length of output: 2829
🏁 Script executed:
#!/bin/bash
set -u
echo "== hook file =="
wc -l apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarAgentKill/useDashboardSidebarAgentKill.ts
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarAgentKill/useDashboardSidebarAgentKill.ts
echo
echo "== chip file =="
wc -l apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/DashboardSidebarAgentsChip.tsx
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/DashboardSidebarAgentsChip.tsx
echo
echo "== hover row file =="
wc -l apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentHoverRow/DashboardSidebarAgentHoverRow.tsx
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentHoverRow/DashboardSidebarAgentHoverRow.tsx
echo
echo "== hook imports/usages =="
rg -n "useDashboardSidebarAgentKill|killAgent|killAgents" apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips -g '*.ts' -g '*.tsx'Repository: superset-sh/superset
Length of output: 15791
🏁 Script executed:
#!/bin/bash
set -euo pipefail
echo "== hover row missing file details =="
ls -la apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/components/DashboardSidebarAgentHoverRow/ || true
echo
echo "== hover row contents (using exact path) =="
cat -n apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip/components/DashboardSidebarAgentHoverRow/DashboardSidebarAgentHoverRow.tsx
echo
echo "== component files with killAgent/killAgents =="
rg -n "useDashboardSidebarAgentKill|killAgent|killAgents" apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/components/DashboardSidebarAgentsChip -g '*.ts' -g '*.tsx'Repository: superset-sh/superset
Length of output: 6381
Share kill in-flight state between bulk and per-row stop actions.
DashboardSidebarAgentsChip and each DashboardSidebarAgentHoverRow call useDashboardSidebarAgentKill(workspaceId), so they have independent isPending state. Clicking “Stop all agents” while an individual row’s × is clicked can let the per-row action call killSession for an already-disposed session and show a “Failed to kill agent” toast after the bulk action succeeds. Track killing terminal IDs in a shared workspace-scoped store/context (or disable targeted row buttons when a bulk kill includes them) so both actions cannot race on the same sessions.
🤖 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
`@apps/desktop/src/renderer/routes/_authenticated/_dashboard/components/DashboardSidebar/components/DashboardSidebarWorkspaceItem/components/DashboardSidebarExpandedWorkspaceRow/components/DashboardSidebarWorkspaceChips/hooks/useDashboardSidebarAgentKill/useDashboardSidebarAgentKill.ts`
around lines 54 - 94, The useDashboardSidebarAgentKill hook currently keeps
isPending local to each caller, allowing bulk and per-row kill actions to race
on the same terminal IDs. Share in-flight terminal IDs across
DashboardSidebarAgentsChip and DashboardSidebarAgentHoverRow within the
workspace, or disable row actions for IDs included in an active bulk kill, and
ensure killAgents and killAgent consult that shared state before invoking
terminal.killSession.
🧹 Preview Cleanup CompleteThe following preview resources have been cleaned up:
Thank you for your contribution! 🎉 |
…host CLI ParadevAI#5885, Cursor session labels ParadevAI#5882, open-in-IDE v2 ParadevAI#5877, collapsed sidebar DnD ParadevAI#5871, automations FK drop ParadevAI#5878) # Conflicts: # apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts
Upstream ParadevAI#5887 rewrote the expanded workspace row's action cluster to appear on group-focus-within as well as group-hover. The F1 commits-to-pull (down N) badge cluster only hid on group-hover, so on the active/focused row the badge and the row actions overlapped in the shared grid cell. Hide the badge cluster on group-focus-within too, restoring the clean badge/actions swap.
…host CLI ParadevAI#5885, Cursor session labels ParadevAI#5882, open-in-IDE v2 ParadevAI#5877, collapsed sidebar DnD ParadevAI#5871, automations FK drop ParadevAI#5878) # Conflicts: # apps/desktop/src/main/lib/agent-setup/notify-hook.test.ts
Upstream ParadevAI#5887 rewrote the expanded workspace row's action cluster to appear on group-focus-within as well as group-hover. The F1 commits-to-pull (down N) badge cluster only hid on group-hover, so on the active/focused row the badge and the row actions overlapped in the shared grid cell. Hide the badge cluster on group-focus-within too, restoring the clean badge/actions swap.
What
Replaces the per-workspace sidebar activity strip (the hover-morph row with staggered port badges) with compact chips on a slim activity line under each workspace row, adding a first-class way to bulk-close all ports (and stop all agents) for a workspace.
bg-muted/60). Hovering the chip cross-fades the count to an × in place (constant width); clicking kills every port via the existingkillPortsand toasts "Closed N ports" (no undo — the processes are gone).STATUS_PRIORITY, newest on ties) + total count; renders only with 2+ running agents (existing noise rule). Clicking the × stops all agents via a new bulkkillAgents(Promise.allSettledover the existingterminal.killSessionmutation).HoverCard, 150 ms): list every port (label · localhost:port, click-to-open respectingportOpenAction, per-row ×) or agent (icon, label, status, per-row ×), with a plain-styled "Close all ports" / "Stop all agents" footer. Rows are layout-shift-free (visibility swaps, no display toggles).DashboardSidebarHoverProviderwhile hovered, so the workspace hover card yields to the chip card and returns instantly afterward.Refactors / removals
usePortOpenActionsextracted fromDashboardSidebarPortBadgeand shared with the new hover rows (no behavior change to the bottom-panel badge).useDashboardSidebarWorkspaceRunningAgents+useDashboardSidebarAgentKillmoved under the newDashboardSidebarWorkspaceChips; kill hook gainskillAgents.DashboardSidebarWorkspaceDetails(morph strip,DashboardSidebarWorkspaceAgentBadge,DashboardSidebarWorkspaceDetailsAction, stagger constants), thedetails-expanded@custom-variantin globals.css, and thegroup/item/data-activescaffolding.Flags
Unchanged: ports chip behind
inline-workspace-ports, agents chip behindworkspace-agents-row; flag-off still shows the bottom PORTS panel. Store teardown docs updated to point at the chip components.Testing
bun run lintandapps/desktoptypecheck clean; no remaining references to the deleted strip/variant.https://claude.ai/code/session_01HGmkhuu5WVFn83PhDC7Yoz
Summary by cubic
Replace the sidebar activity strip with compact ports/agents chips under each workspace row. Adds bulk-close actions and hover cards for quick per-item actions; flags remain the same and the bottom PORTS panel is unchanged.
New Features
Refactors
Written for commit d196a09. Summary will update on new commits.
Summary by CodeRabbit
New Features
Bug Fixes