feat: tab context menu pin submenu

M2 of pinned terminals (v0.17.0):
- TabBar: onPinTab prop (optional, hidden when unset for legacy callers)
- Terminal tabs get a 'Pin ▸' submenu (workspace/global) or 'Unpin' row
- UI vs Agent label distinction via isAgentTabId
- Non-terminal tabs get no pin entry (5-item legacy menu preserved)
- WorkbenchActions.pinTab threaded through split-pane LeafView
- Sidebar.onPinTab wires store.reduce(setTabPin) with cwd snapshot
- 12 new i18n keys synced across all 19 language dictionaries
This commit is contained in:
HuanLinOTO
2026-08-26 18:33:02 +08:00
parent 68f771e65f
commit 5d019d78ac
24 changed files with 412 additions and 10 deletions
+8 -1
View File
@@ -37,7 +37,7 @@ import { appendToDraft } from './conversation-draft.ts'
import {
BOTTOM_MIN, PANEL_MIN, agentUuidOf, dockFloat, firstLeaf, floatTab, isAgentTabId, leafWithTab, migrateBottomTabs,
moveFloat, moveTab, moveTabToEdge, openDiffTab, raiseFloat, reconcileAgentTerminals,
resizeFloat, resizeSplitIn, setBottomHeight, setWidth, toggleBottomPanel, toggleExpanded, togglePanel,
resizeFloat, resizeSplitIn, setBottomHeight, setTabPin, setWidth, toggleBottomPanel, toggleExpanded, togglePanel,
type DropZone, type SidebarState, type SidebarStore, type SidebarTab, type SplitNode,
} from './state.ts'
import { IconPanelBottomOutline16, IconPanelRightOutline16 } from './icons.tsx'
@@ -1203,6 +1203,13 @@ export function Sidebar(props: { ctx: Context; store: SidebarStore }) {
const y = rect !== null ? (rect.top + rect.bottom) / 2 : window.innerHeight / 2
store.reduce(s => floatTab(s, tabId, x, y))
},
// Pin/unpin a terminal tab (v0.17.0+): the home cwd is snapshotted at
// pin time so a workspace-scoped pin only resurfaces in sessions whose
// cwd matches. Unpin passes null — the tab stays open in its home
// session, just unmarked.
pinTab: (tabId, scope) => {
store.reduce(s => setTabPin(s, tabId, scope === null ? null : { scope, homeCwd: cwd }))
},
}), [store, sessionId, cwd])
/**