@weniatall/dsh-file-viewer 0.3.17 → 0.3.18

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.
Files changed (3) hide show
  1. package/README.md +2 -0
  2. package/lib/client.js +55 -14
  3. package/package.json +6 -1
package/README.md CHANGED
@@ -94,6 +94,8 @@ dsh plugin --profile web add <release-tgz-url>
94
94
 
95
95
  桌面端(Windows)抽屉会自动避让窗口右上角的原生标题栏按钮(最小化 / 最大化 / 关闭):宿主在 `<html>` 上标记 `data-windows-titlebar` 并给出 `--dsh-windows-titlebar-height`,抽屉顶部从其下方开始,因此标题栏里的图标不会再压在窗口按钮下面;窗口进入全屏后该条带消失,抽屉相应贴到顶边。
96
96
 
97
+ 这条偏移带 `!important`:抽屉为退出宿主"居中卡片化"而声明的 `inset:0!important` 会展开出同样重要的 `top:0`,普通声明赢不过它——这正是该问题在 v0.3.6 之后复发的原因。现已由测试锁死层叠结果,规范见仓库根目录 `AGENTS.md` 的「桌面版兼容」一节。
98
+
97
99
  ## Git 面板
98
100
 
99
101
  左侧栏顶部有两个页签:**文件**(目录树)与 **Git**(源代码管理)。Git 页签按 VSCode 源代码管理面板的方式组织:仓库 → 分支 → 提交记录 → 该提交的改动文件。
