dsh-vscode-workbench 0.1.5 → 0.1.6

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.
package/lib/client.js CHANGED
@@ -115,6 +115,13 @@ window.__ModuleLoader__.load({
115
115
  };
116
116
  //#endregion
117
117
  //#region src/client/api.ts
118
+ var ApiError = class extends Error {
119
+ code;
120
+ constructor(code, message) {
121
+ super(message);
122
+ this.code = code;
123
+ }
124
+ };
118
125
  async function api(method, body, signal) {
119
126
  const response = await fetch(`/dsh-vscode/api/${method}`, {
120
127
  method: "POST",
@@ -123,7 +130,7 @@ window.__ModuleLoader__.load({
123
130
  signal
124
131
  });
125
132
  const answer = await response.json();
126
- if (!response.ok || !answer.ok) throw new Error(answer.error?.message ?? `request failed (${response.status})`);
133
+ if (!response.ok || !answer.ok) throw new ApiError(answer.error?.code ?? "request-failed", answer.error?.message ?? `request failed (${response.status})`);
127
134
  return answer.value;
128
135
  }
129
136
  //#endregion
@@ -4468,6 +4475,32 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4468
4475
  };
4469
4476
  //#endregion
4470
4477
  //#region src/client/Workbench.tsx
4478
+ function unloadedTab(path) {
4479
+ return {
4480
+ path,
4481
+ content: "",
4482
+ draft: "",
4483
+ revision: "",
4484
+ size: 0,
4485
+ binary: false,
4486
+ tooLarge: false,
4487
+ dirty: false,
4488
+ loaded: false,
4489
+ preview: /\.md$/i.test(path)
4490
+ };
4491
+ }
4492
+ /** Restore tab headers without reading their file contents. */
4493
+ function restoreTabPaths(paths) {
4494
+ return [...new Set(paths)].map(unloadedTab);
4495
+ }
4496
+ /** Return whether file changes require reloading one visible directory. */
4497
+ function directoryAffected(directory, changedPaths) {
4498
+ return changedPaths.some((changed) => {
4499
+ const normalized = changed.replace(/\\/g, "/");
4500
+ const parent = normalized.includes("/") ? normalized.slice(0, normalized.lastIndexOf("/")) : "";
4501
+ return normalized === directory || parent === directory;
4502
+ });
4503
+ }
4471
4504
  /** Return the basename shown in a tab while preserving its full internal path. */
4472
4505
  function tabLabel(tab) {
4473
4506
  const path = tab.title ?? tab.diffSourcePath ?? tab.path.replace(/^diff:/, "");
@@ -4497,7 +4530,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4497
4530
  }, []);
4498
4531
  return dark;
4499
4532
  }
4500
- function Tree({ sessionId, path, depth, expanded, selected, selectedDirectory, statuses, onToggle, onSelectDirectory, onOpen, onChange, onUpload }) {
4533
+ function Tree({ sessionId, path, depth, expanded, selected, selectedDirectory, statuses, onToggle, onSelectDirectory, onOpen, onChange, onUpload, creating, onCreatingChange, onCreatingCancel, onCreatingSubmit }) {
4501
4534
  const [rows, setRows] = (0, react.useState)([]);
4502
4535
  const [error, setError] = (0, react.useState)();
4503
4536
  const [dropTarget, setDropTarget] = (0, react.useState)(false);
@@ -4512,7 +4545,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4512
4545
  }, [sessionId, path]);
4513
4546
  (0, react.useEffect)(load, [load]);
4514
4547
  (0, react.useEffect)(() => {
4515
- const handler = () => load();
4548
+ const handler = (event) => {
4549
+ const paths = event.detail;
4550
+ if (paths === void 0 || directoryAffected(path, paths)) load();
4551
+ };
4516
4552
  window.addEventListener("dvw-files-changed", handler);
4517
4553
  return () => window.removeEventListener("dvw-files-changed", handler);
4518
4554
  }, [load]);
@@ -4579,6 +4615,41 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4579
4615
  className: "dvw-error",
4580
4616
  children: error
4581
4617
  }),
