feat: canonical workspace tab IDs with URL routing and draft agent tabs

Rewrite workspace tab management to use stable tab IDs (agent_, terminal_, draft_, file_) with
dedicated /tab/[tabId] routes, replace separate file-tabs store with unified workspace-tabs store,
support multiple concurrent draft agent tabs, and detect text files by content instead of extension allowlist.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Mohamed Boudra
2026-03-03 20:11:55 +07:00
co-authored by Claude Opus 4.6
parent 97f0f4b266
commit 73eabd2eea
17 changed files with 1732 additions and 768 deletions
@@ -436,16 +436,28 @@ function AgentScreenContent({
);
const streamItems = streamItemsRaw ?? EMPTY_STREAM_ITEMS;
const pendingCreate = useCreateFlowStore((state) => state.pending);
const pendingByDraftId = useCreateFlowStore((state) => state.pendingByDraftId);
const markPendingCreateLifecycle = useCreateFlowStore(
(state) => state.markLifecycle
);
const clearPendingCreate = useCreateFlowStore((state) => state.clear);
const isPendingCreateForRoute =
Boolean(pendingCreate) &&
pendingCreate?.lifecycle === "active" &&
pendingCreate?.serverId === serverId &&
pendingCreate?.agentId === resolvedAgentId;
const pendingCreate = useMemo(() => {
if (!resolvedAgentId) {
return null;
}
const values = Object.values(pendingByDraftId);
for (const entry of values) {
if (
entry.lifecycle === "active" &&
entry.serverId === serverId &&
entry.agentId === resolvedAgentId
) {
return entry;
}
}
return null;
}, [pendingByDraftId, resolvedAgentId, serverId]);
const isPendingCreateForRoute = Boolean(pendingCreate);
// Select only the specific initializing state
const isInitializingFromMap = useSessionStore((state) =>
@@ -740,7 +752,7 @@ function AgentScreenContent({
item.kind === "user_message" &&
item.id === pendingCreate.clientMessageId
);
if (hasUserMessage && canFinalizePendingCreate) {
if (hasUserMessage && canFinalizePendingCreate) {
if (
resolvedAgentId &&
pendingCreate.images &&
@@ -766,8 +778,8 @@ function AgentScreenContent({
return next;
});
}
markPendingCreateLifecycle("sent");
clearPendingCreate();
markPendingCreateLifecycle({ draftId: pendingCreate.draftId, lifecycle: "sent" });
clearPendingCreate({ draftId: pendingCreate.draftId });
}
}, [
canFinalizePendingCreate,