@ryanyahya/opendoc 0.4.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 (789) hide show
  1. package/.agents/skills/opendoc-apply-comments/SKILL.md +30 -0
  2. package/.agents/skills/opendoc-apply-comments/agents/openai.yaml +4 -0
  3. package/.agents/skills/opendoc-create/SKILL.md +63 -0
  4. package/.agents/skills/opendoc-create/agents/openai.yaml +6 -0
  5. package/.agents/skills/opendoc-create-template/SKILL.md +50 -0
  6. package/.agents/skills/opendoc-create-template/agents/openai.yaml +4 -0
  7. package/.agents/skills/opendoc-create-template/references/structured-reports.md +11 -0
  8. package/.agents/skills/opendoc-create-theme/SKILL.md +40 -0
  9. package/.agents/skills/opendoc-create-theme/agents/openai.yaml +4 -0
  10. package/.agents/skills/opendoc-current-document/SKILL.md +32 -0
  11. package/.agents/skills/opendoc-current-document/agents/openai.yaml +4 -0
  12. package/.agents/skills/opendoc-current-document/references/context-fields.md +20 -0
  13. package/.agents/skills/opendoc-review-document/SKILL.md +60 -0
  14. package/.agents/skills/opendoc-review-document/agents/openai.yaml +4 -0
  15. package/AGENTS.md +69 -0
  16. package/CONTRIBUTING.md +38 -0
  17. package/LICENSE +21 -0
  18. package/README.md +179 -0
  19. package/SECURITY.md +9 -0
  20. package/THIRD_PARTY.md +49 -0
  21. package/VALIDATION.md +99 -0
  22. package/bin/opendoc.mjs +6 -0
  23. package/dist/assets/Markdown-sNZyjJi7.js +29 -0
  24. package/dist/assets/OpenDocSerif-Regular-D3OXs9A9.ttf +0 -0
  25. package/dist/assets/OpenDocSerif-Semibold-CjL9oDRo.ttf +0 -0
  26. package/dist/assets/geist-latin-ext-wght-italic-XgKbg5n7.woff2 +0 -0
  27. package/dist/assets/geist-latin-ext-wght-normal-DC-KSUi6.woff2 +0 -0
  28. package/dist/assets/geist-latin-wght-italic-dTnRTFFJ.woff2 +0 -0
  29. package/dist/assets/geist-latin-wght-normal-BgDaEnEv.woff2 +0 -0
  30. package/dist/assets/index-DdrZ6ozO.css +1 -0
  31. package/dist/assets/index-DgNBz7mL.js +39 -0
  32. package/dist/assets/pdf-B7Emtyld.js +54 -0
  33. package/dist/assets/pdf.worker.min-Dswkl-cV.mjs +29 -0
  34. package/dist/assets/wordmark-DsqLlZpb.png +0 -0
  35. package/dist/index.html +17 -0
  36. package/docs/APPEARANCE.md +21 -0
  37. package/docs/ASSETS.md +176 -0
  38. package/docs/AUTHORING.md +178 -0
  39. package/docs/HEADLESS.md +120 -0
  40. package/docs/MEDIA.md +112 -0
  41. package/docs/PROJECTS.md +55 -0
  42. package/docs/PROSE.md +15 -0
  43. package/docs/SELECTION.md +59 -0
  44. package/docs/TEMPLATES.md +55 -0
  45. package/docs/THEMES.md +106 -0
  46. package/node_modules/@formepdf/core/LICENSE +21 -0
  47. package/node_modules/@formepdf/core/README.md +53 -0
  48. package/node_modules/@formepdf/core/dist/attachments.d.ts +55 -0
  49. package/node_modules/@formepdf/core/dist/attachments.js +59 -0
  50. package/node_modules/@formepdf/core/dist/extract.d.ts +8 -0
  51. package/node_modules/@formepdf/core/dist/extract.js +56 -0
  52. package/node_modules/@formepdf/core/dist/index.d.ts +311 -0
  53. package/node_modules/@formepdf/core/dist/index.js +167 -0
  54. package/node_modules/@formepdf/core/dist/layout.d.ts +132 -0
  55. package/node_modules/@formepdf/core/dist/layout.js +240 -0
  56. package/node_modules/@formepdf/core/dist/shared/browserHelpers.d.ts +19 -0
  57. package/node_modules/@formepdf/core/dist/shared/browserHelpers.js +128 -0
  58. package/node_modules/@formepdf/core/package.json +35 -0
  59. package/node_modules/@formepdf/core/pkg-node/LICENSE +21 -0
  60. package/node_modules/@formepdf/core/pkg-node/README.md +84 -0
  61. package/node_modules/@formepdf/core/pkg-node/forme.d.ts +39 -0
  62. package/node_modules/@formepdf/core/pkg-node/forme.js +394 -0
  63. package/node_modules/@formepdf/core/pkg-node/forme_bg.wasm +0 -0
  64. package/node_modules/@formepdf/core/pkg-node/forme_bg.wasm.d.ts +21 -0
  65. package/node_modules/@formepdf/core/pkg-node/package.json +25 -0
  66. package/node_modules/@formepdf/react/README.md +70 -0
  67. package/node_modules/@formepdf/react/dist/charts.d.ts +72 -0
  68. package/node_modules/@formepdf/react/dist/charts.js +338 -0
  69. package/node_modules/@formepdf/react/dist/components.d.ts +533 -0
  70. package/node_modules/@formepdf/react/dist/components.js +608 -0
  71. package/node_modules/@formepdf/react/dist/expr.d.ts +20 -0
  72. package/node_modules/@formepdf/react/dist/expr.js +39 -0
  73. package/node_modules/@formepdf/react/dist/font.d.ts +2 -0
  74. package/node_modules/@formepdf/react/dist/font.js +1 -0
  75. package/node_modules/@formepdf/react/dist/index.d.ts +11 -0
  76. package/node_modules/@formepdf/react/dist/index.js +15 -0
  77. package/node_modules/@formepdf/react/dist/render.d.ts +12 -0
  78. package/node_modules/@formepdf/react/dist/render.js +15 -0
  79. package/node_modules/@formepdf/react/dist/serialize.d.ts +12 -0
  80. package/node_modules/@formepdf/react/dist/serialize.js +1470 -0
  81. package/node_modules/@formepdf/react/dist/stylesheet.d.ts +4 -0
  82. package/node_modules/@formepdf/react/dist/stylesheet.js +5 -0
  83. package/node_modules/@formepdf/react/dist/template-proxy.d.ts +38 -0
  84. package/node_modules/@formepdf/react/dist/template-proxy.js +100 -0
  85. package/node_modules/@formepdf/react/dist/types.d.ts +287 -0
  86. package/node_modules/@formepdf/react/dist/types.js +1 -0
  87. package/node_modules/@formepdf/react/package.json +41 -0
  88. package/node_modules/@formepdf/shared/README.md +7 -0
  89. package/node_modules/@formepdf/shared/dist/canvas.d.ts +6 -0
  90. package/node_modules/@formepdf/shared/dist/canvas.js +43 -0
  91. package/node_modules/@formepdf/shared/dist/charts.d.ts +100 -0
  92. package/node_modules/@formepdf/shared/dist/charts.js +98 -0
  93. package/node_modules/@formepdf/shared/dist/encode.d.ts +27 -0
  94. package/node_modules/@formepdf/shared/dist/encode.js +93 -0
  95. package/node_modules/@formepdf/shared/dist/font.d.ts +13 -0
  96. package/node_modules/@formepdf/shared/dist/font.js +52 -0
  97. package/node_modules/@formepdf/shared/dist/index.d.ts +10 -0
  98. package/node_modules/@formepdf/shared/dist/index.js +15 -0
  99. package/node_modules/@formepdf/shared/dist/parser.d.ts +30 -0
  100. package/node_modules/@formepdf/shared/dist/parser.js +756 -0
  101. package/node_modules/@formepdf/shared/dist/semantics.d.ts +15 -0
  102. package/node_modules/@formepdf/shared/dist/semantics.js +44 -0
  103. package/node_modules/@formepdf/shared/dist/style.d.ts +7 -0
  104. package/node_modules/@formepdf/shared/dist/style.js +1024 -0
  105. package/node_modules/@formepdf/shared/dist/types.d.ts +743 -0
  106. package/node_modules/@formepdf/shared/dist/types.js +2 -0
  107. package/node_modules/@formepdf/shared/package.json +35 -0
  108. package/node_modules/@types/node/LICENSE +21 -0
  109. package/node_modules/@types/node/README.md +15 -0
  110. package/node_modules/@types/node/assert/strict.d.ts +111 -0
  111. package/node_modules/@types/node/assert.d.ts +1078 -0
  112. package/node_modules/@types/node/async_hooks.d.ts +603 -0
  113. package/node_modules/@types/node/buffer.buffer.d.ts +472 -0
  114. package/node_modules/@types/node/buffer.d.ts +1934 -0
  115. package/node_modules/@types/node/child_process.d.ts +1476 -0
  116. package/node_modules/@types/node/cluster.d.ts +578 -0
  117. package/node_modules/@types/node/compatibility/disposable.d.ts +14 -0
  118. package/node_modules/@types/node/compatibility/index.d.ts +9 -0
  119. package/node_modules/@types/node/compatibility/indexable.d.ts +20 -0
  120. package/node_modules/@types/node/compatibility/iterators.d.ts +20 -0
  121. package/node_modules/@types/node/console.d.ts +452 -0
  122. package/node_modules/@types/node/constants.d.ts +21 -0
  123. package/node_modules/@types/node/crypto.d.ts +4545 -0
  124. package/node_modules/@types/node/dgram.d.ts +600 -0
  125. package/node_modules/@types/node/diagnostics_channel.d.ts +576 -0
  126. package/node_modules/@types/node/dns/promises.d.ts +503 -0
  127. package/node_modules/@types/node/dns.d.ts +923 -0
  128. package/node_modules/@types/node/domain.d.ts +170 -0
  129. package/node_modules/@types/node/events.d.ts +976 -0
  130. package/node_modules/@types/node/fs/promises.d.ts +1306 -0
  131. package/node_modules/@types/node/fs.d.ts +4463 -0
  132. package/node_modules/@types/node/globals.d.ts +172 -0
  133. package/node_modules/@types/node/globals.typedarray.d.ts +38 -0
  134. package/node_modules/@types/node/http.d.ts +2097 -0
  135. package/node_modules/@types/node/http2.d.ts +2843 -0
  136. package/node_modules/@types/node/https.d.ts +579 -0
  137. package/node_modules/@types/node/index.d.ts +98 -0
  138. package/node_modules/@types/node/inspector.d.ts +253 -0
  139. package/node_modules/@types/node/inspector.generated.d.ts +4052 -0
  140. package/node_modules/@types/node/module.d.ts +891 -0
  141. package/node_modules/@types/node/net.d.ts +1076 -0
  142. package/node_modules/@types/node/os.d.ts +506 -0
  143. package/node_modules/@types/node/package.json +145 -0
  144. package/node_modules/@types/node/path.d.ts +200 -0
  145. package/node_modules/@types/node/perf_hooks.d.ts +968 -0
  146. package/node_modules/@types/node/process.d.ts +2084 -0
  147. package/node_modules/@types/node/punycode.d.ts +117 -0
  148. package/node_modules/@types/node/querystring.d.ts +152 -0
  149. package/node_modules/@types/node/readline/promises.d.ts +161 -0
  150. package/node_modules/@types/node/readline.d.ts +594 -0
  151. package/node_modules/@types/node/repl.d.ts +428 -0
  152. package/node_modules/@types/node/sea.d.ts +162 -0
  153. package/node_modules/@types/node/sqlite.d.ts +722 -0
  154. package/node_modules/@types/node/stream/consumers.d.ts +38 -0
  155. package/node_modules/@types/node/stream/promises.d.ts +90 -0
  156. package/node_modules/@types/node/stream/web.d.ts +573 -0
  157. package/node_modules/@types/node/stream.d.ts +1694 -0
  158. package/node_modules/@types/node/string_decoder.d.ts +67 -0
  159. package/node_modules/@types/node/test.d.ts +2265 -0
  160. package/node_modules/@types/node/timers/promises.d.ts +108 -0
  161. package/node_modules/@types/node/timers.d.ts +287 -0
  162. package/node_modules/@types/node/tls.d.ts +1319 -0
  163. package/node_modules/@types/node/trace_events.d.ts +197 -0
  164. package/node_modules/@types/node/ts5.6/buffer.buffer.d.ts +468 -0
  165. package/node_modules/@types/node/ts5.6/globals.typedarray.d.ts +34 -0
  166. package/node_modules/@types/node/ts5.6/index.d.ts +98 -0
  167. package/node_modules/@types/node/tty.d.ts +208 -0
  168. package/node_modules/@types/node/url.d.ts +984 -0
  169. package/node_modules/@types/node/util.d.ts +2606 -0
  170. package/node_modules/@types/node/v8.d.ts +920 -0
  171. package/node_modules/@types/node/vm.d.ts +1053 -0
  172. package/node_modules/@types/node/wasi.d.ts +181 -0
  173. package/node_modules/@types/node/web-globals/abortcontroller.d.ts +34 -0
  174. package/node_modules/@types/node/web-globals/domexception.d.ts +68 -0
  175. package/node_modules/@types/node/web-globals/events.d.ts +97 -0
  176. package/node_modules/@types/node/web-globals/fetch.d.ts +55 -0
  177. package/node_modules/@types/node/web-globals/navigator.d.ts +22 -0
  178. package/node_modules/@types/node/web-globals/storage.d.ts +24 -0
  179. package/node_modules/@types/node/web-globals/streams.d.ts +22 -0
  180. package/node_modules/@types/node/worker_threads.d.ts +819 -0
  181. package/node_modules/@types/node/zlib.d.ts +747 -0
  182. package/node_modules/@types/react/LICENSE +21 -0
  183. package/node_modules/@types/react/README.md +15 -0
  184. package/node_modules/@types/react/canary.d.ts +129 -0
  185. package/node_modules/@types/react/compiler-runtime.d.ts +4 -0
  186. package/node_modules/@types/react/experimental.d.ts +184 -0
  187. package/node_modules/@types/react/global.d.ts +166 -0
  188. package/node_modules/@types/react/index.d.ts +4378 -0
  189. package/node_modules/@types/react/jsx-dev-runtime.d.ts +33 -0
  190. package/node_modules/@types/react/jsx-runtime.d.ts +24 -0
  191. package/node_modules/@types/react/package.json +210 -0
  192. package/node_modules/@types/react/ts5.0/canary.d.ts +129 -0
  193. package/node_modules/@types/react/ts5.0/experimental.d.ts +184 -0
  194. package/node_modules/@types/react/ts5.0/global.d.ts +166 -0
  195. package/node_modules/@types/react/ts5.0/index.d.ts +4365 -0
  196. package/node_modules/@types/react/ts5.0/jsx-dev-runtime.d.ts +33 -0
  197. package/node_modules/@types/react/ts5.0/jsx-runtime.d.ts +24 -0
  198. package/node_modules/csstype/LICENSE +19 -0
  199. package/node_modules/csstype/README.md +291 -0
  200. package/node_modules/csstype/index.d.ts +22569 -0
  201. package/node_modules/csstype/index.js.flow +6863 -0
  202. package/node_modules/csstype/package.json +70 -0
  203. package/node_modules/entities/LICENSE +11 -0
  204. package/node_modules/entities/decode.d.ts +1 -0
  205. package/node_modules/entities/decode.js +3 -0
  206. package/node_modules/entities/dist/commonjs/decode-codepoint.d.ts +19 -0
  207. package/node_modules/entities/dist/commonjs/decode-codepoint.d.ts.map +1 -0
  208. package/node_modules/entities/dist/commonjs/decode-codepoint.js +77 -0
  209. package/node_modules/entities/dist/commonjs/decode-codepoint.js.map +1 -0
  210. package/node_modules/entities/dist/commonjs/decode.d.ts +209 -0
  211. package/node_modules/entities/dist/commonjs/decode.d.ts.map +1 -0
  212. package/node_modules/entities/dist/commonjs/decode.js +511 -0
  213. package/node_modules/entities/dist/commonjs/decode.js.map +1 -0
  214. package/node_modules/entities/dist/commonjs/encode.d.ts +22 -0
  215. package/node_modules/entities/dist/commonjs/encode.d.ts.map +1 -0
  216. package/node_modules/entities/dist/commonjs/encode.js +73 -0
  217. package/node_modules/entities/dist/commonjs/encode.js.map +1 -0
  218. package/node_modules/entities/dist/commonjs/escape.d.ts +43 -0
  219. package/node_modules/entities/dist/commonjs/escape.d.ts.map +1 -0
  220. package/node_modules/entities/dist/commonjs/escape.js +121 -0
  221. package/node_modules/entities/dist/commonjs/escape.js.map +1 -0
  222. package/node_modules/entities/dist/commonjs/generated/decode-data-html.d.ts +2 -0
  223. package/node_modules/entities/dist/commonjs/generated/decode-data-html.d.ts.map +1 -0
  224. package/node_modules/entities/dist/commonjs/generated/decode-data-html.js +10 -0
  225. package/node_modules/entities/dist/commonjs/generated/decode-data-html.js.map +1 -0
  226. package/node_modules/entities/dist/commonjs/generated/decode-data-xml.d.ts +2 -0
  227. package/node_modules/entities/dist/commonjs/generated/decode-data-xml.d.ts.map +1 -0
  228. package/node_modules/entities/dist/commonjs/generated/decode-data-xml.js +10 -0
  229. package/node_modules/entities/dist/commonjs/generated/decode-data-xml.js.map +1 -0
  230. package/node_modules/entities/dist/commonjs/generated/encode-html.d.ts +8 -0
  231. package/node_modules/entities/dist/commonjs/generated/encode-html.d.ts.map +1 -0
  232. package/node_modules/entities/dist/commonjs/generated/encode-html.js +13 -0
  233. package/node_modules/entities/dist/commonjs/generated/encode-html.js.map +1 -0
  234. package/node_modules/entities/dist/commonjs/index.d.ts +96 -0
  235. package/node_modules/entities/dist/commonjs/index.d.ts.map +1 -0
  236. package/node_modules/entities/dist/commonjs/index.js +131 -0
  237. package/node_modules/entities/dist/commonjs/index.js.map +1 -0
  238. package/node_modules/entities/dist/commonjs/package.json +3 -0
  239. package/node_modules/entities/dist/esm/decode-codepoint.d.ts +19 -0
  240. package/node_modules/entities/dist/esm/decode-codepoint.d.ts.map +1 -0
  241. package/node_modules/entities/dist/esm/decode-codepoint.js +72 -0
  242. package/node_modules/entities/dist/esm/decode-codepoint.js.map +1 -0
  243. package/node_modules/entities/dist/esm/decode.d.ts +209 -0
  244. package/node_modules/entities/dist/esm/decode.d.ts.map +1 -0
  245. package/node_modules/entities/dist/esm/decode.js +497 -0
  246. package/node_modules/entities/dist/esm/decode.js.map +1 -0
  247. package/node_modules/entities/dist/esm/encode.d.ts +22 -0
  248. package/node_modules/entities/dist/esm/encode.d.ts.map +1 -0
  249. package/node_modules/entities/dist/esm/encode.js +69 -0
  250. package/node_modules/entities/dist/esm/encode.js.map +1 -0
  251. package/node_modules/entities/dist/esm/escape.d.ts +43 -0
  252. package/node_modules/entities/dist/esm/escape.d.ts.map +1 -0
  253. package/node_modules/entities/dist/esm/escape.js +117 -0
  254. package/node_modules/entities/dist/esm/escape.js.map +1 -0
  255. package/node_modules/entities/dist/esm/generated/decode-data-html.d.ts +2 -0
  256. package/node_modules/entities/dist/esm/generated/decode-data-html.d.ts.map +1 -0
  257. package/node_modules/entities/dist/esm/generated/decode-data-html.js +7 -0
  258. package/node_modules/entities/dist/esm/generated/decode-data-html.js.map +1 -0
  259. package/node_modules/entities/dist/esm/generated/decode-data-xml.d.ts +2 -0
  260. package/node_modules/entities/dist/esm/generated/decode-data-xml.d.ts.map +1 -0
  261. package/node_modules/entities/dist/esm/generated/decode-data-xml.js +7 -0
  262. package/node_modules/entities/dist/esm/generated/decode-data-xml.js.map +1 -0
  263. package/node_modules/entities/dist/esm/generated/encode-html.d.ts +8 -0
  264. package/node_modules/entities/dist/esm/generated/encode-html.d.ts.map +1 -0
  265. package/node_modules/entities/dist/esm/generated/encode-html.js +10 -0
  266. package/node_modules/entities/dist/esm/generated/encode-html.js.map +1 -0
  267. package/node_modules/entities/dist/esm/index.d.ts +96 -0
  268. package/node_modules/entities/dist/esm/index.d.ts.map +1 -0
  269. package/node_modules/entities/dist/esm/index.js +107 -0
  270. package/node_modules/entities/dist/esm/index.js.map +1 -0
  271. package/node_modules/entities/dist/esm/package.json +3 -0
  272. package/node_modules/entities/escape.d.ts +1 -0
  273. package/node_modules/entities/escape.js +3 -0
  274. package/node_modules/entities/package.json +118 -0
  275. package/node_modules/entities/readme.md +122 -0
  276. package/node_modules/entities/src/decode-codepoint.ts +81 -0
  277. package/node_modules/entities/src/decode.spec.ts +320 -0
  278. package/node_modules/entities/src/decode.ts +620 -0
  279. package/node_modules/entities/src/encode.spec.ts +78 -0
  280. package/node_modules/entities/src/encode.ts +77 -0
  281. package/node_modules/entities/src/escape.spec.ts +14 -0
  282. package/node_modules/entities/src/escape.ts +148 -0
  283. package/node_modules/entities/src/generated/.eslintrc.json +10 -0
  284. package/node_modules/entities/src/generated/decode-data-html.ts +8 -0
  285. package/node_modules/entities/src/generated/decode-data-xml.ts +8 -0
  286. package/node_modules/entities/src/generated/encode-html.ts +17 -0
  287. package/node_modules/entities/src/index.spec.ts +125 -0
  288. package/node_modules/entities/src/index.ts +188 -0
  289. package/node_modules/parse5/LICENSE +19 -0
  290. package/node_modules/parse5/README.md +38 -0
  291. package/node_modules/parse5/dist/cjs/common/doctype.d.ts +4 -0
  292. package/node_modules/parse5/dist/cjs/common/doctype.js +118 -0
  293. package/node_modules/parse5/dist/cjs/common/error-codes.d.ts +67 -0
  294. package/node_modules/parse5/dist/cjs/common/error-codes.js +66 -0
  295. package/node_modules/parse5/dist/cjs/common/foreign-content.d.ts +9 -0
  296. package/node_modules/parse5/dist/cjs/common/foreign-content.js +237 -0
  297. package/node_modules/parse5/dist/cjs/common/html.d.ts +289 -0
  298. package/node_modules/parse5/dist/cjs/common/html.js +528 -0
  299. package/node_modules/parse5/dist/cjs/common/token.d.ts +84 -0
  300. package/node_modules/parse5/dist/cjs/common/token.js +24 -0
  301. package/node_modules/parse5/dist/cjs/common/unicode.d.ts +42 -0
  302. package/node_modules/parse5/dist/cjs/common/unicode.js +70 -0
  303. package/node_modules/parse5/dist/cjs/index.d.ts +71 -0
  304. package/node_modules/parse5/dist/cjs/index.js +54 -0
  305. package/node_modules/parse5/dist/cjs/package.json +1 -0
  306. package/node_modules/parse5/dist/cjs/parser/formatting-element-list.d.ts +36 -0
  307. package/node_modules/parse5/dist/cjs/parser/formatting-element-list.js +114 -0
  308. package/node_modules/parse5/dist/cjs/parser/index.d.ts +221 -0
  309. package/node_modules/parse5/dist/cjs/parser/index.js +3240 -0
  310. package/node_modules/parse5/dist/cjs/parser/open-element-stack.d.ts +53 -0
  311. package/node_modules/parse5/dist/cjs/parser/open-element-stack.js +328 -0
  312. package/node_modules/parse5/dist/cjs/serializer/index.d.ts +60 -0
  313. package/node_modules/parse5/dist/cjs/serializer/index.js +172 -0
  314. package/node_modules/parse5/dist/cjs/tokenizer/index.d.ts +247 -0
  315. package/node_modules/parse5/dist/cjs/tokenizer/index.js +2714 -0
  316. package/node_modules/parse5/dist/cjs/tokenizer/preprocessor.d.ts +36 -0
  317. package/node_modules/parse5/dist/cjs/tokenizer/preprocessor.js +200 -0
  318. package/node_modules/parse5/dist/cjs/tree-adapters/default.d.ts +84 -0
  319. package/node_modules/parse5/dist/cjs/tree-adapters/default.js +176 -0
  320. package/node_modules/parse5/dist/cjs/tree-adapters/interface.d.ts +255 -0
  321. package/node_modules/parse5/dist/cjs/tree-adapters/interface.js +2 -0
  322. package/node_modules/parse5/dist/common/doctype.d.ts +4 -0
  323. package/node_modules/parse5/dist/common/doctype.js +114 -0
  324. package/node_modules/parse5/dist/common/error-codes.d.ts +67 -0
  325. package/node_modules/parse5/dist/common/error-codes.js +63 -0
  326. package/node_modules/parse5/dist/common/foreign-content.d.ts +9 -0
  327. package/node_modules/parse5/dist/common/foreign-content.js +228 -0
  328. package/node_modules/parse5/dist/common/html.d.ts +289 -0
  329. package/node_modules/parse5/dist/common/html.js +523 -0
  330. package/node_modules/parse5/dist/common/token.d.ts +84 -0
  331. package/node_modules/parse5/dist/common/token.js +20 -0
  332. package/node_modules/parse5/dist/common/unicode.d.ts +42 -0
  333. package/node_modules/parse5/dist/common/unicode.js +62 -0
  334. package/node_modules/parse5/dist/index.d.ts +71 -0
  335. package/node_modules/parse5/dist/index.js +42 -0
  336. package/node_modules/parse5/dist/parser/formatting-element-list.d.ts +36 -0
  337. package/node_modules/parse5/dist/parser/formatting-element-list.js +110 -0
  338. package/node_modules/parse5/dist/parser/index.d.ts +221 -0
  339. package/node_modules/parse5/dist/parser/index.js +3245 -0
  340. package/node_modules/parse5/dist/parser/open-element-stack.d.ts +53 -0
  341. package/node_modules/parse5/dist/parser/open-element-stack.js +324 -0
  342. package/node_modules/parse5/dist/serializer/index.d.ts +60 -0
  343. package/node_modules/parse5/dist/serializer/index.js +168 -0
  344. package/node_modules/parse5/dist/tokenizer/index.d.ts +247 -0
  345. package/node_modules/parse5/dist/tokenizer/index.js +2710 -0
  346. package/node_modules/parse5/dist/tokenizer/preprocessor.d.ts +36 -0
  347. package/node_modules/parse5/dist/tokenizer/preprocessor.js +196 -0
  348. package/node_modules/parse5/dist/tree-adapters/default.d.ts +84 -0
  349. package/node_modules/parse5/dist/tree-adapters/default.js +173 -0
  350. package/node_modules/parse5/dist/tree-adapters/interface.d.ts +255 -0
  351. package/node_modules/parse5/dist/tree-adapters/interface.js +1 -0
  352. package/node_modules/parse5/package.json +50 -0
  353. package/node_modules/undici-types/LICENSE +21 -0
  354. package/node_modules/undici-types/README.md +6 -0
  355. package/node_modules/undici-types/agent.d.ts +31 -0
  356. package/node_modules/undici-types/api.d.ts +43 -0
  357. package/node_modules/undici-types/balanced-pool.d.ts +29 -0
  358. package/node_modules/undici-types/cache.d.ts +36 -0
  359. package/node_modules/undici-types/client.d.ts +108 -0
  360. package/node_modules/undici-types/connector.d.ts +34 -0
  361. package/node_modules/undici-types/content-type.d.ts +21 -0
  362. package/node_modules/undici-types/cookies.d.ts +28 -0
  363. package/node_modules/undici-types/diagnostics-channel.d.ts +66 -0
  364. package/node_modules/undici-types/dispatcher.d.ts +256 -0
  365. package/node_modules/undici-types/env-http-proxy-agent.d.ts +21 -0
  366. package/node_modules/undici-types/errors.d.ts +149 -0
  367. package/node_modules/undici-types/eventsource.d.ts +61 -0
  368. package/node_modules/undici-types/fetch.d.ts +209 -0
  369. package/node_modules/undici-types/file.d.ts +39 -0
  370. package/node_modules/undici-types/filereader.d.ts +54 -0
  371. package/node_modules/undici-types/formdata.d.ts +108 -0
  372. package/node_modules/undici-types/global-dispatcher.d.ts +9 -0
  373. package/node_modules/undici-types/global-origin.d.ts +7 -0
  374. package/node_modules/undici-types/handlers.d.ts +15 -0
  375. package/node_modules/undici-types/header.d.ts +4 -0
  376. package/node_modules/undici-types/index.d.ts +71 -0
  377. package/node_modules/undici-types/interceptors.d.ts +17 -0
  378. package/node_modules/undici-types/mock-agent.d.ts +50 -0
  379. package/node_modules/undici-types/mock-client.d.ts +25 -0
  380. package/node_modules/undici-types/mock-errors.d.ts +12 -0
  381. package/node_modules/undici-types/mock-interceptor.d.ts +93 -0
  382. package/node_modules/undici-types/mock-pool.d.ts +25 -0
  383. package/node_modules/undici-types/package.json +55 -0
  384. package/node_modules/undici-types/patch.d.ts +33 -0
  385. package/node_modules/undici-types/pool-stats.d.ts +19 -0
  386. package/node_modules/undici-types/pool.d.ts +39 -0
  387. package/node_modules/undici-types/proxy-agent.d.ts +28 -0
  388. package/node_modules/undici-types/readable.d.ts +65 -0
  389. package/node_modules/undici-types/retry-agent.d.ts +8 -0
  390. package/node_modules/undici-types/retry-handler.d.ts +116 -0
  391. package/node_modules/undici-types/util.d.ts +18 -0
  392. package/node_modules/undici-types/webidl.d.ts +228 -0
  393. package/node_modules/undici-types/websocket.d.ts +150 -0
  394. package/package.json +80 -0
  395. package/scripts/rebuild-forme.mjs +61 -0
  396. package/src/app/AppearanceControl.tsx +44 -0
  397. package/src/app/AssetThemeDefaults.tsx +87 -0
  398. package/src/app/AssetsBrowser.tsx +273 -0
  399. package/src/app/CommentDock.tsx +108 -0
  400. package/src/app/CreateDocumentDialog.tsx +51 -0
  401. package/src/app/DocumentActions.tsx +69 -0
  402. package/src/app/DocumentCard.tsx +50 -0
  403. package/src/app/DocumentViewControl.tsx +31 -0
  404. package/src/app/DocumentsBrowser.tsx +70 -0
  405. package/src/app/ExportHistoryDialog.tsx +86 -0
  406. package/src/app/ExportMenu.tsx +127 -0
  407. package/src/app/GuideDialog.tsx +80 -0
  408. package/src/app/InspectionLayer.tsx +77 -0
  409. package/src/app/LogoUploadDialog.tsx +92 -0
  410. package/src/app/Markdown.tsx +34 -0
  411. package/src/app/MaterialsBrowser.tsx +149 -0
  412. package/src/app/PageNumberInput.tsx +56 -0
  413. package/src/app/Pdf.tsx +561 -0
  414. package/src/app/ProjectDialogs.tsx +100 -0
  415. package/src/app/Reader.tsx +769 -0
  416. package/src/app/SearchField.tsx +13 -0
  417. package/src/app/Sidebar.tsx +86 -0
  418. package/src/app/SkillIndex.tsx +30 -0
  419. package/src/app/TemplatesBrowser.tsx +149 -0
  420. package/src/app/ThemesBrowser.tsx +159 -0
  421. package/src/app/api.ts +47 -0
  422. package/src/app/appearance.ts +35 -0
  423. package/src/app/assets.css +139 -0
  424. package/src/app/catalogPreview.ts +20 -0
  425. package/src/app/colors.css +167 -0
  426. package/src/app/comment-dock.css +53 -0
  427. package/src/app/commentDrafts.ts +67 -0
  428. package/src/app/componentCorrection.ts +17 -0
  429. package/src/app/edit-workbench.css +87 -0
  430. package/src/app/editSession.ts +238 -0
  431. package/src/app/editorRecovery.ts +25 -0
  432. package/src/app/export-history.css +31 -0
  433. package/src/app/export.css +17 -0
  434. package/src/app/guideLinks.ts +21 -0
  435. package/src/app/guides.css +40 -0
  436. package/src/app/main.tsx +221 -0
  437. package/src/app/materials.css +53 -0
  438. package/src/app/pdfLoading.ts +12 -0
  439. package/src/app/pdfSelection.ts +112 -0
  440. package/src/app/projects.css +63 -0
  441. package/src/app/reader-toolbar.css +65 -0
  442. package/src/app/selection.css +150 -0
  443. package/src/app/sidebar.css +60 -0
  444. package/src/app/skills.css +7 -0
  445. package/src/app/style.css +1061 -0
  446. package/src/app/templates.css +49 -0
  447. package/src/app/themes.css +80 -0
  448. package/src/app/ui/Icon.tsx +92 -0
  449. package/src/app/ui/README.md +27 -0
  450. package/src/app/ui/fonts.css +36 -0
  451. package/src/app/ui/index.tsx +223 -0
  452. package/src/app/ui/ui.css +388 -0
  453. package/src/app/useReaderPreview.ts +19 -0
  454. package/src/app/useTextEditing.ts +155 -0
  455. package/src/assets/adapter.ts +122 -0
  456. package/src/assets/files.ts +378 -0
  457. package/src/assets/import-worker.ts +294 -0
  458. package/src/assets/imports.ts +124 -0
  459. package/src/assets/index.ts +60 -0
  460. package/src/assets/store.ts +798 -0
  461. package/src/assets/usage.ts +118 -0
  462. package/src/cli/check.ts +25 -0
  463. package/src/cli/init.ts +119 -0
  464. package/src/cli/lock.ts +34 -0
  465. package/src/cli/main.ts +118 -0
  466. package/src/cli/process.ts +19 -0
  467. package/src/cli/start.ts +96 -0
  468. package/src/cli/update.ts +348 -0
  469. package/src/cli/workspace.ts +94 -0
  470. package/src/document/index.tsx +641 -0
  471. package/src/document/page.tsx +53 -0
  472. package/src/document/text-targets.tsx +162 -0
  473. package/src/media/files.ts +124 -0
  474. package/src/media/render.ts +63 -0
  475. package/src/rendering/resolve-sources.ts +84 -0
  476. package/src/runtime/jsx-dev-runtime.ts +2 -0
  477. package/src/runtime/jsx-runtime.ts +2 -0
  478. package/src/runtime/paths.ts +12 -0
  479. package/src/server/assets-cli.ts +162 -0
  480. package/src/server/assets-http.ts +100 -0
  481. package/src/server/client.ts +50 -0
  482. package/src/server/clipboard.ts +22 -0
  483. package/src/server/comments-cli.ts +64 -0
  484. package/src/server/comments.ts +130 -0
  485. package/src/server/create-cli.ts +33 -0
  486. package/src/server/create.ts +228 -0
  487. package/src/server/dependencies.ts +91 -0
  488. package/src/server/documents.ts +143 -0
  489. package/src/server/edits.ts +249 -0
  490. package/src/server/export-batch.ts +105 -0
  491. package/src/server/export-errors.ts +2 -0
  492. package/src/server/export-file.ts +57 -0
  493. package/src/server/export-inputs.ts +66 -0
  494. package/src/server/export-preview.ts +69 -0
  495. package/src/server/export.ts +32 -0
  496. package/src/server/exports.ts +283 -0
  497. package/src/server/files.ts +21 -0
  498. package/src/server/guides.ts +71 -0
  499. package/src/server/index.ts +432 -0
  500. package/src/server/materials.ts +39 -0
  501. package/src/server/media-cli.ts +73 -0
  502. package/src/server/media-mutations.ts +168 -0
  503. package/src/server/pptx.ts +160 -0
  504. package/src/server/preflight.ts +129 -0
  505. package/src/server/projects-cli.ts +29 -0
  506. package/src/server/projects.ts +187 -0
  507. package/src/server/render-error.ts +9 -0
  508. package/src/server/render-source.ts +24 -0
  509. package/src/server/render.ts +48 -0
  510. package/src/server/review-cli.ts +40 -0
  511. package/src/server/review.ts +161 -0
  512. package/src/server/selection.ts +23 -0
  513. package/src/server/source-overrides.ts +97 -0
  514. package/src/server/templates-cli.ts +51 -0
  515. package/src/server/templates.ts +186 -0
  516. package/src/server/text-layout.ts +41 -0
  517. package/src/server/text-source.ts +307 -0
  518. package/src/server/themes-cli.ts +59 -0
  519. package/src/server/themes.ts +215 -0
  520. package/src/server/ttf2eot.d.ts +3 -0
  521. package/src/server/worker.ts +166 -0
  522. package/src/server/workspace.ts +242 -0
  523. package/src/shared/anchors.ts +69 -0
  524. package/src/shared/assets.ts +154 -0
  525. package/src/shared/export.ts +34 -0
  526. package/src/shared/media.ts +33 -0
  527. package/src/shared/projects.ts +19 -0
  528. package/src/shared/selection.ts +94 -0
  529. package/src/shared/skill-index.ts +14 -0
  530. package/src/shared/starters.ts +19 -0
  531. package/src/shared/templates.ts +18 -0
  532. package/src/shared/themes.ts +24 -0
  533. package/src/shared/types.ts +39 -0
  534. package/src/template/index.ts +27 -0
  535. package/src/themes/index.ts +3 -0
  536. package/src/themes/neutral.ts +20 -0
  537. package/src/themes/types.ts +110 -0
  538. package/starter/assets/brand/wordmark.png +0 -0
  539. package/starter/assets/fonts/InterUI-LICENSE.txt +93 -0
  540. package/starter/assets/fonts/InterUI-Medium.ttf +0 -0
  541. package/starter/assets/fonts/InterUI-Regular.ttf +0 -0
  542. package/starter/assets/fonts/InterUI-source.json +19 -0
  543. package/starter/assets/fonts/OpenDocMono-Regular.ttf +0 -0
  544. package/starter/assets/fonts/OpenDocSans-Italic.ttf +0 -0
  545. package/starter/assets/fonts/OpenDocSans-Regular.ttf +0 -0
  546. package/starter/assets/fonts/OpenDocSans-Semibold.ttf +0 -0
  547. package/starter/assets/fonts/OpenDocSans-SemiboldItalic.ttf +0 -0
  548. package/starter/assets/fonts/OpenDocSerif-Italic.ttf +0 -0
  549. package/starter/assets/fonts/OpenDocSerif-Regular.ttf +0 -0
  550. package/starter/assets/fonts/OpenDocSerif-Semibold.ttf +0 -0
  551. package/starter/assets/fonts/OpenDocSerif-SemiboldItalic.ttf +0 -0
  552. package/starter/assets/fonts/SourceCode-LICENSE.md +93 -0
  553. package/starter/assets/fonts/SourceSans-LICENSE.md +93 -0
  554. package/starter/assets/fonts/SourceSerif-LICENSE.md +93 -0
  555. package/starter/assets/fonts/geist/OFL.txt +93 -0
  556. package/starter/assets/fonts/geist/asset.json +7 -0
  557. package/starter/assets/fonts/geist/files/1a9874b3be0fc168692a3d2327bccb4d28835653d3b91537a755296a467a83e3.ttf +0 -0
  558. package/starter/assets/fonts/geist/files/a8eefb873ba9c9020665f8a98302697ce1f3b79ab2deef6ee508c0ca6a82e876.ttf +0 -0
  559. package/starter/assets/fonts/geist/files/ccce59465ae5c61930ada5453834f60500fbd95084d85db63e0e65fad0125455.ttf +0 -0
  560. package/starter/assets/fonts/geist/files/f5e95110d70593672515080bee9148463ada0a7f77b89ed65a3d43cb2b0ff83a.pdf +0 -0
  561. package/starter/assets/fonts/geist/revisions/9a2bc12fb2f23cb27bc227087e63e1407bb804ee0586480bd6b7966ec69fb474.json +60 -0
  562. package/starter/assets/fonts/inter/OFL.txt +93 -0
  563. package/starter/assets/fonts/inter/asset.json +7 -0
  564. package/starter/assets/fonts/inter/files/1b08e7fc267a5c7e1d614100f604b83e7e8a0be241f0f288faa2b3ac93a683ba.ttf +0 -0
  565. package/starter/assets/fonts/inter/files/5b6f0c6f26e5c554a64cdcc3a9b5d802b7f9aec6206f1dbcfcdcad7b5860a2a9.ttf +0 -0
  566. package/starter/assets/fonts/inter/files/957db8560d510a198a7d0333fae7b6b444b00c20801581126273978d778eb3c3.pdf +0 -0
  567. package/starter/assets/fonts/inter/files/b37284b5701b6b168dfc770aa1a4ac492106422fd3ba76bc7641e37434e8019c.ttf +0 -0
  568. package/starter/assets/fonts/inter/files/c1811d32ac0f6eed1bbf57529f2ecdfc6c717d48a969dd864942436315a1b198.ttf +0 -0
  569. package/starter/assets/fonts/inter/files/e7a1aaf7eda9f2fad4131725fa556265ec75ca7b2d756260173a040363e8d4f7.ttf +0 -0
  570. package/starter/assets/fonts/inter/files/ef8f755d933b9310804cc9152f1f18cb2ac460d6b05054ec913d889c9f11f79e.ttf +0 -0
  571. package/starter/assets/fonts/inter/revisions/600391517c413963b71e278a57c26c40366c81fa3939a6b9755f446206766d12.json +99 -0
  572. package/starter/assets/fonts/inter/source.json +57 -0
  573. package/starter/assets/fonts/lato/OFL.txt +93 -0
  574. package/starter/assets/fonts/lato/asset.json +7 -0
  575. package/starter/assets/fonts/lato/files/4a6b566dbb28adb473a8789ce5169306f42f0c8a070ac92bae93636f36348b0d.pdf +0 -0
  576. package/starter/assets/fonts/lato/files/5adeb6b334d8fb99ef053bc052b952773d544315e0e1d668a12c0ce8bd51375c.ttf +0 -0
  577. package/starter/assets/fonts/lato/files/62c1b7f0d2e74b45960154c3520efc337b553db0961bfdc950d5618334596cc8.ttf +0 -0
  578. package/starter/assets/fonts/lato/files/71b8b7decbe75a881ed267be539d402bd1e9420b799658aada4e0d1bd5af803c.ttf +0 -0
  579. package/starter/assets/fonts/lato/files/8a0aace75d33794eece4b28187bfc1df0bbd2888b5d8a56e01788c8d65d16be1.ttf +0 -0
  580. package/starter/assets/fonts/lato/files/d636e4683231f931eda222d588e944d082bfd3bdba02f928bee461c0f185b251.ttf +0 -0
  581. package/starter/assets/fonts/lato/files/e399c44efe1387100531d26c7e4800c5d12251b890d6654a3098c7c679cb1786.ttf +0 -0
  582. package/starter/assets/fonts/lato/revisions/49ccb93400a9d466fab7d9843b1ecf8916ec6d47a475dd79a597a22eb39616bc.json +99 -0
  583. package/starter/assets/fonts/lato/source.json +57 -0
  584. package/starter/assets/fonts/merriweather/OFL.txt +93 -0
  585. package/starter/assets/fonts/merriweather/asset.json +7 -0
  586. package/starter/assets/fonts/merriweather/files/1c54f91b910454251c4c2b7bd4e472e1abf265fba7ca3c0ef2eee2f41324099e.ttf +0 -0
  587. package/starter/assets/fonts/merriweather/files/8bde8da2bb7f0265e27ba0481f70bb48d0f98a976c145f2f239c648ddd6532e0.pdf +0 -0
  588. package/starter/assets/fonts/merriweather/files/90b7db4460fcd1a5ca49575c2668a793f3cd6447664d312a068d690e1c01e6bc.ttf +0 -0
  589. package/starter/assets/fonts/merriweather/files/afff81272b7653417bb6f8bef57add055b1f9fda36f27eba5f32b40752bd08da.ttf +0 -0
  590. package/starter/assets/fonts/merriweather/files/e029d3b71e5bae3877c73f4f2b15af28db16593d49b0aed2a328d37ff7075846.ttf +0 -0
  591. package/starter/assets/fonts/merriweather/files/e9864a6790bf712a04e20f3662b206a1535186ed3e877fd859dd5fb9682335df.ttf +0 -0
  592. package/starter/assets/fonts/merriweather/files/fa71baebdd20a7d0de2e5e445a5922530fe9e1d740efbe791f113b682d3cb093.ttf +0 -0
  593. package/starter/assets/fonts/merriweather/revisions/1888e390b3e4f38a3d7cd2dc1bb805aeb6ce13f986a97656f0265fd436bc91d2.json +99 -0
  594. package/starter/assets/fonts/merriweather/source.json +57 -0
  595. package/starter/assets/fonts/open-sans/OFL.txt +92 -0
  596. package/starter/assets/fonts/open-sans/asset.json +7 -0
  597. package/starter/assets/fonts/open-sans/files/1bc0594a83762b2c25d38915a34464b7fec6088298ca22d151757bf98d6700ec.ttf +0 -0
  598. package/starter/assets/fonts/open-sans/files/47ed1cfaa7ffca04a04ab850b2938fd52c09d1b44cfa3e2980878e166b0cbd87.ttf +0 -0
  599. package/starter/assets/fonts/open-sans/files/574939af301f07df8e21cfff279465c5981dd9d9cf62086f7515897df951c6f5.pdf +0 -0
  600. package/starter/assets/fonts/open-sans/files/6d3884d0d57c1b67e9c50c54eca7b69c535c114e76bd7a9cfda0258588185553.ttf +0 -0
  601. package/starter/assets/fonts/open-sans/files/733eb93b167e6d45de09aa768f6c0355647c2c26f5416dbc7407a08ed41e81c6.ttf +0 -0
  602. package/starter/assets/fonts/open-sans/files/75367e6da7d914eaed87883ba51b2a2b5816b5cdbb39f96ec5c81a5b032bfd5a.ttf +0 -0
  603. package/starter/assets/fonts/open-sans/files/ff4fdc11204e0edd14e9a0bb6c374b847a9093b0ec2c16702e03afca79ac4ef7.ttf +0 -0
  604. package/starter/assets/fonts/open-sans/revisions/bbbe3a4915b9bccbc883c0decb4097511c43b6bd09ae22a1516aff128c219bc5.json +99 -0
  605. package/starter/assets/fonts/open-sans/source.json +57 -0
  606. package/starter/assets/fonts/roboto/OFL.txt +93 -0
  607. package/starter/assets/fonts/roboto/asset.json +7 -0
  608. package/starter/assets/fonts/roboto/files/22d45d989248a624252b8382067ac56550212fa675dcdbaf12f03d297dd87a58.pdf +0 -0
  609. package/starter/assets/fonts/roboto/files/35757eaa996b3359022f47f164a67ee9eac04971af24c5b8fbd4b89e844ff190.ttf +0 -0
  610. package/starter/assets/fonts/roboto/files/4d3035883c531846bd1ce2dc89f36d932e72dc114f56062c7e3a9cdd74f3d3ea.ttf +0 -0
  611. package/starter/assets/fonts/roboto/files/be7639306a4fc4a02ce2ae2ff59eb1335e4db4a7379dd0a384ed7cc053817789.ttf +0 -0
  612. package/starter/assets/fonts/roboto/files/d2920cbb1b60a3fd11fbf22049286e43a6baf522d37fdf1e7c89562319bcfea5.ttf +0 -0
  613. package/starter/assets/fonts/roboto/files/dece7c71bbe61710787f0839c722c65e87355dd2eb4a823e6b5488121ff81e4b.ttf +0 -0
  614. package/starter/assets/fonts/roboto/files/f478ba88b0292e685e487901dfbb1401457d32d6ebee4030cdfd5698235bffa8.ttf +0 -0
  615. package/starter/assets/fonts/roboto/revisions/bda5e2d84c17313373dd2dbf3e64fe0dd450c7e1b59e6929e69557e122fe976e.json +99 -0
  616. package/starter/assets/fonts/roboto/source.json +57 -0
  617. package/starter/assets/logos/opendoc/asset.json +6 -0
  618. package/starter/assets/logos/opendoc/files/831d5ccd9eb6709cc8439ea257c03d81452e81a28a3d1eedbd6a3b7daff687b6.png +0 -0
  619. package/starter/assets/logos/opendoc/revisions/834dde8926ddb81de687254195a168bb13bfb77803307f1257de1f2228be63b2.json +33 -0
  620. package/starter/assets/ui-fonts/geist/OFL.txt +93 -0
  621. package/starter/assets/ui-fonts/geist/geist-latin-ext-wght-italic.woff2 +0 -0
  622. package/starter/assets/ui-fonts/geist/geist-latin-ext-wght-normal.woff2 +0 -0
  623. package/starter/assets/ui-fonts/geist/geist-latin-wght-italic.woff2 +0 -0
  624. package/starter/assets/ui-fonts/geist/geist-latin-wght-normal.woff2 +0 -0
  625. package/starter/assets/ui-fonts/geist/source.json +26 -0
  626. package/starter/documents/welcome/assets.json +7 -0
  627. package/starter/documents/welcome/index.tsx +160 -0
  628. package/starter/documents/welcome/media/.gitkeep +0 -0
  629. package/starter/documents/welcome/media/paper-architecture/image.png +0 -0
  630. package/starter/documents/welcome/media/paper-architecture/meta.json +9 -0
  631. package/starter/documents/welcome/media/paper-architecture/prompt.txt +1 -0
  632. package/starter/documents/welcome/media/studio-rhythm/chart.svg +2343 -0
  633. package/starter/documents/welcome/media/studio-rhythm/data.json +234 -0
  634. package/starter/documents/welcome/media/studio-rhythm/generation.json +9 -0
  635. package/starter/documents/welcome/media/studio-rhythm/image.png +0 -0
  636. package/starter/documents/welcome/media/studio-rhythm/meta.json +11 -0
  637. package/starter/documents/welcome/media/studio-rhythm/recipe.py +79 -0
  638. package/starter/documents/welcome/theme.tsx +5 -0
  639. package/starter/documents/welcome-presentation/assets.json +7 -0
  640. package/starter/documents/welcome-presentation/index.tsx +166 -0
  641. package/starter/documents/welcome-presentation/media/.gitkeep +0 -0
  642. package/starter/documents/welcome-presentation/media/paper-architecture/image.png +0 -0
  643. package/starter/documents/welcome-presentation/media/paper-architecture/meta.json +9 -0
  644. package/starter/documents/welcome-presentation/media/paper-architecture/prompt.txt +1 -0
  645. package/starter/documents/welcome-presentation/media/studio-rhythm/chart.svg +1336 -0
  646. package/starter/documents/welcome-presentation/media/studio-rhythm/data.json +234 -0
  647. package/starter/documents/welcome-presentation/media/studio-rhythm/generation.json +9 -0
  648. package/starter/documents/welcome-presentation/media/studio-rhythm/image.png +0 -0
  649. package/starter/documents/welcome-presentation/media/studio-rhythm/meta.json +13 -0
  650. package/starter/documents/welcome-presentation/media/studio-rhythm/recipe.py +49 -0
  651. package/starter/documents/welcome-presentation/theme.tsx +5 -0
  652. package/starter/projects.json +17 -0
  653. package/starter/templates/AGENTS.md +9 -0
  654. package/starter/templates/_shared/BusinessLogo.tsx +14 -0
  655. package/starter/templates/_shared/Pricing.tsx +40 -0
  656. package/starter/templates/_shared/commerce.ts +93 -0
  657. package/starter/templates/brand-guidelines/AGENTS.md +32 -0
  658. package/starter/templates/brand-guidelines/index.tsx +62 -0
  659. package/starter/templates/brand-guidelines/preview.tsx +181 -0
  660. package/starter/templates/brand-guidelines/starter.tsx +179 -0
  661. package/starter/templates/brand-guidelines/template.json +15 -0
  662. package/starter/templates/business-proposal/AGENTS.md +13 -0
  663. package/starter/templates/business-proposal/README.md +36 -0
  664. package/starter/templates/business-proposal/data.json +37 -0
  665. package/starter/templates/business-proposal/examples/long.json +142 -0
  666. package/starter/templates/business-proposal/examples/sparse.json +17 -0
  667. package/starter/templates/business-proposal/examples/typical.json +37 -0
  668. package/starter/templates/business-proposal/index.tsx +59 -0
  669. package/starter/templates/business-proposal/preview.tsx +47 -0
  670. package/starter/templates/business-proposal/schema.ts +14 -0
  671. package/starter/templates/business-proposal/starter.tsx +18 -0
  672. package/starter/templates/business-proposal/template.json +11 -0
  673. package/starter/templates/company-profile/AGENTS.md +48 -0
  674. package/starter/templates/company-profile/index.tsx +69 -0
  675. package/starter/templates/company-profile/preview.tsx +185 -0
  676. package/starter/templates/company-profile/starter.tsx +186 -0
  677. package/starter/templates/company-profile/template.json +15 -0
  678. package/starter/templates/consulting-report/AGENTS.md +11 -0
  679. package/starter/templates/consulting-report/README.md +13 -0
  680. package/starter/templates/consulting-report/RESEARCH.md +10 -0
  681. package/starter/templates/consulting-report/index.tsx +44 -0
  682. package/starter/templates/consulting-report/preview.tsx +39 -0
  683. package/starter/templates/consulting-report/starter.tsx +15 -0
  684. package/starter/templates/consulting-report/template.json +11 -0
  685. package/starter/templates/editorial-essay/AGENTS.md +11 -0
  686. package/starter/templates/editorial-essay/README.md +11 -0
  687. package/starter/templates/editorial-essay/index.tsx +30 -0
  688. package/starter/templates/editorial-essay/preview.tsx +26 -0
  689. package/starter/templates/editorial-essay/starter.tsx +15 -0
  690. package/starter/templates/editorial-essay/template.json +11 -0
  691. package/starter/templates/executive-brief/AGENTS.md +9 -0
  692. package/starter/templates/executive-brief/README.md +11 -0
  693. package/starter/templates/executive-brief/index.tsx +33 -0
  694. package/starter/templates/executive-brief/preview.tsx +37 -0
  695. package/starter/templates/executive-brief/starter.tsx +11 -0
  696. package/starter/templates/executive-brief/template.json +11 -0
  697. package/starter/templates/image-story/AGENTS.md +37 -0
  698. package/starter/templates/image-story/index.tsx +106 -0
  699. package/starter/templates/image-story/preview.tsx +33 -0
  700. package/starter/templates/image-story/starter.tsx +24 -0
  701. package/starter/templates/image-story/template.json +13 -0
  702. package/starter/templates/invoice/AGENTS.md +13 -0
  703. package/starter/templates/invoice/README.md +40 -0
  704. package/starter/templates/invoice/data.json +61 -0
  705. package/starter/templates/invoice/examples/long.json +241 -0
  706. package/starter/templates/invoice/examples/sparse.json +33 -0
  707. package/starter/templates/invoice/examples/typical.json +61 -0
  708. package/starter/templates/invoice/index.tsx +57 -0
  709. package/starter/templates/invoice/preview.tsx +12 -0
  710. package/starter/templates/invoice/schema.ts +45 -0
  711. package/starter/templates/invoice/starter.tsx +12 -0
  712. package/starter/templates/invoice/template.json +11 -0
  713. package/starter/templates/literary-text/AGENTS.md +9 -0
  714. package/starter/templates/literary-text/README.md +11 -0
  715. package/starter/templates/literary-text/index.tsx +31 -0
  716. package/starter/templates/literary-text/preview.tsx +20 -0
  717. package/starter/templates/literary-text/starter.tsx +11 -0
  718. package/starter/templates/literary-text/template.json +11 -0
  719. package/starter/templates/magazine-feature/AGENTS.md +11 -0
  720. package/starter/templates/magazine-feature/README.md +13 -0
  721. package/starter/templates/magazine-feature/index.tsx +41 -0
  722. package/starter/templates/magazine-feature/preview.tsx +34 -0
  723. package/starter/templates/magazine-feature/starter.tsx +15 -0
  724. package/starter/templates/magazine-feature/template.json +11 -0
  725. package/starter/templates/monthly-report/AGENTS.md +11 -0
  726. package/starter/templates/monthly-report/README.md +24 -0
  727. package/starter/templates/monthly-report/data.json +131 -0
  728. package/starter/templates/monthly-report/examples/long.json +519 -0
  729. package/starter/templates/monthly-report/examples/sparse.json +20 -0
  730. package/starter/templates/monthly-report/examples/typical.json +37 -0
  731. package/starter/templates/monthly-report/index.tsx +92 -0
  732. package/starter/templates/monthly-report/preview.tsx +14 -0
  733. package/starter/templates/monthly-report/schema.ts +127 -0
  734. package/starter/templates/monthly-report/starter.tsx +13 -0
  735. package/starter/templates/monthly-report/template.json +11 -0
  736. package/starter/templates/pitch-deck/AGENTS.md +32 -0
  737. package/starter/templates/pitch-deck/index.tsx +46 -0
  738. package/starter/templates/pitch-deck/preview.tsx +142 -0
  739. package/starter/templates/pitch-deck/starter.tsx +140 -0
  740. package/starter/templates/pitch-deck/template.json +15 -0
  741. package/starter/templates/proposal-deck/AGENTS.md +25 -0
  742. package/starter/templates/proposal-deck/index.tsx +52 -0
  743. package/starter/templates/proposal-deck/preview.tsx +172 -0
  744. package/starter/templates/proposal-deck/starter.tsx +168 -0
  745. package/starter/templates/proposal-deck/template.json +7 -0
  746. package/starter/templates/quotation/AGENTS.md +13 -0
  747. package/starter/templates/quotation/README.md +42 -0
  748. package/starter/templates/quotation/data.json +64 -0
  749. package/starter/templates/quotation/examples/long.json +272 -0
  750. package/starter/templates/quotation/examples/sparse.json +31 -0
  751. package/starter/templates/quotation/examples/typical.json +64 -0
  752. package/starter/templates/quotation/index.tsx +51 -0
  753. package/starter/templates/quotation/preview.tsx +12 -0
  754. package/starter/templates/quotation/schema.ts +27 -0
  755. package/starter/templates/quotation/starter.tsx +12 -0
  756. package/starter/templates/quotation/template.json +11 -0
  757. package/starter/templates/scientific-paper/AGENTS.md +11 -0
  758. package/starter/templates/scientific-paper/README.md +13 -0
  759. package/starter/templates/scientific-paper/index.tsx +47 -0
  760. package/starter/templates/scientific-paper/preview.tsx +43 -0
  761. package/starter/templates/scientific-paper/starter.tsx +15 -0
  762. package/starter/templates/scientific-paper/template.json +11 -0
  763. package/starter/themes/Specimen.tsx +54 -0
  764. package/starter/themes/civic-spectrum/components.tsx +92 -0
  765. package/starter/themes/civic-spectrum/design.md +70 -0
  766. package/starter/themes/civic-spectrum/index.ts +57 -0
  767. package/starter/themes/civic-spectrum/preview.tsx +66 -0
  768. package/starter/themes/field-manual/components.tsx +106 -0
  769. package/starter/themes/field-manual/design.md +72 -0
  770. package/starter/themes/field-manual/index.ts +57 -0
  771. package/starter/themes/field-manual/preview.tsx +82 -0
  772. package/starter/themes/index.ts +3 -0
  773. package/starter/themes/mckinsey-consulting/components.tsx +69 -0
  774. package/starter/themes/mckinsey-consulting/design.md +68 -0
  775. package/starter/themes/mckinsey-consulting/index.ts +60 -0
  776. package/starter/themes/mckinsey-consulting/preview.tsx +53 -0
  777. package/starter/themes/neutral/design.md +35 -0
  778. package/starter/themes/neutral/index.ts +21 -0
  779. package/starter/themes/neutral/preview.tsx +4 -0
  780. package/starter/themes/opendoc-neutral/components.tsx +77 -0
  781. package/starter/themes/opendoc-neutral/design.md +70 -0
  782. package/starter/themes/opendoc-neutral/index.ts +54 -0
  783. package/starter/themes/opendoc-neutral/preview.tsx +43 -0
  784. package/starter/themes/types.ts +3 -0
  785. package/tsconfig.workspace.json +24 -0
  786. package/vendor/formepdf/LICENSE +21 -0
  787. package/vendor/formepdf/README.md +97 -0
  788. package/vendor/formepdf/font-subset.patch +228 -0
  789. package/vendor/formepdf/text-writer.patch +507 -0
