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
@@ -1,4 +1,4 @@
import { useCallback, useEffect, useState, type ReactElement } from "react";
import { useCallback, useState, type ReactElement } from "react";
import {
DndContext,
closestCenter,
@@ -118,14 +118,8 @@ export function SortableInlineList<T>({
onDragBegin?: () => void;
}): ReactElement {
const [activeId, setActiveId] = useState<string | null>(null);
const [items, setItems] = useState<T[]>(() => data);
useEffect(() => {
if (activeId) {
return;
}
setItems((current) => (current === data ? current : data));
}, [activeId, data]);
const [dragItems, setDragItems] = useState<T[] | null>(null);
const items = dragItems ?? data;
const sensors = useSensors(
useSensor(PointerSensor, {
@@ -143,10 +137,11 @@ export function SortableInlineList<T>({
if (disabled) {
return;
}
setDragItems(data);
setActiveId(String(event.active.id));
onDragBegin?.();
},
[disabled, onDragBegin]
[data, disabled, onDragBegin]
);
const handleDragEnd = useCallback(
@@ -154,6 +149,7 @@ export function SortableInlineList<T>({
const { active, over } = event;
setActiveId(null);
setDragItems(null);
if (disabled) {
return;
@@ -169,10 +165,11 @@ export function SortableInlineList<T>({
if (oldIndex >= 0 && newIndex >= 0 && oldIndex !== newIndex) {
const newItems = arrayMove(items, oldIndex, newIndex);
setItems(newItems);
setDragItems(newItems);
onDragEnd?.(newItems);
}
}
},
[disabled, items, keyExtractor, onDragEnd]
);