dsh-vscode-workbench 0.2.3 → 0.2.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -11,6 +11,10 @@ import type { MarkdownHeading } from './MarkdownPreview.tsx'
11
11
  import { MonacoDiffEditor, MonacoEditor, isMacPlatform, loadMonaco, preloadMonaco, retainEditorModels } from './MonacoEditor.tsx'
12
12
  import { DshConversationPanel, DshConversationTheme } from './DshConversationPanel.tsx'
13
13
  import { DshMark } from './DshMark.tsx'
14
+ import { DshPanelPage } from './DshPanelPage.tsx'
15
+ import { DshBrowserPage } from './DshBrowserPage.tsx'
16
+ import type { NativeBrowser, BrowserSnapshot } from './browser.ts'
17
+ import { dshPanelLabel } from './dshPanels.ts'
14
18
  import { ActivityBar, normalizeActivityOrder, type ActivityView } from './ActivityBar.tsx'
15
19
  import { WorkspaceSwitcher } from './WorkspaceSwitcher.tsx'
16
20
  import type { DshNavigation, WorkspaceChoice } from './dshNavigation.ts'
@@ -438,11 +442,44 @@ const ExplorerView = memo(Explorer)
438
442
  const SearchView = memo(Search)
439
443
  const GitView = memo(Git)
440
444
 
