mirror of
https://github.com/block/buzz.git
synced 2026-11-13 12:51:34 +00:00
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:
@@ -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>
|
||||
|
||||
@@ -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<
|
||||
|
||||
@@ -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;
|
||||
|
||||
Reference in New Issue
Block a user