mirror of
https://github.com/block/buzz.git
synced 2026-11-09 02:41:57 +00:00
Efficiency pass over the chat features
Four fixes from a branch-wide review of the hot paths: - useChatWorkAutomation content-compares its snapshot: a fresh object per storage event re-ran every consumer effect and re-rendered the panel even when nothing changed. - ChatMessageRow and ChatActivityTranscript are memoized: ChatDetail re-renders on every observer frame during a live turn, and every persisted message re-rendered its whole Markdown tree each time. - The active-agent pubkey list is key-stabilized: the managed-agents 30s refetch minted a fresh array identity that resubscribed the transcript store with an unchanged set. - GitHub API commands share one reqwest client: the PR monitor polls on short intervals and built a new connection pool + TLS session cache per call. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
@@ -99,7 +99,20 @@ export function useChatWorkAutomation(chatId: string): ChatWorkAutomation {
|
||||
);
|
||||
|
||||
React.useEffect(() => {
|
||||
const refresh = () => setState(readChatWorkAutomation(chatId));
|
||||
// Content-compare: a fresh object per storage event would re-run every
|
||||
// consumer effect (and re-render every panel) even when nothing changed.
|
||||
const refresh = () =>
|
||||
setState((current) => {
|
||||
const next = readChatWorkAutomation(chatId);
|
||||
return current.autoFixCi === next.autoFixCi &&
|
||||
current.addressComments === next.addressComments &&
|
||||
current.lastCiNudgeSha === next.lastCiNudgeSha &&
|
||||
current.lastCommentNudgeCount === next.lastCommentNudgeCount &&
|
||||
current.lastCiNudgeAt === next.lastCiNudgeAt &&
|
||||
current.lastCommentNudgeAt === next.lastCommentNudgeAt
|
||||
? current
|
||||
: next;
|
||||
});
|
||||
refresh();
|
||||
window.addEventListener(STORAGE_EVENT, refresh);
|
||||
window.addEventListener("storage", refresh);
|
||||
|
||||
@@ -87,47 +87,52 @@ function isSetupLifecycleItem(item: TranscriptItem) {
|
||||
);
|
||||
}
|
||||
|
||||
export function ChatActivityTranscript({
|
||||
activeTurnIds,
|
||||
agent,
|
||||
blocks,
|
||||
identityPubkey,
|
||||
profiles,
|
||||
showAgentIdentity = true,
|
||||
}: {
|
||||
/** Turn ids currently live in this channel — drives per-turn rendering. */
|
||||
activeTurnIds?: ReadonlySet<string>;
|
||||
agent: ManagedAgent | null;
|
||||
blocks: ChatActivityRenderBlock[];
|
||||
identityPubkey?: string;
|
||||
profiles?: UserProfileLookup;
|
||||
/** Hidden in solo chats so agent replies read as part of the stream. */
|
||||
showAgentIdentity?: boolean;
|
||||
}) {
|
||||
if (blocks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
// Memoized: rendered once per anchored message; placement recomputes on
|
||||
// every transcript event, but rows whose props didn't change must not
|
||||
// re-render their Markdown trees.
|
||||
export const ChatActivityTranscript = React.memo(
|
||||
function ChatActivityTranscript({
|
||||
activeTurnIds,
|
||||
agent,
|
||||
blocks,
|
||||
identityPubkey,
|
||||
profiles,
|
||||
showAgentIdentity = true,
|
||||
}: {
|
||||
/** Turn ids currently live in this channel — drives per-turn rendering. */
|
||||
activeTurnIds?: ReadonlySet<string>;
|
||||
agent: ManagedAgent | null;
|
||||
blocks: ChatActivityRenderBlock[];
|
||||
identityPubkey?: string;
|
||||
profiles?: UserProfileLookup;
|
||||
/** Hidden in solo chats so agent replies read as part of the stream. */
|
||||
showAgentIdentity?: boolean;
|
||||
}) {
|
||||
if (blocks.length === 0) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return (
|
||||
<>
|
||||
{blocks.map((renderBlock) => (
|
||||
<ChatActivityBlockView
|
||||
agent={agent}
|
||||
block={renderBlock.block}
|
||||
identityPubkey={identityPubkey}
|
||||
isTurnActive={
|
||||
renderBlock.block.kind === "turn" &&
|
||||
(activeTurnIds?.has(renderBlock.block.turnId) ?? false)
|
||||
}
|
||||
key={renderBlock.id}
|
||||
profiles={profiles}
|
||||
showAgentIdentity={showAgentIdentity}
|
||||
suppressPromptMessage={renderBlock.suppressPromptMessage}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
}
|
||||
return (
|
||||
<>
|
||||
{blocks.map((renderBlock) => (
|
||||
<ChatActivityBlockView
|
||||
agent={agent}
|
||||
block={renderBlock.block}
|
||||
identityPubkey={identityPubkey}
|
||||
isTurnActive={
|
||||
renderBlock.block.kind === "turn" &&
|
||||
(activeTurnIds?.has(renderBlock.block.turnId) ?? false)
|
||||
}
|
||||
key={renderBlock.id}
|
||||
profiles={profiles}
|
||||
showAgentIdentity={showAgentIdentity}
|
||||
suppressPromptMessage={renderBlock.suppressPromptMessage}
|
||||
/>
|
||||
))}
|
||||
</>
|
||||
);
|
||||
},
|
||||
);
|
||||
|
||||
function ChatActivityBlockView({
|
||||
agent,
|
||||
|
||||
@@ -35,7 +35,10 @@ function profileName(
|
||||
);
|
||||
}
|
||||
|
||||
export function ChatMessageRow({
|
||||
// Memoized: ChatDetail re-renders on every observer frame during a live
|
||||
// turn, and without this every persisted message re-renders its whole
|
||||
// Markdown tree each time. All props are identity-stable between events.
|
||||
export const ChatMessageRow = React.memo(function ChatMessageRow({
|
||||
event,
|
||||
isAgent,
|
||||
isOwn,
|
||||
@@ -138,7 +141,7 @@ export function ChatMessageRow({
|
||||
</MessageContent>
|
||||
</Message>
|
||||
);
|
||||
}
|
||||
});
|
||||
|
||||
// Following the stream is owned entirely by the MessageScroller's built-in
|
||||
// autoScroll (content mutation + resize observers). This anchor only handles
|
||||
|
||||
@@ -125,15 +125,22 @@ export function ChatDetail({
|
||||
// Every active managed agent, not just the default: a chat can have
|
||||
// several agents working and all of their activity must render.
|
||||
const managedAgentsQuery = useManagedAgentsQuery();
|
||||
const activeAgentPubkeys = React.useMemo(() => {
|
||||
// Key-stabilized: the managed-agents query refetches on a 30s interval and
|
||||
// a fresh array identity would resubscribe the transcript store each time
|
||||
// even when the active set is unchanged.
|
||||
const activeAgentPubkeysKey = React.useMemo(() => {
|
||||
const pubkeys = (managedAgentsQuery.data ?? [])
|
||||
.filter(isManagedAgentActive)
|
||||
.map((agent) => normalizePubkey(agent.pubkey));
|
||||
if (defaultAgent && isManagedAgentActive(defaultAgent)) {
|
||||
pubkeys.push(normalizePubkey(defaultAgent.pubkey));
|
||||
}
|
||||
return [...new Set(pubkeys)].sort();
|
||||
return [...new Set(pubkeys)].sort().join(",");
|
||||
}, [defaultAgent, managedAgentsQuery.data]);
|
||||
const activeAgentPubkeys = React.useMemo(
|
||||
() => activeAgentPubkeysKey.split(",").filter(Boolean),
|
||||
[activeAgentPubkeysKey],
|
||||
);
|
||||
const hasObserver = activeAgentPubkeys.length > 0;
|
||||
const activeChannelTurns = useActiveAgentTurnsByChannel();
|
||||
// Per-turn ids, not a channel-wide boolean: while a new turn runs, older
|
||||
|
||||
Reference in New Issue
Block a user