-
Notifications
You must be signed in to change notification settings - Fork 4.7k
fix(web): no stale-title flash after renaming a thread #7822
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
e9c8f50
11b94da
fec63dc
95217ae
7de3548
ee675b9
0ef381e
ec44a92
2e99cc9
656a84e
f12fbc0
ff8eb75
7e68265
390ef55
093c1a9
77c5150
811ff68
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -109,7 +109,8 @@ import { useEnvironments, usePrimaryEnvironmentId } from "../state/environments" | |
| import { useProjects, useThreadShells } from "../state/entities"; | ||
| import { environmentServerConfigsAtom, primaryServerKeybindingsAtom } from "../state/server"; | ||
| import { vcsEnvironment } from "../state/vcs"; | ||
| import { threadEnvironment } from "../state/threads"; | ||
| import { appAtomRegistry } from "../rpc/atomRegistry"; | ||
| import { environmentThreadShells, threadEnvironment } from "../state/threads"; | ||
| import { useEnvironmentQuery } from "../state/query"; | ||
| import { useAtomCommand } from "../state/use-atom-command"; | ||
| import { | ||
|
|
@@ -1022,6 +1023,9 @@ const SidebarThreadRow = memo(function SidebarThreadRow(props: { | |
| ); | ||
| const handleRenameBlur = useCallback(() => { | ||
| if (!renameCommittedRef.current) { | ||
| // Mark committed so the blur-commit path cannot resubmit if the editor | ||
| // is refocused while it waits for the store to catch up. | ||
| renameCommittedRef.current = true; | ||
| onCommitRename(threadRef, renamingTitle, thread.title); | ||
| } | ||
| }, [onCommitRename, renamingTitle, thread.title, threadRef]); | ||
|
|
@@ -2389,29 +2393,39 @@ export default function Sidebar() { | |
| const cancelThreadRename = useCallback(() => setRenamingThreadKey(null), []); | ||
| const commitThreadRename = useCallback( | ||
| (threadRef: ScopedThreadRef, title: string, originalTitle: string) => { | ||
| void (async () => { | ||
| const trimmed = title.trim(); | ||
| const trimmed = title.trim(); | ||
| if (trimmed.length === 0) { | ||
| setRenamingThreadKey(null); | ||
| if (trimmed.length === 0) { | ||
| toastManager.add({ type: "warning", title: "Thread title cannot be empty" }); | ||
| return; | ||
| } | ||
| if (trimmed === originalTitle) return; | ||
| const result = await updateThreadMetadata({ | ||
| environmentId: threadRef.environmentId, | ||
| input: { threadId: threadRef.threadId, title: trimmed }, | ||
| }); | ||
| if (result._tag === "Failure" && !isAtomCommandInterrupted(result)) { | ||
| const error = squashAtomCommandFailure(result); | ||
| toastManager.add( | ||
| stackedThreadToast({ | ||
| type: "error", | ||
| title: "Failed to rename thread", | ||
| description: error instanceof Error ? error.message : "An error occurred.", | ||
| }), | ||
| ); | ||
| } | ||
| })(); | ||
| toastManager.add({ type: "warning", title: "Thread title cannot be empty" }); | ||
| return; | ||
| } | ||
| if (trimmed === originalTitle) { | ||
| setRenamingThreadKey(null); | ||
| return; | ||
| } | ||
| setRenamingThreadKey(null); | ||
| environmentThreadShells.setOptimisticThreadTitle( | ||
| appAtomRegistry, | ||
| threadRef, | ||
| trimmed, | ||
| originalTitle, | ||
| ); | ||
| void updateThreadMetadata({ | ||
| environmentId: threadRef.environmentId, | ||
| input: { threadId: threadRef.threadId, title: trimmed }, | ||
| }).then((result) => { | ||
| if (result._tag !== "Failure") return; | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Success returns without touching the map, so the entry survives forever and its Stored Smallest fix is to baseline on the raw stored title rather than the displayed one (e.g. read Posted via Macroscope — UI Consistency
macroscopeapp[bot] marked this conversation as resolved.
|
||
| environmentThreadShells.clearOptimisticThreadTitle(appAtomRegistry, threadRef, trimmed); | ||
| if (isAtomCommandInterrupted(result)) return; | ||
| const error = squashAtomCommandFailure(result); | ||
| toastManager.add( | ||
| stackedThreadToast({ | ||
| type: "error", | ||
| title: "Failed to rename thread", | ||
| description: error instanceof Error ? error.message : "An error occurred.", | ||
| }), | ||
| ); | ||
| }); | ||
| }, | ||
| [updateThreadMetadata], | ||
| ); | ||
|
|
@@ -3656,6 +3670,7 @@ export default function Sidebar() { | |
| // not from the sidebar second-guessing what still matters. | ||
| const isCard = section === "active" || section === "pinned"; | ||
| const rowVariant = isCard ? "card" : "slim"; | ||
| const rowThread = thread; | ||
| return ( | ||
| <SidebarThreadRow | ||
| // Keyed per variant on purpose: when a thread settles, | ||
|
|
@@ -3665,7 +3680,7 @@ export default function Sidebar() { | |
| // are translucent, so a crossing row reads as text | ||
| // painted over text). | ||
| key={`${threadKey}:${rowVariant}`} | ||
| thread={thread} | ||
| thread={rowThread} | ||
| variant={rowVariant} | ||
| // Snoozed rows wake; settled rows un-settle (explicit | ||
| // settles clear the override, auto-settled rows get | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Renaming back to the still-stored title inside the coalescing window retires the entry immediately, so the intermediate title still flashes.
Stored
A, rename toB→ entry{title:"B", chain:[A,B]}, row showsB. Rename back toAbeforeB's shell frame lands: the row passes its displayed title (B) asoriginalTitle, sonextOptimisticThreadTitlesproduces{title:"A", chain:[A,B,A]}— but the filtered view (packages/client-runtime/src/state/threadShell.ts:116) prunes onsource.title === entry.title, and the store is still atA. The entry dies on the next read, and whenB's frame arrives every shell reader (row, header, search, menu confirmations) rendersBuntilA's frame lands.The retire rule can't distinguish "store hasn't moved yet" from "store reached the final title". Smallest fix is to make fulfillment depend on the rename actually having settled — e.g. carry a pending-commit count on the entry, incremented here and decremented when
updateThreadMetadatasettles (success as well as failure), and only apply thesource.title === entry.titleprune when that count is zero.ChatHeader.tsx:191goes through the same helper, so one change covers both surfaces.Posted via Macroscope — UI Consistency