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:
@@ -74,7 +74,7 @@
|
||||
"changes.commentOn": "Comment on {{at}} — pasted into the pane you used last; nothing is sent until you press Enter there",
|
||||
"changes.commentSend": "Paste into pane",
|
||||
"changes.commentCancel": "Cancel",
|
||||
"changes.commentNoPane": "No pane of this workspace is open — open its session first",
|
||||
"changes.commentNoPane": "No terminal of this workspace is on screen — open its session (or narrow the Changes panel) first",
|
||||
"changes.layout.split": "Side-by-side",
|
||||
"changes.layout.unified": "Unified",
|
||||
"changes.loading": "Reading the working copy…",
|
||||
@@ -390,7 +390,7 @@
|
||||
"panes.split.bottom": "Split down",
|
||||
"panes.full": "4 panes is the most a workspace can show",
|
||||
"panes.newTerminal": "New terminal",
|
||||
"panes.close": "Close pane (its session keeps running as a tab)",
|
||||
"panes.close": "Hide pane (its session keeps running as a tab)",
|
||||
"panes.resize": "Resize panes",
|
||||
"tabs.home": "Workspace home",
|
||||
"sessionMenu.open": "Open",
|
||||
@@ -401,9 +401,47 @@
|
||||
"panes.show": "Show {{name}} here",
|
||||
"panes.splitNew.right": "Split right with a new terminal",
|
||||
"panes.splitNew.bottom": "Split down with a new terminal",
|
||||
"panes.closeShort": "Close pane",
|
||||
"panes.closeShort": "Hide pane",
|
||||
"changes.copyPath": "Copy relative path",
|
||||
"changes.viewCommit": "View commit",
|
||||
"changes.copySha": "Copy commit SHA",
|
||||
"changes.copySubject": "Copy commit message"
|
||||
"changes.copySubject": "Copy commit message",
|
||||
"replay.live": "This session is open in a tab — continue it there.",
|
||||
"replay.noResume": "{{tool}} cannot resume a session by id, so this conversation is read-only here.",
|
||||
"replay.newSession": "Start a new session",
|
||||
"tabs.state.awaiting": "Waiting for you",
|
||||
"tabs.state.error": "Error",
|
||||
"tabs.state.running": "Running",
|
||||
"tabs.renameHint": "Double-click to rename",
|
||||
"tabs.stopTitle": "Stop “{{name}}”?",
|
||||
"tabs.stopBody": "Closing the tab stops its session, even mid-turn. It can be resumed later from Recents.",
|
||||
"tabs.stopConfirm": "Stop session",
|
||||
"changes.loadMore": "Load more commits",
|
||||
"changes.commentPasted": "Pasted into tab {{tab}} — press Enter there to send it",
|
||||
"changes.splitter": "Resize the changes panel",
|
||||
"tree.archiveLiveTitle": "Archive a live session?",
|
||||
"tree.archiveLiveBody": "Its tab keeps running; archiving only hides it from this list. Unarchive it from the archived fold.",
|
||||
"tree.state.awaiting": "waiting for you",
|
||||
"tree.state.error": "error",
|
||||
"tree.state.live": "live",
|
||||
"tree.state.idle": "ended",
|
||||
"tree.showMore_one": "Show {{count}} more",
|
||||
"tree.showMore_other": "Show {{count}} more",
|
||||
"home.contextPanel": "Workspace context",
|
||||
"row.state.needsYou": "needs you",
|
||||
"row.state.error": "error",
|
||||
"row.state.running": "running",
|
||||
"row.state.active": "active",
|
||||
"row.state.idle": "idle",
|
||||
"menu.stopTitle_one": "Stop the running session?",
|
||||
"menu.stopTitle_other": "Stop {{count}} running sessions?",
|
||||
"menu.stopBody": "Every tab of this workspace stops, even mid-turn. Its sessions can be resumed later from Recents.",
|
||||
"gitBar.done.fetch": "Fetched",
|
||||
"gitBar.done.pull": "Pulled",
|
||||
"gitBar.done.push": "Pushed",
|
||||
"gitBar.done.publish": "Published",
|
||||
"runtime.listening": "listening",
|
||||
"runtime.notListening": "not listening",
|
||||
"replay.pasted": "That session was already open in a tab — your message is pasted there; press Enter to send it.",
|
||||
"replay.notPasted": "That session is open in a tab, but your message could not be pasted there: {{text}}"
|
||||
}
|
||||
|
||||
@@ -148,5 +148,17 @@
|
||||
"imagesOnly": "Only images can be dropped here",
|
||||
"remove": "Remove image",
|
||||
"view": "View image"
|
||||
},
|
||||
"terminal": {
|
||||
"ended": "[session ended, exit code {{code}}]",
|
||||
"reconnecting": "Reconnecting… keystrokes are not reaching the session until the terminal is back.",
|
||||
"keyboardHint": "Terminal. Press Ctrl+M to toggle whether Tab moves focus out of the terminal; Ctrl+Shift+F (Cmd+F on macOS) finds text.",
|
||||
"tabMoves": "Tab moves focus — Ctrl+M to give Tab back to the terminal",
|
||||
"selectHint": "Shift+drag to select text in the browser; a plain drag selects in tmux and copies on release.",
|
||||
"dismissHint": "Dismiss",
|
||||
"find": "Find in terminal",
|
||||
"findPrev": "Previous match",
|
||||
"findNext": "Next match",
|
||||
"findClose": "Close find"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -74,7 +74,7 @@
|
||||
"changes.commentOn": "Comment {{at}} — dán vào pane bạn dùng gần nhất; chưa gửi gì cho tới khi bạn nhấn Enter ở đó",
|
||||
"changes.commentSend": "Dán vào pane",
|
||||
"changes.commentCancel": "Huỷ",
|
||||
"changes.commentNoPane": "Workspace này chưa mở pane nào — hãy mở session trước",
|
||||
"changes.commentNoPane": "Chưa có terminal nào của workspace này đang hiện — hãy mở session (hoặc thu hẹp khung Changes) trước",
|
||||
"changes.layout.split": "Hai cột",
|
||||
"changes.layout.unified": "Một cột",
|
||||
"changes.loading": "Đang đọc working copy…",
|
||||
@@ -390,7 +390,7 @@
|
||||
"panes.split.bottom": "Tách xuống dưới",
|
||||
"panes.full": "Một workspace hiện tối đa 4 pane",
|
||||
"panes.newTerminal": "Terminal mới",
|
||||
"panes.close": "Đóng pane (session vẫn chạy dưới dạng tab)",
|
||||
"panes.close": "Ẩn pane (session vẫn chạy dưới dạng tab)",
|
||||
"panes.resize": "Đổi kích thước pane",
|
||||
"tabs.home": "Trang chủ workspace",
|
||||
"sessionMenu.open": "Mở",
|
||||
@@ -401,9 +401,47 @@
|
||||
"panes.show": "Hiện {{name}} ở đây",
|
||||
"panes.splitNew.right": "Chia sang phải với terminal mới",
|
||||
"panes.splitNew.bottom": "Chia xuống dưới với terminal mới",
|
||||
"panes.closeShort": "Đóng pane",
|
||||
"panes.closeShort": "Ẩn pane",
|
||||
"changes.copyPath": "Sao chép đường dẫn tương đối",
|
||||
"changes.viewCommit": "Xem commit",
|
||||
"changes.copySha": "Sao chép SHA của commit",
|
||||
"changes.copySubject": "Sao chép commit message"
|
||||
"changes.copySubject": "Sao chép commit message",
|
||||
"replay.live": "Session này đang mở trong một tab — hãy tiếp tục ở đó.",
|
||||
"replay.noResume": "{{tool}} không thể tiếp tục (resume) một session theo id, nên hội thoại này chỉ để đọc.",
|
||||
"replay.newSession": "Bắt đầu session mới",
|
||||
"tabs.state.awaiting": "Đang chờ bạn",
|
||||
"tabs.state.error": "Lỗi",
|
||||
"tabs.state.running": "Đang chạy",
|
||||
"tabs.renameHint": "Nhấp đúp để đổi tên",
|
||||
"tabs.stopTitle": "Dừng “{{name}}”?",
|
||||
"tabs.stopBody": "Đóng tab sẽ dừng phiên của nó, kể cả khi đang chạy dở. Bạn có thể tiếp tục (resume) sau từ mục Gần đây.",
|
||||
"tabs.stopConfirm": "Dừng phiên",
|
||||
"changes.loadMore": "Tải thêm commit",
|
||||
"changes.commentPasted": "Đã dán vào tab {{tab}} — nhấn Enter ở đó để gửi",
|
||||
"changes.splitter": "Đổi độ rộng khung thay đổi",
|
||||
"tree.archiveLiveTitle": "Lưu trữ một session đang chạy?",
|
||||
"tree.archiveLiveBody": "Tab của nó vẫn chạy; lưu trữ chỉ ẩn nó khỏi danh sách này. Bỏ lưu trữ trong mục đã lưu trữ.",
|
||||
"tree.state.awaiting": "đang chờ bạn",
|
||||
"tree.state.error": "lỗi",
|
||||
"tree.state.live": "đang chạy",
|
||||
"tree.state.idle": "đã kết thúc",
|
||||
"tree.showMore_one": "Hiện thêm {{count}}",
|
||||
"tree.showMore_other": "Hiện thêm {{count}}",
|
||||
"home.contextPanel": "Ngữ cảnh workspace",
|
||||
"row.state.needsYou": "cần bạn",
|
||||
"row.state.error": "lỗi",
|
||||
"row.state.running": "đang chạy",
|
||||
"row.state.active": "đang hoạt động",
|
||||
"row.state.idle": "nghỉ",
|
||||
"menu.stopTitle_one": "Dừng phiên đang chạy?",
|
||||
"menu.stopTitle_other": "Dừng {{count}} phiên đang chạy?",
|
||||
"menu.stopBody": "Mọi tab của workspace này sẽ dừng, kể cả khi đang chạy dở. Có thể tiếp tục các phiên sau từ mục Gần đây.",
|
||||
"gitBar.done.fetch": "Đã fetch",
|
||||
"gitBar.done.pull": "Đã pull",
|
||||
"gitBar.done.push": "Đã push",
|
||||
"gitBar.done.publish": "Đã publish",
|
||||
"runtime.listening": "đang lắng nghe",
|
||||
"runtime.notListening": "không lắng nghe",
|
||||
"replay.pasted": "Session đó đã mở trong một tab — tin nhắn đã được dán vào đó; nhấn Enter để gửi.",
|
||||
"replay.notPasted": "Session đó đang mở trong một tab nhưng không dán được tin nhắn vào đó: {{text}}"
|
||||
}
|
||||
|
||||
@@ -55,7 +55,7 @@
|
||||
"approvalOnRequest": "Khi cần",
|
||||
"cwd": "Thư mục làm việc",
|
||||
"cwdGroups": {
|
||||
"dashboard": "Dashboard server",
|
||||
"dashboard": "Máy chủ dashboard",
|
||||
"home": "Home",
|
||||
"repo": "Repo nguồn",
|
||||
"recent": "Gần đây - Claude Code đã dùng"
|
||||
@@ -79,7 +79,7 @@
|
||||
"permissionBypass": "bypassPermissions (NGUY HIỂM - cho phép tất cả mà không hỏi)",
|
||||
"bypass": "Bỏ qua xin quyền cho lần chạy này",
|
||||
"permissionDefault": "default (prompt tương tác - headless sẽ treo)",
|
||||
"permissionMode": "Permission mode",
|
||||
"permissionMode": "Chế độ quyền",
|
||||
"permissionPlan": "plan (chỉ đọc, lập kế hoạch)",
|
||||
"prompt": "Prompt",
|
||||
"promptPlaceholder": "Hỏi Claude bất cứ điều gì…",
|
||||
@@ -92,7 +92,7 @@
|
||||
"footer": {
|
||||
"cost": "Chi phí",
|
||||
"duration": "Thời lượng",
|
||||
"exitCode": "Exit code",
|
||||
"exitCode": "Mã thoát",
|
||||
"sessionId": "Session ID",
|
||||
"turns": "Lượt"
|
||||
},
|
||||
@@ -134,7 +134,7 @@
|
||||
"session": "Session"
|
||||
},
|
||||
"tokens": {
|
||||
"cacheRead": "Cache hit",
|
||||
"cacheRead": "Trúng cache",
|
||||
"cost": "Chi phí",
|
||||
"input": "Input",
|
||||
"label": "Context",
|
||||
@@ -147,5 +147,17 @@
|
||||
"imagesOnly": "Chỉ thả được ảnh vào đây",
|
||||
"remove": "Bỏ ảnh",
|
||||
"view": "Xem ảnh"
|
||||
},
|
||||
"terminal": {
|
||||
"ended": "[phiên đã kết thúc, mã thoát {{code}}]",
|
||||
"reconnecting": "Đang kết nối lại… phím gõ chưa tới được phiên cho tới khi terminal kết nối lại.",
|
||||
"keyboardHint": "Terminal. Nhấn Ctrl+M để bật/tắt chế độ Tab chuyển focus ra khỏi terminal; Ctrl+Shift+F (Cmd+F trên macOS) để tìm chữ.",
|
||||
"tabMoves": "Tab chuyển focus — Ctrl+M để trả Tab lại cho terminal",
|
||||
"selectHint": "Shift+kéo để chọn chữ trong trình duyệt; kéo thường sẽ chọn trong tmux và sao chép khi thả chuột.",
|
||||
"dismissHint": "Bỏ qua",
|
||||
"find": "Tìm trong terminal",
|
||||
"findPrev": "Kết quả trước",
|
||||
"findNext": "Kết quả sau",
|
||||
"findClose": "Đóng tìm kiếm"
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user