dsh-plugin-core 0.29.0 → 0.29.2

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 (137) hide show
  1. package/dist/design/tokens-css.generated.d.ts +1 -1
  2. package/dist/design/tokens-css.generated.d.ts.map +1 -1
  3. package/dist/design/tokens-css.generated.js +1 -1
  4. package/dist/design/tokens-css.generated.js.map +1 -1
  5. package/dist/explorer/FileBrowser.d.ts +2 -0
  6. package/dist/explorer/FileBrowser.d.ts.map +1 -1
  7. package/dist/explorer/FileBrowser.js +2 -0
  8. package/dist/explorer/FileBrowser.js.map +1 -1
  9. package/dist/explorer/open.d.ts +11 -0
  10. package/dist/explorer/open.d.ts.map +1 -1
  11. package/dist/explorer/open.js +7 -4
  12. package/dist/explorer/open.js.map +1 -1
  13. package/dist/explorer/preview/TextPreview.d.ts +2 -0
  14. package/dist/explorer/preview/TextPreview.d.ts.map +1 -1
  15. package/dist/explorer/preview/TextPreview.js +23 -10
  16. package/dist/explorer/preview/TextPreview.js.map +1 -1
  17. package/dist/explorer/preview/index.d.ts +2 -0
  18. package/dist/explorer/preview/index.d.ts.map +1 -1
  19. package/dist/explorer/preview/index.js +1 -0
  20. package/dist/explorer/preview/index.js.map +1 -1
  21. package/dist/session/SessionViewModal.d.ts +3 -1
  22. package/dist/session/SessionViewModal.d.ts.map +1 -1
  23. package/dist/session/SessionViewModal.js +38 -9
  24. package/dist/session/SessionViewModal.js.map +1 -1
  25. package/dist/session/changes.d.ts +1 -1
  26. package/dist/session/changes.d.ts.map +1 -1
  27. package/dist/session/changes.js +1 -1
  28. package/dist/session/changes.js.map +1 -1
  29. package/dist/session/faces.d.ts +24 -0
  30. package/dist/session/faces.d.ts.map +1 -1
  31. package/dist/session/index.d.ts +1 -1
  32. package/dist/session/index.d.ts.map +1 -1
  33. package/dist/session/mirror/AskCard.d.ts +35 -0
  34. package/dist/session/mirror/AskCard.d.ts.map +1 -0
  35. package/dist/session/mirror/AskCard.js +131 -0
  36. package/dist/session/mirror/AskCard.js.map +1 -0
  37. package/dist/session/mirror/ChangedFileDiff.d.ts +2 -2
  38. package/dist/session/mirror/ChangedFileDiff.js +9 -8
  39. package/dist/session/mirror/ChangedFileDiff.js.map +1 -1
  40. package/dist/session/mirror/ChangedFilesCard.d.ts.map +1 -1
  41. package/dist/session/mirror/ChangedFilesCard.js +8 -1
  42. package/dist/session/mirror/ChangedFilesCard.js.map +1 -1
  43. package/dist/session/mirror/ChatGroupSeat.d.ts.map +1 -1
  44. package/dist/session/mirror/ChatGroupSeat.js +2 -1
  45. package/dist/session/mirror/ChatGroupSeat.js.map +1 -1
  46. package/dist/session/mirror/ChatNodeSeat.d.ts +1 -1
  47. package/dist/session/mirror/ChatNodeSeat.d.ts.map +1 -1
  48. package/dist/session/mirror/ChatNodeSeat.js +2 -2
  49. package/dist/session/mirror/ChatNodeSeat.js.map +1 -1
  50. package/dist/session/mirror/ContextInjectionRow.d.ts +18 -0
  51. package/dist/session/mirror/ContextInjectionRow.d.ts.map +1 -0
  52. package/dist/session/mirror/ContextInjectionRow.js +50 -0
  53. package/dist/session/mirror/ContextInjectionRow.js.map +1 -0
  54. package/dist/session/mirror/Deliverables.d.ts.map +1 -1
  55. package/dist/session/mirror/Deliverables.js +30 -4
  56. package/dist/session/mirror/Deliverables.js.map +1 -1
  57. package/dist/session/mirror/GenericCommandCard.d.ts +56 -2
  58. package/dist/session/mirror/GenericCommandCard.d.ts.map +1 -1
  59. package/dist/session/mirror/GenericCommandCard.js +360 -46
  60. package/dist/session/mirror/GenericCommandCard.js.map +1 -1
  61. package/dist/session/mirror/MessageItem.d.ts +22 -0
  62. package/dist/session/mirror/MessageItem.d.ts.map +1 -1
  63. package/dist/session/mirror/MessageItem.js +45 -4
  64. package/dist/session/mirror/MessageItem.js.map +1 -1
  65. package/dist/session/mirror/StatDialog.d.ts.map +1 -1
  66. package/dist/session/mirror/StatDialog.js +14 -1
  67. package/dist/session/mirror/StatDialog.js.map +1 -1
  68. package/dist/session/mirror/ToolDetails.d.ts +65 -0
  69. package/dist/session/mirror/ToolDetails.d.ts.map +1 -0
  70. package/dist/session/mirror/ToolDetails.js +268 -0
  71. package/dist/session/mirror/ToolDetails.js.map +1 -0
  72. package/dist/session/mirror/TurnTriggerNodeView.d.ts +2 -0
  73. package/dist/session/mirror/TurnTriggerNodeView.d.ts.map +1 -1
  74. package/dist/session/mirror/TurnTriggerNodeView.js +1 -1
  75. package/dist/session/mirror/TurnTriggerNodeView.js.map +1 -1
  76. package/dist/session/mirror/control-details.d.ts +9 -0
  77. package/dist/session/mirror/control-details.d.ts.map +1 -0
  78. package/dist/session/mirror/control-details.js +549 -0
  79. package/dist/session/mirror/control-details.js.map +1 -0
  80. package/dist/session/mirror/detail-i18n.d.ts +4 -0
  81. package/dist/session/mirror/detail-i18n.d.ts.map +1 -0
  82. package/dist/session/mirror/detail-i18n.js +153 -0
  83. package/dist/session/mirror/detail-i18n.js.map +1 -0
  84. package/dist/session/official-classes.d.ts.map +1 -1
  85. package/dist/session/official-classes.js +4 -0
  86. package/dist/session/official-classes.js.map +1 -1
  87. package/dist/session/open-review.d.ts +2 -2
  88. package/dist/session/open-review.d.ts.map +1 -1
  89. package/dist/session/open-review.js +8 -3
  90. package/dist/session/open-review.js.map +1 -1
  91. package/dist/session/pending-inputs.d.ts +11 -0
  92. package/dist/session/pending-inputs.d.ts.map +1 -0
  93. package/dist/session/pending-inputs.js +31 -0
  94. package/dist/session/pending-inputs.js.map +1 -0
  95. package/dist/ui/CodeViewer.d.ts +2 -0
  96. package/dist/ui/CodeViewer.d.ts.map +1 -1
  97. package/dist/ui/CodeViewer.js +13 -1
  98. package/dist/ui/CodeViewer.js.map +1 -1
  99. package/dist/ui/FlipText.d.ts +5 -0
  100. package/dist/ui/FlipText.d.ts.map +1 -1
  101. package/dist/ui/FlipText.js +2 -1
  102. package/dist/ui/FlipText.js.map +1 -1
  103. package/dist/ui/Modal.js +1 -1
  104. package/dist/ui/Modal.js.map +1 -1
  105. package/dist/ui/PanelDock.d.ts.map +1 -1
  106. package/dist/ui/PanelDock.js +20 -18
  107. package/dist/ui/PanelDock.js.map +1 -1
  108. package/dist/ui/SideSheet.js +1 -1
  109. package/dist/ui/SideSheet.js.map +1 -1
  110. package/dist/ui/code-viewer-css.generated.d.ts +1 -1
  111. package/dist/ui/code-viewer-css.generated.d.ts.map +1 -1
  112. package/dist/ui/code-viewer-css.generated.js +1 -1
  113. package/dist/ui/code-viewer-css.generated.js.map +1 -1
  114. package/dist/ui/flip-css.generated.d.ts +1 -1
  115. package/dist/ui/flip-css.generated.d.ts.map +1 -1
  116. package/dist/ui/flip-css.generated.js +1 -1
  117. package/dist/ui/flip-css.generated.js.map +1 -1
  118. package/dist/ui/panel-dock-css.generated.d.ts +1 -1
  119. package/dist/ui/panel-dock-css.generated.d.ts.map +1 -1
  120. package/dist/ui/panel-dock-css.generated.js +1 -1
  121. package/dist/ui/panel-dock-css.generated.js.map +1 -1
  122. package/dist/utils/i18n.d.ts +1 -1
  123. package/dist/utils/i18n.d.ts.map +1 -1
  124. package/dist/utils/i18n.js +436 -0
  125. package/dist/utils/i18n.js.map +1 -1
  126. package/docs/design/external/dsh-host.md +21 -0
  127. package/docs/design/style.md +1 -1
  128. package/docs/help/explorer.doc.ts +20 -5
  129. package/docs/help/explorer.md +6 -4
  130. package/docs/help/integration.doc.ts +1 -1
  131. package/docs/help/integration.md +1 -1
  132. package/docs/help/session.doc.ts +5 -5
  133. package/docs/help/session.md +4 -4
  134. package/docs/help/ui.doc.ts +11 -3
  135. package/docs/help/ui.md +10 -2
  136. package/docs/requirements.md +1 -1
  137. package/package.json +1 -1
