@puredesktop/puredesktop-ui-bridge 1.0.0-beta.1

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 (412) hide show
  1. package/package.json +96 -0
  2. package/src/agents/runtime/agentLiveReducer.test.ts +72 -0
  3. package/src/agents/runtime/agentLiveReducer.ts +139 -0
  4. package/src/agents/runtime/agentRunEventTypes.ts +115 -0
  5. package/src/agents/runtime/contextWindowUsage.test.ts +38 -0
  6. package/src/agents/runtime/contextWindowUsage.ts +25 -0
  7. package/src/agents/runtime/index.ts +24 -0
  8. package/src/agents/runtime/mapAgentMessages.ts +54 -0
  9. package/src/agents/runtime/mapAgentSnapshot.ts +71 -0
  10. package/src/agents/runtime/mergeUsageTurn.ts +23 -0
  11. package/src/agents/runtime/pendingToolCalls.test.ts +85 -0
  12. package/src/agents/runtime/pendingToolCalls.ts +14 -0
  13. package/src/agents/runtime/toolRequiresApproval.ts +20 -0
  14. package/src/agents/runtime/unresolvedToolCalls.ts +42 -0
  15. package/src/bridge/agentModelTiers.ts +50 -0
  16. package/src/bridge/agentToolHandlers.ts +23 -0
  17. package/src/bridge/agentToolHelpers.test.ts +27 -0
  18. package/src/bridge/agentToolHelpers.ts +20 -0
  19. package/src/bridge/agentTypes.ts +294 -0
  20. package/src/bridge/agents.d.mts +113 -0
  21. package/src/bridge/agents.mjs +233 -0
  22. package/src/bridge/agents.ts +62 -0
  23. package/src/bridge/appSettings.d.mts +9 -0
  24. package/src/bridge/appSettings.mjs +18 -0
  25. package/src/bridge/appSettings.ts +4 -0
  26. package/src/bridge/applyThemeFromHost.d.mts +7 -0
  27. package/src/bridge/applyThemeFromHost.mjs +33 -0
  28. package/src/bridge/assets.d.mts +66 -0
  29. package/src/bridge/assets.mjs +121 -0
  30. package/src/bridge/catalog.d.mts +5 -0
  31. package/src/bridge/catalog.mjs +24 -0
  32. package/src/bridge/catalog.ts +1 -0
  33. package/src/bridge/client.d.mts +19 -0
  34. package/src/bridge/client.mjs +147 -0
  35. package/src/bridge/collectionAssetAdapter.ts +63 -0
  36. package/src/bridge/collectionAssets.d.mts +1 -0
  37. package/src/bridge/collectionAssets.mjs +2 -0
  38. package/src/bridge/collectionAssets.ts +1 -0
  39. package/src/bridge/collectionDocumentHtml.test.ts +71 -0
  40. package/src/bridge/collectionDocumentHtml.ts +118 -0
  41. package/src/bridge/collectionImagePaste.test.ts +32 -0
  42. package/src/bridge/collectionImagePaste.ts +130 -0
  43. package/src/bridge/context.mjs +12 -0
  44. package/src/bridge/credentials.mjs +22 -0
  45. package/src/bridge/dialog.d.mts +5 -0
  46. package/src/bridge/dialog.mjs +13 -0
  47. package/src/bridge/events.d.mts +14 -0
  48. package/src/bridge/events.mjs +12 -0
  49. package/src/bridge/filePreviewKind.test.ts +43 -0
  50. package/src/bridge/filePreviewKind.ts +76 -0
  51. package/src/bridge/fs.d.mts +41 -0
  52. package/src/bridge/fs.mjs +82 -0
  53. package/src/bridge/fs.test.ts +16 -0
  54. package/src/bridge/fs.ts +10 -0
  55. package/src/bridge/index.ts +12 -0
  56. package/src/bridge/inlinePreview.d.mts +35 -0
  57. package/src/bridge/inlinePreview.mjs +99 -0
  58. package/src/bridge/inlinePreview.test.ts +56 -0
  59. package/src/bridge/inlinePreview.ts +13 -0
  60. package/src/bridge/methods.d.mts +115 -0
  61. package/src/bridge/methods.mjs +132 -0
  62. package/src/bridge/os.d.mts +11 -0
  63. package/src/bridge/os.mjs +26 -0
  64. package/src/bridge/plugins.d.mts +9 -0
  65. package/src/bridge/plugins.mjs +31 -0
  66. package/src/bridge/plugins.ts +5 -0
  67. package/src/bridge/preferences.d.mts +7 -0
  68. package/src/bridge/preferences.mjs +17 -0
  69. package/src/bridge/preferences.ts +13 -0
  70. package/src/bridge/printDocument.test.ts +47 -0
  71. package/src/bridge/printDocument.ts +75 -0
  72. package/src/bridge/pureRender/adapters.test.ts +144 -0
  73. package/src/bridge/pureRender/adapters.ts +175 -0
  74. package/src/bridge/pureRender/assets.test.ts +44 -0
  75. package/src/bridge/pureRender/base.css +76 -0
  76. package/src/bridge/pureRender/compiler.test.ts +279 -0
  77. package/src/bridge/pureRender/compiler.ts +660 -0
  78. package/src/bridge/pureRender/document.test.ts +283 -0
  79. package/src/bridge/pureRender/document.ts +35 -0
  80. package/src/bridge/pureRender/extraction.test.ts +432 -0
  81. package/src/bridge/pureRender/extraction.ts +1023 -0
  82. package/src/bridge/pureRender/flow.css +3 -0
  83. package/src/bridge/pureRender/index.ts +66 -0
  84. package/src/bridge/pureRender/normalize.test.ts +442 -0
  85. package/src/bridge/pureRender/normalize.ts +682 -0
  86. package/src/bridge/pureRender/presets.ts +365 -0
  87. package/src/bridge/pureRender/profiles/book.css +19 -0
  88. package/src/bridge/pureRender/profiles/manuscript.css +11 -0
  89. package/src/bridge/pureRender/profiles/writer.css +10 -0
  90. package/src/bridge/pureRender/reset.css +5 -0
  91. package/src/bridge/pureRender/samples.ts +94 -0
  92. package/src/bridge/pureRender/styles.ts +164 -0
  93. package/src/bridge/pureRender/theme.test.ts +53 -0
  94. package/src/bridge/pureRender/theme.ts +258 -0
  95. package/src/bridge/pureRender/tokens/default.css +36 -0
  96. package/src/bridge/pureRender/tokens/default.json +32 -0
  97. package/src/bridge/pureRender/types.ts +264 -0
  98. package/src/bridge/pureRender/visualLayoutInspection.test.ts +57 -0
  99. package/src/bridge/pureRender/visualLayoutInspection.ts +161 -0
  100. package/src/bridge/react/useDebouncedEffect.ts +21 -0
  101. package/src/bridge/react/useIpc.tsx +75 -0
  102. package/src/bridge/react/usePlatformAgentRunLiveState.tsx +78 -0
  103. package/src/bridge/react/usePlatformAgentSessionDrive.tsx +729 -0
  104. package/src/bridge/react/usePlatformAgentSessions.tsx +60 -0
  105. package/src/bridge/react/usePlatformAgentTools.tsx +82 -0
  106. package/src/bridge/react/usePlatformAppManifest.tsx +32 -0
  107. package/src/bridge/react/usePlatformBridge.tsx +56 -0
  108. package/src/bridge/react/usePlatformDeepLink.tsx +57 -0
  109. package/src/bridge/react/usePlatformFileBinaryDataUrl.tsx +54 -0
  110. package/src/bridge/react/usePlatformFilePreview.tsx +92 -0
  111. package/src/bridge/react/usePlatformPreferences.tsx +77 -0
  112. package/src/bridge/react/usePlatformThemeDocumentAttrs.ts +35 -0
  113. package/src/bridge/react/usePlatformThemeFromBridge.tsx +16 -0
  114. package/src/bridge/react/usePlatformViewportResource.test.tsx +106 -0
  115. package/src/bridge/react/usePlatformViewportResource.tsx +72 -0
  116. package/src/bridge/react/useScheduledTask.tsx +59 -0
  117. package/src/bridge/render.d.mts +31 -0
  118. package/src/bridge/render.mjs +62 -0
  119. package/src/bridge/render.ts +8 -0
  120. package/src/bridge/screenAnnotation.test.ts +103 -0
  121. package/src/bridge/screenAnnotation.ts +269 -0
  122. package/src/bridge/theme.d.mts +3 -0
  123. package/src/bridge/theme.mjs +9 -0
  124. package/src/bridge/types.ts +439 -0
  125. package/src/bridge/watch.d.mts +9 -0
  126. package/src/bridge/watch.mjs +34 -0
  127. package/src/bridge/watch.ts +1 -0
  128. package/src/bridge/window.d.mts +44 -0
  129. package/src/bridge/window.mjs +33 -0
  130. package/src/chrome/index.ts +1 -0
  131. package/src/components/accessibility/AccessibilityReviewDialog.test.tsx +104 -0
  132. package/src/components/accessibility/AccessibilityReviewDialog.tsx +350 -0
  133. package/src/components/accessibility/accessibilityReview.test.ts +101 -0
  134. package/src/components/accessibility/accessibilityReview.ts +639 -0
  135. package/src/components/accessibility/index.ts +16 -0
  136. package/src/components/agent-feedback/ScreenAnnotationAgentDialog.tsx +509 -0
  137. package/src/components/agent-feedback/index.ts +5 -0
  138. package/src/components/agents/AgentComposer.tsx +34 -0
  139. package/src/components/agents/AgentContextUsageBar.tsx +127 -0
  140. package/src/components/agents/AgentDrawerPanel.tsx +82 -0
  141. package/src/components/agents/AgentMarkdown.tsx +12 -0
  142. package/src/components/agents/AgentMessageBubble.tsx +82 -0
  143. package/src/components/agents/AgentMessageList.tsx +47 -0
  144. package/src/components/agents/AgentModelMenu.tsx +55 -0
  145. package/src/components/agents/AgentThinkingBlock.tsx +66 -0
  146. package/src/components/agents/AgentToolApprovalActions.tsx +65 -0
  147. package/src/components/agents/AgentToolCallCard.tsx +26 -0
  148. package/src/components/agents/AgentToolPendingList.tsx +39 -0
  149. package/src/components/agents/AgentToolPendingReview.tsx +118 -0
  150. package/src/components/agents/agentPanelStyles.ts +79 -0
  151. package/src/components/agents/agentTypes.ts +96 -0
  152. package/src/components/agents/formatToolCallArguments.test.ts +16 -0
  153. package/src/components/agents/formatToolCallArguments.ts +9 -0
  154. package/src/components/agents/index.ts +40 -0
  155. package/src/components/assets/AssetLibraryDialog.tsx +5 -0
  156. package/src/components/assets/asset-library/AssetLibraryDialog.tsx +43 -0
  157. package/src/components/assets/asset-library/AssetLibraryWorkspace.tsx +54 -0
  158. package/src/components/assets/asset-library/detail/AssetLibraryDetailActions.tsx +68 -0
  159. package/src/components/assets/asset-library/detail/AssetLibraryDetailHeader.tsx +56 -0
  160. package/src/components/assets/asset-library/detail/AssetLibraryDetailPane.tsx +90 -0
  161. package/src/components/assets/asset-library/detail/AssetLibraryDetailPreview.tsx +67 -0
  162. package/src/components/assets/asset-library/detail/AssetLibraryInlinePreview.tsx +87 -0
  163. package/src/components/assets/asset-library/detail/AssetLibraryInlineSourceField.tsx +29 -0
  164. package/src/components/assets/asset-library/detail/AssetLibraryMetadataFields.tsx +34 -0
  165. package/src/components/assets/asset-library/detail/AssetLibraryUsagePanel.tsx +46 -0
  166. package/src/components/assets/asset-library/detail/AssetLibraryVersionsPanel.tsx +145 -0
  167. package/src/components/assets/asset-library/helpers/assetKind.ts +74 -0
  168. package/src/components/assets/asset-library/helpers.ts +41 -0
  169. package/src/components/assets/asset-library/hooks/useAssetDetailDraft.ts +81 -0
  170. package/src/components/assets/asset-library/hooks/useAssetLibraryDialog.ts +233 -0
  171. package/src/components/assets/asset-library/hooks/useAssetLibraryMutations.ts +266 -0
  172. package/src/components/assets/asset-library/hooks/useAssetLibrarySelectionSync.ts +24 -0
  173. package/src/components/assets/asset-library/hooks/useAssetLibraryVersions.ts +145 -0
  174. package/src/components/assets/asset-library/hooks/useInlineAssetPreview.ts +44 -0
  175. package/src/components/assets/asset-library/index.ts +19 -0
  176. package/src/components/assets/asset-library/layout/assetLibraryLayout.ts +201 -0
  177. package/src/components/assets/asset-library/shared/fieldStyles.ts +62 -0
  178. package/src/components/assets/asset-library/sidebar/AssetLibraryFilterPills.tsx +89 -0
  179. package/src/components/assets/asset-library/sidebar/AssetLibraryList.tsx +91 -0
  180. package/src/components/assets/asset-library/sidebar/AssetLibraryListRow.tsx +99 -0
  181. package/src/components/assets/asset-library/sidebar/AssetLibraryListStatus.tsx +24 -0
  182. package/src/components/assets/asset-library/sidebar/AssetLibrarySidebar.tsx +48 -0
  183. package/src/components/assets/asset-library/sidebar/AssetLibrarySidebarActions.tsx +38 -0
  184. package/src/components/assets/asset-library/sidebar/AssetLibrarySidebarToolbar.tsx +78 -0
  185. package/src/components/assets/asset-library/sidebar/AssetListThumbnail.tsx +86 -0
  186. package/src/components/assets/asset-library/types.ts +128 -0
  187. package/src/components/assets/asset-library/utils/assetLibraryUtils.ts +181 -0
  188. package/src/components/assets/asset-library/utils/pathBasename.ts +5 -0
  189. package/src/components/assets/asset-library/utils/sanitizeUploadFileName.ts +7 -0
  190. package/src/components/assets/asset-library/utils/uploadCollectionAsset.ts +52 -0
  191. package/src/components/chrome/OverlayActionCapsule.test.tsx +82 -0
  192. package/src/components/chrome/OverlayActionCapsule.tsx +90 -0
  193. package/src/components/chrome/PanelTabStrip.tsx +113 -0
  194. package/src/components/chrome/PlatformIcon.tsx +27 -0
  195. package/src/components/chrome/PureAppWordmark.tsx +89 -0
  196. package/src/components/chrome/ShellWindowControls.tsx +51 -0
  197. package/src/components/chrome/ThemeModeButton.tsx +16 -0
  198. package/src/components/chrome/Titlebar.tsx +79 -0
  199. package/src/components/chrome/WorkspaceTabStrip.tsx +300 -0
  200. package/src/components/chrome/index.ts +31 -0
  201. package/src/components/chrome/workspaceTabTypes.ts +29 -0
  202. package/src/components/common/buttons/Button.tsx +241 -0
  203. package/src/components/common/buttons/ChoiceChip.tsx +73 -0
  204. package/src/components/common/buttons/CleanButton.tsx +34 -0
  205. package/src/components/common/buttons/IconButton.tsx +89 -0
  206. package/src/components/common/buttons/QuietAction.tsx +29 -0
  207. package/src/components/common/buttons/SegmentedControl.tsx +67 -0
  208. package/src/components/common/buttons/TextActionButton.tsx +55 -0
  209. package/src/components/common/buttons/TextLink.tsx +38 -0
  210. package/src/components/common/canvas/CanvasElementFrame.test.tsx +79 -0
  211. package/src/components/common/canvas/CanvasElementFrame.tsx +182 -0
  212. package/src/components/common/canvas/canvasGeometry.test.ts +66 -0
  213. package/src/components/common/canvas/canvasGeometry.ts +133 -0
  214. package/src/components/common/canvas/index.ts +27 -0
  215. package/src/components/common/canvas/useCanvasDragResize.test.tsx +152 -0
  216. package/src/components/common/canvas/useCanvasDragResize.ts +161 -0
  217. package/src/components/common/chat/ChatAssistantMessage.tsx +81 -0
  218. package/src/components/common/chat/ChatBox.tsx +189 -0
  219. package/src/components/common/chat/ChatMarkdown.tsx +109 -0
  220. package/src/components/common/chat/ChatModelMenu.tsx +258 -0
  221. package/src/components/common/chat/ChatStatusRow.tsx +24 -0
  222. package/src/components/common/chat/ChatThinkingIndicator.tsx +54 -0
  223. package/src/components/common/chat/ChatThread.tsx +59 -0
  224. package/src/components/common/chat/ChatToolRow.tsx +141 -0
  225. package/src/components/common/chat/ChatUserBubble.tsx +45 -0
  226. package/src/components/common/chat/chatUtils.ts +7 -0
  227. package/src/components/common/chat/index.ts +24 -0
  228. package/src/components/common/connections/ConnectionSetup.tsx +529 -0
  229. package/src/components/common/connections/index.ts +26 -0
  230. package/src/components/common/containers/AppFrame.test.tsx +47 -0
  231. package/src/components/common/containers/AppFrame.tsx +65 -0
  232. package/src/components/common/containers/AppHeader.tsx +60 -0
  233. package/src/components/common/containers/AutoFillGrid.tsx +37 -0
  234. package/src/components/common/containers/Collapsible.tsx +43 -0
  235. package/src/components/common/containers/CollapsibleGrid.tsx +174 -0
  236. package/src/components/common/containers/MotionScope.tsx +32 -0
  237. package/src/components/common/containers/PageHeader.tsx +35 -0
  238. package/src/components/common/containers/PanelToolbarHeader.tsx +113 -0
  239. package/src/components/common/containers/QuietDisclosure.tsx +91 -0
  240. package/src/components/common/containers/QuietSurface.tsx +41 -0
  241. package/src/components/common/containers/ScrollPanel.tsx +33 -0
  242. package/src/components/common/containers/StatusBar.tsx +42 -0
  243. package/src/components/common/containers/SurfacePanel.tsx +66 -0
  244. package/src/components/common/containers/ToolbarStrip.tsx +80 -0
  245. package/src/components/common/containers/flex.ts +117 -0
  246. package/src/components/common/containers/useOpenTransition.ts +33 -0
  247. package/src/components/common/context/ContextSelector.tsx +269 -0
  248. package/src/components/common/context/index.ts +7 -0
  249. package/src/components/common/desk/ActionReader.tsx +270 -0
  250. package/src/components/common/desk/ActivityLedger.tsx +143 -0
  251. package/src/components/common/desk/CreationDesk.tsx +139 -0
  252. package/src/components/common/desk/DailyBrief.tsx +160 -0
  253. package/src/components/common/desk/DeskCard.tsx +157 -0
  254. package/src/components/common/desk/index.ts +30 -0
  255. package/src/components/common/dropdown/ContextMenu.tsx +87 -0
  256. package/src/components/common/dropdown/MenuButtonDropdown.tsx +121 -0
  257. package/src/components/common/dropdown/MenuDropdown.tsx +63 -0
  258. package/src/components/common/dropdown/MenuDropdownEntry.tsx +51 -0
  259. package/src/components/common/dropdown/MenuDropdownItem.tsx +82 -0
  260. package/src/components/common/dropdown/MenuDropdownSubmenu.tsx +145 -0
  261. package/src/components/common/dropdown/MenuPanel.tsx +69 -0
  262. package/src/components/common/dropdown/MenuPortal.tsx +162 -0
  263. package/src/components/common/dropdown/MenuPortalLayer.tsx +6 -0
  264. package/src/components/common/dropdown/SelectMenu.tsx +243 -0
  265. package/src/components/common/dropdown/SelectMenuControl.tsx +23 -0
  266. package/src/components/common/dropdown/SelectMenuOptions.tsx +38 -0
  267. package/src/components/common/dropdown/menuPortalConstants.ts +11 -0
  268. package/src/components/common/dropdown/menuTypes.ts +71 -0
  269. package/src/components/common/dropdown/menuViewportPosition.test.ts +139 -0
  270. package/src/components/common/dropdown/menuViewportPosition.ts +122 -0
  271. package/src/components/common/dropdown/selectMenuLayout.test.ts +42 -0
  272. package/src/components/common/dropdown/selectMenuLayout.ts +123 -0
  273. package/src/components/common/dropdown/useMenuAnchorPosition.ts +109 -0
  274. package/src/components/common/dropdown/useSelectMenuAnchor.ts +30 -0
  275. package/src/components/common/evidence/StructuredEvidenceView.test.tsx +77 -0
  276. package/src/components/common/evidence/StructuredEvidenceView.tsx +312 -0
  277. package/src/components/common/evidence/index.ts +13 -0
  278. package/src/components/common/feedback/Badge.tsx +33 -0
  279. package/src/components/common/feedback/CapabilityPills.tsx +36 -0
  280. package/src/components/common/feedback/EmptyState.tsx +132 -0
  281. package/src/components/common/feedback/InfoHint.tsx +67 -0
  282. package/src/components/common/feedback/InlineBanner.tsx +82 -0
  283. package/src/components/common/feedback/PlatformChip.tsx +104 -0
  284. package/src/components/common/feedback/QuietPill.tsx +75 -0
  285. package/src/components/common/feedback/WorkProgressPanel.tsx +156 -0
  286. package/src/components/common/feedback/index.ts +33 -0
  287. package/src/components/common/inputs/BareInput.tsx +31 -0
  288. package/src/components/common/inputs/BareTextarea.tsx +10 -0
  289. package/src/components/common/inputs/Checkbox.tsx +13 -0
  290. package/src/components/common/inputs/FieldControlShell.tsx +49 -0
  291. package/src/components/common/inputs/FormField.tsx +40 -0
  292. package/src/components/common/inputs/HybridSearchField.tsx +280 -0
  293. package/src/components/common/inputs/InlineComposer.tsx +197 -0
  294. package/src/components/common/inputs/PaletteCirclePicker.tsx +26 -0
  295. package/src/components/common/inputs/PaletteDropdown.tsx +413 -0
  296. package/src/components/common/inputs/Radio.tsx +19 -0
  297. package/src/components/common/inputs/SearchField.tsx +128 -0
  298. package/src/components/common/inputs/SelectField.tsx +169 -0
  299. package/src/components/common/inputs/SurfacePalettePicker.tsx +54 -0
  300. package/src/components/common/inputs/TextAreaField.tsx +22 -0
  301. package/src/components/common/inputs/TextField.tsx +51 -0
  302. package/src/components/common/inputs/ToggleSwitch.tsx +110 -0
  303. package/src/components/common/inputs/WorkspacePicker.tsx +349 -0
  304. package/src/components/common/inputs/fieldStyles.ts +137 -0
  305. package/src/components/common/layout/PanelGroup.tsx +479 -0
  306. package/src/components/common/layout/index.ts +31 -0
  307. package/src/components/common/layout/panelLayoutConstants.ts +7 -0
  308. package/src/components/common/layout/panelLayoutPersistence.ts +35 -0
  309. package/src/components/common/layout/panelLayoutState.test.ts +38 -0
  310. package/src/components/common/layout/panelLayoutState.ts +174 -0
  311. package/src/components/common/layout/panelLayoutTypes.ts +19 -0
  312. package/src/components/common/layout/tabs/Tabs.tsx +136 -0
  313. package/src/components/common/lists/listRowStyles.ts +32 -0
  314. package/src/components/common/lists/quiet-ledger/QuietActivityList.tsx +64 -0
  315. package/src/components/common/lists/quiet-ledger/QuietLedgerRow.tsx +119 -0
  316. package/src/components/common/lists/quiet-ledger/index.ts +9 -0
  317. package/src/components/common/lists/reorderable/DragHandle.tsx +65 -0
  318. package/src/components/common/lists/reorderable/ReorderableList.tsx +280 -0
  319. package/src/components/common/lists/reorderable/ReorderableListRow.tsx +102 -0
  320. package/src/components/common/lists/reorderable/ReorderableSections.tsx +344 -0
  321. package/src/components/common/lists/reorderable/ReorderableSectionsDemo.tsx +94 -0
  322. package/src/components/common/lists/reorderable/index.ts +15 -0
  323. package/src/components/common/lists/reorderable/reorderableUtils.test.ts +28 -0
  324. package/src/components/common/lists/reorderable/reorderableUtils.ts +23 -0
  325. package/src/components/common/lists/reorderable/types.ts +40 -0
  326. package/src/components/common/lists/reorderable/useReorderableDragState.ts +49 -0
  327. package/src/components/common/media/ImageReferencePicker.tsx +242 -0
  328. package/src/components/common/menubar/MenuBar.tsx +78 -0
  329. package/src/components/common/menubar/MenuBarTrigger.tsx +70 -0
  330. package/src/components/common/narrative/NarrativeMention.tsx +74 -0
  331. package/src/components/common/navigation/PathBreadcrumbs.tsx +78 -0
  332. package/src/components/common/navigation/UnderlineTabs.tsx +83 -0
  333. package/src/components/common/overlays/InstallAppPromptDialog.tsx +59 -0
  334. package/src/components/common/overlays/Modal.tsx +285 -0
  335. package/src/components/common/overlays/ModalChromeHeader.tsx +105 -0
  336. package/src/components/common/overlays/ObjectActionStrip.tsx +80 -0
  337. package/src/components/common/people/AutoUpdateReviewList.tsx +182 -0
  338. package/src/components/common/people/EntityList.tsx +122 -0
  339. package/src/components/common/people/EntityPortrait.tsx +64 -0
  340. package/src/components/common/people/LinkedDossierList.tsx +99 -0
  341. package/src/components/common/people/OpportunityList.tsx +94 -0
  342. package/src/components/common/people/PeopleSourceList.tsx +145 -0
  343. package/src/components/common/people/RelationshipDossier.tsx +168 -0
  344. package/src/components/common/people/RelationshipMap.tsx +362 -0
  345. package/src/components/common/people/index.ts +40 -0
  346. package/src/components/common/preview/FileAudioPreview.tsx +89 -0
  347. package/src/components/common/preview/FileImagePreview.tsx +88 -0
  348. package/src/components/common/preview/FilePdfPreview.tsx +87 -0
  349. package/src/components/common/preview/FileTextPreview.tsx +96 -0
  350. package/src/components/common/preview/FileVideoPreview.tsx +87 -0
  351. package/src/components/common/preview/PlatformFilePreview.tsx +166 -0
  352. package/src/components/common/preview/previewLayout.ts +27 -0
  353. package/src/components/common/render/Each.tsx +42 -0
  354. package/src/components/common/research/EvidenceDossier.tsx +894 -0
  355. package/src/components/common/research/index.ts +7 -0
  356. package/src/components/common/tiles/CatalogCard.tsx +116 -0
  357. package/src/components/common/tiles/Tile.tsx +45 -0
  358. package/src/components/common/tiles/TileGrid.tsx +13 -0
  359. package/src/components/common/typography/Heading.tsx +27 -0
  360. package/src/components/common/typography/Kicker.tsx +30 -0
  361. package/src/components/common/typography/Text.tsx +80 -0
  362. package/src/components/credentials/CredentialsPanel.tsx +169 -0
  363. package/src/components/credentials/credentialsTypes.ts +14 -0
  364. package/src/components/data/DataChart.tsx +913 -0
  365. package/src/components/data/dataTable.ts +99 -0
  366. package/src/components/editor/EditorCommentPromptDialog.tsx +124 -0
  367. package/src/components/editor/EditorLinkPromptDialog.tsx +65 -0
  368. package/src/components/print-design/PrintDesignPanel.test.tsx +1446 -0
  369. package/src/components/print-design/PrintDesignPanel.tsx +3043 -0
  370. package/src/components/print-design/index.ts +9 -0
  371. package/src/components/proofing/PagedProofDialog.tsx +453 -0
  372. package/src/components/proofing/index.ts +4 -0
  373. package/src/editor/DocumentEditor.tsx +28 -0
  374. package/src/editor/EditorViewModeToggle.tsx +35 -0
  375. package/src/editor/index.ts +97 -0
  376. package/src/editor/toolbarOverlays.ts +8 -0
  377. package/src/index.ts +209 -0
  378. package/src/theme/ThemeProvider.tsx +113 -0
  379. package/src/theme/appAccents.ts +87 -0
  380. package/src/theme/buildThemeBridgePayload.ts +23 -0
  381. package/src/theme/composePlatformTheme.test.ts +30 -0
  382. package/src/theme/composePlatformTheme.ts +23 -0
  383. package/src/theme/platformFont.d.mts +7 -0
  384. package/src/theme/platformFont.mjs +20 -0
  385. package/src/theme/platformFont.ts +6 -0
  386. package/src/theme/reportsShellChromeSelectors.ts +136 -0
  387. package/src/theme/styled.d.ts +5 -0
  388. package/src/theme/surfacePalettes.ts +178 -0
  389. package/src/theme/surfaceRoleSelectors.ts +113 -0
  390. package/src/theme/themeVars.ts +13 -0
  391. package/src/theme/themes/dark.ts +59 -0
  392. package/src/theme/themes/light.ts +60 -0
  393. package/src/theme/themes/palette.ts +44 -0
  394. package/src/theme/themes/shared.ts +68 -0
  395. package/src/theme/tokens.ts +114 -0
  396. package/src/theme/utils/GlobalStyles.ts +88 -0
  397. package/src/theme/utils/appFrameDocumentStyles.d.mts +5 -0
  398. package/src/theme/utils/appFrameDocumentStyles.mjs +89 -0
  399. package/src/theme/utils/appFrameDocumentStyles.ts +5 -0
  400. package/src/theme/utils/applyTheme.ts +50 -0
  401. package/src/theme/utils/cssVariables.ts +49 -0
  402. package/src/theme/utils/useInjectThemeIntoIframe.ts +40 -0
  403. package/src/utils/extensionIconMap.ts +123 -0
  404. package/src/utils/generalUtils.ts +157 -0
  405. package/src/writing/DraftAssistantDialog.tsx +279 -0
  406. package/src/writing/WritingStyleManager.tsx +416 -0
  407. package/src/writing/condenseWritingStyle.ts +103 -0
  408. package/src/writing/draftTextWithAgent.ts +134 -0
  409. package/src/writing/index.ts +39 -0
  410. package/src/writing/styleStore.ts +32 -0
  411. package/src/writing/writingStyle.test.ts +122 -0
  412. package/src/writing/writingStyle.ts +217 -0
