dsh-vscode-workbench 0.1.3 → 0.1.4

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.
@@ -1,5 +1,5 @@
1
1
  import type { ClientContext } from '@deepseek-ai/dsh-client-runtime/client'
2
- import { useLayoutEffect, useRef, useState } from 'react'
2
+ import { useEffect, useLayoutEffect, useState } from 'react'
3
3
  import { createPortal } from 'react-dom'
4
4
  import type {} from '@deepseek-ai/dsh-client-ui-layout/client'
5
5
  import type {} from '@deepseek-ai/dsh-client-ui-sidebar/client'
@@ -19,10 +19,12 @@ const Overlay = ({ service, useSessions }: PropsRuntime<'shell.overlay'> & { ser
19
19
  const sessionId = useSessions(state => state.current) as string | undefined
20
20
  return <Workbench key={sessionId ?? 'no-session'} service={service} useSessions={useSessions as never}/>
21
21
  }
22
- const Footer = ({ service, wide }: PropsRuntime<'sidebar.footer.action'> & { service: VscodeWorkbench }) => {
23
- const marker=useRef<HTMLSpanElement>(null);const [target,setTarget]=useState<HTMLElement|null>()
24
- useLayoutEffect(()=>{let container=marker.current?.parentElement;while(container?.previousElementSibling===null)container=container.parentElement;const region=container?.previousElementSibling;if(!(region instanceof HTMLElement)){setTarget(null);return}const mount=document.createElement('div');mount.className='dvw-sidebar-launcher-mount';region.prepend(mount);setTarget(mount);return()=>mount.remove()},[])
25
- return <><span ref={marker} hidden/>{target===undefined?null:target===null?<Launcher service={service} wide={wide}/>:createPortal(<Launcher service={service} wide={wide}/>,target)}</>
22
+ const Footer = ({ service }: PropsRuntime<'sidebar.footer.action'> & { service: VscodeWorkbench }) => {
23
+ const [target, setTarget] = useState<HTMLElement>()
24
+ const [visible, setVisible] = useState(() => service.getSnapshot().visible)
25
+ useLayoutEffect(() => { const mount = document.createElement('div'); mount.className = 'dvw-launcher-mount'; document.body.append(mount); setTarget(mount); return () => mount.remove() }, [])
26
+ useEffect(() => service.subscribe(() => setVisible(service.getSnapshot().visible)), [service])
27
+ return target === undefined || visible ? null : createPortal(<Launcher service={service}/>, target)
26
28
  }
27
29
 
28
30
  export const inject = ['slots']
@@ -1,20 +1,20 @@
1
1
  const CSS = String.raw`
2
2
  @font-face{font-family:codicon;font-display:block;src:url('/dsh-vscode/codicons/codicon.ttf') format('truetype')}
3
3
  .codicon{display:inline-block;font:normal normal normal 16px/1 codicon;text-align:center;text-decoration:none;text-rendering:auto;-webkit-font-smoothing:antialiased;user-select:none}
4
- .codicon-add:before{content:"\ea60"}.codicon-git-branch:before,.codicon-source-control:before{content:"\ea68"}.codicon-search:before{content:"\ea6d"}.codicon-close:before{content:"\ea76"}.codicon-close-all:before{content:"\eac1"}.codicon-file:before{content:"\ea7b"}.codicon-new-file:before{content:"\ea7f"}.codicon-new-folder:before{content:"\ea80"}.codicon-folder:before{content:"\ea83"}.codicon-trash:before{content:"\ea81"}.codicon-code:before{content:"\eac4"}.codicon-check:before{content:"\eab2"}.codicon-chevron-down:before{content:"\eab4"}.codicon-chevron-right:before{content:"\eab6"}.codicon-color-mode:before{content:"\eac6"}.codicon-diff:before,.codicon-diff-sidebyside:before{content:"\eae1"}.codicon-diff-single:before{content:"\ec22"}.codicon-discard:before{content:"\eae2"}.codicon-file-code:before{content:"\eae9"}.codicon-file-media:before{content:"\eaea"}.codicon-file-pdf:before{content:"\eaeb"}.codicon-file-zip:before{content:"\eaef"}.codicon-files:before{content:"\eaf0"}.codicon-folder-opened:before{content:"\eaf7"}.codicon-git-commit:before{content:"\eafc"}.codicon-graph:before{content:"\eb03"}.codicon-home:before{content:"\eb06"}.codicon-list-tree:before{content:"\eb86"}.codicon-preview:before{content:"\eb2f"}.codicon-refresh:before{content:"\eb37"}.codicon-remove:before{content:"\eb3b"}.codicon-check-all:before{content:"\ebb1"}
4
+ .codicon-add:before{content:"\ea60"}.codicon-git-branch:before,.codicon-source-control:before{content:"\ea68"}.codicon-search:before{content:"\ea6d"}.codicon-edit:before{content:"\ea73"}.codicon-close:before{content:"\ea76"}.codicon-close-all:before{content:"\eac1"}.codicon-cloud-upload:before{content:"\eac3"}.codicon-file:before{content:"\ea7b"}.codicon-new-file:before{content:"\ea7f"}.codicon-new-folder:before{content:"\ea80"}.codicon-folder:before{content:"\ea83"}.codicon-trash:before{content:"\ea81"}.codicon-code:before{content:"\eac4"}.codicon-check:before{content:"\eab2"}.codicon-chevron-down:before{content:"\eab4"}.codicon-chevron-right:before{content:"\eab6"}.codicon-color-mode:before{content:"\eac6"}.codicon-diff:before,.codicon-diff-sidebyside:before{content:"\eae1"}.codicon-diff-single:before{content:"\ec22"}.codicon-discard:before{content:"\eae2"}.codicon-file-code:before{content:"\eae9"}.codicon-file-media:before{content:"\eaea"}.codicon-file-pdf:before{content:"\eaeb"}.codicon-file-zip:before{content:"\eaef"}.codicon-files:before{content:"\eaf0"}.codicon-folder-opened:before{content:"\eaf7"}.codicon-git-commit:before{content:"\eafc"}.codicon-graph:before{content:"\eb03"}.codicon-home:before{content:"\eb06"}.codicon-list-tree:before{content:"\eb86"}.codicon-preview:before{content:"\eb2f"}.codicon-refresh:before{content:"\eb37"}.codicon-remove:before{content:"\eb3b"}.codicon-check-all:before{content:"\ebb1"}
5
5
  .codicon-layout-sidebar-right:before{content:"\ebf4"}.codicon-layout-sidebar-right-off:before{content:"\ec00"}.codicon-settings:before{content:"\eb51"}.codicon-sparkle:before{content:"\ec10"}
6
- .dvw-launch{display:flex;align-items:center;gap:10px;width:100%;height:36px;padding:0 9px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#aaa);cursor:pointer;font:inherit}.dvw-launch:hover{background:var(--dsw-alias-interactive-bg-hover,#292929);color:var(--dsw-alias-label-primary,#fff)}.dvw-launch-icon{width:22px;font-size:19px}
6
+ .dvw-launch{display:flex;align-items:center;gap:10px;width:100%;height:36px;padding:0 9px;border:0;border-radius:8px;background:transparent;color:var(--dsw-alias-label-secondary,#aaa);cursor:pointer;font:inherit}.dvw-launch:hover{background:var(--dsw-alias-interactive-bg-hover,#292929);color:var(--dsw-alias-label-primary,#fff)}.dvw-launcher-mount{position:fixed;z-index:20;right:16px;bottom:16px}.dvw-launcher-mount .dvw-launch{width:auto;height:32px;padding:0 12px;justify-content:center;border:1px solid var(--dsw-alias-border-l3,#e5e7eb);border-radius:6px;background:var(--dsw-alias-bg-layer-1,#fff);box-shadow:0 1px 2px rgba(0,0,0,.06);color:var(--dsw-alias-label-primary,#333)}body[data-ds-dark-theme] .dvw-launcher-mount .dvw-launch{color:var(--dsw-alias-label-primary,#ddd)}@media(max-width:640px){.dvw-launcher-mount{right:8px;bottom:8px}.dvw-launcher-mount .dvw-launch{height:30px;padding:0 10px;font-size:12px}}
7
7
  .dvw-overlay{--dvw-side-width:210px;--dvw-base:var(--dsw-alias-bg-base,#181818);--dvw-surface-1:var(--dsw-alias-bg-layer-1,#1e1e1e);--dvw-surface-2:var(--dsw-alias-bg-layer-2,#252526);--dvw-surface-3:var(--dsw-alias-bg-layer-3,#2d2d30);--dvw-text:var(--dsw-alias-label-primary,#ccc);--dvw-text-2:var(--dsw-alias-label-secondary,#aaa);--dvw-text-3:var(--dsw-alias-label-tertiary,#858585);--dvw-border:var(--dsw-alias-border-l2,#333);--dvw-border-soft:var(--dsw-alias-border-l3,#292929);--dvw-hover:var(--dsw-alias-interactive-bg-hover,rgba(255,255,255,.08));--dvw-active:var(--dsw-alias-interactive-bg-active,rgba(255,255,255,.14));--dvw-accent:var(--dsw-alias-state-business-primary,#3b82f6);--dvw-error:var(--dsw-alias-state-error-primary,#f48771);--dvw-success:var(--dsw-alias-state-success-primary,#73c991);--dvw-warn:var(--dsw-alias-state-warn-primary,#d7ba7d);position:absolute;inset:0;z-index:100;display:grid;grid-template-columns:48px var(--dvw-side-width) minmax(0,1fr);grid-template-rows:minmax(0,1fr) auto 24px;background:var(--dvw-base);color:var(--dvw-text);pointer-events:auto;font:13px/1.4 var(--dsw-font-family,-apple-system,BlinkMacSystemFont,"Segoe UI",sans-serif);overflow:hidden;color-scheme:light}
8
8
  body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-scheme:dark}
9
9
  .dvw-overlay[data-dvw-theme=dark]{--dvw-base:#151517;--dvw-surface-1:#232324;--dvw-surface-2:#2c2c2e;--dvw-surface-3:#353638;--dvw-text:#f9fafb;--dvw-text-2:#cfd3d6;--dvw-text-3:#adb2b8;--dvw-border:#ffffff1f;--dvw-border-soft:#ffffff29;--dvw-hover:#ffffff14;--dvw-active:#ffffff24;--dvw-accent:#679efe;--dvw-error:#f25a5a;--dvw-success:#22c55e;--dvw-warn:#f59e0b}
10
10
  .dvw-overlay[data-dvw-theme=light]{--dvw-base:#fff;--dvw-surface-1:#fff;--dvw-surface-2:#fff;--dvw-surface-3:#fff;--dvw-text:#0f1115;--dvw-text-2:#61666b;--dvw-text-3:#81858c;--dvw-border:#0000001a;--dvw-border-soft:#0000001f;--dvw-hover:#2631480f;--dvw-active:#2631481a;--dvw-accent:#4176e6;--dvw-error:#ec1313;--dvw-success:#22c55e;--dvw-warn:#f59e0b;color-scheme:light}
11
- .dvw-overlay *{box-sizing:border-box}.dvw-activity{grid-row:1/3;background:var(--dvw-base);border-right:1px solid var(--dvw-border-soft);display:flex;flex-direction:column;align-items:center}.dvw-act{width:48px;height:48px;border:0;border-left:2px solid transparent;background:transparent;color:var(--dvw-text-3);font-size:22px;cursor:pointer}.dvw-act:hover,.dvw-act[data-active=true]{color:var(--dvw-text)}.dvw-act[data-active=true]{border-left-color:var(--dvw-accent)}.dvw-act.dsh{margin-top:auto}.dvw-side{grid-column:2;grid-row:1/3;min-width:0;width:100%;background:var(--dvw-surface-1);border-right:1px solid var(--dvw-border);display:flex;flex-direction:column}.dvw-side-resizer{grid-column:2;grid-row:1/3;justify-self:end;width:7px;height:100%;z-index:4;cursor:ew-resize;transform:translateX(3px);touch-action:none}.dvw-side-resizer:hover,.dvw-side-resizer:active{background:var(--dvw-accent)}.dvw-side-head{height:36px;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-tree,.dvw-results,.dvw-git{overflow:auto;flex:1}.dvw-tree-row{display:flex;align-items:center;height:24px;padding-right:6px;cursor:pointer;white-space:nowrap}.dvw-tree-row:hover{background:var(--dvw-hover)}.dvw-tree-row[data-selected=true]{background:var(--dvw-active)}.dvw-chevron{width:16px;text-align:center;color:var(--dvw-text-3)}.dvw-file-icon{width:18px;color:var(--dvw-text-2)}.dvw-file-icon.folder{color:var(--dvw-accent)}
11
+ .dvw-overlay *{box-sizing:border-box}.dvw-activity{grid-row:1/3;background:var(--dvw-base);border-right:1px solid var(--dvw-border-soft);display:flex;flex-direction:column;align-items:center}.dvw-act{width:48px;height:48px;border:0;border-left:2px solid transparent;background:transparent;color:var(--dvw-text-3);font-size:22px;cursor:pointer}.dvw-act:hover,.dvw-act[data-active=true]{color:var(--dvw-text)}.dvw-act[data-active=true]{border-left-color:var(--dvw-accent)}.dvw-act.dsh{margin-top:auto}.dvw-side{grid-column:2;grid-row:1/3;min-width:0;width:100%;background:var(--dvw-surface-1);border-right:1px solid var(--dvw-border);display:flex;flex-direction:column}.dvw-side-resizer{grid-column:2;grid-row:1/3;justify-self:end;width:7px;height:100%;z-index:4;cursor:ew-resize;transform:translateX(3px);touch-action:none}.dvw-side-resizer:hover,.dvw-side-resizer:active{background:var(--dvw-accent)}.dvw-side-head{height:36px;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:24px;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;text-align:center;color:var(--dvw-text-3)}.dvw-file-icon{width:18px;color:var(--dvw-text-2)}.dvw-file-icon.folder{color:var(--dvw-accent)}.dvw-tree-name{overflow:hidden;text-overflow:ellipsis}.dvw-tree-rename{min-width:0;flex:1;height:20px;border:1px solid var(--dvw-accent);background:var(--dvw-surface-1);color:var(--dvw-text);font:inherit;outline:0}.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)}
12
12
  .dvw-main{grid-column:3;grid-row:1;display:flex;min-width:0;min-height:0;flex-direction:column;background:var(--dvw-surface-1)}.dvw-tabs{height:36px;background:var(--dvw-base);display:flex;overflow-x:auto}.dvw-tab{height:36px;min-width:110px;max-width:260px;border:0;border-right:1px solid var(--dvw-border-soft);background:var(--dvw-base);color:var(--dvw-text-2);padding:0 8px 0 12px;display:flex;align-items:center;gap:8px;cursor:pointer}.dvw-tab[data-active=true]{background:var(--dvw-surface-1);color:var(--dvw-text);border-top:1px solid var(--dvw-accent)}.dvw-tab-name{overflow:hidden;text-overflow:ellipsis;flex:1;text-align:left}.dvw-editor{position:relative;flex:1;min-height:0}.dvw-editor-toolbar{position:absolute;z-index:3;top:0;right:0;left:0;height:36px;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:36px}.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)}
13
13
  .dvw-bottom{grid-column:3;grid-row:2;height:260px;min-height:120px;border-top:1px solid var(--dvw-border);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-accent);color:#fff;display:flex;align-items:center;padding:0 8px;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,#17181c 50%,#fff 50%)}.dvw-theme-orb.dark{background:#17181c}.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,#17181c 0 50%,#fff 50%)}.dvw-theme-swatch.dark{background:#17181c}.dvw-theme-swatch.light{background:#fff}
14
14
  .dvw-search-form{padding:8px;display:grid;gap:6px}.dvw-input,.dvw-textarea,.dvw-select{width:100%;border:1px solid var(--dvw-border);background:var(--dvw-surface-2);color:var(--dvw-text);padding:5px 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{padding:5px 8px;font-weight:600}.dvw-result{padding:4px 8px 4px 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}
15
15
  .dvw-git-head{padding:7px 10px;border-bottom:1px solid var(--dvw-border);display:flex;align-items:center;gap:8px}.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-commit{display:grid;grid-template-columns:1fr auto;gap:6px;padding:9px}.dvw-commit .dvw-input{grid-column:1/3}.dvw-commit-button{grid-column:1/3;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-group-head{height:30px;display:flex;align-items:center;gap:5px;padding:0 6px 0 9px;border-radius:5px;font-weight:600;position:sticky;top:0;background:var(--dvw-surface-1);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;min-height:28px;padding:3px 5px 3px 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-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)}
16
16
  .dvw-tabs{overflow:hidden}.dvw-tab-list{display:flex;min-width:0;flex:1;overflow-x:auto}.dvw-tab-actions{flex:none;width:42px;display:grid;place-items:center;padding:0 6px;background:var(--dvw-base)}.dvw-close-all{border:1px solid var(--dvw-border);background:var(--dvw-base)}.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:27px;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}
17
- .dvw-sidebar-launcher-mount{flex:none;padding:4px 8px 6px;border-bottom:1px solid var(--dvw-border-soft)}.dvw-sidebar-launcher-mount .dvw-launch{color:var(--dsw-alias-label-primary,#333)}body[data-ds-dark-theme] .dvw-sidebar-launcher-mount .dvw-launch{color:var(--dsw-alias-label-primary,#ddd)}.dvw-outline{flex:none;max-height:38%;display:flex;flex-direction:column;border-bottom:1px solid var(--dvw-border)}.dvw-outline-head{height:30px;display:flex;align-items:center;justify-content:space-between;padding:0 5px 0 10px;font-weight:600}.dvw-outline-head>span{display:flex;align-items:center;gap:6px}.dvw-outline-list{overflow:auto;padding-bottom:5px}.dvw-outline-item{display:block;width:100%;height:25px;border:0;background:transparent;color:var(--dvw-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left;cursor:pointer}.dvw-outline-item:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-image-preview{position:absolute;inset:0;overflow:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:24px;background:repeating-conic-gradient(var(--dvw-surface-2) 0 25%,var(--dvw-surface-1) 0 50%) 0/20px 20px}.dvw-image-preview img{max-width:100%;max-height:calc(100% - 32px);object-fit:contain;box-shadow:0 4px 24px rgba(0,0,0,.22)}.dvw-image-preview span{max-width:100%;padding:3px 8px;border-radius:5px;background:var(--dvw-surface-1);color:var(--dvw-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
17
+ .dvw-outline{flex:none;max-height:38%;display:flex;flex-direction:column;border-bottom:1px solid var(--dvw-border)}.dvw-outline-head{height:30px;display:flex;align-items:center;justify-content:space-between;padding:0 5px 0 10px;font-weight:600}.dvw-outline-head>span{display:flex;align-items:center;gap:6px}.dvw-outline-list{overflow:auto;padding-bottom:5px}.dvw-outline-item{display:block;width:100%;height:25px;border:0;background:transparent;color:var(--dvw-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap;text-align:left;cursor:pointer}.dvw-outline-item:hover{background:var(--dvw-hover);color:var(--dvw-text)}.dvw-image-preview{position:absolute;inset:0;overflow:auto;display:flex;flex-direction:column;align-items:center;justify-content:center;gap:14px;padding:24px;background:repeating-conic-gradient(var(--dvw-surface-2) 0 25%,var(--dvw-surface-1) 0 50%) 0/20px 20px}.dvw-image-preview img{max-width:100%;max-height:calc(100% - 32px);object-fit:contain;box-shadow:0 4px 24px rgba(0,0,0,.22)}.dvw-image-preview span{max-width:100%;padding:3px 8px;border-radius:5px;background:var(--dvw-surface-1);color:var(--dvw-text-2);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
18
18
  .dvw-markdown{height:100%;overflow:auto;padding:28px clamp(24px,6vw,80px) 60px;color:var(--dvw-text);font-size:15px;line-height:1.65}.dvw-markdown>*:first-child{margin-top:0}.dvw-markdown h1,.dvw-markdown h2{padding-bottom:.3em;border-bottom:1px solid var(--dvw-border)}.dvw-markdown h1{font-size:2em}.dvw-markdown h2{font-size:1.5em}.dvw-markdown h3{font-size:1.25em}.dvw-markdown a{color:var(--dvw-accent)}.dvw-markdown code{font-family:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;background:var(--dvw-surface-2);border-radius:4px;padding:.15em .35em}.dvw-markdown pre{overflow:auto;padding:14px;border:1px solid var(--dvw-border-soft);border-radius:7px;background:var(--dvw-base)}.dvw-markdown pre code{padding:0;background:transparent}.dvw-markdown blockquote{margin-left:0;padding-left:14px;border-left:3px solid var(--dvw-border);color:var(--dvw-text-2)}.dvw-markdown table{border-collapse:collapse;display:block;overflow:auto}.dvw-markdown th,.dvw-markdown td{padding:6px 12px;border:1px solid var(--dvw-border)}.dvw-markdown tr:nth-child(2n){background:var(--dvw-hover)}.dvw-markdown img{max-width:100%}.dvw-markdown hr{border:0;border-top:1px solid var(--dvw-border)}
19
19
  .dvw-sidebar-launcher-mount{border-bottom:1px solid var(--dsw-alias-border-l3,#e5e7eb)}.dvw-outline-toggle{margin-right:auto}.dvw-outline-toggle[data-active=true]{background:var(--dvw-active);color:var(--dvw-text)}.dvw-editor-outline{position:absolute;z-index:2;top:36px;bottom:0;left:0;width:240px;max-height:none;background:var(--dvw-surface-1);border-right:1px solid var(--dvw-border);border-bottom:0}.dvw-editor[data-outline=true] .dvw-editor-body{left:240px}.dvw-diff-preview-switch{margin-right:auto;display:flex;gap:4px}.dvw-commit-files{position:relative;padding-top:1px;padding-right:0;padding-bottom:5px}.dvw-graph-continuation{position:absolute;top:0;bottom:0;left:7px;height:100%;pointer-events:none}.dvw-commit-file-list{position:relative;min-width:0}.dvw-overlay button:focus-visible,.dvw-overlay input:focus-visible,.dvw-overlay select:focus-visible{outline:1px solid var(--dvw-accent);outline-offset:-1px}@media(max-width:800px){.dvw-overlay{grid-template-columns:44px var(--dvw-side-width) minmax(0,1fr)}.dvw-editor-outline{width:210px}.dvw-editor[data-outline=true] .dvw-editor-body{left:210px}}
20
20
  .dvw-overlay[data-dvw-chat=true]{grid-template-columns:48px var(--dvw-side-width) minmax(0,1fr) var(--dvw-chat-width)}
@@ -32,6 +32,8 @@ body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-sc
32
32
  .dvw-chat-toggle{position:absolute;z-index:5;top:6px;right:6px;border:1px solid var(--dvw-border);background:var(--dvw-base)}
33
33
  .dvw-overlay:not([data-dvw-chat=true]) .dvw-tabs{padding-right:36px}
34
34
  [data-dvw-conversation-panel]{position:fixed!important;top:36px!important;right:0!important;bottom:var(--dvw-status-height,24px)!important;left:auto!important;width:var(--dvw-chat-width)!important;height:auto!important;min-width:0!important;z-index:21!important;border-left:1px solid var(--dsw-alias-border-l2)!important;box-shadow:none!important}
35
+ .dvw-confirm-backdrop{position:fixed;z-index:50;inset:0;display:grid;place-items:center;padding:24px;background:rgba(0,0,0,.38)}.dvw-confirm{width:min(380px,calc(100vw - 48px));border:1px solid var(--dvw-border);border-radius:7px;background:var(--dvw-surface-1);box-shadow:0 12px 36px rgba(0,0,0,.34);padding:18px}.dvw-confirm h2{margin:0 0 12px;font-size:15px}.dvw-confirm p{margin:0;color:var(--dvw-text)}.dvw-confirm-note{margin-top:8px!important;color:var(--dvw-text-3)!important;font-size:12px}.dvw-confirm footer{display:flex;justify-content:flex-end;gap:8px;margin-top:20px}.dvw-confirm footer button{height:28px;border:1px solid var(--dvw-border);border-radius:4px;background:var(--dvw-surface-2);color:var(--dvw-text);padding:0 12px;font:inherit;cursor:pointer}.dvw-confirm footer button:hover{background:var(--dvw-hover)}.dvw-confirm footer .dvw-danger-button{border-color:color-mix(in srgb,var(--dvw-error) 65%,var(--dvw-border));background:var(--dvw-error);color:#fff}.dvw-confirm footer .dvw-danger-button:hover{background:color-mix(in srgb,var(--dvw-error) 82%,#000)}
36
+ .dvw-tree[data-dvw-native-drop-target=true]{outline:1px dashed var(--dvw-accent);outline-offset:-3px}.dvw-tree-row[data-dvw-native-drop-target=true]{background:var(--dvw-active)}
35
37
  .dvw-settings-backdrop{position:absolute;inset:0;z-index:30;display:grid;place-items:center;padding:24px;background:rgba(0,0,0,.34);backdrop-filter:blur(2px)}
36
38
  .dvw-settings{width:min(720px,calc(100vw - 48px));max-height:min(720px,calc(100vh - 48px));display:flex;flex-direction:column;border:1px solid var(--dvw-border);border-radius:10px;background:var(--dvw-surface-1);box-shadow:0 16px 50px rgba(0,0,0,.32);overflow:hidden}
37
39
  .dvw-settings>header{height:44px;flex:none;display:flex;align-items:center;justify-content:space-between;padding:0 10px 0 14px;border-bottom:1px solid var(--dvw-border);font-size:14px;font-weight:600}.dvw-settings>header>span{display:flex;align-items:center;gap:8px}
package/src/host.ts CHANGED
@@ -95,6 +95,17 @@ export async function createEntry(cwd: string, path: string, kind: 'file' | 'dir
95
95
  else { const handle = await open(absolute, 'wx'); await handle.close() }
96
96
  }
97
97
 
98
+ /** Write an uploaded file into an existing workspace directory without overwriting files. */
99
+ export async function uploadFile(cwd: string, directory: string, name: string, content: Buffer): Promise<FileEntry> {
100
+ if (name === '' || name === '.' || name === '..' || name.includes('/') || name.includes('\\')) throw new WorkbenchError('filename-invalid', 'uploaded file name is invalid')
101
+ const targetPath = directory === '' ? name : `${directory}/${name}`
102
+ const { root, absolute } = await workspacePath(cwd, targetPath, true)
103
+ const handle = await open(absolute, 'wx')
104
+ try { await handle.writeFile(content) } finally { await handle.close() }
105
+ const info = await lstat(absolute)
106
+ return { name, path: relativePath(root, absolute), kind: 'file', size: info.size, mtimeMs: info.mtimeMs }
107
+ }
108
+
98
109
  export async function renameEntry(cwd: string, path: string, nextPath: string): Promise<void> {
99
110
  const source = await workspacePath(cwd, path)
100
111
  const target = await workspacePath(cwd, nextPath, true)
package/src/index.ts CHANGED
@@ -5,9 +5,9 @@ import type { Duplex } from 'node:stream'
5
5
  import chokidar from 'chokidar'
6
6
  import { WebSocket, WebSocketServer } from 'ws'
7
7
  import type { HostContext } from './context.ts'
8
- import { codiconAsset, createEntry, cwdFor, deleteEntry, gitAction, gitStatus, listFiles, monacoAsset, readDocument, renameEntry, saveDocument, searchWorkspace, workspaceImage } from './host.ts'
8
+ import { codiconAsset, createEntry, cwdFor, deleteEntry, gitAction, gitStatus, listFiles, monacoAsset, readDocument, renameEntry, saveDocument, searchWorkspace, uploadFile, workspaceImage } from './host.ts'
9
9
  import type { SearchRequest } from './types.ts'
10
- import { readBody, send, sendError, stringField, trusted, WorkbenchError } from './wire.ts'
10
+ import { readBinaryBody, readBody, send, sendError, stringField, trusted, WorkbenchError } from './wire.ts'
11
11
 
12
12
  export { WorkbenchError } from './wire.ts'
13
13
  export * from './types.ts'
@@ -57,6 +57,19 @@ export function apply(ctx: HostContext): void {
57
57
  },
58
58
  }), 'dsh-vscode-workbench: API')
59
59
 
60
+ ctx.effect(() => ctx.webServer.register({
61
+ kind: 'exact', path: '/dsh-vscode/upload', handler: async (req, res) => {
62
+ if (!trusted(req, ctx.webRuntime.trustedHosts)) return send(res, 403, { ok: false, error: { code: 'forbidden', message: 'forbidden' } })
63
+ if (req.method !== 'POST') return send(res, 405, { ok: false, error: { code: 'method', message: 'POST required' } })
64
+ try {
65
+ const url = new URL(req.url ?? '/', 'http://dsh.local')
66
+ const sessionId = url.searchParams.get('sessionId'); const directory = url.searchParams.get('directory'); const name = url.searchParams.get('name')
67
+ if (sessionId === null || directory === null || name === null) throw new WorkbenchError('bad-request', 'sessionId, directory and name are required')
68
+ send(res, 201, { ok: true, value: await uploadFile(cwdFor(ctx, sessionId), directory, name, await readBinaryBody(req)) })
69
+ } catch (error) { sendError(res, error) }
70
+ },
71
+ }), 'dsh-vscode-workbench: file upload')
72
+
60
73
  ctx.effect(() => ctx.webServer.register({
61
74
  kind:'prefix',path:'/dsh-vscode/file',handler:async(req,res)=>{if(!trusted(req,ctx.webRuntime.trustedHosts)){res.writeHead(403);res.end();return}try{const url=new URL(req.url??'/','http://dsh.local');const sessionId=url.searchParams.get('sessionId');const path=url.searchParams.get('path');if(sessionId===null||path===null)throw new WorkbenchError('bad-request','sessionId and path are required');const asset=await workspaceImage(cwdFor(ctx,sessionId),path);res.writeHead(200,{'content-type':asset.type,'x-content-type-options':'nosniff','cache-control':'no-store'});res.end(await readFile(asset.path))}catch(error){sendError(res,error)}}
62
75
  }), 'dsh-vscode-workbench: image preview')
package/src/wire.ts CHANGED
@@ -20,6 +20,19 @@ export async function readBody(req: IncomingMessage): Promise<Record<string, unk
20
20
  } catch { throw new WorkbenchError('bad-json', 'request body must be a JSON object') }
21
21
  }
22
22
 
23
+ /** Read a binary upload while enforcing a fixed request limit. */
24
+ export async function readBinaryBody(req: IncomingMessage, limit = 100 * 1024 * 1024): Promise<Buffer> {
25
+ const chunks: Buffer[] = []
26
+ let size = 0
27
+ for await (const chunk of req) {
28
+ const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk)
29
+ size += value.length
30
+ if (size > limit) throw new WorkbenchError('too-large', 'uploaded file is too large', 413)
31
+ chunks.push(value)
32
+ }
33
+ return Buffer.concat(chunks)
34
+ }
35
+
23
36
  export function stringField(body: Record<string, unknown>, key: string): string {
24
37
  const value = body[key]
25
38
  if (typeof value !== 'string') throw new WorkbenchError('bad-request', `${key} must be a string`)