441
- export function Workbench({ service, sessionId, cwd, navigation }: { service: VscodeWorkbench; sessionId?: string; cwd?: string; navigation?: DshNavigation }) {
445
+ export function Workbench({ service, browser, sessionId, cwd, navigation, dshPanelId: hostDshPanelId, onOpenDshPanel, onRestoreConversation, onCloseDshPanel }: { service: VscodeWorkbench; browser?: NativeBrowser; sessionId?: string; cwd?: string; navigation?: DshNavigation; dshPanelId?: string; onOpenDshPanel?(panelId: string): void; onRestoreConversation?(): void; onCloseDshPanel?(): void }) {
442
446
  const [snapshot, setSnapshot] = useState(() => service.getSnapshot()); const [panels, setPanels] = useState(() => service.getPanels())
443
447
  useEffect(() => service.subscribe(() => { setSnapshot(service.getSnapshot()); setPanels(service.getPanels()) }), [service])
444
- const [workspace,setWorkspace]=useState<{cwd:string;name:string}>(); const [tabs,setTabs]=useState<Tab[]>([]); const [active,setActive]=useState<string>(); const [error,setError]=useState<string>(); const [panelHeight,setPanelHeight]=useState(260); const [sideWidth,setSideWidth]=useState(210); const [primarySideVisible,setPrimarySideVisible]=useState(()=>localStorage.getItem('dvw:primary-side-visible')!=='closed');const [statusVisible,setStatusVisible]=useState(()=>localStorage.getItem('dvw:status-bar-visible')==='open');const [settingsOpen,setSettingsOpen]=useState(false);const [chatWidth,setChatWidth]=useState(()=>clampChatWidth(Number(localStorage.getItem('dvw:dsh-chat-width'))||DSH_CHAT_DEFAULT_WIDTH));const [chatVisible,setChatVisible]=useState(()=>localStorage.getItem('dvw:dsh-chat-visible')!=='closed');const [outlineVisible,setOutlineVisible]=useState(()=>localStorage.getItem('dvw:outline')!=='closed');const [tsIntellisense,setTsIntellisense]=useState(tsIntellisenseEnabled);const [tsIndex,setTsIndex]=useState<{state:'idle'|'loading'|'ready'|'error';files?:number;truncated?:boolean;error?:string}>({state:'idle'});const [lspServers,setLspServers]=useState<LspServerStatus[]>([]);const [lspBusy,setLspBusy]=useState<string>();const [lspError,setLspError]=useState('');const lspManager=useRef<LspManager>();const [fontSize,setFontSize]=useState(()=>{const saved=Math.round(Number(localStorage.getItem('dvw:font-size')));return Number.isFinite(saved)&&saved>=11&&saved<=20?saved:14});const [tabMenu,setTabMenu]=useState<{x:number;y:number;path:string}>()
448
+ const [workspace,setWorkspace]=useState<{cwd:string;name:string}>(); const [tabs,setTabs]=useState<Tab[]>([]); const [active,setActive]=useState<string>(); const [dshTabActive,setDshTabActive]=useState(hostDshPanelId!==undefined); const [dshTabId,setDshTabId]=useState<string|undefined>(hostDshPanelId); const dshPanelId=dshTabId; const [error,setError]=useState<string>(); const [panelHeight,setPanelHeight]=useState(260); const [sideWidth,setSideWidth]=useState(210); const [primarySideVisible,setPrimarySideVisible]=useState(()=>localStorage.getItem('dvw:primary-side-visible')!=='closed');const [statusVisible,setStatusVisible]=useState(()=>localStorage.getItem('dvw:status-bar-visible')==='open');const [settingsOpen,setSettingsOpen]=useState(false);const [chatWidth,setChatWidth]=useState(()=>clampChatWidth(Number(localStorage.getItem('dvw:dsh-chat-width'))||DSH_CHAT_DEFAULT_WIDTH));const [chatVisible,setChatVisible]=useState(()=>localStorage.getItem('dvw:dsh-chat-visible')!=='closed');const [outlineVisible,setOutlineVisible]=useState(()=>localStorage.getItem('dvw:outline')!=='closed');const [tsIntellisense,setTsIntellisense]=useState(tsIntellisenseEnabled);const [tsIndex,setTsIndex]=useState<{state:'idle'|'loading'|'ready'|'error';files?:number;truncated?:boolean;error?:string}>({state:'idle'});const [lspServers,setLspServers]=useState<LspServerStatus[]>([]);const [lspBusy,setLspBusy]=useState<string>();const [lspError,setLspError]=useState('');const lspManager=useRef<LspManager>();const [fontSize,setFontSize]=useState(()=>{const saved=Math.round(Number(localStorage.getItem('dvw:font-size')));return Number.isFinite(saved)&&saved>=11&&saved<=20?saved:14});const [tabMenu,setTabMenu]=useState<{x:number;y:number;path:string}>()
449
+ const [browserSnapshot,setBrowserSnapshot]=useState<BrowserSnapshot>(()=>browser?.getSnapshot()??{available:false,tabs:[]})
450
+ const [browserId,setBrowserId]=useState<string>()
451
+ const browserTabs=browserSnapshot.tabs.filter(tab=>tab.sessionId===sessionId)
452
+ const previousBrowserIds=useRef(new Set(browserTabs.map(tab=>tab.id)))
453
+ useEffect(()=>{if(browser===undefined)return;setBrowserSnapshot(browser.getSnapshot());return browser.subscribe(()=>setBrowserSnapshot(browser.getSnapshot()))},[browser])
454
+ useEffect(()=>{
455
+ const next=new Set(browserTabs.map(tab=>tab.id))
456
+ const added=browserTabs.find(tab=>!previousBrowserIds.current.has(tab.id))
457
+ previousBrowserIds.current=next
458
+ if(added!==undefined&&snapshot.visible){setBrowserId(added.id);setDshTabActive(false)}
459
+ else if(browserId!==undefined&&!next.has(browserId))setBrowserId(undefined)
460
+ },[browserSnapshot,sessionId,browserId,snapshot.visible])
461
+ const browserRequest=useRef<AbortController>()
462
+ useEffect(()=>()=>browserRequest.current?.abort(),[browser,sessionId,snapshot.visible])
463
+ const newBrowser=async()=>{
464
+ if(browser===undefined||sessionId===undefined)return
465
+ setDshTabActive(false);onRestoreConversation?.()
466
+ browserRequest.current?.abort();const request=new AbortController();browserRequest.current=request
467
+ try{setBrowserId(await browser.open(sessionId,request.signal))}catch(error){if(!request.signal.aborted)setError(error instanceof Error?error.message:String(error))}
468
+ }
469
+ useEffect(()=>{if(browserId!==undefined&&!dshTabActive)browser?.focus(browserId)},[browser,browserId,dshTabActive])
470
+ useEffect(()=>{if(!snapshot.visible)return;const open=()=>void newBrowser();window.addEventListener('dvw-browser-new',open);return()=>window.removeEventListener('dvw-browser-new',open)},[browser,sessionId,snapshot.visible])
471
+ useEffect(()=>{if(hostDshPanelId!==undefined)setBrowserId(undefined)},[hostDshPanelId])
445
472
  const tabsRef=useRef(tabs);tabsRef.current=tabs
473
+ // DSH unmounts its conversation while a global panel (Plugins…) is selected,
474
+ // so the tab outlives the panel: opening a file hands the centre back to the
475
+ // conversation, and clicking the tab re-selects the panel.
476
+ const dshTabActiveRef=useRef(dshTabActive);dshTabActiveRef.current=dshTabActive
477
+ const dshTabIdRef=useRef(dshTabId);dshTabIdRef.current=dshTabId
478
+ const hostDshPanelIdRef=useRef(hostDshPanelId);hostDshPanelIdRef.current=hostDshPanelId
479
+ const openDshPanelRef=useRef(onOpenDshPanel);openDshPanelRef.current=onOpenDshPanel
480
+ const restoreConversationRef=useRef(onRestoreConversation);restoreConversationRef.current=onRestoreConversation
481
+ const leaveDshPanel=useCallback(()=>{setBrowserId(undefined);const wasActive=dshTabActiveRef.current;setDshTabActive(false);if(wasActive)restoreConversationRef.current?.()},[])
482
+ const openDshTab=useCallback(()=>{setBrowserId(undefined);const id=dshTabIdRef.current;if(id===undefined)return;setDshTabActive(true);if(hostDshPanelIdRef.current!==id)openDshPanelRef.current?.(id)},[])
446
483
  const [settingsSection,setSettingsSection]=useState<'appearance'|'layout'|'commit'|'language'>('appearance')
447
484
  const tabListRef=useRef<HTMLDivElement>(null)
448
485
  const workspaceRequests=useRef(new WorkspaceRequestScope());const loadedWorkspaceCwd=useRef<string>();const loadingTabs=useRef(new Map<string, symbol>())
@@ -464,6 +501,23 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
464
501
  useEffect(()=>localStorage.setItem('dvw:primary-side-visible',primarySideVisible?'open':'closed'),[primarySideVisible])
465
502
  useEffect(()=>localStorage.setItem('dvw:status-bar-visible',statusVisible?'open':'closed'),[statusVisible])
466
503
  useEffect(()=>localStorage.setItem('dvw:font-size',String(fontSize)),[fontSize])
504
+ useEffect(()=>{
505
+ if(hostDshPanelId!==undefined){setDshTabId(hostDshPanelId);setDshTabActive(true);return}
506
+ // The panel went away while it was on screen (closed outside the tab), so forget the tab.
507
+ if(dshTabActiveRef.current)setDshTabId(undefined)
508
+ },[hostDshPanelId])
509
+ // The host only mounts its conversation while no global panel is selected, so
510
+ // showing a file must hand the centre back; clicking the tab re-selects it.
511
+ useEffect(()=>{
512
+ if(dshTabActive||dshTabId===undefined||hostDshPanelId===undefined)return
513
+ restoreConversationRef.current?.()
514
+ },[dshTabActive,dshTabId,hostDshPanelId])
515
+ useEffect(()=>{
516
+ if(!dshTabActive)return
517
+ const id=dshTabIdRef.current
518
+ if(id===undefined||hostDshPanelId===id)return
519
+ openDshPanelRef.current?.(id)
520
+ },[dshTabActive,hostDshPanelId])
467
521
  useEffect(()=>{if(tabMenu===undefined)return;const close=()=>setTabMenu(undefined);const escape=(event:KeyboardEvent)=>{if(event.key==='Escape')close()};window.addEventListener('pointerdown',close);window.addEventListener('keydown',escape);return()=>{window.removeEventListener('pointerdown',close);window.removeEventListener('keydown',escape)}},[tabMenu])
468
522
  useEffect(() => {
469
523
  if (!snapshot.visible) return
@@ -474,7 +528,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
474
528
  // The conversation may still be attaching after the chat column opens, so retry for about a second.
475
529
  const addToDsh=useCallback((reference:string)=>{setChatVisible(true);let frames=0;const attempt=()=>{void insertIntoDshComposer(reference).then(done=>{if(done)return;if(++frames<60){requestAnimationFrame(attempt);return}alert('DSH 对话输入框当前不可用,请先选择会话或等待当前回复结束。')})};requestAnimationFrame(attempt)},[])
476
530
  const addEntryToDsh=useCallback((path:string,directory:boolean)=>addToDsh(dshReference(path,{directory})),[addToDsh])
477
- const openForWorkspace=(path:string,line?:number,column?:number,highlight?:string)=>{const existing=tabsRef.current.find(tab=>tab.path===path);if(existing)setTabs(rows=>rows.map(tab=>tab.path===path?{...tab,line,column,highlight,preview:line!==undefined?false:tab.preview}:tab));else setTabs(rows=>[...rows,{...unloadedTab(path),line,column,highlight,...(line!==undefined?{preview:false}:{})}]);setActive(path)}
531
+ const openForWorkspace=(path:string,line?:number,column?:number,highlight?:string)=>{const existing=tabsRef.current.find(tab=>tab.path===path);if(existing)setTabs(rows=>rows.map(tab=>tab.path===path?{...tab,line,column,highlight,preview:line!==undefined?false:tab.preview}:tab));else setTabs(rows=>[...rows,{...unloadedTab(path),line,column,highlight,...(line!==undefined?{preview:false}:{})}]);leaveDshPanel();setActive(path)}
478
532
  useEffect(() => {
479
533
  if (sessionId === undefined) return
480
534
  // Switching sessions inside one workspace must keep the explorer, tabs and layout as they are.
@@ -671,7 +725,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
671
725
  return()=>{cancelled=true;for(const disposable of disposables)disposable.dispose()}
672
726
  },[snapshot.visible,sessionId,workspace,tsIntellisense])
673
727
  const openDiff=useCallback((path:string,diff:GitDiff,title?:string,diffSourcePath?:string)=>{const key=`diff:${path}`;const source=diffSourcePath??path;// Previewable documents open like VS Code's PDF/Office editors; history diffs keep the binary notice.
674
- if(isPreviewDocumentPath(source)&&!path.startsWith('commit:')){openPath(source);return}const binary=isBinaryDiffPath(source);setTabs(rows=>[...rows.filter(row=>row.path!==key),{path:key,title,content:diff.diff,draft:diff.diff,revision:'',size:diff.diff.length,binary,tooLarge:false,dirty:false,diff:binary?undefined:diff,diffMode:'inline',diffSourcePath:source}]);setActive(key)},[openPath])
728
+ if(isPreviewDocumentPath(source)&&!path.startsWith('commit:')){openPath(source);return}const binary=isBinaryDiffPath(source);setTabs(rows=>[...rows.filter(row=>row.path!==key),{path:key,title,content:diff.diff,draft:diff.diff,revision:'',size:diff.diff.length,binary,tooLarge:false,dirty:false,diff:binary?undefined:diff,diffMode:'inline',diffSourcePath:source}]);leaveDshPanel();setActive(key)},[openPath,leaveDshPanel])
675
729
  useEffect(()=>{const listener=(event:Event)=>void open((event as CustomEvent<string>).detail);window.addEventListener('dvw-open-workspace-path',listener);return()=>window.removeEventListener('dvw-open-workspace-path',listener)},[sessionId,tabs])
676
730
  const savingTabs = useRef(new Set<string>())
677
731
  const save = async (tab: Tab) => {
@@ -693,7 +747,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
693
747
  const activeTab=tabs.find(tab=>tab.path===active); const Panel=panels.find(panel=>panel.id===snapshot.bottomPanel)?.component; const terminalAvailable=panels.some(panel=>panel.id==='terminal')
694
748
  const menuTab=tabMenu===undefined?undefined:tabs.find(tab=>tab.path===tabMenu.path); const menuIndex=menuTab===undefined?-1:tabs.findIndex(tab=>tab.path===menuTab.path); const savedTabs=tabs.filter(tab=>!tab.dirty)
695
749
  const selectView=(view:WorkbenchView)=>{if(snapshot.view===view&&primarySideVisible){setPrimarySideVisible(false);return}setPrimarySideVisible(true);service.show(view)}
696
- const ACTIVITY_IDS=['explorer','search','git','terminal','chat'] as const
750
+ const ACTIVITY_IDS=['explorer','search','git','terminal','browser','chat'] as const
697
751
  const [activityOrder,setActivityOrder]=useState<string[]>(()=>normalizeActivityOrder(readActivityJSON('dvw:activity-order'),ACTIVITY_IDS))
698
752
  const [activityHidden,setActivityHidden]=useState<string[]>(()=>{const value=readActivityJSON('dvw:activity-hidden');return Array.isArray(value)?value.filter((id):id is string=>typeof id==='string'&&(ACTIVITY_IDS as readonly string[]).includes(id)):[]})
699
753
  useEffect(()=>localStorage.setItem('dvw:activity-order',JSON.stringify(activityOrder)),[activityOrder])
@@ -707,15 +761,20 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
707
761
  const suppressAttachmentDrop=(event:DragEvent)=>{if(!event.dataTransfer.types.includes('Files'))return;const target=event.target;const insideTree=target instanceof Element&&target.closest('.dvw-tree')!==null;if(!insideTree){event.preventDefault();event.stopPropagation()}}
708
762
  if(!snapshot.visible)return null
709
763
  if(sessionId===undefined)return <div className="dvw-overlay" data-dvw-theme={colorScheme} data-dvw-theme-name={theme}><WorkbenchTitlebar/><div className="dvw-empty" style={{gridColumn:'1/4'}}><div>请先选择或创建一个 DSH 会话。<br/><button onClick={()=>service.hide()}>返回 DSH</button></div></div></div>
764
+ const showingDshPanel=dshTabId!==undefined&&dshTabActive&&!settingsOpen
765
+ const closeDshPanel=()=>{setDshTabActive(false);setDshTabId(undefined);onCloseDshPanel?.()}
766
+ const openFromView=(path:string,line?:number,column?:number,highlight?:string)=>{leaveDshPanel();openPath(path,line,column,highlight)}
767
+ const chatShown=chatVisible&&!settingsOpen&&!showingDshPanel
710
768
  const markdown=activeTab!==undefined&&!activeTab.path.startsWith('diff:')&&/\.md$/i.test(activeTab.path)
711
769
  const diffMarkdown=activeTab?.diff!==undefined&&/\.md$/i.test(activeTab.diffSourcePath??'')
712
770
  const markdownPreview=markdown||(diffMarkdown&&activeTab?.preview===true)
713
771
  const headings=markdownPreview&&activeTab?markdownHeadings(activeTab.diff?.modified??activeTab.draft):[]
714
- return <div className="dvw-overlay" data-dvw-theme={colorScheme} data-dvw-theme-name={theme} data-dvw-chat={chatVisible||undefined} data-dvw-primary-side={primarySideVisible||undefined} data-dvw-status={statusVisible||undefined} data-dvw-bottom-panel={snapshot.bottomPanel||undefined} style={{'--dvw-side-width':`${sideWidth}px`,'--dvw-chat-width':`${chatWidth}px`} as CSSProperties} role="dialog" aria-label="VS Code 工作台" onDragEnterCapture={suppressAttachmentDrop} onDragOverCapture={suppressAttachmentDrop} onDropCapture={suppressAttachmentDrop}>
772
+ return <div className="dvw-overlay" data-dvw-theme={colorScheme} data-dvw-theme-name={theme} data-dvw-chat={chatShown||undefined} data-dvw-primary-side={primarySideVisible||undefined} data-dvw-status={statusVisible||undefined} data-dvw-bottom-panel={snapshot.bottomPanel||undefined} style={{'--dvw-side-width':`${sideWidth}px`,'--dvw-chat-width':`${chatWidth}px`} as CSSProperties} role="dialog" aria-label="VS Code 工作台" onDragEnterCapture={suppressAttachmentDrop} onDragOverCapture={suppressAttachmentDrop} onDropCapture={suppressAttachmentDrop}>
715
773
  <WorkbenchTitlebar name={workspace?.name}/>
716
- <DshConversationPanel visible={chatVisible&&!settingsOpen} width={chatWidth} statusHeight={statusVisible?30:6}/>
717
- <DshConversationTheme visible={chatVisible} colorScheme={colorScheme} followDsh={false}/>
718
- {chatVisible&&<aside className="dvw-chat"><PanelResizer className="dvw-chat-resizer" label="拖动调整 DSH 对话栏宽度" width={chatWidth} min={DSH_CHAT_MIN_WIDTH} max={DSH_CHAT_MAX_WIDTH} defaultWidth={DSH_CHAT_DEFAULT_WIDTH} direction={-1} hideBelow={200} onHide={()=>setChatVisible(false)} onWidth={setChatWidth}/><header className="dvw-chat-head"><span><DshMark/> DSH</span><button className="dvw-icon" title="关闭 DSH 对话栏" onClick={()=>setChatVisible(false)}><Icon name="close"/></button></header><div className="dvw-chat-body"/></aside>}
774
+ {dshTabId!==undefined&&<DshPanelPage panelId={dshTabId} active={showingDshPanel}/>}
775
+ <DshConversationPanel visible={chatShown} width={chatWidth} statusHeight={statusVisible?30:6}/>
776
+ <DshConversationTheme visible={chatShown} colorScheme={colorScheme} followDsh={false}/>
777
+ {chatShown&&<aside className="dvw-chat"><PanelResizer className="dvw-chat-resizer" label="拖动调整 DSH 对话栏宽度" width={chatWidth} min={DSH_CHAT_MIN_WIDTH} max={DSH_CHAT_MAX_WIDTH} defaultWidth={DSH_CHAT_DEFAULT_WIDTH} direction={-1} hideBelow={200} onHide={()=>setChatVisible(false)} onWidth={setChatWidth}/><header className="dvw-chat-head"><span><DshMark/> DSH</span><button className="dvw-icon" title="关闭 DSH 对话栏" onClick={()=>setChatVisible(false)}><Icon name="close"/></button></header><div className="dvw-chat-body"/></aside>}
719
778
  {switcherOpen&&<WorkspaceSwitcher choices={switcherChoices} activeSessionId={sessionId} unavailable={navigation?.available===false} busy={switcherBusy} error={switcherError} theme={colorScheme} onCreate={()=>void createWorkspace()} onSwitch={switchSession} onOpenWorkspace={enterWorkspace} onClose={()=>setSwitcherOpen(false)}/>}
720
779
  {settingsOpen&&<div className="dvw-settings-backdrop" role="presentation" onMouseDown={()=>setSettingsOpen(false)}>
721
780
  <section className="dvw-settings" role="dialog" aria-modal="true" aria-label="工作台设置" onMouseDown={event=>event.stopPropagation()}>
@@ -752,6 +811,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
752
811
  {id:'search',label:'搜索',icon:<Icon name="search"/>,active:primarySideVisible&&snapshot.view==='search',onActivate:()=>selectView('search')},
753
812
  {id:'git',label:'源代码管理',icon:<Icon name="source-control"/>,active:primarySideVisible&&snapshot.view==='git',onActivate:()=>selectView('git')},
754
813
  {id:'terminal',label:terminalAvailable?(snapshot.bottomPanel==='terminal'?'隐藏终端':'显示终端'):'终端适配器未安装',icon:<Icon name="terminal"/>,active:snapshot.bottomPanel==='terminal',disabled:!terminalAvailable,onActivate:()=>toggleTerminal(service)},
814
+ {id:'browser',label:'新建浏览器',icon:<Icon name="globe"/>,active:browserId!==undefined,disabled:!browserSnapshot.available||sessionId===undefined,onActivate:()=>void newBrowser()},
755
815
  {id:'chat',label:chatVisible?'关闭 DSH 对话栏 (Ctrl+Alt+B)':'显示 DSH 对话栏 (Ctrl+Alt+B)',icon:<DshMark size={22}/>,active:chatVisible,onActivate:()=>setChatVisible(value=>!value)},
756
816
  ]}
757
817
  fixed={[{id:'workspaces',label:'打开工作区 / 切换会话',icon:<Icon name="folder-opened"/>,active:false,onActivate:openSwitcher}]}
@@ -761,9 +821,9 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
761
821
  ]}
762
822
  order={activityOrder} hidden={activityHidden}
763
823
  onOrderChange={setActivityOrder} onHiddenChange={setActivityHidden}/>