4618
+ creating?.directory === path && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)(react_jsx_runtime.Fragment, { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4619
+ className: "dvw-tree-row dvw-tree-create-row",
4620
+ style: { paddingLeft: 6 + depth * 13 },
4621
+ children: [
4622
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("span", { className: "dvw-chevron" }),
4623
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, {
4624
+ name: creating.kind === "directory" ? "folder" : "new-file",
4625
+ className: `dvw-file-icon ${creating.kind === "directory" ? "folder" : ""}`
4626
+ }),
4627
+ /* @__PURE__ */ (0, react_jsx_runtime.jsx)("input", {
4628
+ className: "dvw-tree-create-input",
4629
+ value: creating.value,
4630
+ autoFocus: true,
4631
+ "aria-label": creating.kind === "directory" ? "新建文件夹名称" : "新建文件名称",
4632
+ "aria-invalid": creating.error !== void 0,
4633
+ placeholder: creating.kind === "directory" ? "文件夹名称" : "文件名称",
4634
+ onChange: (event) => onCreatingChange(event.target.value),
4635
+ onKeyDown: (event) => {
4636
+ if (event.key === "Enter") {
4637
+ event.preventDefault();
4638
+ onCreatingSubmit();
4639
+ }
4640
+ if (event.key === "Escape") {
4641
+ event.preventDefault();
4642
+ onCreatingCancel();
4643
+ }
4644
+ },
4645
+ onBlur: onCreatingCancel
4646
+ })
4647
+ ]
4648
+ }), creating.error && /* @__PURE__ */ (0, react_jsx_runtime.jsx)("div", {
4649
+ className: "dvw-tree-create-error",
4650
+ style: { paddingLeft: 42 + depth * 13 },
4651
+ children: creating.error
4652
+ })] }),
4582
4653
  rows.map((row) => /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", { children: [/* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4583
4654
  className: "dvw-tree-row",
4584
4655
  "data-dvw-upload-directory": row.kind === "directory" ? row.path : void 0,
@@ -4656,7 +4727,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4656
4727
  onSelectDirectory,
4657
4728
  onOpen,
4658
4729
  onChange,
4659
- onUpload
4730
+ onUpload,
4731
+ creating,
4732
+ onCreatingChange,
4733
+ onCreatingCancel,
4734
+ onCreatingSubmit
4660
4735
  })] }, row.path)),
4661
4736
  context && /* @__PURE__ */ (0, react_jsx_runtime.jsxs)("div", {
4662
4737
  className: "dvw-context-menu",
@@ -4732,6 +4807,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4732
4807
  const [statuses, setStatuses] = (0, react.useState)(/* @__PURE__ */ new Map());
4733
4808
  const [dropTarget, setDropTarget] = (0, react.useState)(false);
4734
4809
  const [selectedDirectory, setSelectedDirectory] = (0, react.useState)("");
4810
+ const [creating, setCreating] = (0, react.useState)();
4735
4811
  const picker = (0, react.useRef)(null);
4736
4812
  (0, react.useEffect)(() => {
4737
4813
  if (props.cwd) try {
@@ -4753,19 +4829,42 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4753
4829
  window.addEventListener("dvw-files-changed", handler);
4754
4830
  return () => window.removeEventListener("dvw-files-changed", handler);
4755
4831
  }, [refreshStatus, refreshStatusDebounced]);
4756
- const create = async (kind) => {
4757
- const path = prompt(kind === "file" ? "新文件路径" : "新目录路径");
4758
- if (!path) return;
4832
+ const startCreating = (kind) => {
4833
+ const directory = selectedDirectory;
4834
+ if (directory) setExpanded((current) => new Set(current).add(directory));
4835
+ setCreating({
4836
+ kind,
4837
+ directory,
4838
+ value: ""
4839
+ });
4840
+ };
4841
+ const submitCreating = async () => {
4842
+ if (creating === void 0) return;
4843
+ const value = creating.value.trim();
4844
+ if (value === "") {
4845
+ setCreating((current) => current && {
4846
+ ...current,
4847
+ error: "请输入名称"
4848
+ });
4849
+ return;
4850
+ }
4851
+ const path = createdEntryPath(creating.directory, value);
4759
4852
  try {
4760
4853
  await api("fs.create", {
4761
4854
  sessionId: props.sessionId,
4762
4855
  path,
4763
- kind
4856
+ kind: creating.kind
4764
4857
  });
4858
+ const created = path;
4859
+ setCreating(void 0);
4860
+ setSelectedDirectory(creating.kind === "directory" ? created : creating.directory);
4765
4861
  setRevision((x) => x + 1);
4766
4862
  window.dispatchEvent(new Event("dvw-files-changed"));
4767
4863
  } catch (error) {
4768
- alert(String(error));
4864
+ setCreating((current) => current && {
4865
+ ...current,
4866
+ error: error instanceof Error ? error.message : String(error)
4867
+ });
4769
4868
  }
4770
4869
  };
4771
4870
  const upload = (0, react.useCallback)(async (directory, files) => {
@@ -4876,13 +4975,13 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4876
4975
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4877
4976
  className: "dvw-icon",
4878
4977
  title: "新建文件",
4879
- onClick: () => void create("file"),
4978
+ onClick: () => startCreating("file"),
4880
4979
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "new-file" })
4881
4980
  }),
4882
4981
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
4883
4982
  className: "dvw-icon",
4884
- title: "新建目录",
4885
- onClick: () => void create("directory"),
4983
+ title: "新建文件夹",
4984
+ onClick: () => startCreating("directory"),
4886
4985
  children: /* @__PURE__ */ (0, react_jsx_runtime.jsx)(Icon, { name: "new-folder" })
4887
4986
  }),
