@dsh-workbench/shared 0.3.3 → 0.3.5

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (193) hide show
  1. package/dist/api/blocker-table.d.ts +13 -0
  2. package/dist/api/blocker-table.js +13 -0
  3. package/dist/api/blocker.d.ts +6 -0
  4. package/dist/api/context-ref.d.ts +31 -0
  5. package/dist/api/context-ref.js +28 -0
  6. package/dist/api/dsh-client.d.ts +99 -0
  7. package/dist/api/dsh.d.ts +55 -3
  8. package/dist/api/facts.d.ts +42 -0
  9. package/dist/api/facts.js +33 -0
  10. package/dist/api/host-write.d.ts +19 -1
  11. package/dist/api/host-write.js +12 -0
  12. package/dist/api/long-task.d.ts +36 -0
  13. package/dist/api/long-task.js +27 -0
  14. package/dist/api/unit-board.d.ts +25 -0
  15. package/dist/api/unit-board.js +16 -0
  16. package/dist/api/unit-declaration.d.ts +36 -0
  17. package/dist/api/unit-declaration.js +24 -0
  18. package/dist/api/unit.d.ts +32 -0
  19. package/dist/api/unit.js +5 -0
  20. package/dist/api/workbench-client.d.ts +25 -0
  21. package/dist/api/workbench-client.js +13 -0
  22. package/dist/api/workbench-contract.d.ts +12 -0
  23. package/dist/components/Activity.d.ts +33 -0
  24. package/dist/components/Activity.js +24 -0
  25. package/dist/components/ApprovalDetail.d.ts +15 -1
  26. package/dist/components/ApprovalDetail.js +13 -1
  27. package/dist/components/ApprovalPlan.d.ts +14 -0
  28. package/dist/components/ApprovalPlan.js +8 -0
  29. package/dist/components/Card.d.ts +13 -0
  30. package/dist/components/Card.js +10 -0
  31. package/dist/components/CardAnchor.d.ts +14 -0
  32. package/dist/components/CardAnchor.js +11 -0
  33. package/dist/components/CardBoundary.d.ts +20 -0
  34. package/dist/components/CardBoundary.js +14 -0
  35. package/dist/components/CardHead.d.ts +13 -0
  36. package/dist/components/CardHead.js +10 -0
  37. package/dist/components/ChoiceGroup.d.ts +24 -0
  38. package/dist/components/ChoiceGroup.js +12 -0
  39. package/dist/components/Field.d.ts +10 -0
  40. package/dist/components/Field.js +7 -0
  41. package/dist/components/Form.css +6 -0
  42. package/dist/components/Form.d.ts +20 -0
  43. package/dist/components/Form.js +58 -0
  44. package/dist/components/FormActions.d.ts +14 -0
  45. package/dist/components/FormActions.js +5 -0
  46. package/dist/components/FormNodes.d.ts +103 -0
  47. package/dist/components/FormNodes.js +153 -0
  48. package/dist/components/FormRows.d.ts +20 -0
  49. package/dist/components/FormRows.js +14 -0
  50. package/dist/components/FormTypes.d.ts +59 -0
  51. package/dist/components/FormTypes.js +1 -0
  52. package/dist/components/Link.d.ts +12 -0
  53. package/dist/components/Link.js +9 -0
  54. package/dist/components/LongTaskRow.d.ts +19 -0
  55. package/dist/components/LongTaskRow.js +13 -0
  56. package/dist/components/Note.d.ts +11 -0
  57. package/dist/components/Note.js +8 -0
  58. package/dist/components/ResourceStates.d.ts +25 -0
  59. package/dist/components/ResourceStates.js +22 -0
  60. package/dist/components/Rows.d.ts +17 -0
  61. package/dist/components/Rows.js +11 -0
  62. package/dist/components/SaveBar.d.ts +12 -0
  63. package/dist/components/SaveBar.js +9 -0
  64. package/dist/components/SelectField.d.ts +26 -0
  65. package/dist/components/SelectField.js +22 -0
  66. package/dist/components/SelectFieldNodes.d.ts +24 -0
  67. package/dist/components/SelectFieldNodes.js +24 -0
  68. package/dist/components/SettingsSection.d.ts +11 -0
  69. package/dist/components/SettingsSection.js +8 -0
  70. package/dist/components/Spinner.d.ts +9 -0
  71. package/dist/components/Spinner.js +6 -0
  72. package/dist/components/Stack.d.ts +8 -0
  73. package/dist/components/Stack.js +5 -0
  74. package/dist/components/StateBlock.d.ts +13 -0
  75. package/dist/components/StateBlock.js +10 -0
  76. package/dist/components/TextField.d.ts +8 -0
  77. package/dist/components/TextField.js +5 -0
  78. package/dist/components/board-module.d.ts +30 -0
  79. package/dist/components/icons.d.ts +38 -0
  80. package/dist/components/icons.js +35 -0
  81. package/dist/config/constants.d.ts +45 -0
  82. package/dist/config/constants.js +45 -0
  83. package/dist/config/storage.d.ts +4 -0
  84. package/dist/config/storage.js +4 -0
  85. package/dist/host/browser-read.d.ts +1 -0
  86. package/dist/host/browser-read.js +1 -0
  87. package/dist/host/http.d.ts +3 -0
  88. package/dist/host/http.js +10 -1
  89. package/dist/host/tool-approval.d.ts +5 -0
  90. package/dist/host/tool-approval.js +11 -5
  91. package/dist/host/unit-face.d.ts +5 -1
  92. package/dist/host.d.ts +1 -0
  93. package/dist/host.js +1 -0
  94. package/dist/i18n/en.d.ts +1 -0
  95. package/dist/i18n/en.js +22 -8
  96. package/dist/i18n/index.d.ts +25 -9
  97. package/dist/i18n/index.js +1 -0
  98. package/dist/i18n/merge.d.ts +5 -0
  99. package/dist/i18n/merge.js +2 -0
  100. package/dist/i18n/zh.d.ts +23 -8
  101. package/dist/i18n/zh.js +22 -8
  102. package/dist/index.d.ts +7 -2
  103. package/dist/index.js +7 -2
  104. package/dist/lib/blocker-action.d.ts +22 -0
  105. package/dist/lib/blocker-action.js +13 -0
  106. package/dist/lib/board-fact.d.ts +27 -0
  107. package/dist/lib/board-fact.js +18 -0
  108. package/dist/lib/busy.d.ts +10 -0
  109. package/dist/lib/busy.js +7 -0
  110. package/dist/lib/diy-debug.d.ts +42 -2
  111. package/dist/lib/diy-debug.js +19 -1
  112. package/dist/lib/diy-record.d.ts +98 -0
  113. package/dist/lib/diy-record.js +96 -0
  114. package/dist/lib/form-problem.d.ts +10 -0
  115. package/dist/lib/form-problem.js +10 -0
  116. package/dist/lib/panel-state.d.ts +60 -0
  117. package/dist/lib/panel-state.js +30 -0
  118. package/dist/lib/react.d.ts +39 -0
  119. package/dist/lib/react.js +36 -0
  120. package/dist/lib/text.d.ts +22 -0
  121. package/dist/lib/text.js +22 -0
  122. package/dist/lib/ui.d.ts +50 -0
  123. package/dist/lib/ui.js +41 -0
  124. package/package.json +1 -1
  125. package/src/api/blocker-table.ts +13 -0
  126. package/src/api/blocker.ts +6 -0
  127. package/src/api/context-ref.ts +31 -0
  128. package/src/api/dsh-client.ts +99 -0
  129. package/src/api/dsh.ts +56 -1
  130. package/src/api/facts.ts +42 -0
  131. package/src/api/host-write.ts +20 -1
  132. package/src/api/long-task.ts +36 -0
  133. package/src/api/unit-board.ts +25 -0
  134. package/src/api/unit-declaration.ts +36 -0
  135. package/src/api/unit.ts +32 -0
  136. package/src/api/workbench-client.ts +25 -0
  137. package/src/api/workbench-contract.ts +12 -0
  138. package/src/components/Activity.tsx +33 -0
  139. package/src/components/ApprovalDetail.tsx +17 -1
  140. package/src/components/ApprovalPlan.tsx +14 -0
  141. package/src/components/Card.tsx +13 -0
  142. package/src/components/CardAnchor.tsx +14 -0
  143. package/src/components/CardBoundary.tsx +20 -0
  144. package/src/components/CardHead.tsx +13 -0
  145. package/src/components/ChoiceGroup.tsx +24 -0
  146. package/src/components/Field.tsx +10 -0
  147. package/src/components/Form.css +6 -0
  148. package/src/components/Form.test.tsx +475 -0
  149. package/src/components/Form.tsx +78 -0
  150. package/src/components/FormActions.tsx +14 -0
  151. package/src/components/FormNodes.tsx +193 -0
  152. package/src/components/FormRows.tsx +20 -0
  153. package/src/components/FormTypes.ts +65 -0
  154. package/src/components/FormWiring.test.tsx +88 -0
  155. package/src/components/Link.tsx +12 -0
  156. package/src/components/LongTaskRow.tsx +19 -0
  157. package/src/components/Note.tsx +11 -0
  158. package/src/components/ResourceStates.tsx +25 -0
  159. package/src/components/Rows.tsx +17 -0
  160. package/src/components/SaveBar.tsx +12 -0
  161. package/src/components/SelectField.tsx +26 -0
  162. package/src/components/SelectFieldNodes.tsx +24 -0
  163. package/src/components/SettingsSection.tsx +11 -0
  164. package/src/components/Spinner.tsx +9 -0
  165. package/src/components/Stack.tsx +8 -0
  166. package/src/components/StateBlock.tsx +13 -0
  167. package/src/components/TextField.tsx +8 -0
  168. package/src/components/board-module.ts +30 -0
  169. package/src/components/icons.tsx +38 -0
  170. package/src/config/constants.ts +48 -0
  171. package/src/config/storage.ts +4 -0
  172. package/src/host/browser-read.ts +1 -0
  173. package/src/host/http.ts +15 -1
  174. package/src/host/tool-approval.ts +18 -4
  175. package/src/host/unit-face.ts +6 -1
  176. package/src/host.ts +1 -0
  177. package/src/i18n/en.ts +22 -8
  178. package/src/i18n/index.ts +3 -0
  179. package/src/i18n/merge.ts +5 -0
  180. package/src/i18n/zh.ts +23 -8
  181. package/src/index.ts +7 -2
  182. package/src/lib/blocker-action.ts +22 -0
  183. package/src/lib/board-fact.ts +27 -0
  184. package/src/lib/busy.ts +10 -0
  185. package/src/lib/diy-debug.test.ts +2 -1
  186. package/src/lib/diy-debug.ts +44 -3
  187. package/src/lib/diy-record.test.ts +171 -0
  188. package/src/lib/diy-record.ts +185 -0
  189. package/src/lib/form-problem.ts +10 -0
  190. package/src/lib/panel-state.ts +60 -0
  191. package/src/lib/react.ts +39 -0
  192. package/src/lib/text.ts +22 -0
  193. package/src/lib/ui.ts +50 -0
