+
+ The repairs are verified in{" "}
+
+ .
+
+ ,
+ );
+ const link = screen.getByRole("link", { name: "Thread, Code store foundation" });
+ await expect.element(link).toHaveAttribute("href", `/local/${encodeURIComponent(threadId)}`);
+ await screen.unmount();
+ expect(appAtomRegistry.get(familyLoaded)).toBe(false);
+});
diff --git a/apps/web/src/components/chat/MarkdownThreadLink.tsx b/apps/web/src/components/chat/MarkdownThreadLink.tsx
index b5d3bbb5ac782..cc90b5f995ecc 100644
--- a/apps/web/src/components/chat/MarkdownThreadLink.tsx
+++ b/apps/web/src/components/chat/MarkdownThreadLink.tsx
@@ -29,17 +29,17 @@ export function MarkdownThreadLink(props: {
written === null && decodedId !== null ? scopeThreadRef(environmentId, decodedId) : null,
);
const local = written ?? decoded;
- // Shells leave out idle subagent children and older settled threads, so a cited thread the
- // shell lacks is read with its family until it lands; only a settled empty read proves it gone.
- const missingThreadId = local === null ? (decodedId ?? props.link.threadId) : null;
- // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- mounts the family read for a cited thread the shell omits while this link shows it
- useEffect(
- () =>
- missingThreadId === null
- ? undefined
- : threadFamilies.mount(appAtomRegistry, environmentId, missingThreadId),
- [environmentId, missingThreadId],
- );
+ const threadId = props.link.threadId;
+ // oxlint-disable-next-line t3code/no-unreasoned-state-sync -- keeps cited rows mounted for the link's lifetime, independently of the family data it loads
+ useEffect(() => {
+ const ids = decodedId === null ? [threadId] : [threadId, decodedId];
+ const releases = ids.map((id) =>
+ threadFamilies.mount(appAtomRegistry, environmentId, id, { onlyWhenMissing: true }),
+ );
+ return () => {
+ for (const release of releases) release();
+ };
+ }, [environmentId, threadId, decodedId]);
const guess = local === null && props.link.environmentId === null;
const refs = useThreadRefs(guess);
const remote = useThreadShell(guess ? guessThreadLinkRef(props.link, refs) : null);
diff --git a/packages/client-runtime/src/state/threadFamilies.test.ts b/packages/client-runtime/src/state/threadFamilies.test.ts
index b08656975dbd9..df30aef139b2e 100644
--- a/packages/client-runtime/src/state/threadFamilies.test.ts
+++ b/packages/client-runtime/src/state/threadFamilies.test.ts
@@ -125,8 +125,12 @@ describe("createThreadFamilyAtoms", () => {
yield* Queue.unbounded<
Effect.Effect