feat(workspace): no double resume, server-picked tabs, terminal a11y
- A resume of a session a live pane already holds is refused (409 ESESSIONLIVE with error.run_id) and the client opens that pane, pasting any typed message there; catalog tools refuse resumeSessionId (EUNSUPPORTED) instead of silently starting fresh. - POST /api/lanes/:id/start takes slot: "auto": the server picks the free tab under the lane lock (components/lanes/startSession.ts on the client, kept out of WorkspaceHome so the sidebar does not pull react-markdown into the main bundle). - Unsaved whole-file edits are guarded on every exit (Back, another file, tab switch, close, lane/session change, beforeunload). - Closing an AI tab asks first; hiding a pane has its own icon. Tabs and pane headers show the session name, a state dot, rename by double-click. - Terminal: Ctrl+M toggles Tab-moves-focus (no keyboard trap), Cmd+F / Ctrl+Shift+F find bar, clickable links (noopener), a write-only OSC 52 clipboard, a Reconnecting overlay, debounced resizes, optional screen reader mode. Alt+1-4 / Alt+\ / Alt+W focus, split and hide panes. - Changes panel: draggable splitter, wrapping git bar, history paging, disarming discard/undo/abort, inline diff comments pasted only into a visible pane; drag MIME guard on sidebar rows; IME-safe Enter; Show more in the sidebar and Recents; idle dots readable with sr-only text. - Adds @xterm/addon-clipboard, @xterm/addon-search, @xterm/addon-web-links.
This commit is contained in:
@@ -8,10 +8,11 @@
|
||||
* @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from "react";
|
||||
import { useEffect, useRef, useState } from "react";
|
||||
import { useTranslation } from "react-i18next";
|
||||
import { FolderOpen, FolderGit2, ArrowUp } from "lucide-react";
|
||||
import { api } from "../../lib/api";
|
||||
import { useDialog } from "../../hooks/useDialog";
|
||||
|
||||
export function FolderBrowseModal({
|
||||
open,
|
||||
@@ -45,14 +46,10 @@ export function FolderBrowseModal({
|
||||
// eslint-disable-next-line react-hooks/exhaustive-deps
|
||||
}, [open]);
|
||||
|
||||
useEffect(() => {
|
||||
if (!open) return;
|
||||
const onKey = (e: KeyboardEvent) => {
|
||||
if (e.key === "Escape") onClose();
|
||||
};
|
||||
document.addEventListener("keydown", onKey);
|
||||
return () => document.removeEventListener("keydown", onKey);
|
||||
}, [open, onClose]);
|
||||
// The shared dialog contract: focus in, Tab trapped, Escape closes (only
|
||||
// this one when it sits over the Add-workspace modal), focus restored.
|
||||
const panel = useRef<HTMLDivElement>(null);
|
||||
useDialog(open, panel, onClose);
|
||||
|
||||
if (!open) return null;
|
||||
|
||||
@@ -63,6 +60,7 @@ export function FolderBrowseModal({
|
||||
role="presentation"
|
||||
>
|
||||
<div
|
||||
ref={panel}
|
||||
className="relative flex max-h-[70vh] w-full max-w-md flex-col rounded-xl border border-border bg-surface-1 shadow-xl shadow-black/40"
|
||||
onClick={(e) => e.stopPropagation()}
|
||||
role="dialog"
|
||||
@@ -99,7 +97,7 @@ export function FolderBrowseModal({
|
||||
className="flex w-full items-center gap-2 rounded px-2 py-1.5 text-left text-xs text-fg-primary hover:bg-surface-2"
|
||||
>
|
||||
{entry.isGitRepo ? (
|
||||
<FolderGit2 className="h-3.5 w-3.5 text-blue-700 dark:text-blue-400" />
|
||||
<FolderGit2 className="h-3.5 w-3.5 text-accent" />
|
||||
) : (
|
||||
<FolderOpen className="h-3.5 w-3.5 text-fg-muted" />
|
||||
)}
|
||||
|
||||
Reference in New Issue
Block a user