diff --git a/apps/app/src/channel.tsx b/apps/app/src/channel.tsx index 49d66b13..a4a0766a 100644 --- a/apps/app/src/channel.tsx +++ b/apps/app/src/channel.tsx @@ -309,7 +309,12 @@ export function Channel( const opener = useRef<() => void>(undefined); useEffect(() => { - publish(channel.id, { diff: () => opener.current?.() }); + publish(channel.id, { + diff: (file) => { + if (file) publish(channel.id, { reveal: { chat, file, key: Date.now() } }); + opener.current?.(); + }, + }); return () => forget(channel.id); }, [channel.id]); diff --git a/apps/app/src/conversation.tsx b/apps/app/src/conversation.tsx index b9b394fa..e0ba0d75 100644 --- a/apps/app/src/conversation.tsx +++ b/apps/app/src/conversation.tsx @@ -8,7 +8,7 @@ import { Composer } from "./composer"; import { DeleteMessage } from "./delete-message"; import { desktop } from "./desktop"; import { EditMessage } from "./edit-message"; -import { publish } from "./details"; +import { publish, useDetails } from "./details"; import { host } from "./host"; import { MessageQueue } from "./message-queue"; import { OfflineHost } from "./onboarding"; @@ -54,6 +54,7 @@ export function Conversation( // until then is not the user's to retry. const failed = !offline && !transition && transcript.error; const chat = transcript.info?.chats.find((value) => value.id === id); + const { diff } = useDetails(channel.id); const model = chat?.model || channel.model; const people = useSyncExternalStore(host.subscribe, () => host.people); // This app's own host, or the peer host relaying this channel to it, stopped answering. @@ -217,6 +218,9 @@ export function Conversation( return source && (() => onOpen(source)); }} intro={{ name: channel.name, createdAt: Math.floor(channel.created / 1000) }} + onFileOpen={diff} + onDiffFileOpen={diff} + onReviewChanges={diff && (() => diff())} /> ) : offline diff --git a/apps/app/src/details.ts b/apps/app/src/details.ts index 3cc7e9f7..ca92ffe4 100644 --- a/apps/app/src/details.ts +++ b/apps/app/src/details.ts @@ -32,8 +32,10 @@ export type Details = { sharedLive?: boolean; desktop?: boolean; desktopLive?: boolean; - /** Shows the chat's Diff tab, opening one when none is open. */ - diff?: () => void; + /** Shows the chat's Diff tab, opening one when none is open, at `file` when it changed. */ + diff?: (file?: string) => void; + /** A file the chat's Diff should show once its changes list it; `null` once shown. */ + reveal?: { chat: number; file: string; key: number } | null; }; const EMPTY: Details = {}; diff --git a/apps/app/src/panels/diff.tsx b/apps/app/src/panels/diff.tsx index 798eb926..d00fbc20 100644 --- a/apps/app/src/panels/diff.tsx +++ b/apps/app/src/panels/diff.tsx @@ -87,7 +87,7 @@ function Note({ children }: { children: string }) { /** The chat's changes, polled while the tab is visible and the window focused. */ export function Diff({ channel: listing, chat, user, active }: Props) { const channel = listing.id; - const { pull } = useDetails(channel); + const { pull, reveal } = useDetails(channel); const git = useGit(listing, chat, user); const status = useSyncExternalStore(host.subscribe, () => host.status); const focused = useSyncExternalStore(subscribe, () => document.hasFocus()); @@ -95,6 +95,7 @@ export function Diff({ channel: listing, chat, user, active }: Props) { const [error, setError] = useState(); const [loaded, setLoaded] = useState(new Map()); const [first, setFirst] = useState(); + const [selected, setSelected] = useState<{ file?: string; key?: number }>({}); const inflight = useRef(new Set()); useEffect(() => { @@ -121,6 +122,14 @@ export function Diff({ channel: listing, chat, user, active }: Props) { }; }, [channel, chat, active, focused, status]); + // A turn's file card asks for its file before this tab may have read the changes listing it. + useEffect(() => { + if (!reveal || reveal.chat !== chat || !changes) return; + const change = changes.files.find((change) => change.file === reveal.file); + if (change) setSelected({ file: key(change.file, group(change)), key: reveal.key }); + publish(channel, { reveal: null }); + }, [channel, chat, changes, reveal]); + // Git actions read the details' changes, so a fetch's new behind count reaches them from here. useEffect(() => { if (changes) publish(channel, { changes }); @@ -229,6 +238,9 @@ export function Diff({ channel: listing, chat, user, active }: Props) { onCreateDraftPr={git.createDraftPr} actions={git.actions} onFileOpen={open} + selected={selected.file} + selectedKey={selected.key} + onSelectedChange={(file) => setSelected({ file })} className="rounded-none border-0" /> );