@tita-agent/dsh-data-table 0.0.0-stage → 0.2.0

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 (267) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +172 -2
  3. package/cordis.patch.yml +7 -0
  4. package/lib/client.js +16766 -0
  5. package/lib/contract-envelope.d.ts +91 -0
  6. package/lib/contract-envelope.js +106 -0
  7. package/lib/contract-layout.d.ts +130 -0
  8. package/lib/contract-layout.js +138 -0
  9. package/lib/contract-refs.d.ts +39 -0
  10. package/lib/contract-refs.js +9 -0
  11. package/lib/contract-version.d.ts +311 -0
  12. package/lib/contract-version.js +270 -0
  13. package/lib/contract-wire.d.ts +147 -0
  14. package/lib/contract-wire.js +99 -0
  15. package/lib/contracts.d.ts +12 -0
  16. package/lib/contracts.js +12 -0
  17. package/lib/types/audit.d.ts +42 -0
  18. package/lib/types/audit.js +39 -0
  19. package/lib/types/client/ProjectImportMenu.d.ts +30 -0
  20. package/lib/types/client/ProjectImportMenu.js +70 -0
  21. package/lib/types/client/SheetPanel.d.ts +33 -0
  22. package/lib/types/client/SheetPanel.js +1766 -0
  23. package/lib/types/client/SheetToggle.d.ts +18 -0
  24. package/lib/types/client/SheetToggle.js +26 -0
  25. package/lib/types/client/index.d.ts +15 -0
  26. package/lib/types/client/index.js +110 -0
  27. package/lib/types/client/persistence/changes.d.ts +15 -0
  28. package/lib/types/client/persistence/changes.js +51 -0
  29. package/lib/types/client/persistence/ids.d.ts +34 -0
  30. package/lib/types/client/persistence/ids.js +48 -0
  31. package/lib/types/client/persistence/merge.d.ts +35 -0
  32. package/lib/types/client/persistence/merge.js +362 -0
  33. package/lib/types/client/store.d.ts +61 -0
  34. package/lib/types/client/store.js +152 -0
  35. package/lib/types/client/surface-context.d.ts +27 -0
  36. package/lib/types/client/surface-context.js +104 -0
  37. package/lib/types/client/surface-control.d.ts +2 -0
  38. package/lib/types/client/surface-control.js +12 -0
  39. package/lib/types/client/transport/workbookApi.d.ts +159 -0
  40. package/lib/types/client/transport/workbookApi.js +246 -0
  41. package/lib/types/client/univer/StandaloneWorkbookPage.d.ts +5 -0
  42. package/lib/types/client/univer/StandaloneWorkbookPage.js +7 -0
  43. package/lib/types/client/univer/WorkbookCanvas.d.ts +40 -0
  44. package/lib/types/client/univer/WorkbookCanvas.js +87 -0
  45. package/lib/types/client/univer/WorkbookView.d.ts +3 -0
  46. package/lib/types/client/univer/WorkbookView.js +2 -0
  47. package/lib/types/client/univer/loader.d.ts +18 -0
  48. package/lib/types/client/univer/loader.js +81 -0
  49. package/lib/types/client/univer/standalone-entry.d.ts +1 -0
  50. package/lib/types/client/univer/standalone-entry.js +19 -0
  51. package/lib/types/client/versions/VersionChrome.d.ts +33 -0
  52. package/lib/types/client/versions/VersionChrome.js +21 -0
  53. package/lib/types/client/versions/inspect.d.ts +70 -0
  54. package/lib/types/client/versions/inspect.js +88 -0
  55. package/lib/types/client/versions/preview-decoration.d.ts +11 -0
  56. package/lib/types/client/versions/preview-decoration.js +54 -0
  57. package/lib/types/client/versions/records.d.ts +19 -0
  58. package/lib/types/client/versions/records.js +70 -0
  59. package/lib/types/client/workspace-host.d.ts +61 -0
  60. package/lib/types/client/workspace-host.js +124 -0
  61. package/lib/types/collaboration-hub.d.ts +31 -0
  62. package/lib/types/collaboration-hub.js +69 -0
  63. package/lib/types/contract/ids.d.ts +24 -0
  64. package/lib/types/contract/ids.js +46 -0
  65. package/lib/types/contract/mutations.d.ts +25 -0
  66. package/lib/types/contract/mutations.js +782 -0
  67. package/lib/types/contract/operations.d.ts +54 -0
  68. package/lib/types/contract/operations.js +203 -0
  69. package/lib/types/contract/snapshots.d.ts +176 -0
  70. package/lib/types/contract/snapshots.js +351 -0
  71. package/lib/types/contract/univer-api-catalog.d.ts +29 -0
  72. package/lib/types/contract/univer-api-catalog.js +264 -0
  73. package/lib/types/errors.d.ts +8 -0
  74. package/lib/types/errors.js +12 -0
  75. package/lib/types/http/assets-routes.d.ts +3 -0
  76. package/lib/types/http/assets-routes.js +169 -0
  77. package/lib/types/http/collaboration.d.ts +10 -0
  78. package/lib/types/http/collaboration.js +154 -0
  79. package/lib/types/http/history.d.ts +15 -0
  80. package/lib/types/http/history.js +134 -0
  81. package/lib/types/http/project-files.d.ts +79 -0
  82. package/lib/types/http/project-files.js +206 -0
  83. package/lib/types/http/project-scope.d.ts +50 -0
  84. package/lib/types/http/project-scope.js +52 -0
  85. package/lib/types/http/routes.d.ts +98 -0
  86. package/lib/types/http/routes.js +323 -0
  87. package/lib/types/http/standalone.d.ts +9 -0
  88. package/lib/types/http/standalone.js +31 -0
  89. package/lib/types/http/trunk.d.ts +10 -0
  90. package/lib/types/http/trunk.js +322 -0
  91. package/lib/types/http/worktree.d.ts +9 -0
  92. package/lib/types/http/worktree.js +145 -0
  93. package/lib/types/index.d.ts +18 -0
  94. package/lib/types/index.js +156 -0
  95. package/lib/types/invariant.d.ts +15 -0
  96. package/lib/types/invariant.js +15 -0
  97. package/lib/types/request-params.d.ts +21 -0
  98. package/lib/types/request-params.js +76 -0
  99. package/lib/types/store/paths.d.ts +68 -0
  100. package/lib/types/store/paths.js +148 -0
  101. package/lib/types/store/project.d.ts +88 -0
  102. package/lib/types/store/project.js +120 -0
  103. package/lib/types/store/read.d.ts +110 -0
  104. package/lib/types/store/read.js +404 -0
  105. package/lib/types/store/retention.d.ts +27 -0
  106. package/lib/types/store/retention.js +111 -0
  107. package/lib/types/store/write.d.ts +22 -0
  108. package/lib/types/store/write.js +210 -0
  109. package/lib/types/tools/arguments.d.ts +40 -0
  110. package/lib/types/tools/arguments.js +130 -0
  111. package/lib/types/tools/cells.d.ts +50 -0
  112. package/lib/types/tools/cells.js +298 -0
  113. package/lib/types/tools/index.d.ts +2 -0
  114. package/lib/types/tools/index.js +250 -0
  115. package/lib/types/tools/render.d.ts +47 -0
  116. package/lib/types/tools/render.js +290 -0
  117. package/lib/types/tools/univer-api.d.ts +18 -0
  118. package/lib/types/tools/univer-api.js +198 -0
  119. package/lib/types/tools/workbook-catalog.d.ts +3 -0
  120. package/lib/types/tools/workbook-catalog.js +83 -0
  121. package/lib/types/tools/workbook-edit.d.ts +192 -0
  122. package/lib/types/tools/workbook-edit.js +912 -0
  123. package/lib/types/tools/workbook-find.d.ts +11 -0
  124. package/lib/types/tools/workbook-find.js +89 -0
  125. package/lib/types/tools/workbook-history.d.ts +2 -0
  126. package/lib/types/tools/workbook-history.js +59 -0
  127. package/lib/types/tools/workbook-read.d.ts +21 -0
  128. package/lib/types/tools/workbook-read.js +204 -0
  129. package/lib/types/types.d.ts +377 -0
  130. package/lib/types/types.js +34 -0
  131. package/lib/types/univer/apply-operation.d.ts +14 -0
  132. package/lib/types/univer/apply-operation.js +398 -0
  133. package/lib/types/univer/chunk.d.ts +68 -0
  134. package/lib/types/univer/chunk.js +16 -0
  135. package/lib/types/univer/decoration.d.ts +34 -0
  136. package/lib/types/univer/decoration.js +88 -0
  137. package/lib/types/univer/diagnostics.d.ts +31 -0
  138. package/lib/types/univer/diagnostics.js +141 -0
  139. package/lib/types/univer/engine.d.ts +33 -0
  140. package/lib/types/univer/engine.js +338 -0
  141. package/lib/types/univer/entry.d.ts +8 -0
  142. package/lib/types/univer/entry.js +11 -0
  143. package/lib/types/univer/facade.d.ts +107 -0
  144. package/lib/types/univer/facade.js +1 -0
  145. package/lib/types/univer/fonts.d.ts +202 -0
  146. package/lib/types/univer/fonts.js +140 -0
  147. package/lib/types/univer/plugins/collaboration.d.ts +23 -0
  148. package/lib/types/univer/plugins/collaboration.js +47 -0
  149. package/lib/types/univer/plugins/export.d.ts +39 -0
  150. package/lib/types/univer/plugins/export.js +246 -0
  151. package/lib/types/univer/plugins/history.d.ts +24 -0
  152. package/lib/types/univer/plugins/history.js +50 -0
  153. package/lib/types/univer/plugins/version.d.ts +13 -0
  154. package/lib/types/univer/plugins/version.js +13 -0
  155. package/lib/types/version/diff.d.ts +36 -0
  156. package/lib/types/version/diff.js +156 -0
  157. package/lib/types/version/grouping.d.ts +7 -0
  158. package/lib/types/version/grouping.js +94 -0
  159. package/lib/types/version/records.d.ts +17 -0
  160. package/lib/types/version/records.js +102 -0
  161. package/lib/types/version/session.d.ts +36 -0
  162. package/lib/types/version/session.js +89 -0
  163. package/lib/types/version/summary.d.ts +24 -0
  164. package/lib/types/version/summary.js +111 -0
  165. package/lib/types/workbook-manager.d.ts +191 -0
  166. package/lib/types/workbook-manager.js +935 -0
  167. package/lib/types/xlsx-export-worker.d.ts +5 -0
  168. package/lib/types/xlsx-export-worker.js +24 -0
  169. package/lib/types/xlsx-export.d.ts +21 -0
  170. package/lib/types/xlsx-export.js +371 -0
  171. package/lib/types/xlsx-import-worker.d.ts +1 -0
  172. package/lib/types/xlsx-import-worker.js +19 -0
  173. package/lib/univer-client.js +690 -0
  174. package/lib/workbook-page.js +23826 -0
  175. package/lib/xlsx-export-worker.js +104 -0
  176. package/lib/xlsx-import-worker.js +46 -0
  177. package/package.json +106 -3
  178. package/src/audit.ts +58 -0
  179. package/src/client/ProjectImportMenu.tsx +100 -0
  180. package/src/client/SheetPanel.module.css +1578 -0
  181. package/src/client/SheetPanel.tsx +1934 -0
  182. package/src/client/SheetToggle.module.css +73 -0
  183. package/src/client/SheetToggle.tsx +65 -0
  184. package/src/client/contract.d.ts +19 -0
  185. package/src/client/index.ts +131 -0
  186. package/src/client/persistence/changes.ts +58 -0
  187. package/src/client/persistence/ids.ts +60 -0
  188. package/src/client/persistence/merge.ts +392 -0
  189. package/src/client/store.ts +188 -0
  190. package/src/client/surface-context.ts +123 -0
  191. package/src/client/surface-control.ts +13 -0
  192. package/src/client/transport/workbookApi.ts +355 -0
  193. package/src/client/univer/SheetView.module.css +110 -0
  194. package/src/client/univer/StandaloneWorkbookPage.module.css +8 -0
  195. package/src/client/univer/StandaloneWorkbookPage.tsx +15 -0
  196. package/src/client/univer/WorkbookCanvas.tsx +128 -0
  197. package/src/client/univer/WorkbookView.module.css +30 -0
  198. package/src/client/univer/WorkbookView.tsx +3 -0
  199. package/src/client/univer/loader.ts +90 -0
  200. package/src/client/univer/react-dom-client.d.ts +10 -0
  201. package/src/client/univer/standalone-entry.tsx +19 -0
  202. package/src/client/univer/standalone.css +12 -0
  203. package/src/client/univer/univer-locales.d.ts +89 -0
  204. package/src/client/univer/univer-modules.d.ts +26 -0
  205. package/src/client/versions/VersionChrome.tsx +114 -0
  206. package/src/client/versions/inspect.ts +138 -0
  207. package/src/client/versions/preview-decoration.ts +54 -0
  208. package/src/client/versions/records.ts +78 -0
  209. package/src/client/workspace-host.ts +147 -0
  210. package/src/collaboration-hub.ts +83 -0
  211. package/src/contract/ids.ts +50 -0
  212. package/src/contract/mutations.ts +791 -0
  213. package/src/contract/operations.ts +221 -0
  214. package/src/contract/snapshots.ts +383 -0
  215. package/src/contract/univer-api-catalog.ts +289 -0
  216. package/src/css-modules.d.ts +6 -0
  217. package/src/errors.ts +25 -0
  218. package/src/http/assets-routes.ts +177 -0
  219. package/src/http/collaboration.ts +144 -0
  220. package/src/http/history.ts +151 -0
  221. package/src/http/project-files.ts +227 -0
  222. package/src/http/project-scope.ts +77 -0
  223. package/src/http/routes.ts +364 -0
  224. package/src/http/standalone.ts +32 -0
  225. package/src/http/trunk.ts +322 -0
  226. package/src/http/worktree.ts +119 -0
  227. package/src/index.ts +165 -0
  228. package/src/invariant.ts +17 -0
  229. package/src/request-params.ts +92 -0
  230. package/src/store/paths.ts +165 -0
  231. package/src/store/project.ts +137 -0
  232. package/src/store/read.ts +454 -0
  233. package/src/store/retention.ts +106 -0
  234. package/src/store/write.ts +230 -0
  235. package/src/tools/arguments.ts +128 -0
  236. package/src/tools/cells.ts +298 -0
  237. package/src/tools/index.ts +274 -0
  238. package/src/tools/render.ts +301 -0
  239. package/src/tools/univer-api.ts +243 -0
  240. package/src/tools/workbook-catalog.ts +94 -0
  241. package/src/tools/workbook-edit.ts +987 -0
  242. package/src/tools/workbook-find.ts +80 -0
  243. package/src/tools/workbook-history.ts +63 -0
  244. package/src/tools/workbook-read.ts +210 -0
  245. package/src/types.ts +432 -0
  246. package/src/univer/apply-operation.ts +369 -0
  247. package/src/univer/chunk.ts +77 -0
  248. package/src/univer/decoration.ts +111 -0
  249. package/src/univer/diagnostics.ts +136 -0
  250. package/src/univer/engine.ts +370 -0
  251. package/src/univer/entry.ts +14 -0
  252. package/src/univer/facade.ts +91 -0
  253. package/src/univer/fonts.ts +148 -0
  254. package/src/univer/overrides.css +33 -0
  255. package/src/univer/plugins/collaboration.ts +47 -0
  256. package/src/univer/plugins/export.ts +236 -0
  257. package/src/univer/plugins/history.ts +49 -0
  258. package/src/univer/plugins/version.ts +13 -0
  259. package/src/version/diff.ts +169 -0
  260. package/src/version/grouping.ts +96 -0
  261. package/src/version/records.ts +113 -0
  262. package/src/version/session.ts +90 -0
  263. package/src/version/summary.ts +117 -0
  264. package/src/workbook-manager.ts +1111 -0
  265. package/src/xlsx-export-worker.ts +34 -0
  266. package/src/xlsx-export.ts +373 -0
  267. package/src/xlsx-import-worker.ts +30 -0
