@springbrand/file-renderers 0.0.0-stage → 0.1.0-alpha.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 (259) hide show
  1. package/README.md +266 -2
  2. package/dist/file-renderers/src/attachment-format.d.ts +17 -0
  3. package/dist/file-renderers/src/attachment-format.js +180 -0
  4. package/dist/file-renderers/src/attachment.d.ts +17 -0
  5. package/dist/file-renderers/src/attachment.js +96 -0
  6. package/dist/file-renderers/src/code/index.d.ts +19 -0
  7. package/dist/file-renderers/src/code/index.js +72 -0
  8. package/dist/file-renderers/src/code/mime.d.ts +4 -0
  9. package/dist/file-renderers/src/code/mime.js +48 -0
  10. package/dist/file-renderers/src/code/source.module.css +7 -0
  11. package/dist/file-renderers/src/config/index.d.ts +30 -0
  12. package/dist/file-renderers/src/config/index.js +88 -0
  13. package/dist/file-renderers/src/config/limits.d.ts +14 -0
  14. package/dist/file-renderers/src/config/limits.js +39 -0
  15. package/dist/file-renderers/src/config/mime.d.ts +4 -0
  16. package/dist/file-renderers/src/config/mime.js +25 -0
  17. package/dist/file-renderers/src/config/toml.d.ts +2 -0
  18. package/dist/file-renderers/src/config/toml.js +24 -0
  19. package/dist/file-renderers/src/config/xml.d.ts +2 -0
  20. package/dist/file-renderers/src/config/xml.js +59 -0
  21. package/dist/file-renderers/src/config/yaml.d.ts +10 -0
  22. package/dist/file-renderers/src/config/yaml.js +60 -0
  23. package/dist/file-renderers/src/contracts.d.ts +155 -0
  24. package/dist/file-renderers/src/contracts.js +25 -0
  25. package/dist/file-renderers/src/csv/csv.module.css +62 -0
  26. package/dist/file-renderers/src/csv/index.d.ts +48 -0
  27. package/dist/file-renderers/src/csv/index.js +345 -0
  28. package/dist/file-renderers/src/csv/mime.d.ts +4 -0
  29. package/dist/file-renderers/src/csv/mime.js +19 -0
  30. package/dist/file-renderers/src/docx/accessible-view.d.ts +4 -0
  31. package/dist/file-renderers/src/docx/accessible-view.js +90 -0
  32. package/dist/file-renderers/src/docx/index.d.ts +25 -0
  33. package/dist/file-renderers/src/docx/index.js +124 -0
  34. package/dist/file-renderers/src/docx/mime.d.ts +4 -0
  35. package/dist/file-renderers/src/docx/mime.js +16 -0
  36. package/dist/file-renderers/src/drawio/index.d.ts +16 -0
  37. package/dist/file-renderers/src/drawio/index.js +106 -0
  38. package/dist/file-renderers/src/drawio/mime.d.ts +6 -0
  39. package/dist/file-renderers/src/drawio/mime.js +24 -0
  40. package/dist/file-renderers/src/drawio/parse.d.ts +33 -0
  41. package/dist/file-renderers/src/drawio/parse.js +213 -0
  42. package/dist/file-renderers/src/excalidraw/index.d.ts +23 -0
  43. package/dist/file-renderers/src/excalidraw/index.js +128 -0
  44. package/dist/file-renderers/src/excalidraw/mime.d.ts +6 -0
  45. package/dist/file-renderers/src/excalidraw/mime.js +20 -0
  46. package/dist/file-renderers/src/file-format.d.ts +11 -0
  47. package/dist/file-renderers/src/file-format.js +117 -0
  48. package/dist/file-renderers/src/file-name.d.ts +2 -0
  49. package/dist/file-renderers/src/file-name.js +10 -0
  50. package/dist/file-renderers/src/glb/index.d.ts +17 -0
  51. package/dist/file-renderers/src/glb/index.js +108 -0
  52. package/dist/file-renderers/src/glb/mime.d.ts +6 -0
  53. package/dist/file-renderers/src/glb/mime.js +17 -0
  54. package/dist/file-renderers/src/glb/parse.d.ts +4 -0
  55. package/dist/file-renderers/src/glb/parse.js +170 -0
  56. package/dist/file-renderers/src/html/index.d.ts +15 -0
  57. package/dist/file-renderers/src/html/index.js +43 -0
  58. package/dist/file-renderers/src/html/mime.d.ts +4 -0
  59. package/dist/file-renderers/src/html/mime.js +20 -0
  60. package/dist/file-renderers/src/html/preview-project.d.ts +16 -0
  61. package/dist/file-renderers/src/html/preview-project.js +531 -0
  62. package/dist/file-renderers/src/html/preview.d.ts +8 -0
  63. package/dist/file-renderers/src/html/preview.js +29 -0
  64. package/dist/file-renderers/src/image/index.d.ts +20 -0
  65. package/dist/file-renderers/src/image/index.js +75 -0
  66. package/dist/file-renderers/src/image/mime.d.ts +5 -0
  67. package/dist/file-renderers/src/image/mime.js +34 -0
  68. package/dist/file-renderers/src/index.d.ts +27 -0
  69. package/dist/file-renderers/src/index.js +27 -0
  70. package/dist/file-renderers/src/json/index.d.ts +22 -0
  71. package/dist/file-renderers/src/json/index.js +69 -0
  72. package/dist/file-renderers/src/json/json.typecheck.d.ts +1 -0
  73. package/dist/file-renderers/src/json/json.typecheck.js +24 -0
  74. package/dist/file-renderers/src/json/jsonl.d.ts +15 -0
  75. package/dist/file-renderers/src/json/jsonl.js +43 -0
  76. package/dist/file-renderers/src/json/mime.d.ts +10 -0
  77. package/dist/file-renderers/src/json/mime.js +39 -0
  78. package/dist/file-renderers/src/json/parse.d.ts +52 -0
  79. package/dist/file-renderers/src/json/parse.js +179 -0
  80. package/dist/file-renderers/src/markdown/index.d.ts +19 -0
  81. package/dist/file-renderers/src/markdown/index.js +235 -0
  82. package/dist/file-renderers/src/markdown/mime.d.ts +3 -0
  83. package/dist/file-renderers/src/markdown/mime.js +12 -0
  84. package/dist/file-renderers/src/media/index.d.ts +25 -0
  85. package/dist/file-renderers/src/media/index.js +80 -0
  86. package/dist/file-renderers/src/media/mime.d.ts +8 -0
  87. package/dist/file-renderers/src/media/mime.js +61 -0
  88. package/dist/file-renderers/src/media/validate.d.ts +2 -0
  89. package/dist/file-renderers/src/media/validate.js +225 -0
  90. package/dist/file-renderers/src/mermaid/asset.d.ts +4 -0
  91. package/dist/file-renderers/src/mermaid/frame.d.ts +2 -0
  92. package/dist/file-renderers/src/mermaid/frame.js +65 -0
  93. package/dist/file-renderers/src/mermaid/index.d.ts +16 -0
  94. package/dist/file-renderers/src/mermaid/index.js +44 -0
  95. package/dist/file-renderers/src/mermaid/mime.d.ts +6 -0
  96. package/dist/file-renderers/src/mermaid/mime.js +19 -0
  97. package/dist/file-renderers/src/notebook/index.d.ts +24 -0
  98. package/dist/file-renderers/src/notebook/index.js +65 -0
  99. package/dist/file-renderers/src/notebook/mime.d.ts +4 -0
  100. package/dist/file-renderers/src/notebook/mime.js +9 -0
  101. package/dist/file-renderers/src/notebook/notebook.module.css +82 -0
  102. package/dist/file-renderers/src/notebook/parse.d.ts +33 -0
  103. package/dist/file-renderers/src/notebook/parse.js +138 -0
  104. package/dist/file-renderers/src/office/preview.d.ts +19 -0
  105. package/dist/file-renderers/src/office/preview.js +69 -0
  106. package/dist/file-renderers/src/office/preview.module.css +143 -0
  107. package/dist/file-renderers/src/office/test-fixture.d.ts +1 -0
  108. package/dist/file-renderers/src/office/test-fixture.js +8 -0
  109. package/dist/file-renderers/src/pdf/index.d.ts +23 -0
  110. package/dist/file-renderers/src/pdf/index.js +95 -0
  111. package/dist/file-renderers/src/pdf/mime.d.ts +4 -0
  112. package/dist/file-renderers/src/pdf/mime.js +17 -0
  113. package/dist/file-renderers/src/pdf/pdf.module.css +6 -0
  114. package/dist/file-renderers/src/ppt/index.d.ts +21 -0
  115. package/dist/file-renderers/src/ppt/index.js +69 -0
  116. package/dist/file-renderers/src/ppt/mime.d.ts +4 -0
  117. package/dist/file-renderers/src/ppt/mime.js +16 -0
  118. package/dist/file-renderers/src/preview-content.d.ts +20 -0
  119. package/dist/file-renderers/src/preview-content.js +128 -0
  120. package/dist/file-renderers/src/preview-error-boundary.d.ts +16 -0
  121. package/dist/file-renderers/src/preview-error-boundary.js +20 -0
  122. package/dist/file-renderers/src/preview-host.d.ts +97 -0
  123. package/dist/file-renderers/src/preview-host.js +358 -0
  124. package/dist/file-renderers/src/preview-surface.d.ts +26 -0
  125. package/dist/file-renderers/src/preview-surface.js +61 -0
  126. package/dist/file-renderers/src/registry.d.ts +31 -0
  127. package/dist/file-renderers/src/registry.js +29 -0
  128. package/dist/file-renderers/src/static-svg-frame.d.ts +4 -0
  129. package/dist/file-renderers/src/static-svg-frame.js +14 -0
  130. package/dist/file-renderers/src/structured-tree/tree.d.ts +22 -0
  131. package/dist/file-renderers/src/structured-tree/tree.js +62 -0
  132. package/dist/file-renderers/src/structured-tree/tree.module.css +157 -0
  133. package/dist/file-renderers/src/styles.d.ts +6 -0
  134. package/dist/file-renderers/src/svg/index.d.ts +17 -0
  135. package/dist/file-renderers/src/svg/index.js +43 -0
  136. package/dist/file-renderers/src/svg/mime.d.ts +6 -0
  137. package/dist/file-renderers/src/svg/mime.js +17 -0
  138. package/dist/file-renderers/src/svg/parse.d.ts +9 -0
  139. package/dist/file-renderers/src/svg/parse.js +218 -0
  140. package/dist/file-renderers/src/svg/svg.typecheck.d.ts +1 -0
  141. package/dist/file-renderers/src/svg/svg.typecheck.js +16 -0
  142. package/dist/file-renderers/src/wasm/index.d.ts +17 -0
  143. package/dist/file-renderers/src/wasm/index.js +44 -0
  144. package/dist/file-renderers/src/wasm/mime.d.ts +6 -0
  145. package/dist/file-renderers/src/wasm/mime.js +17 -0
  146. package/dist/file-renderers/src/wasm/parse.d.ts +16 -0
  147. package/dist/file-renderers/src/wasm/parse.js +154 -0
  148. package/dist/file-renderers/src/wasm/wasm.typecheck.d.ts +1 -0
  149. package/dist/file-renderers/src/wasm/wasm.typecheck.js +16 -0
  150. package/dist/file-renderers/src/xlsx/accessible-view.d.ts +4 -0
  151. package/dist/file-renderers/src/xlsx/accessible-view.js +181 -0
  152. package/dist/file-renderers/src/xlsx/index.d.ts +21 -0
  153. package/dist/file-renderers/src/xlsx/index.js +102 -0
  154. package/dist/file-renderers/src/xlsx/mime.d.ts +4 -0
  155. package/dist/file-renderers/src/xlsx/mime.js +16 -0
  156. package/dist/file-renderers/src/zip/index.d.ts +22 -0
  157. package/dist/file-renderers/src/zip/index.js +88 -0
  158. package/dist/file-renderers/src/zip/mime.d.ts +4 -0
  159. package/dist/file-renderers/src/zip/mime.js +10 -0
  160. package/dist/file-renderers/src/zip/parse.d.ts +17 -0
  161. package/dist/file-renderers/src/zip/parse.js +54 -0
  162. package/dist/utils/src/attachment.d.ts +12 -0
  163. package/dist/utils/src/attachment.js +50 -0
  164. package/dist/utils/src/base64.d.ts +3 -0
  165. package/dist/utils/src/base64.js +27 -0
  166. package/dist/utils/src/content-hash.d.ts +5 -0
  167. package/dist/utils/src/content-hash.js +27 -0
  168. package/dist/utils/src/database.d.ts +1 -0
  169. package/dist/utils/src/database.js +9 -0
  170. package/dist/utils/src/html.d.ts +2 -0
  171. package/dist/utils/src/html.js +9 -0
  172. package/dist/utils/src/image.d.ts +33 -0
  173. package/dist/utils/src/image.js +35 -0
  174. package/dist/utils/src/index.d.ts +12 -0
  175. package/dist/utils/src/index.js +12 -0
  176. package/dist/utils/src/ip-address.d.ts +6 -0
  177. package/dist/utils/src/ip-address.js +11 -0
  178. package/dist/utils/src/json.d.ts +6 -0
  179. package/dist/utils/src/json.js +18 -0
  180. package/dist/utils/src/ooxml.d.ts +14 -0
  181. package/dist/utils/src/ooxml.js +170 -0
  182. package/dist/utils/src/serialized-size.d.ts +9 -0
  183. package/dist/utils/src/serialized-size.js +11 -0
  184. package/dist/utils/src/strict-zip.d.ts +68 -0
  185. package/dist/utils/src/strict-zip.js +177 -0
  186. package/dist/utils/src/uuid.d.ts +9 -0
  187. package/dist/utils/src/uuid.js +27 -0
  188. package/node_modules/@silurus/ooxml/LICENSE +21 -0
  189. package/node_modules/@silurus/ooxml/README.md +1169 -0
  190. package/node_modules/@silurus/ooxml/THIRD_PARTY_NOTICES.md +369 -0
  191. package/node_modules/@silurus/ooxml/dist/assets/render-worker-1uPAxMZr.js +31 -0
  192. package/node_modules/@silurus/ooxml/dist/assets/render-worker-BlvJybji.js +10 -0
  193. package/node_modules/@silurus/ooxml/dist/assets/render-worker-CzmiJfFL.js +11 -0
  194. package/node_modules/@silurus/ooxml/dist/bounded-raw-part-cache-Cg1BsSZQ.js +119 -0
  195. package/node_modules/@silurus/ooxml/dist/canvas-viewer-mechanics-DbLz0rYW.js +1384 -0
  196. package/node_modules/@silurus/ooxml/dist/chart-ex.mjs +982 -0
  197. package/node_modules/@silurus/ooxml/dist/chunk-DmhlhrBa.js +11 -0
  198. package/node_modules/@silurus/ooxml/dist/cjk-fallback-bLI3z70h.js +46 -0
  199. package/node_modules/@silurus/ooxml/dist/comment-occurrence-C8Y2uoVr.js +6 -0
  200. package/node_modules/@silurus/ooxml/dist/comment-ui-runtime-BsK8pmUT.js +2 -0
  201. package/node_modules/@silurus/ooxml/dist/comment-ui-runtime-CWGqn5Vr.js +112 -0
  202. package/node_modules/@silurus/ooxml/dist/comment-ui-runtime-DcJOYlw-.js +171 -0
  203. package/node_modules/@silurus/ooxml/dist/comments-Diid5ujN.js +246 -0
  204. package/node_modules/@silurus/ooxml/dist/delimited-text-worker-host-BVY-Ecni.js +27 -0
  205. package/node_modules/@silurus/ooxml/dist/document-pull-client-B9PTM-pL.js +27280 -0
  206. package/node_modules/@silurus/ooxml/dist/docx-BF9WXrYH.js +3103 -0
  207. package/node_modules/@silurus/ooxml/dist/docx.mjs +7 -0
  208. package/node_modules/@silurus/ooxml/dist/docx_parser_bg.wasm +0 -0
  209. package/node_modules/@silurus/ooxml/dist/dom-interaction-boundary-Cnm8DSBN.js +223 -0
  210. package/node_modules/@silurus/ooxml/dist/highlight-rect-C5hkRqUr.js +13 -0
  211. package/node_modules/@silurus/ooxml/dist/hyperlink-Cxzhet30.js +36446 -0
  212. package/node_modules/@silurus/ooxml/dist/index.mjs +4 -0
  213. package/node_modules/@silurus/ooxml/dist/line-distribute-D1WNHKz_.js +1023 -0
  214. package/node_modules/@silurus/ooxml/dist/line-metrics-B60C86uh.js +60 -0
  215. package/node_modules/@silurus/ooxml/dist/math.mjs +56 -0
  216. package/node_modules/@silurus/ooxml/dist/mathjax-DoyWZbSp.js +21 -0
  217. package/node_modules/@silurus/ooxml/dist/mathjax-stix2.js +15 -0
  218. package/node_modules/@silurus/ooxml/dist/node.mjs +1327 -0
  219. package/node_modules/@silurus/ooxml/dist/pixel-budget-9P63Bsk5.js +37 -0
  220. package/node_modules/@silurus/ooxml/dist/plot-area-frame-DLTYKHNP.js +4354 -0
  221. package/node_modules/@silurus/ooxml/dist/pptx-DkyYBYoL.js +3065 -0
  222. package/node_modules/@silurus/ooxml/dist/pptx.mjs +7 -0
  223. package/node_modules/@silurus/ooxml/dist/pptx_parser_bg.wasm +0 -0
  224. package/node_modules/@silurus/ooxml/dist/raster-target-ojDdQizC.js +91 -0
  225. package/node_modules/@silurus/ooxml/dist/read-only-comment-decoration-Dz7eTQ4d.js +96 -0
  226. package/node_modules/@silurus/ooxml/dist/read-only-comment-margin-DkgzASAl.js +189 -0
  227. package/node_modules/@silurus/ooxml/dist/region-map.mjs +12714 -0
  228. package/node_modules/@silurus/ooxml/dist/render-D3iqVKCo.js +69 -0
  229. package/node_modules/@silurus/ooxml/dist/render-worker-host-B5wQfB7U.js +9 -0
  230. package/node_modules/@silurus/ooxml/dist/render-worker-host-D4123J27.js +9 -0
  231. package/node_modules/@silurus/ooxml/dist/render-worker-host-Di3LxLc0.js +9 -0
  232. package/node_modules/@silurus/ooxml/dist/renderer-B4GZb5We.js +6492 -0
  233. package/node_modules/@silurus/ooxml/dist/renderer-module-contract-D91rkYHH.js +32 -0
  234. package/node_modules/@silurus/ooxml/dist/resource-measurement-SHQCbkh2.js +150 -0
  235. package/node_modules/@silurus/ooxml/dist/session-BGq81gRQ.js +743 -0
  236. package/node_modules/@silurus/ooxml/dist/slide-pull-client-DiPnm240.js +6253 -0
  237. package/node_modules/@silurus/ooxml/dist/source-key-DIJG8BA0.js +42 -0
  238. package/node_modules/@silurus/ooxml/dist/three-d-DJBl8g8M.js +1162 -0
  239. package/node_modules/@silurus/ooxml/dist/three-d.mjs +2767 -0
  240. package/node_modules/@silurus/ooxml/dist/tiff.mjs +710 -0
  241. package/node_modules/@silurus/ooxml/dist/transfer-p6iCC2R7.js +6 -0
  242. package/node_modules/@silurus/ooxml/dist/types/chart-ex.d.ts +1087 -0
  243. package/node_modules/@silurus/ooxml/dist/types/docx.d.ts +2701 -0
  244. package/node_modules/@silurus/ooxml/dist/types/index.d.ts +5031 -0
  245. package/node_modules/@silurus/ooxml/dist/types/math.d.ts +16 -0
  246. package/node_modules/@silurus/ooxml/dist/types/node.d.ts +3255 -0
  247. package/node_modules/@silurus/ooxml/dist/types/pptx.d.ts +2419 -0
  248. package/node_modules/@silurus/ooxml/dist/types/region-map.d.ts +1087 -0
  249. package/node_modules/@silurus/ooxml/dist/types/three-d.d.ts +1087 -0
  250. package/node_modules/@silurus/ooxml/dist/types/tiff.d.ts +19 -0
  251. package/node_modules/@silurus/ooxml/dist/types/xlsx.d.ts +2709 -0
  252. package/node_modules/@silurus/ooxml/dist/units-BzZ0gAxs.js +4 -0
  253. package/node_modules/@silurus/ooxml/dist/visible-index-BgiHEc1z.js +17 -0
  254. package/node_modules/@silurus/ooxml/dist/worksheet-pull-client-BM8ywvbu.js +458 -0
  255. package/node_modules/@silurus/ooxml/dist/xlsx-C9NVjgaV.js +8484 -0
  256. package/node_modules/@silurus/ooxml/dist/xlsx.mjs +7 -0
  257. package/node_modules/@silurus/ooxml/dist/xlsx_parser_bg.wasm +0 -0
  258. package/node_modules/@silurus/ooxml/package.json +155 -0
  259. package/package.json +270 -4
