| author | |
| committer | |
| log | 18fa89c4adaae722e619f6abde093becf80efc08 |
| tree | ae47196c180750ef2d0db2c6b744ee9b18ac853a |
| parent | f74eb62661734927aea45a243e2c3effced43fb9 |
| signature | Signed by PGP key B5690EEEBB952194 |
Co-authored-by: codex <codex@users.noreply.github.com>3 files changed, 61 insertions(+), 3 deletions(-)
apps/web/src/components/Sidebar.tsx+10-3| ... | ... | @@ -118,7 +118,7 @@ import { vcsEnvironment } from "../state/vcs"; |
| 118 | 118 | import { useEnvironment, useEnvironments, usePrimaryEnvironmentId } from "../state/environments"; |
| 119 | 119 | import { |
| 120 | 120 | buildThreadRouteParams, |
| 121 | resolveThreadRouteRef, | |
| 121 | resolveActiveThreadRouteRef, | |
| 122 | 122 | resolveThreadRouteTarget, |
| 123 | 123 | } from "../threadRoutes"; |
| 124 | 124 | import { stackedThreadToast, toastManager } from "./ui/toast"; |
| ... | ... | @@ -3068,10 +3068,17 @@ export default function Sidebar() { |
| 3068 | 3068 | const handleNewThread = useNewThreadHandler(); |
| 3069 | 3069 | const { archiveThread, deleteThread } = useThreadActions(); |
| 3070 | 3070 | const { isMobile, setOpenMobile } = useSidebar(); |
| 3071 | const routeThreadRef = useParams({ | |
| 3071 | const routeTarget = useParams({ | |
| 3072 | 3072 | strict: false, |
| 3073 | select: (params) => resolveThreadRouteRef(params), | |
| 3073 | select: (params) => resolveThreadRouteTarget(params), | |
| 3074 | 3074 | }); |
| 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 | ); | |
| 3075 | 3082 | const routeThreadKey = routeThreadRef ? scopedThreadKey(routeThreadRef) : null; |
| 3076 | 3083 | const routeTerminalOpen = useTerminalUiStateStore((state) => |
| 3077 | 3084 | routeThreadRef |
apps/web/src/threadRoutes.test.ts+28| ... | ... | @@ -6,6 +6,7 @@ import { DraftId } from "./composerDraftStore"; |
| 6 | 6 | import { |
| 7 | 7 | buildDraftThreadRouteParams, |
| 8 | 8 | buildThreadRouteParams, |
| 9 | resolveActiveThreadRouteRef, | |
| 9 | 10 | resolveThreadRouteRef, |
| 10 | 11 | resolveThreadRouteTarget, |
| 11 | 12 | } from "./threadRoutes"; |
| ... | ... | @@ -64,4 +65,31 @@ describe("threadRoutes", () => { |
| 64 | 65 | draftId: "draft-1", |
| 65 | 66 | }); |
| 66 | 67 | }); |
| 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 | }); | |
| 67 | 95 | }); |
apps/web/src/threadRoutes.ts+23| ... | ... | @@ -12,6 +12,12 @@ export type ThreadRouteTarget = |
| 12 | 12 | draftId: DraftId; |
| 13 | 13 | }; |
| 14 | 14 | |
| 15 | type DraftThreadRouteState = { | |
| 16 | environmentId: EnvironmentId; | |
| 17 | threadId: ThreadId; | |
| 18 | promotedTo?: ScopedThreadRef | null; | |
| 19 | }; | |
| 20 | ||
| 15 | 21 | export function buildThreadRouteParams(ref: ScopedThreadRef): { |
| 16 | 22 | environmentId: EnvironmentId; |
| 17 | 23 | threadId: ThreadId; |
| ... | ... | @@ -57,3 +63,20 @@ export function resolveThreadRouteTarget( |
| 57 | 63 | draftId: params.draftId as DraftId, |
| 58 | 64 | }; |
| 59 | 65 | } |
| 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 | } |