mirror of
https://github.com/block/buzz.git
synced 2026-11-03 07:51:22 +00:00
Fix task conversation review issues
This commit is contained in:
@@ -91,8 +91,8 @@ const overrides = new Map([
|
||||
// applyWorkspace reposDir parameter plus the validateReposDir binding,
|
||||
// threaded through Tauri invokes for configurable repos_dir, plus the
|
||||
// harness-persona-sync `harnessOverride` create-input bit — load-bearing
|
||||
// parameter plumbing, not generic debt growth. Approved override; still
|
||||
// queued to split.
|
||||
// parameter plumbing, plus continued-agent-conversations client task-anchor
|
||||
// tags on message sends. Approved override; still queued to split.
|
||||
["src/shared/api/tauri.ts", 1235],
|
||||
// harness-persona-sync feature growth, queued to split in the resolver-unify
|
||||
// refactor followup. discovery.rs is dominated by the new test module
|
||||
|
||||
@@ -283,6 +283,7 @@ pub async fn send_channel_message(
|
||||
media_tags: Option<Vec<Vec<String>>>,
|
||||
emoji_tags: Option<Vec<Vec<String>>>,
|
||||
mention_tags: Option<Vec<Vec<String>>>,
|
||||
client_tags: Option<Vec<Vec<String>>>,
|
||||
mention_pubkeys: Option<Vec<String>>,
|
||||
kind: Option<u32>,
|
||||
state: State<'_, AppState>,
|
||||
@@ -294,7 +295,11 @@ pub async fn send_channel_message(
|
||||
let media = media_tags.unwrap_or_default();
|
||||
let emoji = emoji_tags.unwrap_or_default();
|
||||
let mention_refs_only = mention_tags.unwrap_or_default();
|
||||
let client = client_tags.unwrap_or_default();
|
||||
let kind_num = kind.unwrap_or(buzz_core_pkg::kind::KIND_STREAM_MESSAGE);
|
||||
if kind_num != buzz_core_pkg::kind::KIND_STREAM_MESSAGE && !client.is_empty() {
|
||||
return Err("client tags are only supported on stream messages".into());
|
||||
}
|
||||
|
||||
let mut resolved_root: Option<String> = None;
|
||||
|
||||
@@ -330,7 +335,7 @@ pub async fn send_channel_message(
|
||||
}
|
||||
None => None,
|
||||
};
|
||||
events::build_message(
|
||||
events::build_message_with_client_tags(
|
||||
channel_uuid,
|
||||
content.trim(),
|
||||
thread_ref.as_ref(),
|
||||
@@ -338,6 +343,7 @@ pub async fn send_channel_message(
|
||||
&media,
|
||||
&emoji,
|
||||
&mention_refs_only,
|
||||
&client,
|
||||
)?
|
||||
}
|
||||
};
|
||||
|
||||
@@ -556,6 +556,9 @@ export function AgentConversationScreen({
|
||||
mediaTags?: string[][],
|
||||
) => {
|
||||
await sendMessageMutation.mutateAsync({
|
||||
clientTags: [
|
||||
["client", "agent-conversation", conversation.agentReply.id],
|
||||
],
|
||||
content,
|
||||
mediaTags,
|
||||
mentionPubkeys: buildAgentConversationMentionPubkeys({
|
||||
@@ -565,7 +568,12 @@ export function AgentConversationScreen({
|
||||
parentEventId: replyParentEventId,
|
||||
});
|
||||
},
|
||||
[autoRoutedAgentPubkeys, replyParentEventId, sendMessageMutation],
|
||||
[
|
||||
autoRoutedAgentPubkeys,
|
||||
conversation.agentReply.id,
|
||||
replyParentEventId,
|
||||
sendMessageMutation,
|
||||
],
|
||||
);
|
||||
|
||||
const isComposerDisabled =
|
||||
|
||||
@@ -126,6 +126,7 @@ export function createOptimisticMessage(
|
||||
mentionPubkeys: string[] = [],
|
||||
parentEventId: string | null = null,
|
||||
mediaTags: string[][] = [],
|
||||
clientTags: string[][] = [],
|
||||
): RelayEvent {
|
||||
const localKey = `optimistic-${crypto.randomUUID()}`;
|
||||
const tags: string[][] = [];
|
||||
@@ -154,6 +155,9 @@ export function createOptimisticMessage(
|
||||
for (const tag of mediaTags) {
|
||||
tags.push(tag);
|
||||
}
|
||||
for (const tag of clientTags) {
|
||||
tags.push(tag);
|
||||
}
|
||||
|
||||
return {
|
||||
id: localKey,
|
||||
@@ -341,10 +345,12 @@ export function useSendMessageMutation(
|
||||
mentionPubkeys?: string[];
|
||||
parentEventId?: string | null;
|
||||
mediaTags?: string[][];
|
||||
clientTags?: string[][];
|
||||
},
|
||||
MessageQueryContext | undefined
|
||||
>({
|
||||
mutationFn: async ({
|
||||
clientTags,
|
||||
content,
|
||||
mentionPubkeys,
|
||||
parentEventId,
|
||||
@@ -371,7 +377,12 @@ export function useSendMessageMutation(
|
||||
// Messages carrying media OR custom-emoji tags MUST go through REST so
|
||||
// the relay's tag validation runs. The WebSocket path emits no extra
|
||||
// tags, so emoji-only messages would otherwise lose their emoji tag.
|
||||
if (parentEventId || imetaTags.length > 0 || emojiTags.length > 0) {
|
||||
if (
|
||||
parentEventId ||
|
||||
imetaTags.length > 0 ||
|
||||
emojiTags.length > 0 ||
|
||||
(clientTags?.length ?? 0) > 0
|
||||
) {
|
||||
const cachedMessages =
|
||||
queryClient.getQueryData<RelayEvent[]>(
|
||||
channelMessagesKey(channel.id),
|
||||
@@ -385,6 +396,7 @@ export function useSendMessageMutation(
|
||||
undefined,
|
||||
emojiTags,
|
||||
mentionTags,
|
||||
clientTags,
|
||||
);
|
||||
|
||||
// Build tags matching relay-emitted shape: h, author p, mention ps, reply es, imeta, emoji.
|
||||
@@ -423,6 +435,7 @@ export function useSendMessageMutation(
|
||||
...imetaTags,
|
||||
...emojiTags,
|
||||
...mentionTags,
|
||||
...(clientTags ?? []),
|
||||
],
|
||||
content: content.trim(),
|
||||
sig: "",
|
||||
@@ -436,7 +449,13 @@ export function useSendMessageMutation(
|
||||
mentionTags,
|
||||
);
|
||||
},
|
||||
onMutate: async ({ content, mentionPubkeys, parentEventId, mediaTags }) => {
|
||||
onMutate: async ({
|
||||
clientTags,
|
||||
content,
|
||||
mentionPubkeys,
|
||||
parentEventId,
|
||||
mediaTags,
|
||||
}) => {
|
||||
if (!channel || !identity || channel.channelType === "forum") {
|
||||
return undefined;
|
||||
}
|
||||
@@ -454,6 +473,7 @@ export function useSendMessageMutation(
|
||||
mentionPubkeys ?? [],
|
||||
parentEventId ?? null,
|
||||
mediaTags ?? [],
|
||||
clientTags ?? [],
|
||||
);
|
||||
|
||||
queryClient.setQueryData<RelayEvent[]>(
|
||||
|
||||
@@ -883,8 +883,7 @@ export const MessageRow = React.memo(
|
||||
prev.onCollapseDescendants === next.onCollapseDescendants &&
|
||||
prev.onCollapseDescendantsHoverChange ===
|
||||
next.onCollapseDescendantsHoverChange &&
|
||||
Boolean(prev.onOpenAgentConversation) ===
|
||||
Boolean(next.onOpenAgentConversation) &&
|
||||
prev.onOpenAgentConversation === next.onOpenAgentConversation &&
|
||||
prev.profiles === next.profiles &&
|
||||
prev.searchQuery === next.searchQuery &&
|
||||
prev.videoReviewContext === next.videoReviewContext,
|
||||
|
||||
@@ -265,6 +265,7 @@ const MessageTimelineBase = React.forwardRef<
|
||||
isLoading: showTimelineSkeleton,
|
||||
messages: deferredMessages,
|
||||
onTargetReached,
|
||||
resetKey: scrollRouteKey,
|
||||
scrollContainerRef,
|
||||
targetMessageId,
|
||||
});
|
||||
|
||||
@@ -40,6 +40,8 @@ type UseAnchoredScrollOptions = {
|
||||
contentRef: React.RefObject<HTMLDivElement | null>;
|
||||
/** Resets when changed; lets us drop anchor + scroll state across channels. */
|
||||
channelId?: string | null;
|
||||
/** Resets when changed; includes channel plus route-specific layout state. */
|
||||
resetKey?: string | null;
|
||||
/** Suppresses initial scroll-to-bottom while a skeleton is showing. */
|
||||
isLoading: boolean;
|
||||
/** Source of truth for the rendered list. Used to detect new-at-bottom
|
||||
@@ -145,6 +147,7 @@ export function useAnchoredScroll({
|
||||
scrollContainerRef,
|
||||
contentRef,
|
||||
channelId,
|
||||
resetKey = channelId ?? null,
|
||||
isLoading,
|
||||
messages,
|
||||
|
||||
@@ -181,10 +184,10 @@ export function useAnchoredScroll({
|
||||
// guard runs on a native scroll event, outside React's render cycle.
|
||||
const settlingRef = React.useRef(false);
|
||||
|
||||
// Reset everything when the channel changes — the layout effect that runs
|
||||
// immediately after this reset is responsible for either jumping to bottom
|
||||
// or to the target message for the new channel.
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: channelId is intentionally the sole trigger — we want this effect to fire exactly when the channel changes (and on mount).
|
||||
// Reset everything when the route's scroll identity changes — the layout
|
||||
// effect that runs immediately after this reset is responsible for either
|
||||
// jumping to bottom or to the target message for the new view.
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: resetKey is intentionally the sole trigger — it includes channel identity plus route-specific layout state.
|
||||
React.useLayoutEffect(() => {
|
||||
anchorRef.current = { kind: "at-bottom" };
|
||||
setIsAtBottom(true);
|
||||
@@ -205,7 +208,7 @@ export function useAnchoredScroll({
|
||||
cancelAnimationFrame(mountPinRafIdRef.current);
|
||||
mountPinRafIdRef.current = null;
|
||||
}
|
||||
}, [channelId]);
|
||||
}, [resetKey]);
|
||||
|
||||
const scrollToBottomImperative = React.useCallback(
|
||||
(behavior: ScrollBehavior = "auto") => {
|
||||
@@ -454,7 +457,7 @@ export function useAnchoredScroll({
|
||||
// mid-history, native scroll anchoring (overflow-anchor) holds the reading
|
||||
// row across the reflow, so there's nothing to do.
|
||||
// ---------------------------------------------------------------------------
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: channelId is a deliberate re-subscription trigger — the effect body reads only the stable refs, but on a channel switch the keyed scroll container remounts and contentRef.current becomes a fresh node, so the observer must disconnect from the previous channel's detached node and re-observe the live one.
|
||||
// biome-ignore lint/correctness/useExhaustiveDependencies: resetKey is a deliberate re-subscription trigger — the effect body reads only the stable refs, but on route identity changes the keyed scroll container remounts and contentRef.current becomes a fresh node, so the observer must disconnect from the previous route's detached node and re-observe the live one.
|
||||
React.useEffect(() => {
|
||||
const content = contentRef.current;
|
||||
if (!content || typeof ResizeObserver === "undefined") return;
|
||||
@@ -467,7 +470,7 @@ export function useAnchoredScroll({
|
||||
});
|
||||
observer.observe(content);
|
||||
return () => observer.disconnect();
|
||||
}, [channelId, contentRef, scrollContainerRef]);
|
||||
}, [resetKey, contentRef, scrollContainerRef]);
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Target message handling (deep link, jump-to-reply, etc.). Distinct from
|
||||
|
||||
@@ -717,6 +717,7 @@ export async function sendChannelMessage(
|
||||
kind?: number,
|
||||
emojiTags?: string[][],
|
||||
mentionTags?: string[][],
|
||||
clientTags?: string[][],
|
||||
): Promise<SendChannelMessageResult> {
|
||||
const response = await invokeTauri<RawSendChannelMessageResult>(
|
||||
"send_channel_message",
|
||||
@@ -727,6 +728,7 @@ export async function sendChannelMessage(
|
||||
mediaTags: mediaTags ?? null,
|
||||
emojiTags: emojiTags ?? null,
|
||||
mentionTags: mentionTags ?? null,
|
||||
clientTags: clientTags ?? null,
|
||||
mentionPubkeys: mentionPubkeys ?? null,
|
||||
kind: kind ?? null,
|
||||
},
|
||||
|
||||
Reference in New Issue
Block a user