@@ -0,0 +1,104 @@
1
+ export const DATA_SHEET_SURFACE_ID = 'data-sheet';
2
+ /** Keep source-specific focus data opaque to the bridge, but stable in transcript metadata. */
3
+ export function dataSheetFocusId(target) {
4
+ return JSON.stringify(target);
5
+ }
6
+ /** Decode a focus id emitted by this source. */
7
+ export function dataSheetFocusTargetOf(value) {
8
+ try {
9
+ const target = JSON.parse(value);
10
+ if (typeof target.workbookId !== 'string' || typeof target.sheetId !== 'string')
11
+ return undefined;
12
+ for (const key of ['startRow', 'startColumn', 'endRow', 'endColumn']) {
13
+ if (!Number.isSafeInteger(target[key]) || target[key] < 0)
14
+ return undefined;
15
+ }
16
+ return target;
17
+ }
18
+ catch {
19
+ return undefined;
20
+ }
21
+ }
22
+ function columnName(index) {
23
+ let value = index + 1;
24
+ let name = '';
25
+ while (value > 0) {
26
+ const remainder = (value - 1) % 26;
27
+ name = String.fromCharCode(65 + remainder) + name;
28
+ value = Math.floor((value - 1) / 26);
29
+ }
30
+ return name;
31
+ }
32
+ function cellAddress(row, column) {
33
+ return `${columnName(column)}${row + 1}`;
34
+ }
35
+ function rangeAddress(range) {
36
+ const start = cellAddress(range.startRow, range.startColumn);
37
+ const end = cellAddress(range.endRow, range.endColumn);
38
+ return `${range.sheetName}!${start === end ? start : `${start}:${end}`}`;
39
+ }
40
+ function cellText(value) {
41
+ if (value === null || value === undefined)
42
+ return '';
43
+ if (typeof value === 'string')
44
+ return value;
45
+ if (typeof value === 'number' || typeof value === 'boolean')
46
+ return String(value);
47
+ try {
48
+ return JSON.stringify(value) ?? '';
49
+ }
50
+ catch {
51
+ return String(value);
52
+ }
53
+ }
54
+ function previewText(values) {
55
+ if (values.length === 0)
56
+ return undefined;
57
+ const rendered = values.map(row => row.map(cellText).join('\t')).join('\n').slice(0, 2_000);
58
+ return rendered.length === 0 ? undefined : rendered;
59
+ }
60
+ /** Project the active Univer ranges into the bridge's shared context vocabulary. */
61
+ export function projectDataSheetSelection(input) {
62
+ const ranges = input.ranges.filter(range => range.startRow <= range.endRow && range.startColumn <= range.endColumn);
63
+ if (ranges.length === 0)
64
+ return null;
65
+ const count = ranges.reduce((total, range) => total
66
+ + (range.endRow - range.startRow + 1) * (range.endColumn - range.startColumn + 1), 0);
67
+ const items = ranges.slice(0, 40).map(range => {
68
+ const address = rangeAddress(range);
69
+ return {
70
+ id: dataSheetFocusId({ workbookId: input.workbookId, ...range }),
71
+ type: 'range',
72
+ label: address,
73
+ ...(previewText(range.values) === undefined ? {} : { text: previewText(range.values) }),
74
+ attributes: {
75
+ workbookId: input.workbookId,
76
+ sheetId: range.sheetId,
77
+ sheetName: range.sheetName,
78
+ address,
79
+ startRow: range.startRow,
80
+ startColumn: range.startColumn,
81
+ endRow: range.endRow,
82
+ endColumn: range.endColumn,
83
+ },
84
+ };
85
+ });
86
+ const labels = ranges.map(rangeAddress);
87
+ const summary = ranges.length === 1
88
+ ? `已选中 ${labels[0]},共 ${count} 个单元格`
89
+ : `已选中 ${ranges.length} 个区域,共 ${count} 个单元格`;
90
+ const note = `工作簿 ID:${input.workbookId};选区:${labels.join('、')}。条目文本最多预览每个区域前 10 行 × 20 列;完整数据用 workbook_read(workbookId="${input.workbookId}", target={kind:"range", sheetId, range}) 读取。写入前先读取最新 revision,再用 workbook_edit。`;
91
+ return {
92
+ source: DATA_SHEET_SURFACE_ID,
93
+ revision: input.revision,
94
+ count,
95
+ countLabel: `${count} 个单元格`,
96
+ title: 'Table',
97
+ summary,
98
+ items,
99
+ ...(ranges.length > items.length ? { truncated: true } : {}),
100
+ resource: { name: input.workbookId, display: input.workbookName, version: input.documentRevision },
101
+ notes: [note],
102
+ capabilities: [`使用 workbook_read / workbook_edit 操作工作簿 ${input.workbookId};写入时传该 id 和最新 revision。`],
103
+ };
104
+ }
@@ -0,0 +1,2 @@
1
+ export declare function registerDataSheetFocusHandler(handler: (itemId: string) => void): () => void;
2
+ export declare function focusDataSheetItem(itemId: string): void;
@@ -0,0 +1,12 @@
1
+ /** One live workbook view can honour focus requests from the shared bridge chip. */
2
+ let focusHandler;
3
+ export function registerDataSheetFocusHandler(handler) {
4
+ focusHandler = handler;
5
+ return () => {
6
+ if (focusHandler === handler)
7
+ focusHandler = undefined;
8
+ };
9
+ }
10
+ export function focusDataSheetItem(itemId) {
11
+ focusHandler?.(itemId);
12
+ }
@@ -0,0 +1,159 @@
1
+ import type { WorkbookLocalChange, WorkbookOperation, WorkbookSummary, WorkbookChange, WorkbookPresencePayload, WorkbookHistoryVersion } from '../../types.ts';
2
+ export declare class HttpStatusError extends Error {
3
+ readonly status: number;
4
+ readonly code?: string | undefined;
5
+ readonly details?: unknown | undefined;
6
+ constructor(status: number, message: string, code?: string | undefined, details?: unknown | undefined);
7
+ }
8
+ export declare class RevisionConflictError extends HttpStatusError {
9
+ constructor(message?: string, details?: unknown);
10
+ }
11
+ /** Tell the transport which DSH Session this page belongs to. */
12
+ export declare function setDshSessionId(id: string | undefined): void;
13
+ /** The Session this page is bound to, when it is bound to one. */
14
+ export declare function currentDshSessionId(): string | null;
15
+ /** Route browser requests through the official Electron Connection carrier. */
16
+ export declare function dataSheetClientPath(path: string): string;
17
+ /** Stable per-tab scope used by worktree APIs and Host audit logs. */
18
+ export declare function dataSheetSessionId(): string;
19
+ /**
20
+ * The live collaboration stream for one workbook.
21
+ *
22
+ * `EventSource` cannot set the `x-dsh-session` header every other call carries,
23
+ * so the Session — which decides *which project's* workbook this is — travels as
24
+ * a query parameter it can carry. The `session` parameter is a different thing:
25
+ * this browser tab's own id, used for presence and worktrees.
26
+ *
27
+ * @param workbookId - Workbook to watch.
28
+ * @param after - Revision to resume from.
29
+ * @returns a same-origin path, ready for `dataSheetClientPath`.
30
+ */
31
+ export declare function collaborationStreamPath(workbookId: string, after: number): string;
32
+ /**
33
+ * The standalone page for one workbook.
34
+ *
35
+ * A new tab has no sidebar to hand it a Session id, so the project travels in
36
+ * the URL: without it the page resolves against the sandbox policy root and
37
+ * could open another project's workbook.
38
+ *
39
+ * @param workbookId - Workbook to open in a plain browser document.
40
+ * @returns a same-origin path.
41
+ */
42
+ export declare function standaloneWorkbookPath(workbookId: string): string;
43
+ export interface WorkbookEnvelope {
44
+ ok?: boolean;
45
+ workbookId?: string;
46
+ revision?: number;
47
+ updatedAt?: string;
48
+ snapshot?: object;
49
+ }
50
+ export interface ChangesEnvelope {
51
+ revision?: number;
52
+ operations?: WorkbookOperation[];
53
+ requiresSnapshot?: boolean;
54
+ snapshot?: object;
55
+ }
56
+ export interface HistoryEntry {
57
+ opId: string;
58
+ revision: number;
59
+ parentRevision?: number;
60
+ actor: 'agent' | 'user';
61
+ type: string;
62
+ timestamp: string;
63
+ summary: string;
64
+ canPreview?: boolean;
65
+ }
66
+ export interface HistoryEnvelope {
67
+ entries?: HistoryEntry[];
68
+ versions?: WorkbookHistoryVersion[];
69
+ checkpoints?: number[];
70
+ revision?: number;
71
+ }
72
+ export interface HistoryDiffEnvelope {
73
+ workbookId?: string;
74
+ from?: number;
75
+ to?: number;
76
+ changes?: WorkbookChange[];
77
+ /** True when the server stopped at its diff limit, so the counts are a floor. */
78
+ truncated?: boolean;
79
+ }
80
+ export interface WorkbookMutationEnvelope {
81
+ workbook?: {
82
+ id: string;
83
+ name: string;
84
+ sheets?: WorkbookSummary['sheets'];
85
+ updatedAt?: string;
86
+ };
87
+ }
88
+ export declare function fetchWorkbook(id: string): Promise<WorkbookEnvelope>;
89
+ /** One entry of the project file chooser's listing; mirrors the Host's shape. */
90
+ export interface ProjectFileEntry {
91
+ name: string;
92
+ path: string;
93
+ type: 'file' | 'directory';
94
+ size: number;
95
+ createdAt: string;
96
+ modifiedAt: string;
97
+ }
98
+ /** One directory of `<project>/storages`, with sizes and dates. */
99
+ export declare function fetchProjectFiles(directory: string): Promise<{
100
+ directory: string;
101
+ entries: ProjectFileEntry[];
102
+ truncated?: number;
103
+ }>;
104
+ /**
105
+ * Read one file the chooser listed.
106
+ *
107
+ * The browser cannot open a path, so the Host reads it and the bytes arrive here
108
+ * as a buffer, ready for the import pipeline that already existed.
109
+ */
110
+ export declare function readProjectFile(path: string): Promise<{
111
+ name: string;
112
+ data: ArrayBuffer;
113
+ }>;
114
+ export declare function fetchWorkbooks(): Promise<{
115
+ workbooks?: WorkbookSummary[];
116
+ }>;
117
+ export declare function createWorkbook(name: string): Promise<WorkbookMutationEnvelope>;
118
+ export declare function renameWorkbook(id: string, name: string): Promise<WorkbookMutationEnvelope>;
119
+ export declare function fetchChanges(id: string, after: number): Promise<ChangesEnvelope>;
120
+ export declare function fetchHistory(id: string): Promise<HistoryEnvelope>;
121
+ export interface HistorySnapshotEnvelope {
122
+ revision?: number;
123
+ snapshot?: object;
124
+ step?: number;
125
+ stepCount?: number;
126
+ /** True when the Step's change list stopped at the server's limit. */
127
+ truncated?: boolean;
128
+ /**
129
+ * What the requested Step changed. Present for Step previews, so the canvas
130
+ * can locate and tint the cells instead of leaving the reader to find them
131
+ * (PRD v3.3 §7.2).
132
+ */
133
+ changes?: WorkbookChange[];
134
+ }
135
+ export declare function fetchHistorySnapshot(id: string, revision: number, step?: number): Promise<HistorySnapshotEnvelope>;
136
+ export declare function fetchHistoryDiff(id: string, from: number, to: number): Promise<HistoryDiffEnvelope>;
137
+ export declare function rollbackWorkbook(id: string, targetRevision: number, baseRevision: number, summary?: string): Promise<{
138
+ revision?: number;
139
+ }>;
140
+ export declare function deleteWorkbook(id: string): Promise<void>;
141
+ export interface SaveResult {
142
+ revision: number;
143
+ operation?: WorkbookOperation;
144
+ rebasedOperations?: WorkbookOperation[];
145
+ }
146
+ export declare function saveOperationChanges(id: string, changes: WorkbookLocalChange[], baseRevision: number, opId: string, version?: {
147
+ id: string;
148
+ status: 'open' | 'final';
149
+ }): Promise<SaveResult | null>;
150
+ export interface SnapshotSaveOptions {
151
+ /** Named save: the Version gets this summary and closes the session. */
152
+ summary?: string;
153
+ /** Session Step: joins the open user Version (PRD v3.1 §4). */
154
+ versionId?: string;
155
+ versionStatus?: 'open' | 'final';
156
+ versionKind?: 'named' | 'auto';
157
+ }
158
+ export declare function saveSnapshot(id: string, snapshotData: unknown, baseRevision: number, opId: string, options?: SnapshotSaveOptions): Promise<SaveResult | null>;
159
+ export declare function publishPresence(workbookId: string, presence: WorkbookPresencePayload): Promise<void>;
@@ -0,0 +1,246 @@
1
+ export class HttpStatusError extends Error {
2
+ status;
3
+ code;
4
+ details;
5
+ constructor(status, message, code, details) {
6
+ super(message);
7
+ this.status = status;
8
+ this.code = code;
9
+ this.details = details;
10
+ this.name = 'HttpStatusError';
11
+ }
12
+ }
13
+ export class RevisionConflictError extends HttpStatusError {
14
+ constructor(message = 'workbook revision conflict', details) {
15
+ super(409, message, 'REVISION_CONFLICT', details);
16
+ this.name = 'RevisionConflictError';
17
+ }
18
+ }
19
+ const SESSION_STORAGE_KEY = 'data-sheet.session-id';
20
+ let cachedSessionId = null;
21
+ /**
22
+ * The DSH Session whose project this page reads and writes.
23
+ *
24
+ * Set once when the panel mounts (the sidebar hands its Session id to the body)
25
+ * or, for the standalone page, read from its URL. Every workbook path is
26
+ * resolved by the Host inside that Session's project —
27
+ * `<project>/storages/data-table` — so a request that names the wrong Session
28
+ * reads and writes another project's workbooks. A request that names **no**
29
+ * Session keeps the pre-project per-user store instead, which is right for a
30
+ * script and wrong for a page: the panel always sets this.
31
+ */
32
+ let dshSessionId = null;
33
+ /** Tell the transport which DSH Session this page belongs to. */
34
+ export function setDshSessionId(id) {
35
+ dshSessionId = id === undefined || id.length === 0 ? null : id;
36
+ }
37
+ /** The Session this page is bound to, when it is bound to one. */
38
+ export function currentDshSessionId() {
39
+ return dshSessionId;
40
+ }
41
+ /** Route browser requests through the official Electron Connection carrier. */
42
+ export function dataSheetClientPath(path) {
43
+ const transport = globalThis.__DSH_TRANSPORT__;
44
+ return transport?.ownsHost === true
45
+ ? `/api/data-sheet?path=${encodeURIComponent(path)}`
46
+ : path;
47
+ }
48
+ /** Stable per-tab scope used by worktree APIs and Host audit logs. */
49
+ export function dataSheetSessionId() {
50
+ if (cachedSessionId !== null)
51
+ return cachedSessionId;
52
+ try {
53
+ const existing = window.sessionStorage.getItem(SESSION_STORAGE_KEY);
54
+ if (existing !== null && existing.trim().length > 0) {
55
+ cachedSessionId = existing;
56
+ return existing;
57
+ }
58
+ const generated = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
59
+ ? crypto.randomUUID()
60
+ : `${Date.now()}-${Math.random().toString(36).slice(2)}`;
61
+ window.sessionStorage.setItem(SESSION_STORAGE_KEY, generated);
62
+ cachedSessionId = generated;
63
+ return generated;
64
+ }
65
+ catch {
66
+ cachedSessionId = `ephemeral-${Date.now()}-${Math.random().toString(36).slice(2)}`;
67
+ return cachedSessionId;
68
+ }
69
+ }
70
+ /**
71
+ * The live collaboration stream for one workbook.
72
+ *
73
+ * `EventSource` cannot set the `x-dsh-session` header every other call carries,
74
+ * so the Session — which decides *which project's* workbook this is — travels as
75
+ * a query parameter it can carry. The `session` parameter is a different thing:
76
+ * this browser tab's own id, used for presence and worktrees.
77
+ *
78
+ * @param workbookId - Workbook to watch.
79
+ * @param after - Revision to resume from.
80
+ * @returns a same-origin path, ready for `dataSheetClientPath`.
81
+ */
82
+ export function collaborationStreamPath(workbookId, after) {
83
+ const project = currentDshSessionId();
84
+ const scope = project === null ? '' : `&sessionId=${encodeURIComponent(project)}`;
85
+ return `/data-sheet/workbook/${encodeURIComponent(workbookId)}/collaboration?after=${after}`
86
+ + `&session=${encodeURIComponent(dataSheetSessionId())}${scope}`;
87
+ }
88
+ /**
89
+ * The standalone page for one workbook.
90
+ *
91
+ * A new tab has no sidebar to hand it a Session id, so the project travels in
92
+ * the URL: without it the page resolves against the sandbox policy root and
93
+ * could open another project's workbook.
94
+ *
95
+ * @param workbookId - Workbook to open in a plain browser document.
96
+ * @returns a same-origin path.
97
+ */
98
+ export function standaloneWorkbookPath(workbookId) {
99
+ const project = currentDshSessionId();
100
+ return `/data-sheet/workbook/${encodeURIComponent(workbookId)}/view`
101
+ + (project === null ? '' : `?sessionId=${encodeURIComponent(project)}`);
102
+ }
103
+ async function requestJson(path, init = {}) {
104
+ const headers = new Headers(init.headers);
105
+ headers.set('accept', 'application/json');
106
+ headers.set('x-data-sheet-session', dataSheetSessionId());
107
+ // Which *project* the Host should resolve the path inside. Distinct from the
108
+ // header above, which is this browser tab's own id for worktrees and presence.
109
+ if (dshSessionId !== null)
110
+ headers.set('x-dsh-session', dshSessionId);
111
+ if (init.body !== undefined && !headers.has('content-type'))
112
+ headers.set('content-type', 'application/json');
113
+ let response = null;
114
+ let networkError = null;
115
+ // GETs and writes carrying an opId are safe to retry: the Host's idempotency
116
+ // check makes a lost response indistinguishable from a successful retry.
117
+ const retryable = init.method === undefined || init.method === 'GET' || init.method === 'HEAD'
118
+ || typeof init.body === 'string' && init.body.includes('"opId"');
119
+ for (let attempt = 0; attempt < (retryable ? 3 : 1); attempt += 1) {
120
+ try {
121
+ response = await fetch(dataSheetClientPath(path), { ...init, headers });
122
+ networkError = null;
123
+ break;
124
+ }
125
+ catch (error) {
126
+ networkError = error;
127
+ if (attempt + 1 < (retryable ? 3 : 1))
128
+ await new Promise(resolve => window.setTimeout(resolve, 150 * (attempt + 1)));
129
+ }
130
+ }
131
+ if (response === null) {
132
+ const detail = networkError instanceof Error ? networkError.message : '';
133
+ throw new HttpStatusError(0, detail === 'Failed to fetch' ? '网络请求失败,请确认 DSH 服务仍在运行后重试' : (detail || '网络请求失败'), 'NETWORK_ERROR', { detail });
134
+ }
135
+ let body = null;
136
+ try {
137
+ body = await response.json();
138
+ }
139
+ catch { /* non-json host/proxy failure */ }
140
+ if (!response.ok) {
141
+ const record = typeof body === 'object' && body !== null ? body : {};
142
+ const message = typeof record.error === 'string' ? record.error
143
+ : typeof record.message === 'string' ? record.message
144
+ : `${response.status} ${response.statusText || '请求失败'}`;
145
+ const code = typeof record.code === 'string' ? record.code : undefined;
146
+ if (response.status === 409 || code === 'REVISION_CONFLICT')
147
+ throw new RevisionConflictError(message, body);
148
+ throw new HttpStatusError(response.status, message, code, body);
149
+ }
150
+ return body;
151
+ }
152
+ export async function fetchWorkbook(id) {
153
+ const value = await requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}.json`);
154
+ if (typeof value === 'object' && value !== null && 'snapshot' in value)
155
+ return value;
156
+ return { ok: true, workbookId: id, revision: 0, snapshot: value };
157
+ }
158
+ /** One directory of `<project>/storages`, with sizes and dates. */
159
+ export async function fetchProjectFiles(directory) {
160
+ return requestJson(`/data-sheet/project-files.json?dir=${encodeURIComponent(directory)}`);
161
+ }
162
+ /**
163
+ * Read one file the chooser listed.
164
+ *
165
+ * The browser cannot open a path, so the Host reads it and the bytes arrive here
166
+ * as a buffer, ready for the import pipeline that already existed.
167
+ */
168
+ export async function readProjectFile(path) {
169
+ const headers = new Headers({ accept: 'application/octet-stream' });
170
+ if (dshSessionId !== null)
171
+ headers.set('x-dsh-session', dshSessionId);
172
+ const response = await fetch(dataSheetClientPath(`/data-sheet/project-file.json?path=${encodeURIComponent(path)}`), { headers });
173
+ if (!response.ok) {
174
+ const body = await response.json().catch(() => null);
175
+ throw new HttpStatusError(response.status, typeof body?.error === 'string' ? body.error : `读取文件失败:${response.status}`);
176
+ }
177
+ const encoded = response.headers.get('x-file-name');
178
+ const name = encoded === null ? (path.split('/').pop() ?? path) : decodeURIComponent(encoded);
179
+ return { name, data: await response.arrayBuffer() };
180
+ }
181
+ export async function fetchWorkbooks() {
182
+ return requestJson('/data-sheet/workbooks.json');
183
+ }
184
+ export async function createWorkbook(name) {
185
+ return requestJson('/data-sheet/workbooks.json', {
186
+ method: 'POST',
187
+ body: JSON.stringify({ action: 'create', name }),
188
+ });
189
+ }
190
+ export async function renameWorkbook(id, name) {
191
+ return requestJson('/data-sheet/workbooks.json', {
192
+ method: 'POST',
193
+ body: JSON.stringify({ action: 'rename', workbookId: id, name }),
194
+ });
195
+ }
196
+ export async function fetchChanges(id, after) {
197
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/changes?after=${after}`);
198
+ }
199
+ export async function fetchHistory(id) {
200
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/history`);
201
+ }
202
+ export async function fetchHistorySnapshot(id, revision, step) {
203
+ const query = step === undefined ? `revision=${revision}` : `revision=${revision}&step=${step}`;
204
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/history?${query}`);
205
+ }
206
+ export async function fetchHistoryDiff(id, from, to) {
207
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/history/diff?from=${from}&to=${to}`);
208
+ }
209
+ export async function rollbackWorkbook(id, targetRevision, baseRevision, summary) {
210
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/rollback`, {
211
+ method: 'POST',
212
+ body: JSON.stringify({ targetRevision, baseRevision, ...(summary === undefined ? {} : { summary }) }),
213
+ });
214
+ }
215
+ export async function deleteWorkbook(id) {
216
+ await requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}.json?action=delete`, { method: 'POST', body: JSON.stringify({ confirm: true }) });
217
+ }
218
+ export async function saveOperationChanges(id, changes, baseRevision, opId, version) {
219
+ const operation = changes.length === 1 ? changes[0] : { type: 'batch', payload: { operations: changes } };
220
+ const body = JSON.stringify({ type: operation.type, payload: operation.payload, actor: 'user', baseRevision, opId, ...(version === undefined ? {} : { versionId: version.id, versionStatus: version.status }) });
221
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/operations`, {
222
+ method: 'POST',
223
+ ...(body.length <= 60 * 1024 ? { keepalive: true } : {}),
224
+ body,
225
+ });
226
+ }
227
+ export async function saveSnapshot(id, snapshotData, baseRevision, opId, options = {}) {
228
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}.json`, {
229
+ method: 'POST',
230
+ body: JSON.stringify({
231
+ snapshot: snapshotData,
232
+ baseRevision,
233
+ opId,
234
+ ...(options.summary === undefined ? {} : { versionSummary: options.summary }),
235
+ ...(options.versionId === undefined ? {} : { versionId: options.versionId }),
236
+ ...(options.versionStatus === undefined ? {} : { versionStatus: options.versionStatus }),
237
+ ...(options.versionKind === undefined ? {} : { versionKind: options.versionKind }),
238
+ }),
239
+ });
240
+ }
241
+ export async function publishPresence(workbookId, presence) {
242
+ await requestJson(`/data-sheet/workbook/${encodeURIComponent(workbookId)}/presence`, {
243
+ method: 'POST',
244
+ body: JSON.stringify(presence),
245
+ });
246
+ }
@@ -0,0 +1,5 @@
1
+ export interface StandaloneWorkbookPageProps {
2
+ workbookId: string;
3
+ }
4
+ /** Full-browser workbook workspace. It deliberately reuses the embedded controller. */
5
+ export declare function StandaloneWorkbookPage({ workbookId }: StandaloneWorkbookPageProps): import("react").JSX.Element;
@@ -0,0 +1,7 @@
1
+ import { jsx as _jsx } from "react/jsx-runtime";
2
+ import { WorkbookWorkspace } from "../SheetPanel.js";
3
+ import styles from './StandaloneWorkbookPage.module.css';
4
+ /** Full-browser workbook workspace. It deliberately reuses the embedded controller. */
5
+ export function StandaloneWorkbookPage({ workbookId }) {
6
+ return (_jsx("main", { className: styles.root, children: _jsx(WorkbookWorkspace, { open: true, standalone: true, initialWorkbookId: workbookId }) }));
7
+ }
@@ -0,0 +1,40 @@
1
+ /**
2
+ * Shared Univer canvas used by the embedded Datasheet panel and the
3
+ * standalone workbook page. The workspace owns persistence and sync; this
4
+ * component owns only Univer residency and its imperative editing surface.
5
+ */
6
+ import { type WorkbookDecoration } from './loader.ts';
7
+ import type { WorkbookFocusRange, WorkbookLocalChange, WorkbookOperation, WorkbookPresencePayload, WorkbookSelectionRange } from '../../types.ts';
8
+ export interface WorkbookCanvasProps {
9
+ workbookData: object;
10
+ /**
11
+ * Preview residency (docs/issue/0002 PRD v3.2 §7.2): the canvas is showing a past
12
+ * Version rather than live content, so it is mounted with Univer's editable
13
+ * permission off and its mutations are never reported as changes. The caller
14
+ * owns the preview state; this component only refuses to fork a Version.
15
+ */
16
+ readOnly?: boolean;
17
+ /**
18
+ * Diff overlay for a preview residency: the cells that differ from the
19
+ * baseline, painted by the engine at mount (PRD v3.3 §7.2). Part of the
20
+ * residency, not a live API — a Step jump remounts the canvas, so a stale
21
+ * highlight cannot survive it.
22
+ */
23
+ decoration?: WorkbookDecoration;
24
+ onDirty?: (change?: WorkbookLocalChange) => void;
25
+ onPresence?: (presence: WorkbookPresencePayload) => void;
26
+ onSelectionChange?: (ranges: readonly WorkbookSelectionRange[]) => void;
27
+ onReady?: () => void;
28
+ }
29
+ export interface WorkbookCanvasHandle {
30
+ save(): unknown;
31
+ applyOperation(operation: WorkbookOperation): Promise<void>;
32
+ focusRange(range: WorkbookFocusRange): boolean;
33
+ /**
34
+ * Show a different workbook in the same residency. Returns false when the
35
+ * engine could not swap, so the caller can fall back to a remount.
36
+ */
37
+ swapWorkbook(next: object, decoration?: WorkbookDecoration): boolean;
38
+ }
39
+ /** Resident workbook canvas shared by embedded and standalone workspaces. */
40
+ export declare const WorkbookCanvas: import("react").ForwardRefExoticComponent<WorkbookCanvasProps & import("react").RefAttributes<WorkbookCanvasHandle>>;
@@ -0,0 +1,87 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ /**
3
+ * Shared Univer canvas used by the embedded Datasheet panel and the
4
+ * standalone workbook page. The workspace owns persistence and sync; this
5
+ * component owns only Univer residency and its imperative editing surface.
6
+ */
7
+ import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react';
8
+ import { mountWorkbook } from "./loader.js";
9
+ import styles from './WorkbookView.module.css';
10
+ /** Resident workbook canvas shared by embedded and standalone workspaces. */
11
+ export const WorkbookCanvas = forwardRef(function WorkbookCanvas({ workbookData, readOnly = false, decoration, onDirty, onPresence, onSelectionChange, onReady }, ref) {
12
+ const containerRef = useRef(null);
13
+ const handleRef = useRef(null);
14
+ const [ready, setReady] = useState(false);
15
+ const [failed, setFailed] = useState(false);
16
+ // Read through a ref so the mount effect stays keyed on the residency
17
+ // itself: `workbookData`/`readOnly` are fixed for a residency's lifetime,
18
+ // and the caller switches residency by changing the React `key`.
19
+ const readOnlyRef = useRef(readOnly);
20
+ readOnlyRef.current = readOnly;
21
+ const decorationRef = useRef(decoration);
22
+ decorationRef.current = decoration;
23
+ const selectionChangeRef = useRef(onSelectionChange);
24
+ selectionChangeRef.current = onSelectionChange;
25
+ const readyRef = useRef(onReady);
26
+ readyRef.current = onReady;
27
+ useEffect(() => {
28
+ if (handleRef.current !== null)
29
+ return;
30
+ const element = containerRef.current;
31
+ if (element === null)
32
+ return;
33
+ let disposed = false;
34
+ let mounted = null;
35
+ const readOnlyMount = readOnlyRef.current;
36
+ void (async () => {
37
+ await new Promise(resolve => requestAnimationFrame(() => resolve()));
38
+ if (disposed)
39
+ return;
40
+ // Univer emits initialization/layout mutations while createUnit is
41
+ // still mounting. They describe the initial projection, not a user
42
+ // edit; forwarding them would mark the workbook dirty immediately and
43
+ // attempt a large snapshot save before the canvas is ready.
44
+ const mount = await mountWorkbook(element, workbookData, change => {
45
+ if (mounted !== null && !readOnlyMount)
46
+ onDirty?.(change);
47
+ }, presence => {
48
+ if (mounted !== null && !readOnlyMount)
49
+ onPresence?.(presence);
50
+ }, { readOnly: readOnlyMount, ...(decorationRef.current === undefined ? {} : { decoration: decorationRef.current }) }, ranges => {
51
+ if (mounted !== null && !readOnlyMount)
52
+ selectionChangeRef.current?.(ranges);
53
+ });
54
+ mounted = mount;
55
+ if (disposed) {
56
+ mount.dispose();
57
+ return;
58
+ }
59
+ handleRef.current = {
60
+ save: () => mount.save(),
61
+ applyOperation: operation => mount.applyOperation(operation),
62
+ focusRange: range => mount.focusRange(range),
63
+ swapWorkbook: (next, nextDecoration) => mount.swapWorkbook(next, nextDecoration === undefined ? {} : { decoration: nextDecoration }),
64
+ };
65
+ setReady(true);
66
+ readyRef.current?.();
67
+ })().catch(() => {
68
+ if (!disposed)
69
+ setFailed(true);
70
+ });
71
+ return () => {
72
+ disposed = true;
73
+ mounted?.dispose();
74
+ mounted = null;
75
+ handleRef.current = null;
76
+ };
77
+ // Workbook data is fixed for the residency's lifetime.
78
+ // eslint-disable-next-line react-hooks/exhaustive-deps
79
+ }, []);
80
+ useImperativeHandle(ref, () => ({
81
+ save: () => (handleRef.current?.save() ?? null),
82
+ applyOperation: async (operation) => { await handleRef.current?.applyOperation(operation); },
83
+ focusRange: range => handleRef.current?.focusRange(range) ?? false,
84
+ swapWorkbook: (next, nextDecoration) => handleRef.current?.swapWorkbook(next, nextDecoration) ?? false,
85
+ }));
86
+ return (_jsx("div", { className: styles.view, children: _jsxs("div", { className: styles.body, children: [!ready && !failed && _jsx("div", { className: styles.hint, children: "\u8868\u683C\u52A0\u8F7D\u4E2D\u2026" }), failed && _jsx("div", { className: styles.hint, children: "\u8868\u683C\u6E32\u67D3\u5931\u8D25" }), _jsx("div", { ref: containerRef, className: styles.univer })] }) }));
87
+ });
@@ -0,0 +1,3 @@
1
+ /** Backwards-compatible alias for the shared WorkbookCanvas. */
2
+ export { WorkbookCanvas as WorkbookView } from './WorkbookCanvas.tsx';
3
+ export type { WorkbookCanvasHandle as WorkbookViewHandle, WorkbookCanvasProps as WorkbookViewProps } from './WorkbookCanvas.tsx';