@@ -46,8 +46,8 @@ const doc: DocTopic = {
46
46
  en: 'Give exactly one mount: the element wrapping the main area (core makes it a flex row and lets the main area stretch, restoring it on close). Either an element or a selector (`\'#page-row\'`) works, so no ref bookkeeping is needed; an unresolvable selector abandons the open (never falls back to `body`). Never pass `document.body` with your own fixed positioning — that is an overlay, not a split.',
47
47
  },
48
48
  {
49
- zh: '**关闭归面板壳**(2026-11-04):文件与 `content` 两种模式都由 dock 右上角的浮动 ✕ 关闭(Esc 同效),内容组件不画关闭钮;`FileBrowser` 的 `onClose` 只服务没有壳的选文件弹窗。`content` 与 `path` 二选一,同传时忽略 `path` 并 warn。',
50
- en: '**Closing belongs to the dock shell** (2026-11-04): both file and `content` modes close via the floating ✕ at the panel’s top-right (Esc works too); content components draw no close button. `FileBrowser.onClose` only serves the headerless picker popover. `content` and `path` are mutually exclusive — passing both ignores `path` with a warning.',
49
+ zh: '**关钮可退让**(2026-11-04 用户拍板):dock 默认不在面板层画关钮 —— 文件模式用 `FileBrowser` 面包屑行尾那枚,`content` 模式由你给的内容自己画(无头面板画不画、画在哪,归调用方);不想自己画就传 `opts.closeButton: \'floating\'`。Esc 始终有效。`content` 与 `path` 二选一,同传时忽略 `path` 并 warn。',
50
+ en: '**The close button yields to the caller** (2026-11-04): by default the dock draws none at panel level — file mode uses the one at the end of the breadcrumb row, `content` mode draws its own (on a headerless panel that is the caller’s call); pass `opts.closeButton: \'floating\'` if you would rather have the dock draw it. Esc always works. `content` and `path` are mutually exclusive — passing both ignores `path` with a warning.',
51
51
  },
