Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
---
# beans-f05i
title: Show PR status icons on workspace cards in sidebar
status: completed
type: feature
priority: normal
created_at: 2026-03-17T18:18:15Z
updated_at: 2026-03-17T18:19:28Z
---

In integrate: pr mode, replace the green checkmark on workspace cards with PR status icons: no PR = no icon, checks pending = orange, checks failed = red, checks passed = green, merged = purple.

## Summary of Changes

Modified `frontend/src/lib/components/Sidebar.svelte` to show PR status icons on workspace cards in the sidebar when in `integrate: pr` mode:

- Added `pullRequest` data to the `WorkspaceItem` interface, piped from the worktree subscription
- New PR status icon branch in the status icon logic (before the existing "ready to integrate" check):
- **Merged**: purple rotated branch icon (`icon-[uil--code-branch]`)
- **Checks failed**: red X circle (`icon-[uil--times-circle]`)
- **Checks pending**: orange clock (`icon-[uil--clock]`)
- **Checks passed**: green check circle (`icon-[uil--check-circle]`)
- **No PR**: no icon shown (falls through to existing logic)
- Icons are clickable links to the PR URL
- On hover, the destroy button still appears (same pattern as the existing checkmark)
- In `integrate: local` mode, behavior is completely unchanged
37 changes: 35 additions & 2 deletions frontend/src/lib/components/Sidebar.svelte
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,20 @@
import { decryptText } from '$lib/actions/decryptText';
import ConfirmModal from './ConfirmModal.svelte';

interface PullRequestInfo {
state: string;
checkStatus: string;
number: number;
url: string;
}

interface WorkspaceItem {
id: string;
label: string;
description: string | null;
beans: Bean[];
settingUp: boolean;
pullRequest: PullRequestInfo | null;
}

/** Look up full Bean objects for a worktree's detected bean IDs. */
Expand All @@ -27,7 +35,7 @@
.filter((b): b is Bean => b != null);
}

const mainWorkspace: WorkspaceItem = $derived({ id: MAIN_WORKSPACE_ID, label: configStore.mainBranch, description: null, beans: [], settingUp: false });
const mainWorkspace: WorkspaceItem = $derived({ id: MAIN_WORKSPACE_ID, label: configStore.mainBranch, description: null, beans: [], settingUp: false, pullRequest: null });

const workspaceItems = $derived([
mainWorkspace,
Expand All @@ -36,7 +44,13 @@
label: wt.name ?? wt.branch,
description: wt.description ?? null,
beans: beansForWorktree(wt.beanIds),
settingUp: wt.setupStatus === 'RUNNING'
settingUp: wt.setupStatus === 'RUNNING',
pullRequest: wt.pullRequest ? {
state: wt.pullRequest.state,
checkStatus: wt.pullRequest.checkStatus,
number: wt.pullRequest.number,
url: wt.pullRequest.url,
} : null,
}))
]);

Expand Down Expand Up @@ -228,6 +242,25 @@
<div class="loader absolute inset-0" transition:fade={{ duration: 200 }}></div>
{:else if item.id === MAIN_WORKSPACE_ID && mainHasChanges}
<span class="icon-[uil--exclamation-triangle] absolute inset-0 block size-4 text-warning" title="Uncommitted changes"></span>
{:else if item.id !== MAIN_WORKSPACE_ID && configStore.worktreeIntegrateMode === 'pr' && item.pullRequest}
{@const pr = item.pullRequest}
{@const [iconClass, colorClass, title] =
pr.state === 'merged'
? ['icon-[uil--code-branch] rotate-180', 'text-purple-400', `PR #${pr.number} merged`]
: pr.checkStatus === 'fail'
? ['icon-[uil--times-circle]', 'text-danger', `PR #${pr.number}: checks failed`]
: pr.checkStatus === 'pending'
? ['icon-[uil--clock]', 'text-warning', `PR #${pr.number}: checks running`]
: ['icon-[uil--check-circle]', 'text-success', `PR #${pr.number}: checks passed`]
}
<a href={pr.url} target="_blank" rel="noopener noreferrer" class={["absolute inset-0 block size-4 group-hover:hidden", iconClass, colorClass]} title={title}></a>
<button
onclick={() => promptDestroy(item.id)}
class="absolute inset-0 hidden cursor-pointer items-center justify-center rounded text-text-faint transition-opacity hover:text-danger group-hover:flex"
aria-label="Destroy worktree"
>
<span class="icon-[uil--archive] block size-3.5"></span>
</button>
{:else if item.id !== MAIN_WORKSPACE_ID && readyWorktreeIds.has(item.id)}
<span class="icon-[uil--check] absolute inset-0 block size-4 text-success group-hover:hidden" title="Ready to integrate"></span>
<button
Expand Down
Loading