@@ -7,9 +7,19 @@ const FOCUSABLE_SELECTOR = [
7
7
  'a[href]',
8
8
  '[tabindex]:not([tabindex="-1"])',
9
9
  ].join(', ');
10
+ /**
11
+ * 找 `root` 里第一个带 `[data-wb-problem]` 标记的元素 —— 表单校验失败时,调用方拿它定位「哪一格错了」。
12
+ * @param root - 要搜的范围(表单容器,或 document)。
13
+ * @returns 第一个出问题的元素;一个标记都没有就给 `null`。
14
+ */
10
15
  export function firstProblemOf(root) {
11
16
  return root.querySelector(PROBLEM_SELECTOR);
12
17
  }
18
+ /**
19
+ * 把第一个出问题的元素滚进视野、并把焦点交给它里面第一个可聚焦控件(输入框 / 按钮 / 链接等)。
20
+ * @param root - 要搜的范围。
21
+ * @returns 找到并滚过给 `true`;没有问题给 `false`(不滚、不抢焦点);元素不可滚或没有可聚焦控件也不报错,只是少做一步。
22
+ */
13
23
  export function revealFirstProblem(root) {
14
24
  const problem = firstProblemOf(root);
15
25
  if (problem === null)
@@ -1,8 +1,17 @@
1
+ /**
2
+ * 一张面板卡片取数走到哪一步:`idle` 还没取、`loading` 取着、`ready` 拿到了、`error` 失败了。
3
+ */
1
4
  export type PanelCardPhase = 'idle' | 'loading' | 'ready' | 'error';
5
+ /**
6
+ * 一次取数失败:机器码 + 给人看的一句话。
7
+ */
2
8
  export interface PanelCardError {
3
9
  readonly code: string;
4
10
  readonly message: string;
5
11
  }
12
+ /**
13
+ * 一张卡片最近一次取数的读数:哪个单元、哪条路由、什么相位、什么时候、失败原因。
14
+ */
6
15
  export interface PanelCardRead {
7
16
  readonly unit: string;
8
17
  readonly route: string;
@@ -10,12 +19,18 @@ export interface PanelCardRead {
10
19
  readonly at: number;
11
20
  readonly error?: PanelCardError;
12
21
  }
22
+ /**
23
+ * 一张卡片崩了的读数:单元、原因、时间,以及同一原因连着崩了几次(窗口见 `PANEL_STATE_CRASH_WINDOW_MS`)。
24
+ */
13
25
  export interface PanelCardCrash {
14
26
  readonly unit: string;
15
27
  readonly reason: string;
16
28
  readonly at: number;
17
29
  readonly count: number;
18
30
  }
31
+ /**
32
+ * 一个单元此刻的读数:它的卡片、最近一次读成的时间(`okAt`,没成过给 `null`),以及崩了的那一条。
33
+ */
19
34
  export interface PanelUnitReport {
20
35
  readonly unit: string;
21
36
  readonly at: number;
@@ -23,31 +38,61 @@ export interface PanelUnitReport {
23
38
  readonly cards: readonly PanelCardRead[];
24
39
  readonly crash?: PanelCardCrash;
25
40
  }
41
+ /**
42
+ * 一个工作区此刻的全量读数(面板按工作区存):所有单元的读数 + 整块最近动过的时间。
43
+ */
26
44
  export interface PanelWorkspaceReport {
27
45
  readonly workspace: string | null;
28
46
  readonly at: number;
29
47
  readonly units: readonly PanelUnitReport[];
30
48
  }
49
+ /**
50
+ * 客户端往上报的一批卡片读数(`POST` 请求体的形状)。
51
+ */
31
52
  export interface PanelCardsReport {
32
53
  readonly workspace: string;
33
54
  readonly cards: readonly PanelCardRead[];
34
55
  }
56
+ /**
57
+ * 客户端往上报的一次卡片崩溃(`POST` 请求体的形状)。
58
+ */
35
59
  export interface PanelCrashReport {
36
60
  readonly workspace: string;
37
61
  readonly unit: string;
38
62
  readonly reason: string;
39
63
  readonly at: number;
40
64
  }
65
+ /**
66
+ * 面板运行态的留存口(宿主持有一份):收卡片读数与崩溃,按工作区取快照。
67
+ */
41
68
  export interface PanelStateStore {
42
69
  cards(report: PanelCardsReport): void;
43
70
  crash(report: PanelCrashReport): void;
44
71
  snapshot(workspace: string | null): PanelWorkspaceReport;
45
72
  }
73
+ /**
74
+ * 留存的工作区上限(LRU,满 64 个丢最旧的)。
75
+ */
46
76
  export declare const PANEL_STATE_MAX_UNITS = 64;
77
+ /**
78
+ * 每个单元留存的卡片上限(按路由 LRU,满 12 张丢最旧的)。
79
+ */
47
80
  export declare const PANEL_STATE_MAX_CARDS = 12;
81
+ /**
82
+ * 一次上报最多读入的卡片条数(32 条之后的丢掉)。
83
+ */
48
84
  export declare const PANEL_STATE_MAX_REPORTS = 32;
85
+ /**
86
+ * 同一原因的连续崩溃算进同一次计数的窗口(10 分钟,毫秒)。
87
+ */
49
88
  export declare const PANEL_STATE_CRASH_WINDOW_MS = 600000;
89
+ /**
90
+ * 上报文本字段的截断长度(2000 字符;workspace / reason / message 共用)。
91
+ */
50
92
  export declare const PANEL_STATE_MAX_TEXT = 2000;
93
+ /**
94
+ * 上报解析的结果:`ok: true` 带收好的值,`ok: false` 带一句中文原因(直接回给上报方)。
95
+ */
51
96
  export type PanelParse<T> = {
52
97
  readonly ok: true;
53
98
  readonly value: T;
@@ -55,6 +100,21 @@ export type PanelParse<T> = {
55
100
  readonly ok: false;
56
101
  readonly message: string;
57
102
  };
103
+ /**
104
+ * 收一批卡片读数上报。
105
+ * @param payload - `POST` 的请求体。
106
+ * @returns 至少收到一条能认的卡片才 `ok: true`;请求体不是对象、`workspace` 为空、`cards` 不是数组,
107
+ * 或一条都认不出(`unit` / `route` / `phase` / `at` 缺一)时 `ok: false` + 中文原因;认不出的单条静默跳过。
108
+ */
58
109
  export declare function parseCardsReport(payload: unknown): PanelParse<PanelCardsReport>;
110
+ /**
111
+ * 收一次卡片崩溃上报。
112
+ * @param payload - `POST` 的请求体。
113
+ * @returns `workspace` / `unit` / `reason` 都不能空,缺一给 `ok: false` + 中文原因;`at` 缺省或不合法时取当前时间。
114
+ */
59
115
  export declare function parseCrashReport(payload: unknown): PanelParse<PanelCrashReport>;
116
+ /**
117
+ * 造一份面板运行态的留存(进程内存、按工作区分桶,带上面那几条上限的 LRU)。
118
+ * @returns 留存口:`cards` / `crash` 收上报,`snapshot(workspace)` 取全量;没存过的工作区(含 `null`)给一份空的。
119
+ */
60
120
  export declare function createPanelStateStore(): PanelStateStore;
@@ -1,7 +1,22 @@
1
+ /**
2
+ * 留存的工作区上限(LRU,满 64 个丢最旧的)。
3
+ */
1
4
  export const PANEL_STATE_MAX_UNITS = 64;
5
+ /**
6
+ * 每个单元留存的卡片上限(按路由 LRU,满 12 张丢最旧的)。
7
+ */
2
8
  export const PANEL_STATE_MAX_CARDS = 12;
9
+ /**
10
+ * 一次上报最多读入的卡片条数(32 条之后的丢掉)。
11
+ */
3
12
  export const PANEL_STATE_MAX_REPORTS = 32;
13
+ /**
14
+ * 同一原因的连续崩溃算进同一次计数的窗口(10 分钟,毫秒)。
15
+ */
4
16
  export const PANEL_STATE_CRASH_WINDOW_MS = 600000;
17
+ /**
18
+ * 上报文本字段的截断长度(2000 字符;workspace / reason / message 共用)。
19
+ */
5
20
  export const PANEL_STATE_MAX_TEXT = 2000;
6
21
  const PHASES = ['idle', 'loading', 'ready', 'error'];
7
22
  function asRecord(value) {
@@ -30,6 +45,12 @@ function errorOf(raw) {
30
45
  return undefined;
31
46
  return { code: code === '' ? 'unknown' : code, message };
32
47
  }
48
+ /**
49
+ * 收一批卡片读数上报。
50
+ * @param payload - `POST` 的请求体。
51
+ * @returns 至少收到一条能认的卡片才 `ok: true`;请求体不是对象、`workspace` 为空、`cards` 不是数组,
52
+ * 或一条都认不出(`unit` / `route` / `phase` / `at` 缺一)时 `ok: false` + 中文原因;认不出的单条静默跳过。
53
+ */
33
54
  export function parseCardsReport(payload) {
34
55
  const record = asRecord(payload);
35
56
  if (record === undefined)
@@ -58,6 +79,11 @@ export function parseCardsReport(payload) {
58
79
  return { ok: false, message: 'cards 里没有一条能认的(unit / route / phase / at 都要有)' };
59
80
  return { ok: true, value: { workspace, cards } };
60
81
  }
82
+ /**
83
+ * 收一次卡片崩溃上报。
84
+ * @param payload - `POST` 的请求体。
85
+ * @returns `workspace` / `unit` / `reason` 都不能空,缺一给 `ok: false` + 中文原因;`at` 缺省或不合法时取当前时间。
86
+ */
61
87
  export function parseCrashReport(payload) {
62
88
  const record = asRecord(payload);
63
89
  if (record === undefined)
@@ -73,6 +99,10 @@ export function parseCrashReport(payload) {
73
99
  return { ok: false, message: 'reason 不能是空的(没有原因的崩溃没法查)' };
74
100
  return { ok: true, value: { workspace, unit, reason, at: atOf(record['at']) ?? Date.now() } };
75
101
  }
102
+ /**
103
+ * 造一份面板运行态的留存(进程内存、按工作区分桶,带上面那几条上限的 LRU)。
104
+ * @returns 留存口:`cards` / `crash` 收上报,`snapshot(workspace)` 取全量;没存过的工作区(含 `null`)给一份空的。
105
+ */
76
106
  export function createPanelStateStore() {
77
107
  const workspaces = new Map();
78
108
  function trim(entry, max) {
@@ -1,14 +1,53 @@
1
1
  import type * as ReactTypes from 'react';
2
+ /**
3
+ * React 的 `createElement` 换个短名字再导出 —— 这套代码用 `h(...)` 写元素。
4
+ */
2
5
  export declare const h: typeof ReactTypes.createElement;
6
+ /**
7
+ * React 的 `Fragment` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
8
+ */
3
9
  export declare const Fragment: ReactTypes.ExoticComponent<ReactTypes.FragmentProps>;
10
+ /**
11
+ * React 的 `Component` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
12
+ */
4
13
  export declare const Component: typeof ReactTypes.Component;
14
+ /**
15
+ * React 的 `createContext` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
16
+ */
5
17
  export declare const createContext: typeof ReactTypes.createContext;
18
+ /**
19
+ * React 的 `useContext` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
20
+ */
6
21
  export declare const useContext: typeof ReactTypes.useContext;
22
+ /**
23
+ * React 的 `useState` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
24
+ */
7
25
  export declare const useState: typeof ReactTypes.useState;
26
+ /**
27
+ * React 的 `useEffect` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
28
+ */
8
29
  export declare const useEffect: typeof ReactTypes.useEffect;
30
+ /**
31
+ * React 的 `useLayoutEffect` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
32
+ */
9
33
  export declare const useLayoutEffect: typeof ReactTypes.useLayoutEffect;
34
+ /**
35
+ * React 的 `useMemo` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
36
+ */
10
37
  export declare const useMemo: typeof ReactTypes.useMemo;
38
+ /**
39
+ * React 的 `useCallback` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
40
+ */
11
41
  export declare const useCallback: typeof ReactTypes.useCallback;
42
+ /**
43
+ * React 的 `useRef` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
44
+ */
12
45
  export declare const useRef: typeof ReactTypes.useRef;
46
+ /**
47
+ * React 的 `useSyncExternalStore` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
48
+ */
13
49
  export declare const useSyncExternalStore: typeof ReactTypes.useSyncExternalStore;
50
+ /**
51
+ * 从 react 借来的三个类型(`ReactNode` / `CSSProperties` / `ChangeEvent`)—— 模块从这里拿,就不必自己直接依赖 react。
52
+ */
14
53
  export type { ReactNode, CSSProperties, ChangeEvent } from 'react';
package/dist/lib/react.js CHANGED
@@ -1,13 +1,49 @@
1
1
  const react = require('react');
2
+ /**
3
+ * React 的 `createElement` 换个短名字再导出 —— 这套代码用 `h(...)` 写元素。
4
+ */
2
5
  export const h = react.createElement;
6
+ /**
7
+ * React 的 `Fragment` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
8
+ */
3
9
  export const Fragment = react.Fragment;
10
+ /**
11
+ * React 的 `Component` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
12
+ */
4
13
  export const Component = react.Component;
14
+ /**
15
+ * React 的 `createContext` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
16
+ */
5
17
  export const createContext = react.createContext;
18
+ /**
19
+ * React 的 `useContext` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
20
+ */
6
21
  export const useContext = react.useContext;
22
+ /**
23
+ * React 的 `useState` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
24
+ */
7
25
  export const useState = react.useState;
26
+ /**
27
+ * React 的 `useEffect` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
28
+ */
8
29
  export const useEffect = react.useEffect;
30
+ /**
31
+ * React 的 `useLayoutEffect` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
32
+ */
9
33
  export const useLayoutEffect = react.useLayoutEffect;
34
+ /**
35
+ * React 的 `useMemo` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
36
+ */
10
37
  export const useMemo = react.useMemo;
38
+ /**
39
+ * React 的 `useCallback` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
40
+ */
11
41
  export const useCallback = react.useCallback;
42
+ /**
43
+ * React 的 `useRef` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
44
+ */
12
45
  export const useRef = react.useRef;
46
+ /**
47
+ * React 的 `useSyncExternalStore` 原样再导出 —— 模块从这里拿,就不必自己直接依赖 react。
48
+ */
13
49
  export const useSyncExternalStore = react.useSyncExternalStore;
@@ -1,4 +1,26 @@
1
+ /**
2
+ * 把一个值裹成 shell 里的单引号字面量(值里的单引号一并转义),保证元字符按字面量传递。
3
+ * @param value - 任意值;不是字符串的先 `String()`。
4
+ * @returns 可安全拼进 `/bin/sh -c` 的一段文本 —— 含空格 / `$()` / `;` / 通配符的值都不会被当成语法。
5
+ */
1
6
  export declare function quoteShellArg(value: unknown): string;
7
+ /**
8
+ * 取一段文本的末尾若干行(如命令输出),顺带去掉首尾空白。
9
+ * @param text - 原文;`null` / `undefined` 当空串。
10
+ * @param lines - 最多留几行,默认 6;0 给全篇、负数从开头砍掉那么多行(都是 `slice` 语义),调用方传正数。
11
+ * @returns 末尾若干行、去首尾空行后的文本;原文行数不够就全给,空输入给空串。
12
+ */
2
13
  export declare function tailLines(text: string | null | undefined, lines?: number): string;
14
+ /**
15
+ * 宽松解析一段前后可能夹着人话的 JSON:整段不通就从行首的 `[` / `{` 起、截到最后一个闭合符再试。
16
+ * @param text - 原文;`null` / `undefined` 当空串。
17
+ * @returns 解析出的值(对象 / 数组 / 标量都原样);空输入或怎么都解析不出给 `undefined`,调用方据此报「输出认不出」。
18
+ */
3
19
  export declare function parseJsonLoose(text: string | null | undefined): unknown;
20
+ /**
21
+ * 把一段 Markdown 压成一行纯文本摘要:图片剥掉、链接只留可见文字、标记符号压成空格,超长按字符截断。
22
+ * @param text - 原文;`null` / `undefined` 当空串。
23
+ * @param limit - 摘要最多多少字符,默认 80。
24
+ * @returns 单行摘要;没超限原样给,超了截到 `limit` 并补省略号(总长 `limit + 1`;第 `limit` 个字符是空白时会被 `trimEnd` 去掉、略短),空输入给空串。
25
+ */
4
26
  export declare function excerpt(text: string | null | undefined, limit?: number): string;
package/dist/lib/text.js CHANGED
@@ -1,6 +1,17 @@
1
+ /**
2
+ * 把一个值裹成 shell 里的单引号字面量(值里的单引号一并转义),保证元字符按字面量传递。
3
+ * @param value - 任意值;不是字符串的先 `String()`。
4
+ * @returns 可安全拼进 `/bin/sh -c` 的一段文本 —— 含空格 / `$()` / `;` / 通配符的值都不会被当成语法。
5
+ */
1
6
  export function quoteShellArg(value) {
2
7
  return `'${String(value).replace(/'/g, `'\\''`)}'`;
3
8
  }
9
+ /**
10
+ * 取一段文本的末尾若干行(如命令输出),顺带去掉首尾空白。
11
+ * @param text - 原文;`null` / `undefined` 当空串。
12
+ * @param lines - 最多留几行,默认 6;0 给全篇、负数从开头砍掉那么多行(都是 `slice` 语义),调用方传正数。
13
+ * @returns 末尾若干行、去首尾空行后的文本;原文行数不够就全给,空输入给空串。
14
+ */
4
15
  export function tailLines(text, lines = 6) {
5
16
  const all = String(text ?? '')
6
17
  .trim()
@@ -24,6 +35,11 @@ function candidateStarts(raw) {
24
35
  }
25
36
  return starts;
26
37
  }
38
+ /**
39
+ * 宽松解析一段前后可能夹着人话的 JSON:整段不通就从行首的 `[` / `{` 起、截到最后一个闭合符再试。
40
+ * @param text - 原文;`null` / `undefined` 当空串。
41
+ * @returns 解析出的值(对象 / 数组 / 标量都原样);空输入或怎么都解析不出给 `undefined`,调用方据此报「输出认不出」。
42
+ */
27
43
  export function parseJsonLoose(text) {
28
44
  const raw = String(text ?? '').trim();
29
45
  if (!raw)
@@ -50,6 +66,12 @@ export function parseJsonLoose(text) {
50
66
  const MARKDOWN_IMAGE = /!\[[^\]]*\]\([^)]*\)/g;
51
67
  const MARKDOWN_LINK = /\[([^\]]*)\]\([^)]*\)/g;
52
68
  const MARKDOWN_MARKS = /[*`_>#]/g;
69
+ /**
70
+ * 把一段 Markdown 压成一行纯文本摘要:图片剥掉、链接只留可见文字、标记符号压成空格,超长按字符截断。
71
+ * @param text - 原文;`null` / `undefined` 当空串。
72
+ * @param limit - 摘要最多多少字符,默认 80。
73
+ * @returns 单行摘要;没超限原样给,超了截到 `limit` 并补省略号(总长 `limit + 1`;第 `limit` 个字符是空白时会被 `trimEnd` 去掉、略短),空输入给空串。
74
+ */
53
75
  export function excerpt(text, limit = 80) {
54
76
  const flat = String(text ?? '')
55
77
  .replace(MARKDOWN_IMAGE, ' ')
package/dist/lib/ui.d.ts CHANGED
@@ -1,13 +1,63 @@
1
+ /**
2
+ * 拼类名:只留真值、用空格连接(`false` / `null` / `undefined` / 空串都丢掉)。
3
+ * @param parts - 若干类名或条件值。
4
+ * @returns 拼好的类字符串;一个真值都没有给空串。
5
+ */
1
6
  export declare function cx(...parts: Array<string | false | null | undefined>): string;
7
+ /**
8
+ * 时间文案取词:i18n 的 `(key, params) => 文案`;相对时间那几件都从它拿词。
9
+ */
2
10
  export type TimeTranslate = (key: string, params?: Record<string, unknown>) => string;
11
+ /**
12
+ * 相对时间的档位名,对应词表里的 `time.short.*`。
13
+ */
3
14
  export type RelativeTimeUnit = 'now' | 'minutes' | 'hours' | 'days' | 'months' | 'years';
15
+ /**
16
+ * 相对时间的一档:`unit` 是档位,`n` 是这一档的数量(单位随档位,见 `relativeTimeBucket`)。
17
+ */
4
18
  export interface RelativeTimeBucket {
5
19
  readonly unit: RelativeTimeUnit;
6
20
  readonly n: number;
7
21
  }
22
+ /**
23
+ * 把时间戳按 dsh 的分档切成「刚刚 / N 分钟 / N 小时 / N 天 / N 月 / N 年」—— 30 天进月、365 天进年。
24
+ * @param epochMs - 要换算的时间戳(毫秒)。
25
+ * @param now - 基准时间,默认 `Date.now()`。
26
+ * @returns 档位与数量;时间戳或基准不是有限数给 `null`,未来时间钳到「刚刚」(`now` / 0)。
27
+ */
8
28
  export declare function relativeTimeBucket(epochMs: number, now?: number): RelativeTimeBucket | null;
29
+ /**
30
+ * 行内用的紧凑相对时间(「3 分钟」这种,不带「前」)。
31
+ * @param epochMs - 要换算的时间戳(毫秒)。
32
+ * @param t - 词表取词函数。
33
+ * @param now - 基准时间,默认 `Date.now()`。
34
+ * @returns 紧凑文案;时间戳非法给空串。
35
+ */
9
36
  export declare function relativeTimeCompact(epochMs: number, t: TimeTranslate, now?: number): string;
37
+ /**
38
+ * 悬停卡里用的相对时间(「3 分钟前」);「刚刚」那一档不加「前」。
39
+ * @param epochMs - 要换算的时间戳(毫秒)。
40
+ * @param t - 词表取词函数。
41
+ * @param now - 基准时间,默认 `Date.now()`。
42
+ * @returns 带「前」的文案;时间戳非法给空串。
43
+ */
10
44
  export declare function relativeTimeAgo(epochMs: number, t: TimeTranslate, now?: number): string;
45
+ /**
46
+ * 会话行那一版相对时间:一小时内按分钟、一天内按小时、七天内按天,再久直接给绝对时间(到秒)。
47
+ * @param epochMs - 要换算的时间戳(毫秒)。
48
+ * @param t - 词表取词函数。
49
+ * @returns 文案;时间戳不是有限数给空串,未来时间落「刚刚」,七天及以上给本地化的年月日时分秒。
50
+ */
11
51
  export declare function relativeTime(epochMs: number, t: TimeTranslate): string;
52
+ /**
53
+ * 把 `/Users/<用户名>/…` 缩成 `~/…`,免得会话行里全是一样的长前缀。
54
+ * @param path - 任意值。
55
+ * @returns 缩好的路径;不在用户主目录下原样返回,不是字符串给空串。
56
+ */
12
57
  export declare function shortenPath(path: unknown): string;
58
+ /**
59
+ * 解析不带时区的本地时间戳(`YYYY-MM-DD HH:mm:ss`,也接受 `T` 分隔),按本机时区换算。
60
+ * @param text - 原文;`null` / `undefined` 当空串;首尾空白容忍。
61
+ * @returns 毫秒时间戳;形态不认识给 `NaN`(调用方据此回退到原始字符串,宁可不换算也不显示错时间)。
62
+ */
13
63
  export declare function parseLocalTimestamp(text: string | null | undefined): number;
package/dist/lib/ui.js CHANGED
@@ -1,3 +1,8 @@
1
+ /**
2
+ * 拼类名:只留真值、用空格连接(`false` / `null` / `undefined` / 空串都丢掉)。
3
+ * @param parts - 若干类名或条件值。
4
+ * @returns 拼好的类字符串;一个真值都没有给空串。
5
+ */
1
6
  export function cx(...parts) {
2
7
  return parts.filter(Boolean).join(' ');
3
8
  }
@@ -6,6 +11,12 @@ const HOUR = 60 * MINUTE;
6
11
  const DAY = 24 * HOUR;
7
12
  const MONTH = 30 * DAY;
8
13
  const YEAR = 365 * DAY;
14
+ /**
15
+ * 把时间戳按 dsh 的分档切成「刚刚 / N 分钟 / N 小时 / N 天 / N 月 / N 年」—— 30 天进月、365 天进年。
16
+ * @param epochMs - 要换算的时间戳(毫秒)。
17
+ * @param now - 基准时间,默认 `Date.now()`。
18
+ * @returns 档位与数量;时间戳或基准不是有限数给 `null`,未来时间钳到「刚刚」(`now` / 0)。
19
+ */
9
20
  export function relativeTimeBucket(epochMs, now = Date.now()) {
10
21
  if (!Number.isFinite(epochMs) || !Number.isFinite(now))
11
22
  return null;
@@ -25,10 +36,24 @@ export function relativeTimeBucket(epochMs, now = Date.now()) {
25
36
  function bucketLabel(bucket, t) {
26
37
  return bucket.unit === 'now' ? t('time.now') : t(`time.short.${bucket.unit}`, { n: bucket.n });
27
38
  }
39
+ /**
40
+ * 行内用的紧凑相对时间(「3 分钟」这种,不带「前」)。
41
+ * @param epochMs - 要换算的时间戳(毫秒)。
42
+ * @param t - 词表取词函数。
43
+ * @param now - 基准时间,默认 `Date.now()`。
44
+ * @returns 紧凑文案;时间戳非法给空串。
45
+ */
28
46
  export function relativeTimeCompact(epochMs, t, now) {
29
47
  const bucket = relativeTimeBucket(epochMs, now);
30
48
  return bucket === null ? '' : bucketLabel(bucket, t);
31
49
  }
50
+ /**
51
+ * 悬停卡里用的相对时间(「3 分钟前」);「刚刚」那一档不加「前」。
52
+ * @param epochMs - 要换算的时间戳(毫秒)。
53
+ * @param t - 词表取词函数。
54
+ * @param now - 基准时间,默认 `Date.now()`。
55
+ * @returns 带「前」的文案;时间戳非法给空串。
56
+ */
32
57
  export function relativeTimeAgo(epochMs, t, now) {
33
58
  const bucket = relativeTimeBucket(epochMs, now);
34
59
  if (bucket === null)
@@ -36,6 +61,12 @@ export function relativeTimeAgo(epochMs, t, now) {
36
61
  const label = bucketLabel(bucket, t);
37
62
  return bucket.unit === 'now' ? label : t('time.ago', { t: label });
38
63
  }
64
+ /**
65
+ * 会话行那一版相对时间:一小时内按分钟、一天内按小时、七天内按天,再久直接给绝对时间(到秒)。
66
+ * @param epochMs - 要换算的时间戳(毫秒)。
67
+ * @param t - 词表取词函数。
68
+ * @returns 文案;时间戳不是有限数给空串,未来时间落「刚刚」,七天及以上给本地化的年月日时分秒。
69
+ */
39
70
  export function relativeTime(epochMs, t) {
40
71
  if (!Number.isFinite(epochMs))
41
72
  return '';
@@ -57,6 +88,11 @@ export function relativeTime(epochMs, t) {
57
88
  second: '2-digit',
58
89
  });
59
90
  }
91
+ /**
92
+ * 把 `/Users/<用户名>/…` 缩成 `~/…`,免得会话行里全是一样的长前缀。
93
+ * @param path - 任意值。
94
+ * @returns 缩好的路径;不在用户主目录下原样返回,不是字符串给空串。
95
+ */
60
96
  export function shortenPath(path) {
61
97
  if (typeof path !== 'string')
62
98
  return '';
@@ -64,6 +100,11 @@ export function shortenPath(path) {
64
100
  return hit === null ? path : path.replace(hit[0], '~');
65
101
  }
66
102
  const LOCAL_TIMESTAMP = /^(\d{4})-(\d{2})-(\d{2})[ T](\d{2}):(\d{2}):(\d{2})$/;
103
+ /**
104
+ * 解析不带时区的本地时间戳(`YYYY-MM-DD HH:mm:ss`,也接受 `T` 分隔),按本机时区换算。
105
+ * @param text - 原文;`null` / `undefined` 当空串;首尾空白容忍。
106
+ * @returns 毫秒时间戳;形态不认识给 `NaN`(调用方据此回退到原始字符串,宁可不换算也不显示错时间)。
107
+ */
67
108
  export function parseLocalTimestamp(text) {
68
109
  const raw = String(text ?? '').trim();
69
110
  if (!LOCAL_TIMESTAMP.test(raw))
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dsh-workbench/shared",
3
- "version": "0.3.3",
3
+ "version": "0.3.5",
4
4
  "description": "工作台与模块共享的纯代码与契约类型:类型、纯逻辑、哑组件、平台解析。宿主侧的东西从 /host 进(不影响浏览器产物)。",
5
5
  "license": "MIT",
6
6
  "type": "module",
@@ -12,6 +12,13 @@ function copyAction(t: Translate, text: string): BlockerAction {
12
12
  return { kind: 'copy', label: t('blocker.copy'), text };
13
13
  }
14
14
 
15
+ /**
16
+ * 把一次读失败翻成一张卡住态,交给卡片直接渲染。
17
+ * @param error 读口回的错误;undefined 表示连错误都没拿到(多半是网络断,回「原因未知」)。
18
+ * @param t 文案函数,只取 blocker.* 命名空间的键。
19
+ * @param context 判定现场;platform 缺省时标题不带平台名。
20
+ * @returns 一定有一张卡,actions 至少一颗「重试」。
21
+ */
15
22
  export function blockerOfError(
16
23
  error: ApiError | undefined,
17
24
  t: Translate,
@@ -103,6 +110,12 @@ export function blockerOfError(
103
110
  }
104
111
  }
105
112
 
113
+ /**
114
+ * 把宿主回的环境读数翻成卡住态:探针没查过(unchecked)只算提示,不挡卡片。
115
+ * @param capability 宿主回的单元环境读数。
116
+ * @param t 文案函数,键都在 env.unit.* / blocker.* 下。
117
+ * @returns status 为 'ok' 时给 undefined(没有卡住态),其余状态一律给一张。
118
+ */
106
119
  export function blockerOfCapability(capability: CapabilityPayload, t: Translate): ModuleBlocker | undefined {
107
120
  if (capability.status === 'ok') return undefined;
108
121
 
@@ -1,3 +1,6 @@
1
+ /**
2
+ * 卡住时用户能点的那一下(开链接 / 复制命令 / 重试 / 装或升模块):由判据产出,卡片照原样渲染。
3
+ */
1
4
  export type BlockerAction =
2
5
  | {
3
6
  readonly kind: 'open-url';
@@ -15,6 +18,9 @@ export type BlockerAction =
15
18
  }
16
19
  | { readonly kind: 'open-market'; readonly label: string; readonly unit: string };
17
20
 
21
+ /**
22
+ * 一个单元此刻的卡住态:block 顶掉整张卡片,notice 只在角上提示一句;读口与探针产出,卡片消费。
23
+ */
18
24
  export interface ModuleBlocker {
19
25
  readonly id: string;
20
26
  readonly severity: 'block' | 'notice';
@@ -1,5 +1,11 @@
1
+ /**
2
+ * 引用标记的固定名字;完整标记是 @<这个名字>:<编码文本>,改它等于作废历史会话里已写下的引用。
3
+ */
1
4
  export const CONTEXT_MARKER_NAME = '工作台';
2
5
 
6
+ /**
7
+ * 一条解析后的引用(种类加路径分段):输入框写标记时产出,跳转与展示时消费。
8
+ */
3
9
  export interface ContextRef {
4
10
  readonly kind: string;
5
11
  readonly parts: readonly string[];
@@ -9,10 +15,20 @@ const MARKER = new RegExp(`@${CONTEXT_MARKER_NAME}:([a-z][a-z0-9-]*:[A-Za-z0-9._
9
15
 
10
16
  const TRAILING_PUNCTUATION = /[.,;:!?]+$/;
11
17
 
18
+ /**
19
+ * 把引用拼回标记体里的那一段文本(不含 @ 与标记名)。
20
+ * @param ref 要拼的引用。
21
+ * @returns 形如 kind:part/part;分段里的 / 不转义,所以分段名本身不能含 /。
22
+ */
12
23
  export function encodeRef(ref: ContextRef): string {
13
24
  return `${ref.kind}:${ref.parts.join('/')}`;
14
25
  }
15
26
 
27
+ /**
28
+ * 把 kind:part/part 文本读回引用。
29
+ * @param text 待解析文本。
30
+ * @returns 没冒号、冒号在首尾、种类不是小写字母开头、或冒号后为空时给 undefined;不抛异常。
31
+ */
16
32
  export function decodeRef(text: string): ContextRef | undefined {
17
33
  const at = text.indexOf(':');
18
34
  if (at <= 0 || at === text.length - 1) return undefined;
@@ -23,14 +39,29 @@ export function decodeRef(text: string): ContextRef | undefined {
23
39
  return { kind, parts };
24
40
  }
25
41
 
42
+ /**
43
+ * 拼出 @ 后面那半截(标记名加已编码文本),比较与拼接都用它。
44
+ * @param encoded encodeRef 的结果。
45
+ * @returns 形如 工作台:file:src/a.ts。
46
+ */
26
47
  export function markerBody(encoded: string): string {
27
48
  return `${CONTEXT_MARKER_NAME}:${encoded}`;
28
49
  }
29
50
 
51
+ /**
52
+ * 把一条引用拼成能写进对话文本的完整标记。
53
+ * @param ref 要标记的引用。
54
+ * @returns 形如 @工作台:file:src/a.ts 的一段文本。
55
+ */
30
56
  export function buildMarker(ref: ContextRef): string {
31
57
  return `@${markerBody(encodeRef(ref))}`;
32
58
  }
33
59
 
60
+ /**
61
+ * 从一段文本里按出现顺序捞出所有标记引用,同一个引用只留第一次。
62
+ * @param text 任意文本;null / undefined 当空串。
63
+ * @returns 解析不出来的标记会被丢掉,所以结果只含合法引用,可能为空数组。
64
+ */
34
65
  export function parseMarkers(text: string): ContextRef[] {
35
66
  const seen = new Set<string>();
36
67
  const refs: ContextRef[] = [];