@@ -0,0 +1,31 @@
1
+ import { useEffect, useState } from 'react';
2
+ import { ToggleGroup } from '@base-ui/react/toggle-group';
3
+ import { ToggleButton } from './ui';
4
+ import { Icon } from './ui/Icon';
5
+
6
+ export type DocumentView = 'gallery' | 'list';
7
+ const storageKey = 'opendoc-document-view';
8
+ function readView(): DocumentView {
9
+ try { if (localStorage.getItem(storageKey) === 'list') return 'list'; } catch { /* Keep gallery available without browser storage. */ }
10
+ return 'gallery';
11
+ }
12
+
13
+ export function useDocumentView() {
14
+ const [view, setView] = useState<DocumentView>(readView);
15
+ useEffect(() => {
16
+ const sync = (event: StorageEvent) => { if (event.key === storageKey || event.key === null) setView(readView()); };
17
+ window.addEventListener('storage', sync);
18
+ return () => window.removeEventListener('storage', sync);
19
+ }, []);
20
+ return { view, changeView: (next: DocumentView) => {
21
+ setView(next);
22
+ try { localStorage.setItem(storageKey, next); } catch { /* Keep the selection for this session. */ }
23
+ } };
24
+ }
25
+
26
+ export function DocumentViewControl({ value, onChange }: { value: DocumentView; onChange: (value: DocumentView) => void }) {
27
+ return <ToggleGroup className="document-view-toggle" aria-label="Document view" value={[value]} onValueChange={values => { if (values[0]) onChange(values[0]); }}>
28
+ <ToggleButton static label="Gallery view" value="gallery"><Icon name="grid" size={16} /><span>Gallery</span></ToggleButton>
29
+ <ToggleButton static label="List view" value="list"><Icon name="list" size={16} /><span>List</span></ToggleButton>
30
+ </ToggleGroup>;
31
+ }
@@ -0,0 +1,70 @@
1
+ import { useState, type ReactNode } from 'react';
2
+ import { Menu } from '@base-ui/react/menu';
3
+ import type { Project } from '../shared/projects';
4
+ import { documentName, documentFormat, type DocumentFormat, type DocumentSummary } from '../shared/types';
5
+ import { DocumentCard } from './DocumentCard';
6
+ import type { DocumentActionHandler } from './DocumentActions';
7
+ import { DocumentViewControl, type DocumentView } from './DocumentViewControl';
8
+ import { Button, IconButton, SelectControl } from './ui';
9
+ import { SearchField } from './SearchField';
10
+ import { Icon } from './ui/Icon';
11
+ import './projects.css';
12
+
13
+ type ViewProps = { view: DocumentView; onViewChange: (view: DocumentView) => void };
14
+
15
+ function DocumentTools({ noun = 'document', query, onQueryChange, view, onViewChange, filter }: ViewProps & { noun?: string; query: string; onQueryChange: (query: string) => void; filter?: ReactNode }) {
16
+ return <div className="library-tools document-browser-tools">
17
+ <SearchField label={`Search ${noun}s`} value={query} onValueChange={onQueryChange} />
18
+ {filter}
19
+ <div className="document-view-tools"><DocumentViewControl value={view} onChange={onViewChange} /></div>
20
+ </div>;
21
+ }
22
+
23
+ export function DocumentsBrowser({ format = 'document', projects, documents, loaded, view, onViewChange, onCreate, onMove, onAction, disabled }: ViewProps & {
24
+ format?: DocumentFormat; projects: Project[]; documents: DocumentSummary[]; loaded: boolean; onCreate: () => void; onMove: (document: DocumentSummary) => void; onAction: DocumentActionHandler; disabled: boolean;
25
+ }) {
26
+ const [query, setQuery] = useState('');
27
+ const plural = format === 'presentation' ? 'Presentations' : 'Documents';
28
+ const projectById = new Map(projects.map(project => [project.id, project]));
29
+ const visible = documents.filter(document => `${documentName(document)} ${document.artifact?.meta.description ?? ''} ${projectById.get(document.projectId ?? '')?.name ?? ''}`.toLowerCase().includes(query.toLowerCase()));
30
+ return <section className="library-content documents-content">
31
+ <div className="library-heading"><h1>{plural}</h1><Button className="primary" aria-label={`Create ${format}`} onClick={onCreate} disabled={!loaded || disabled}><Icon name="plus" size={17} /><span>Create {format}</span></Button></div>
32
+ <DocumentTools noun={format} query={query} onQueryChange={setQuery} view={view} onViewChange={onViewChange} />
33
+ <div className={view === 'list' ? 'documents-list' : 'document-grid'} role="list">{visible.map(document => {
34
+ const project = projectById.get(document.projectId ?? '');
35
+ return <article className="project-document" key={document.id} role="listitem">
36
+ <DocumentCard document={document} view={view} onAction={onAction} disabled={disabled} onOpen={() => { location.hash = `document/${document.id}`; }} />
37
+ {project ? <a className="document-project-link" href={`#project/${project.id}`}><Icon name="folder" size={15} /><span>{project.name}</span></a>
38
+ : <Button className="text-button document-move" onClick={() => onMove(document)} disabled={!projects.length}>Choose a project</Button>}
39
+ </article>;
40
+ })}</div>
41
+ {!visible.length && <div className="empty-state"><h2>{!loaded ? `Loading ${plural.toLowerCase()}…` : query ? `No matching ${plural.toLowerCase()}` : `Your ${plural.toLowerCase()} start here`}</h2><p>{!loaded ? 'Opening your local workspace.' : query ? 'Try a different title or project name.' : projects.length ? format === 'presentation' ? 'Create a presentation with your agent and review every slide here.' : 'Create a document in a project, or choose a layout from Templates.' : 'Create your first project using the + beside Projects in the sidebar.'}</p></div>}
42
+ </section>;
43
+ }
44
+
45
+ export function ProjectDocuments({ project, documents, loaded, view, onViewChange, onCreate, onCreatePresentation, onSettings, onAction, disabled }: ViewProps & {
46
+ project: Project; documents: DocumentSummary[]; loaded: boolean; onCreate: () => void; onCreatePresentation: () => void; onSettings: () => void; onAction: DocumentActionHandler; disabled: boolean;
47
+ }) {
48
+ const [query, setQuery] = useState('');
49
+ const [format, setFormat] = useState<'all' | DocumentFormat>('all');
50
+ const hasFilters = Boolean(query) || format !== 'all';
51
+ const visible = documents.filter(document => (format === 'all' || documentFormat(document) === format)
52
+ && `${documentName(document)} ${document.artifact?.meta.description ?? ''}`.toLowerCase().includes(query.toLowerCase()));
53
+ return <section className="library-content project-documents">
54
+ <div className="library-heading"><h1>{project.name}</h1><div className="project-actions">
55
+ <IconButton label="Project settings" onClick={onSettings}><Icon name="gear" size={18} /></IconButton>
56
+ <Menu.Root><Menu.Trigger render={<Button className="primary project-create-trigger" disabled={!loaded || disabled} />}>
57
+ <Icon name="plus" size={16} /><span>Create</span><Icon name="down" size={14} />
58
+ </Menu.Trigger><Menu.Portal><Menu.Positioner className="ui-positioner" sideOffset={6} align="end"><Menu.Popup className="ui-menu-popup">
59
+ <Menu.Item className="ui-menu-item" onClick={onCreate}><Icon name="document" size={16} />Document</Menu.Item>
60
+ <Menu.Item className="ui-menu-item" onClick={onCreatePresentation}><Icon name="monitor" size={16} />Presentation</Menu.Item>
61
+ </Menu.Popup></Menu.Positioner></Menu.Portal></Menu.Root>
62
+ </div></div>
63
+ <DocumentTools noun="item" query={query} onQueryChange={setQuery} view={view} onViewChange={onViewChange}
64
+ filter={<SelectControl label="Filter by format" value={format} onValueChange={value => { if (value === 'all' || value === 'document' || value === 'presentation') setFormat(value); }} items={[
65
+ { label: 'All formats', value: 'all' }, { label: 'Documents', value: 'document' }, { label: 'Presentations', value: 'presentation' },
66
+ ]} />} />
67
+ <div className={view === 'list' ? 'documents-list' : 'document-grid'} role="list">{visible.map(document => <article className="project-document" key={document.id} role="listitem"><DocumentCard document={document} view={view} onAction={onAction} disabled={disabled} onOpen={() => { location.hash = `document/${document.id}`; }} /></article>)}</div>
68
+ {!visible.length && <div className="empty-state"><h2>{!loaded ? 'Loading project…' : hasFilters ? 'No matching items' : 'Your project is ready'}</h2><p>{!loaded ? 'Opening your local workspace.' : hasFilters ? 'Try another search or choose a different format.' : 'Create a document or presentation with your agent.'}</p>{loaded && (hasFilters ? <Button onClick={() => { setQuery(''); setFormat('all'); }}>Clear filters</Button> : <a href="#templates" className="project-templates-link">Browse templates<Icon name="arrow" size={16} /></a>)}</div>}
69
+ </section>;
70
+ }
@@ -0,0 +1,86 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { documentName, type DocumentSummary } from '../shared/types';
3
+ import { exportInfo, type ExportHistoryEntry } from '../shared/export';
4
+ import { api } from './api';
5
+ import { Button, Dialog, IconButton, useNotifications } from './ui';
6
+ import { Icon } from './ui/Icon';
7
+ import './export-history.css';
8
+
9
+ const date = new Intl.DateTimeFormat(undefined, { dateStyle: 'medium', timeStyle: 'short' });
10
+ function fileSize(bytes: number) { return `${new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(bytes / (bytes >= 1048576 ? 1048576 : 1024))} ${bytes >= 1048576 ? 'MB' : 'KB'}`; }
11
+
12
+ export function ExportHistoryDialog({ document, connected, onClose }: { document: DocumentSummary | null; connected: boolean; onClose: () => void }) {
13
+ const [entries, setEntries] = useState<ExportHistoryEntry[]>([]);
14
+ const [loading, setLoading] = useState(false);
15
+ const [error, setError] = useState('');
16
+ const [canCopy, setCanCopy] = useState(false);
17
+ const [busy, setBusy] = useState<string | null>(null);
18
+ const working = useRef(false);
19
+ const request = useRef(0);
20
+ const selected = useRef(document?.id);
21
+ selected.current = document?.id;
22
+ const notifications = useNotifications();
23
+ const refresh = useCallback(async () => {
24
+ if (!document || !connected) return;
25
+ const sequence = ++request.current;
26
+ setLoading(true); setError('');
27
+ try {
28
+ const rows = await api<ExportHistoryEntry[]>(`/api/documents/${document.id}/exports`, { signal: AbortSignal.timeout(12_000) });
29
+ if (sequence === request.current) setEntries(rows);
30
+ } catch (failure) { if (sequence === request.current) setError((failure as Error).message); }
31
+ finally { if (sequence === request.current) setLoading(false); }
32
+ }, [document?.id, connected]);
33
+ useEffect(() => {
34
+ setEntries([]); setError(''); setCanCopy(false);
35
+ void refresh();
36
+ if (document && connected) {
37
+ api<{ copyFile: boolean }>('/api/exports/capabilities', { signal: AbortSignal.timeout(5_000) }).then(value => { if (selected.current === document.id) setCanCopy(value.copyFile); }).catch(() => {});
38
+ }
39
+ const focus = () => { if (!working.current) void refresh(); };
40
+ window.addEventListener('focus', focus);
41
+ return () => { request.current++; window.removeEventListener('focus', focus); };
42
+ }, [refresh]);
43
+ async function act(entry: ExportHistoryEntry, action: 'copy' | 'reveal' | 'delete') {
44
+ if (working.current || !connected) return;
45
+ working.current = true; setBusy(entry.id); setError('');
46
+ try {
47
+ await api(`/api/exports/${entry.id}${action === 'delete' ? '' : `/${action}`}`, { method: action === 'delete' ? 'DELETE' : 'POST', signal: AbortSignal.timeout(12_000) });
48
+ if (action === 'delete') {
49
+ request.current++; setLoading(false);
50
+ setEntries(rows => rows.filter(row => row.id !== entry.id));
51
+ notifications.success(`${entry.filename} removed`, { label: 'Undo', onClick: async () => {
52
+ await api(`/api/exports/${entry.id}/restore`, { method: 'POST', signal: AbortSignal.timeout(12_000) });
53
+ if (selected.current === entry.documentId) await refresh();
54
+ notifications.success('Export restored');
55
+ } });
56
+ } else if (action === 'copy') notifications.success(`${exportInfo(entry.format).label} file copied to clipboard`);
57
+ } catch (failure) { setError((failure as Error).message); }
58
+ finally { working.current = false; setBusy(null); }
59
+ }
60
+ return <Dialog.Root open={!!document} onOpenChange={open => { if (!open && !working.current) onClose(); }}>
61
+ <Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" /><Dialog.Popup className="help-dialog export-history-dialog">
62
+ <Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close export history" disabled={!!busy} />}><Icon name="close" /></Dialog.Close>
63
+ <div className="export-history-heading"><span className="export-history-symbol"><Icon name="history" size={22} /></span><div><Dialog.Title>Previous exports</Dialog.Title><Dialog.Description>{document ? documentName(document) : ''}</Dialog.Description></div></div>
64
+ <div className="export-history-summary"><span>{loading ? 'Checking saved files…' : `${entries.length} ${entries.length === 1 ? 'export' : 'exports'} · Newest first`}</span><Button className="text-button" disabled={loading || !!busy || !connected} onClick={() => void refresh()}>Refresh</Button></div>
65
+ {!connected && <p className="field-error" role="status">Reconnect to view and manage exports.</p>}
66
+ {error && <p className="field-error" role="alert">{error}</p>}
67
+ <div className="export-history-content" aria-busy={loading || !!busy}>
68
+ {!entries.length && !loading && !error && connected && <div className="export-history-empty"><Icon name="document" size={34} /><strong>No exports yet</strong><p>Export this document and it will appear here.</p></div>}
69
+ {!!entries.length && <ul className="export-history-list">{entries.map(entry => <li key={entry.id} className={`export-history-row${entry.available ? '' : ' unavailable'}`}>
70
+ <span className="export-history-paper" aria-hidden="true"><Icon name="document" size={24} /><span>{(entry.format ?? 'pdf').toUpperCase()}</span></span>
71
+ <div className="export-history-details">
72
+ {entry.available ? <a href={`/api/exports/${entry.id}/open`} target="_blank" rel="noreferrer" title={`Open ${entry.filename}`}>{entry.filename}</a> : <strong>{entry.filename}</strong>}
73
+ <span><time dateTime={entry.createdAt}>{date.format(new Date(entry.createdAt))}</time><span aria-hidden="true"> · </span>{fileSize(entry.bytes)}</span>
74
+ {!entry.available && <span className="export-history-unavailable">File moved, removed, or changed</span>}
75
+ </div>
76
+ <div className="export-history-actions" aria-label={`Actions for ${entry.filename}`}>
77
+ {canCopy && <IconButton label={`Copy ${entry.filename}`} disabled={!!busy || !connected || !entry.available} onClick={() => void act(entry, 'copy')}><Icon name="copy" size={17} /></IconButton>}
78
+ <IconButton label={`${canCopy ? 'Show in Finder' : 'Show in folder'}: ${entry.filename}`} disabled={!!busy || !connected || !entry.available} onClick={() => void act(entry, 'reveal')}><Icon name="folder" size={17} /></IconButton>
79
+ <IconButton label={`Delete export: ${entry.filename}`} className="export-history-delete" disabled={!!busy || !connected} onClick={() => void act(entry, 'delete')}><Icon name="trash" size={17} /></IconButton>
80
+ </div>
81
+ </li>)}</ul>}
82
+ </div>
83
+ <p className="export-history-footnote">Saved files are independent of later document edits. Deleting an export offers Undo.</p>
84
+ </Dialog.Popup></Dialog.Portal>
85
+ </Dialog.Root>;
86
+ }
@@ -0,0 +1,127 @@
1
+ import { useEffect, useRef, useState } from 'react';
2
+ import { Popover } from '@base-ui/react/popover';
3
+ import { documentName, documentFormat, type DocumentState } from '../shared/types';
4
+ import { suggestedExportName, exportInfo, type ExportFormat, type SavedExport } from '../shared/export';
5
+ import { api, ApiError } from './api';
6
+ import { Button, Input, SelectControl } from './ui';
7
+ import { Icon } from './ui/Icon';
8
+ import './export.css';
9
+
10
+ type Attempt = { id: string; hash: string; filename: string; format?: ExportFormat };
11
+ function remembered(id: string): Attempt | null {
12
+ try {
13
+ const value = JSON.parse(sessionStorage.getItem(`opendoc:export:${id}`) ?? 'null');
14
+ return value && typeof value.id === 'string' && typeof value.hash === 'string' && typeof value.filename === 'string' && (value.format === undefined || value.format === 'pdf' || value.format === 'pptx') ? value : null;
15
+ } catch { return null; }
16
+ }
17
+
18
+ export function ExportMenu({ state, connected, ready, unsaved, saving, correctionError, canSave, onSave, onShowExports }: {
19
+ state: DocumentState; connected: boolean; ready: boolean; unsaved: number; saving: boolean; correctionError: string;
20
+ canSave: boolean; onSave: () => Promise<boolean | undefined>; onShowExports: () => void;
21
+ }) {
22
+ const [open, setOpen] = useState(false);
23
+ const [format, setFormat] = useState<ExportFormat>(() => remembered(state.id)?.format ?? 'pdf');
24
+ const [filename, setFilename] = useState(() => suggestedExportName(documentName(state), format));
25
+ const [attempt, setAttempt] = useState<Attempt | null>(() => remembered(state.id));
26
+ const [result, setResult] = useState<SavedExport | null>(null);
27
+ const [busy, setBusy] = useState(false);
28
+ const working = useRef(false);
29
+ const [error, setError] = useState('');
30
+ const [notice, setNotice] = useState('');
31
+ const [canCopy, setCanCopy] = useState(false);
32
+ const [copying, setCopying] = useState(false);
33
+ const input = useRef<HTMLInputElement>(null);
34
+ const showFile = useRef<HTMLButtonElement>(null);
35
+ const showHistory = useRef(false);
36
+ const activeFormat = result?.format ?? attempt?.format ?? format;
37
+ const formatLabel = exportInfo(activeFormat).label;
38
+ useEffect(() => { if (open && result) showFile.current?.focus(); }, [open, result]);
39
+ useEffect(() => {
40
+ if (!open || !connected) return;
41
+ let current = true;
42
+ api<{ copyFile: boolean }>('/api/exports/capabilities', { signal: AbortSignal.timeout(5_000) })
43
+ .then(value => { if (current) setCanCopy(value.copyFile); })
44
+ .catch(() => { if (current) setCanCopy(false); });
45
+ return () => { current = false; };
46
+ }, [open, connected]);
47
+ function remember(value: Attempt | null) {
48
+ setAttempt(value);
49
+ try { if (value) sessionStorage.setItem(`opendoc:export:${state.id}`, JSON.stringify(value)); else sessionStorage.removeItem(`opendoc:export:${state.id}`); } catch { /* The open reader retains its request. */ }
50
+ }
51
+ async function perform() {
52
+ if (working.current || !connected || (!attempt && (!ready || unsaved || saving || correctionError))) return;
53
+ working.current = true; setBusy(true); setError(''); setNotice('');
54
+ const request = attempt ?? { id: crypto.randomUUID(), hash: state.artifact!.hash, filename, format };
55
+ remember(request);
56
+ try {
57
+ // Retrying the same receipt never creates a second output, including after a restart.
58
+ const saved = await api<SavedExport>(`/api/documents/${state.id}/exports`, { method: 'POST', body: JSON.stringify(request), signal: AbortSignal.timeout(20_000) });
59
+ setResult(saved); remember(null);
60
+ } catch (failure) {
61
+ if (failure instanceof ApiError && failure.status >= 400 && failure.status < 500) { remember(null); setError(failure.message); }
62
+ else setError('OpenDoc could not confirm whether the file was saved. Check the export to recover it without creating another copy.');
63
+ } finally { working.current = false; setBusy(false); }
64
+ }
65
+ async function reveal() {
66
+ if (!result || working.current) return;
67
+ working.current = true; setBusy(true); setError('');
68
+ try { await api(`/api/exports/${result.id}/reveal`, { method: 'POST', signal: AbortSignal.timeout(12_000) }); }
69
+ catch (failure) { setError((failure as Error).message); }
70
+ finally { working.current = false; setBusy(false); }
71
+ }
72
+ async function saveChanges() {
73
+ if (await onSave()) setOpen(false);
74
+ }
75
+ async function copy() {
76
+ if (!result || working.current || !connected || !canCopy) return;
77
+ working.current = true; setBusy(true); setCopying(true); setError(''); setNotice('');
78
+ try {
79
+ await api(`/api/exports/${result.id}/copy`, { method: 'POST', signal: AbortSignal.timeout(12_000) });
80
+ setNotice(`${formatLabel} file copied. Paste it into Finder or an app that accepts file attachments.`);
81
+ } catch (failure) { setError(failure instanceof ApiError ? failure.message : 'OpenDoc could not confirm the copy. Check your connection and try again.'); }
82
+ finally { working.current = false; setBusy(false); setCopying(false); }
83
+ }
84
+ return <Popover.Root open={open} onOpenChange={next => {
85
+ setOpen(next);
86
+ if (!next && result && !working.current) { setResult(null); setError(''); setNotice(''); setFilename(suggestedExportName(documentName(state), format)); }
87
+ }} onOpenChangeComplete={next => {
88
+ // Let the dropdown restore focus to Export before the history dialog opens.
89
+ if (!next && showHistory.current) { showHistory.current = false; onShowExports(); }
90
+ }}>
91
+ <Popover.Trigger render={<Button className="primary export-button" aria-label={`Export ${documentFormat(state)}`} />}><Icon name="download" size={16} /><span>Export</span><Icon name="down" size={12} /></Popover.Trigger>
92
+ <Popover.Portal><Popover.Positioner className="ui-positioner" align="end" sideOffset={8} collisionPadding={8}>
93
+ <Popover.Popup className="ui-popover-popup export-dropdown" initialFocus={interaction => interaction === 'touch' ? true : result || attempt ? true : input.current}>
94
+ <form onSubmit={event => { event.preventDefault(); if (!result) void perform(); }}>
95
+ <div className="export-dropdown-body">
96
+ <div className="export-dropdown-heading">
97
+ <Popover.Title>{result ? `${formatLabel} saved` : `Export ${formatLabel}`}</Popover.Title>
98
+ <Popover.Close render={<Button className="icon-button" aria-label="Close export menu" />}><Icon name="close" size={16} /></Popover.Close>
99
+ </div>
100
+ <Popover.Description>{result ? 'Saved in your workspace’s output folder.' : activeFormat === 'pptx' ? 'Editable text and shapes · Embedded fonts' : `Original quality · All ${documentFormat(state) === 'presentation' ? 'slides' : 'pages'}`}</Popover.Description>
101
+ {result ? <>
102
+ <div className="export-file" role="status"><Icon name="document" size={24} /><div><strong>{result.filename}</strong><span>{new Intl.NumberFormat(undefined, { maximumFractionDigits: 1 }).format(result.bytes / 1024)} KB · {activeFormat.toUpperCase()}</span></div><Icon name="check" size={18} /></div>
103
+ </> : <>
104
+ {documentFormat(state) === 'presentation' && !attempt && <div className="create-field export-format"><span>Format</span><SelectControl label="Export format" value={format} disabled={busy} items={[{ value: 'pdf', label: 'PDF' }, { value: 'pptx', label: 'PowerPoint (.pptx)' }]} onValueChange={value => { const next = value as ExportFormat; setFormat(next); setFilename(name => name.trim() ? name.replace(/\.(pdf|pptx)$/i, '') + exportInfo(next).extension : suggestedExportName(documentName(state), next)); setError(''); }} /></div>}
105
+ <label className="create-field export-filename" htmlFor="export-filename"><span>Filename</span><Input ref={input} id="export-filename" value={attempt?.filename ?? filename} onChange={event => { setFilename(event.target.value); setError(''); }} disabled={busy || !!attempt} maxLength={120} autoComplete="off" /></label>
106
+ {attempt && !busy && <p className="export-status" role="status">An export is awaiting confirmation. Check it to recover the saved file or finish the same request.</p>}
107
+ {!attempt && (unsaved > 0 || correctionError || saving) && <div className="export-status" role="status"><strong>{saving ? 'Saving your changes…' : 'Save your corrections first'}</strong><p>{correctionError || 'Save your changes and review the updated PDF before exporting.'}</p>{unsaved > 0 && <Button disabled={!canSave || saving} onClick={() => void saveChanges()}>Save changes first</Button>}</div>}
108
+ {!attempt && !unsaved && !saving && !correctionError && !ready && <p className="export-status" role="status">{!connected ? 'Reconnect to the local workspace before exporting.' : state.status === 'error' ? 'Fix the document’s render error before exporting.' : 'Waiting for the current PDF preview…'}</p>}
109
+ </>}
110
+ {error && <p className="field-error" role="alert">{error}</p>}
111
+ {notice && <p className="export-notice" role="status">{notice}</p>}
112
+ {busy && <p className="export-notice" role="status">{copying ? 'Copying the file…' : result ? 'Opening the export folder…' : `Saving ${formatLabel}… You can close this menu and return to check it.`}</p>}
113
+ {!result && <Button type="submit" className="primary export-save" disabled={busy || !connected || (!attempt && (!ready || !!unsaved || !!correctionError || saving || !filename.trim()))}>{busy ? 'Saving…' : attempt ? 'Check export' : `Save ${formatLabel}`}</Button>}
114
+ </div>
115
+ </form>
116
+ {result && <div className="ui-menu-group">
117
+ <Button ref={showFile} className="ui-menu-item" onClick={() => void reveal()} disabled={busy || !connected}><Icon name="folder" size={16} />Show in folder</Button>
118
+ <a className="ui-menu-item" href={`/api/exports/${result.id}/download`} download={result.filename} onClick={() => setNotice('Download requested. Your browser handles where the copy is saved.')}><Icon name="download" size={16} />Download a copy</a>
119
+ {canCopy && <Button className="ui-menu-item" onClick={() => void copy()} disabled={busy || !connected} focusableWhenDisabled={copying}><Icon name="copy" size={16} />Copy to clipboard</Button>}
120
+ {activeFormat === 'pdf' && <a className="ui-menu-item" href={`/api/exports/${result.id}/open`} target="_blank" rel="noreferrer"><Icon name="arrow" size={16} />Open PDF to print</a>}
121
+ <Button className="ui-menu-item" disabled={busy} onClick={() => { setResult(null); setError(''); setNotice(''); setFilename(suggestedExportName(documentName(state), format)); }}><Icon name="plus" size={16} />Export another copy</Button>
122
+ </div>}
123
+ <div className="ui-menu-separator" />
124
+ <Button className="ui-menu-item" onClick={() => { showHistory.current = true; setOpen(false); }}><Icon name="history" size={16} />Previous exports</Button>
125
+ </Popover.Popup></Popover.Positioner></Popover.Portal>
126
+ </Popover.Root>;
127
+ }
@@ -0,0 +1,80 @@
1
+ import { useCallback, useEffect, useRef, useState } from 'react';
2
+ import { api } from './api';
3
+ import { Button, Dialog } from './ui';
4
+ import { Icon } from './ui/Icon';
5
+ import type { GuideLocation } from './guideLinks';
6
+ import './guides.css';
7
+
8
+ let markdownModule: Promise<typeof import('./Markdown')> | undefined;
9
+ function loadMarkdown() {
10
+ markdownModule ??= import('./Markdown').catch(error => { markdownModule = undefined; throw error; });
11
+ return markdownModule;
12
+ }
13
+ type GuideVisit = GuideLocation & { scrollTop?: number };
14
+
15
+ export function GuideDialog({ kind, id, name, generation }: { kind: 'template' | 'theme'; id: string; name: string; generation: number | string }) {
16
+ const template = kind === 'template';
17
+ const initialFile = template ? `templates/${id}/AGENTS.md` : `themes/${id}/design.md`;
18
+ const title = template ? 'AGENTS.md' : 'Design guide';
19
+ const body = useRef<HTMLDivElement>(null);
20
+ const [open, setOpen] = useState(false);
21
+ const [visits, setVisits] = useState<GuideVisit[]>([{ file: initialFile }]);
22
+ const visit = visits.at(-1)!;
23
+ const [content, setContent] = useState<{ key: string; markdown: string; file: string; Renderer: typeof import('./Markdown')['Markdown'] }>();
24
+ const [error, setError] = useState('');
25
+ const [attempt, setAttempt] = useState(0);
26
+ const key = JSON.stringify([visit.file, generation, attempt]);
27
+ const guide = content?.key === key ? content : undefined;
28
+ const Markdown = guide?.Renderer;
29
+ const initial = visit.file === initialFile;
30
+
31
+ useEffect(() => {
32
+ if (!open) return;
33
+ const controller = new AbortController();
34
+ setError(''); setContent(undefined);
35
+ void api<{ markdown: string; file: string }>(`/api/guides?path=${encodeURIComponent(visit.file)}`, { signal: controller.signal })
36
+ .then(async result => {
37
+ if (controller.signal.aborted) return;
38
+ const { Markdown } = await loadMarkdown();
39
+ if (!controller.signal.aborted) setContent({ key, ...result, Renderer: Markdown });
40
+ })
41
+ .catch(error => { if (!controller.signal.aborted) { setError(error.message); body.current?.focus({ preventScroll: true }); } });
42
+ return () => controller.abort();
43
+ }, [open, visit.file, key]);
44
+
45
+ const restorePosition = useCallback(() => {
46
+ const container = body.current;
47
+ if (!container) return;
48
+ const heading = visit.fragment ? container.querySelector<HTMLElement>(`[data-guide-heading="${CSS.escape(visit.fragment)}"]`) : null;
49
+ if (visit.scrollTop !== undefined) container.scrollTop = visit.scrollTop;
50
+ else if (heading) heading.scrollIntoView({ block: 'start' });
51
+ else container.scrollTop = 0;
52
+ container.focus({ preventScroll: true });
53
+ }, [visit.file, visit.fragment, visit.scrollTop, visits.length]);
54
+
55
+ function navigate(next: GuideLocation) {
56
+ setError('');
57
+ const scrollTop = body.current?.scrollTop ?? 0;
58
+ setVisits(previous => [...previous.slice(0, -1), { ...previous.at(-1)!, scrollTop }, next]);
59
+ }
60
+
61
+ return <Dialog.Root open={open} onOpenChange={next => { if (next) { setVisits([{ file: initialFile }]); setError(''); } setOpen(next); }}>
62
+ <Dialog.Trigger render={<Button className={`text-button${template ? ' template-guide-trigger' : ''}`} />}>{template ? 'View AGENTS.md' : <>Design guide<Icon name="arrow" size={16} /></>}</Dialog.Trigger>
63
+ <Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" />
64
+ <Dialog.Popup className="help-dialog template-guide-dialog">
65
+ <header className="template-guide-heading">
66
+ <Dialog.Title>{initial ? title : visit.file.split('/').at(-1)}</Dialog.Title>
67
+ <Dialog.Description>{initial ? name : visit.file}</Dialog.Description>
68
+ <Dialog.Close render={<Button className="icon-button modal-close" aria-label={template ? 'Close template guide' : 'Close design guide'} />}><Icon name="close" /></Dialog.Close>
69
+ {visits.length > 1 && <Button className="text-button guide-back" onClick={() => { setError(''); setVisits(previous => previous.length > 1 ? previous.slice(0, -1) : previous); }}><Icon name="left" size={15} />Back to {visits.at(-2)!.file === initialFile ? title : visits.at(-2)!.file.split('/').at(-1)}</Button>}
70
+ </header>
71
+ <div ref={body} className="template-guide-body" tabIndex={0} role="region" aria-label={template ? 'Template guide content' : `${name} design guide`} aria-busy={!guide && !error}>
72
+ {error ? <div role="alert"><p>{initial ? template ? 'Could not load this template’s AGENTS.md.' : 'Could not load this theme’s design guide.' : 'Could not load this guide.'}</p><p>{error}</p><Button onClick={() => { body.current?.focus(); setAttempt(value => value + 1); }}>Try again</Button></div>
73
+ : !guide ? <p role="status">Loading guide…</p>
74
+ : guide.markdown.trim() && Markdown ? <Markdown file={guide.file} onNavigate={navigate} onReady={restorePosition}>{guide.markdown}</Markdown>
75
+ : <p>{initial ? template ? 'This template’s AGENTS.md is empty.' : 'This theme’s design guide is empty.' : 'This guide is empty.'}</p>}
76
+ </div>
77
+ </Dialog.Popup>
78
+ </Dialog.Portal>
79
+ </Dialog.Root>;
80
+ }
@@ -0,0 +1,77 @@
1
+ import { useLayoutEffect, useRef, useState, type PointerEvent, type ReactNode } from 'react';
2
+
3
+ type HoverTarget = { left: number; top: number; width: number; height: number; label: string };
4
+
5
+ // Keep one full-size rectangle alive. Enlarging one-pixel strips magnifies
6
+ // raster rounding at fractional display scales and separates the frame edges.
7
+ export function InspectionLayer({ children }: { children: ReactNode }) {
8
+ const [target, setTarget] = useState<HoverTarget | null>(null);
9
+ const [visible, setVisible] = useState(false);
10
+ const [morph, setMorph] = useState(false);
11
+ const hovered = useRef<HTMLElement | null>(null);
12
+ const hiddenAt = useRef(-Infinity);
13
+ const box = useRef<HTMLDivElement>(null);
14
+
15
+ useLayoutEffect(() => {
16
+ const element = box.current;
17
+ const reduced = window.matchMedia('(prefers-reduced-motion: reduce)');
18
+ const cancel = () => element?.getAnimations().forEach(animation => animation.cancel());
19
+ const onChange = () => { if (reduced.matches) cancel(); };
20
+ reduced.addEventListener('change', onChange);
21
+ return () => { reduced.removeEventListener('change', onChange); cancel(); };
22
+ }, []);
23
+
24
+ useLayoutEffect(() => {
25
+ const element = box.current;
26
+ if (!element || !target) return;
27
+ // Read the in-flight visual bounds before retargeting, then animate back
28
+ // from those bounds to the new geometry using a single transform.
29
+ const previous = element.getBoundingClientRect();
30
+ element.getAnimations().forEach(animation => animation.cancel());
31
+ Object.assign(element.style, {
32
+ left: `${target.left}px`, top: `${target.top}px`,
33
+ width: `${target.width}px`, height: `${target.height}px`,
34
+ });
35
+ const next = element.getBoundingClientRect();
36
+ if (!morph || !previous.width || !previous.height || !next.width || !next.height ||
37
+ !window.matchMedia('(hover: hover) and (pointer: fine) and (prefers-reduced-motion: no-preference)').matches) return;
38
+ element.animate([
39
+ { transform: `translate(${(previous.left - next.left) * target.width / next.width}px, ${(previous.top - next.top) * target.height / next.height}px) scale(${previous.width / next.width}, ${previous.height / next.height})` },
40
+ { transform: 'none' },
41
+ ], { duration: 180, easing: 'cubic-bezier(0.23, 1, 0.32, 1)' });
42
+ }, [target, morph]);
43
+
44
+ function hide() {
45
+ if (!hovered.current) return;
46
+ hovered.current = null;
47
+ hiddenAt.current = performance.now();
48
+ setVisible(false);
49
+ }
50
+
51
+ function hover(event: PointerEvent<HTMLDivElement>) {
52
+ if (event.pointerType !== 'mouse' || !window.matchMedia('(hover: hover) and (pointer: fine)').matches) return;
53
+ const element = event.target instanceof Element ? event.target.closest<HTMLElement>('.component-target') : null;
54
+ if (!element || element.classList.contains('selected')) { hide(); return; }
55
+ if (hovered.current === element) return;
56
+ setMorph(!!hovered.current || performance.now() - hiddenAt.current < 150);
57
+ hovered.current = element;
58
+ setTarget({
59
+ left: parseFloat(element.style.left), top: parseFloat(element.style.top),
60
+ width: parseFloat(element.style.width), height: parseFloat(element.style.height),
61
+ label: element.querySelector('.block-label')?.textContent ?? 'Component',
62
+ });
63
+ setVisible(true);
64
+ }
65
+
66
+ const { left: x, top: y } = target ?? { left: 0, top: 0 };
67
+ return <div className="inspection-layer" onPointerOver={hover} onPointerLeave={hide}
68
+ onPointerOut={event => {
69
+ if (!(event.relatedTarget instanceof Element) || !event.relatedTarget.closest('.component-target')) hide();
70
+ }} onPointerDownCapture={hide} onKeyDownCapture={hide}>
71
+ {children}
72
+ <div className="inspection-hover-frame" aria-hidden="true" data-visible={visible} data-morph={morph}>
73
+ <div ref={box} className="inspection-hover-box" />
74
+ <span className="block-label" style={{ transform: `translate(${x}px, ${y}px)` }}>{target?.label}</span>
75
+ </div>
76
+ </div>;
77
+ }
@@ -0,0 +1,92 @@
1
+ import { useEffect, useRef, useState, type FormEvent } from 'react';
2
+ import type { AssetInspection } from '../shared/assets';
3
+ import { api } from './api';
4
+ import { Button, Dialog, Input } from './ui';
5
+ import { Icon } from './ui/Icon';
6
+
7
+ function UploadPreview({ file }: { file: File }) {
8
+ const [url, setUrl] = useState('');
9
+ useEffect(() => { const next = URL.createObjectURL(file); setUrl(next); return () => URL.revokeObjectURL(next); }, [file]);
10
+ return <span className="logo-upload-preview asset-background-transparent">{url && <img src={url} alt="" />}</span>;
11
+ }
12
+ const accept = '.svg,.png,image/svg+xml,image/png';
13
+
14
+ /** Creation asks only for the folder name and its default artwork. */
15
+ export function CreateLogoDialog({ connected, onClose, onSaved }: { connected: boolean; onClose: () => void; onSaved: (value: AssetInspection) => void }) {
16
+ const [name, setName] = useState('');
17
+ const [step, setStep] = useState<'name' | 'default'>('name');
18
+ const [file, setFile] = useState<File>();
19
+ const [error, setError] = useState('');
20
+ const [busy, setBusy] = useState(false);
21
+ const working = useRef(false);
22
+ const nameInput = useRef<HTMLInputElement>(null);
23
+ const fileInput = useRef<HTMLInputElement>(null);
24
+ useEffect(() => { if (step === 'default') { if (file && fileInput.current) { const selection = new DataTransfer(); selection.items.add(file); fileInput.current.files = selection.files; } fileInput.current?.focus(); } else nameInput.current?.focus(); }, [step, file]);
25
+ async function submit(event: FormEvent) {
26
+ event.preventDefault();
27
+ if (working.current || !connected || !name.trim()) return;
28
+ if (step === 'name') { setStep('default'); return; }
29
+ if (!file) return;
30
+ working.current = true; setBusy(true); setError('');
31
+ try {
32
+ const form = new FormData(); form.set('metadata', JSON.stringify({ name: name.trim() })); form.set('file', file);
33
+ const result = await api<AssetInspection>('/api/assets/logo', { method: 'POST', body: form });
34
+ onSaved(result); onClose();
35
+ } catch (failure) { setError((failure as Error).message); }
36
+ finally { working.current = false; setBusy(false); }
37
+ }
38
+ return <Dialog.Root open onOpenChange={open => { if (!open && !working.current) onClose(); }}><Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" /><Dialog.Popup className="help-dialog create-dialog asset-dialog logo-create-dialog" initialFocus={nameInput}>
39
+ <Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close add logo" disabled={busy} />}><Icon name="close" /></Dialog.Close>
40
+ <Dialog.Title>{step === 'name' ? 'Add logo' : 'Upload default logo'}</Dialog.Title>
41
+ <Dialog.Description>{step === 'name' ? 'Give this logo a name. Its variations will live together inside.' : `${name.trim()} · You can add more variations after this.`}</Dialog.Description>
42
+ <form onSubmit={event => void submit(event)} aria-busy={busy}>
43
+ {step === 'name' ? <label className="create-field"><span>Name</span><Input ref={nameInput} value={name} onChange={event => setName(event.target.value)} maxLength={120} required autoComplete="off" placeholder="For example, Northstar" /></label> : <label className="create-field asset-upload logo-default-upload"><span>Default logo</span>{file && <UploadPreview file={file} />}<input ref={fileInput} type="file" accept={accept} required={!file} disabled={busy} onChange={event => { setFile(event.target.files?.[0]); setError(''); }} /><span className="field-hint">SVG or PNG</span></label>}
44
+ {error && <p className="field-error" role="alert">{error}</p>}
45
+ <div className="dialog-actions"><Button disabled={busy} onClick={() => { if (step === 'name') onClose(); else { setStep('name'); setError(''); } }}>{step === 'name' ? 'Cancel' : 'Back'}</Button><Button type="submit" className="primary" disabled={busy || !connected || !name.trim() || (step === 'default' && !file)}>{busy ? 'Uploading…' : step === 'name' ? 'Next' : 'Create logo'}</Button></div>
46
+ </form>
47
+ </Dialog.Popup></Dialog.Portal></Dialog.Root>;
48
+ }
49
+
50
+ type PendingVariation = { id: number; file: File; name: string };
51
+ export function AddLogoVariationsDialog({ inspection, connected, onClose, onSaved }: { inspection: AssetInspection; connected: boolean; onClose: () => void; onSaved: (value: AssetInspection) => void }) {
52
+ const [pending, setPending] = useState<PendingVariation[]>([]);
53
+ const [error, setError] = useState('');
54
+ const [busy, setBusy] = useState(false);
55
+ const [progress, setProgress] = useState('');
56
+ const current = useRef(inspection);
57
+ const counter = useRef(0);
58
+ const working = useRef(false);
59
+ const input = useRef<HTMLInputElement>(null);
60
+ const count = current.current.asset.kind === 'logo' ? current.current.asset.variations.length : 0;
61
+ async function submit(event: FormEvent) {
62
+ event.preventDefault(); if (!connected || working.current || !pending.length || pending.some(item => !item.name.trim())) return;
63
+ working.current = true; setBusy(true); setError('');
64
+ let saved = 0;
65
+ try {
66
+ for (const item of pending) {
67
+ setProgress(`Uploading ${saved + 1} of ${pending.length}…`);
68
+ const form = new FormData(); form.set('metadata', JSON.stringify({ expectedRevision: current.current.head.revision, name: item.name.trim() })); form.set('file', item.file);
69
+ current.current = await api<AssetInspection>(`/api/assets/logo/${encodeURIComponent(inspection.asset.id)}/variations`, { method: 'POST', body: form });
70
+ saved++; setPending(list => list.filter(value => value.id !== item.id));
71
+ }
72
+ onSaved(current.current); onClose();
73
+ } catch (failure) {
74
+ if (saved) onSaved(current.current);
75
+ setError(`${saved ? `${saved} ${saved === 1 ? 'variation was' : 'variations were'} added. The remaining files are still here. ` : ''}${(failure as Error).message}`);
76
+ } finally { working.current = false; setBusy(false); setProgress(''); }
77
+ }
78
+ return <Dialog.Root open onOpenChange={open => { if (!open && !working.current) onClose(); }}><Dialog.Portal><Dialog.Backdrop className="ui-dialog-backdrop" /><Dialog.Popup className="help-dialog create-dialog asset-dialog" initialFocus={input}>
79
+ <Dialog.Close render={<Button className="icon-button modal-close" aria-label="Close add variations" disabled={busy} />}><Icon name="close" /></Dialog.Close>
80
+ <Dialog.Title>Add variations</Dialog.Title><Dialog.Description>{inspection.asset.name} · Select one or more SVG or PNG files.</Dialog.Description>
81
+ <form onSubmit={event => void submit(event)} aria-busy={busy}>
82
+ <label className="create-field asset-upload"><span>Upload variations</span><input ref={input} type="file" accept={accept} multiple disabled={busy} onChange={event => {
83
+ const files = Array.from(event.target.files ?? []); setError('');
84
+ if (count + pending.length + files.length > 64) { setError('A logo can hold up to 64 variations. Choose fewer files.'); return; }
85
+ setPending(list => [...list, ...files.map(file => ({ id: counter.current++, file, name: file.name.replace(/\.[^.]+$/, '').replace(/[-_]+/g, ' ').slice(0, 120) || 'Variation' }))]); event.target.value = '';
86
+ }} /></label>
87
+ {pending.length > 0 && <div className="logo-upload-list">{pending.map(item => <div className="logo-upload-row" key={item.id}><UploadPreview file={item.file} /><label className="create-field"><span>Variation name</span><Input value={item.name} required maxLength={120} disabled={busy} onChange={event => setPending(list => list.map(value => value.id === item.id ? { ...value, name: event.target.value } : value))} /><small>{item.file.name}</small></label><Button className="icon-button" aria-label={`Remove ${item.file.name} from upload`} disabled={busy} onClick={() => { setPending(list => list.filter(value => value.id !== item.id)); setError(''); }}><Icon name="close" size={16} /></Button></div>)}</div>}
88
+ {error && <p className="field-error" role="alert">{error}</p>}
89
+ <div className="dialog-actions"><Button onClick={onClose} disabled={busy}>Cancel</Button><Button type="submit" className="primary" disabled={busy || !connected || !pending.length || pending.some(item => !item.name.trim())}>{busy ? progress : pending.length ? `Add ${pending.length} ${pending.length === 1 ? 'variation' : 'variations'}` : 'Add variations'}</Button></div>
90
+ </form>
91
+ </Dialog.Popup></Dialog.Portal></Dialog.Root>;
92
+ }
@@ -0,0 +1,34 @@
1
+ import { Children, isValidElement, useEffect, type ReactNode } from 'react';
2
+ import ReactMarkdown from 'react-markdown';
3
+ import remarkGfm from 'remark-gfm';
4
+ import { guideHeadingSlug, resolveGuideLink, type GuideLocation } from './guideLinks';
5
+
6
+ function plainText(children: ReactNode): string {
7
+ return Children.toArray(children).map(child => typeof child === 'string' || typeof child === 'number' ? String(child) : isValidElement<{ children?: ReactNode }>(child) ? plainText(child.props.children) : '').join('');
8
+ }
9
+
10
+ export function Markdown({ children, file, onNavigate, onReady }: { children: string; file: string; onNavigate: (location: GuideLocation) => void; onReady?: () => void }) {
11
+ useEffect(() => { onReady?.(); }, [children, file, onReady]);
12
+ const headings = new Set<string>();
13
+ function heading(level: 1 | 2 | 3 | 4 | 5 | 6, children: ReactNode) {
14
+ const slug = guideHeadingSlug(plainText(children));
15
+ let unique = slug;
16
+ for (let count = 1; headings.has(unique); count += 1) unique = `${slug}-${count}`;
17
+ headings.add(unique);
18
+ const Tag = `h${level}` as const;
19
+ return <Tag id={`guide-${unique}`} data-guide-heading={unique}>{children}</Tag>;
20
+ }
21
+ return <div className="markdown-body">
22
+ <ReactMarkdown remarkPlugins={[remarkGfm]} skipHtml components={{
23
+ a: ({ node: _node, href, ...props }) => {
24
+ const link = href && resolveGuideLink(file, href);
25
+ return !link ? <span title={href}>{props.children}</span> : link.kind === 'external'
26
+ ? <a {...props} href={link.href} target="_blank" rel="noopener noreferrer" />
27
+ : <a {...props} href={`/api/guides?path=${encodeURIComponent(link.location.file)}${link.location.fragment ? `#${encodeURIComponent(link.location.fragment)}` : ''}`} onClick={event => { event.preventDefault(); onNavigate(link.location); }} />;
28
+ },
29
+ h1: ({ children }) => heading(1, children), h2: ({ children }) => heading(2, children), h3: ({ children }) => heading(3, children),
30
+ h4: ({ children }) => heading(4, children), h5: ({ children }) => heading(5, children), h6: ({ children }) => heading(6, children),
31
+ table: ({ node: _node, ...props }) => <div className="markdown-table"><table {...props} /></div>,
32
+ }}>{children}</ReactMarkdown>
33
+ </div>;
34
+ }