4888
4987
  /* @__PURE__ */ (0, react_jsx_runtime.jsx)("button", {
@@ -4920,7 +5019,15 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4920
5019
  onSelectDirectory: setSelectedDirectory,
4921
5020
  onOpen: props.onOpen,
4922
5021
  onChange: () => setRevision((x) => x + 1),
4923
- onUpload: (directory, files) => void upload(directory, files)
5022
+ onUpload: (directory, files) => void upload(directory, files),
5023
+ creating,
5024
+ onCreatingChange: (value) => setCreating((current) => current && {
5025
+ ...current,
5026
+ value,
5027
+ error: void 0
5028
+ }),
5029
+ onCreatingCancel: () => setCreating(void 0),
5030
+ onCreatingSubmit: () => void submitCreating()
4924
5031
  })
4925
5032
  }, revision)] });
4926
5033
  }
@@ -4928,6 +5035,10 @@ Please report this to https://github.com/markedjs/marked.`, e) {
4928
5035
  function uploadDestination(selectedDirectory) {
4929
5036
  return selectedDirectory ?? "";
4930
5037
  }
5038
+ /** Join an inline-created entry name to the currently selected Explorer directory. */
5039
+ function createdEntryPath(directory, name) {
5040
+ return directory ? `${directory}/${name}` : name;
5041
+ }
4931
5042
  /** Replace the final path component while preserving its workspace-relative directory. */
4932
5043
  function renamedPath(path, name) {
4933
5044
  return [...path.split("/").slice(0, -1), name].filter(Boolean).join("/");
@@ -5329,7 +5440,11 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5329
5440
  setStatus(nextStatus);
5330
5441
  setCommits(nextCommits);
5331
5442
  setError(void 0);
5332
- }).catch((error) => setError(String(error)));
5443
+ }).catch((error) => {
5444
+ setStatus(void 0);
5445
+ setCommits([]);
5446
+ setError(error instanceof ApiError && error.code === "not-repository" ? void 0 : String(error));
5447
+ });
5333
5448
  }, [props.sessionId]);
5334
5449
  const refreshDebounced = useDebouncedCallback(refresh, 500);
5335
5450
  (0, react.useEffect)(refresh, [refresh]);
@@ -5598,6 +5713,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5598
5713
  const tabsRef = (0, react.useRef)(tabs);
5599
5714
  tabsRef.current = tabs;
5600
5715
  const workspaceRequests = (0, react.useRef)(new WorkspaceRequestScope());
5716
+ const loadingTabs = (0, react.useRef)(/* @__PURE__ */ new Set());
5601
5717
  const dshDark = useDshDarkTheme();
5602
5718
  const [theme, setTheme] = (0, react.useState)(() => {
5603
5719
  const saved = localStorage.getItem("dvw:theme");
@@ -5620,61 +5736,24 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5620
5736
  window.addEventListener("keydown", onKey, true);
5621
5737
  return () => window.removeEventListener("keydown", onKey, true);
5622
5738
  }, [snapshot.visible]);