@@ -0,0 +1,1023 @@
1
+ import type {
2
+ PrintStylePaletteObservation,
3
+ PrintStyleProfile,
4
+ PrintStyleRule,
5
+ PrintStyleTypeObservation,
6
+ } from './types.js'
7
+
8
+ export const PRINT_TOKEN_EXTRACTION_PROMPT = `PRINT-TOKEN EXTRACTION PROMPT - v1.0
9
+ =========================================================================
10
+ Sent to a vision model alongside one or more reference images (a journal
11
+ page, a book spread, a typeset PDF, a cover). Produces a STYLE PROFILE that
12
+ the compiler turns into a print theme.json for the Pure Render system.
13
+
14
+ Pair this prompt with: print-style-profile.schema.json (the output shape).
15
+ =========================================================================
16
+
17
+ ROLE
18
+ You are a print design-system extractor. Given one or more reference images of
19
+ TYPESET PAGES, reverse-engineer a reusable visual system that could set NEW text
20
+ in the same family. You are not describing the image; you are inferring the rules
21
+ a typesetter followed.
22
+
23
+ DIVISION OF LABOR (critical)
24
+ - You judge classification, relationships, mood, and rules - what you are good at.
25
+ - You do NOT report exact colors or measurements as truth. A separate pixel pass
26
+ refines every numeric value. Give approx values as HINTS only.
27
+ - You NEVER name or identify a typeface. Report ATTRIBUTES that let a substitute be
28
+ chosen from a licensed library.
29
+ - Print-specific: estimate things in RELATIVE terms (ratios, proportions of the
30
+ page, x-heights relative to cap height) rather than absolute points, because you
31
+ cannot measure the trim size from pixels.
32
+
33
+ PER IMAGE, record OBSERVATIONS (what is literally present):
34
+
35
+ 1. palette - print is restrained. By ROLE, not swatch:
36
+ - surface (paper/background), ink (body text), accent (rules/links/numbers - note
37
+ how sparingly used), and any secondary (captions, folios).
38
+ - For each: role, approx_hex (HINT), where_seen, coverage (dominant|frequent|sparse).
39
+
40
+ 2. type - by ATTRIBUTE not identity, per ROLE you can see (display/headings, body,
41
+ caption/folio, mono/code):
42
+ - class (serif|sans|slab|script), weight, width, contrast/stress (thick-thin),
43
+ x_height (low|medium|high), case_usage, and the SERIF DETAIL if serif
44
+ (bracketed|slab|hairline|wedge) - this most determines the substitute.
45
+ - pairing: heading/body relationship, and size_contrast (dramatic|moderate|subtle)
46
+ - implies the scale ratio.
47
+ - type_scale: relative or estimated scale for title, chapter title, section
48
+ heading, body, caption, and footnote. Keep absolute point sizes low-confidence
49
+ unless the source provides a known trim/scale.
50
+
51
+ 3. text setting - the heart of print. Estimate RELATIVELY:
52
+ - alignment (justified|flush-left), hyphenation (present|absent),
53
+ - measure: approx characters per line (count a representative full line),
54
+ - leading_feel (tight|normal|airy) - ratio of line gap to text size,
55
+ - paragraphing (indented runs | space-between | both),
56
+ - density (airy|balanced|dense).
57
+
58
+ 4. page model - what you can infer about the page:
59
+ - columns (1|2|n),
60
+ - margin_generosity (tight|moderate|generous) and which margins are larger
61
+ (inner/outer/top/bottom asymmetry -> suggests recto/verso canon),
62
+ - running heads (present|absent, position), folio (position),
63
+ - figure/table treatment (captions above|below, rule style, numbering visible).
64
+
65
+ 5. page furniture and ornaments - manuscript/book references often depend on
66
+ details outside body text. Capture these as structured observations:
67
+ - running_heads: presence, position, transform, rule/no rule, font_role.
68
+ - folio: position, style, suppress_on first/opener pages if visible.
69
+ - opener: chapter/opening spacing, recto_start, ornament, drop_cap.
70
+ - ornaments: none|rule|divider|fleuron|chapter-mark, weight, placement.
71
+ - table_treatment: rule_weight, header_treatment, row_rules, density.
72
+ - caption_treatment: position, label_style, alignment.
73
+
74
+ 6. finish & mood - register (scholarly|literary|editorial|technical|...),
75
+ temperature, era. Subjective; only you can supply it.
76
+
77
+ THEN INFER THE RULES (the most important output)
78
+ List the constraints the setting obeys - what must stay true for a NEW page to
79
+ belong to this family.
80
+
81
+ REQUIREMENTS
82
+ - Cite evidence for every field (where in the image you saw it).
83
+ - Give confidence 0..1 per field. When unsure, LOWER confidence - do NOT guess to
84
+ fill a slot. A low-confidence field is dropped for a safe default; a confident
85
+ wrong guess corrupts the system.
86
+ - Output OBSERVATIONS and RULES only. Do NOT output final tokens or a theme - the
87
+ compiler derives those. Leave "derived": null.
88
+ - Flag bad exemplars: if a source is a poor reference, set exemplar_quality.usable=false
89
+ with a reason, so it is not faithfully reproduced.
90
+ - Multiple images: observations PER source id. Do NOT merge or average; the compiler
91
+ reconciles.
92
+
93
+ OUTPUT
94
+ Strict JSON conforming to print-style-profile.schema.json. No prose, no markdown,
95
+ no commentary outside the JSON.`
96
+
97
+ export const PRINT_STYLE_PROFILE_SCHEMA = {
98
+ type: 'object',
99
+ required: [
100
+ 'schema_version',
101
+ 'sources',
102
+ 'exemplar_quality',
103
+ 'observations',
104
+ 'rules',
105
+ 'derived',
106
+ ],
107
+ properties: {
108
+ schema_version: { const: '1.0' },
109
+ sources: {
110
+ type: 'array',
111
+ items: {
112
+ type: 'object',
113
+ required: ['id'],
114
+ properties: { id: { type: 'string' }, note: { type: 'string' } },
115
+ },
116
+ },
117
+ exemplar_quality: {
118
+ type: 'object',
119
+ required: ['usable', 'reason', 'confidence'],
120
+ properties: {
121
+ usable: { type: 'boolean' },
122
+ reason: { type: 'string' },
123
+ confidence: { type: 'number', minimum: 0, maximum: 1 },
124
+ },
125
+ },
126
+ observations: {
127
+ type: 'object',
128
+ properties: {
129
+ palette: {
130
+ type: 'array',
131
+ items: {
132
+ type: 'object',
133
+ required: ['role', 'confidence'],
134
+ properties: {
135
+ role: { enum: ['surface', 'ink', 'accent', 'secondary'] },
136
+ approx_hex: {
137
+ type: 'string',
138
+ description: 'HINT only; refined by pixel pass',
139
+ },
140
+ coverage: { enum: ['dominant', 'frequent', 'sparse'] },
141
+ where_seen: { type: 'string' },
142
+ confidence: { type: 'number' },
143
+ },
144
+ },
145
+ },
146
+ type: {
147
+ type: 'array',
148
+ items: {
149
+ type: 'object',
150
+ required: ['role', 'class', 'confidence'],
151
+ properties: {
152
+ role: { enum: ['display', 'body', 'caption', 'mono'] },
153
+ class: { enum: ['serif', 'sans', 'slab', 'script'] },
154
+ mood: {
155
+ enum: [
156
+ 'old-style',
157
+ 'transitional',
158
+ 'slab',
159
+ 'humanist-sans',
160
+ 'grotesque-sans',
161
+ 'geometric-sans',
162
+ 'mono',
163
+ ],
164
+ },
165
+ serif_detail: {
166
+ enum: ['bracketed', 'slab', 'hairline', 'wedge', 'none'],
167
+ },
168
+ weight: { type: 'string' },
169
+ width: { type: 'string' },
170
+ stress: { enum: ['high-contrast', 'moderate', 'low-contrast'] },
171
+ x_height: { enum: ['low', 'medium', 'high'] },
172
+ case_usage: { type: 'string' },
173
+ where_seen: { type: 'string' },
174
+ confidence: { type: 'number' },
175
+ },
176
+ },
177
+ },
178
+ type_pairing: {
179
+ type: 'object',
180
+ properties: {
181
+ size_contrast: { enum: ['dramatic', 'moderate', 'subtle'] },
182
+ note: { type: 'string' },
183
+ confidence: { type: 'number' },
184
+ },
185
+ },
186
+ type_scale: {
187
+ type: 'object',
188
+ properties: {
189
+ body_pt: { type: 'number' },
190
+ title_scale: { type: 'number' },
191
+ chapter_title_scale: { type: 'number' },
192
+ section_scale: { type: 'number' },
193
+ caption_scale: { type: 'number' },
194
+ footnote_scale: { type: 'number' },
195
+ confidence: { type: 'number' },
196
+ },
197
+ },
198
+ text_setting: {
199
+ type: 'object',
200
+ properties: {
201
+ alignment: { enum: ['justified', 'flush-left'] },
202
+ hyphenation: { enum: ['present', 'absent'] },
203
+ measure_cpl: { type: 'number' },
204
+ leading_feel: { enum: ['tight', 'normal', 'airy'] },
205
+ paragraphing: { enum: ['indented', 'spaced', 'both'] },
206
+ density: { enum: ['airy', 'balanced', 'dense'] },
207
+ confidence: { type: 'number' },
208
+ },
209
+ },
210
+ page_model: {
211
+ type: 'object',
212
+ properties: {
213
+ columns: { type: 'integer' },
214
+ margin_generosity: { enum: ['tight', 'moderate', 'generous'] },
215
+ margin_asymmetry: { type: 'string' },
216
+ running_heads: { enum: ['present', 'absent'] },
217
+ folio_position: { type: 'string' },
218
+ caption_position: { enum: ['above', 'below'] },
219
+ confidence: { type: 'number' },
220
+ },
221
+ },
222
+ running_heads: {
223
+ type: 'object',
224
+ properties: {
225
+ presence: { enum: ['present', 'absent'] },
226
+ position: { type: 'string' },
227
+ transform: {
228
+ enum: ['uppercase', 'small-caps', 'title-case', 'none'],
229
+ },
230
+ rule: { enum: ['none', 'hairline', 'solid'] },
231
+ font_role: {
232
+ enum: ['display', 'body', 'caption', 'sans'],
233
+ },
234
+ evidence: { type: 'string' },
235
+ confidence: { type: 'number' },
236
+ },
237
+ },
238
+ folio: {
239
+ type: 'object',
240
+ properties: {
241
+ position: { type: 'string' },
242
+ style: { enum: ['plain', 'small-caps', 'bracketed'] },
243
+ suppress_on: {
244
+ type: 'array',
245
+ items: { type: 'string' },
246
+ },
247
+ evidence: { type: 'string' },
248
+ confidence: { type: 'number' },
249
+ },
250
+ },
251
+ opener: {
252
+ type: 'object',
253
+ properties: {
254
+ spacing: { enum: ['compact', 'balanced', 'generous'] },
255
+ recto_start: { type: 'boolean' },
256
+ ornament: {
257
+ enum: ['none', 'rule', 'divider', 'fleuron', 'chapter-mark'],
258
+ },
259
+ drop_cap: { type: 'boolean' },
260
+ evidence: { type: 'string' },
261
+ confidence: { type: 'number' },
262
+ },
263
+ },
264
+ ornaments: {
265
+ type: 'object',
266
+ properties: {
267
+ kind: {
268
+ enum: ['none', 'rule', 'divider', 'fleuron', 'chapter-mark'],
269
+ },
270
+ weight: { enum: ['hairline', 'light', 'medium'] },
271
+ placement: { type: 'string' },
272
+ evidence: { type: 'string' },
273
+ confidence: { type: 'number' },
274
+ },
275
+ },
276
+ table_treatment: {
277
+ type: 'object',
278
+ properties: {
279
+ rule_weight: { enum: ['hairline', 'light', 'medium'] },
280
+ header_treatment: { type: 'string' },
281
+ row_rules: { enum: ['none', 'hairline', 'light'] },
282
+ density: { enum: ['airy', 'balanced', 'dense'] },
283
+ evidence: { type: 'string' },
284
+ confidence: { type: 'number' },
285
+ },
286
+ },
287
+ caption_treatment: {
288
+ type: 'object',
289
+ properties: {
290
+ position: { enum: ['above', 'below'] },
291
+ label_style: { enum: ['bold', 'small-caps', 'plain'] },
292
+ alignment: { enum: ['left', 'center', 'justified'] },
293
+ evidence: { type: 'string' },
294
+ confidence: { type: 'number' },
295
+ },
296
+ },
297
+ finish: {
298
+ type: 'object',
299
+ properties: {
300
+ register: { type: 'string' },
301
+ temperature: { type: 'string' },
302
+ era: { type: 'string' },
303
+ confidence: { type: 'number' },
304
+ },
305
+ },
306
+ },
307
+ },
308
+ rules: {
309
+ type: 'array',
310
+ items: {
311
+ type: 'object',
312
+ required: ['rule', 'confidence'],
313
+ properties: {
314
+ rule: { type: 'string' },
315
+ evidence: { type: 'string' },
316
+ confidence: { type: 'number' },
317
+ },
318
+ },
319
+ },
320
+ derived: {
321
+ type: ['object', 'null'],
322
+ description: 'compiler-only; model leaves null',
323
+ },
324
+ },
325
+ } as const
326
+
327
+ export interface PrintStyleProfilePromptInput {
328
+ mode: 'image' | 'description'
329
+ concerns?: string
330
+ imageName?: string
331
+ imageMimeType?: string
332
+ imagePixelSummary?: string
333
+ }
334
+
335
+ export function buildPrintStyleProfilePrompt(
336
+ input: PrintStyleProfilePromptInput,
337
+ ): string {
338
+ const reference =
339
+ input.mode === 'image'
340
+ ? [
341
+ `Reference image: ${input.imageName ?? 'uploaded image'}`,
342
+ input.imageMimeType ? `MIME type: ${input.imageMimeType}` : '',
343
+ input.imagePixelSummary
344
+ ? `Pixel pass summary: ${input.imagePixelSummary}`
345
+ : 'Use the attached image as the visual source.',
346
+ ]
347
+ .filter(Boolean)
348
+ .join('\n')
349
+ : [
350
+ 'Text-only design brief. This is valid style evidence.',
351
+ 'Do not reject the exemplar merely because no image is attached.',
352
+ 'Infer the print style profile from the user concerns and keep visual-only fields low-confidence.',
353
+ ].join('\n')
354
+
355
+ return [
356
+ PRINT_TOKEN_EXTRACTION_PROMPT,
357
+ '',
358
+ 'JSON SCHEMA',
359
+ JSON.stringify(PRINT_STYLE_PROFILE_SCHEMA, null, 2),
360
+ '',
361
+ 'USER DESIGN INPUT',
362
+ reference,
363
+ `Concerns/preferences: ${input.concerns?.trim() || 'none supplied'}`,
364
+ '',
365
+ 'Return only one JSON object matching the schema.',
366
+ ].join('\n')
367
+ }
368
+
369
+ export function parsePrintStyleProfileResponse(
370
+ text: string,
371
+ ): PrintStyleProfile {
372
+ const parsed = JSON.parse(extractJsonObject(text)) as unknown
373
+ if (!isPrintStyleProfile(parsed)) {
374
+ throw new Error(
375
+ 'Model response did not match the print style profile schema',
376
+ )
377
+ }
378
+ return parsed
379
+ }
380
+
381
+ export function reconcilePrintStyleProfiles(
382
+ profiles: readonly PrintStyleProfile[],
383
+ ): PrintStyleProfile {
384
+ const usable = profiles.filter(profile => profile.exemplar_quality.usable)
385
+ const inputs = usable.length > 0 ? usable : profiles
386
+ if (inputs.length === 0) {
387
+ throw new Error('No print style profiles to reconcile')
388
+ }
389
+ if (inputs.length === 1) return inputs[0]!
390
+
391
+ return {
392
+ schema_version: '1.0',
393
+ sources: inputs.flatMap(profile => profile.sources ?? []),
394
+ exemplar_quality: {
395
+ usable: usable.length > 0,
396
+ reason:
397
+ usable.length === profiles.length
398
+ ? 'reconciled multiple usable print references'
399
+ : 'reconciled usable print references; discarded unusable exemplars',
400
+ confidence: average(
401
+ inputs.map(profile => profile.exemplar_quality.confidence),
402
+ ),
403
+ },
404
+ observations: {
405
+ palette: bestByRole(
406
+ inputs.flatMap(profile => profile.observations.palette ?? []),
407
+ ),
408
+ type: bestByRole(
409
+ inputs.flatMap(profile => profile.observations.type ?? []),
410
+ ),
411
+ type_pairing: bestByConfidence(
412
+ inputs.map(profile => profile.observations.type_pairing),
413
+ ),
414
+ type_scale: bestByConfidence(
415
+ inputs.map(profile => profile.observations.type_scale),
416
+ ),
417
+ text_setting: bestByConfidence(
418
+ inputs.map(profile => profile.observations.text_setting),
419
+ ),
420
+ page_model: bestByConfidence(
421
+ inputs.map(profile => profile.observations.page_model),
422
+ ),
423
+ running_heads: bestByConfidence(
424
+ inputs.map(profile => profile.observations.running_heads),
425
+ ),
426
+ folio: bestByConfidence(
427
+ inputs.map(profile => profile.observations.folio),
428
+ ),
429
+ opener: bestByConfidence(
430
+ inputs.map(profile => profile.observations.opener),
431
+ ),
432
+ ornaments: bestByConfidence(
433
+ inputs.map(profile => profile.observations.ornaments),
434
+ ),
435
+ table_treatment: bestByConfidence(
436
+ inputs.map(profile => profile.observations.table_treatment),
437
+ ),
438
+ caption_treatment: bestByConfidence(
439
+ inputs.map(profile => profile.observations.caption_treatment),
440
+ ),
441
+ finish: bestByConfidence(
442
+ inputs.map(profile => profile.observations.finish),
443
+ ),
444
+ },
445
+ rules: [
446
+ ...reconcileRules(inputs.flatMap(profile => profile.rules ?? [])),
447
+ ...detectReconciliationConflicts(inputs),
448
+ ],
449
+ derived: null,
450
+ }
451
+ }
452
+
453
+ export function isPrintStyleProfile(
454
+ value: unknown,
455
+ ): value is PrintStyleProfile {
456
+ if (!value || typeof value !== 'object') return false
457
+ const candidate = value as Partial<PrintStyleProfile>
458
+ return (
459
+ typeof candidate.schema_version === 'string' &&
460
+ Boolean(candidate.observations) &&
461
+ typeof candidate.observations === 'object' &&
462
+ Boolean(candidate.exemplar_quality) &&
463
+ typeof candidate.exemplar_quality === 'object' &&
464
+ typeof candidate.exemplar_quality.usable === 'boolean' &&
465
+ typeof candidate.exemplar_quality.reason === 'string' &&
466
+ typeof candidate.exemplar_quality.confidence === 'number' &&
467
+ Array.isArray(candidate.rules) &&
468
+ candidate.derived === null
469
+ )
470
+ }
471
+
472
+ export interface PrintReferenceImageSample {
473
+ paper: string
474
+ ink: string
475
+ accent: string
476
+ accentConfidence: number
477
+ }
478
+
479
+ export async function samplePrintReferenceImageColors(
480
+ image: File,
481
+ ): Promise<PrintReferenceImageSample> {
482
+ const bitmap = await createImageBitmap(image)
483
+ try {
484
+ const canvas = document.createElement('canvas')
485
+ canvas.width = 80
486
+ canvas.height = 80
487
+ const context = canvas.getContext('2d')
488
+ if (!context) return defaultPrintReferenceImageSample()
489
+
490
+ context.drawImage(bitmap, 0, 0, canvas.width, canvas.height)
491
+ const pixels = context.getImageData(0, 0, canvas.width, canvas.height).data
492
+ const buckets = new Map<
493
+ string,
494
+ { rgb: [number, number, number]; count: number }
495
+ >()
496
+ for (let index = 0; index < pixels.length; index += 16) {
497
+ const alpha = pixels[index + 3]
498
+ if (alpha < 128) continue
499
+ const rgb: [number, number, number] = [
500
+ quantizeColor(pixels[index]),
501
+ quantizeColor(pixels[index + 1]),
502
+ quantizeColor(pixels[index + 2]),
503
+ ]
504
+ const key = rgb.join(',')
505
+ const bucket = buckets.get(key)
506
+ if (bucket) bucket.count += 1
507
+ else buckets.set(key, { rgb, count: 1 })
508
+ }
509
+
510
+ const ranked = Array.from(buckets.values()).sort(
511
+ (a, b) => b.count - a.count,
512
+ )
513
+ const light = ranked.find(bucket => colorLuminance(bucket.rgb) > 0.72)
514
+ const dark =
515
+ ranked
516
+ .filter(bucket => colorLuminance(bucket.rgb) < 0.36)
517
+ .sort((a, b) => b.count - a.count)[0] ?? ranked[ranked.length - 1]
518
+ const saturated = ranked
519
+ .filter(bucket => colorSaturation(bucket.rgb) > 0.22)
520
+ .sort(
521
+ (a, b) =>
522
+ colorSaturation(b.rgb) * b.count - colorSaturation(a.rgb) * a.count,
523
+ )[0]
524
+
525
+ return {
526
+ paper: rgbToHex(light?.rgb ?? [255, 255, 255]),
527
+ ink: rgbToHex(dark?.rgb ?? [26, 26, 26]),
528
+ accent: rgbToHex(saturated?.rgb ?? [70, 88, 75]),
529
+ accentConfidence: saturated ? 0.72 : 0.45,
530
+ }
531
+ } finally {
532
+ bitmap.close()
533
+ }
534
+ }
535
+
536
+ export function buildBookPrintStyleProfileFromReference({
537
+ source,
538
+ prompt,
539
+ sample,
540
+ }: {
541
+ source: string
542
+ prompt: string
543
+ sample?: PrintReferenceImageSample
544
+ }): PrintStyleProfile {
545
+ const text = prompt.toLowerCase()
546
+ const sans = text.includes('sans')
547
+ const dramatic = text.includes('dramatic') || text.includes('literary')
548
+ const airy = text.includes('airy') || text.includes('generous')
549
+ return {
550
+ schema_version: '1.0',
551
+ sources: [{ id: source, note: 'book print design reference' }],
552
+ exemplar_quality: {
553
+ usable: true,
554
+ reason: sample
555
+ ? 'local pixel extraction from uploaded book reference image'
556
+ : 'book design concern inferred from description',
557
+ confidence: sample ? 0.78 : 0.68,
558
+ },
559
+ observations: {
560
+ palette: [
561
+ {
562
+ role: 'surface',
563
+ approx_hex:
564
+ sample?.paper ?? (text.includes('cream') ? '#FBFAF4' : '#FFFFFF'),
565
+ coverage: 'dominant',
566
+ where_seen: sample ? 'largest light color cluster' : 'description',
567
+ confidence: sample ? 0.82 : 0.64,
568
+ },
569
+ {
570
+ role: 'ink',
571
+ approx_hex: sample?.ink ?? '#1A1A1A',
572
+ coverage: 'frequent',
573
+ where_seen: sample ? 'dark text-like pixels' : 'description',
574
+ confidence: sample ? 0.76 : 0.64,
575
+ },
576
+ {
577
+ role: 'accent',
578
+ approx_hex:
579
+ sample?.accent ?? (text.includes('red') ? '#7A2634' : '#46584B'),
580
+ coverage: 'sparse',
581
+ where_seen: sample
582
+ ? 'most saturated non-neutral cluster'
583
+ : 'description',
584
+ confidence: sample?.accentConfidence ?? 0.62,
585
+ },
586
+ ],
587
+ type: [
588
+ {
589
+ role: 'display',
590
+ class: sans ? 'sans' : 'serif',
591
+ serif_detail: dramatic ? 'hairline' : 'bracketed',
592
+ confidence: 0.72,
593
+ },
594
+ {
595
+ role: 'body',
596
+ class: sans ? 'sans' : 'serif',
597
+ serif_detail: 'bracketed',
598
+ confidence: 0.72,
599
+ },
600
+ {
601
+ role: 'caption',
602
+ class: 'sans',
603
+ confidence: 0.7,
604
+ },
605
+ ],
606
+ type_pairing: {
607
+ size_contrast: dramatic ? 'dramatic' : 'moderate',
608
+ confidence: 0.72,
609
+ },
610
+ type_scale: {
611
+ body_pt: dramatic ? 10.8 : 10.6,
612
+ title_scale: dramatic ? 2.6 : 2.2,
613
+ chapter_title_scale: dramatic ? 2.2 : 1.9,
614
+ section_scale: dramatic ? 1.5 : 1.35,
615
+ caption_scale: 0.82,
616
+ footnote_scale: 0.78,
617
+ confidence: sample ? 0.68 : 0.62,
618
+ },
619
+ text_setting: {
620
+ alignment: text.includes('flush') ? 'flush-left' : 'justified',
621
+ hyphenation: text.includes('no hyphen') ? 'absent' : 'present',
622
+ measure_cpl: text.includes('narrow') ? 54 : 58,
623
+ leading_feel: airy ? 'airy' : 'normal',
624
+ confidence: 0.7,
625
+ },
626
+ page_model: {
627
+ columns: 1,
628
+ margin_generosity: airy ? 'generous' : 'moderate',
629
+ margin_asymmetry: 'inner<outer',
630
+ running_heads: 'present',
631
+ folio_position: 'outside bottom',
632
+ caption_position: 'below',
633
+ confidence: 0.7,
634
+ },
635
+ running_heads: {
636
+ presence: 'present',
637
+ position: 'outer top',
638
+ transform: text.includes('uppercase') ? 'uppercase' : 'small-caps',
639
+ rule: text.includes('rule') || dramatic ? 'hairline' : 'none',
640
+ font_role: 'caption',
641
+ evidence: source,
642
+ confidence: 0.7,
643
+ },
644
+ folio: {
645
+ position: 'outside bottom',
646
+ style: 'plain',
647
+ suppress_on: ['title', 'opener'],
648
+ evidence: source,
649
+ confidence: 0.7,
650
+ },
651
+ opener: {
652
+ spacing: airy ? 'generous' : 'compact',
653
+ recto_start: true,
654
+ ornament: text.includes('ornament') || dramatic ? 'rule' : 'none',
655
+ drop_cap: false,
656
+ evidence: source,
657
+ confidence: 0.68,
658
+ },
659
+ ornaments: {
660
+ kind: text.includes('ornament') || dramatic ? 'rule' : 'none',
661
+ weight: 'hairline',
662
+ placement: 'chapter opener',
663
+ evidence: source,
664
+ confidence: 0.66,
665
+ },
666
+ table_treatment: {
667
+ rule_weight: 'hairline',
668
+ header_treatment: 'plain',
669
+ row_rules: 'header-only',
670
+ density: 'balanced',
671
+ evidence: source,
672
+ confidence: 0.7,
673
+ },
674
+ caption_treatment: {
675
+ position: 'below',
676
+ label_style: 'small-caps',
677
+ alignment: text.includes('left') ? 'left' : 'center',
678
+ evidence: source,
679
+ confidence: 0.7,
680
+ },
681
+ finish: {
682
+ register: 'literary',
683
+ confidence: 0.7,
684
+ },
685
+ },
686
+ rules: [
687
+ {
688
+ rule: 'emulate the uploaded book reference through safe Pure Render tokens',
689
+ evidence: source,
690
+ confidence: sample ? 0.78 : 0.68,
691
+ },
692
+ {
693
+ rule: prompt.trim() || 'use restrained long-form book treatment',
694
+ evidence: 'book design concern',
695
+ confidence: prompt.trim() ? 0.72 : 0.62,
696
+ },
697
+ ],
698
+ derived: null,
699
+ }
700
+ }
701
+
702
+ export function buildWriterPrintStyleProfileFromReference({
703
+ source,
704
+ prompt,
705
+ sample,
706
+ }: {
707
+ source: string
708
+ prompt: string
709
+ sample?: PrintReferenceImageSample
710
+ }): PrintStyleProfile {
711
+ const text = prompt.toLowerCase()
712
+ const sans = text.includes('sans') || text.includes('modern')
713
+ const airy = text.includes('airy') || text.includes('open')
714
+ const compact = text.includes('compact') || text.includes('dense')
715
+ const highContrast =
716
+ text.includes('high contrast') || text.includes('crisp')
717
+ const flush = !text.includes('justify')
718
+ return {
719
+ schema_version: '1.0',
720
+ sources: [{ id: source, note: 'writer print design reference' }],
721
+ exemplar_quality: {
722
+ usable: true,
723
+ reason: sample
724
+ ? 'local pixel extraction from uploaded writer reference image'
725
+ : 'writer design concern inferred from description',
726
+ confidence: sample ? 0.74 : 0.66,
727
+ },
728
+ observations: {
729
+ palette: [
730
+ {
731
+ role: 'surface',
732
+ approx_hex:
733
+ sample?.paper ?? (text.includes('cream') ? '#FBFAF4' : '#FFFFFF'),
734
+ coverage: 'dominant',
735
+ where_seen: sample ? 'largest light color cluster' : 'description',
736
+ confidence: sample ? 0.78 : 0.62,
737
+ },
738
+ {
739
+ role: 'ink',
740
+ approx_hex: sample?.ink ?? (highContrast ? '#111111' : '#202020'),
741
+ coverage: 'frequent',
742
+ where_seen: sample ? 'dark text-like pixels' : 'description',
743
+ confidence: sample ? 0.74 : 0.64,
744
+ },
745
+ {
746
+ role: 'accent',
747
+ approx_hex:
748
+ sample?.accent ?? (text.includes('blue') ? '#335f8f' : '#5f7f6c'),
749
+ coverage: 'sparse',
750
+ where_seen: sample
751
+ ? 'most saturated non-neutral cluster'
752
+ : 'description',
753
+ confidence: sample?.accentConfidence ?? 0.62,
754
+ },
755
+ ],
756
+ type: [
757
+ {
758
+ role: 'display',
759
+ class: sans ? 'sans' : 'serif',
760
+ serif_detail: 'bracketed',
761
+ confidence: 0.68,
762
+ },
763
+ {
764
+ role: 'body',
765
+ class: sans ? 'sans' : 'serif',
766
+ serif_detail: 'bracketed',
767
+ confidence: 0.7,
768
+ },
769
+ {
770
+ role: 'caption',
771
+ class: 'sans',
772
+ confidence: 0.68,
773
+ },
774
+ ],
775
+ type_pairing: {
776
+ size_contrast: compact ? 'subtle' : 'moderate',
777
+ confidence: 0.66,
778
+ },
779
+ type_scale: {
780
+ body_pt: compact ? 10.2 : 10.8,
781
+ title_scale: compact ? 1.75 : 2,
782
+ section_scale: compact ? 1.2 : 1.28,
783
+ caption_scale: 0.82,
784
+ footnote_scale: 0.78,
785
+ confidence: sample ? 0.64 : 0.6,
786
+ },
787
+ text_setting: {
788
+ alignment: flush ? 'flush-left' : 'justified',
789
+ hyphenation: flush ? 'absent' : 'present',
790
+ measure_cpl: compact ? 68 : 62,
791
+ leading_feel: airy ? 'airy' : compact ? 'tight' : 'normal',
792
+ confidence: 0.68,
793
+ },
794
+ page_model: {
795
+ columns: 1,
796
+ margin_generosity: airy ? 'generous' : 'moderate',
797
+ margin_asymmetry: 'symmetric',
798
+ running_heads: 'absent',
799
+ folio_position: 'bottom center',
800
+ caption_position: 'below',
801
+ confidence: 0.62,
802
+ },
803
+ table_treatment: {
804
+ rule_weight: 'hairline',
805
+ header_treatment: 'plain',
806
+ row_rules: 'header-only',
807
+ density: compact ? 'compact' : 'balanced',
808
+ evidence: source,
809
+ confidence: 0.66,
810
+ },
811
+ caption_treatment: {
812
+ position: 'below',
813
+ label_style: 'plain',
814
+ alignment: 'left',
815
+ evidence: source,
816
+ confidence: 0.66,
817
+ },
818
+ finish: {
819
+ register: 'draft',
820
+ confidence: 0.72,
821
+ },
822
+ },
823
+ rules: [
824
+ {
825
+ rule: 'emulate the uploaded writer reference through safe Pure Render tokens',
826
+ evidence: source,
827
+ confidence: sample ? 0.74 : 0.66,
828
+ },
829
+ {
830
+ rule:
831
+ prompt.trim() ||
832
+ 'keep writer output simple, readable, and free of manuscript noise',
833
+ evidence: 'writer design concern',
834
+ confidence: prompt.trim() ? 0.68 : 0.62,
835
+ },
836
+ ],
837
+ derived: null,
838
+ }
839
+ }
840
+
841
+ function extractJsonObject(text: string): string {
842
+ const trimmed = text.trim()
843
+ if (trimmed.startsWith('{') && trimmed.endsWith('}')) return trimmed
844
+ const start = trimmed.indexOf('{')
845
+ const end = trimmed.lastIndexOf('}')
846
+ if (start === -1 || end === -1 || end <= start) {
847
+ throw new Error('Model response did not contain a JSON object')
848
+ }
849
+ return trimmed.slice(start, end + 1)
850
+ }
851
+
852
+ function bestByRole<
853
+ T extends PrintStylePaletteObservation | PrintStyleTypeObservation,
854
+ >(items: readonly T[]): T[] {
855
+ const byRole = new Map<string, T>()
856
+ for (const item of items) {
857
+ const existing = byRole.get(item.role)
858
+ if (!existing || item.confidence > existing.confidence) {
859
+ byRole.set(item.role, item)
860
+ }
861
+ }
862
+ return Array.from(byRole.values())
863
+ }
864
+
865
+ function bestByConfidence<T extends { confidence?: number }>(
866
+ items: readonly (T | undefined)[],
867
+ ): T | undefined {
868
+ return items
869
+ .filter((item): item is T => Boolean(item))
870
+ .sort((a, b) => (b.confidence ?? 0) - (a.confidence ?? 0))[0]
871
+ }
872
+
873
+ function reconcileRules(rules: readonly PrintStyleRule[]): PrintStyleRule[] {
874
+ const byRule = new Map<string, PrintStyleRule>()
875
+ for (const rule of rules) {
876
+ const key = rule.rule.trim().toLowerCase()
877
+ if (!key) continue
878
+ const existing = byRule.get(key)
879
+ if (!existing || rule.confidence > existing.confidence) {
880
+ byRule.set(key, rule)
881
+ }
882
+ }
883
+ return Array.from(byRule.values()).sort((a, b) => b.confidence - a.confidence)
884
+ }
885
+
886
+ function detectReconciliationConflicts(
887
+ profiles: readonly PrintStyleProfile[],
888
+ ): PrintStyleRule[] {
889
+ const conflicts: PrintStyleRule[] = []
890
+ const addConflict = (label: string, values: readonly FieldValue[]): void => {
891
+ const confident = values.filter(
892
+ item => item.value !== undefined && item.value !== '' && item.confidence >= 0.6,
893
+ )
894
+ const unique = Array.from(
895
+ new Map(confident.map(item => [String(item.value), item])).values(),
896
+ )
897
+ if (unique.length < 2) return
898
+ conflicts.push({
899
+ rule: `Reference conflict: ${label} disagrees across uploaded images (${unique
900
+ .map(item => `${item.source}: ${item.value}`)
901
+ .join('; ')}). Highest-confidence value was used; review before saving as a shared theme.`,
902
+ evidence: unique.map(item => item.source).join(', '),
903
+ confidence: Math.min(0.9, average(unique.map(item => item.confidence))),
904
+ })
905
+ }
906
+
907
+ const sourceId = (profile: PrintStyleProfile, index: number): string =>
908
+ profile.sources?.[0]?.id ?? `source-${index + 1}`
909
+
910
+ addConflict(
911
+ 'body font class',
912
+ profiles.map((profile, index) => ({
913
+ source: sourceId(profile, index),
914
+ value: profile.observations.type?.find(item => item.role === 'body')?.class,
915
+ confidence:
916
+ profile.observations.type?.find(item => item.role === 'body')?.confidence ??
917
+ 0,
918
+ })),
919
+ )
920
+ addConflict(
921
+ 'accent color',
922
+ profiles.map((profile, index) => ({
923
+ source: sourceId(profile, index),
924
+ value: profile.observations.palette?.find(item => item.role === 'accent')
925
+ ?.approx_hex,
926
+ confidence:
927
+ profile.observations.palette?.find(item => item.role === 'accent')
928
+ ?.confidence ?? 0,
929
+ })),
930
+ )
931
+ addConflict(
932
+ 'text alignment',
933
+ profiles.map((profile, index) => ({
934
+ source: sourceId(profile, index),
935
+ value: profile.observations.text_setting?.alignment,
936
+ confidence: profile.observations.text_setting?.confidence ?? 0,
937
+ })),
938
+ )
939
+ addConflict(
940
+ 'page columns',
941
+ profiles.map((profile, index) => ({
942
+ source: sourceId(profile, index),
943
+ value: profile.observations.page_model?.columns,
944
+ confidence: profile.observations.page_model?.confidence ?? 0,
945
+ })),
946
+ )
947
+ addConflict(
948
+ 'running heads',
949
+ profiles.map((profile, index) => ({
950
+ source: sourceId(profile, index),
951
+ value: profile.observations.running_heads?.presence,
952
+ confidence: profile.observations.running_heads?.confidence ?? 0,
953
+ })),
954
+ )
955
+ addConflict(
956
+ 'opener ornament',
957
+ profiles.map((profile, index) => ({
958
+ source: sourceId(profile, index),
959
+ value: profile.observations.opener?.ornament,
960
+ confidence: profile.observations.opener?.confidence ?? 0,
961
+ })),
962
+ )
963
+ addConflict(
964
+ 'table density',
965
+ profiles.map((profile, index) => ({
966
+ source: sourceId(profile, index),
967
+ value: profile.observations.table_treatment?.density,
968
+ confidence: profile.observations.table_treatment?.confidence ?? 0,
969
+ })),
970
+ )
971
+ addConflict(
972
+ 'caption position',
973
+ profiles.map((profile, index) => ({
974
+ source: sourceId(profile, index),
975
+ value: profile.observations.caption_treatment?.position,
976
+ confidence: profile.observations.caption_treatment?.confidence ?? 0,
977
+ })),
978
+ )
979
+
980
+ return conflicts
981
+ }
982
+
983
+ interface FieldValue {
984
+ source: string
985
+ value: string | number | undefined
986
+ confidence: number
987
+ }
988
+
989
+ function average(values: readonly number[]): number {
990
+ if (values.length === 0) return 0
991
+ return values.reduce((sum, value) => sum + value, 0) / values.length
992
+ }
993
+
994
+ function defaultPrintReferenceImageSample(): PrintReferenceImageSample {
995
+ return {
996
+ paper: '#FFFFFF',
997
+ ink: '#1A1A1A',
998
+ accent: '#46584B',
999
+ accentConfidence: 0.45,
1000
+ }
1001
+ }
1002
+
1003
+ function quantizeColor(value: number | undefined): number {
1004
+ return Math.round((value ?? 0) / 24) * 24
1005
+ }
1006
+
1007
+ function rgbToHex(rgb: readonly number[]): string {
1008
+ return `#${rgb
1009
+ .map(value =>
1010
+ Math.max(0, Math.min(255, value)).toString(16).padStart(2, '0'),
1011
+ )
1012
+ .join('')}`
1013
+ }
1014
+
1015
+ function colorLuminance([r, g, b]: readonly number[]): number {
1016
+ return (0.2126 * r + 0.7152 * g + 0.0722 * b) / 255
1017
+ }
1018
+
1019
+ function colorSaturation([r, g, b]: readonly number[]): number {
1020
+ const max = Math.max(r, g, b)
1021
+ const min = Math.min(r, g, b)
1022
+ return max === 0 ? 0 : (max - min) / max
1023
+ }