52
52
  {
53
53
  zh: '面包屑口径不变:路径所属工作区名作根名,其下按相对层级展开。▾ 下拉**根层置顶**(其余仍列各工作区):POSIX 是一条「根目录」,**Windows 没有根目录这东西,列的是盘符**(`C:/`、`D:/`…,由宿主 `op:roots` 探出来)。点其他工作区 = 直接列该区根目录(不需要会话)。',
@@ -83,8 +83,8 @@ const doc: DocTopic = {
83
83
  type: 'OpenFilePreviewOptions => FilePreviewHandle',
84
84
  def: '—',
85
85
  desc: {
86
- zh: '在右侧分栏打开文件 / 文件夹预览,返回可关闭它的句柄。预览内容二选一:`path`(核心解析并渲染)或 `content`(由调用方给内容);两种模式的 ✕ 都在面板右上角,由 dock 提供',
87
- en: 'Opens a file/folder preview in the right-side dock and returns a handle to close it. Body is one of two: `path` (resolved and rendered by core) or `content` (rendered from your own node); in both modes the ✕ sits at the panel’s top-right, provided by the dock',
86
+ zh: '在右侧分栏打开文件 / 文件夹预览,返回可关闭它的句柄。预览内容二选一:`path`(核心解析并渲染)或 `content`(由调用方给内容)。面板**默认不画关钮**:文件模式用面包屑行尾那枚,`content` 模式由内容自己画;不想自己画就传 `opts.closeButton`',
87
+ en: 'Opens a file/folder preview in the right-side dock and returns a handle to close it. Body is one of two: `path` (resolved and rendered by core) or `content` (rendered from your own node). The panel draws **no close button by default**: file mode uses the one at the end of the breadcrumb row, `content` mode draws its own — pass `opts.closeButton` if you would rather have the dock draw it',
88
88
  },
89
89
  },
90
90
  {
@@ -115,11 +115,26 @@ const doc: DocTopic = {
115
115
  en: 'Absolute or workspace-relative (a relative one needs a resolvable base, else `null` + a warning); omitted ⇒ the root node. Mutually exclusive with `content`',
116
116
  },
117
117
  },
118
+ {
119
+ name: 'opts.line',
120
+ type: 'number',
121
+ def: '—',
122
+ desc: {
123
+ zh: '打开后定位到这一行(1 起;只对文本 / 源码态生效)。首屏没读到就自动续翻,最多 10 页;`content` 模式忽略。会话预览的 read 工具卡会把 `offset` 作为这一行传进来',
124
+ en: 'Scroll to this line after opening (1-based; text/source view only). Auto-pages up to 10 pages when the line is beyond the first chunk; ignored in `content` mode. The session mirror passes a read tool’s `offset` here',
125
+ },
126
+ },
118
127
  {
119
128
  name: 'opts.content',
120
129
  type: '() => ReactNode',
121
130
  def: '—',
122
- desc: { zh: '自定义预览内容,塞进同一个右侧 dock;平时优先用领域入口(如 `openSessionReview`)。关闭钮由 dock 提供,内容不画 ✕;与 `path` 二选一', en: 'Custom preview body rendered in the same dock; prefer a domain entry such as `openSessionReview` when one exists. The dock provides the close button — content draws no ✕; mutually exclusive with `path`' },
131
+ desc: { zh: '自定义预览内容,塞进同一个右侧 dock;平时优先用领域入口(如 `openSessionReview`)。面板默认不出关钮 ⇒ 内容自己画 ✕(或用 `opts.closeButton` 让 dock 画);与 `path` 二选一', en: 'Custom preview body rendered in the same dock; prefer a domain entry such as `openSessionReview` when one exists. No panel close button by default ⇒ the content draws its own ✕ (or use `opts.closeButton`); mutually exclusive with `path`' },
132
+ },
133
+ {
134
+ name: 'opts.closeButton',
135
+ type: "'auto' | 'header' | 'floating' | 'none'",
136
+ def: "'none'",
137
+ desc: { zh: '面板关钮位置。默认 `none` = 不在面板层画(文件模式用面包屑行尾、`content` 模式由内容画);传 `floating` 让壳在右上角出一枚(占 28px,内容右上要让位)', en: 'Panel close-button placement. Default `none` = draw none at panel level (file mode uses the breadcrumb row, `content` mode draws its own); pass `floating` to have the dock put one at the top-right (occupies 28px — headerless content must leave room)' },
123
138
  },
124
139
  {
125
140
  name: 'opts.officeToPdf',
@@ -22,9 +22,9 @@
22
22
 
23
23
  > EN: Give exactly one mount: the element wrapping the main area (core makes it a flex row and lets the main area stretch, restoring it on close). Either an element or a selector (`'#page-row'`) works, so no ref bookkeeping is needed; an unresolvable selector abandons the open (never falls back to `body`). Never pass `document.body` with your own fixed positioning — that is an overlay, not a split.
24
24
 
25
- **关闭归面板壳**(2026-11-04):文件与 `content` 两种模式都由 dock 右上角的浮动 ✕ 关闭(Esc 同效),内容组件不画关闭钮;`FileBrowser` 的 `onClose` 只服务没有壳的选文件弹窗。`content` 与 `path` 二选一,同传时忽略 `path` 并 warn。
25
+ **关钮可退让**(2026-11-04 用户拍板):dock 默认不在面板层画关钮 —— 文件模式用 `FileBrowser` 面包屑行尾那枚,`content` 模式由你给的内容自己画(无头面板画不画、画在哪,归调用方);不想自己画就传 `opts.closeButton: 'floating'`。Esc 始终有效。`content` 与 `path` 二选一,同传时忽略 `path` 并 warn。
26
26
 
27
- > EN: **Closing belongs to the dock shell** (2026-11-04): both file and `content` modes close via the floating ✕ at the panel’s top-right (Esc works too); content components draw no close button. `FileBrowser.onClose` only serves the headerless picker popover. `content` and `path` are mutually exclusive — passing both ignores `path` with a warning.
27
+ > EN: **The close button yields to the caller** (2026-11-04): by default the dock draws none at panel level — file mode uses the one at the end of the breadcrumb row, `content` mode draws its own (on a headerless panel that is the caller’s call); pass `opts.closeButton: 'floating'` if you would rather have the dock draw it. Esc always works. `content` and `path` are mutually exclusive — passing both ignores `path` with a warning.
28
28
 
29
29
  面包屑口径不变:路径所属工作区名作根名,其下按相对层级展开。▾ 下拉**根层置顶**(其余仍列各工作区):POSIX 是一条「根目录」,**Windows 没有根目录这东西,列的是盘符**(`C:/`、`D:/`…,由宿主 `op:roots` 探出来)。点其他工作区 = 直接列该区根目录(不需要会话)。
30
30
 
@@ -78,11 +78,13 @@ await pickWorkspaceFile({ anchor, placement: 'left-start' }) // 左侧、顶
78
78
 
79
79
  | 参数 | 类型 | 必填 | 默认 | 说明(zh) | 说明(en) |
80
80
  |---|---|---|---|---|---|
81
- | `openFilePreview(opts)` | `OpenFilePreviewOptions => FilePreviewHandle` | — | — | 在右侧分栏打开文件 / 文件夹预览,返回可关闭它的句柄。预览内容二选一:`path`(核心解析并渲染)或 `content`(由调用方给内容);两种模式的 ✕ 都在面板右上角,由 dock 提供 | Opens a file/folder preview in the right-side dock and returns a handle to close it. Body is one of two: `path` (resolved and rendered by core) or `content` (rendered from your own node); in both modes the ✕ sits at the panel’s top-right, provided by the dock |
81
+ | `openFilePreview(opts)` | `OpenFilePreviewOptions => FilePreviewHandle` | — | — | 在右侧分栏打开文件 / 文件夹预览,返回可关闭它的句柄。预览内容二选一:`path`(核心解析并渲染)或 `content`(由调用方给内容)。面板**默认不画关钮**:文件模式用面包屑行尾那枚,`content` 模式由内容自己画;不想自己画就传 `opts.closeButton` | Opens a file/folder preview in the right-side dock and returns a handle to close it. Body is one of two: `path` (resolved and rendered by core) or `content` (rendered from your own node). The panel draws **no close button by default**: file mode uses the one at the end of the breadcrumb row, `content` mode draws its own — pass `opts.closeButton` if you would rather have the dock draw it |
82
82
  | `opts.mount` | `HTMLElement \| string` | 是 | — | 页面里**包住主区**的那个元素,或指向它的选择器(`'#page-row'`);不必自己写 flex(核心补成 flex 行并让主区伸展,关闭时还原)。选择器解析不到 ⇒ 返回 `null` 并 warn,不回落 body。不要传 `document.body` | The element wrapping the main area, or a selector pointing at it (`'#page-row'`); no flex needed (core turns it into a flex row, stretches the main area, and restores it on close). An unresolvable selector ⇒ `null` + a warning, never a `body` fallback. Never `document.body` |
83
83
  | `opts.sessionId` | `string` | — | — | **只在两种情况下需要**:① 相对路径的基准工作区;② Office 预览(转 PDF 要一条有效会话,缺省由核心取最近有会话的工作区) | Needed in exactly two cases: (1) the base workspace for a relative path; (2) Office preview (conversion needs one valid session; core falls back to the most recent workspace with one) |
84
84
  | `opts.path` | `string` | — | — | 绝对路径或工作区相对路径(相对路径要有可解析的基准,否则返回 `null` 并 warn);不传 ⇒ 根节点。与 `content` 二选一 | Absolute or workspace-relative (a relative one needs a resolvable base, else `null` + a warning); omitted ⇒ the root node. Mutually exclusive with `content` |
85
- | `opts.content` | `() => ReactNode` | — | — | 自定义预览内容,塞进同一个右侧 dock;平时优先用领域入口(如 `openSessionReview`)。关闭钮由 dock 提供,内容不画 ✕;与 `path` 二选一 | Custom preview body rendered in the same dock; prefer a domain entry such as `openSessionReview` when one exists. The dock provides the close button — content draws no ✕; mutually exclusive with `path` |
85
+ | `opts.line` | `number` | — | — | 打开后定位到这一行(1 起;只对文本 / 源码态生效)。首屏没读到就自动续翻,最多 10 页;`content` 模式忽略。会话预览的 read 工具卡会把 `offset` 作为这一行传进来 | Scroll to this line after opening (1-based; text/source view only). Auto-pages up to 10 pages when the line is beyond the first chunk; ignored in `content` mode. The session mirror passes a read tool’s `offset` here |
86
+ | `opts.content` | `() => ReactNode` | — | — | 自定义预览内容,塞进同一个右侧 dock;平时优先用领域入口(如 `openSessionReview`)。面板默认不出关钮 ⇒ 内容自己画 ✕(或用 `opts.closeButton` 让 dock 画);与 `path` 二选一 | Custom preview body rendered in the same dock; prefer a domain entry such as `openSessionReview` when one exists. No panel close button by default ⇒ the content draws its own ✕ (or use `opts.closeButton`); mutually exclusive with `path` |
87
+ | `opts.closeButton` | `'auto' \| 'header' \| 'floating' \| 'none'` | — | 'none' | 面板关钮位置。默认 `none` = 不在面板层画(文件模式用面包屑行尾、`content` 模式由内容画);传 `floating` 让壳在右上角出一枚(占 28px,内容右上要让位) | Panel close-button placement. Default `none` = draw none at panel level (file mode uses the breadcrumb row, `content` mode draws its own); pass `floating` to have the dock put one at the top-right (occupies 28px — headerless content must leave room) |
86
88
  | `opts.officeToPdf` | `OfficeToPdfFace` | — | — | Office 转 PDF 能力;缺失则该格式走「不支持」而不是报错 | Office→PDF capability; without it that format shows "unsupported" instead of erroring |
87
89
  | `opts.rootName` | `string` | — | — | 兜底显示名(路径匹配不出工作区时用) | Fallback display name, used when no workspace matches the path |
88
90
  | `pickWorkspaceFile(opts)` | `PickWorkspaceFileOptions => Promise<PickedPath \| null>` | — | — | 锚定按钮弹出模态选文件,解析出 `{ path, isDir, workspaceRel?, workspace? }`;`path` 是**绝对路径** | Anchored modal picker resolving `{ path, isDir, workspaceRel?, workspace? }`; `path` is **absolute** |
@@ -52,7 +52,7 @@ if (view !== null) {
52
52
  // 关闭必须释放 retain 引用
53
53
  onClose: () => { view.dispose(); setOpen(false) },
54
54
  // 文件出口:已经点开的那条会话 id 会作为第二参传进来(相对路径 / Office 都按它定位)
55
- onOpenFile: (path, sid) => openFilePreview({ mount: pageRowEl, path, sessionId: sid }),
55
+ onOpenFile: (path, sid, opts) => openFilePreview({ mount: pageRowEl, path, sessionId: sid, line: opts?.line }),
56
56
  })
57
57
  }`
58
58
  const cleanup = `kit.dispose() // 撤掉 <style> 标签并清空登记域(插件卸载 / HMR)`
