Skip to content
This repository was archived by the owner on Oct 10, 2026. It is now read-only.

Commit 248769f

Browse files
juliusmarmingecodex
authored andcommitted
Preserve draft thread highlighting during promotion (pingdotgg#4283)
Co-authored-by: codex <codex@users.noreply.github.com> (cherry picked from commit 18fa89c)
1 parent 859f78e commit 248769f

3 files changed

Lines changed: 61 additions & 3 deletions

File tree

‎apps/web/src/components/Sidebar.tsx‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -118,7 +118,7 @@ import { vcsEnvironment } from "../state/vcs";
118118
import { useEnvironment, useEnvironments, usePrimaryEnvironmentId } from "../state/environments";
119119
import {
120120
buildThreadRouteParams,
121-
resolveThreadRouteRef,
121+
resolveActiveThreadRouteRef,
122122
resolveThreadRouteTarget,
123123
} from "../threadRoutes";
124124
import { stackedThreadToast, toastManager } from "./ui/toast";
@@ -3068,10 +3068,17 @@ export default function Sidebar() {
30683068
const handleNewThread = useNewThreadHandler();
30693069
const { archiveThread, deleteThread } = useThreadActions();
30703070
const { isMobile, setOpenMobile } = useSidebar();
3071-
const routeThreadRef = useParams({
3071+
const routeTarget = useParams({
30723072
strict: false,
3073-
select: (params) => resolveThreadRouteRef(params),
3073+
select: (params) => resolveThreadRouteTarget(params),
30743074
});
3075+
const routeDraftThread = useComposerDraftStore((store) =>
3076+
routeTarget?.kind === "draft" ? store.getDraftSession(routeTarget.draftId) : null,
3077+
);
3078+
const routeThreadRef = useMemo(
3079+
() => resolveActiveThreadRouteRef(routeTarget, routeDraftThread),
3080+
[routeDraftThread, routeTarget],
3081+
);
30753082
const routeThreadKey = routeThreadRef ? scopedThreadKey(routeThreadRef) : null;
30763083
const routeTerminalOpen = useTerminalUiStateStore((state) =>
30773084
routeThreadRef

‎apps/web/src/threadRoutes.test.ts‎

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import { DraftId } from "./composerDraftStore";
66
import {
77
buildDraftThreadRouteParams,
88
buildThreadRouteParams,
9+
resolveActiveThreadRouteRef,
910
resolveThreadRouteRef,
1011
resolveThreadRouteTarget,
1112
} from "./threadRoutes";
@@ -64,4 +65,31 @@ describe("threadRoutes", () => {
6465
draftId: "draft-1",
6566
});
6667
});
68+
69+
it("resolves the backing thread while a draft route is being promoted", () => {
70+
const target = resolveThreadRouteTarget({ draftId: "draft-1" });
71+
72+
expect(
73+
resolveActiveThreadRouteRef(target, {
74+
environmentId: "env-1" as never,
75+
threadId: ThreadId.make("draft-thread"),
76+
promotedTo: scopeThreadRef("env-2" as never, ThreadId.make("server-thread")),
77+
}),
78+
).toEqual({
79+
environmentId: "env-2",
80+
threadId: "server-thread",
81+
});
82+
});
83+
84+
it("does not treat a draft's reserved thread ref as an active sidebar thread", () => {
85+
const target = resolveThreadRouteTarget({ draftId: "draft-1" });
86+
87+
expect(
88+
resolveActiveThreadRouteRef(target, {
89+
environmentId: "env-1" as never,
90+
threadId: ThreadId.make("draft-thread"),
91+
promotedTo: null,
92+
}),
93+
).toBeNull();
94+
});
6795
});

‎apps/web/src/threadRoutes.ts‎

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,12 @@ export type ThreadRouteTarget =
1212
draftId: DraftId;
1313
};
1414

15+
type DraftThreadRouteState = {
16+
environmentId: EnvironmentId;
17+
threadId: ThreadId;
18+
promotedTo?: ScopedThreadRef | null;
19+
};
20+
1521
export function buildThreadRouteParams(ref: ScopedThreadRef): {
1622
environmentId: EnvironmentId;
1723
threadId: ThreadId;
@@ -57,3 +63,20 @@ export function resolveThreadRouteTarget(
5763
draftId: params.draftId as DraftId,
5864
};
5965
}
66+
67+
/**
68+
* Resolves the thread represented by either a canonical thread route or a
69+
* draft route whose promotion to a server thread has been recorded.
70+
*/
71+
export function resolveActiveThreadRouteRef(
72+
target: ThreadRouteTarget | null,
73+
draftThread: DraftThreadRouteState | null,
74+
): ScopedThreadRef | null {
75+
if (target?.kind === "server") {
76+
return target.threadRef;
77+
}
78+
if (target?.kind !== "draft" || !draftThread?.promotedTo) {
79+
return null;
80+
}
81+
return draftThread.promotedTo;
82+
}

0 commit comments

Comments
 (0)