764
- <aside className="dvw-side" data-side-width={sideWidth}>{snapshot.view==='explorer'&&workspace!==undefined&&<ExplorerView sessionId={sessionId} cwd={workspace.cwd} selected={active} onOpen={openPath} onAddToDsh={addEntryToDsh}/>} {snapshot.view==='search'&&<SearchView sessionId={sessionId} onOpen={openPath}/>} {snapshot.view==='git'&&<GitView sessionId={sessionId} preferences={commitPreferences} onDiff={openDiff}/>}</aside>
824
+ <aside className="dvw-side" data-side-width={sideWidth}>{snapshot.view==='explorer'&&workspace!==undefined&&<ExplorerView sessionId={sessionId} cwd={workspace.cwd} selected={active} onOpen={openFromView} onAddToDsh={addEntryToDsh}/>} {snapshot.view==='search'&&<SearchView sessionId={sessionId} onOpen={openFromView}/>} {snapshot.view==='git'&&<GitView sessionId={sessionId} preferences={commitPreferences} onDiff={(path,diff,title,source)=>{leaveDshPanel();openDiff(path,diff,title,source)}}/>}</aside>
765
825
  <PanelResizer className="dvw-side-resizer" label="拖动调整源码管理侧边栏宽度" width={sideWidth} min={210} max={640} defaultWidth={210} direction={1} hideBelow={105} onHide={()=>setPrimarySideVisible(false)} onWidth={setSideWidth}/>