5623
- const openForWorkspace = async (targetSessionId, epoch, path, line, column) => {
5624
- if (!workspaceRequests.current.isCurrent(epoch)) return;
5625
- if (tabsRef.current.find((tab) => tab.path === path)) {
5626
- setActive(path);
5627
- setTabs((rows) => rows.map((tab) => tab.path === path ? {
5628
- ...tab,
5629
- line,
5630
- column
5631
- } : tab));
5632
- return;
5633
- }
5634
- if (/\.(?:avif|bmp|gif|ico|jpe?g|png|svg|webp)$/i.test(path)) {
5635
- const imageUrl = `/dsh-vscode/file?sessionId=${encodeURIComponent(targetSessionId)}&path=${encodeURIComponent(path)}`;
5636
- if (!workspaceRequests.current.isCurrent(epoch)) return;
5637
- setTabs((rows) => workspaceRequests.current.isCurrent(epoch) ? [...rows.filter((tab) => tab.path !== path), {
5638
- path,
5639
- content: "",
5640
- draft: "",
5641
- revision: "",
5642
- size: 0,
5643
- binary: false,
5644
- tooLarge: false,
5645
- dirty: false,
5646
- imageUrl
5647
- }] : rows);
5648
- setActive(path);
5649
- return;
5650
- }
5651
- try {
5652
- const doc = await api("fs.read", {
5653
- sessionId: targetSessionId,
5654
- path
5655
- });
5656
- if (!workspaceRequests.current.isCurrent(epoch)) return;
5657
- if (doc.binary || doc.tooLarge) {
5658
- setError(doc.binary ? "二进制文件不能在文本编辑器中打开" : `文件超过 5 MiB:${path}`);
5659
- return;
5660
- }
5661
- setTabs((rows) => workspaceRequests.current.isCurrent(epoch) ? [...rows.filter((tab) => tab.path !== path), {
5662
- ...doc,
5663
- draft: doc.content,
5664
- dirty: false,
5665
- line,
5666
- column,
5667
- preview: /\.md$/i.test(path)
5668
- }] : rows);
5669
- setActive(path);
5670
- } catch (error) {
5671
- if (workspaceRequests.current.isCurrent(epoch)) setError(String(error));
5672
- }
5739
+ const openForWorkspace = (path, line, column) => {
5740
+ if (tabsRef.current.find((tab) => tab.path === path)) setTabs((rows) => rows.map((tab) => tab.path === path ? {
5741
+ ...tab,
5742
+ line,
5743
+ column
5744
+ } : tab));
5745
+ else setTabs((rows) => [...rows, {
5746
+ ...unloadedTab(path),
5747
+ line,
5748
+ column
5749
+ }]);
5750
+ setActive(path);
5673
5751
  };
5674
5752
  (0, react.useEffect)(() => {
5675
5753
  if (!snapshot.visible || sessionId === void 0) return;
5676
5754
  const epoch = workspaceRequests.current.begin();
5677
5755
  const cleared = resetWorkspaceDocuments();
5756
+ loadingTabs.current.clear();
5678
5757
  setWorkspace(void 0);
5679
5758
  setTabs(cleared.tabs);
5680
5759
  tabsRef.current = cleared.tabs;
@@ -5690,13 +5769,16 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5690
5769
  if (parsed.view) service.show(parsed.view);
5691
5770
  if (typeof parsed.panelHeight === "number") setPanelHeight(parsed.panelHeight);
5692
5771
  if (typeof parsed.sideWidth === "number") setSideWidth(Math.max(210, Math.min(640, Math.round(parsed.sideWidth))));
5693
- for (const path of parsed.paths ?? []) openForWorkspace(sessionId, epoch, path);
5772
+ const restored = restoreTabPaths(parsed.paths ?? []);
5773
+ setTabs(restored);
5774
+ setActive(restored.some((tab) => tab.path === parsed.active) ? parsed.active : restored.at(-1)?.path);
5694
5775
  } catch {}
5695
5776
  }).catch((error) => {
5696
5777
  if (!controller.signal.aborted && workspaceRequests.current.isCurrent(epoch)) setError(String(error));
5697
5778
  });
5698
5779
  return () => {
5699
5780
  controller.abort();
5781
+ loadingTabs.current.clear();
5700
5782
  workspaceRequests.current.invalidate(epoch);
5701
5783
  };
5702
5784
  }, [snapshot.visible, sessionId]);
@@ -5704,6 +5786,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5704
5786
  if (workspace) localStorage.setItem(`dvw:${workspace.cwd}`, JSON.stringify({
5705
5787
  view: snapshot.view,
5706
5788
  paths: tabs.filter((t) => !t.path.startsWith("diff:")).map((t) => t.path),
5789
+ active,
5707
5790
  panelHeight,
5708
5791
  sideWidth
5709
5792
  }));
@@ -5711,6 +5794,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5711
5794
  workspace,
5712
5795
  snapshot.view,
