@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,123 @@
1
+ import type { SurfaceSelection } from '@tita-agent/dsh-surface-bridge'
2
+
3
+ export const DATA_SHEET_SURFACE_ID = 'data-sheet'
4
+
5
+ export interface DataSheetFocusTarget {
6
+ readonly workbookId: string
7
+ readonly sheetId: string
8
+ readonly startRow: number
9
+ readonly startColumn: number
10
+ readonly endRow: number
11
+ readonly endColumn: number
12
+ }
13
+
14
+ export interface DataSheetRangeContext extends Omit<DataSheetFocusTarget, 'workbookId'> {
15
+ readonly sheetName: string
16
+ readonly values: readonly (readonly unknown[])[]
17
+ }
18
+
19
+ export interface DataSheetSelectionContext {
20
+ readonly workbookId: string
21
+ readonly workbookName: string
22
+ readonly revision: number
23
+ readonly documentRevision: number
24
+ readonly ranges: readonly DataSheetRangeContext[]
25
+ }
26
+
27
+ /** Keep source-specific focus data opaque to the bridge, but stable in transcript metadata. */
28
+ export function dataSheetFocusId(target: DataSheetFocusTarget): string {
29
+ return JSON.stringify(target)
30
+ }
31
+
32
+ /** Decode a focus id emitted by this source. */
33
+ export function dataSheetFocusTargetOf(value: string): DataSheetFocusTarget | undefined {
34
+ try {
35
+ const target = JSON.parse(value) as Partial<DataSheetFocusTarget>
36
+ if (typeof target.workbookId !== 'string' || typeof target.sheetId !== 'string') return undefined
37
+ for (const key of ['startRow', 'startColumn', 'endRow', 'endColumn'] as const) {
38
+ if (!Number.isSafeInteger(target[key]) || target[key]! < 0) return undefined
39
+ }
40
+ return target as DataSheetFocusTarget
41
+ } catch {
42
+ return undefined
43
+ }
44
+ }
45
+
46
+ function columnName(index: number): string {
47
+ let value = index + 1
48
+ let name = ''
49
+ while (value > 0) {
50
+ const remainder = (value - 1) % 26
51
+ name = String.fromCharCode(65 + remainder) + name
52
+ value = Math.floor((value - 1) / 26)
53
+ }
54
+ return name
55
+ }
56
+
57
+ function cellAddress(row: number, column: number): string {
58
+ return `${columnName(column)}${row + 1}`
59
+ }
60
+
61
+ function rangeAddress(range: DataSheetRangeContext): string {
62
+ const start = cellAddress(range.startRow, range.startColumn)
63
+ const end = cellAddress(range.endRow, range.endColumn)
64
+ return `${range.sheetName}!${start === end ? start : `${start}:${end}`}`
65
+ }
66
+
67
+ function cellText(value: unknown): string {
68
+ if (value === null || value === undefined) return ''
69
+ if (typeof value === 'string') return value
70
+ if (typeof value === 'number' || typeof value === 'boolean') return String(value)
71
+ try { return JSON.stringify(value) ?? '' } catch { return String(value) }
72
+ }
73
+
74
+ function previewText(values: readonly (readonly unknown[])[]): string | undefined {
75
+ if (values.length === 0) return undefined
76
+ const rendered = values.map(row => row.map(cellText).join('\t')).join('\n').slice(0, 2_000)
77
+ return rendered.length === 0 ? undefined : rendered
78
+ }
79
+
80
+ /** Project the active Univer ranges into the bridge's shared context vocabulary. */
81
+ export function projectDataSheetSelection(input: DataSheetSelectionContext): SurfaceSelection | null {
82
+ const ranges = input.ranges.filter(range => range.startRow <= range.endRow && range.startColumn <= range.endColumn)
83
+ if (ranges.length === 0) return null
84
+ const count = ranges.reduce((total, range) => total
85
+ + (range.endRow - range.startRow + 1) * (range.endColumn - range.startColumn + 1), 0)
86
+ const items = ranges.slice(0, 40).map(range => {
87
+ const address = rangeAddress(range)
88
+ return {
89
+ id: dataSheetFocusId({ workbookId: input.workbookId, ...range }),
90
+ type: 'range',
91
+ label: address,
92
+ ...(previewText(range.values) === undefined ? {} : { text: previewText(range.values) }),
93
+ attributes: {
94
+ workbookId: input.workbookId,
95
+ sheetId: range.sheetId,
96
+ sheetName: range.sheetName,
97
+ address,
98
+ startRow: range.startRow,
99
+ startColumn: range.startColumn,
100
+ endRow: range.endRow,
101
+ endColumn: range.endColumn,
102
+ },
103
+ }
104
+ })
105
+ const labels = ranges.map(rangeAddress)
106
+ const summary = ranges.length === 1
107
+ ? `已选中 ${labels[0]},共 ${count} 个单元格`
108
+ : `已选中 ${ranges.length} 个区域,共 ${count} 个单元格`
109
+ const note = `工作簿 ID:${input.workbookId};选区:${labels.join('、')}。条目文本最多预览每个区域前 10 行 × 20 列;完整数据用 workbook_read(workbookId="${input.workbookId}", target={kind:"range", sheetId, range}) 读取。写入前先读取最新 revision,再用 workbook_edit。`
110
+ return {
111
+ source: DATA_SHEET_SURFACE_ID,
112
+ revision: input.revision,
113
+ count,
114
+ countLabel: `${count} 个单元格`,
115
+ title: 'Table',
116
+ summary,
117
+ items,
118
+ ...(ranges.length > items.length ? { truncated: true } : {}),
119
+ resource: { name: input.workbookId, display: input.workbookName, version: input.documentRevision },
120
+ notes: [note],
121
+ capabilities: [`使用 workbook_read / workbook_edit 操作工作簿 ${input.workbookId};写入时传该 id 和最新 revision。`],
122
+ }
123
+ }
@@ -0,0 +1,13 @@
1
+ /** One live workbook view can honour focus requests from the shared bridge chip. */
2
+ let focusHandler: ((itemId: string) => void) | undefined
3
+
4
+ export function registerDataSheetFocusHandler(handler: (itemId: string) => void): () => void {
5
+ focusHandler = handler
6
+ return () => {
7
+ if (focusHandler === handler) focusHandler = undefined
8
+ }
9
+ }
10
+
11
+ export function focusDataSheetItem(itemId: string): void {
12
+ focusHandler?.(itemId)
13
+ }
@@ -0,0 +1,355 @@
1
+ import type {
2
+ WorkbookLocalChange,
3
+ WorkbookOperation,
4
+ WorkbookSummary,
5
+ WorkbookWorktree,
6
+ WorkbookWorktreeSummary,
7
+ WorkbookChange,
8
+ WorkbookPresencePayload,
9
+ WorkbookHistoryVersion,
10
+ } from '../../types.ts'
11
+
12
+ export class HttpStatusError extends Error {
13
+ constructor(readonly status: number, message: string, readonly code?: string, readonly details?: unknown) {
14
+ super(message)
15
+ this.name = 'HttpStatusError'
16
+ }
17
+ }
18
+
19
+ export class RevisionConflictError extends HttpStatusError {
20
+ constructor(message = 'workbook revision conflict', details?: unknown) {
21
+ super(409, message, 'REVISION_CONFLICT', details)
22
+ this.name = 'RevisionConflictError'
23
+ }
24
+ }
25
+
26
+ const SESSION_STORAGE_KEY = 'data-sheet.session-id'
27
+ let cachedSessionId: string | null = null
28
+
29
+ /**
30
+ * The DSH Session whose project this page reads and writes.
31
+ *
32
+ * Set once when the panel mounts (the sidebar hands its Session id to the body)
33
+ * or, for the standalone page, read from its URL. Every workbook path is
34
+ * resolved by the Host inside that Session's project —
35
+ * `<project>/storages/data-table` — so a request that names the wrong Session
36
+ * reads and writes another project's workbooks. A request that names **no**
37
+ * Session keeps the pre-project per-user store instead, which is right for a
38
+ * script and wrong for a page: the panel always sets this.
39
+ */
40
+ let dshSessionId: string | null = null
41
+
42
+ /** Tell the transport which DSH Session this page belongs to. */
43
+ export function setDshSessionId(id: string | undefined): void {
44
+ dshSessionId = id === undefined || id.length === 0 ? null : id
45
+ }
46
+
47
+ /** The Session this page is bound to, when it is bound to one. */
48
+ export function currentDshSessionId(): string | null {
49
+ return dshSessionId
50
+ }
51
+
52
+ /** Route browser requests through the official Electron Connection carrier. */
53
+ export function dataSheetClientPath(path: string): string {
54
+ const transport = (globalThis as { __DSH_TRANSPORT__?: { ownsHost?: boolean } }).__DSH_TRANSPORT__
55
+ return transport?.ownsHost === true
56
+ ? `/api/data-sheet?path=${encodeURIComponent(path)}`
57
+ : path
58
+ }
59
+
60
+ /** Stable per-tab scope used by worktree APIs and Host audit logs. */
61
+ export function dataSheetSessionId(): string {
62
+ if (cachedSessionId !== null) return cachedSessionId
63
+ try {
64
+ const existing = window.sessionStorage.getItem(SESSION_STORAGE_KEY)
65
+ if (existing !== null && existing.trim().length > 0) {
66
+ cachedSessionId = existing
67
+ return existing
68
+ }
69
+ const generated = typeof crypto !== 'undefined' && typeof crypto.randomUUID === 'function'
70
+ ? crypto.randomUUID()
71
+ : `${Date.now()}-${Math.random().toString(36).slice(2)}`
72
+ window.sessionStorage.setItem(SESSION_STORAGE_KEY, generated)
73
+ cachedSessionId = generated
74
+ return generated
75
+ } catch {
76
+ cachedSessionId = `ephemeral-${Date.now()}-${Math.random().toString(36).slice(2)}`
77
+ return cachedSessionId
78
+ }
79
+ }
80
+
81
+ /**
82
+ * The live collaboration stream for one workbook.
83
+ *
84
+ * `EventSource` cannot set the `x-dsh-session` header every other call carries,
85
+ * so the Session — which decides *which project's* workbook this is — travels as
86
+ * a query parameter it can carry. The `session` parameter is a different thing:
87
+ * this browser tab's own id, used for presence and worktrees.
88
+ *
89
+ * @param workbookId - Workbook to watch.
90
+ * @param after - Revision to resume from.
91
+ * @returns a same-origin path, ready for `dataSheetClientPath`.
92
+ */
93
+ export function collaborationStreamPath(workbookId: string, after: number): string {
94
+ const project = currentDshSessionId()
95
+ const scope = project === null ? '' : `&sessionId=${encodeURIComponent(project)}`
96
+ return `/data-sheet/workbook/${encodeURIComponent(workbookId)}/collaboration?after=${after}`
97
+ + `&session=${encodeURIComponent(dataSheetSessionId())}${scope}`
98
+ }
99
+
100
+ /**
101
+ * The standalone page for one workbook.
102
+ *
103
+ * A new tab has no sidebar to hand it a Session id, so the project travels in
104
+ * the URL: without it the page resolves against the sandbox policy root and
105
+ * could open another project's workbook.
106
+ *
107
+ * @param workbookId - Workbook to open in a plain browser document.
108
+ * @returns a same-origin path.
109
+ */
110
+ export function standaloneWorkbookPath(workbookId: string): string {
111
+ const project = currentDshSessionId()
112
+ return `/data-sheet/workbook/${encodeURIComponent(workbookId)}/view`
113
+ + (project === null ? '' : `?sessionId=${encodeURIComponent(project)}`)
114
+ }
115
+
116
+ async function requestJson<T>(path: string, init: RequestInit = {}): Promise<T> {
117
+ const headers = new Headers(init.headers)
118
+ headers.set('accept', 'application/json')
119
+ headers.set('x-data-sheet-session', dataSheetSessionId())
120
+ // Which *project* the Host should resolve the path inside. Distinct from the
121
+ // header above, which is this browser tab's own id for worktrees and presence.
122
+ if (dshSessionId !== null) headers.set('x-dsh-session', dshSessionId)
123
+ if (init.body !== undefined && !headers.has('content-type')) headers.set('content-type', 'application/json')
124
+ let response: Response | null = null
125
+ let networkError: unknown = null
126
+ // GETs and writes carrying an opId are safe to retry: the Host's idempotency
127
+ // check makes a lost response indistinguishable from a successful retry.
128
+ const retryable = init.method === undefined || init.method === 'GET' || init.method === 'HEAD'
129
+ || typeof init.body === 'string' && init.body.includes('"opId"')
130
+ for (let attempt = 0; attempt < (retryable ? 3 : 1); attempt += 1) {
131
+ try {
132
+ response = await fetch(dataSheetClientPath(path), { ...init, headers })
133
+ networkError = null
134
+ break
135
+ } catch (error) {
136
+ networkError = error
137
+ if (attempt + 1 < (retryable ? 3 : 1)) await new Promise(resolve => window.setTimeout(resolve, 150 * (attempt + 1)))
138
+ }
139
+ }
140
+ if (response === null) {
141
+ const detail = networkError instanceof Error ? networkError.message : ''
142
+ throw new HttpStatusError(0, detail === 'Failed to fetch' ? '网络请求失败,请确认 DSH 服务仍在运行后重试' : (detail || '网络请求失败'), 'NETWORK_ERROR', { detail })
143
+ }
144
+ let body: unknown = null
145
+ try { body = await response.json() } catch { /* non-json host/proxy failure */ }
146
+ if (!response.ok) {
147
+ const record = typeof body === 'object' && body !== null ? body as Record<string, unknown> : {}
148
+ const message = typeof record.error === 'string' ? record.error
149
+ : typeof record.message === 'string' ? record.message
150
+ : `${response.status} ${response.statusText || '请求失败'}`
151
+ const code = typeof record.code === 'string' ? record.code : undefined
152
+ if (response.status === 409 || code === 'REVISION_CONFLICT') throw new RevisionConflictError(message, body)
153
+ throw new HttpStatusError(response.status, message, code, body)
154
+ }
155
+ return body as T
156
+ }
157
+
158
+ export interface WorkbookEnvelope {
159
+ ok?: boolean
160
+ workbookId?: string
161
+ revision?: number
162
+ updatedAt?: string
163
+ snapshot?: object
164
+ }
165
+
166
+ export interface ChangesEnvelope {
167
+ revision?: number
168
+ operations?: WorkbookOperation[]
169
+ requiresSnapshot?: boolean
170
+ snapshot?: object
171
+ }
172
+
173
+ export interface HistoryEntry {
174
+ opId: string
175
+ revision: number
176
+ parentRevision?: number
177
+ actor: 'agent' | 'user'
178
+ type: string
179
+ timestamp: string
180
+ summary: string
181
+ canPreview?: boolean
182
+ }
183
+
184
+ export interface HistoryEnvelope {
185
+ entries?: HistoryEntry[]
186
+ versions?: WorkbookHistoryVersion[]
187
+ checkpoints?: number[]
188
+ revision?: number
189
+ }
190
+
191
+ export interface HistoryDiffEnvelope {
192
+ workbookId?: string
193
+ from?: number
194
+ to?: number
195
+ changes?: WorkbookChange[]
196
+ /** True when the server stopped at its diff limit, so the counts are a floor. */
197
+ truncated?: boolean
198
+ }
199
+
200
+ export interface WorkbookMutationEnvelope {
201
+ workbook?: {
202
+ id: string
203
+ name: string
204
+ sheets?: WorkbookSummary['sheets']
205
+ updatedAt?: string
206
+ }
207
+ }
208
+
209
+ export async function fetchWorkbook(id: string): Promise<WorkbookEnvelope> {
210
+ const value = await requestJson<WorkbookEnvelope | object>(`/data-sheet/workbook/${encodeURIComponent(id)}.json`)
211
+ if (typeof value === 'object' && value !== null && 'snapshot' in value) return value as WorkbookEnvelope
212
+ return { ok: true, workbookId: id, revision: 0, snapshot: value as object }
213
+ }
214
+
215
+ /** One entry of the project file chooser's listing; mirrors the Host's shape. */
216
+ export interface ProjectFileEntry {
217
+ name: string
218
+ path: string
219
+ type: 'file' | 'directory'
220
+ size: number
221
+ createdAt: string
222
+ modifiedAt: string
223
+ }
224
+
225
+ /** One directory of `<project>/storages`, with sizes and dates. */
226
+ export async function fetchProjectFiles(directory: string): Promise<{ directory: string; entries: ProjectFileEntry[]; truncated?: number }> {
227
+ return requestJson(`/data-sheet/project-files.json?dir=${encodeURIComponent(directory)}`)
228
+ }
229
+
230
+ /**
231
+ * Read one file the chooser listed.
232
+ *
233
+ * The browser cannot open a path, so the Host reads it and the bytes arrive here
234
+ * as a buffer, ready for the import pipeline that already existed.
235
+ */
236
+ export async function readProjectFile(path: string): Promise<{ name: string; data: ArrayBuffer }> {
237
+ const headers = new Headers({ accept: 'application/octet-stream' })
238
+ if (dshSessionId !== null) headers.set('x-dsh-session', dshSessionId)
239
+ const response = await fetch(dataSheetClientPath(`/data-sheet/project-file.json?path=${encodeURIComponent(path)}`), { headers })
240
+ if (!response.ok) {
241
+ const body = await response.json().catch(() => null) as { error?: unknown } | null
242
+ throw new HttpStatusError(response.status, typeof body?.error === 'string' ? body.error : `读取文件失败:${response.status}`)
243
+ }
244
+ const encoded = response.headers.get('x-file-name')
245
+ const name = encoded === null ? (path.split('/').pop() ?? path) : decodeURIComponent(encoded)
246
+ return { name, data: await response.arrayBuffer() }
247
+ }
248
+
249
+ export async function fetchWorkbooks(): Promise<{ workbooks?: WorkbookSummary[] }> {
250
+ return requestJson('/data-sheet/workbooks.json')
251
+ }
252
+
253
+ export async function createWorkbook(name: string): Promise<WorkbookMutationEnvelope> {
254
+ return requestJson('/data-sheet/workbooks.json', {
255
+ method: 'POST',
256
+ body: JSON.stringify({ action: 'create', name }),
257
+ })
258
+ }
259
+
260
+ export async function renameWorkbook(id: string, name: string): Promise<WorkbookMutationEnvelope> {
261
+ return requestJson('/data-sheet/workbooks.json', {
262
+ method: 'POST',
263
+ body: JSON.stringify({ action: 'rename', workbookId: id, name }),
264
+ })
265
+ }
266
+
267
+ export async function fetchChanges(id: string, after: number): Promise<ChangesEnvelope> {
268
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/changes?after=${after}`)
269
+ }
270
+
271
+ export async function fetchHistory(id: string): Promise<HistoryEnvelope> {
272
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/history`)
273
+ }
274
+
275
+ export interface HistorySnapshotEnvelope {
276
+ revision?: number
277
+ snapshot?: object
278
+ step?: number
279
+ stepCount?: number
280
+ /** True when the Step's change list stopped at the server's limit. */
281
+ truncated?: boolean
282
+ /**
283
+ * What the requested Step changed. Present for Step previews, so the canvas
284
+ * can locate and tint the cells instead of leaving the reader to find them
285
+ * (PRD v3.3 §7.2).
286
+ */
287
+ changes?: WorkbookChange[]
288
+ }
289
+
290
+ export async function fetchHistorySnapshot(id: string, revision: number, step?: number): Promise<HistorySnapshotEnvelope> {
291
+ const query = step === undefined ? `revision=${revision}` : `revision=${revision}&step=${step}`
292
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/history?${query}`)
293
+ }
294
+
295
+ export async function fetchHistoryDiff(id: string, from: number, to: number): Promise<HistoryDiffEnvelope> {
296
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/history/diff?from=${from}&to=${to}`)
297
+ }
298
+
299
+ export async function rollbackWorkbook(id: string, targetRevision: number, baseRevision: number, summary?: string): Promise<{ revision?: number }> {
300
+ return requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}/rollback`, {
301
+ method: 'POST',
302
+ body: JSON.stringify({ targetRevision, baseRevision, ...(summary === undefined ? {} : { summary }) }),
303
+ })
304
+ }
305
+
306
+ export async function deleteWorkbook(id: string): Promise<void> {
307
+ await requestJson(`/data-sheet/workbook/${encodeURIComponent(id)}.json?action=delete`, { method: 'POST', body: JSON.stringify({ confirm: true }) })
308
+ }
309
+
310
+ export interface SaveResult {
311
+ revision: number
312
+ operation?: WorkbookOperation
313
+ rebasedOperations?: WorkbookOperation[]
314
+ }
315
+
316
+ export async function saveOperationChanges(id: string, changes: WorkbookLocalChange[], baseRevision: number, opId: string, version?: { id: string; status: 'open' | 'final' }): Promise<SaveResult | null> {
317
+ const operation = changes.length === 1 ? changes[0] : { type: 'batch' as const, payload: { operations: changes } }
318
+ const body = JSON.stringify({ type: operation.type, payload: operation.payload, actor: 'user', baseRevision, opId, ...(version === undefined ? {} : { versionId: version.id, versionStatus: version.status }) })
319
+ return requestJson<SaveResult | null>(`/data-sheet/workbook/${encodeURIComponent(id)}/operations`, {
320
+ method: 'POST',
321
+ ...(body.length <= 60 * 1024 ? { keepalive: true } : {}),
322
+ body,
323
+ })
324
+ }
325
+
326
+ export interface SnapshotSaveOptions {
327
+ /** Named save: the Version gets this summary and closes the session. */
328
+ summary?: string
329
+ /** Session Step: joins the open user Version (PRD v3.1 §4). */
330
+ versionId?: string
331
+ versionStatus?: 'open' | 'final'
332
+ versionKind?: 'named' | 'auto'
333
+ }
334
+
335
+ export async function saveSnapshot(id: string, snapshotData: unknown, baseRevision: number, opId: string, options: SnapshotSaveOptions = {}): Promise<SaveResult | null> {
336
+ return requestJson<SaveResult | null>(`/data-sheet/workbook/${encodeURIComponent(id)}.json`, {
337
+ method: 'POST',
338
+ body: JSON.stringify({
339
+ snapshot: snapshotData,
340
+ baseRevision,
341
+ opId,
342
+ ...(options.summary === undefined ? {} : { versionSummary: options.summary }),
343
+ ...(options.versionId === undefined ? {} : { versionId: options.versionId }),
344
+ ...(options.versionStatus === undefined ? {} : { versionStatus: options.versionStatus }),
345
+ ...(options.versionKind === undefined ? {} : { versionKind: options.versionKind }),
346
+ }),
347
+ })
348
+ }
349
+
350
+ export async function publishPresence(workbookId: string, presence: WorkbookPresencePayload): Promise<void> {
351
+ await requestJson(`/data-sheet/workbook/${encodeURIComponent(workbookId)}/presence`, {
352
+ method: 'POST',
353
+ body: JSON.stringify(presence),
354
+ })
355
+ }
@@ -0,0 +1,110 @@
1
+ .toolRow {
2
+ display: flex;
3
+ flex-direction: column;
4
+ gap: 6px;
5
+ width: 100%;
6
+ }
7
+
8
+ .toolLine {
9
+ display: flex;
10
+ align-items: baseline;
11
+ gap: 8px;
12
+ font-size: 12px;
13
+ opacity: 0.8;
14
+ }
15
+
16
+ .toolName {
17
+ font-weight: 600;
18
+ }
19
+
20
+ .toolState {
21
+ opacity: 0.7;
22
+ }
23
+
24
+ .view {
25
+ display: flex;
26
+ flex-direction: column;
27
+ /* The inline tool row has an auto-height parent, where `height: 100%`
28
+ resolves to auto and would collapse the flex body to 0; floor the
29
+ sheet height so the embedded table always has room. The rail panel
30
+ (definite-height parent) still fills via `height: 100%`. */
31
+ height: 100%;
32
+ min-height: 320px;
33
+ border: 1px solid var(--tita-line);
34
+ border-radius: 8px;
35
+ overflow: hidden;
36
+ background: var(--tita-surface);
37
+ }
38
+
39
+ .bar {
40
+ display: flex;
41
+ align-items: center;
42
+ gap: 8px;
43
+ padding: 6px 10px;
44
+ border-bottom: 1px solid var(--tita-line);
45
+ font-size: 12px;
46
+ flex-shrink: 0;
47
+ }
48
+
49
+ .title {
50
+ font-weight: 600;
51
+ overflow: hidden;
52
+ text-overflow: ellipsis;
53
+ white-space: nowrap;
54
+ }
55
+
56
+ .dimensions {
57
+ opacity: 0.65;
58
+ margin-right: auto;
59
+ }
60
+
61
+ .button {
62
+ padding: 2px 10px;
63
+ border: 1px solid var(--tita-line-strong);
64
+ border-radius: 6px;
65
+ background: transparent;
66
+ font-size: 12px;
67
+ cursor: pointer;
68
+ }
69
+
70
+ .button:hover {
71
+ background: var(--tita-surface-muted);
72
+ }
73
+
74
+ .body {
75
+ position: relative;
76
+ flex: 1;
77
+ min-height: 0;
78
+ }
79
+
80
+ .hint {
81
+ padding: 14px;
82
+ font-size: 12px;
83
+ color: var(--tita-ink-muted);
84
+ text-align: center;
85
+ }
86
+
87
+ .hintSub {
88
+ margin-top: 4px;
89
+ opacity: 0.7;
90
+ }
91
+
92
+ .univer {
93
+ position: absolute;
94
+ inset: 0;
95
+ height: 100%;
96
+ width: 100%;
97
+ }
98
+
99
+ .toggle {
100
+ padding: 3px 10px;
101
+ border: 1px solid var(--tita-line-strong);
102
+ border-radius: 6px;
103
+ background: transparent;
104
+ font-size: 12px;
105
+ cursor: pointer;
106
+ }
107
+
108
+ .toggle:hover {
109
+ background: var(--tita-surface-muted);
110
+ }
@@ -0,0 +1,8 @@
1
+ .root {
2
+ width: 100vw;
3
+ height: 100vh;
4
+ min-width: 760px;
5
+ min-height: 520px;
6
+ overflow: hidden;
7
+ background: var(--tita-canvas);
8
+ }
@@ -0,0 +1,15 @@
1
+ import { WorkbookWorkspace } from '../SheetPanel.tsx'
2
+ import styles from './StandaloneWorkbookPage.module.css'
3
+
4
+ export interface StandaloneWorkbookPageProps {
5
+ workbookId: string
6
+ }
7
+
8
+ /** Full-browser workbook workspace. It deliberately reuses the embedded controller. */
9
+ export function StandaloneWorkbookPage({ workbookId }: StandaloneWorkbookPageProps) {
10
+ return (
11
+ <main className={styles.root}>
12
+ <WorkbookWorkspace open standalone initialWorkbookId={workbookId} />
13
+ </main>
14
+ )
15
+ }