766
- <main className="dvw-main" onDragOver={event => { if (draggedEntry?.kind !== 'file') return; event.preventDefault(); event.dataTransfer.dropEffect = 'copy' }} onDrop={event => { const entry = draggedEntry; if (entry?.kind !== 'file') return; event.preventDefault(); endEntryDrag(); openPath(entry.path) }}><div className="dvw-tabs" data-empty={tabs.length===0||undefined}><div className="dvw-tab-list" ref={tabListRef}>{tabs.map(tab=><button className="dvw-tab" data-active={tab.path===active} key={tab.path} title={tab.diffSourcePath??tab.path} onClick={()=>setActive(tab.path)} onAuxClick={event=>{if(event.button===1){event.preventDefault();close(tab)}}} onMouseDown={event=>{if(event.button===1)event.preventDefault()}} onContextMenu={event=>{event.preventDefault();setActive(tab.path);setTabMenu({x:event.clientX,y:event.clientY,path:tab.path})}}><FileIcon name={tabLabel(tab)} className="dvw-tab-icon"/><span className="dvw-tab-name">{tab.dirty?'● ':''}{tabLabel(tab)}</span><span className="dvw-tab-close" onClick={event=>{event.stopPropagation();close(tab)}}><Icon name="close"/></span></button>)}</div></div><div className="dvw-editor" data-outline={markdownPreview&&outlineVisible&&headings.length>0}>{error&&<div className="dvw-error"><button className="dvw-icon" onClick={()=>setError(undefined)}><Icon name="close"/></button>{error}</div>}{markdownPreview&&outlineVisible&&headings.length>0&&<MarkdownOutline headings={headings} onClose={()=>setOutlineVisible(false)} onHeading={selectHeading}/>} {markdown&&activeTab&&<div className="dvw-editor-toolbar"><button className="dvw-icon dvw-outline-toggle" data-active={outlineVisible} title={outlineVisible?'关闭 Markdown 大纲':'显示 Markdown 大纲'} onClick={()=>setOutlineVisible(value=>!value)}><Icon name="list-tree"/></button><button className="dvw-mode-button" data-active={!activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:false}:tab))}><Icon name="code"/> 源码</button><button className="dvw-mode-button" data-active={activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:true}:tab))}><Icon name="preview"/> 预览</button></div>}{activeTab?.diff&&<div className="dvw-editor-toolbar dvw-diff-toolbar">{diffMarkdown&&<div className="dvw-diff-preview-switch"><button className="dvw-mode-button" data-diff-view="diff" data-active={!activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:false}:tab))}><Icon name="diff"/> 差异</button><button className="dvw-mode-button" data-diff-view="preview" data-active={activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:true}:tab))}><Icon name="preview"/> 预览</button></div>}{!activeTab.preview&&<><button className="dvw-mode-button dvw-diff-mode-button" data-mode="inline" data-active={activeTab.diffMode!=='split'} title="内联视图" onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,diffMode:'inline'}:tab))}><Icon name="diff-single"/> 内联</button><button className="dvw-mode-button dvw-diff-mode-button" data-mode="split" data-active={activeTab.diffMode==='split'} title="并排视图" onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,diffMode:'split'}:tab))}><Icon name="diff-sidebyside"/> 并排</button></>}</div>}<div className="dvw-editor-body" data-toolbar={markdown||activeTab?.diff!==undefined}>{activeTab===undefined?<div className="dvw-empty"><div><strong>DSH Workbench</strong><br/>从资源管理器或搜索结果打开文件</div></div>:activeTab.diff?diffMarkdown&&activeTab.preview?<MarkdownPreview source={activeTab.diff.modified} sessionId={sessionId} path={activeTab.diffSourcePath??''}/>:<MonacoDiffEditor uri={`${workspace?.cwd??''}/${activeTab.path}`} original={activeTab.diff.original} modified={activeTab.diff.modified} mode={activeTab.diffMode??'inline'} theme={colorScheme} fontSize={fontSize}/>:activeTab.imageUrl?<div className="dvw-image-preview"><img src={activeTab.imageUrl} alt={activeTab.path}/><span>{activeTab.path}</span></div>:activeTab.documentPreview&&activeTab.documentUrl?<DocumentPreview kind={activeTab.documentPreview} src={activeTab.documentUrl} path={activeTab.path}/>:markdown&&activeTab.preview?<MarkdownPreview source={activeTab.draft} sessionId={sessionId} path={activeTab.path}/>:activeTab.binary||activeTab.tooLarge?<div className="dvw-message">{activeTab.binary?'二进制文件无法在文本编辑器或差异视图中打开':`文件超过 5 MiB:${activeTab.path}`}{activeTab.binary&&activeTab.diffSourcePath!==undefined&&!activeTab.path.startsWith('diff:commit:')&&<button onClick={()=>void openPath(activeTab.diffSourcePath!)}>打开文件</button>}</div>:activeTab.loaded===false?<div className="dvw-message">正在加载文件…</div>:<MonacoEditor uri={`${workspace?.cwd??''}/${activeTab.path}`} onAddToDsh={selection=>addToDsh(dshReference(activeTab.path,{selection}))} value={activeTab.draft} theme={colorScheme} fontSize={fontSize} line={activeTab.line} column={activeTab.column} highlight={activeTab.highlight} onChange={draft=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,draft,dirty:draft!==tab.content}:tab))} onSave={()=>void save(activeTab)}/>}</div></div>{snapshot.bottomPanel===undefined&&<div className="dvw-bottom-reveal" title="向上拖动打开终端" onPointerDown={event=>{if(event.button!==0)return;event.preventDefault();service.toggleBottomPanel('terminal');const statusHeight=statusVisible?24:0;const heightAt=(clientY:number)=>Math.max(120,Math.min(window.innerHeight-160,window.innerHeight-clientY-statusHeight));const move=(e:PointerEvent)=>setPanelHeight(heightAt(e.clientY));const up=()=>{window.removeEventListener('pointermove',move);window.removeEventListener('pointerup',up)};window.addEventListener('pointermove',move);window.addEventListener('pointerup',up)}}/>}</main>
826
+ <main className="dvw-main" onDragOver={event => { if (draggedEntry?.kind !== 'file') return; event.preventDefault(); event.dataTransfer.dropEffect = 'copy' }} onDrop={event => { const entry = draggedEntry; if (entry?.kind !== 'file') return; event.preventDefault(); endEntryDrag(); openPath(entry.path) }}><div className="dvw-tabs" data-empty={(tabs.length===0&&dshPanelId===undefined&&browserTabs.length===0)||undefined}><div className="dvw-tab-list" ref={tabListRef}>{browserTabs.map(tab=><button className="dvw-tab" key={tab.id} data-dvw-browser-tab={tab.id} data-active={browserId===tab.id&&!dshTabActive} title={tab.title} onClick={()=>{leaveDshPanel();setBrowserId(tab.id)}}><Icon name="globe"/><span className="dvw-tab-name">{tab.title}</span><span className="dvw-tab-close" onClick={event=>{event.stopPropagation();browser?.close(tab.sessionId,tab.id)}}><Icon name="close"/></span></button>)}{dshPanelId!==undefined&&<button className="dvw-tab" data-active={dshTabActive} data-dvw-dsh-tab="" title={`${dshPanelLabel(dshPanelId)}(DSH 官方页面)`} onClick={()=>setDshTabActive(true)}><span className="dvw-dsh-tab-mark"/><span className="dvw-tab-name">{dshPanelLabel(dshPanelId)}</span><span className="dvw-tab-close" onClick={event=>{event.stopPropagation();closeDshPanel()}}><Icon name="close"/></span></button>}{tabs.map(tab=><button className="dvw-tab" data-active={tab.path===active&&!dshTabActive&&browserId===undefined} key={tab.path} title={tab.diffSourcePath??tab.path} onClick={()=>{setBrowserId(undefined);setDshTabActive(false);setActive(tab.path)}} onAuxClick={event=>{if(event.button===1){event.preventDefault();close(tab)}}} onMouseDown={event=>{if(event.button===1)event.preventDefault()}} onContextMenu={event=>{event.preventDefault();setBrowserId(undefined);setDshTabActive(false);setActive(tab.path);setTabMenu({x:event.clientX,y:event.clientY,path:tab.path})}}><FileIcon name={tabLabel(tab)} className="dvw-tab-icon"/><span className="dvw-tab-name">{tab.dirty?'● ':''}{tabLabel(tab)}</span><span className="dvw-tab-close" onClick={event=>{event.stopPropagation();close(tab)}}><Icon name="close"/></span></button>)}</div></div><div className="dvw-editor" data-outline={markdownPreview&&outlineVisible&&headings.length>0} data-browser-active={browserId!==undefined||undefined}>{error&&<div className="dvw-error"><button className="dvw-icon" onClick={()=>setError(undefined)}><Icon name="close"/></button>{error}</div>}{markdownPreview&&outlineVisible&&headings.length>0&&<MarkdownOutline headings={headings} onClose={()=>setOutlineVisible(false)} onHeading={selectHeading}/>} {markdown&&activeTab&&<div className="dvw-editor-toolbar"><button className="dvw-icon dvw-outline-toggle" data-active={outlineVisible} title={outlineVisible?'关闭 Markdown 大纲':'显示 Markdown 大纲'} onClick={()=>setOutlineVisible(value=>!value)}><Icon name="list-tree"/></button><button className="dvw-mode-button" data-active={!activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:false}:tab))}><Icon name="code"/> 源码</button><button className="dvw-mode-button" data-active={activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:true}:tab))}><Icon name="preview"/> 预览</button></div>}{activeTab?.diff&&<div className="dvw-editor-toolbar dvw-diff-toolbar">{diffMarkdown&&<div className="dvw-diff-preview-switch"><button className="dvw-mode-button" data-diff-view="diff" data-active={!activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:false}:tab))}><Icon name="diff"/> 差异</button><button className="dvw-mode-button" data-diff-view="preview" data-active={activeTab.preview} onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,preview:true}:tab))}><Icon name="preview"/> 预览</button></div>}{!activeTab.preview&&<><button className="dvw-mode-button dvw-diff-mode-button" data-mode="inline" data-active={activeTab.diffMode!=='split'} title="内联视图" onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,diffMode:'inline'}:tab))}><Icon name="diff-single"/> 内联</button><button className="dvw-mode-button dvw-diff-mode-button" data-mode="split" data-active={activeTab.diffMode==='split'} title="并排视图" onClick={()=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,diffMode:'split'}:tab))}><Icon name="diff-sidebyside"/> 并排</button></>}</div>}<div className="dvw-editor-body" data-toolbar={markdown||activeTab?.diff!==undefined}>{activeTab===undefined?<div className="dvw-empty"><div><strong>DSH Workbench</strong><br/>从资源管理器或搜索结果打开文件<div className="dvw-start-tools"><button onClick={()=>{if(snapshot.bottomPanel!=='terminal')service.toggleBottomPanel('terminal');queueMicrotask(()=>service.runCommand('terminal.new'))}} disabled={!terminalAvailable}><Icon name="terminal"/>新建终端</button>{browser!==undefined&&<button onClick={()=>void newBrowser()} disabled={!browserSnapshot.available}><Icon name="globe"/>浏览器</button>}</div></div></div>:activeTab.diff?diffMarkdown&&activeTab.preview?<MarkdownPreview source={activeTab.diff.modified} sessionId={sessionId} path={activeTab.diffSourcePath??''}/>:<MonacoDiffEditor uri={`${workspace?.cwd??''}/${activeTab.path}`} original={activeTab.diff.original} modified={activeTab.diff.modified} mode={activeTab.diffMode??'inline'} theme={colorScheme} fontSize={fontSize}/>:activeTab.imageUrl?<div className="dvw-image-preview"><img src={activeTab.imageUrl} alt={activeTab.path}/><span>{activeTab.path}</span></div>:activeTab.documentPreview&&activeTab.documentUrl?<DocumentPreview kind={activeTab.documentPreview} src={activeTab.documentUrl} path={activeTab.path}/>:markdown&&activeTab.preview?<MarkdownPreview source={activeTab.draft} sessionId={sessionId} path={activeTab.path}/>:activeTab.binary||activeTab.tooLarge?<div className="dvw-message">{activeTab.binary?'二进制文件无法在文本编辑器或差异视图中打开':`文件超过 5 MiB:${activeTab.path}`}{activeTab.binary&&activeTab.diffSourcePath!==undefined&&!activeTab.path.startsWith('diff:commit:')&&<button onClick={()=>void openPath(activeTab.diffSourcePath!)}>打开文件</button>}</div>:activeTab.loaded===false?<div className="dvw-message">正在加载文件…</div>:<MonacoEditor uri={`${workspace?.cwd??''}/${activeTab.path}`} onAddToDsh={selection=>addToDsh(dshReference(activeTab.path,{selection}))} value={activeTab.draft} theme={colorScheme} fontSize={fontSize} line={activeTab.line} column={activeTab.column} highlight={activeTab.highlight} onChange={draft=>setTabs(rows=>rows.map(tab=>tab.path===activeTab.path?{...tab,draft,dirty:draft!==tab.content}:tab))} onSave={()=>void save(activeTab)}/>}</div></div>{snapshot.bottomPanel===undefined&&<div className="dvw-bottom-reveal" title="向上拖动打开终端" onPointerDown={event=>{if(event.button!==0)return;event.preventDefault();service.toggleBottomPanel('terminal');const statusHeight=statusVisible?24:0;const heightAt=(clientY:number)=>Math.max(120,Math.min(window.innerHeight-160,window.innerHeight-clientY-statusHeight));const move=(e:PointerEvent)=>setPanelHeight(heightAt(e.clientY));const up=()=>{window.removeEventListener('pointermove',move);window.removeEventListener('pointerup',up)};window.addEventListener('pointermove',move);window.addEventListener('pointerup',up)}}/>}</main>
767
827
  {tabMenu&&menuTab&&<div className="dvw-context-menu dvw-tab-menu" role="menu" style={{left:Math.min(tabMenu.x,window.innerWidth-240),top:Math.min(tabMenu.y,window.innerHeight-180)}} onPointerDown={event=>event.stopPropagation()}>
768
828
  <button role="menuitem" onClick={()=>{setTabMenu(undefined);close(menuTab)}}>关闭<span>Ctrl+F4</span></button>
769
829
  <button role="menuitem" disabled={tabs.length<=1} onClick={()=>removeTabs(tabs.filter(row=>row.path!==menuTab.path),'关闭其他标签页')}>关闭其他</button>
@@ -771,6 +831,7 @@ export function Workbench({ service, sessionId, cwd, navigation }: { service: Vs
771
831
  <button role="menuitem" disabled={savedTabs.length===0} onClick={()=>removeTabs(savedTabs,'关闭已保存标签页')}>关闭已保存<span>Ctrl+K U</span></button>
772
832
  <button role="menuitem" onClick={closeAll}>全部关闭<span>Ctrl+K W</span></button>
773
833
  </div>}
834
+ {sessionId!==undefined&&browserTabs.map(tab=><DshBrowserPage key={tab.id} sessionId={sessionId} tabId={tab.id} colorScheme={colorScheme} active={browserId===tab.id&&!dshTabActive&&!settingsOpen&&!switcherOpen&&snapshot.visible}/>)}
774
835
  {snapshot.bottomPanel&&<section className="dvw-bottom" style={{height:panelHeight}}><div className="dvw-bottom-resizer" onPointerDown={event=>{if(event.button!==0)return;event.preventDefault();const startY=event.clientY,startHeight=panelHeight;const move=(e:PointerEvent)=>setPanelHeight(Math.max(120,Math.min(window.innerHeight-160,startHeight+startY-e.clientY)));const up=(e:PointerEvent)=>{window.removeEventListener('pointermove',move);window.removeEventListener('pointerup',up);if(startHeight+startY-e.clientY<80)service.toggleBottomPanel(snapshot.bottomPanel!)};window.addEventListener('pointermove',move);window.addEventListener('pointerup',up)}}/><div className="dvw-bottom-head"><button className="dvw-bottom-title">{panels.find(p=>p.id===snapshot.bottomPanel)?.title??snapshot.bottomPanel}</button>{snapshot.bottomPanel==='terminal'&&<button className="dvw-icon" title="新建终端" onClick={()=>window.dispatchEvent(new Event('dvw-terminal-new'))}><Icon name="add"/></button>}<button className="dvw-icon" style={{marginLeft:'auto'}} onClick={()=>service.toggleBottomPanel(snapshot.bottomPanel!)}><Icon name="close"/></button></div><div className="dvw-bottom-body">{Panel&&workspace&&<Panel sessionId={sessionId} cwd={workspace.cwd} visible colorScheme={colorScheme}/>}</div></section>}
775
836
  <footer className="dvw-status"><span><Icon name="git-branch"/> 工作区</span><span>{workspace?.cwd??cwd??''}</span>{activeTab?.external&&<span>磁盘文件已变化</span>}<button className="dvw-icon" style={{color:'inherit',marginLeft:'auto'}} onClick={()=>service.hide()}>返回 DSH</button></footer>
776
837
  </div>
@@ -0,0 +1,68 @@
1
+ import type { Context } from '@deepseek-ai/cordis'
2
+ import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'
3
+ import type { TabId } from '@deepseek-ai/dsh-client-ui-sidebar-right/client'
4
+ import type { SessionId } from '@deepseek-ai/dsh-session/types'
5
+
6
+ export interface BrowserTab { id: string; sessionId: string; title: string }
7
+ export interface BrowserSnapshot { available: boolean; tabs: readonly BrowserTab[] }
8
+
9
+ /** Navigate official browser tabs; their controller, renderer and persistence stay with DSH. */
10
+ export class NativeBrowser {
11
+ private snapshot: BrowserSnapshot = { available: false, tabs: [] }
12
+ private readonly listeners = new Set<() => void>()
13
+ private readonly lifetime = new AbortController()
14
+ private readonly disposers: Array<() => void>
15
+
16
+ constructor(private readonly ctx: Context) {
17
+ this.disposers = [ctx.sidebarRight.openTabs.subscribe(() => this.update()), ctx.sidebarRightTabs.subscribe(() => this.update())]
18
+ this.update()
19
+ }
20
+
21
+ getSnapshot = (): BrowserSnapshot => this.snapshot
22
+ subscribe = (listener: () => void): (() => void) => { this.listeners.add(listener); return () => this.listeners.delete(listener) }
23
+
24
+ async open(sessionId: string, request?: AbortSignal): Promise<string> {
25
+ const signal = request === undefined ? this.lifetime.signal : AbortSignal.any([request, this.lifetime.signal])
26
+ signal.throwIfAborted()
27
+ if (!this.snapshot.available) throw new Error('官方浏览器尚未启用,请重新加载 Harness。')
28
+ // mounted changes before React commits. The Session's DOM seat confirms store adoption too.
29
+ const ready = () => this.ctx.sidebarRight.mounted.getSnapshot() === sessionId
30
+ && [...document.querySelectorAll('[data-sidebar-right-session]')].some(node => node.getAttribute('data-sidebar-right-session') === sessionId)
31
+ if (!ready()) await new Promise<void>((resolve, reject) => {
32
+ const clean = () => { clearTimeout(timer); unsubscribe(); observer.disconnect(); signal.removeEventListener('abort', abort) }
33
+ const abort = () => { clean(); reject(signal.reason) }
34
+ const timer = setTimeout(() => { clean(); reject(new Error('工作区尚未就绪,请稍后重试。')) }, 10000)
35
+ const check = () => { if (ready()) { clean(); resolve() } }
36
+ const unsubscribe = this.ctx.sidebarRight.mounted.subscribe(check)
37
+ const observer = new MutationObserver(check)
38
+ observer.observe(document.body, { childList: true, subtree: true })
39
+ signal.addEventListener('abort', abort, { once: true })
40
+ })
41
+ signal.throwIfAborted()
42
+ const before = new Set(this.ctx.sidebarRight.tabsIn(sessionId as SessionId).map(tab => tab.id))
43
+ this.ctx.sidebarRight.openTabIn(sessionId as SessionId, 'browser', { revealIfOpened: false })
44
+ const tab = this.ctx.sidebarRight.tabsIn(sessionId as SessionId).find(tab => tab.kind === 'browser' && !before.has(tab.id))
45
+ if (tab === undefined) throw new Error('浏览器页面未能打开,请重试。')
46
+ this.update()
47
+ return tab.id
48
+ }
49
+
50
+ focus(id: string): void {
51
+ this.ctx.sidebarRight.focus(id as TabId)
52
+ if (!this.ctx.sidebarRight.isExpanded()) this.ctx.sidebarRight.toggleExpanded()
53
+ }
54
+
55
+ close(sessionId: string, id: string): void { this.ctx.sidebarRight.closeIn(sessionId as SessionId, id as TabId); this.update() }
56
+ dispose(): void { this.lifetime.abort(); for (const dispose of this.disposers) dispose(); this.listeners.clear() }
57
+
58
+ private update(): void {
59
+ this.snapshot = {
60
+ available: this.ctx.sidebarRightTabs.get('browser') !== undefined,
61
+ tabs: this.ctx.sidebarRight.openTabs.getSnapshot().filter(tab => tab.kind === 'browser').map(tab => ({
62
+ id: tab.tabId, sessionId: tab.sessionId,
63
+ title: this.ctx.sidebarRight.tabsIn(tab.sessionId).find(record => record.id === tab.tabId)?.title ?? '浏览器',
64
+ })),
65
+ }
66
+ for (const listener of this.listeners) listener()
67
+ }
68
+ }
@@ -0,0 +1,16 @@
1
+ /** Locate the central panel DSH renders when a global panel (Plugins, market…) is open. */
2
+ export function findDshGlobalPanel(documentRoot: Document = document): HTMLElement | null {
3
+ return documentRoot.querySelector<HTMLElement>('[data-global-panel]')
4
+ }
5
+
6
+ const DSH_PANEL_TITLES: Record<string, string> = {
7
+ plugins: '插件',
8
+ 'dsh-market': '插件市场',
9
+ automations: '自动化任务',
10
+ ssh: 'SSH',
11
+ }
12
+
13
+ /** Tab-style label for an embedded DSH panel; unknown ids keep their own name. */
14
+ export function dshPanelLabel(id: string): string {
15
+ return DSH_PANEL_TITLES[id] ?? id
16
+ }
@@ -1,4 +1,10 @@
1
1
  import type { Context as ClientContext } from '@deepseek-ai/cordis'
2
+ import { ClientTerminals } from '@deepseek-ai/dsh-api-terminal-controller/client'
3
+ import type {} from '@deepseek-ai/dsh-api-terminal-controller/remote'
4
+ import type {} from '@deepseek-ai/dsh-api-gateway/client'
5
+ import { TerminalWorkspaces } from './terminal.ts'
6
+ import { NativeBrowser } from './browser.ts'
7
+ import type {} from '@deepseek-ai/dsh-client-ui-sidebar-right/client'
2
8
  import { useLayoutEffect } from 'react'
3
9
  import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
4
10
  import type { MainPanelId } from '@deepseek-ai/dsh-client-ui-layout/client'
@@ -12,7 +18,7 @@ import { WorkbenchController } from './service.ts'
12
18
  import { TerminalPanel } from './TerminalPanel.tsx'
13
19
  import { Workbench } from './Workbench.tsx'
14
20
  import { installStyles } from './styles.ts'
15
- import { handleTerminalShortcut } from './shortcuts.ts'
21
+ import { handleBrowserShortcut, handleTerminalShortcut } from './shortcuts.ts'
16
22
  import { mainViewSession } from './session.ts'
17
23
  import { createDshNavigation } from './dshNavigation.ts'
18
24
  import type { DshNavigation } from './dshNavigation.ts'
@@ -21,10 +27,13 @@ export type { BottomPanelContribution, BottomPanelProps, VscodeWorkbench, Workbe
21
27
 
22
28
  declare module '@deepseek-ai/cordis' { interface Context { vscodeWorkbench: VscodeWorkbench } }
23
29
 
24
- const Overlay = ({ service, useSessions, navigation }: PropsRuntime<'shell.overlay'> & { service: VscodeWorkbench; navigation: DshNavigation }) => {
30
+ const Overlay = ({ service, browser, useSessions, usePanelInfo, navigation, openDshPanel, restoreConversation, closeDshPanel }: PropsRuntime<'shell.overlay'> & { service: VscodeWorkbench; browser: NativeBrowser; navigation: DshNavigation; openDshPanel(panelId: string): void; restoreConversation(): void; closeDshPanel(): void }) => {
25
31
  const session = useSessions(mainViewSession)
26
32
  const cwd = session?.projectionValues?.workingDirectory ?? session?.cwd
27
- return <Workbench key={cwd ?? 'no-workspace'} service={service} sessionId={session?.id} cwd={cwd} navigation={navigation}/>
33
+ const activePanelId = usePanelInfo(info => info.activePanelId)
34
+ // DSH's own panel entry is not a page; every other global panel is shown inside the workbench.
35
+ const dshPanelId = activePanelId === null || activePanelId === WORKBENCH_PANEL_ID ? undefined : activePanelId
36
+ return <Workbench key={cwd ?? 'no-workspace'} service={service} browser={browser} sessionId={session?.id} cwd={cwd} navigation={navigation} dshPanelId={dshPanelId} onOpenDshPanel={openDshPanel} onRestoreConversation={restoreConversation} onCloseDshPanel={closeDshPanel}/>
28
37
  }
29
38
 
30
39
  const WORKBENCH_PANEL_ID = 'vscode-workbench' as MainPanelId
@@ -39,25 +48,35 @@ function WorkbenchPanel({ service, restoreConversation }: { service: VscodeWorkb
39
48
  return null
40
49
  }
41
50
 
42
- export const inject = ['slots', 'layout']
51
+ export const inject = ['slots', 'layout', 'remote', 'remote.terminal', 'sidebarRight', 'sidebarRightTabs']
43
52
  export function apply(ctx: ClientContext): void {
44
53
  const service = new WorkbenchController()
54
+ const browser = new NativeBrowser(ctx)
55
+ ctx.effect(() => () => browser.dispose(), 'dsh-vscode-workbench: browser')
56
+ const terminals = new TerminalWorkspaces(new ClientTerminals(ctx.isolate('webTerminals'), ctx.remote.terminal))
57
+ ctx.effect(() => () => terminals.dispose(), 'dsh-vscode-workbench: terminal membership')
45
58
  // Optional access to DSH's own workspace/session stores, used by the centered workspace switcher.
46
59
  const navigation = createDshNavigation((name) => (ctx as unknown as { get?: (name: string) => unknown }).get?.(name))
47
60
  ctx.effect(() => installStyles(), 'dsh-vscode-workbench: styles')
48
61
  ctx.effect(() => { const dispose = ctx.reflect.provide('vscodeWorkbench', service); return () => { void dispose() } }, 'dsh-vscode-workbench: service')
49
- ctx.effect(() => service.registerBottomPanel({ id: 'terminal', title: '终端', component: TerminalPanel }), 'dsh-vscode-workbench: terminal panel')
62
+ ctx.effect(() => service.registerBottomPanel({ id: 'terminal', title: '终端', component: props => <TerminalPanel key={props.sessionId} {...props} workspace={terminals.get(props.sessionId)}/> }), 'dsh-vscode-workbench: terminal panel')
50
63
  ctx.effect(() => service.registerCommand({ id: 'terminal.new', run() { window.dispatchEvent(new Event('dvw-terminal-new')) } }), 'dsh-vscode-workbench: terminal command')
51
64
  const restoreConversation = () => { ctx.layout?.selectPanel(null) }
65
+ // Re-open a panel that the workbench kept as a tab after handing the centre back.
66
+ const openDshPanel = (panelId: string) => {
67
+ try { ctx.layout?.selectPanel(panelId as MainPanelId) } catch { /* panel no longer registered */ }
68
+ }
69
+ const closeDshPanel = () => { ctx.layout?.selectPanel(null) }
52
70
  ctx.slots.inject('main', () => ctx.slots.inject('sidebar.panellist', () => {
53
71
  const disposeMain = ctx.slots.register({ name: 'main', key: WORKBENCH_PANEL_ID, inject: () => ({ service, restoreConversation }) }, WorkbenchPanel)
54
72
  const disposeIcon = ctx.slots.register({ name: 'sidebar.panellist', id: WORKBENCH_PANEL_ID, order: 60, label: () => 'VS Code 工作台' }, WorkbenchPanelIcon)
55
73
  return () => { disposeIcon(); disposeMain() }
56
74
  }))
57
- ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'dsh-vscode-workbench-overlay', inject: () => ({ service, navigation }) }, Overlay))
75
+ ctx.slots.inject('shell.overlay', () => ctx.slots.register({ name: 'shell.overlay', id: 'dsh-vscode-workbench-overlay', inject: () => ({ service, browser, navigation, openDshPanel, restoreConversation, closeDshPanel }) }, Overlay))
58
76
  ctx.effect(() => {
59
77
  const onKey = (event: KeyboardEvent) => {
60
78
  if (handleTerminalShortcut(event, service)) return
79
+ if (handleBrowserShortcut(event, service)) return
61
80
  if (event.isComposing || !event.ctrlKey || event.altKey) return
62
81
  const view = event.shiftKey && event.code === 'KeyE' ? 'explorer' : event.shiftKey && event.code === 'KeyF' ? 'search' : event.shiftKey && event.code === 'KeyG' ? 'git' : undefined
63
82
  if (view !== undefined) { event.preventDefault(); service.show(view) }
@@ -31,3 +31,12 @@ export function handleTerminalShortcut(event: KeyboardEvent, workbench: VscodeWo
31
31
  }
32
32
  return true
33
33
  }
34
+
35
+ /** Match the official desktop browser shortcut while the workbench owns focus. */
36
+ export function handleBrowserShortcut(event: KeyboardEvent, workbench: VscodeWorkbench): boolean {
37
+ if (!workbench.getSnapshot().visible || event.isComposing || !(event.ctrlKey || event.metaKey) || event.altKey || event.shiftKey || event.code !== 'KeyT') return false
38
+ event.preventDefault()
39
+ event.stopImmediatePropagation()
40
+ window.dispatchEvent(new Event('dvw-browser-new'))
41
+ return true
42
+ }
@@ -17,8 +17,8 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
17
17
  .dvw-overlay[data-dvw-theme=dark]{--dvw-base:#191a1b;--dvw-surface-1:#121314;--dvw-surface-2:#202122;--dvw-surface-3:#252526;--dvw-side:#191a1b;--dvw-tab-active:#2a2b2c;--dvw-gutter:#191a1b;--dvw-graph-ref:#3794ff;--dvw-text:#bfbfbf;--dvw-text-2:#8c8c8c;--dvw-text-3:#6e6e6e;--dvw-border:#2a2b2c;--dvw-border-soft:#242526;--dvw-hover:#ffffff12;--dvw-active:#ffffff1f;--dvw-accent:#307e9f;--dvw-error:#e06c75;--dvw-success:#98c379;--dvw-warn:#e5c07b}
18
18
  .dvw-overlay[data-dvw-theme=light]{--dvw-base:#f9fafb;--dvw-surface-1:#fff;--dvw-surface-2:#fff;--dvw-surface-3:#fff;--dvw-side:#f9fafb;--dvw-tab-active:#e8e8e8;--dvw-gutter:#f9fafb;--dvw-graph-ref:#0063d3;--dvw-text:#1f1f1f;--dvw-text-2:#616161;--dvw-text-3:#8a8a8a;--dvw-border:#e5e5e5;--dvw-border-soft:#ececec;--dvw-hover:#0000000d;--dvw-active:#00000014;--dvw-accent:#0078d4;--dvw-error:#e51400;--dvw-success:#388a34;--dvw-warn:#bf8803;color-scheme:light}
19
19
  .dvw-overlay *{box-sizing:border-box}.dvw-overlay ::-webkit-scrollbar{width:10px;height:10px}.dvw-overlay ::-webkit-scrollbar-track{background:transparent}.dvw-overlay ::-webkit-scrollbar-thumb{background:color-mix(in srgb,var(--dvw-text) 20%,transparent);border:2px solid transparent;background-clip:padding-box;border-radius:6px}.dvw-overlay ::-webkit-scrollbar-thumb:hover{background:color-mix(in srgb,var(--dvw-text) 32%,transparent);background-clip:padding-box}.dvw-overlay ::-webkit-scrollbar-corner{background:transparent}.dvw-activity{grid-row:1/3;background:var(--dvw-base);border:1px solid var(--dvw-border);border-radius:8px 0 0 8px;display:flex;flex-direction:column;align-items:center;overflow:hidden}.dvw-activity-menu{min-width:220px}.dvw-activity-empty{flex:1}.dvw-activity-bottom{width:100%;margin-top:auto;display:flex;flex-direction:column;align-items:center}.dvw-menu-separator{height:1px;margin:4px 0;background:var(--dvw-border-soft)}.dvw-menu-check{display:inline-block;width:14px;flex:none;color:var(--dvw-text)}.dvw-act{touch-action:none;user-select:none}.dvw-act[data-dragging=true]{opacity:.35}.dvw-activity-ghost{position:fixed;z-index:120;width:48px;height:48px;display:grid;place-items:center;pointer-events:none;border-radius:8px;background:var(--dvw-tab-active);color:var(--dvw-text);font-size:22px;box-shadow:0 8px 22px rgba(0,0,0,.3);transform:translate(-50%,-50%) scale(1.08);opacity:.95}.dvw-activity-ghost .codicon{font-size:18px}.dvw-act{position:relative;width:48px;height:48px;padding:0;border:0;background:transparent;color:var(--dvw-text-3);font-size:22px;cursor:pointer;display:grid;place-items:center}.dvw-act::before{content:"";position:absolute;left:50%;top:50%;width:32px;height:32px;border-radius:6px;background:transparent;transform:translate(-50%,-50%)}.dvw-act .codicon,.dvw-act svg{position:relative;z-index:1}.dvw-act .codicon{font-size:18px}.dvw-act:hover,.dvw-act[data-active=true]{color:var(--dvw-text)}.dvw-act:hover::before{background:var(--dvw-hover)}.dvw-act[data-active=true]::before{background:var(--dvw-tab-active)}.dvw-act.dsh{margin-top:auto}.dvw-side{grid-column:2;grid-row:1/3;min-width:0;width:100%;background:var(--dvw-side);border-top:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-top-right-radius:8px;border-bottom-right-radius:8px;overflow:hidden;display:flex;flex-direction:column}.dvw-side-resizer{grid-column:2;grid-row:1/3;justify-self:end;width:4px;height:100%;z-index:4;cursor:ew-resize;transform:translateX(2px);touch-action:none}.dvw-side-resizer:hover,.dvw-side-resizer:focus-visible,.dvw-side-resizer[data-dragging=true]{background:var(--dvw-accent)}.dvw-side-head{height:35px;display:flex;align-items:center;padding:0 12px;text-transform:uppercase;font-size:11px;letter-spacing:.4px}.dvw-toolbar{margin-left:auto;display:flex;gap:2px}.dvw-icon{border:0;background:transparent;color:var(--dvw-text-2);border-radius:4px;min-width:24px;height:24px;display:inline-grid;place-items:center;cursor:pointer}.dvw-icon:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-icon:disabled{opacity:.45;cursor:default}.dvw-file-picker{display:none}.dvw-tree,.dvw-results,.dvw-git{overflow:auto;flex:1}.dvw-tree[data-drop-target=true]{outline:1px dashed var(--dvw-accent);outline-offset:-3px}.dvw-tree-row{display:flex;align-items:center;height:22px;padding-right:6px;cursor:pointer;white-space:nowrap}.dvw-tree-row:hover{background:var(--dvw-hover)}.dvw-tree-row[data-selected=true],.dvw-tree-row[data-drop-target=true]{background:var(--dvw-active)}.dvw-chevron{width:16px;flex:none;text-align:center;color:var(--dvw-text-3)}.dvw-file-icon{width:16px;height:16px;flex:none;display:block;object-fit:contain;color:var(--dvw-text-2)}.dvw-file-icon.folder{color:var(--dvw-accent)}.dvw-tree-name{overflow:hidden;text-overflow:ellipsis}.dvw-tree-rename,.dvw-tree-create-input{min-width:0;flex:1;height:20px;border:1px solid var(--dvw-accent);border-radius:2px;background:var(--dvw-surface-1);color:var(--dvw-text);padding:0 3px;font:inherit;letter-spacing:0;outline:0}.dvw-tree-create-row{cursor:text}.dvw-tree-create-row:hover{background:transparent}.dvw-tree-create-input::placeholder{color:var(--dvw-text-3)}.dvw-tree-create-input[aria-invalid=true]{border-color:var(--dvw-error)}.dvw-tree-create-error{min-height:20px;padding-top:2px;padding-right:6px;padding-bottom:3px;color:var(--dvw-error);font-size:11px;line-height:15px;white-space:normal;overflow-wrap:anywhere}.dvw-tree-status{margin-left:auto;padding-left:6px;font-size:11px;font-weight:600;color:var(--dvw-warn)}.dvw-tree-status[data-status=M]{color:var(--dvw-warn)}.dvw-tree-status[data-status=A]{color:var(--dvw-success)}.dvw-tree-status[data-status=D]{color:var(--dvw-error)}.dvw-context-menu{position:fixed;z-index:40;min-width:148px;padding:4px;border:1px solid var(--dvw-border);border-radius:5px;background:var(--dvw-surface-3);box-shadow:0 6px 20px rgba(0,0,0,.3)}.dvw-context-menu button{width:100%;height:28px;border:0;border-radius:3px;background:transparent;color:var(--dvw-text);display:flex;align-items:center;gap:8px;padding:0 8px;text-align:left;font:inherit;cursor:pointer}.dvw-context-menu button:hover{background:var(--dvw-hover)}.dvw-context-menu .dvw-context-delete{color:var(--dvw-error)}.dvw-tab-menu{min-width:220px}.dvw-tab-menu button{justify-content:space-between;gap:24px}.dvw-tab-menu button span{color:var(--dvw-text-3);font-size:11px}.dvw-tab-menu button:disabled{opacity:.45;cursor:default}.dvw-tab-menu button:disabled:hover{background:transparent}
20
- .dvw-main{grid-column:3;grid-row:1;position:relative;display:flex;min-width:0;min-height:0;flex-direction:column;background:var(--dvw-surface-1);border-left:1px solid var(--dvw-border);border-top:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-radius:8px;margin-left:4px;overflow:hidden}.dvw-tabs{height:35px;background:linear-gradient(to top,var(--dvw-border) 0 1px,var(--dvw-base) 1px 100%);display:flex;overflow:hidden}.dvw-tabs[data-empty]{display:none}.dvw-tab{position:relative;height:35px;margin:0;min-width:110px;max-width:260px;border:0;border-right:1px solid var(--dvw-border-soft);border-radius:8px 8px 0 0;background:transparent;color:var(--dvw-text-2);padding:0 8px 0 12px;display:flex;align-items:center;gap:6px;cursor:pointer;flex:none;white-space:nowrap}.dvw-tab:hover{background:var(--dvw-hover)}.dvw-tab[data-active=true]{background:var(--dvw-surface-1);color:var(--dvw-text);border-top:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border)}.dvw-tab-icon{width:16px;height:16px;flex:none;object-fit:contain}.dvw-tab-close{width:16px;height:16px;flex:none;display:grid;place-items:center;border-radius:3px;opacity:0}.dvw-tab-close:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-tab[data-active=true] .dvw-tab-close,.dvw-tab:hover .dvw-tab-close{opacity:1}.dvw-tab-name{overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0;text-align:left;white-space:nowrap}.dvw-editor{position:relative;flex:1;min-height:0}.dvw-editor-toolbar{position:absolute;z-index:3;top:0;right:0;left:0;height:35px;display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:0 10px;border-bottom:1px solid var(--dvw-border-soft);background:var(--dvw-surface-1)}.dvw-editor-body{position:absolute;inset:0;min-height:0}.dvw-editor-body[data-toolbar=true]{top:35px}.dvw-mode-button{height:27px;padding:0 10px;display:flex;align-items:center;gap:6px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--dvw-text-2);cursor:pointer}.dvw-mode-button:hover{background:var(--dvw-hover)}.dvw-mode-button[data-active=true]{border-color:var(--dvw-border);background:var(--dvw-active);color:var(--dvw-text)}.dvw-empty{height:100%;display:grid;place-items:center;color:var(--dvw-text-3);text-align:center}.dvw-empty strong{font-size:24px;color:var(--dvw-text-2)}.dvw-message{padding:12px;color:var(--dvw-warn)}.dvw-message button{margin-left:10px;padding:2px 10px;border:1px solid var(--dvw-border);border-radius:4px;background:var(--dvw-surface-2);color:var(--dvw-text);font:inherit;cursor:pointer}.dvw-message button:hover{background:var(--dvw-hover)}
21
- .dvw-bottom-resizer{height:4px;cursor:ns-resize}.dvw-bottom-resizer:hover{background:var(--dvw-accent)}.dvw-bottom-reveal{position:absolute;left:0;right:0;bottom:0;height:6px;z-index:4;cursor:ns-resize}.dvw-bottom-reveal::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:transparent}.dvw-bottom-reveal:hover::after{background:var(--dvw-accent)}.dvw-bottom{grid-column:3;grid-row:2;height:260px;min-height:120px;border-top:1px solid var(--dvw-border);border-left:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-radius:8px;margin-top:4px;margin-left:4px;background:var(--dvw-base);display:flex;flex-direction:column}.dvw-bottom-head{height:35px;display:flex;align-items:center;gap:14px;padding:0 12px}.dvw-bottom-title{border:0;border-bottom:1px solid var(--dvw-text);background:transparent;color:var(--dvw-text);height:35px}.dvw-bottom-body{flex:1;min-height:0}.dvw-status{grid-column:1/4;grid-row:3;background:var(--dvw-base);color:var(--dvw-text);display:flex;align-items:center;padding:6px 8px 0;gap:18px;font-size:12px}.dvw-theme-control{position:relative;margin-left:auto}.dvw-theme-trigger{height:22px;min-width:112px;display:flex;align-items:center;gap:7px;padding:0 8px;border:0;border-radius:5px;background:color-mix(in srgb,#000 17%,transparent);color:#fff;cursor:pointer}.dvw-theme-trigger:hover{background:color-mix(in srgb,#000 27%,transparent)}.dvw-theme-orb{width:13px;height:13px;border:2px solid #fff;border-radius:50%;background:linear-gradient(90deg,#191a1b 50%,#fff 50%)}.dvw-theme-orb.dark{background:#191a1b}.dvw-theme-orb.light{background:#fff}.dvw-theme-menu{position:absolute;right:0;bottom:29px;width:224px;padding:6px;border:1px solid var(--dvw-border);border-radius:10px;background:var(--dvw-surface-3);box-shadow:0 10px 35px rgba(0,0,0,.28);color:var(--dvw-text)}.dvw-theme-menu button{width:100%;min-height:48px;border:0;border-radius:7px;background:transparent;color:inherit;display:grid;grid-template-columns:30px 1fr 20px;align-items:center;text-align:left;padding:6px;cursor:pointer}.dvw-theme-menu button:hover,.dvw-theme-menu button[aria-checked=true]{background:var(--dvw-hover)}.dvw-theme-menu strong,.dvw-theme-menu small{display:block}.dvw-theme-menu small{color:var(--dvw-text-3);margin-top:2px}.dvw-theme-swatch{width:22px;height:22px;border-radius:6px;border:1px solid var(--dvw-border);background:linear-gradient(135deg,#191a1b 0 50%,#fff 50%)}.dvw-theme-swatch.dark{background:#191a1b}.dvw-theme-swatch.light{background:#fff}
20
+ .dvw-main{grid-column:3;grid-row:1;position:relative;display:flex;min-width:0;min-height:0;flex-direction:column;background:var(--dvw-surface-1);border-left:1px solid var(--dvw-border);border-top:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-radius:8px;margin-left:3px;overflow:hidden}.dvw-tabs{height:35px;background:linear-gradient(to top,var(--dvw-border) 0 1px,var(--dvw-base) 1px 100%);display:flex;overflow:hidden}.dvw-tabs[data-empty]{display:none}.dvw-tab{position:relative;height:35px;margin:0;min-width:110px;max-width:260px;border:0;border-right:1px solid var(--dvw-border-soft);border-radius:8px 8px 0 0;background:transparent;color:var(--dvw-text-2);padding:0 8px 0 12px;display:flex;align-items:center;gap:6px;cursor:pointer;flex:none;white-space:nowrap}.dvw-tab:hover{background:var(--dvw-hover)}.dvw-tab[data-active=true]{background:var(--dvw-surface-1);color:var(--dvw-text);border-top:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border)}.dvw-tab-icon{width:16px;height:16px;flex:none;object-fit:contain}.dvw-tab-close{width:16px;height:16px;flex:none;display:grid;place-items:center;border-radius:3px;opacity:0}.dvw-tab-close:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-tab[data-active=true] .dvw-tab-close,.dvw-tab:hover .dvw-tab-close{opacity:1}.dvw-tab-name{overflow:hidden;text-overflow:ellipsis;flex:1;min-width:0;text-align:left;white-space:nowrap}.dvw-editor{position:relative;flex:1;min-height:0}.dvw-editor-toolbar{position:absolute;z-index:3;top:0;right:0;left:0;height:35px;display:flex;align-items:center;justify-content:flex-end;gap:4px;padding:0 10px;border-bottom:1px solid var(--dvw-border-soft);background:var(--dvw-surface-1)}.dvw-editor-body{position:absolute;inset:0;min-height:0}.dvw-editor-body[data-toolbar=true]{top:35px}.dvw-mode-button{height:27px;padding:0 10px;display:flex;align-items:center;gap:6px;border:1px solid transparent;border-radius:5px;background:transparent;color:var(--dvw-text-2);cursor:pointer}.dvw-mode-button:hover{background:var(--dvw-hover)}.dvw-mode-button[data-active=true]{border-color:var(--dvw-border);background:var(--dvw-active);color:var(--dvw-text)}.dvw-empty{height:100%;display:grid;place-items:center;color:var(--dvw-text-3);text-align:center}.dvw-empty strong{font-size:24px;color:var(--dvw-text-2)}.dvw-message{padding:12px;color:var(--dvw-warn)}.dvw-message button{margin-left:10px;padding:2px 10px;border:1px solid var(--dvw-border);border-radius:4px;background:var(--dvw-surface-2);color:var(--dvw-text);font:inherit;cursor:pointer}.dvw-message button:hover{background:var(--dvw-hover)}
21
+ .dvw-bottom-resizer{height:4px;cursor:ns-resize}.dvw-bottom-resizer:hover{background:var(--dvw-accent)}.dvw-bottom-reveal{position:absolute;left:0;right:0;bottom:0;height:6px;z-index:4;cursor:ns-resize}.dvw-bottom-reveal::after{content:"";position:absolute;left:0;right:0;bottom:0;height:2px;background:transparent}.dvw-bottom-reveal:hover::after{background:var(--dvw-accent)}.dvw-bottom{grid-column:3;grid-row:2;height:260px;min-height:120px;border-top:1px solid var(--dvw-border);border-left:1px solid var(--dvw-border);border-right:1px solid var(--dvw-border);border-bottom:1px solid var(--dvw-border);border-radius:8px;margin-top:4px;margin-left:3px;background:var(--dvw-base);display:flex;flex-direction:column}.dvw-bottom-head{height:35px;display:flex;align-items:center;gap:14px;padding:0 12px}.dvw-bottom-title{border:0;border-bottom:1px solid var(--dvw-text);background:transparent;color:var(--dvw-text);height:35px}.dvw-bottom-body{flex:1;min-height:0}.dvw-status{grid-column:1/4;grid-row:3;background:var(--dvw-base);color:var(--dvw-text);display:flex;align-items:center;padding:6px 8px 0;gap:18px;font-size:12px}.dvw-theme-control{position:relative;margin-left:auto}.dvw-theme-trigger{height:22px;min-width:112px;display:flex;align-items:center;gap:7px;padding:0 8px;border:0;border-radius:5px;background:color-mix(in srgb,#000 17%,transparent);color:#fff;cursor:pointer}.dvw-theme-trigger:hover{background:color-mix(in srgb,#000 27%,transparent)}.dvw-theme-orb{width:13px;height:13px;border:2px solid #fff;border-radius:50%;background:linear-gradient(90deg,#191a1b 50%,#fff 50%)}.dvw-theme-orb.dark{background:#191a1b}.dvw-theme-orb.light{background:#fff}.dvw-theme-menu{position:absolute;right:0;bottom:29px;width:224px;padding:6px;border:1px solid var(--dvw-border);border-radius:10px;background:var(--dvw-surface-3);box-shadow:0 10px 35px rgba(0,0,0,.28);color:var(--dvw-text)}.dvw-theme-menu button{width:100%;min-height:48px;border:0;border-radius:7px;background:transparent;color:inherit;display:grid;grid-template-columns:30px 1fr 20px;align-items:center;text-align:left;padding:6px;cursor:pointer}.dvw-theme-menu button:hover,.dvw-theme-menu button[aria-checked=true]{background:var(--dvw-hover)}.dvw-theme-menu strong,.dvw-theme-menu small{display:block}.dvw-theme-menu small{color:var(--dvw-text-3);margin-top:2px}.dvw-theme-swatch{width:22px;height:22px;border-radius:6px;border:1px solid var(--dvw-border);background:linear-gradient(135deg,#191a1b 0 50%,#fff 50%)}.dvw-theme-swatch.dark{background:#191a1b}.dvw-theme-swatch.light{background:#fff}
22
22
  .dvw-search-form{padding:8px;display:grid;gap:6px}.dvw-input,.dvw-textarea,.dvw-select{width:100%;border:1px solid var(--dvw-border);border-radius:2px;background:var(--dvw-surface-2);color:var(--dvw-text);padding:3px 7px;outline:none}.dvw-input:focus,.dvw-textarea:focus,.dvw-select:focus{border-color:var(--dvw-accent)}.dvw-options{display:flex;align-items:center;gap:8px;font-size:11px}.dvw-result-file{height:22px;display:flex;align-items:center;gap:4px;padding:0 8px;font-weight:600}.dvw-result{height:22px;align-items:center;padding:0 8px 0 20px;cursor:pointer;display:flex;gap:8px}.dvw-result:hover{background:var(--dvw-hover)}.dvw-line{color:var(--dvw-text-3);min-width:30px;text-align:right}.dvw-preview{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-search-hit{background:color-mix(in srgb,var(--dvw-accent) 28%,transparent);border-radius:2px}
23
23
  .dvw-git-head{padding:7px 10px;border-bottom:1px solid var(--dvw-border);display:flex;align-items:center;gap:8px;position:relative}.dvw-branch{min-width:0;border:0;background:transparent;color:var(--dvw-text);font:inherit;font-weight:600;display:flex;align-items:center;gap:5px;cursor:pointer}.dvw-branch-caret{margin-left:2px;font-size:14px;color:var(--dvw-text-3)}.dvw-branch-menu{position:absolute;z-index:12;top:calc(100% + 2px);left:6px;right:6px;padding:4px;border:1px solid var(--dvw-border);border-radius:8px;background:var(--dvw-surface-2);box-shadow:0 10px 28px rgba(0,0,0,.28);color:var(--dvw-text)}.dvw-branch-current{padding:4px 8px;color:var(--dvw-text-3);font-size:11px}.dvw-branch-list{max-height:min(300px,45vh);overflow:auto}.dvw-branch-list button{width:100%;height:26px;display:flex;align-items:center;gap:6px;border:0;border-radius:4px;background:transparent;color:var(--dvw-text);padding:0 8px;text-align:left;font:inherit;cursor:pointer}.dvw-branch-list button:hover{background:var(--dvw-hover)}.dvw-branch-list button[data-active=true]{background:var(--dvw-active)}.dvw-branch-list button span{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-branch-worktree{margin-left:auto;flex:none;padding:1px 5px;border-radius:8px;background:var(--dvw-surface-3);color:var(--dvw-text-3);font-size:10px;line-height:14px}.dvw-branch-create{display:flex;gap:4px;align-items:center;margin-top:4px;padding-top:6px;border-top:1px solid var(--dvw-border-soft)}.dvw-branch-create .dvw-input{flex:1;min-width:0}.dvw-branch-create-button{flex:none;width:24px;min-width:24px;height:24px;border:1px solid var(--dvw-border);border-radius:4px}.dvw-commit{display:flex;flex-direction:column;gap:6px;padding:9px}.dvw-commit-actions{display:grid;grid-template-columns:1fr auto;gap:6px}.dvw-commit-button{height:31px;border:0;border-radius:6px;background:color-mix(in srgb,var(--dvw-accent) 24%,var(--dvw-surface-2));color:var(--dvw-text);display:flex;align-items:center;justify-content:center;gap:7px;cursor:pointer}.dvw-commit-button:hover{background:color-mix(in srgb,var(--dvw-accent) 34%,var(--dvw-surface-2))}.dvw-commit-button:disabled{opacity:.45;cursor:default}.dvw-commit-ai{height:31px;border:1px solid var(--dvw-border);border-radius:6px;background:var(--dvw-surface-2);color:var(--dvw-text-2);display:flex;align-items:center;justify-content:center;gap:6px;font:inherit;cursor:pointer}.dvw-commit-ai:hover:not(:disabled){background:var(--dvw-hover);color:var(--dvw-text)}.dvw-commit-ai:disabled{opacity:.45;cursor:default}.dvw-group-head{height:30px;display:flex;align-items:center;gap:5px;padding:0 6px 0 9px;font-weight:600;position:sticky;top:0;background:var(--dvw-side);z-index:1}.dvw-group-head:hover{background:var(--dvw-hover)}.dvw-group-head .dvw-count{margin-left:auto;min-width:20px;height:20px;border-radius:10px;display:grid;place-items:center;background:var(--dvw-surface-3);color:var(--dvw-text-2);font-size:11px}.dvw-change{display:flex;align-items:center;height:22px;padding:0 5px 0 19px;gap:5px;cursor:pointer}.dvw-change:hover{background:var(--dvw-hover)}.dvw-change .dvw-icon{opacity:0}.dvw-change:hover .dvw-icon,.dvw-change:focus-within .dvw-icon{opacity:1}.dvw-change-path{overflow:hidden;text-overflow:ellipsis;white-space:nowrap;flex:1}.dvw-badge{font-size:11px;color:var(--dvw-warn)}.dvw-graph{border-top:1px solid var(--dvw-border-soft);margin-top:8px;padding-top:3px}.dvw-graph-list{padding-bottom:8px}.dvw-graph-row{width:100%;height:38px;border:0;background:transparent;color:var(--dvw-text);display:flex;align-items:center;padding:0 7px;text-align:left;cursor:pointer;overflow:hidden}.dvw-graph-row:hover{background:var(--dvw-hover)}.dvw-graph-svg{flex:none;align-self:stretch}.dvw-graph-line{fill:none;stroke-width:1px;stroke-linecap:round;vector-effect:non-scaling-stroke}.dvw-graph-node{stroke:var(--dvw-side);stroke-width:2px;fill:var(--dvw-side)}.dvw-graph-copy{display:flex;min-width:0;flex:1;flex-direction:column;justify-content:center}.dvw-graph-subject{overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-graph-meta{font-size:11px;color:var(--dvw-text-3);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-ref{max-width:82px;margin-left:4px;padding:1px 5px;border:1px solid color-mix(in srgb,var(--dvw-accent) 50%,transparent);border-radius:9px;color:var(--dvw-accent);font-size:10px;overflow:hidden;text-overflow:ellipsis;white-space:nowrap}.dvw-error{padding:8px;color:var(--dvw-error);white-space:pre-wrap}.dvw-diff{margin:0;height:100%;overflow:auto;padding:8px 0;background:var(--dvw-surface-1);font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;white-space:pre}.dvw-diff-line{display:block;min-height:18px;padding:0 14px}.dvw-diff-line.add{background:color-mix(in srgb,var(--dvw-success) 15%,transparent);color:var(--dvw-success)}.dvw-diff-line.remove{background:color-mix(in srgb,var(--dvw-error) 13%,transparent);color:var(--dvw-error)}.dvw-diff-line.hunk{background:color-mix(in srgb,var(--dvw-accent) 12%,transparent);color:var(--dvw-accent)}.dvw-diff-line.meta{color:var(--dvw-text-3)}.dvw-diff-empty{padding:24px;color:var(--dvw-text-3)}
24
24
  .dvw-tab-list{display:flex;min-width:0;flex:1;overflow-x:auto;overflow-y:hidden;overscroll-behavior-x:contain;scrollbar-width:none}.dvw-tab-list::-webkit-scrollbar{display:none}.dvw-monaco-diff{position:absolute;inset:0}.dvw-diff-toolbar{justify-content:flex-end}.dvw-group-head{gap:2px;padding-left:2px}.dvw-group-toggle{min-width:0;height:30px;flex:1;border:0;border-radius:5px;background:transparent;color:var(--dvw-text);font:inherit;font-weight:600;display:flex;align-items:center;gap:5px;padding:0 7px;cursor:pointer;text-align:left}.dvw-group-toggle:hover{background:var(--dvw-hover)}.dvw-graph-entry[data-expanded=true]{background:color-mix(in srgb,var(--dvw-accent) 5%,transparent)}.dvw-commit-files{padding:1px 0 5px 26px}.dvw-commit-file{width:100%;height:22px;border:0;background:transparent;color:var(--dvw-text);display:flex;align-items:center;gap:6px;padding:0 7px;text-align:left;cursor:pointer}.dvw-commit-file:hover{background:var(--dvw-hover)}.dvw-commit-file-name{white-space:nowrap}.dvw-commit-file-dir{min-width:0;flex:1;overflow:hidden;text-overflow:ellipsis;white-space:nowrap;color:var(--dvw-text-3);font-size:11px}
@@ -31,7 +31,7 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
31
31
  .dvw-overlay[data-dvw-chat=true]:not([data-dvw-primary-side=true]){grid-template-columns:48px 0 minmax(0,1fr) var(--dvw-chat-width)}
32
32
  .dvw-overlay:not([data-dvw-primary-side=true]) .dvw-side{visibility:hidden;border-right:0}.dvw-overlay:not([data-dvw-primary-side=true]) .dvw-activity{border-radius:8px}.dvw-overlay:not([data-dvw-primary-side=true]) .dvw-side-resizer{display:none}
33
33
  .dvw-overlay:not([data-dvw-status=true]){grid-template-rows:minmax(0,1fr) auto 6px}.dvw-overlay:not([data-dvw-status=true]) .dvw-status{display:none}
34
- .dvw-overlay[data-dvw-chat=true] .dvw-status{grid-column:1/-1;padding-right:calc(var(--dvw-chat-width) + 8px)}
34
+ .dvw-overlay[data-dvw-chat=true] .dvw-status{grid-column:1/-1;padding-right:calc(var(--dvw-chat-width) + 6px)}
35
35
  .dvw-chat{grid-column:4;grid-row:1/3;min-width:0;background:var(--dvw-gutter);border-left:1px solid var(--dvw-border);position:relative}
36
36
  .dvw-chat-head{height:36px;display:flex;align-items:center;justify-content:space-between;padding:0 6px 0 12px;border-bottom:1px solid var(--dvw-border);background:var(--dvw-base);font-weight:600}
37
37
  .dvw-chat-head>span{display:flex;align-items:center;gap:7px}.dvw-chat-body{height:calc(100% - 36px);background:var(--dvw-base)}
@@ -47,7 +47,7 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
47
47
  .dvw-chat{border-left-color:transparent!important}
48
48
  .dvw-side-resizer::after,.dvw-chat-resizer::after{background:transparent;left:3px;width:4px}
49
49
  .dvw-side-resizer:hover::after,.dvw-side-resizer:focus-visible::after,.dvw-side-resizer[data-dragging=true]::after,.dvw-chat-resizer:hover::after,.dvw-chat-resizer:focus-visible::after,.dvw-chat-resizer[data-dragging=true]::after{background:var(--dvw-accent)}
50
- [data-dvw-conversation-panel]{position:fixed!important;top:calc(var(--dvw-titlebar-height) + 6px)!important;right:4px!important;bottom:var(--dvw-status-height,30px)!important;left:auto!important;width:calc(var(--dvw-chat-width) - 8px)!important;height:auto!important;min-width:0!important;z-index:21!important;border:1px solid var(--dsw-alias-border-l2)!important;border-radius:8px!important;overflow:hidden!important;box-shadow:none!important}
50
+ [data-dvw-conversation-panel]{position:fixed!important;top:calc(var(--dvw-titlebar-height) + 6px)!important;right:3px!important;bottom:var(--dvw-status-height,30px)!important;left:auto!important;width:calc(var(--dvw-chat-width) - 6px)!important;height:auto!important;min-width:0!important;z-index:21!important;border:1px solid var(--dsw-alias-border-l2)!important;border-radius:8px!important;overflow:hidden!important;box-shadow:none!important}
51
51
  html[data-platform=darwin] [data-dvw-conversation-panel]{top:var(--dvw-titlebar-height)!important}
52
52
  body[data-dvw-palette=light] [data-dvw-conversation-panel]{--dsw-alias-bg-base:#f9fafb;--dsw-specific-sidebar-fill:#f9fafb;--dsw-alias-border-l2:#e5e5e5;background:#f9fafb!important}body[data-dvw-palette=dark] [data-dvw-conversation-panel]{--dsw-alias-bg-base:#191a1b;--dsw-specific-sidebar-fill:#191a1b;--dsw-alias-bg-layer-1:#202122;--dsw-alias-bg-layer-2:#252526;--dsw-alias-bg-layer-3:#2a2b2c;--dsw-alias-label-primary:#bfbfbf;--dsw-alias-label-secondary:#8c8c8c;--dsw-alias-label-tertiary:#6e6e6e;--dsw-alias-border-l2:#2a2b2c;--dsw-alias-border-l3:#303132;--dsw-alias-interactive-bg-hover:#ffffff12;--dsw-alias-interactive-bg-active:#ffffff1f}
53
53
  body[data-dvw-workbench-active] [data-sidebar-right-expand],body[data-dvw-conversation-active] [data-sidebar-right-expand]{display:none!important}
@@ -69,7 +69,7 @@ body[data-dvw-workbench-active] [data-sidebar-toggle]{display:none!important}
69
69
  .dvw-settings-themes{display:grid;grid-template-columns:repeat(3,1fr);gap:8px}.dvw-settings-themes button{height:56px;display:grid;grid-template-columns:28px 1fr 18px;align-items:center;gap:7px;padding:0 9px;border:1px solid var(--dvw-border);border-radius:7px;background:var(--dvw-base);color:var(--dvw-text);text-align:left;cursor:pointer}.dvw-settings-themes button:hover{background:var(--dvw-hover)}.dvw-settings-themes button[data-active=true]{border-color:var(--dvw-accent);background:color-mix(in srgb,var(--dvw-accent) 10%,var(--dvw-base))}
70
70
  .dvw-setting-check{height:36px;display:flex;align-items:center;gap:9px;border-bottom:1px solid var(--dvw-border-soft);cursor:pointer}.dvw-setting-check input{accent-color:var(--dvw-accent)}.dvw-setting-range{display:grid;grid-template-columns:145px 1fr 52px;align-items:center;gap:10px;min-height:42px}.dvw-setting-range input{accent-color:var(--dvw-accent)}.dvw-setting-range output{text-align:right;color:var(--dvw-text-3);font-variant-numeric:tabular-nums}
71
71
  .dvw-setting-choice{display:grid;grid-template-columns:145px 1fr;align-items:center;gap:10px;min-height:42px}.dvw-setting-choice>span{color:var(--dvw-text-2);font-size:12px}.dvw-setting-segment{display:inline-flex;justify-self:start;padding:2px;border:1px solid var(--dvw-border);border-radius:6px;background:var(--dvw-base)}.dvw-setting-segment button{min-width:64px;height:26px;border:0;border-radius:4px;background:transparent;color:var(--dvw-text-2);font:inherit;font-size:12px;cursor:pointer}.dvw-setting-segment button:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-setting-segment button[data-active=true]{background:var(--dvw-active);color:var(--dvw-text)}
72
- @media(max-width:900px){.dvw-overlay[data-dvw-chat=true]{--dvw-chat-effective:max(440px,min(var(--dvw-chat-width),calc(100vw - 254px)));grid-template-columns:44px min(210px,var(--dvw-side-width),max(0px,calc(100vw - 164px - var(--dvw-chat-effective)))) minmax(0,1fr) var(--dvw-chat-effective)}.dvw-overlay[data-dvw-chat=true] .dvw-status{padding-right:calc(var(--dvw-chat-effective) + 8px)}.dvw-chat-resizer{right:calc(var(--dvw-chat-effective) - 5px)}[data-dvw-conversation-panel]{width:calc(max(440px,min(var(--dvw-chat-width),calc(100vw - 254px))) - 8px)!important}}
72
+ @media(max-width:900px){.dvw-overlay[data-dvw-chat=true]{--dvw-chat-effective:max(440px,min(var(--dvw-chat-width),calc(100vw - 254px)));grid-template-columns:44px min(210px,var(--dvw-side-width),max(0px,calc(100vw - 164px - var(--dvw-chat-effective)))) minmax(0,1fr) var(--dvw-chat-effective)}.dvw-overlay[data-dvw-chat=true] .dvw-status{padding-right:calc(var(--dvw-chat-effective) + 6px)}.dvw-chat-resizer{right:calc(var(--dvw-chat-effective) - 5px)}[data-dvw-conversation-panel]{width:calc(max(440px,min(var(--dvw-chat-width),calc(100vw - 254px))) - 6px)!important}}
73
73
  .dvw-setting-textarea{width:100%;min-height:84px;resize:vertical;padding:8px 10px;border:1px solid var(--dvw-border);border-radius:6px;background:var(--dvw-base);color:var(--dvw-text);font:12px/1.5 ui-monospace,SFMono-Regular,Menlo,Consolas,monospace}
74
74
  .dvw-setting-textarea:focus{outline:none;border-color:var(--dvw-accent)}
75
75
  .dvw-setting-extension{display:flex;align-items:center;gap:12px;padding:10px 12px;border:1px solid var(--dvw-border);border-radius:7px;background:var(--dvw-base)}.dvw-setting-extension>div{min-width:0;flex:1;display:flex;flex-direction:column;gap:2px}.dvw-setting-extension span{color:var(--dvw-text-3);font-size:12px}.dvw-setting-extension button{height:26px;padding:0 14px;border:1px solid var(--dvw-border);border-radius:4px;background:transparent;color:var(--dvw-text);font:inherit;cursor:pointer}.dvw-setting-extension button:hover{background:var(--dvw-hover)}.dvw-setting-extension button.dvw-primary-button{border-color:var(--dvw-accent);background:var(--dvw-accent);color:#fff}.dvw-setting-hint{margin:8px 0 0;color:var(--dvw-text-3);font-size:11px;line-height:1.5}
@@ -94,9 +94,10 @@ body[data-dvw-workbench-active] [data-sidebar-toggle]{display:none!important}
94
94
  .dvw-tab{border-radius:10px 10px 0 0;border-right:0}
95
95
  .dvw-tabs{background:color-mix(in srgb,var(--dvw-text) 4%,var(--dvw-base))}
96
96
  .dvw-tab[data-active=true]{border-top:0;border-right-color:transparent}
97
+ .dvw-tab:hover:not([data-active=true]){background:color-mix(in srgb,var(--dvw-text) 3%,transparent)}
97
98
  .dvw-tab[data-active=true]::before,.dvw-tab[data-active=true]::after{content:"";position:absolute;bottom:0;width:10px;height:10px;background:var(--dvw-surface-1);pointer-events:none}
98
- .dvw-tab[data-active=true]::before{left:-10px;-webkit-mask:radial-gradient(circle 10px at 0 0,transparent 9px,#000 9.6px);mask:radial-gradient(circle 10px at 0 0,transparent 9px,#000 9.6px)}
99
- .dvw-tab[data-active=true]::after{right:-10px;-webkit-mask:radial-gradient(circle 10px at 100% 0,transparent 9px,#000 9.6px);mask:radial-gradient(circle 10px at 100% 0,transparent 9px,#000 9.6px)}
99
+ .dvw-tab[data-active=true]::before{left:-10px;clip-path:path("M10 0C10 5.5 5.5 10 0 10L10 10Z")}
100
+ .dvw-tab[data-active=true]::after{right:-10px;clip-path:path("M0 0C0 5.5 4.5 10 10 10L0 10Z")}
100
101
  .dvw-commit-box{position:relative;display:flex;align-items:stretch}
101
102
  .dvw-commit-input{width:100%;min-height:30px;max-height:96px;field-sizing:content;resize:none;padding:5px 32px 5px 8px;line-height:18px}
102
103
  .dvw-commit-ai{position:absolute;right:4px;top:50%;transform:translateY(-50%);width:24px;min-width:24px;height:24px;border:0;background:transparent;color:var(--dvw-text-3)}
@@ -188,6 +189,16 @@ body[data-dvw-palette=eyecare] [data-dvw-conversation-panel]{--dsw-alias-bg-base
188
189
  .dvw-workspace-switcher>footer button{height:32px;border-radius:9px;padding:0 16px}
189
190
  .dvw-workspace-switcher .dvw-icon{border-radius:9px}
190
191
  @media(max-width:760px){.dvw-settings-body{grid-template-columns:1fr;grid-template-rows:auto minmax(0,1fr)}.dvw-settings-nav{grid-column:1;grid-row:1;flex-direction:row;border-right:0;border-bottom:1px solid var(--dvw-border-soft);padding:8px}.dvw-settings-nav button{flex:none}.dvw-settings-body>section{grid-column:1;grid-row:2;padding:16px 18px 24px}}
192
+ .dvw-dsh-tab-mark{width:16px;height:16px;flex:none;background:currentColor;-webkit-mask:${DSH_MARK_MASK} center/contain no-repeat;mask:${DSH_MARK_MASK} center/contain no-repeat}
193
+ .codicon-globe:before{content:"\eb01"}
194
+ .dvw-terminal-pane{display:flex;flex:1;min-width:0;min-height:0;flex-direction:column}.dvw-terminal-panes[data-split=true]>.dvw-terminal-pane+.dvw-terminal-pane{border-left:1px solid var(--dvw-border)}
195
+ .dvw-terminal-notice{display:flex;align-items:center;gap:8px;padding:4px 10px;color:var(--dvw-text-2);font-size:12px}.dvw-terminal-notice button,.dvw-start-tools button{border:1px solid var(--dvw-border);border-radius:4px;background:var(--dvw-surface-2);color:var(--dvw-text);font:inherit;cursor:pointer}.dvw-terminal-notice button:hover,.dvw-start-tools button:hover{background:var(--dvw-hover)}
196
+ .dvw-start-tools{display:flex;justify-content:center;gap:10px;margin-top:20px}.dvw-start-tools button{display:flex;align-items:center;gap:8px;padding:8px 16px}.dvw-start-tools button:disabled{opacity:.45;cursor:default}
197
+ .dvw-editor[data-browser-active]>.dvw-editor-body,.dvw-editor[data-browser-active]>.dvw-editor-toolbar,.dvw-editor[data-browser-active]>.dvw-outline{visibility:hidden}
198
+ [data-dvw-browser-page]:popover-open{position:fixed!important;inset:auto!important;margin:0!important;padding:0!important;border:0!important;border-radius:0!important;box-sizing:border-box!important;overflow:hidden!important;background:var(--dsw-alias-bg-base,#fff)!important;color:var(--dsw-alias-label-primary,#171717)!important;display:flex!important;flex-direction:column!important}
199
+ [data-dvw-browser-page]::backdrop{background:transparent;pointer-events:none}
200
+ [data-dvw-dsh-page]{position:fixed!important;z-index:106!important;margin:0!important;display:flex!important;flex-direction:column!important;box-sizing:border-box!important;overflow:hidden!important;background:var(--dsw-alias-bg-base,#fff)!important;border:0!important;border-radius:0!important;box-shadow:none!important;--dsh-frame-top-clearance:0px;--dsh-frame-chrome-top:0px}
201
+ [data-dvw-dsh-page][data-dvw-dsh-hidden]{display:none!important}
191
202
  `
192
203
 
193
204
  export function installStyles(): () => void {