Files
buzz/desktop/src/features/messages/ui/useMentionSendFlow.helpers.ts
Logan Johnson a89eae5dce fix(desktop): share draft intent authority across composer visits
Keep authored intent in the scoped draft store independently of value deletion. Supersede recovery, sent cleanup and lifecycle persistence across reentry and remount while preserving ordinary destination-bound sends and untouched optimistic recovery. Keep editability and automatic prefix restoration programmatic. Exercise the real store, React lifecycle transition matrix and retained browser editor.

Co-authored-by: Larry <627498bd4bd1f281a16431e3c6cce3b5c25b6692798c78672298aefbf2f8f8b5@buzz.block.builderlab.xyz>
Signed-off-by: Logan Johnson <loganj@squareup.com>
(cherry picked from commit ff140562c693147730d548582bc39a12d37ce210)
Signed-off-by: Logan Johnson <loganj@squareup.com>
(cherry picked from commit 4b99bc705ebb8a6920c114620360433556bf88f9)
Signed-off-by: Logan Johnson <loganj@squareup.com>
2026-09-01 11:55:17 -04:00

167 lines
5.4 KiB
TypeScript

import type { MentionRevalidationOptions } from "@/features/messages/lib/agentMentionRevalidation";
import type { ManagedAgent } from "@/shared/api/types";
import {
type ImetaMedia,
mergeOutgoingTags,
} from "@/features/messages/lib/imetaMediaMarkdown";
import type { QueuedMediaAttachment } from "@/features/messages/lib/backgroundMediaUploadStore";
import type { PreparedBackgroundLinkPreviews } from "@/features/messages/lib/linkPreviewPreparationStore";
import type { DraftMentionRef } from "@/features/messages/lib/useDrafts";
import { normalizePubkey } from "@/shared/lib/pubkey";
import { MENTION_REFERENCE_TAG } from "@/shared/lib/resolveMentionNames";
export { MENTION_REFERENCE_TAG };
/** A single visit to a source draft; returning to the same key is a new owner. */
export type ComposerDraftOwner = {
channelId: string | null;
draftKey: string | null | undefined;
/** Read shared source-key intent, never another visible draft key. */
getComposerRevision: () => number;
};
export type PendingNonMemberMentionSend = {
sourceOwner: ComposerDraftOwner;
composerRevision: number;
invitationSignal?: AbortSignal;
addressedAgentPubkeys: string[];
inlineAgentMentionPubkeys: string[];
capturedChannelId: string | null;
capturedThreadContext: {
parentEventId: string | null;
threadHeadId: string | null;
} | null;
trimmed: string;
mentionPubkeys: string[];
nonMemberPubkeys: string[];
outgoingTags?: string[][];
preparedLinkPreviews?: PreparedBackgroundLinkPreviews | null;
preparedManagedAgents?: ManagedAgent[];
readyAgentPubkeys?: string[];
savedContent: string;
savedImeta: ImetaMedia[];
queuedAttachments: QueuedMediaAttachment[];
savedSpoileredAttachmentUrls: Set<string>;
sentDraftKey: string | null | undefined;
recoveryDraftKey: string | null | undefined;
savedMentionRefs: DraftMentionRef[];
};
export type SendMessageWithMentionFlowInput = {
addressedAgentPubkeys?: readonly string[];
capturedChannelId: string | null;
capturedThreadContext?: PendingNonMemberMentionSend["capturedThreadContext"];
pendingImeta: ImetaMedia[];
queuedAttachments?: QueuedMediaAttachment[];
linkPreviewTags?: string[][];
preparedLinkPreviews?: PreparedBackgroundLinkPreviews | null;
sentDraftKey: string | null | undefined;
recoveryDraftKey: string | null | undefined;
spoileredAttachmentUrls?: ReadonlySet<string>;
trimmed: string;
};
export async function resolvePreviewTags(
draft: Pick<PendingNonMemberMentionSend, "preparedLinkPreviews">,
mediaTags: string[][] | undefined,
outgoingTags: string[][] | undefined,
): Promise<string[][] | null> {
const result = await draft.preparedLinkPreviews?.promise;
if (result?.status === "cancelled") return null;
return (
mergeOutgoingTags(mediaTags, [
...(outgoingTags ?? []),
...(result?.tags ?? []),
]) ?? []
);
}
export function mergeOutgoingTagsWithReferenceMentions(
outgoingTags: string[][] | undefined,
pubkeys: Iterable<string>,
) {
const normalizedPubkeys = uniqueNormalizedPubkeys(pubkeys);
if (normalizedPubkeys.length === 0) {
return outgoingTags;
}
return [
...(outgoingTags ?? []),
...normalizedPubkeys.map((pubkey) => [MENTION_REFERENCE_TAG, pubkey]),
];
}
export function getErrorMessage(error: unknown, fallback: string) {
if (error instanceof Error && error.message) return error.message;
if (typeof error === "string" && error.trim()) return error;
if (
typeof error === "object" &&
error !== null &&
"message" in error &&
typeof error.message === "string" &&
error.message.trim()
) {
return error.message;
}
return fallback;
}
export function formatMessageSendError(error: unknown) {
return `Message failed to send: ${getErrorMessage(error, "Unknown error")}`;
}
export function uniqueNormalizedPubkeys(pubkeys: Iterable<string>) {
return [...new Set([...pubkeys].map(normalizePubkey))].filter(Boolean);
}
export function mergeMentionRecipients(
explicitMentionPubkeys: Iterable<string>,
addressedAgentPubkeys: Iterable<string>,
) {
return uniqueNormalizedPubkeys([
...explicitMentionPubkeys,
...addressedAgentPubkeys,
]);
}
export function isManagedAgentRunning(agent: ManagedAgent) {
return agent.status === "running" || agent.status === "deployed";
}
export function isProviderBackedAgent(agent: ManagedAgent) {
return agent.backend.type === "provider";
}
/** Carry captured recipient identity through composer clearing and uploads. */
export function mentionRevalidationOptions(
draft: Pick<
PendingNonMemberMentionSend,
"inlineAgentMentionPubkeys" | "addressedAgentPubkeys"
>,
phase: "prepare" | "publish",
preparedAgentPubkeys: readonly string[] = [],
): MentionRevalidationOptions {
return {
phase,
intendedAgentPubkeys: uniqueNormalizedPubkeys([
...draft.inlineAgentMentionPubkeys,
...draft.addressedAgentPubkeys,
...preparedAgentPubkeys,
]),
};
}
/** Explicit Send without inviting retains nonmembers only as reference tags. */
export function withoutInvitingRecipients(draft: PendingNonMemberMentionSend) {
const nonMemberPubkeys = new Set(draft.nonMemberPubkeys.map(normalizePubkey));
return {
mentionPubkeys: draft.mentionPubkeys.filter(
(pubkey) => !nonMemberPubkeys.has(normalizePubkey(pubkey)),
),
outgoingTags: mergeOutgoingTagsWithReferenceMentions(
draft.outgoingTags,
nonMemberPubkeys,
),
};
}