Work panel: accept PRs from any agent, pop the GitHub stack in

The panel's PR link scan matched only the chat's default agent, so a
pull request posted by a second agent added to the chat never surfaced
— the drawer stayed on "No current branch". The scan now matches any
managed agent (plus the configured default).

When the PR info arrives, the card + CI monitor stack enters with a
slight scale + opacity pop (keyed by PR href, so a newer PR re-runs
it; disabled under prefers-reduced-motion).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
klopez4212
2026-07-04 15:44:41 +01:00
parent f9576ddaa5
commit cf1f4b5dbc
3 changed files with 122 additions and 78 deletions

View File

@@ -119,79 +119,84 @@ export function ChatWorkPanel({
/>
</div>
{preview ? (
<GithubPullRequestCard className="w-full" preview={preview} />
) : null}
{preview ? (
<details
className={cn(CHIP_CLASS, "group/ci block p-0")}
data-testid="chat-ci-monitor"
// Keyed by href so a NEW pull request re-runs the pop-in, not
// just the first one.
<div
className="buzz-work-card-in flex flex-col gap-2"
key={preview.href}
>
<summary className="flex cursor-pointer select-none list-none items-center gap-1.5 px-3 py-2.5 [&::-webkit-details-marker]:hidden">
<CiStatus checks={checks} />
<span
aria-hidden="true"
className="mx-0.5 text-muted-foreground/50"
>
·
</span>
<MessageSquareText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span className="min-w-0 truncate text-muted-foreground">
{openThreads} open comment{openThreads === 1 ? "" : "s"}
</span>
<ChevronDown className="ml-auto h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform group-open/ci:rotate-180" />
</summary>
<div className="flex flex-col gap-2.5 border-t border-border/40 px-3 py-2.5">
{checks && checks.runs.length > 0 ? (
<ul className="flex flex-col gap-1.5">
{dedupeRunKeys(checks.runs).map(({ key, run }) => (
<li className="flex items-center gap-1.5" key={key}>
<CheckRunIcon state={run.state} />
<span className="min-w-0 truncate text-muted-foreground">
{run.name}
</span>
</li>
))}
</ul>
) : (
<span className="text-muted-foreground">
No checks reported yet.
<GithubPullRequestCard className="w-full" preview={preview} />
<details
className={cn(CHIP_CLASS, "group/ci block p-0")}
data-testid="chat-ci-monitor"
>
<summary className="flex cursor-pointer select-none list-none items-center gap-1.5 px-3 py-2.5 [&::-webkit-details-marker]:hidden">
<CiStatus checks={checks} />
<span
aria-hidden="true"
className="mx-0.5 text-muted-foreground/50"
>
·
</span>
)}
<div aria-hidden="true" className="h-px bg-border/40" />
<label
className="flex cursor-pointer items-center gap-2"
htmlFor="automation-auto-fix-ci"
>
<Checkbox
checked={automation.autoFixCi}
data-testid="automation-auto-fix-ci"
id="automation-auto-fix-ci"
onCheckedChange={(checked) =>
updateChatWorkAutomation(chatId, {
autoFixCi: checked === true,
})
}
/>
<span>Auto-fix CI failures</span>
</label>
<label
className="flex cursor-pointer items-center gap-2"
htmlFor="automation-address-comments"
>
<Checkbox
checked={automation.addressComments}
data-testid="automation-address-comments"
id="automation-address-comments"
onCheckedChange={(checked) =>
updateChatWorkAutomation(chatId, {
addressComments: checked === true,
})
}
/>
<span>Address comments & resolve</span>
</label>
</div>
</details>
<MessageSquareText className="h-3.5 w-3.5 shrink-0 text-muted-foreground" />
<span className="min-w-0 truncate text-muted-foreground">
{openThreads} open comment{openThreads === 1 ? "" : "s"}
</span>
<ChevronDown className="ml-auto h-3.5 w-3.5 shrink-0 text-muted-foreground transition-transform group-open/ci:rotate-180" />
</summary>
<div className="flex flex-col gap-2.5 border-t border-border/40 px-3 py-2.5">
{checks && checks.runs.length > 0 ? (
<ul className="flex flex-col gap-1.5">
{dedupeRunKeys(checks.runs).map(({ key, run }) => (
<li className="flex items-center gap-1.5" key={key}>
<CheckRunIcon state={run.state} />
<span className="min-w-0 truncate text-muted-foreground">
{run.name}
</span>
</li>
))}
</ul>
) : (
<span className="text-muted-foreground">
No checks reported yet.
</span>
)}
<div aria-hidden="true" className="h-px bg-border/40" />
<label
className="flex cursor-pointer items-center gap-2"
htmlFor="automation-auto-fix-ci"
>
<Checkbox
checked={automation.autoFixCi}
data-testid="automation-auto-fix-ci"
id="automation-auto-fix-ci"
onCheckedChange={(checked) =>
updateChatWorkAutomation(chatId, {
autoFixCi: checked === true,
})
}
/>
<span>Auto-fix CI failures</span>
</label>
<label
className="flex cursor-pointer items-center gap-2"
htmlFor="automation-address-comments"
>
<Checkbox
checked={automation.addressComments}
data-testid="automation-address-comments"
id="automation-address-comments"
onCheckedChange={(checked) =>
updateChatWorkAutomation(chatId, {
addressComments: checked === true,
})
}
/>
<span>Address comments & resolve</span>
</label>
</div>
</details>
</div>
) : null}
</div>
</div>

View File

@@ -310,17 +310,26 @@ export function ChatsScreen({
);
const archiveChatMutation = useArchiveChatMutation();
// Latest PR link the chat's agent posted — drives the header toggle and
// the top-right work module in the conversation.
// Latest PR link ANY agent in the chat posted — drives the header toggle
// and the top-right work module. Matching only the default agent missed
// PRs from additional agents added to the chat.
const agentPullRequestHref = React.useMemo(() => {
const agentPubkey = metadata?.defaultAgentPubkey ?? defaultAgent?.pubkey;
if (!agentPubkey) {
const agentKeys = new Set(
(managedAgentsQuery.data ?? []).map((agent) =>
normalizePubkey(agent.pubkey),
),
);
const configuredAgent =
metadata?.defaultAgentPubkey ?? defaultAgent?.pubkey;
if (configuredAgent) {
agentKeys.add(normalizePubkey(configuredAgent));
}
if (agentKeys.size === 0) {
return null;
}
const agentKey = normalizePubkey(agentPubkey);
for (let index = messages.length - 1; index >= 0; index--) {
const message = messages[index];
if (normalizePubkey(message.pubkey) !== agentKey) {
if (!agentKeys.has(normalizePubkey(message.pubkey))) {
continue;
}
const preview = extractSupportedLinkPreviews(message.content).find(
@@ -331,7 +340,12 @@ export function ChatsScreen({
}
}
return null;
}, [defaultAgent?.pubkey, messages, metadata?.defaultAgentPubkey]);
}, [
defaultAgent?.pubkey,
managedAgentsQuery.data,
messages,
metadata?.defaultAgentPubkey,
]);
// null = auto: open once the agent has produced a PR, closed otherwise.
// A click stores an explicit preference for the current chat.
const [workPanelPreference, setWorkPanelPreference] = React.useState<

View File

@@ -263,6 +263,31 @@
}
}
/* Work-panel content arriving (the agent posted a PR): a gentle pop —
slight scale + fade, matching the message entrance feel. */
@keyframes buzz-work-card-in {
from {
opacity: 0;
transform: scale(0.96);
}
to {
opacity: 1;
transform: scale(1);
}
}
.buzz-work-card-in {
animation: buzz-work-card-in 260ms cubic-bezier(0.22, 1, 0.36, 1) both;
transform-origin: top center;
}
@media (prefers-reduced-motion: reduce) {
.buzz-work-card-in {
animation: none;
}
}
.buzz-poof-layer {
inset: 0;
overflow: hidden;