5713
5796
  tabs,
5797
+ active,
5714
5798
  panelHeight,
5715
5799
  sideWidth
5716
5800
  ]);
@@ -5718,8 +5802,12 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5718
5802
  if (!snapshot.visible || sessionId === void 0) return;
5719
5803
  const protocol = location.protocol === "https:" ? "wss" : "ws";
5720
5804
  const ws = new WebSocket(`${protocol}://${location.host}/dsh-vscode/ws/files?sessionId=${encodeURIComponent(sessionId)}`);
5721
- ws.onmessage = () => {
5722
- window.dispatchEvent(new Event("dvw-files-changed"));
5805
+ ws.onmessage = (event) => {
5806
+ let paths = [];
5807
+ try {
5808
+ paths = JSON.parse(String(event.data)).paths ?? [];
5809
+ } catch {}
5810
+ window.dispatchEvent(new CustomEvent("dvw-files-changed", { detail: paths }));
5723
5811
  setTabs((current) => current.map((tab) => tab.dirty ? {
5724
5812
  ...tab,
5725
5813
  external: true
@@ -5727,9 +5815,49 @@ Please report this to https://github.com/markedjs/marked.`, e) {
5727
5815
  };
5728
5816
  return () => ws.close();
5729
5817
  }, [snapshot.visible, sessionId]);
5730
- const open = async (path, line, column) => {
5731
- if (sessionId === void 0) return;
5732
- await openForWorkspace(sessionId, workspaceRequests.current.current(), path, line, column);
5818
+ (0, react.useEffect)(() => {
5819
+ if (sessionId === void 0 || active === void 0) return;
5820
+ const tab = tabsRef.current.find((row) => row.path === active);
5821
+ if (tab === void 0 || tab.loaded !== false || loadingTabs.current.has(active)) return;
5822
+ const epoch = workspaceRequests.current.current();
5823
+ loadingTabs.current.add(active);
5824
+ if (/\.(?:avif|bmp|gif|ico|jpe?g|png|svg|webp)$/i.test(active)) {
5825
+ const imageUrl = `/dsh-vscode/file?sessionId=${encodeURIComponent(sessionId)}&path=${encodeURIComponent(active)}`;
5826
+ setTabs((rows) => rows.map((row) => row.path === active ? {
5827
+ ...row,
5828
+ loaded: true,
5829
+ imageUrl
5830
+ } : row));
5831
+ loadingTabs.current.delete(active);
5832
+ return;
5833
+ }
5834
+ api("fs.read", {
5835
+ sessionId,
5836
+ path: active
5837
+ }).then((doc) => {
5838
+ if (!workspaceRequests.current.isCurrent(epoch)) return;
5839
+ if (doc.binary || doc.tooLarge) {
5840
+ setTabs((rows) => rows.filter((row) => row.path !== active));
5841
+ setActive((current) => current === active ? void 0 : current);
5842
+ setError(doc.binary ? "二进制文件不能在文本编辑器中打开" : `文件超过 5 MiB:${active}`);
5843
+ return;
5844
+ }
5845
+ setTabs((rows) => rows.map((row) => row.path === active ? {
5846
+ ...row,
5847
+ ...doc,
5848
+ draft: doc.content,
5849
+ loaded: true
5850
+ } : row));
5851
+ }).catch((error) => {
5852
+ if (workspaceRequests.current.isCurrent(epoch)) setError(String(error));
5853
+ }).finally(() => loadingTabs.current.delete(active));
5854
+ }, [
5855
+ active,
5856
+ sessionId,
5857
+ tabs
5858
+ ]);
5859
+ const open = (path, line, column) => {
5860
+ if (sessionId !== void 0) openForWorkspace(path, line, column);
5733
5861
  };
5734
5862
  (0, react.useEffect)(() => {
5735
5863
  const listener = (event) => void open(event.detail);
@@ -6313,7 +6441,7 @@ Please report this to https://github.com/markedjs/marked.`, e) {
6313
6441
  body[data-ds-dark-theme] .dvw-overlay,.dvw-overlay[data-dvw-theme=dark]{color-scheme:dark}
6314
6442
  .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}
6315
6443
  .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}
6316
- .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)}
6444
+ .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;flex:none;text-align:center;color:var(--dvw-text-3)}.dvw-file-icon{width:18px;flex:none;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:0;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)}
6317
6445
  .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)}
6318
6446
  .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}
6319
6447
  .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}