package/README.md CHANGED
@@ -1,3 +1,267 @@
1
- # Temporary Holding Version
1
+ # `@springbrand/file-renderers`
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ The shared semantic renderer boundary for Workspace files in SP's first-class
4
+ File Canvas.
5
+
6
+ This package contains the reviewed contracts, typed registry, and the first
7
+ read-only Markdown, code, image, SVG, HTML, media, CSV, JSON, JSONL, YAML,
8
+ TOML, XML, Notebook, ZIP, PDF, DOCX, XLSX, PPTX, GLB, WASM, Drawio,
9
+ Excalidraw, and Mermaid
10
+ renderers. SP's File Canvas consumes them through its Workspace host adapter.
11
+ The package is designed for the observation phase while keeping a typed
12
+ boundary for later editing.
13
+
14
+ ## Ownership
15
+
16
+ - SP/Rapp owns the File Canvas controller and consumes the shared renderer
17
+ implementations.
18
+ - This package owns Workspace file format semantics only.
19
+ - `fileFormatFor` selects a renderer format; `fileContentKindFor` classifies
20
+ code and JSON-like files without expanding renderer support.
21
+ Compound formats such as `.excalidraw.json` and `.drawio.xml` keep their
22
+ renderer identity before generic suffix classification.
23
+ - Chat Attachment admission consumes this package's `attachmentFormatFor` and
24
+ `validateAttachmentBytes` boundary. A non-`unknown` Renderer format is
25
+ uploadable; HEIC/HEIF are input-only browser conversion aliases that become
26
+ JPEG before upload.
27
+ - Workspace/Space owns authorization, file versions, locks, leases, and writes.
28
+ - The package never accesses Workspace HTTP, WebSocket, tokens, or storage.
29
+
30
+ The former Doop iframe projection and protocol have been removed. SP owns the
31
+ only production File Canvas, using its existing Severn connection and Workspace
32
+ client. Renderer behavior is integrated through the SP Canvas controller, not
33
+ through a second runtime.
34
+
35
+ Workspace identity and version facts belong to SP/Space. The Canvas controller
36
+ keeps the authoritative snapshot next to the parsed renderer document instead
37
+ of copying those facts into this package. `RendererInput`, document models, and
38
+ resource requests therefore contain no Workspace scope, path, resource ID,
39
+ content version, or mutation revision.
40
+
41
+ ## Observation flow
42
+
43
+ Every renderer is bound to one format and one model type:
44
+
45
+ ```ts
46
+ interface FileRenderer<
47
+ F extends FileRendererFormat,
48
+ Model extends RendererModel<F>,
49
+ > {
50
+ readonly format: F;
51
+ parse(
52
+ input: RendererInput<F>,
53
+ context: RendererParseContext,
54
+ ): Promise<ParsedDocument<F, Model>>;
55
+ render(
56
+ document: ParsedDocument<F, Model>,
57
+ context: RendererRenderContext,
58
+ ): ReactNode;
59
+ }
60
+ ```
61
+
62
+ `RendererInput<F>` contains only format semantics: format, name, MIME type, and
63
+ payload. It binds the payload encoding to the format. The registry keeps that
64
+ format and model relationship when a renderer is looked up. A Markdown renderer
65
+ cannot receive an XLSX input or render a PDF document at the type boundary.
66
+
67
+ The parsed model is a discriminated union:
68
+
69
+ - `structure: "frame"` carries renderer-specific frame data and never has a
70
+ component `root`.
71
+ - `structure: "structured"` carries a component tree and never has a frame
72
+ payload.
73
+
74
+ A format may choose either shape for a particular renderer. A PDF renderer can
75
+ start as a Frame renderer and a later version can provide a structured page
76
+ model without changing the package-wide contract.
77
+
78
+ `createFileRendererRegistry()` builds an immutable registry from an ordered
79
+ renderer list. It preserves the concrete format/model pair at typed lookups,
80
+ copies the registration order, and rejects duplicate formats instead of making
81
+ the winner depend on import order.
82
+
83
+ Parsing has one lifecycle contract: `parse()` resolves a `ParsedDocument`,
84
+ observes the host `AbortSignal`, or throws. Request generation, stale-result
85
+ discarding, retries, error presentation, and snapshot caching remain in the SP
86
+ Canvas controller; the shared package does not create a parallel task state
87
+ machine.
88
+
89
+ ## Host boundary
90
+
91
+ Parsing and rendering receive a host context containing:
92
+
93
+ - `signal`, so a superseded parse or resource request can be cancelled;
94
+ - `basePath`, the Workspace-relative resolution base;
95
+ - `resources.resolve()`, the only way a renderer asks for linked resources.
96
+
97
+ The shared renderer classifies references and their purpose (`image`,
98
+ `stylesheet`, `script`, and so on). The host creates a resolver already bound to
99
+ the authoritative file snapshot. That resolver owns authentication, Workspace
100
+ access, authorization, caching, and the decision to return a safe URL,
101
+ text/bytes, `missing`, or `blocked`. Renderers never call Workspace APIs
102
+ directly, and hosts do not duplicate format-specific reference parsing. The
103
+ Markdown renderer preserves relative `href` and `src` references through its
104
+ Streamdown pipeline before resolving them through this boundary, so the browser
105
+ cannot reinterpret them against the SP page origin. Notebook Markdown cells
106
+ reuse the same renderer and resolver context.
107
+
108
+ Attachment admission and Renderer parsing are deliberately separate stages.
109
+ The API validates authoritative bytes enough to reject format mismatches,
110
+ unsafe OOXML containers, and invalid binary signatures, then stores the
111
+ canonical media type. A malformed but recognized text document may still be
112
+ uploaded so its Renderer can return a source-preserving parse diagnostic. The
113
+ Agent's optional Understanding pipeline remains narrower than upload or
114
+ preview support and does not mark unsupported evidence as a failed upload.
115
+
116
+ ## One capability source
117
+
118
+ `ParsedDocument.capability` is the only capability declaration. There is no
119
+ second `lockScope` field on the model, no static capability copy on the
120
+ renderer, and no separate output capability object.
121
+
122
+ The capability is one of:
123
+
124
+ - `mode: "observe"` for the current read-only phase;
125
+ - `mode: "edit", lock.scope: "frame"` with a whole-document serialization
126
+ contract;
127
+ - `mode: "edit", lock.scope: "node"` for a structured model only.
128
+
129
+ The node branch is structurally required to provide all four prerequisites:
130
+
131
+ 1. stable node identity;
132
+ 2. a serialization boundary locator and serializer;
133
+ 3. comparable node versions;
134
+ 4. a disjoint-mutation checker.
135
+
136
+ The type system cannot manufacture a node capability from boolean flags, and a
137
+ Frame model cannot receive the node capability. These interfaces reserve the
138
+ future edit seam; they do not acquire locks, call CAS, write files, or decide
139
+ whether an operation is authorized.
140
+
141
+ ## Format slots
142
+
143
+ Markdown, code, image, SVG, HTML, media, CSV, PDF, DOCX, XLSX, PPTX, GLB,
144
+ WASM, Drawio, Excalidraw, Mermaid, JSON, JSONL, YAML, TOML, XML, Notebook,
145
+ and ZIP are implemented as read-only renderers following SP's current preview
146
+ semantics.
147
+ SP's live Canvas and revision history use the same preview surface for every
148
+ implemented format. The host retains the exact text payload for Preview/Source
149
+ switching in both views, including syntax-highlighted Code; binary formats
150
+ offer the original download without fabricating text Source. The host's model
151
+ map is checked against every `FileRendererFormat`, and dispatch is exhaustive
152
+ for recognized formats so a new slot cannot silently fall back to a blank Canvas.
153
+ Most deliberately use Frame models until stable structured identity and
154
+ lossless serialization are reviewed. Valid JSON has a source-preserving
155
+ structured preview, invalid JSON a diagnostic Frame, and JSONL a per-record
156
+ Frame; neither advertises node editing. The code renderer recognizes Python,
157
+ TypeScript/TSX, JavaScript/JSX, CSS/SCSS, SQL, and Bash while preserving exact
158
+ source. SVG is a separate text format with a static sanitized image preview
159
+ and exact original Source in both Canvas views. WASM is a binary metadata-only
160
+ preview with download, never generated source or module execution. Other text
161
+ and binary formats continue through the host fallback. The shared
162
+ `isBinaryFileFormat()` policy lets current and historical Canvas reads agree
163
+ on payload encoding. DOCX, XLSX, and PPTX use SP's shared strict OOXML guard
164
+ before retaining original bytes in read-only Frames. Each format then loads its
165
+ `@silurus/ooxml` Canvas viewer from a separate entry point when mounted. DOCX
166
+ and XLSX reuse their viewer's parsed model for a semantic HTML companion:
167
+ DOCX exposes headings, lists, tables, and validated HTTP(S) links; XLSX exposes
168
+ worksheet tables with row and column headers. The former separate extractors
169
+ remain removed. Viewer-owned workers and canvases are destroyed on unmount or
170
+ render cancellation; failures show a download fallback.
171
+ PDF uses PDF.js for a local canvas viewer and reports interactive forms,
172
+ attachments, JavaScript, and rich-media objects as static-preview limitations;
173
+ it never executes or exposes those embedded objects.
174
+ SP bounds both current and historical previews of these formats to 20 MiB
175
+ before parsing. PPTX uses the `ppt` slot only for modern `.pptx` files and does
176
+ not attempt legacy `.ppt` decoding. The viewers render locally without
177
+ resolving Workspace resources, media playback, or remote fonts. DOCX allows
178
+ validated HTTP(S) links; XLSX and PPTX hyperlinks remain disabled. The shared
179
+ OOXML guard remains a separate security inspection, not a second display
180
+ engine; viewer resource budgets match its ZIP limits.
181
+ Other directories reserve sibling format slots; a slot is not an implementation
182
+ promise until its parser, security policy, resource behavior, model,
183
+ serialization, and tests are reviewed.
184
+
185
+ HTML uses the shared bounded resource projector for both live and historical
186
+ previews. The live view reads linked assets through the Chat-scoped Workspace
187
+ client; history resolves the same links only within the selected revision's
188
+ manifest root, never from the current Workspace. The host keeps its sandboxed,
189
+ opaque-origin preview separate from the exact original Source and download.
190
+ Switching files or revisions aborts outstanding linked-resource requests.
191
+
192
+ Format selection is suffix-first. A supported filename suffix is authoritative;
193
+ MIME is used as a fallback when the filename has no recognized suffix, including
194
+ an unknown suffix. Renderer parsing still validates the actual payload before a
195
+ preview is shown, so MIME metadata never bypasses a renderer's content checks.
196
+ CSV additionally detects UTF-8 and UTF-16 with or without a BOM, rejects invalid
197
+ or unreliable encodings, and recognizes comma, semicolon, tab, and pipe
198
+ delimiters from consistent parsed rows. Ambiguous or low-confidence multi-column
199
+ input fails rather than being silently split with the wrong delimiter; a file
200
+ that contains no reliable delimiter remains a single column.
201
+ JSON and JSONL use the same parser and tree view but separate format contracts.
202
+ The paged tree presentation is also reused by configuration formats and ZIP;
203
+ each format keeps its own parsing and identity semantics.
204
+ The parser preserves duplicate keys and exact numeric source tokens; it bounds
205
+ JSON to 512 KiB and JSONL to 2 MiB. SP's existing bounded read path applies
206
+ those limits to both live and historical previews. Specialized
207
+ `.excalidraw.json` continues to take precedence over generic `.json`.
208
+ YAML, TOML, and XML share the JSON tree's paged presentation, not its parser or
209
+ model. YAML preserves source ranges without expanding aliases; TOML keeps large
210
+ integers as BigInt and does not invent per-value source ranges; XML is displayed
211
+ as inert text nodes with DOCTYPE and processing instructions rejected. All three
212
+ have a 512 KiB limit, bounded tree depth and node count, and diagnostic Frames
213
+ for invalid files. Their component IDs are view-local only: node editing and
214
+ lossless structured serialization are not claimed.
215
+
216
+ The Notebook renderer accepts `.ipynb` or an extensionless notebook MIME as
217
+ bounded JSON text. It reuses the package's Markdown, Code, and raster Image
218
+ surfaces for cells and safe outputs, while never running code or rendering rich
219
+ HTML/SVG outputs. It caps the input at 2 MiB, displays at most 200 cells and
220
+ 20 outputs per cell, and bounds displayed text and embedded images. Both Canvas
221
+ views offer exact Source and the original download.
222
+
223
+ The ZIP renderer accepts `.zip` or an extensionless ZIP MIME as binary bytes.
224
+ It reuses SP's strict ZIP inspector for path, entry, CRC, compression, and
225
+ expanded-size checks. A 10 MiB input limit, 1,000 entries, 64 MiB total
226
+ expansion, and 16-level path ceiling bound the preview. Its Frame contains
227
+ only directory paths and sizes, presented through the shared paged tree; it
228
+ never keeps entry contents, extracts into the Workspace, or executes files.
229
+ ZIP has no text Source tab; the original archive remains downloadable. Invalid
230
+ and excessive archives show diagnostic Frames instead of partial listings.
231
+
232
+ The media renderer accepts browser-native audio and video formats as binary Frame
233
+ models. It uses local Blob URLs for the lifetime of the rendered component, keeps
234
+ native playback controls enabled, and never starts playback automatically. Media
235
+ bytes are not decoded by the package and no remote resource is resolved.
236
+
237
+ The GLB renderer accepts only self-contained glTF 2.0 binary models. It
238
+ validates bounded GLB chunks and embedded references before lazily loading
239
+ `<model-viewer>` with camera controls. SP limits current and historical GLB
240
+ reads to 10 MiB using the renderer's own size constant. External references,
241
+ decoder extensions, and `.gltf` JSON remain unsupported; the model is an
242
+ observable Frame, not a stable editable scene graph.
243
+
244
+ The diagram renderers are also read-only Frames. Drawio accepts bounded plain
245
+ or compressed multi-page XML and displays a simplified semantic diagram;
246
+ Excalidraw uses its official exporter to produce a static drawing from bounded
247
+ JSON and embedded raster assets; Mermaid uses its official strict renderer.
248
+ Excalidraw SVG is displayed in an inert CSP-limited iframe. Mermaid renders
249
+ inside a separate script-enabled, opaque-origin iframe with a restrictive CSP;
250
+ only its packaged runtime script and local data images/fonts are permitted.
251
+ Generated navigation is removed. None resolves remote or Workspace-linked
252
+ assets, starts an editor, or declares node locks. The Canvas offers the
253
+ original source alongside the diagram preview.
254
+
255
+ ## Type tests
256
+
257
+ `type-tests/contracts.typecheck.ts` is compiled by the package check:
258
+
259
+ ```bash
260
+ pnpm --filter @springbrand/file-renderers check
261
+ ```
262
+
263
+ The file uses `@ts-expect-error` assertions to ensure these invalid contracts
264
+ remain rejected: cross-format parse/render, missing host cancellation context,
265
+ bytes supplied to a text format, Workspace identity/version facts inserted into
266
+ renderer contracts, a Frame model with `root`, an incomplete node lock, and a
267
+ node lock attached to a Frame model.
@@ -0,0 +1,17 @@
1
+ import { type DetectedFileFormat, type FileFormatInput } from "./file-format";
2
+ /**
3
+ * Formats which can cross the Attachment boundary. `text` is the Canvas
4
+ * source fallback for plain text files; `unknown` never crosses the boundary.
5
+ */
6
+ export type AttachmentFormat = Exclude<DetectedFileFormat, "unknown">;
7
+ /**
8
+ * These are the canonical media types returned by the upload boundary. The
9
+ * list is intentionally owned by the renderer package so HTTP, Web, and Agent
10
+ * cannot grow separate format lists.
11
+ */
12
+ export declare const ATTACHMENT_MEDIA_TYPES: readonly ["application/json", "application/javascript", "application/pdf", "application/sql", "application/toml", "application/typescript", "application/xhtml+xml", "application/vnd.excalidraw+json", "application/vnd.jgraph.mxfile", "application/vnd.openxmlformats-officedocument.presentationml.presentation", "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet", "application/vnd.openxmlformats-officedocument.wordprocessingml.document", "application/wasm", "application/x-ipynb+json", "application/x-ndjson", "application/x-python", "application/x-python-code", "application/x-sh", "application/x-sql", "application/x-typescript", "application/xml", "application/yaml", "application/zip", "audio/aac", "audio/flac", "audio/mp4", "audio/mpeg", "audio/ogg", "audio/wav", "audio/webm", "image/apng", "image/avif", "image/gif", "image/jpeg", "image/png", "image/svg+xml", "image/webp", "model/gltf-binary", "text/csv", "text/css", "text/html", "text/javascript", "text/markdown", "text/plain", "text/python", "text/typescript", "text/vnd.mermaid", "text/x-python", "text/x-scss", "text/x-sh", "text/x-sql", "video/mp4", "video/ogg", "video/webm"];
13
+ export type AttachmentMediaType = (typeof ATTACHMENT_MEDIA_TYPES)[number];
14
+ export declare function attachmentFormatFor(input: FileFormatInput): AttachmentFormat | null;
15
+ export declare function isSupportedAttachmentInput(input: FileFormatInput): boolean;
16
+ export declare function isAttachmentMediaType(value: string): value is AttachmentMediaType;
17
+ export declare function attachmentMediaTypeFor(input: FileFormatInput, format?: AttachmentFormat | null): AttachmentMediaType | null;
@@ -0,0 +1,180 @@
1
+ import { codeLanguageFor, normalizedCodeMimeType, } from "./code/mime";
2
+ import { configMimeTypeFor } from "./config/mime";
3
+ import { csvMimeTypeFor } from "./csv/mime";
4
+ import { docxMimeTypeFor } from "./docx/mime";
5
+ import { drawioMimeTypeFor } from "./drawio/mime";
6
+ import { excalidrawMimeTypeFor } from "./excalidraw/mime";
7
+ import { fileFormatFor } from "./file-format";
8
+ import { glbMimeTypeFor } from "./glb/mime";
9
+ import { htmlMimeTypeFor } from "./html/mime";
10
+ import { imageMimeTypeFor, isSupportedImageMimeType } from "./image/mime";
11
+ import { jsonlMimeTypeFor, jsonMimeTypeFor } from "./json/mime";
12
+ import { isSupportedMarkdownInput } from "./markdown/mime";
13
+ import { canonicalMediaMimeType, mediaMimeTypeFor } from "./media/mime";
14
+ import { mermaidMimeTypeFor } from "./mermaid/mime";
15
+ import { NOTEBOOK_MIME_TYPE } from "./notebook/mime";
16
+ import { pdfMimeTypeFor } from "./pdf/mime";
17
+ import { pptMimeTypeFor } from "./ppt/mime";
18
+ import { svgMimeTypeFor } from "./svg/mime";
19
+ import { wasmMimeTypeFor } from "./wasm/mime";
20
+ import { xlsxMimeTypeFor } from "./xlsx/mime";
21
+ import { ZIP_MIME_TYPE, isSupportedZipInput } from "./zip/mime";
22
+ /**
23
+ * These are the canonical media types returned by the upload boundary. The
24
+ * list is intentionally owned by the renderer package so HTTP, Web, and Agent
25
+ * cannot grow separate format lists.
26
+ */
27
+ export const ATTACHMENT_MEDIA_TYPES = [
28
+ "application/json",
29
+ "application/javascript",
30
+ "application/pdf",
31
+ "application/sql",
32
+ "application/toml",
33
+ "application/typescript",
34
+ "application/xhtml+xml",
35
+ "application/vnd.excalidraw+json",
36
+ "application/vnd.jgraph.mxfile",
37
+ "application/vnd.openxmlformats-officedocument.presentationml.presentation",
38
+ "application/vnd.openxmlformats-officedocument.spreadsheetml.sheet",
39
+ "application/vnd.openxmlformats-officedocument.wordprocessingml.document",
40
+ "application/wasm",
41
+ "application/x-ipynb+json",
42
+ "application/x-ndjson",
43
+ "application/x-python",
44
+ "application/x-python-code",
45
+ "application/x-sh",
46
+ "application/x-sql",
47
+ "application/x-typescript",
48
+ "application/xml",
49
+ "application/yaml",
50
+ "application/zip",
51
+ "audio/aac",
52
+ "audio/flac",
53
+ "audio/mp4",
54
+ "audio/mpeg",
55
+ "audio/ogg",
56
+ "audio/wav",
57
+ "audio/webm",
58
+ "image/apng",
59
+ "image/avif",
60
+ "image/gif",
61
+ "image/jpeg",
62
+ "image/png",
63
+ "image/svg+xml",
64
+ "image/webp",
65
+ "model/gltf-binary",
66
+ "text/csv",
67
+ "text/css",
68
+ "text/html",
69
+ "text/javascript",
70
+ "text/markdown",
71
+ "text/plain",
72
+ "text/python",
73
+ "text/typescript",
74
+ "text/vnd.mermaid",
75
+ "text/x-python",
76
+ "text/x-scss",
77
+ "text/x-sh",
78
+ "text/x-sql",
79
+ "video/mp4",
80
+ "video/ogg",
81
+ "video/webm",
82
+ ];
83
+ const ATTACHMENT_MEDIA_TYPE_SET = new Set(ATTACHMENT_MEDIA_TYPES);
84
+ export function attachmentFormatFor(input) {
85
+ const format = fileFormatFor(input);
86
+ return format === "unknown" ? null : format;
87
+ }
88
+ export function isSupportedAttachmentInput(input) {
89
+ return attachmentFormatFor(input) !== null;
90
+ }
91
+ export function isAttachmentMediaType(value) {
92
+ return ATTACHMENT_MEDIA_TYPE_SET.has(value);
93
+ }
94
+ export function attachmentMediaTypeFor(input, format = attachmentFormatFor(input)) {
95
+ if (!format)
96
+ return null;
97
+ const mimeInput = { name: "", mimeType: input.mimeType };
98
+ switch (format) {
99
+ case "image": {
100
+ const mediaType = imageMimeTypeFor(input) ?? imageMimeTypeFor(mimeInput) ??
101
+ normalizedMimeType(input.mimeType);
102
+ return isSupportedImageMimeType(mediaType) ? asAttachmentMediaType(mediaType) : null;
103
+ }
104
+ case "media":
105
+ return asAttachmentMediaType(mediaMimeTypeFor(input) ?? mediaMimeTypeFor(mimeInput) ?? canonicalMediaMimeType(input.mimeType));
106
+ case "html":
107
+ return asAttachmentMediaType(htmlMimeTypeFor(input) ?? htmlMimeTypeFor(mimeInput));
108
+ case "markdown":
109
+ return isSupportedMarkdownInput(input) || isSupportedMarkdownInput(mimeInput)
110
+ ? "text/markdown"
111
+ : null;
112
+ case "csv":
113
+ return asAttachmentMediaType(csvMimeTypeFor(input) ?? csvMimeTypeFor(mimeInput));
114
+ case "json":
115
+ return asAttachmentMediaType(jsonMimeTypeFor(input) ?? jsonMimeTypeFor(mimeInput));
116
+ case "jsonl":
117
+ return asAttachmentMediaType(jsonlMimeTypeFor(input) ?? jsonlMimeTypeFor(mimeInput));
118
+ case "docx":
119
+ return asAttachmentMediaType(docxMimeTypeFor(input) ?? docxMimeTypeFor(mimeInput));
120
+ case "xlsx":
121
+ return asAttachmentMediaType(xlsxMimeTypeFor(input) ?? xlsxMimeTypeFor(mimeInput));
122
+ case "ppt":
123
+ return asAttachmentMediaType(pptMimeTypeFor(input) ?? pptMimeTypeFor(mimeInput));
124
+ case "pdf":
125
+ return asAttachmentMediaType(pdfMimeTypeFor(input) ?? pdfMimeTypeFor(mimeInput));
126
+ case "glb":
127
+ return asAttachmentMediaType(glbMimeTypeFor(input) ?? glbMimeTypeFor(mimeInput));
128
+ case "wasm":
129
+ return asAttachmentMediaType(wasmMimeTypeFor(input) ?? wasmMimeTypeFor(mimeInput));
130
+ case "zip":
131
+ return isSupportedZipInput(input) || isSupportedZipInput(mimeInput) ? ZIP_MIME_TYPE : null;
132
+ case "drawio":
133
+ return asAttachmentMediaType(drawioMimeTypeFor(input) ?? drawioMimeTypeFor(mimeInput));
134
+ case "excalidraw":
135
+ return asAttachmentMediaType(excalidrawMimeTypeFor(input) ?? excalidrawMimeTypeFor(mimeInput));
136
+ case "svg":
137
+ return asAttachmentMediaType(svgMimeTypeFor(input) ?? svgMimeTypeFor(mimeInput));
138
+ case "mermaid":
139
+ return asAttachmentMediaType(mermaidMimeTypeFor(input) ?? mermaidMimeTypeFor(mimeInput));
140
+ case "notebook":
141
+ return NOTEBOOK_MIME_TYPE;
142
+ case "yaml":
143
+ case "toml":
144
+ case "xml": {
145
+ const configFormat = configMimeTypeFor(input) ?? configMimeTypeFor(mimeInput);
146
+ if (!configFormat)
147
+ return null;
148
+ return configFormat === "yaml"
149
+ ? "application/yaml"
150
+ : configFormat === "toml"
151
+ ? "application/toml"
152
+ : "application/xml";
153
+ }
154
+ case "code": {
155
+ if (!codeLanguageFor(input) && !codeLanguageFor(mimeInput))
156
+ return null;
157
+ const mediaType = normalizedCodeMimeType(input.mimeType);
158
+ return isCodeMimeType(mediaType) ? asAttachmentMediaType(mediaType) : "text/plain";
159
+ }
160
+ case "text":
161
+ return "text/plain";
162
+ }
163
+ }
164
+ function normalizedMimeType(value) {
165
+ return value?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
166
+ }
167
+ function asAttachmentMediaType(value) {
168
+ return value && ATTACHMENT_MEDIA_TYPE_SET.has(value)
169
+ ? value
170
+ : null;
171
+ }
172
+ function isCodeMimeType(value) {
173
+ return value === "application/javascript" || value === "application/typescript" ||
174
+ value === "application/sql" || value === "text/css" || value === "text/javascript" ||
175
+ value === "text/python" || value === "text/typescript" || value === "text/x-python" ||
176
+ value === "text/x-scss" || value === "text/x-sh" || value === "text/x-sql" ||
177
+ value === "application/x-python" || value === "application/x-python-code" ||
178
+ value === "application/x-sh" || value === "application/x-sql" ||
179
+ value === "application/x-typescript";
180
+ }
@@ -0,0 +1,17 @@
1
+ import { type AttachmentFormat, type AttachmentMediaType } from "./attachment-format";
2
+ import type { FileFormatInput } from "./file-format";
3
+ export * from "./attachment-format";
4
+ export type AttachmentImageMediaTypeReader = (bytes: Uint8Array) => Promise<string | null>;
5
+ export type ValidatedAttachment = {
6
+ readonly format: AttachmentFormat;
7
+ readonly mediaType: AttachmentMediaType;
8
+ };
9
+ /**
10
+ * Validates the byte-level facts needed before an Attachment becomes durable.
11
+ * Renderer parsing remains responsible for parse diagnostics and format-specific
12
+ * preview errors; upload admission only rejects a format mismatch or unsafe
13
+ * container.
14
+ */
15
+ export declare function validateAttachmentBytes(input: FileFormatInput & {
16
+ readonly bytes: Uint8Array;
17
+ }, readImageMediaType: AttachmentImageMediaTypeReader): Promise<ValidatedAttachment | null>;
@@ -0,0 +1,96 @@
1
+ import { hasZipSignature, inspectSafeOoxml, isStrictUtf8 } from "../../utils/src/index.js";
2
+ import { attachmentFormatFor, attachmentMediaTypeFor, isAttachmentMediaType, } from "./attachment-format";
3
+ import { isSupportedImageMimeType } from "./image/mime";
4
+ import { isValidMediaBytes } from "./media/validate";
5
+ export * from "./attachment-format";
6
+ /**
7
+ * Validates the byte-level facts needed before an Attachment becomes durable.
8
+ * Renderer parsing remains responsible for parse diagnostics and format-specific
9
+ * preview errors; upload admission only rejects a format mismatch or unsafe
10
+ * container.
11
+ */
12
+ export async function validateAttachmentBytes(input, readImageMediaType) {
13
+ const format = attachmentFormatFor(input);
14
+ if (!format || input.bytes.byteLength === 0)
15
+ return null;
16
+ switch (format) {
17
+ case "image": {
18
+ const mediaType = normalizedMimeType(await readImageMediaType(input.bytes));
19
+ if (!isSupportedImageMimeType(mediaType) || !isAttachmentMediaType(mediaType))
20
+ return null;
21
+ return { format, mediaType };
22
+ }
23
+ case "docx":
24
+ if (!await safeOoxml(input.bytes, "docx"))
25
+ return null;
26
+ break;
27
+ case "xlsx":
28
+ if (!await safeOoxml(input.bytes, "xlsx"))
29
+ return null;
30
+ break;
31
+ case "ppt":
32
+ if (!await safeOoxml(input.bytes, "pptx"))
33
+ return null;
34
+ break;
35
+ case "pdf":
36
+ if (!hasAsciiPrefix(input.bytes, "%PDF-"))
37
+ return null;
38
+ break;
39
+ case "zip":
40
+ if (!hasZipSignature(input.bytes))
41
+ return null;
42
+ break;
43
+ case "glb":
44
+ if (!hasAsciiPrefix(input.bytes, "glTF") || input.bytes[4] !== 2)
45
+ return null;
46
+ break;
47
+ case "wasm":
48
+ if (!hasBytes(input.bytes, [0, 0x61, 0x73, 0x6d, 1, 0, 0, 0]))
49
+ return null;
50
+ break;
51
+ case "media":
52
+ break;
53
+ default:
54
+ if (!isTextPayload(input.bytes))
55
+ return null;
56
+ break;
57
+ }
58
+ const mediaType = attachmentMediaTypeFor(input, format);
59
+ if (!mediaType)
60
+ return null;
61
+ if (format === "media" && !isValidMediaBytes(input.bytes, mediaType))
62
+ return null;
63
+ return { format, mediaType };
64
+ }
65
+ function normalizedMimeType(value) {
66
+ return value?.split(";", 1)[0]?.trim().toLowerCase() ?? "";
67
+ }
68
+ function isTextPayload(bytes) {
69
+ const encoding = bytes[0] === 0xff && bytes[1] === 0xfe
70
+ ? "utf-16le"
71
+ : bytes[0] === 0xfe && bytes[1] === 0xff
72
+ ? "utf-16be"
73
+ : "utf-8";
74
+ try {
75
+ const text = new TextDecoder(encoding, { fatal: true, ignoreBOM: false }).decode(bytes);
76
+ return encoding !== "utf-8" || isStrictUtf8(bytes) ? !text.includes("\u0000") : false;
77
+ }
78
+ catch {
79
+ return false;
80
+ }
81
+ }
82
+ function hasAsciiPrefix(bytes, value) {
83
+ return hasBytes(bytes, [...value].map((character) => character.charCodeAt(0)));
84
+ }
85
+ function hasBytes(bytes, expected) {
86
+ return expected.every((value, index) => bytes[index] === value);
87
+ }
88
+ async function safeOoxml(bytes, kind) {
89
+ try {
90
+ await inspectSafeOoxml(bytes, kind);
91
+ return true;
92
+ }
93
+ catch {
94
+ return false;
95
+ }
96
+ }
@@ -0,0 +1,19 @@
1
+ import type { FileRenderer, FrameDocumentModel, RendererInput } from "../contracts";
2
+ import { type CodeLanguage } from "./mime";
3
+ export { codeLanguageFor, type CodeLanguage } from "./mime";
4
+ type CodeFrame = {
5
+ readonly text: string;
6
+ readonly language: CodeLanguage;
7
+ };
8
+ export type CodeModel = FrameDocumentModel<"code", CodeFrame>;
9
+ export declare class UnsupportedCodeLanguageError extends Error {
10
+ readonly fileName: string;
11
+ readonly mimeType: string;
12
+ constructor(input: Pick<RendererInput<"code">, "name" | "mimeType">);
13
+ }
14
+ export declare function CodeDocument({ text, language, signal, }: {
15
+ readonly text: string;
16
+ readonly language: string;
17
+ readonly signal?: AbortSignal;
18
+ }): import("react").JSX.Element;
19
+ export declare const codeRenderer: FileRenderer<"code", CodeModel>;