@@ -107,7 +107,7 @@ if (view !== null) {
107
107
  // 关闭必须释放 retain 引用
108
108
  onClose: () => { view.dispose(); setOpen(false) },
109
109
  // 文件出口:已经点开的那条会话 id 会作为第二参传进来(相对路径 / Office 都按它定位)
110
- onOpenFile: (path, sid) => openFilePreview({ mount: pageRowEl, path, sessionId: sid }),
110
+ onOpenFile: (path, sid, opts) => openFilePreview({ mount: pageRowEl, path, sessionId: sid, line: opts?.line }),
111
111
  })
112
112
  }
113
113
  ```
@@ -22,7 +22,7 @@ if (view !== null) {
22
22
  openHostSession(child)
23
23
  },
24
24
  // 文件打开出口:给一个落点即可(元素或选择器),核心自带右侧分栏
25
- onOpenFile: (path, sid) => openFilePreview({ mount: '#page-row', path, sessionId: sid }),
25
+ onOpenFile: (path, sid, opts) => openFilePreview({ mount: '#page-row', path, sessionId: sid, line: opts?.line }),
26
26
  // 变更文件审阅:一行打开那次改动的 diff(面板 / 关闭钮全归核心)
27
27
  onOpenReview: (locator) => openSessionReview({ mount: '#page-row', locator }),
28
28
  archived, // 是否已归档
@@ -63,8 +63,8 @@ const doc: DocTopic = {
63
63
  en: 'There is exactly one file exit: tool-card paths, inline mentions and delivered cards all go through `onOpenFile`; without it links degrade to plain text (clickable-looking but no exit — not a break). If you already own a preview surface, call yours inside the callback (core touches no DOM); otherwise wire core’s dock in one line: `onOpenFile: (path, sid) => openFilePreview({ mount: \'#page-row\', path, sessionId: sid })` — the mount takes an element or a selector, and the dialog needs no mask props.',
64
64
  },
65
65
  {
66
- zh: '变更文件的审阅出口是 `onOpenReview`,落点用 `openSessionReview({ mount, locator })` 一行接上:`locator`(sessionId / seq / index / display)到 diff 的拼装归会话域,调用方不挑组件、不塞节点、不补 ✕。面板关闭钮由 dock 出;`ChangedFileDiff.onClose` 只给自带壳的容器,不是关面板的手段。',
67
- en: 'The review exit for changed files is `onOpenReview`; wire it with one line, `openSessionReview({ mount, locator })`. Turning the locator (sessionId / seq / index / display) into a diff belongs to the session domain — pick no components, pass no nodes, add no ✕. The panel close belongs to the dock; `ChangedFileDiff.onClose` is for containers that own their own chrome, not a way to close the panel.',
66
+ zh: '变更文件的审阅出口是 `onOpenReview`,落点用 `openSessionReview({ mount, locator })` 一行接上:`locator`(sessionId / seq / index / display)到 diff 的拼装归会话域,调用方不挑组件、不塞节点、不补 ✕。审阅面板是无头的,`openSessionReview` 已把关闭钮画在审阅头行尾(与换行钮同排),你什么都不用做。',
67
+ en: 'The review exit for changed files is `onOpenReview`; wire it with one line, `openSessionReview({ mount, locator })`. Turning the locator (sessionId / seq / index / display) into a diff belongs to the session domain — pick no components, pass no nodes, add no ✕. The review panel is headerless, so `openSessionReview` already draws the close button at the end of the review header row (next to the wrap toggle); nothing left for you to do.',
68
68
  },
69
69
  {
70
70
  zh: '限制:官方样式未注入时回退自绘观感(功能不受影响);长会话一次全渲染,用「加载更早记录」向前翻页;👍👎 反馈走官方 slots,弹窗没有该插槽。',
@@ -136,9 +136,9 @@ const doc: DocTopic = {
136
136
  },
137
137
  {
138
138
  name: 'onOpenFile',
139
- type: '(path: string, sessionId: string) => void',
139
+ type: '(path: string, sessionId: string, options?: { line?: number }) => void',
140
140
  def: '—',
141
- desc: { zh: '文件打开出口(工具卡 fileLink / 行内引用 / 交付卡共用);第二参是文件所属会话 id,别拿别的会话去定位;缺省 = 链接纯文本', en: 'File-open exit shared by fileLinks, mentions and delivered cards; the second arg is the owning session id — locate the file there, not elsewhere; absent = plain text' },
141
+ desc: { zh: '文件打开出口(工具卡 fileLink / 行内引用 / 交付卡共用);第二参是文件所属会话 id,别拿别的会话去定位;第三参可带 `line`(read 工具卡的 `offset`,直接转给 `openFilePreview` 的 `opts.line`);缺省 = 链接纯文本', en: 'File-open exit shared by fileLinks, mentions and delivered cards; the second arg is the owning session id — locate the file there, not elsewhere; the third may carry `line` (a read call’s `offset`, forward it to `openFilePreview`’s `opts.line`); absent = plain text' },
142
142
  },
143
143
  {
144
144
  name: 'onOpenReview',
@@ -26,9 +26,9 @@
26
26
 
27
27
  > EN: There is exactly one file exit: tool-card paths, inline mentions and delivered cards all go through `onOpenFile`; without it links degrade to plain text (clickable-looking but no exit — not a break). If you already own a preview surface, call yours inside the callback (core touches no DOM); otherwise wire core’s dock in one line: `onOpenFile: (path, sid) => openFilePreview({ mount: '#page-row', path, sessionId: sid })` — the mount takes an element or a selector, and the dialog needs no mask props.
28
28
 
29
- 变更文件的审阅出口是 `onOpenReview`,落点用 `openSessionReview({ mount, locator })` 一行接上:`locator`(sessionId / seq / index / display)到 diff 的拼装归会话域,调用方不挑组件、不塞节点、不补 ✕。面板关闭钮由 dock 出;`ChangedFileDiff.onClose` 只给自带壳的容器,不是关面板的手段。
29
+ 变更文件的审阅出口是 `onOpenReview`,落点用 `openSessionReview({ mount, locator })` 一行接上:`locator`(sessionId / seq / index / display)到 diff 的拼装归会话域,调用方不挑组件、不塞节点、不补 ✕。审阅面板是无头的,`openSessionReview` 已把关闭钮画在审阅头行尾(与换行钮同排),你什么都不用做。
30
30
 
31
- > EN: The review exit for changed files is `onOpenReview`; wire it with one line, `openSessionReview({ mount, locator })`. Turning the locator (sessionId / seq / index / display) into a diff belongs to the session domain — pick no components, pass no nodes, add no ✕. The panel close belongs to the dock; `ChangedFileDiff.onClose` is for containers that own their own chrome, not a way to close the panel.
31
+ > EN: The review exit for changed files is `onOpenReview`; wire it with one line, `openSessionReview({ mount, locator })`. Turning the locator (sessionId / seq / index / display) into a diff belongs to the session domain — pick no components, pass no nodes, add no ✕. The review panel is headerless, so `openSessionReview` already draws the close button at the end of the review header row (next to the wrap toggle); nothing left for you to do.
32
32
 
33
33
  限制:官方样式未注入时回退自绘观感(功能不受影响);长会话一次全渲染,用「加载更早记录」向前翻页;👍👎 反馈走官方 slots,弹窗没有该插槽。
34
34
 
@@ -52,7 +52,7 @@
52
52
  | `forkSession` | `(sessionId, atSeq?) => Promise<string>` | — | — | 开分支并返回子会话 id;缺 openHostSession 则不渲染「继续对话」 | Fork and resolve the child session id; without openHostSession "Continue" does not render |
53
53
  | `openHostSession` | `(sessionId: string) => void` | — | — | 跳官方会话区(uiWorkspace.openSession) | Jump into the official session area (uiWorkspace.openSession) |
54
54
  | `onContinue` | `(message: string) => Promise<void>` | — | — | 暂存输入框的确定:调用方完成 fork + 发首轮 + 跳转;缺省 = 不渲染输入框 | Staging input confirm: caller forks, sends the first turn, navigates; absent = no input rendered |
55
- | `onOpenFile` | `(path: string, sessionId: string) => void` | — | — | 文件打开出口(工具卡 fileLink / 行内引用 / 交付卡共用);第二参是文件所属会话 id,别拿别的会话去定位;缺省 = 链接纯文本 | File-open exit shared by fileLinks, mentions and delivered cards; the second arg is the owning session id — locate the file there, not elsewhere; absent = plain text |
55
+ | `onOpenFile` | `(path: string, sessionId: string, options?: { line?: number }) => void` | — | — | 文件打开出口(工具卡 fileLink / 行内引用 / 交付卡共用);第二参是文件所属会话 id,别拿别的会话去定位;第三参可带 `line`(read 工具卡的 `offset`,直接转给 `openFilePreview` 的 `opts.line`);缺省 = 链接纯文本 | File-open exit shared by fileLinks, mentions and delivered cards; the second arg is the owning session id — locate the file there, not elsewhere; the third may carry `line` (a read call’s `offset`, forward it to `openFilePreview`’s `opts.line`); absent = plain text |
56
56
  | `onOpenReview` | `(locator: ChangedFileReviewLocator) => void` | — | — | 变更文件的审阅出口(等价官方 openReview);落点用 `openSessionReview({ mount, locator })` 一行接上;缺省 = 变更卡整张不可点 | Review exit for changed files (equals official openReview); wire it with `openSessionReview({ mount, locator })`; absent = the change card is not clickable |
57
57
  | `t` | `Translate` | — | 内置 | 覆盖文案(内置字典已含全部会话文案,中英) | Override copy (the built-in dictionary has all session copy, zh+en) |
58
58
 
@@ -78,7 +78,7 @@ if (view !== null) {
78
78
  openHostSession(child)
79
79
  },
80
80
  // 文件打开出口:给一个落点即可(元素或选择器),核心自带右侧分栏
81
- onOpenFile: (path, sid) => openFilePreview({ mount: '#page-row', path, sessionId: sid }),
81
+ onOpenFile: (path, sid, opts) => openFilePreview({ mount: '#page-row', path, sessionId: sid, line: opts?.line }),
82
82
  // 变更文件审阅:一行打开那次改动的 diff(面板 / 关闭钮全归核心)
83
83
  onOpenReview: (locator) => openSessionReview({ mount: '#page-row', locator }),
84
84
  archived, // 是否已归档
@@ -18,7 +18,10 @@ h(FlipText, { text: step })
18
18
  h(FlipText, { text: step, maxWidth: 220 })
19
19
 
20
20
  // 宽度三档:width 优先,其次 minWidth / maxWidth
21
- h(FlipText, { text: step, width: 200 })`
21
+ h(FlipText, { text: step, width: 200 })
22
+
23
+ // 常驻扫光:文字不变也在动(默认关;弹窗 / 抽屉底栏的 notice 默认开)
24
+ h(FlipText, { text: step, sweep: true })`
22
25
 
23
26
  const cardExample = `import { Card, Chip, StripCard } from 'dsh-plugin-core/ui'
24
27
 
@@ -650,6 +653,10 @@ const doc: DocTopic = {
650
653
  zh: '宽度三档同 Button / Chip:`width` 优先,`width` 未设时 `minWidth` / `maxWidth` 生效;三者都不传 = 随父容器(flex 子项自然伸缩,弹窗底栏就是这么用的)。要放进弹窗底栏左侧**不要自己包**,直接用 `Modal` / `ConfirmModal` / `AlertModal` / `SideSheet` 的 `notice`。',
651
654
  en: 'Widths follow Button / Chip: `width` wins; `minWidth` / `maxWidth` apply only when it is unset; none of the three means "follow the parent" (a flex child stretches naturally — that is how the dialog footer uses it). To put it on the left of a dialog footer, use the `notice` prop on `Modal` / `ConfirmModal` / `AlertModal` / `SideSheet` rather than wrapping it yourself.',
652
655
  },
656
+ {
657
+ zh: '`sweep` = 常驻扫光:一道斜光每 1.5s 从文字上扫过,扫到的字提亮,甚至文案一直不变也看得出「还在干活」。**默认关**;`Modal` / `SideSheet` 的 `notice` 默认打开(确认框与过程提示的场景)。光只落在字形上(`background-clip:text`),不会在底栏的空白区显出一条白光;`prefers-reduced-motion` 下与翻牌一起关掉。',
658
+ en: '`sweep` is a persistent sheen: a slanted highlight crosses the text every 1.5s, brightening the characters it passes, so an unchanged line still reads as "still working". **Off by default**; the `notice` prop on `Modal` / `SideSheet` turns it on (confirm dialogs and progress hints). The light is clipped to the glyphs (`background-clip:text`), so it never shows as a white bar across the empty part of a footer, and it is turned off together with the flip under `prefers-reduced-motion`.',
659
+ },
653
660
  ],
654
661
  api: [
655
662
  {
@@ -659,6 +666,7 @@ const doc: DocTopic = {
659
666
  { name: 'width', type: 'number | string', def: '—', desc: { zh: '固定宽(px 或任意 CSS 宽度);与 minWidth / maxWidth 互斥,一旦设定后两者自动失效', en: 'Fixed width (px or any CSS width); mutually exclusive with minWidth/maxWidth — once set, the other two are ignored' } },
660
667
  { name: 'minWidth', type: 'number | string', def: '—', desc: { zh: '最小宽:width 未设时生效', en: 'Minimum width: applies only when width is unset' } },
661
668
  { name: 'maxWidth', type: 'number | string', def: '—', desc: { zh: '最大宽:width 未设时生效,文字超出折叠成省略号,hover 以跑马灯展开', en: 'Maximum width: applies only when width is unset; overflowing text collapses to an ellipsis and marquees on hover' } },
669
+ { name: 'sweep', type: 'boolean', def: 'false', desc: { zh: '常驻扫光:每 1.5s 一道斜光扫过文字,文案不变也能看出在动。`Modal` / `SideSheet` 的 `notice` 默认传 true', en: 'Persistent sheen: a slanted highlight crosses the text every 1.5s so a static line still reads as active. The `notice` prop on `Modal` / `SideSheet` passes true by default' } },
662
670
  { name: 'className', type: 'string', def: '—', desc: { zh: '追加 class(只补字体 / 颜色这类皮肤,布局归组件自己)', en: 'Extra class (font / colour skin only; layout belongs to the component)' } },
663
671
  ],
664
672
  },
@@ -852,7 +860,7 @@ const doc: DocTopic = {
852
860
  { name: 'maxWidth', type: 'string', def: '按 size', desc: { zh: '任意 CSS 值,覆盖 size 的宽度上限;不要用 vw(遮罩已按右侧分栏让位)', en: 'Any CSS value, overriding the size’s max width; avoid vw (the mask already yielded to the right column)' } },
853
861
  { name: 'hideHeader', type: 'boolean', def: "'false'", desc: { zh: '不渲染头部(沉浸式)', en: 'Skip header (immersive)' } },
854
862
  { name: 'footer', type: 'ReactNode', def: '—', desc: { zh: '底栏(按钮区;上下内距相等、内容垂直居中)', en: 'Footer (button area; symmetric padding, vertically centred)' } },
855
- { name: 'notice', type: 'string', def: '—', desc: { zh: '底栏**左侧**的一行过程提示:换文案即翻牌,超长省略 + hover 跑马灯。不传 = 不占位、与以前完全一样;**传空字符串 = 清场**;footer 也可不给(它会自己撑起底栏)', en: 'One-line progress notice on the **left** of the footer: changing the string flips it, overflow elides and marquees on hover. Omitted = nothing at all (unchanged behaviour); **an empty string clears it**; `footer` may be omitted too — the notice alone brings the footer up' } },
863
+ { name: 'notice', type: 'string', def: '—', desc: { zh: '底栏**左侧**的一行过程提示:换文案即翻牌,**默认带常驻扫光**(不动也看得出在动),超长省略 + hover 跑马灯。不传 = 不占位、与以前完全一样;**传空字符串 = 清场**;footer 也可不给(它会自己撑起底栏)', en: 'One-line progress notice on the **left** of the footer: changing the string flips it, and it carries the **persistent sheen by default** (still reads as active when unchanged); overflow elides and marquees on hover. Omitted = nothing at all (unchanged behaviour); **an empty string clears it**; `footer` may be omitted too — the notice alone brings the footer up' } },
856
864
  { name: 'footerDivider', type: 'boolean', def: "'true'", desc: { zh: '底栏上面的那条分隔线;传 false 关掉(sm 确认框本来就没有)', en: 'The divider above the footer; pass false to drop it (sm dialogs never have one)' } },
857
865
  { name: 'className', type: 'string', def: '—', desc: { zh: '追加 class(只补布局 / 视觉修饰,别覆盖皮肤)', en: 'Extra class (layout / visual tweaks only; do not override the skin)' } },
858
866
  { name: 'children', type: 'ReactNode', def: '—', desc: { zh: '主体', en: 'Body' } },
@@ -935,7 +943,7 @@ const doc: DocTopic = {
935
943
  { name: 'width / minWidth / maxWidth', type: 'number / number / number', def: '460 / 320 / —', desc: { zh: '初始宽度 / 拖拽下限 / 拖拽上限(px;上限缺省 = 遮罩盒宽)', en: 'Initial width / resize floor / resize ceiling (px; ceiling defaults to the mask box)' } },
936
944
  { name: 'resizable', type: 'boolean', def: 'true', desc: { zh: '能否拖宽(关掉就没有拖拽条)', en: 'Whether it resizes by dragging (off = no handle)' } },
937
945
  { name: 'footer / children', type: 'ReactNode / ReactNode', def: '— / —', desc: { zh: '底栏 / 主体(主体是唯一滚动容器)', en: 'Footer / body (the body is the only scroll container)' } },
938
- { name: 'notice', type: 'string', def: '—', desc: { zh: '底栏**左侧**的一行过程提示(换文案即翻牌,超长省略 + hover 跑马灯)。不传 = 不占位;**传空字符串 = 清场**(抽屉常驻渲染,关掉再开时用它清上一轮)', en: 'One-line progress notice on the **left** of the footer (changing it flips; overflow elides and marquees on hover). Omitted = nothing at all; **an empty string clears it** (the drawer stays mounted, so this is how the previous round is wiped)' } },
946
+ { name: 'notice', type: 'string', def: '—', desc: { zh: '底栏**左侧**的一行过程提示(换文案即翻牌,**默认带常驻扫光**,超长省略 + hover 跑马灯)。不传 = 不占位;**传空字符串 = 清场**(抽屉常驻渲染,关掉再开时用它清上一轮)', en: 'One-line progress notice on the **left** of the footer (changing it flips; it carries the **persistent sheen by default**; overflow elides and marquees on hover). Omitted = nothing at all; **an empty string clears it** (the drawer stays mounted, so this is how the previous round is wiped)' } },
939
947
  { name: 'translate', type: 'Translate', def: '内置字典', desc: { zh: '覆盖关闭 / 拖拽条的文案', en: 'Override close / resize copy' } },
940
948
  ],
941
949
  },
package/docs/help/ui.md CHANGED
@@ -413,12 +413,17 @@ h(StripCard, {
413
413
 
414
414
  > EN: Widths follow Button / Chip: `width` wins; `minWidth` / `maxWidth` apply only when it is unset; none of the three means "follow the parent" (a flex child stretches naturally — that is how the dialog footer uses it). To put it on the left of a dialog footer, use the `notice` prop on `Modal` / `ConfirmModal` / `AlertModal` / `SideSheet` rather than wrapping it yourself.
415
415
 
416
+ `sweep` = 常驻扫光:一道斜光每 1.5s 从文字上扫过,扫到的字提亮,甚至文案一直不变也看得出「还在干活」。**默认关**;`Modal` / `SideSheet` 的 `notice` 默认打开(确认框与过程提示的场景)。光只落在字形上(`background-clip:text`),不会在底栏的空白区显出一条白光;`prefers-reduced-motion` 下与翻牌一起关掉。
417
+
418
+ > EN: `sweep` is a persistent sheen: a slanted highlight crosses the text every 1.5s, brightening the characters it passes, so an unchanged line still reads as "still working". **Off by default**; the `notice` prop on `Modal` / `SideSheet` turns it on (confirm dialogs and progress hints). The light is clipped to the glyphs (`background-clip:text`), so it never shows as a white bar across the empty part of a footer, and it is turned off together with the flip under `prefers-reduced-motion`.
419
+
416
420
  | 参数 | 类型 | 默认 | 说明(zh) | 说明(en) |
417
421
  |---|---|---|---|---|
418
422
  | `text` | `string` | —(必填) | 当前文本;变了就翻一次牌。**空串 = 没有文案**:不渲染、不占位(同一块地方复用靠它清场);清空后再给值是直接出现,不从下方翻上来 | Current text; a change flips it once. **An empty string means no line at all**: nothing rendered, no space taken (that is how a reused spot is cleared); the next value appears outright instead of sliding in |
419
423
  | `width` | `number \| string` | — | 固定宽(px 或任意 CSS 宽度);与 minWidth / maxWidth 互斥,一旦设定后两者自动失效 | Fixed width (px or any CSS width); mutually exclusive with minWidth/maxWidth — once set, the other two are ignored |
420
424
  | `minWidth` | `number \| string` | — | 最小宽:width 未设时生效 | Minimum width: applies only when width is unset |
421
425
  | `maxWidth` | `number \| string` | — | 最大宽:width 未设时生效,文字超出折叠成省略号,hover 以跑马灯展开 | Maximum width: applies only when width is unset; overflowing text collapses to an ellipsis and marquees on hover |
426
+ | `sweep` | `boolean` | false | 常驻扫光:每 1.5s 一道斜光扫过文字,文案不变也能看出在动。`Modal` / `SideSheet` 的 `notice` 默认传 true | Persistent sheen: a slanted highlight crosses the text every 1.5s so a static line still reads as active. The `notice` prop on `Modal` / `SideSheet` passes true by default |
422
427
  | `className` | `string` | — | 追加 class(只补字体 / 颜色这类皮肤,布局归组件自己) | Extra class (font / colour skin only; layout belongs to the component) |
423
428
 
424
429
  ```tsx id="ui-flip-text-example"
@@ -433,6 +438,9 @@ h(FlipText, { text: step, maxWidth: 220 })
433
438
 
434
439
  // 宽度三档:width 优先,其次 minWidth / maxWidth
435
440
  h(FlipText, { text: step, width: 200 })
441
+
442
+ // 常驻扫光:文字不变也在动(默认关;弹窗 / 抽屉底栏的 notice 默认开)
443
+ h(FlipText, { text: step, sweep: true })
436
444
  ```
437
445
 
438
446
  ## Table(表格)
@@ -719,7 +727,7 @@ Modal 是自绘 overlay,不走 react-dom portal。
719
727
  | `maxWidth` | `string` | 按 size | 任意 CSS 值,覆盖 size 的宽度上限;不要用 vw(遮罩已按右侧分栏让位) | Any CSS value, overriding the size’s max width; avoid vw (the mask already yielded to the right column) |
720
728
  | `hideHeader` | `boolean` | 'false' | 不渲染头部(沉浸式) | Skip header (immersive) |
721
729
  | `footer` | `ReactNode` | — | 底栏(按钮区;上下内距相等、内容垂直居中) | Footer (button area; symmetric padding, vertically centred) |
722
- | `notice` | `string` | — | 底栏**左侧**的一行过程提示:换文案即翻牌,超长省略 + hover 跑马灯。不传 = 不占位、与以前完全一样;**传空字符串 = 清场**;footer 也可不给(它会自己撑起底栏) | One-line progress notice on the **left** of the footer: changing the string flips it, overflow elides and marquees on hover. Omitted = nothing at all (unchanged behaviour); **an empty string clears it**; `footer` may be omitted too — the notice alone brings the footer up |
730
+ | `notice` | `string` | — | 底栏**左侧**的一行过程提示:换文案即翻牌,**默认带常驻扫光**(不动也看得出在动),超长省略 + hover 跑马灯。不传 = 不占位、与以前完全一样;**传空字符串 = 清场**;footer 也可不给(它会自己撑起底栏) | One-line progress notice on the **left** of the footer: changing the string flips it, and it carries the **persistent sheen by default** (still reads as active when unchanged); overflow elides and marquees on hover. Omitted = nothing at all (unchanged behaviour); **an empty string clears it**; `footer` may be omitted too — the notice alone brings the footer up |
723
731
  | `footerDivider` | `boolean` | 'true' | 底栏上面的那条分隔线;传 false 关掉(sm 确认框本来就没有) | The divider above the footer; pass false to drop it (sm dialogs never have one) |
724
732
  | `className` | `string` | — | 追加 class(只补布局 / 视觉修饰,别覆盖皮肤) | Extra class (layout / visual tweaks only; do not override the skin) |
725
733
  | `children` | `ReactNode` | — | 主体 | Body |
@@ -790,7 +798,7 @@ Modal 是自绘 overlay,不走 react-dom portal。
790
798
  | `width / minWidth / maxWidth` | `number / number / number` | 460 / 320 / — | 初始宽度 / 拖拽下限 / 拖拽上限(px;上限缺省 = 遮罩盒宽) | Initial width / resize floor / resize ceiling (px; ceiling defaults to the mask box) |
791
799
  | `resizable` | `boolean` | true | 能否拖宽(关掉就没有拖拽条) | Whether it resizes by dragging (off = no handle) |
792
800
  | `footer / children` | `ReactNode / ReactNode` | — / — | 底栏 / 主体(主体是唯一滚动容器) | Footer / body (the body is the only scroll container) |
793
- | `notice` | `string` | — | 底栏**左侧**的一行过程提示(换文案即翻牌,超长省略 + hover 跑马灯)。不传 = 不占位;**传空字符串 = 清场**(抽屉常驻渲染,关掉再开时用它清上一轮) | One-line progress notice on the **left** of the footer (changing it flips; overflow elides and marquees on hover). Omitted = nothing at all; **an empty string clears it** (the drawer stays mounted, so this is how the previous round is wiped) |
801
+ | `notice` | `string` | — | 底栏**左侧**的一行过程提示(换文案即翻牌,**默认带常驻扫光**,超长省略 + hover 跑马灯)。不传 = 不占位;**传空字符串 = 清场**(抽屉常驻渲染,关掉再开时用它清上一轮) | One-line progress notice on the **left** of the footer (changing it flips; it carries the **persistent sheen by default**; overflow elides and marquees on hover). Omitted = nothing at all; **an empty string clears it** (the drawer stays mounted, so this is how the previous round is wiped) |
794
802
  | `translate` | `Translate` | 内置字典 | 覆盖关闭 / 拖拽条的文案 | Override close / resize copy |
795
803
 
796
804
  ```tsx id="ui-sidesheet-example"
@@ -160,7 +160,7 @@ dsh-plugin-core/
160
160
  类并补 `display:flex`,关闭时还原;子项规则只让第一个非预览子项伸展,不要传 `document.body`;
161
161
  - 依据:插件沙箱里没有 `fs`,读字节唯一通道是 ctx 注入的服务对象 ⇒ 服务只能「接线一次」交进来,
162
162
  不能每次调用各传一遍(这是权限边界,不是能力缺口)。
163
- - **关闭钮归面板壳**(2026-11-04 用户拍板):`openFilePreview` 的 dock 一律用壳的浮动 ✕(`closeButton: 'floating'`,文件 / content 两种模式位置一致),`FileBrowser` 不再接 `onClose`(它的 `onClose` 只留给无壳的 picker 弹窗);内容组件(`ChangedFileDiff` 等)不画关,只在右上让 28px。根因:关在内容上曾是可选 prop,漏传即整块面板无关闭钮(dsh-task-ledger 报的缺陷)。`content` 与 `path` 同传 ⇒ warn 并忽略 `path`(`handle.path` 不再回报未生效的路径)。
163
+ - **关钮可退让**(2026-11-04 用户拍板,同日二次修正):`openFilePreview` 默认**不在面板层画关钮**(`closeButton: 'none'`)—— 文件模式用 `FileBrowser` 面包屑行尾那枚,`content` 模式由内容自己画;新增 `OpenFilePreviewOptions.closeButton`,传 `'floating'` 才由壳在右上出一枚。审阅面板(`openSessionReview`)的 ✕ 画在审阅头行尾,与换行钮同排。理由:无头面板画不画、画在哪归调用方,dock 不强制浮动。`content` 与 `path` 同传 ⇒ warn 并忽略 `path`(`handle.path` 不再回报未生效的路径)。
164
164
  - 控件纪律(2026-10-21):核心有的控件一律核心(`Button` ghost 面包屑 / `IconButton` / `Segmented` / `MarqueeText` / `PanelDock`);核心没有的那一项(▾ 选层浮层)严格按兄弟项目度量照抄
165
165
  - 选工作区(`WorkspacePicker`)在 `ui`,不在这里;`FilePicker` 已删(2026-10-20)
166
166
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-core",
3
- "version": "0.29.0",
3
+ "version": "0.29.2",
4
4
  "packageManager": "pnpm@12.3.4",
5
5
  "description": "Shared base for DSH plugins: design tokens, UI controls, utils, workspace explorer, session preview",
6
6
  "keywords": [