package/lib/client.js CHANGED
@@ -443,8 +443,22 @@ window.__ModuleLoader__.load({
443
443
  header controls could never be clicked. Start the drawer below the
444
444
  strip instead, which also leaves the strip free to drag the window.
445
445
  The host sets data-fullscreen while the window is fullscreen and the
446
- strip is hidden, so the offset has to drop then. */
447
- html[data-windows-titlebar]:not([data-fullscreen]) .fv-scrim{top:var(--dsh-windows-titlebar-height,40px)}
446
+ strip is hidden, so the offset has to drop then.
447
+
448
+ The declaration carries !important on purpose. It is not defensive
449
+ padding: the drawer's own opt-out above neutralises the host's
450
+ modal-card treatment with an important inset, and the longhand top that
451
+ shorthand expands to outranks any normal declaration — including this
452
+ one. Without !important the strip wins and the drawer slides back under
453
+ the caption buttons, which is exactly the regression this rule was
454
+ written for (fixed in 0.2.8, broken again once 0.3.6 added the
455
+ important inset, because the guard test only checked that the rule
456
+ exists rather than that it wins the cascade).
457
+
458
+ The role="dialog" qualifier keeps this at least as specific as the inset
459
+ rule it has to beat, so the outcome is decided by the important flag and
460
+ the attribute count rather than by sheet order. */
461
+ html[data-windows-titlebar]:not([data-fullscreen]) .fv-scrim[role="dialog"]{top:var(--dsh-windows-titlebar-height,40px)!important}
448
462
  @keyframes fv-fade{from{opacity:0}to{opacity:1}}
449
463
  @keyframes fv-slide{from{transform:translateX(100%)}to{transform:translateX(0)}}
450
464
  @media(prefers-reduced-motion:reduce){.fv-scrim,.fv-shell{animation:none}}
@@ -5656,18 +5670,16 @@ window.__ModuleLoader__.load({
5656
5670
  treeGeneration.current += 1;
5657
5671
  setNodes(new Map());
5658
5672
  if (rootChanged) {
5659
- // Tabs describe paths inside the root that opened them; carrying
5660
- // one across a root switch would point it at a different file with
5661
- // the same name, so the whole set is dropped with the old tree.
5662
- // (The open path already cleared them; this covers a root switched
5663
- // from the picker while the drawer stays open.)
5664
- tabsRef.current = null;
5665
- setTabs(null);
5666
- activeTabIdRef.current = "";
5667
- setActiveTabId("");
5668
- setTabMenu(null);
5673
+ // Only the tree is rebuilt here. Tabs are deliberately NOT touched:
5674
+ // this effect re-runs on the very commit that resolved `root`, i.e.
5675
+ // *after* the roots effect above has already cleared the previous
5676
+ // visit's tabs and opened the one the arrival path asked for.
5677
+ // Clearing them here wiped that brand-new tab, so the first click on
5678
+ // a file in a conversation (a path or a changes card) opened an empty
5679
+ // pane and only the second click — now that `treeBuiltForRef` and
5680
+ // `visitContextRef` both already name this root — loaded anything.
5681
+ // A root switched from the picker resets tabs in `chooseRoot`.
5669
5682
  setExpanded(new Set());
5670
- setPane("tree");
5671
5683
  }
5672
5684
  const chain = revealChanged && reveal !== "" ? [...ancestorsOf(reveal), reveal] : [];
5673
5685
  if (chain.length > 0) {
@@ -5820,6 +5832,35 @@ window.__ModuleLoader__.load({
5820
5832
  }
5821
5833
  };
5822
5834
 
5835
+ /**
5836
+ * Switch the root from the header picker.
5837
+ *
5838
+ * A root switch is the one thing that invalidates every open tab: their
5839
+ * ids and paths only mean something inside the root that opened them, so
5840
+ * carrying them over would point at a different file with the same name.
5841
+ * The reset lives here, on the reader's own action, rather than in the
5842
+ * tree-building effect — that effect also re-runs on the commit that
5843
+ * resolves `root` for an arrival, and clearing there wiped the tab the
5844
+ * arrival had just opened (first click on a file did nothing, second one
5845
+ * worked because the guards had caught up by then).
5846
+ */
5847
+ const chooseRoot = (next) => {
5848
+ const resolved = typeof next === "string" ? next : "";
5849
+ if (resolved === "" || resolved === root) return;
5850
+ tabsRef.current = null;
5851
+ setTabs(null);
5852
+ activeTabIdRef.current = "";
5853
+ setActiveTabId("");
5854
+ setTabMenu(null);
5855
+ setPane("tree");
5856
+ setExpanded(new Set());
5857
+ // The arrival context belongs to the root that is being left; naming it
5858
+ // here keeps the next roots response from treating the switch as an
5859
+ // arrival that has to be reset all over again.
5860
+ visitContextRef.current = resolved;
5861
+ setRoot(resolved);
5862
+ };
5863
+
5823
5864
  /** Patch one tab by id. Used by asynchronous reads that outlive a switch. */
5824
5865
  const patchTab = (id, mutator) => {
5825
5866
  const current = tabList();
@@ -6332,7 +6373,7 @@ window.__ModuleLoader__.load({
6332
6373
  className: "fv-root-select",
6333
6374
  value: root,
6334
6375
  "aria-label": "工作区与安全路径",
6335
- onChange: (event) => setRoot(event.target.value),
6376
+ onChange: (event) => chooseRoot(event.target.value),
6336
6377
  }, ...renderRootOptions(roots))
6337
6378
  : react.createElement("span", { className: "fv-crumbs" }, activeRoot ? activeRoot.label : ""),
6338
6379
  // The tree shows structure, so the header carries the selected
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@weniatall/dsh-file-viewer",
3
- "version": "0.3.17",
3
+ "version": "0.3.18",
4
4
  "description": "Workspace file viewer and Git panel for DeepSeek Harness: file tree, syntax-highlighted code, images, PDF, Excel and Word, plus local/remote branches, paged commit history, commit diffs, working-tree changes and changed-file navigation",
5
5
  "type": "module",
6
6
  "main": "lib/index.js",
@@ -70,6 +70,11 @@
70
70
  "engines": {
71
71
  "node": ">=20.3"
72
72
  },
73
+ "devDependencies": {
74
+ "jsdom": "29.1.1",
75
+ "react": "18.3.1",
76
+ "react-dom": "18.3.1"
77
+ },
73
78
  "scripts": {
74
79
  "test": "node --test test/*.test.mjs && node ../../scripts/check-host-settings.mjs plugins/dsh-file-viewer file-viewer"
75
80
  }