@kortix/ui 0.14.8 → 0.14.9

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 (528) hide show
  1. package/README.md +2 -2
  2. package/dist/_vendor/kortix-shared.js +63 -3
  3. package/dist/_vendor/shared/app-config.d.ts +175 -0
  4. package/dist/_vendor/shared/channel-message.d.ts +34 -5
  5. package/dist/_vendor/shared/index.d.ts +1 -0
  6. package/dist/_vendor/shared/space-slug.d.ts +37 -0
  7. package/dist/access/access/access-detail-shell.js +1 -1
  8. package/dist/access/access/principal-picker.d.ts +11 -0
  9. package/dist/access/access/principal-picker.js +3 -2
  10. package/dist/apps/app-preview-pool.d.ts +7 -2
  11. package/dist/apps/app-preview-pool.js +11 -5
  12. package/dist/apps/app-shared.d.ts +22 -15
  13. package/dist/apps/app-shared.js +37 -19
  14. package/dist/apps/index.d.ts +2 -2
  15. package/dist/apps/index.js +2 -2
  16. package/dist/artifacts/artifact-frame.d.ts +16 -0
  17. package/dist/artifacts/artifact-frame.js +51 -0
  18. package/dist/artifacts/artifact-session.d.ts +18 -0
  19. package/dist/artifacts/artifact-session.js +101 -0
  20. package/dist/artifacts/frame-policy.d.ts +30 -0
  21. package/dist/artifacts/frame-policy.js +61 -0
  22. package/dist/artifacts/index.d.ts +3 -0
  23. package/dist/artifacts/index.js +5 -0
  24. package/dist/browser/sandbox-browser-chrome.d.ts +4 -1
  25. package/dist/browser/sandbox-browser-chrome.js +2 -2
  26. package/dist/customize/agents/agent-grant-pages.d.ts +5 -1
  27. package/dist/customize/agents/agent-grant-pages.js +30 -4
  28. package/dist/customize/agents/agent-page.js +3 -1
  29. package/dist/customize/agents/agent-policy-sets.d.ts +43 -0
  30. package/dist/customize/agents/agent-policy-sets.js +72 -0
  31. package/dist/customize/agents/agent-triggers-section.js +2 -1
  32. package/dist/customize/connectors/add-mcp/add-mcp-server-dialog.d.ts +6 -1
  33. package/dist/customize/connectors/add-mcp/add-mcp-server-dialog.js +10 -4
  34. package/dist/customize/connectors/agent-access.d.ts +35 -0
  35. package/dist/customize/connectors/agent-access.js +77 -0
  36. package/dist/customize/connectors/catalog/browse-sections.d.ts +2 -29
  37. package/dist/customize/connectors/catalog/browse-sections.js +7 -74
  38. package/dist/customize/connectors/catalog/catalog-entry.d.ts +18 -37
  39. package/dist/customize/connectors/catalog/catalog-entry.js +28 -40
  40. package/dist/customize/connectors/catalog/connector-browse.d.ts +3 -32
  41. package/dist/customize/connectors/catalog/connector-browse.js +19 -50
  42. package/dist/customize/connectors/catalog/use-catalog.d.ts +20 -59
  43. package/dist/customize/connectors/catalog/use-catalog.js +48 -175
  44. package/dist/customize/connectors/connector-identity.d.ts +9 -11
  45. package/dist/customize/connectors/connector-identity.js +13 -42
  46. package/dist/customize/connectors/connector-moves-banner.d.ts +2 -1
  47. package/dist/customize/connectors/connector-moves-banner.js +4 -6
  48. package/dist/customize/connectors/connector-routes.d.ts +21 -9
  49. package/dist/customize/connectors/connector-routes.js +29 -20
  50. package/dist/customize/connectors/connectors-page.js +53 -82
  51. package/dist/customize/connectors/detail/app-page.js +226 -98
  52. package/dist/customize/connectors/detail/computer-app-page.d.ts +16 -0
  53. package/dist/customize/connectors/detail/computer-app-page.js +39 -0
  54. package/dist/customize/connectors/detail/connector-accounts.js +3 -1
  55. package/dist/customize/connectors/detail/connector-advanced-tools.d.ts +4 -1
  56. package/dist/customize/connectors/detail/connector-advanced-tools.js +2 -2
  57. package/dist/customize/connectors/detail/connector-agents.d.ts +14 -0
  58. package/dist/customize/connectors/detail/connector-agents.js +69 -0
  59. package/dist/customize/connectors/detail/connector-detail-tabs.js +2 -1
  60. package/dist/customize/connectors/detail/connector-move-row.d.ts +8 -5
  61. package/dist/customize/connectors/detail/connector-move-row.js +23 -14
  62. package/dist/customize/connectors/detail/connector-page.d.ts +4 -1
  63. package/dist/customize/connectors/detail/connector-page.js +14 -10
  64. package/dist/customize/connectors/detail/connector-policy-sets.d.ts +28 -0
  65. package/dist/customize/connectors/detail/connector-policy-sets.js +203 -0
  66. package/dist/customize/connectors/detail/connector-settings.d.ts +4 -1
  67. package/dist/customize/connectors/detail/connector-settings.js +7 -3
  68. package/dist/customize/connectors/detail/connector-status.d.ts +19 -6
  69. package/dist/customize/connectors/detail/connector-status.js +34 -10
  70. package/dist/customize/connectors/detail/connector-tabs.d.ts +4 -1
  71. package/dist/customize/connectors/detail/connector-tabs.js +8 -2
  72. package/dist/customize/connectors/detail/connector-tools.d.ts +3 -3
  73. package/dist/customize/connectors/detail/connector-tools.js +105 -48
  74. package/dist/customize/connectors/detail/email-inboxes.d.ts +43 -0
  75. package/dist/customize/connectors/detail/email-inboxes.js +184 -0
  76. package/dist/customize/connectors/detail/provider-info.d.ts +1 -1
  77. package/dist/customize/connectors/detail/provider-info.js +2 -2
  78. package/dist/customize/connectors/detail/status-badge.d.ts +1 -1
  79. package/dist/customize/connectors/detail/status-badge.js +4 -5
  80. package/dist/customize/connectors/detail/use-connector-detail.d.ts +6 -0
  81. package/dist/customize/connectors/detail/use-connector-detail.js +14 -4
  82. package/dist/customize/connectors/install/install-dialog.d.ts +23 -0
  83. package/dist/customize/connectors/install/install-dialog.js +40 -0
  84. package/dist/customize/connectors/install/install.d.ts +13 -2
  85. package/dist/customize/connectors/install/install.js +9 -4
  86. package/dist/customize/connectors/install/use-install.d.ts +19 -2
  87. package/dist/customize/connectors/install/use-install.js +65 -37
  88. package/dist/customize/connectors/install/way-kind.d.ts +5 -0
  89. package/dist/customize/connectors/install/way-kind.js +16 -0
  90. package/dist/customize/connectors/provider-label.d.ts +2 -1
  91. package/dist/customize/connectors/provider-label.js +13 -3
  92. package/dist/customize/connectors/tools/pattern-rule-draft.d.ts +4 -0
  93. package/dist/customize/connectors/tools/pattern-rule-draft.js +12 -3
  94. package/dist/customize/connectors/tools/policy-sets.d.ts +27 -0
  95. package/dist/customize/connectors/tools/policy-sets.js +41 -0
  96. package/dist/customize/connectors/tools/tool-groups.d.ts +32 -9
  97. package/dist/customize/connectors/tools/tool-groups.js +78 -16
  98. package/dist/customize/connectors/tools/tool-policy-control.d.ts +11 -10
  99. package/dist/customize/connectors/tools/tool-policy-control.js +15 -16
  100. package/dist/customize/connectors/tools/tool-policy-labels.d.ts +6 -5
  101. package/dist/customize/connectors/tools/tool-policy-labels.js +17 -16
  102. package/dist/customize/connectors/tools/tool-policy.d.ts +50 -4
  103. package/dist/customize/connectors/tools/tool-policy.js +74 -8
  104. package/dist/customize/connectors/use-agent-access.d.ts +85 -0
  105. package/dist/customize/connectors/use-agent-access.js +72 -0
  106. package/dist/customize/migrate-to-v2/index.d.ts +1 -2
  107. package/dist/customize/migrate-to-v2/index.js +1 -2
  108. package/dist/customize/migrate-to-v2/migration-prompt.d.ts +12 -25
  109. package/dist/customize/migrate-to-v2/migration-prompt.js +34 -236
  110. package/dist/customize/migrate-to-v2/upgrade-defs.d.ts +1 -1
  111. package/dist/customize/migrate-to-v2/upgrade-defs.js +7 -6
  112. package/dist/customize/migrate-to-v2/upgrade-view.js +7 -7
  113. package/dist/customize/migrate-to-v2/use-run-upgrade.d.ts +3 -3
  114. package/dist/customize/providers/provider-accordion.js +17 -2
  115. package/dist/customize/sections/component/channel-access-panel.d.ts +16 -0
  116. package/dist/customize/sections/component/channel-access-panel.js +146 -0
  117. package/dist/customize/sections/component/channel-connect-hero.d.ts +3 -2
  118. package/dist/customize/sections/component/channel-connect-hero.js +3 -2
  119. package/dist/customize/sections/component/channel-hero-frame.d.ts +31 -0
  120. package/dist/customize/sections/component/channel-hero-frame.js +40 -0
  121. package/dist/customize/sections/component/channel-mocks.d.ts +6 -0
  122. package/dist/customize/sections/component/channel-mocks.js +46 -0
  123. package/dist/customize/sections/component/channel-row.d.ts +13 -3
  124. package/dist/customize/sections/component/channel-row.js +10 -4
  125. package/dist/customize/sections/component/email-connect-form.d.ts +27 -0
  126. package/dist/customize/sections/component/email-connect-form.js +183 -0
  127. package/dist/customize/sections/component/slack-connect-card.d.ts +7 -0
  128. package/dist/customize/sections/component/slack-connect-card.js +2 -3
  129. package/dist/customize/sections/component/teams-connect-card.d.ts +7 -0
  130. package/dist/customize/sections/component/teams-connect-card.js +2 -3
  131. package/dist/customize/sections/component/telegram-channel-row.js +16 -25
  132. package/dist/customize/sections/connectors-view.d.ts +1 -15
  133. package/dist/customize/sections/connectors-view.js +81 -314
  134. package/dist/customize/sections/view/agent-editor-catalog.js +5 -0
  135. package/dist/customize/sections/view/channels-view.d.ts +5 -1
  136. package/dist/customize/sections/view/channels-view.js +61 -75
  137. package/dist/customize/sections/view/gateway/gateway-overview.js +10 -2
  138. package/dist/customize/settings/tabs/experimental-tab.js +35 -16
  139. package/dist/customize/shared/catalog/catalog-empty-state.d.ts +1 -12
  140. package/dist/customize/shared/catalog/catalog-empty-state.js +3 -3
  141. package/dist/customize/shared/channels-switch-dialog.d.ts +18 -0
  142. package/dist/customize/shared/channels-switch-dialog.js +30 -0
  143. package/dist/customize/shared/feature-gate-screen.js +27 -9
  144. package/dist/customize/shared/use-customize-prefetch.d.ts +2 -3
  145. package/dist/customize/shared/use-customize-prefetch.js +9 -27
  146. package/dist/customize/triggers/schedule/post-to-field.d.ts +18 -0
  147. package/dist/customize/triggers/schedule/post-to-field.js +49 -0
  148. package/dist/customize/triggers/schedule/schedule-copy.d.ts +10 -6
  149. package/dist/customize/triggers/schedule/schedule-copy.js +42 -11
  150. package/dist/customize/triggers/schedule/schedule-detail-sheet.js +2 -1
  151. package/dist/customize/triggers/schedule/trigger-composer-logic.d.ts +22 -0
  152. package/dist/customize/triggers/schedule/trigger-composer-logic.js +25 -0
  153. package/dist/customize/triggers/schedule/trigger-composer.d.ts +5 -0
  154. package/dist/customize/triggers/schedule/trigger-composer.js +23 -14
  155. package/dist/customize/triggers/schedule/trigger-edit-logic.js +3 -0
  156. package/dist/customize/triggers/schedule/trigger-filter.js +1 -1
  157. package/dist/customize/triggers/schedule/trigger-runs-logic.d.ts +2 -2
  158. package/dist/customize/triggers/schedule/trigger-runs-panel.js +1 -0
  159. package/dist/customize/triggers/schedule/trigger-toast.js +2 -1
  160. package/dist/customize/triggers/schedule-view.js +5 -2
  161. package/dist/files/index.d.ts +1 -0
  162. package/dist/files/index.js +1 -0
  163. package/dist/files/project/change-request-recovery.d.ts +1 -26
  164. package/dist/files/project/change-request-recovery.js +1 -59
  165. package/dist/files/project/components/change-request-detail-dialog.js +13 -3
  166. package/dist/files/project/components/drive-explorer.js +15 -15
  167. package/dist/files/project/components/drive-toolbar.d.ts +16 -2
  168. package/dist/files/project/components/drive-toolbar.js +14 -5
  169. package/dist/files/project/hooks/use-change-request-recovery.js +3 -19
  170. package/dist/files/project/hooks/use-change-requests.d.ts +1 -0
  171. package/dist/files/project/hooks/use-change-requests.js +11 -2
  172. package/dist/files/renderers/image-frame.d.ts +46 -0
  173. package/dist/files/renderers/image-frame.js +59 -0
  174. package/dist/files/renderers/image-renderer.d.ts +11 -1
  175. package/dist/files/renderers/image-renderer.js +13 -3
  176. package/dist/files/renderers/pdf/pdf-clipboard.d.ts +2 -0
  177. package/dist/files/renderers/pdf/pdf-clipboard.js +21 -0
  178. package/dist/files/renderers/pdf/pdf-viewer.js +4 -2
  179. package/dist/files/renderers/show-content-renderer.d.ts +9 -1
  180. package/dist/files/renderers/show-content-renderer.js +56 -14
  181. package/dist/files/runtime/file-source.js +2 -0
  182. package/dist/files/runtime/hooks/use-binary-blob.d.ts +10 -1
  183. package/dist/files/runtime/hooks/use-binary-blob.js +10 -4
  184. package/dist/files/runtime/hooks/use-shared-image.d.ts +41 -0
  185. package/dist/files/runtime/hooks/use-shared-image.js +177 -0
  186. package/dist/files/viewer/file-content-renderer.d.ts +22 -1
  187. package/dist/files/viewer/file-content-renderer.js +50 -7
  188. package/dist/files/viewer/file-header.d.ts +7 -3
  189. package/dist/files/viewer/file-header.js +20 -11
  190. package/dist/files/viewer/file-preview-modal.d.ts +1 -1
  191. package/dist/files/viewer/file-preview-modal.js +10 -22
  192. package/dist/files/viewer/file-source.d.ts +13 -0
  193. package/dist/files/viewer/use-file-content-state.d.ts +2 -0
  194. package/dist/files/viewer/use-file-content-state.js +28 -10
  195. package/dist/files/viewer/viewer-actions.d.ts +3 -1
  196. package/dist/files/viewer/viewer-actions.js +2 -2
  197. package/dist/hooks/use-media-query.d.ts +1 -0
  198. package/dist/hooks/use-mobile.d.ts +5 -0
  199. package/dist/hooks/use-mobile.js +8 -0
  200. package/dist/i18n/en.d.ts +873 -85
  201. package/dist/i18n/en.js +888 -100
  202. package/dist/i18n/ui-catalog/localize-ui-catalog.js +4 -0
  203. package/dist/i18n/ui-catalog/remaining-ui-translation-keys.generated.js +15 -3
  204. package/dist/index.d.ts +2 -2
  205. package/dist/index.js +2 -2
  206. package/dist/internal/account-hub/account-settings-sidebar.js +1 -1
  207. package/dist/internal/account-hub/hub-sheet.d.ts +9 -1
  208. package/dist/internal/account-hub/hub-sheet.js +21 -5
  209. package/dist/internal/account-hub/sections.d.ts +1 -1
  210. package/dist/internal/account-hub/sections.js +2 -1
  211. package/dist/internal/auth/auth-card-shell.js +3 -3
  212. package/dist/internal/components/agent-mark.js +5 -2
  213. package/dist/internal/connector-logo/connector-logo-source.d.ts +63 -0
  214. package/dist/internal/connector-logo/connector-logo-source.js +48 -0
  215. package/dist/internal/connector-logo/connector-logo.d.ts +39 -0
  216. package/dist/internal/connector-logo/connector-logo.js +56 -0
  217. package/dist/internal/connector-logo/connector-mark-files.d.ts +11 -0
  218. package/dist/internal/connector-logo/connector-mark-files.js +35 -0
  219. package/dist/internal/files/file-read-retry.d.ts +13 -0
  220. package/dist/internal/files/file-read-retry.js +23 -3
  221. package/dist/internal/files/session-drive-files.d.ts +5 -1
  222. package/dist/internal/files/session-drive-files.js +7 -2
  223. package/dist/internal/hooks/channels/use-channels-installations.d.ts +32 -5
  224. package/dist/internal/hooks/channels/use-channels-installations.js +55 -7
  225. package/dist/internal/hooks/channels/use-email-inboxes.d.ts +19 -0
  226. package/dist/internal/hooks/channels/use-email-inboxes.js +48 -0
  227. package/dist/internal/hooks/connectors/use-add-account.js +1 -1
  228. package/dist/internal/hooks/index.d.ts +1 -1
  229. package/dist/internal/hooks/index.js +1 -1
  230. package/dist/internal/hooks/projects/use-new-project-session.d.ts +9 -0
  231. package/dist/internal/hooks/projects/use-new-project-session.js +28 -12
  232. package/dist/internal/hooks/use-auto-scroll.d.ts +8 -1
  233. package/dist/internal/hooks/use-auto-scroll.js +4 -4
  234. package/dist/internal/icons/telegram.d.ts +5 -0
  235. package/dist/internal/icons/telegram.js +6 -1
  236. package/dist/internal/layout/nav-hover-card.d.ts +35 -0
  237. package/dist/internal/layout/nav-hover-card.js +84 -0
  238. package/dist/internal/layout/project-nav-row.d.ts +3 -1
  239. package/dist/internal/layout/project-nav-row.js +3 -2
  240. package/dist/internal/layout/project-page-header.d.ts +4 -2
  241. package/dist/internal/layout/project-page-header.js +4 -3
  242. package/dist/internal/layout/project-settings-nav.js +59 -2
  243. package/dist/internal/lib/browser-error-noise.d.ts +1 -1
  244. package/dist/internal/lib/browser-error-noise.js +1 -1
  245. package/dist/internal/lib/browser-noise/rules/injected-scripts.d.ts +7 -0
  246. package/dist/internal/lib/browser-noise/rules/injected-scripts.js +15 -0
  247. package/dist/internal/lib/manifest-version.d.ts +1 -1
  248. package/dist/internal/lib/manifest-version.js +2 -2
  249. package/dist/internal/lib/memory-href.d.ts +16 -0
  250. package/dist/internal/lib/memory-href.js +46 -0
  251. package/dist/internal/lib/use-project-feature-flags.js +9 -9
  252. package/dist/internal/lib/utils/heic-convert.d.ts +1 -1
  253. package/dist/internal/lib/utils/heic-convert.js +3 -4
  254. package/dist/internal/lib/web-notifications.d.ts +20 -0
  255. package/dist/internal/lib/web-notifications.js +48 -4
  256. package/dist/internal/notifications/notification-bell.d.ts +4 -1
  257. package/dist/internal/notifications/notification-bell.js +2 -2
  258. package/dist/internal/selection/selection-bar.d.ts +2 -1
  259. package/dist/internal/selection/selection-bar.js +1 -1
  260. package/dist/internal/stores/file-preview-store.d.ts +29 -2
  261. package/dist/internal/stores/file-preview-store.js +19 -1
  262. package/dist/internal/stores/kortix-computer-store.js +3 -0
  263. package/dist/internal/stores/sidebar-nav-store.d.ts +1 -1
  264. package/dist/internal/stores/sidebar-nav-store.js +3 -1
  265. package/dist/lib/routes.d.ts +13 -1
  266. package/dist/lib/routes.js +13 -0
  267. package/dist/markdown/index.d.ts +2 -1
  268. package/dist/markdown/index.js +2 -1
  269. package/dist/markdown/setup-links/approval-decision.d.ts +1 -0
  270. package/dist/markdown/setup-links/approval-decision.js +6 -2
  271. package/dist/markdown/setup-links/connector-handshake.d.ts +3 -1
  272. package/dist/markdown/setup-links/connector-handshake.js +12 -10
  273. package/dist/markdown/setup-links/secret-intake-form.d.ts +0 -10
  274. package/dist/markdown/setup-links/secret-intake-form.js +1 -1
  275. package/dist/markdown/setup-links/show-connect-hero.d.ts +28 -0
  276. package/dist/markdown/setup-links/show-connect-hero.js +91 -0
  277. package/dist/markdown/unified-markdown.js +5 -5
  278. package/dist/primitives/badge.d.ts +1 -1
  279. package/dist/primitives/badge.js +2 -0
  280. package/dist/primitives/disclosure.js +14 -2
  281. package/dist/primitives/empty-art.d.ts +12 -0
  282. package/dist/primitives/empty-art.js +11 -0
  283. package/dist/primitives/mermaid-renderer.d.ts +2 -0
  284. package/dist/primitives/mermaid-renderer.js +21 -15
  285. package/dist/primitives/resizable.js +6 -1
  286. package/dist/primitives/resize-seam.d.ts +11 -0
  287. package/dist/primitives/resize-seam.js +12 -0
  288. package/dist/primitives/sidebar-panel.js +4 -7
  289. package/dist/primitives/tabs.d.ts +14 -1
  290. package/dist/primitives/tabs.js +1 -1
  291. package/dist/providers/ui-provider.d.ts +9 -0
  292. package/dist/session/composer/ComposerAboveCard.d.ts +1 -1
  293. package/dist/session/composer/ComposerAboveCard.js +2 -2
  294. package/dist/session/composer/ComposerCard.d.ts +1 -1
  295. package/dist/session/composer/ComposerCard.js +3 -3
  296. package/dist/session/composer/agent-selector.d.ts +13 -1
  297. package/dist/session/composer/agent-selector.js +25 -5
  298. package/dist/session/composer/composer-agent-access.d.ts +15 -0
  299. package/dist/session/composer/composer-agent-access.js +27 -0
  300. package/dist/session/composer/composer-chat-input.d.ts +8 -2
  301. package/dist/session/composer/composer-chat-input.js +8 -6
  302. package/dist/session/composer/composer-logic.d.ts +16 -0
  303. package/dist/session/composer/composer-logic.js +12 -0
  304. package/dist/session/composer/composer-underbar.d.ts +3 -1
  305. package/dist/session/composer/composer-underbar.js +4 -3
  306. package/dist/session/composer/composer.d.ts +15 -1
  307. package/dist/session/composer/composer.js +19 -1
  308. package/dist/session/composer/draft/composer-draft.d.ts +6 -2
  309. package/dist/session/composer/draft/composer-draft.js +5 -1
  310. package/dist/session/composer/reasoning-effort-selector.js +1 -1
  311. package/dist/session/models/chatgpt-connection-button.d.ts +11 -0
  312. package/dist/session/models/chatgpt-connection-button.js +54 -0
  313. package/dist/session/models/model-selector.js +5 -2
  314. package/dist/session/models/use-model-connection-gate.d.ts +5 -5
  315. package/dist/session/models/use-model-connection-gate.js +5 -5
  316. package/dist/session/outcomes/jump-to-outcome.d.ts +10 -0
  317. package/dist/session/outcomes/jump-to-outcome.js +11 -0
  318. package/dist/session/outcomes/session-outcomes-bridge.d.ts +14 -0
  319. package/dist/session/outcomes/session-outcomes-bridge.js +12 -0
  320. package/dist/session/outcomes/session-outcomes-provider.d.ts +3 -1
  321. package/dist/session/outcomes/session-outcomes-provider.js +33 -3
  322. package/dist/session/panel/easy/app-preview.d.ts +10 -0
  323. package/dist/session/panel/easy/app-preview.js +31 -12
  324. package/dist/session/panel/easy/change-requests-card.d.ts +7 -0
  325. package/dist/session/panel/easy/change-requests-card.js +17 -0
  326. package/dist/session/panel/easy/easy-panel-logic.js +7 -1
  327. package/dist/session/panel/easy/easy-panel.js +5 -2
  328. package/dist/session/panel/easy/file-preview.js +1 -1
  329. package/dist/session/panel/easy/outputs-card.d.ts +2 -1
  330. package/dist/session/panel/easy/outputs-card.js +3 -2
  331. package/dist/session/panel/mobile-tool-drawer.js +7 -1
  332. package/dist/session/panel/session-audit-panel.js +9 -2
  333. package/dist/session/panel/session-detail-panel.js +9 -3
  334. package/dist/session/panel/session-files-explorer.d.ts +9 -0
  335. package/dist/session/panel/session-files-explorer.js +12 -3
  336. package/dist/session/panel/session-panel-provider.d.ts +15 -1
  337. package/dist/session/panel/session-panel-provider.js +32 -7
  338. package/dist/session/panel/shared/derive-panels.d.ts +8 -1
  339. package/dist/session/panel/shared/derive-panels.js +12 -2
  340. package/dist/session/panel/shared/output-priority.js +2 -0
  341. package/dist/session/participants/session-participants.d.ts +5 -1
  342. package/dist/session/participants/session-participants.js +9 -2
  343. package/dist/session/prompts/approval-request.d.ts +5 -0
  344. package/dist/session/prompts/approval-request.js +7 -1
  345. package/dist/session/prompts/session-approval-review.d.ts +9 -0
  346. package/dist/session/prompts/session-approval-review.js +18 -0
  347. package/dist/session/status/session-error-banner.js +2 -49
  348. package/dist/session/status/session-starting-loader.d.ts +14 -2
  349. package/dist/session/status/session-starting-loader.js +16 -4
  350. package/dist/session/thread/after-first-prompt.d.ts +7 -0
  351. package/dist/session/thread/after-first-prompt.js +8 -0
  352. package/dist/session/thread/detect-command.d.ts +12 -0
  353. package/dist/session/thread/detect-command.js +20 -0
  354. package/dist/session/thread/session-chat.d.ts +3 -1
  355. package/dist/session/thread/session-chat.js +377 -252
  356. package/dist/session/thread/session-thread.d.ts +3 -1
  357. package/dist/session/thread/session-thread.js +9 -2
  358. package/dist/session/thread/thread-boot-rows.d.ts +36 -0
  359. package/dist/session/thread/thread-boot-rows.js +89 -0
  360. package/dist/session/thread/transcript-rows.d.ts +31 -0
  361. package/dist/session/thread/transcript-rows.js +3 -0
  362. package/dist/session/thread/transcript.d.ts +0 -9
  363. package/dist/session/thread/transcript.js +104 -19
  364. package/dist/session/tool/shared/infrastructure-contexts.d.ts +28 -0
  365. package/dist/session/tool/shared/infrastructure-contexts.js +31 -3
  366. package/dist/session/tool/tool-part-renderer.d.ts +5 -4
  367. package/dist/session/tool/tool-part-renderer.js +8 -3
  368. package/dist/session/tool/tools/bash-tool.js +7 -6
  369. package/dist/session/tool/tools/channel-send-card.d.ts +1 -2
  370. package/dist/session/tool/tools/channel-send.d.ts +2 -1
  371. package/dist/session/tool/tools/codemode-tool.js +1 -1
  372. package/dist/session/tool/tools/connector-tools.js +1 -1
  373. package/dist/session/tool/tools/image-gen-tool.d.ts +1 -1
  374. package/dist/session/tool/tools/image-gen-tool.js +240 -53
  375. package/dist/session/tool/tools/show-tool.js +19 -8
  376. package/dist/session/turn/channel-brand.d.ts +8 -3
  377. package/dist/session/turn/channel-brand.js +20 -4
  378. package/dist/session/turn/message-author.d.ts +8 -0
  379. package/dist/session/turn/message-author.js +24 -0
  380. package/dist/session/turn/message-row.d.ts +52 -0
  381. package/dist/session/turn/message-row.js +44 -0
  382. package/dist/session/turn/plan-anchor.d.ts +1 -1
  383. package/dist/session/turn/plan-anchor.js +1 -1
  384. package/dist/session/turn/user-message.d.ts +7 -1
  385. package/dist/session/turn/user-message.js +21 -7
  386. package/dist/styles.css +1 -1
  387. package/dist/workspace/home/access-requests-bell.d.ts +9 -2
  388. package/dist/workspace/home/access-requests-bell.js +20 -6
  389. package/dist/workspace/home/first-chat.d.ts +8 -21
  390. package/dist/workspace/home/first-chat.js +22 -72
  391. package/dist/workspace/index.d.ts +6 -0
  392. package/dist/workspace/index.js +6 -0
  393. package/dist/workspace/layout/project-home.d.ts +14 -2
  394. package/dist/workspace/layout/project-home.js +60 -21
  395. package/dist/workspace/layout/use-project-home-send.d.ts +9 -1
  396. package/dist/workspace/layout/use-project-home-send.js +82 -3
  397. package/dist/workspace/memory/body-doc.d.ts +17 -0
  398. package/dist/workspace/memory/body-doc.js +208 -0
  399. package/dist/workspace/memory/body-extensions.d.ts +38 -0
  400. package/dist/workspace/memory/body-extensions.js +103 -0
  401. package/dist/workspace/memory/company-memory-page.d.ts +8 -0
  402. package/dist/workspace/memory/company-memory-page.js +16 -0
  403. package/dist/workspace/memory/memory-all.d.ts +20 -0
  404. package/dist/workspace/memory/memory-all.js +27 -0
  405. package/dist/workspace/memory/memory-body.d.ts +19 -0
  406. package/dist/workspace/memory/memory-body.js +139 -0
  407. package/dist/workspace/memory/memory-empty.d.ts +13 -0
  408. package/dist/workspace/memory/memory-empty.js +28 -0
  409. package/dist/workspace/memory/memory-history.d.ts +27 -0
  410. package/dist/workspace/memory/memory-history.js +108 -0
  411. package/dist/workspace/memory/memory-labels.d.ts +16 -0
  412. package/dist/workspace/memory/memory-labels.js +47 -0
  413. package/dist/workspace/memory/memory-page-list.d.ts +34 -0
  414. package/dist/workspace/memory/memory-page-list.js +156 -0
  415. package/dist/workspace/memory/memory-reader.d.ts +58 -0
  416. package/dist/workspace/memory/memory-reader.js +123 -0
  417. package/dist/workspace/memory/memory-view.d.ts +21 -0
  418. package/dist/workspace/memory/memory-view.js +124 -0
  419. package/dist/workspace/memory/remember-popover.d.ts +19 -0
  420. package/dist/workspace/memory/remember-popover.js +61 -0
  421. package/dist/workspace/memory/remember-this.d.ts +12 -0
  422. package/dist/workspace/memory/remember-this.js +89 -0
  423. package/dist/workspace/memory/source-mark.d.ts +18 -0
  424. package/dist/workspace/memory/source-mark.js +79 -0
  425. package/dist/workspace/memory/used-memory-chip.d.ts +14 -0
  426. package/dist/workspace/memory/used-memory-chip.js +61 -0
  427. package/dist/workspace/new/advanced-fields.d.ts +2 -1
  428. package/dist/workspace/new/advanced-fields.js +2 -2
  429. package/dist/workspace/new/source-param.d.ts +1 -1
  430. package/dist/workspace/new/source-param.js +3 -1
  431. package/dist/workspace/new/use-create-workspace.d.ts +8 -10
  432. package/dist/workspace/new/use-create-workspace.js +3 -3
  433. package/dist/workspace/projects/onboarding/app-logo.d.ts +7 -2
  434. package/dist/workspace/projects/onboarding/app-logo.js +6 -13
  435. package/dist/workspace/projects/policies-panel.js +5 -3
  436. package/dist/workspace/review/review-actions.d.ts +4 -2
  437. package/dist/workspace/review/review-actions.js +8 -2
  438. package/dist/workspace/review/review-center.js +11 -7
  439. package/dist/workspace/review/review-detail.d.ts +10 -0
  440. package/dist/workspace/review/review-detail.js +101 -13
  441. package/dist/workspace/saved-session-skeleton.d.ts +2 -2
  442. package/dist/workspace/saved-session-skeleton.js +3 -3
  443. package/dist/workspace/session-workspace.js +15 -13
  444. package/dist/workspace/sessions/project-sessions-view.js +3 -2
  445. package/dist/workspace/sessions/sessions-toolbar.d.ts +3 -1
  446. package/dist/workspace/sessions/sessions-toolbar.js +2 -2
  447. package/dist/workspace/sidebar/footer/project-apps-nav.d.ts +5 -0
  448. package/dist/workspace/sidebar/footer/project-apps-nav.js +41 -2
  449. package/dist/workspace/sidebar/footer/project-artifacts-nav.d.ts +6 -0
  450. package/dist/workspace/sidebar/footer/project-artifacts-nav.js +22 -0
  451. package/dist/workspace/sidebar/footer/project-manifest-upgrade-alert.js +13 -13
  452. package/dist/workspace/sidebar/footer/project-memory-nav.d.ts +7 -0
  453. package/dist/workspace/sidebar/footer/project-memory-nav.js +19 -0
  454. package/dist/workspace/sidebar/nav-row-meta.js +14 -2
  455. package/dist/workspace/sidebar/project-nav-items.js +3 -0
  456. package/dist/workspace/sidebar/project-session-list.js +68 -56
  457. package/dist/workspace/sidebar/project-sidebar.js +9 -2
  458. package/dist/workspace/sidebar/project-spaces-nav.d.ts +8 -0
  459. package/dist/workspace/sidebar/project-spaces-nav.js +69 -0
  460. package/dist/workspace/sidebar/session-group-order.d.ts +9 -0
  461. package/dist/workspace/sidebar/session-group-order.js +25 -0
  462. package/dist/workspace/sidebar/session-group-sections.d.ts +62 -0
  463. package/dist/workspace/sidebar/session-group-sections.js +267 -0
  464. package/dist/workspace/sidebar/session-list-chrome.d.ts +28 -0
  465. package/dist/workspace/sidebar/session-list-chrome.js +50 -0
  466. package/dist/workspace/spaces/channel-glyph.d.ts +5 -0
  467. package/dist/workspace/spaces/channel-glyph.js +6 -0
  468. package/dist/workspace/spaces/channel-setup.d.ts +16 -0
  469. package/dist/workspace/spaces/channel-setup.js +105 -0
  470. package/dist/workspace/spaces/channels-onboarding.d.ts +13 -0
  471. package/dist/workspace/spaces/channels-onboarding.js +37 -0
  472. package/dist/workspace/spaces/create-channel-dialog.d.ts +16 -0
  473. package/dist/workspace/spaces/create-channel-dialog.js +158 -0
  474. package/dist/workspace/spaces/private-channels-not-in.d.ts +9 -0
  475. package/dist/workspace/spaces/private-channels-not-in.js +69 -0
  476. package/dist/workspace/spaces/space-agents.d.ts +10 -0
  477. package/dist/workspace/spaces/space-agents.js +10 -0
  478. package/dist/workspace/spaces/space-details-dialog.d.ts +20 -0
  479. package/dist/workspace/spaces/space-details-dialog.js +176 -0
  480. package/dist/workspace/spaces/space-link-label.d.ts +49 -0
  481. package/dist/workspace/spaces/space-link-label.js +73 -0
  482. package/dist/workspace/spaces/space-links.d.ts +19 -0
  483. package/dist/workspace/spaces/space-links.js +230 -0
  484. package/dist/workspace/spaces/space-members.d.ts +46 -0
  485. package/dist/workspace/spaces/space-members.js +182 -0
  486. package/dist/workspace/spaces/space-roster.d.ts +15 -0
  487. package/dist/workspace/spaces/space-roster.js +44 -0
  488. package/dist/workspace/spaces/space-threads-tab.d.ts +56 -0
  489. package/dist/workspace/spaces/space-threads-tab.js +156 -0
  490. package/dist/workspace/spaces/space-timeline-view.d.ts +254 -0
  491. package/dist/workspace/spaces/space-timeline-view.js +235 -0
  492. package/dist/workspace/spaces/space-timeline.d.ts +106 -0
  493. package/dist/workspace/spaces/space-timeline.js +183 -0
  494. package/dist/workspace/spaces/space-triggers.d.ts +13 -0
  495. package/dist/workspace/spaces/space-triggers.js +78 -0
  496. package/dist/workspace/spaces/space-view.d.ts +37 -0
  497. package/dist/workspace/spaces/space-view.js +436 -0
  498. package/package.json +27 -36
  499. package/skills/kortix-ui/SKILL.md +3 -3
  500. package/skills/kortix-ui/references/custom-dashboard.md +3 -3
  501. package/skills/kortix-ui/references/modules.md +28 -4
  502. package/skills/kortix-ui/references/theming.md +1 -1
  503. package/dist/customize/connectors/catalog/merge-search.d.ts +0 -10
  504. package/dist/customize/connectors/catalog/merge-search.js +0 -26
  505. package/dist/customize/connectors/detail/find-easy-connect-app.d.ts +0 -29
  506. package/dist/customize/connectors/detail/find-easy-connect-app.js +0 -22
  507. package/dist/customize/migrate-to-v2/use-migrate-to-v2.d.ts +0 -9
  508. package/dist/customize/migrate-to-v2/use-migrate-to-v2.js +0 -24
  509. package/dist/customize/sections/connector-connection-header.d.ts +0 -36
  510. package/dist/customize/sections/connector-connection-header.js +0 -33
  511. package/dist/customize/sections/connector-connection-modal.d.ts +0 -34
  512. package/dist/customize/sections/connector-connection-modal.js +0 -72
  513. package/dist/customize/sections/discover-catalogue-card.d.ts +0 -19
  514. package/dist/customize/sections/discover-catalogue-card.js +0 -26
  515. package/dist/customize/sections/discover-catalogue-detail.d.ts +0 -15
  516. package/dist/customize/sections/discover-catalogue-detail.js +0 -24
  517. package/dist/customize/sections/discover-catalogue.d.ts +0 -17
  518. package/dist/customize/sections/discover-catalogue.js +0 -132
  519. package/dist/customize/sections/llm-provider/provider-access-menu.d.ts +0 -10
  520. package/dist/customize/sections/llm-provider/provider-access-menu.js +0 -23
  521. package/dist/files/project/components/drive-empty-art.d.ts +0 -2
  522. package/dist/files/project/components/drive-empty-art.js +0 -5
  523. package/dist/internal/hooks/use-media-query.d.ts +0 -2
  524. package/dist/internal/share/public-share-link-button.d.ts +0 -14
  525. package/dist/internal/share/public-share-link-button.js +0 -17
  526. package/dist/markdown/setup-links/app-credentials-form.d.ts +0 -12
  527. package/dist/markdown/setup-links/app-credentials-form.js +0 -115
  528. /package/dist/{internal/hooks → hooks}/use-media-query.js +0 -0
@@ -36,6 +36,7 @@ import Loading from '../../primitives/loading.js';
36
36
  import { TextShimmer } from '../../primitives/text-shimmer.js';
37
37
  import { framePolicy } from '../viewer/preview-policy.js';
38
38
  import { useBinaryBlob } from '../runtime/hooks/use-binary-blob.js';
39
+ import { useSharedImage } from '../runtime/hooks/use-shared-image.js';
39
40
  import { sessionDriveMountPath } from '../../internal/files/session-drive-files.js';
40
41
  import { useFileContent } from '../runtime/hooks/use-file-content.js';
41
42
  import { useHeicBlob } from '../../internal/hooks/use-heic-url.js';
@@ -49,10 +50,14 @@ import { buildStaticFileLocalUrl, isSessionAttachmentRef } from '@kortix/sdk';
49
50
  import { WarningIcon as AlertTriangle, CaretLeftIcon as ChevronLeft, CaretRightIcon as ChevronRight, ArrowSquareOutIcon as ExternalLink, FileIcon, FileTextIcon as FileText, FileXIcon as FileWarning, GlobeIcon as Globe, MusicNotesIcon as Music, } from '@phosphor-icons/react';
50
51
  import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from 'react';
51
52
  import { ImageRenderer } from './image-renderer.js';
53
+ import { ImageFrame, ImageUnavailable } from './image-frame.js';
52
54
  import { MermaidDiagram } from './mermaid/mermaid-diagram.js';
53
55
  import { ViewerFrame } from './shared/viewer-frame.js';
54
56
  import { resolveShowType, shouldRenderFromSandboxFile } from './show-type-utils.js';
55
57
  import { VideoRenderer } from './video-renderer.js';
58
+ import { ShowConnectHero, showConnectTarget } from '../../markdown/setup-links/show-connect-hero.js';
59
+ import { ShowArtifactCard } from '../../artifacts/artifact-session.js';
60
+ import { parseArtifactUrl } from '../../artifacts/frame-policy.js';
56
61
  // ── Lazy-load heavy renderers ──────────────────────────────────────────────
57
62
  const PdfRenderer = lazy(() => import('./pdf/pdf-renderer.js').then((m) => ({ default: m.PdfRenderer })));
58
63
  const CsvRenderer = lazy(() => import('./csv/csv-renderer.js').then((m) => ({ default: m.CsvRenderer })));
@@ -84,6 +89,35 @@ export function showDomain(url) {
84
89
  return url;
85
90
  }
86
91
  }
92
+ const SHOW_LINK_COPY = { open: 'Open' };
93
+ /**
94
+ * The `url` show: one card, centered in the panel.
95
+ *
96
+ * A connect URL (a Kortix link or a provider's sign-in page) is not a page to
97
+ * visit: it renders the connect step, `ShowConnectHero`. Every other URL is a link card:
98
+ * favicon, title, domain, the agent's sentence, and an explicit Open action.
99
+ */
100
+ function ShowLink({ url, title, description }) {
101
+ const connect = showConnectTarget(url, title);
102
+ if (connect) {
103
+ return _jsx(ShowConnectHero, { url: url, target: connect, title: title, description: description });
104
+ }
105
+ // A Kortix artifact's link shows the artifact (a session page opens it in its panel,
106
+ // like an App link); a link the viewer cannot open stays a link.
107
+ if (parseArtifactUrl(url)) {
108
+ return _jsx(ShowArtifactCard, { url: url, fallback: _jsx(ShowLinkCard, { url: url, title: title, description: description }) });
109
+ }
110
+ return _jsx(ShowLinkCard, { url: url, title: title, description: description });
111
+ }
112
+ function ShowLinkCard({ url, title, description }) {
113
+ const copy = useLocalizedUiCatalog(SHOW_LINK_COPY);
114
+ const favicon = showFavicon(url);
115
+ const domain = showDomain(url);
116
+ const [faviconFailed, setFaviconFailed] = useState(false);
117
+ return (_jsxs("a", { href: url, target: "_blank", rel: "noopener noreferrer", "data-testid": "show-link-card", className: "group border-border bg-card hover:bg-hover flex w-full max-w-xl items-start gap-3 rounded-lg border p-4 transition-colors", children: [_jsx("span", { className: "bg-muted flex size-9 shrink-0 items-center justify-center overflow-hidden rounded-md", children: favicon && !faviconFailed ? (
118
+ /* eslint-disable-next-line @next/next/no-img-element */
119
+ _jsx("img", { src: favicon, alt: "", className: "size-5 rounded-sm", onError: () => setFaviconFailed(true) })) : (_jsx(Globe, { className: "text-muted-foreground size-4" })) }), _jsxs("span", { className: "min-w-0 flex-1", children: [_jsx("span", { className: "text-foreground block truncate text-sm font-medium", children: title || domain }), _jsx("span", { className: "text-muted-foreground block truncate text-xs", children: domain }), description ? (_jsx("span", { className: "text-muted-foreground mt-2 line-clamp-3 block text-sm text-pretty", children: description })) : null] }), _jsxs("span", { className: "text-muted-foreground group-hover:text-foreground flex shrink-0 items-center gap-1 text-xs font-medium transition-colors", children: [copy.open, _jsx(ExternalLink, { className: "size-3.5" })] })] }));
120
+ }
87
121
  export function showAspectRatioToCSS(ar) {
88
122
  if (!ar || ar === 'auto')
89
123
  return undefined;
@@ -138,8 +172,9 @@ function FileCard({ title, fileName, path }) {
138
172
  return (_jsxs("div", { className: "flex items-center gap-4 px-5 py-5", children: [_jsx("div", { className: "bg-muted/20 flex size-12 items-center justify-center rounded-xl", children: _jsx(FileText, { className: "text-muted-foreground/40 size-6" }) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-foreground truncate text-sm font-medium", children: title || fileName }), _jsx("div", { className: "text-muted-foreground/50 mt-0.5 truncate font-mono text-xs", children: path })] })] }));
139
173
  }
140
174
  // ── Component ──────────────────────────────────────────────────────────────
141
- export function ShowContentRenderer({ type, title = '', description = '', path = '', url = '', content = '', language = '', aspectRatio = '', attachment, LocalhostPreview, fill = false, onStatusChange, toolbarActions, }) {
175
+ export function ShowContentRenderer({ type, title = '', description = '', path = '', url = '', content = '', language = '', aspectRatio = '', attachment, sha256, LocalhostPreview, fill = false, onStatusChange, toolbarActions, }) {
142
176
  const tI18nComplete = useTranslations('hardcodedUi.i18nComplete');
177
+ const tImageGen = useTranslations('imageGen');
143
178
  const arCSS = showAspectRatioToCSS(aspectRatio);
144
179
  // ── Height presets — fill the panel vs. the compact inline card ──
145
180
  // `mediaH` → visual media (image/video/pdf/sheets/docs/generic file)
@@ -220,9 +255,16 @@ export function ShowContentRenderer({ type, title = '', description = '', path =
220
255
  // its sandbox read is a base64 text read, but a stored copy is bytes.
221
256
  const storedCopy = isSessionAttachmentRef(attachment) ? attachment : null;
222
257
  const blobFilePath = showBlobSource({ effectiveType, attachment, sandboxPath });
223
- const { blobUrl, blob: rawBlob, isLoading: blobLoading, error: blobError, } = useBinaryBlob(blobFilePath);
224
258
  // HEIC conversion — converts the raw blob to renderable JPEG
225
259
  const isHeic = isImage && isHeicFile(fileName);
260
+ // Every other image draws from the page's shared image store (R38): by its
261
+ // sha256 when another surface already loaded those bytes, else read once.
262
+ const sharedImage = useSharedImage(isImage && !isHeic ? blobFilePath : null, isImage && !isHeic ? sha256 : null);
263
+ const binary = useBinaryBlob(isImage && !isHeic ? null : blobFilePath);
264
+ const { blob: rawBlob } = binary;
265
+ const blobUrl = isImage && !isHeic ? sharedImage.url : binary.blobUrl;
266
+ const blobLoading = isImage && !isHeic ? sharedImage.loading : binary.isLoading;
267
+ const blobError = isImage && !isHeic ? sharedImage.error : binary.error;
226
268
  const { url: heicImageUrl, isConverting: heicConverting } = useHeicBlob(isHeic ? rawBlob : null, fileName);
227
269
  // PDF: base64 content via SDK, decoded by PdfRenderer into a Blob URL.
228
270
  const pdfLoadPath = isPdf && sandboxPath && !storedCopy ? sandboxPath : null;
@@ -352,13 +394,7 @@ export function ShowContentRenderer({ type, title = '', description = '', path =
352
394
  if (!safeExternalUrl) {
353
395
  return (_jsx("div", { className: "px-5 py-4", children: _jsxs("div", { className: "text-muted-foreground flex items-center gap-2 truncate font-mono text-xs", children: [_jsx(Globe, { className: "size-3.5 shrink-0" }), url] }) }));
354
396
  }
355
- const favicon = showFavicon(safeExternalUrl);
356
- const domain = showDomain(safeExternalUrl);
357
- return (_jsx("div", { className: cn('px-5 py-5', fill && 'flex h-full items-center'), children: _jsxs("a", { href: safeExternalUrl, target: "_blank", rel: "noopener noreferrer", className: "group border-border/30 bg-muted/5 hover:bg-muted/20 flex w-full items-center gap-4 rounded-2xl border p-4 transition-colors", children: [_jsx("div", { className: "bg-muted/30 flex size-10 shrink-0 items-center justify-center overflow-hidden rounded-lg", children: favicon ? (
358
- /* eslint-disable-next-line @next/next/no-img-element */
359
- _jsx("img", { src: favicon, alt: "", className: "size-6 rounded", onError: (e) => {
360
- e.target.style.display = 'none';
361
- } })) : (_jsx(Globe, { className: "text-muted-foreground/50 size-5" })) }), _jsxs("div", { className: "min-w-0 flex-1", children: [_jsx("div", { className: "text-foreground group-hover:text-primary truncate text-sm font-medium transition-colors", children: title || domain }), _jsx("div", { className: "text-muted-foreground/60 mt-0.5 truncate font-mono text-xs", children: domain }), description && (_jsx("div", { className: "text-muted-foreground mt-1 line-clamp-2 text-xs", children: description }))] }), _jsx(ExternalLink, { className: "text-muted-foreground/30 group-hover:text-muted-foreground/60 size-4 shrink-0 transition-colors" })] }) }));
397
+ return (_jsx("div", { className: cn('flex justify-center px-5 py-5', fill && 'h-full items-center'), children: _jsx(ShowLink, { url: safeExternalUrl, title: title, description: description }) }));
362
398
  }
363
399
  // ═════════════════════════════════════════════════════════════════════════
364
400
  // Image — loaded via useBinaryBlob → blobUrl → ImageRenderer
@@ -369,10 +405,16 @@ export function ShowContentRenderer({ type, title = '', description = '', path =
369
405
  // it an image had no way to open in the side panel.
370
406
  // ═════════════════════════════════════════════════════════════════════════
371
407
  if (isImage && path) {
372
- if (blobLoading || heicConverting)
373
- return _jsx(RendererFallback, { className: mediaH });
374
- if (blobError)
375
- return _jsx(LoadError, { message: blobError });
408
+ // The same frame the image_gen card draws while its image is on the way,
409
+ // inside the same header the loaded image gets, so nothing grows on load.
410
+ // Never a bare path.
411
+ if (blobLoading || heicConverting) {
412
+ return framed(_jsx("div", { className: mediaH, children: _jsx(ImageFrame, { aspectRatio: "auto", src: null, alt: title || fileName, pending: true, label: _jsxs("span", { className: "text-muted-foreground flex items-center gap-2 text-xs", children: [_jsx(Loading, { className: "size-4 shrink-0" }), tImageGen('loading')] }), className: "h-full rounded-none" }) }));
413
+ }
414
+ // Never the read's raw error text on an image (R38): a short note and Try again.
415
+ if (blobError) {
416
+ return framed(_jsx("div", { className: mediaH, children: _jsx(ImageFrame, { aspectRatio: "auto", src: null, alt: title || fileName, pending: false, label: _jsx(ImageUnavailable, { onRetry: sharedImage.retry }), className: "h-full rounded-none" }) }));
417
+ }
376
418
  // HEIC: use the converted JPEG URL
377
419
  const imageUrl = isHeic ? heicImageUrl : blobUrl;
378
420
  if (imageUrl) {
@@ -695,7 +737,7 @@ export function ShowCarousel({ items, LocalhostPreview, onIndexChange, fill = fa
695
737
  return (_jsxs("div", { role: "group", className: cn('outline-none', fill && 'flex h-full flex-col'),
696
738
  // -1: a click on the content focuses the card, so ←/→ page it next,
697
739
  // without adding a Tab stop. Tab still reaches the chevrons inside.
698
- tabIndex: fill || count < 2 ? undefined : -1, onKeyDown: fill || count < 2 ? undefined : (e) => handleArrowKey(e), children: [_jsx("div", { className: cn(fill ? 'min-h-0 flex-1 overflow-hidden' : 'min-h-[420px]'), children: currentItem.status === 'pending' ? (_jsxs("div", { className: cn('flex items-center justify-center gap-3', fill ? 'h-full' : 'min-h-[420px]'), children: [_jsx(Loading, { className: "text-muted-foreground size-4" }), _jsx(TextShimmer, { duration: 1, spread: 2, className: "text-sm", children: tHardcodedUi.raw('componentsSessionToolRenderers.line4935JsxTextPreparingOutput') })] })) : (_jsx(ShowContentRenderer, { type: currentItem.type, title: currentItem.title, description: currentItem.description, path: currentItem.path, url: currentItem.url, content: currentItem.content, language: currentItem.language, aspectRatio: currentItem.aspect_ratio, attachment: currentItem.attachment, LocalhostPreview: LocalhostPreview, toolbarActions: toolbarActions, fill: fill })) }), count > 1 && !hideNav && (_jsxs("div", { className: "border-border flex shrink-0 items-center gap-2 border-t px-2 py-1.5 pr-3.5", children: [_jsxs("div", { className: "flex shrink-0 items-center", children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: prev, className: "hit-area-2 hit-area-r-0 transition-transform active:scale-[0.96]", disabled: currentIndex === 0, "aria-label": tI18nComplete.raw('text81b35f1b4332'), children: _jsx(ChevronLeft, { className: "size-4" }) }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: next, className: "hit-area-2 hit-area-l-0 transition-transform active:scale-[0.96]", disabled: currentIndex >= count - 1, "aria-label": tI18nComplete.raw('text1e47d4f7a1a3'), children: _jsx(ChevronRight, { className: "size-4" }) })] }), _jsx(FadedScrollArea, { ref: stripRef, orientation: "horizontal", fadeColor: "from-secondary", className: "min-w-0 flex-1 overscroll-x-contain", children: _jsx("div", { className: "flex w-max min-w-0 items-center gap-1", children: items.map((item, i) => {
740
+ tabIndex: fill || count < 2 ? undefined : -1, onKeyDown: fill || count < 2 ? undefined : (e) => handleArrowKey(e), children: [_jsx("div", { className: cn(fill ? 'min-h-0 flex-1 overflow-hidden' : 'min-h-[420px]'), children: currentItem.status === 'pending' ? (_jsxs("div", { className: cn('flex items-center justify-center gap-3', fill ? 'h-full' : 'min-h-[420px]'), children: [_jsx(Loading, { className: "text-muted-foreground size-4" }), _jsx(TextShimmer, { duration: 1, spread: 2, className: "text-sm", children: tHardcodedUi.raw('componentsSessionToolRenderers.line4935JsxTextPreparingOutput') })] })) : (_jsx(ShowContentRenderer, { type: currentItem.type, title: currentItem.title, description: currentItem.description, path: currentItem.path, url: currentItem.url, content: currentItem.content, language: currentItem.language, aspectRatio: currentItem.aspect_ratio, attachment: currentItem.attachment, sha256: currentItem.sha256, LocalhostPreview: LocalhostPreview, toolbarActions: toolbarActions, fill: fill })) }), count > 1 && !hideNav && (_jsxs("div", { className: "border-border flex shrink-0 items-center gap-2 border-t px-2 py-1.5 pr-3.5", children: [_jsxs("div", { className: "flex shrink-0 items-center", children: [_jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: prev, className: "hit-area-2 hit-area-r-0 transition-transform active:scale-[0.96]", disabled: currentIndex === 0, "aria-label": tI18nComplete.raw('text81b35f1b4332'), children: _jsx(ChevronLeft, { className: "size-4" }) }), _jsx(Button, { type: "button", variant: "ghost", size: "icon", onClick: next, className: "hit-area-2 hit-area-l-0 transition-transform active:scale-[0.96]", disabled: currentIndex >= count - 1, "aria-label": tI18nComplete.raw('text1e47d4f7a1a3'), children: _jsx(ChevronRight, { className: "size-4" }) })] }), _jsx(FadedScrollArea, { ref: stripRef, orientation: "horizontal", fadeColor: "from-secondary", className: "min-w-0 flex-1 overscroll-x-contain", children: _jsx("div", { className: "flex w-max min-w-0 items-center gap-1", children: items.map((item, i) => {
699
741
  const label = labels[i] ?? 'Item';
700
742
  const isPortLabel = label.startsWith(':');
701
743
  return (_jsx("button", { ref: (el) => {
@@ -3,6 +3,7 @@ import { downloadFile, uploadFile } from '../../internal/files/runtime-files.js'
3
3
  import { useFileContent } from './hooks/index.js';
4
4
  import { useBinaryBlob } from './hooks/use-binary-blob.js';
5
5
  import { useFileRefresh } from './hooks/use-file-refresh.js';
6
+ import { useViewerImage } from './hooks/use-shared-image.js';
6
7
  // Do not import this component through the project-files barrel. The barrel
7
8
  // reaches useGitStatus through useChangeRequests and re-enters packages/ui/src/files/runtime.
8
9
  // Webpack cannot evaluate that async cycle while building this module constant.
@@ -19,4 +20,5 @@ export const workspaceFileSource = {
19
20
  upload: uploadFile,
20
21
  Breadcrumbs: FilePathBreadcrumbs,
21
22
  useRefresh: useFileRefresh,
23
+ useSharedImage: useViewerImage,
22
24
  };
@@ -21,9 +21,18 @@ export { binaryBlobKeys };
21
21
  * renders its bytes instead of waiting on a box that is not coming. A stored
22
22
  * copy is immutable, so it is never refetched and never polled.
23
23
  */
24
- export declare function useBinaryBlob(filePath: string | null): {
24
+ export declare function useBinaryBlob(filePath: string | null, options?: {
25
+ /**
26
+ * An image tile's wait (R38): a runtime still starting is polled with a
27
+ * backoff and given up after about 2 min (`imageWakingRefetchInterval`),
28
+ * instead of the viewer's open-ended waking poll.
29
+ */
30
+ imageWait?: boolean;
31
+ }): {
25
32
  blobUrl: string | null;
26
33
  blob: Blob | null;
27
34
  isLoading: boolean;
28
35
  error: string | null;
36
+ /** The image wait gave up (`options.imageWait`): show it unavailable, with Try again. */
37
+ waitedOut: boolean;
29
38
  };
@@ -6,7 +6,7 @@ import { useEffect, useMemo, useState } from 'react';
6
6
  import { readRuntimeFileWithRetry } from '../../../internal/files/runtime-file-read.js';
7
7
  import { readFileAsBlob } from '../../../internal/files/runtime-files.js';
8
8
  import { keepBlobIfUnchanged } from './blob-identity.js';
9
- import { sandboxWakingRefetchInterval } from '../../../internal/files/file-read-retry.js';
9
+ import { imageWaitedOut, imageWakingRefetchInterval, sandboxWakingRefetchInterval, } from '../../../internal/files/file-read-retry.js';
10
10
  import { useServerHealth } from './use-server-health.js';
11
11
  // ── Query keys ─────────────────────────────────────────────────────────────
12
12
  // Keyed by the SDK factory: the live event stream invalidates it on
@@ -34,7 +34,8 @@ export { binaryBlobKeys };
34
34
  * renders its bytes instead of waiting on a box that is not coming. A stored
35
35
  * copy is immutable, so it is never refetched and never polled.
36
36
  */
37
- export function useBinaryBlob(filePath) {
37
+ export function useBinaryBlob(filePath, options) {
38
+ const imageWait = !!options?.imageWait;
38
39
  const serverUrl = useRuntimeStore((s) => s.getActiveServerUrl());
39
40
  // Asleep, not booting — the re-read below takes the slow lane.
40
41
  const { parked } = useServerHealth();
@@ -72,7 +73,11 @@ export function useBinaryBlob(filePath) {
72
73
  // A readiness 503 is a pending state, not a failure. A booting box earns the
73
74
  // fast cadence; a parked one is watched slowly. See the helper. A stored
74
75
  // copy has no box to wait for.
75
- refetchInterval: (query) => stored ? false : sandboxWakingRefetchInterval(query.state.error, parked),
76
+ refetchInterval: (query) => stored
77
+ ? false
78
+ : imageWait
79
+ ? imageWakingRefetchInterval(query.state.error, query.state.errorUpdateCount)
80
+ : sandboxWakingRefetchInterval(query.state.error, parked),
76
81
  });
77
82
  const cachedBlob = query.data ?? null;
78
83
  // ── Blob URL — derived per-mount, revoked on unmount/change ─────────
@@ -98,5 +103,6 @@ export function useBinaryBlob(filePath) {
98
103
  blob: cachedBlob,
99
104
  isLoading: query.isLoading,
100
105
  error: query.error?.message ?? null,
101
- }), [blobUrl, cachedBlob, query.isLoading, query.error?.message]);
106
+ waitedOut: imageWait && imageWaitedOut(query.error, query.errorUpdateCount),
107
+ }), [blobUrl, cachedBlob, query.isLoading, query.error, query.errorUpdateCount, imageWait]);
102
108
  }
@@ -0,0 +1,41 @@
1
+ /**
2
+ * Drops every shared image: the digests, and the URL of every image no surface
3
+ * draws (the rest go when their surface unmounts). Runs on a sign-out and an
4
+ * account switch.
5
+ */
6
+ export declare function clearSharedImages(): void;
7
+ export interface SharedImage {
8
+ /** The image's object URL; null until its bytes are in. */
9
+ url: string | null;
10
+ /** `"<w> / <h>"` once any surface measured the image. */
11
+ naturalRatio: string | null;
12
+ /**
13
+ * The bytes are on the way: a read in flight, or the session runtime still
14
+ * starting (its URL not pinned yet, a waking 503), polled with a backoff for
15
+ * about 2 min.
16
+ */
17
+ loading: boolean;
18
+ /** A genuine read failure, or a wait that ran out. A host shows "Image unavailable" and `retry`, never this text. */
19
+ error: string | null;
20
+ /** Reads the file again, from a fresh wait. */
21
+ retry: () => void;
22
+ /** Records the image's natural size for the next surface that draws it. */
23
+ onNaturalSize: (width: number, height: number) => void;
24
+ }
25
+ /**
26
+ * The image at `path` (a sandbox path or a stored attachment ref), or the
27
+ * bytes with digest `sha256`. A digest already in `byDigest` draws at once and
28
+ * reads nothing. Otherwise the file is read through its one shared query, and
29
+ * bytes whose digest matches `sha256` join `byDigest`.
30
+ */
31
+ export declare function useSharedImage(path: string | null, sha256?: string | null, options?: {
32
+ /**
33
+ * Keep the path's query mounted under a digest hit: the digest's bytes draw
34
+ * at once, and the file's own bytes replace them once read and found
35
+ * different. The viewer passes it, so its Refresh and `file.edited` always
36
+ * refetch the file, also when a card's caption named a digest for the path.
37
+ */
38
+ revalidate?: boolean;
39
+ }): SharedImage;
40
+ /** The full-screen viewer's image (`FileSource.useSharedImage`): a digest draws at once, the file is always read too. */
41
+ export declare function useViewerImage(path: string | null, sha256?: string | null): SharedImage;
@@ -0,0 +1,177 @@
1
+ 'use client';
2
+ import { createImageCache, isRuntimeStartingError, isSessionAttachmentRef } from '@kortix/sdk';
3
+ import { binaryBlobKeys, useRuntimeStore } from '@kortix/sdk/react';
4
+ import { useQueryClient } from '@tanstack/react-query';
5
+ import { useCallback, useEffect, useMemo, useState, useSyncExternalStore } from 'react';
6
+ import { useCurrentAccountStore } from '../../../internal/stores/current-account-store.js';
7
+ import { useBinaryBlob } from './use-binary-blob.js';
8
+ /**
9
+ * The page's shared images (R38). Two stores, one per key:
10
+ *
11
+ * - **By path: React Query's blob cache** (`useBinaryBlob`, `binaryBlobKeys`).
12
+ * The image card, the full-screen viewer, a `show` card and a Files preview
13
+ * read ONE query per sandbox file. Every invalidation that already exists
14
+ * reaches them all: `file.edited`, the turn end, the show card's Refresh and
15
+ * the viewer's Refresh refetch it, and a file rewritten in place draws its
16
+ * new bytes everywhere (`keepBlobIfUnchanged` keeps unchanged bytes the same
17
+ * Blob, so nothing reloads for them).
18
+ * - **By sha256: `byDigest`.** Bytes whose digest the tool output named, after
19
+ * the digest is checked against the bytes. An image already loaded under one
20
+ * path draws at once under another path with the same bytes (a Drive copy),
21
+ * with no read. Content-addressed and verified, so it never goes stale.
22
+ * Evicted least recently used past 64 images or 256 MiB.
23
+ *
24
+ * One object URL per Blob, shared by every surface that draws it, so a second
25
+ * surface never decodes the image again. It is revoked about 1 s after the last
26
+ * surface lets it go.
27
+ *
28
+ * Bound: an image on screen is never evicted. Its Blob stays while a surface
29
+ * draws it, so memory is the images mounted at once plus `byDigest`'s 256 MiB.
30
+ * This is the same as before R38, when each mount held its own blob URL.
31
+ */
32
+ const byDigest = createImageCache({ maxEntries: 64, maxBytes: 256 * 1024 * 1024 });
33
+ // One object URL per Blob. Revoked a moment after its last release, so a
34
+ // StrictMode remount (release, then acquire in the same tick) and a render that
35
+ // never commits keep a live URL instead of a revoked one.
36
+ const urls = new Map();
37
+ const naturalRatios = new WeakMap();
38
+ const REVOKE_GRACE_MS = 1000;
39
+ function urlFor(blob) {
40
+ let held = urls.get(blob);
41
+ if (!held) {
42
+ held = { url: URL.createObjectURL(blob), refs: 0 };
43
+ urls.set(blob, held);
44
+ scheduleRevoke(blob);
45
+ }
46
+ return held.url;
47
+ }
48
+ function scheduleRevoke(blob) {
49
+ setTimeout(() => {
50
+ const held = urls.get(blob);
51
+ if (!held || held.refs > 0)
52
+ return;
53
+ URL.revokeObjectURL(held.url);
54
+ urls.delete(blob);
55
+ }, REVOKE_GRACE_MS);
56
+ }
57
+ /**
58
+ * Drops every shared image: the digests, and the URL of every image no surface
59
+ * draws (the rest go when their surface unmounts). Runs on a sign-out and an
60
+ * account switch.
61
+ */
62
+ export function clearSharedImages() {
63
+ byDigest.clear();
64
+ for (const [blob, held] of urls) {
65
+ if (held.refs > 0)
66
+ continue;
67
+ URL.revokeObjectURL(held.url);
68
+ urls.delete(blob);
69
+ }
70
+ }
71
+ // A sign-out clears the selected account; an account switch changes it.
72
+ useCurrentAccountStore.subscribe((state, previous) => {
73
+ if (state.selectedAccountId !== previous.selectedAccountId)
74
+ clearSharedImages();
75
+ });
76
+ /** The sha256 of each Blob read and hashed. */
77
+ const blobDigests = new WeakMap();
78
+ /** The bytes a digest disagreed with, read again once each (never a loop). */
79
+ const rereadOnce = new WeakSet();
80
+ /** `useBinaryBlob`'s query key for `path`. */
81
+ function blobKey(serverUrl, path) {
82
+ return isSessionAttachmentRef(path) ? ['session-attachment', 'blob', path] : binaryBlobKeys.file(serverUrl, path);
83
+ }
84
+ /** The lowercase hex sha256 of `blob`, or null when it cannot be computed (no Web Crypto, over 32 MiB). */
85
+ async function digestOf(blob) {
86
+ if (blob.size > 32 * 1024 * 1024 || !globalThis.crypto?.subtle)
87
+ return null;
88
+ const hash = await globalThis.crypto.subtle.digest('SHA-256', await blob.arrayBuffer());
89
+ return [...new Uint8Array(hash)].map((byte) => byte.toString(16).padStart(2, '0')).join('');
90
+ }
91
+ /**
92
+ * The image at `path` (a sandbox path or a stored attachment ref), or the
93
+ * bytes with digest `sha256`. A digest already in `byDigest` draws at once and
94
+ * reads nothing. Otherwise the file is read through its one shared query, and
95
+ * bytes whose digest matches `sha256` join `byDigest`.
96
+ */
97
+ export function useSharedImage(path, sha256, options) {
98
+ const revalidate = !!options?.revalidate;
99
+ const serverUrl = useRuntimeStore((s) => s.getActiveServerUrl());
100
+ const read = useCallback(() => (sha256 ? byDigest.get({ sha256 })?.value : undefined), [sha256]);
101
+ const pinned = useSyncExternalStore(byDigest.subscribe, read, read);
102
+ const file = useBinaryBlob(path && (!pinned || revalidate) ? path : null, { imageWait: true });
103
+ const queryClient = useQueryClient();
104
+ // Re-renders once a read's digest is known.
105
+ const [, setDigested] = useState(0);
106
+ const readDigest = file.blob ? blobDigests.get(file.blob) : undefined;
107
+ // The file's own bytes win once known to differ from the pinned digest.
108
+ const blob = revalidate && file.blob && readDigest !== undefined && readDigest !== sha256 ? file.blob : (pinned ?? file.blob);
109
+ // Trust a digest only once the bytes match it: the file may have changed
110
+ // after the tool hashed it, and a wrong entry would draw under every later
111
+ // lookup. Cached bytes that do not match are read again once: the output
112
+ // names newer bytes than the cache holds (the file was rewritten).
113
+ useEffect(() => {
114
+ const read = file.blob;
115
+ if (!read || !sha256 || !path)
116
+ return;
117
+ let live = true;
118
+ void digestOf(read).then((digest) => {
119
+ if (!live || digest === null)
120
+ return;
121
+ blobDigests.set(read, digest);
122
+ setDigested((n) => n + 1);
123
+ if (digest === sha256)
124
+ byDigest.put({ sha256 }, read, read.size);
125
+ else if (!revalidate && !rereadOnce.has(read)) {
126
+ rereadOnce.add(read);
127
+ void queryClient.invalidateQueries({ queryKey: blobKey(serverUrl, path) });
128
+ }
129
+ });
130
+ return () => {
131
+ live = false;
132
+ };
133
+ }, [file.blob, sha256, path, queryClient, serverUrl, revalidate]);
134
+ // An image on screen is never evicted from `byDigest`: held while drawn.
135
+ useEffect(() => {
136
+ const entry = sha256 && pinned ? byDigest.get({ sha256 }) : undefined;
137
+ return entry ? byDigest.retain(entry) : undefined;
138
+ }, [sha256, pinned]);
139
+ // Held while drawn, so the URL is never revoked under an <img>.
140
+ useEffect(() => {
141
+ if (!blob)
142
+ return;
143
+ const held = urls.get(blob);
144
+ if (!held)
145
+ return;
146
+ held.refs++;
147
+ return () => {
148
+ held.refs--;
149
+ if (held.refs === 0)
150
+ scheduleRevoke(blob);
151
+ };
152
+ }, [blob]);
153
+ const starting = !!file.error && isRuntimeStartingError(file.error) && !file.waitedOut;
154
+ const retry = useCallback(() => {
155
+ if (!path)
156
+ return;
157
+ // A reset, not a refetch: the wait's failure count starts over.
158
+ void queryClient.resetQueries({ queryKey: blobKey(serverUrl, path) });
159
+ }, [path, queryClient, serverUrl]);
160
+ const onNaturalSize = useCallback((width, height) => {
161
+ if (blob)
162
+ naturalRatios.set(blob, `${width} / ${height}`);
163
+ }, [blob]);
164
+ return useMemo(() => ({
165
+ // The read's own URL covers a render with no Blob in hand (a server render).
166
+ url: blob ? urlFor(blob) : file.blobUrl,
167
+ naturalRatio: blob ? (naturalRatios.get(blob) ?? null) : null,
168
+ loading: !blob && !!path && (file.isLoading || starting),
169
+ error: blob || starting ? null : file.waitedOut ? 'unavailable' : file.error,
170
+ retry,
171
+ onNaturalSize,
172
+ }), [blob, file.blobUrl, file.isLoading, file.error, file.waitedOut, starting, path, retry, onNaturalSize]);
173
+ }
174
+ /** The full-screen viewer's image (`FileSource.useSharedImage`): a digest draws at once, the file is always read too. */
175
+ export function useViewerImage(path, sha256) {
176
+ return useSharedImage(path, sha256, { revalidate: true });
177
+ }
@@ -1,9 +1,23 @@
1
1
  import React from 'react';
2
+ import { type Variant } from 'motion/react';
3
+ /**
4
+ * The caption's disclosure motion (kortix-brand motion.md: a disclosure is
5
+ * `duration-moderate`, ease-out; the exit runs faster, at `duration-normal`).
6
+ * Height and opacity together, so the image above shrinks and grows with it.
7
+ * Reduced motion: no transition, the caption is simply there or not.
8
+ */
9
+ export declare function captionMotion(reduceMotion: boolean): {
10
+ expanded: Variant;
11
+ collapsed: Variant;
12
+ };
2
13
  export interface FileContentRendererProps {
3
14
  /** Path to the file to render */
4
15
  filePath: string;
5
16
  /** Whether to show the compact header bar with file name, toggles, save/download buttons */
6
17
  showHeader?: boolean;
18
+ /** Render the header's edit controls into this host toolbar instead of a
19
+ * row of their own (`FilePreviewModal`). The host owns the path and Download. */
20
+ headerSlot?: HTMLElement | null;
7
21
  /** Additional header actions (rendered after built-in buttons) */
8
22
  headerActions?: React.ReactNode;
9
23
  /** Callback when unsaved state changes */
@@ -44,5 +58,12 @@ export interface FileContentRendererProps {
44
58
  * their own bytes (xlsx, sqlite, the HTML frame), which a cache refetch
45
59
  * cannot reach. */
46
60
  reloadKey?: number;
61
+ /**
62
+ * A full-screen viewer (the preview modal, the panel's file preview) shows
63
+ * an image's caption under it when it was opened with one (a generated
64
+ * image's prompt and model). Off for every embedded render: a Files grid
65
+ * tile, a `show` card.
66
+ */
67
+ showCaption?: boolean;
47
68
  }
48
- export declare function FileContentRenderer({ filePath, showHeader, headerActions, onUnsavedChange, onSaved, className, errorFallback, targetLine, readOnly, markdownPreview, onMarkdownPreviewChange, onStatusChange, imageControls, fitOnOpen, findText, codeEditorEditorClassName, reloadKey, }: FileContentRendererProps): React.JSX.Element;
69
+ export declare function FileContentRenderer({ filePath, showHeader, headerSlot, headerActions, onUnsavedChange, onSaved, className, errorFallback, targetLine, readOnly, markdownPreview, onMarkdownPreviewChange, onStatusChange, imageControls, fitOnOpen, findText, codeEditorEditorClassName, reloadKey, showCaption, }: FileContentRendererProps): React.JSX.Element;
@@ -1,5 +1,5 @@
1
1
  'use client';
2
- import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "react/jsx-runtime";
3
3
  import { useTranslations } from '../../i18n/index.js';
4
4
  import { ErrorBoundary } from '../../primitives/error-boundary.js';
5
5
  import { CodeEditor } from '../editor/lazy-code-editor.js';
@@ -7,16 +7,20 @@ import { MarkdownWithFrontmatter } from '../../markdown/markdown-frontmatter.js'
7
7
  import { Button } from '../../primitives/button.js';
8
8
  import { InfoBanner } from '../../primitives/info-banner.js';
9
9
  import Loading from '../../primitives/loading.js';
10
+ import { ImageUnavailable } from '../renderers/image-frame.js';
10
11
  import { MermaidDiagram } from '../renderers/mermaid/mermaid-diagram.js';
11
12
  import { cn } from '../../lib/utils.js';
12
13
  import { toSandboxAbsolutePath } from '@kortix/sdk';
13
14
  import { GitDiffIcon as FileDiff, FileXIcon as FileWarning, FileXIcon as FileX, } from '@phosphor-icons/react';
14
- import { lazy, Suspense, useCallback } from 'react';
15
+ import { lazy, Suspense, useCallback, useMemo, useState } from 'react';
16
+ import { useReducedMotion } from 'motion/react';
17
+ import { Disclosure, DisclosureContent } from '../../primitives/disclosure.js';
15
18
  // Direct module import, not the feature barrel: the barrel re-exports THIS file.
16
19
  import { HtmlPreview } from './html-preview.js';
17
20
  import { JsonTreeView } from './json-tree-view.js';
18
21
  import { FileHeader } from './file-header.js';
19
22
  import { useFileContentState } from './use-file-content-state.js';
23
+ import { useFileCaption } from '../../internal/stores/file-preview-store.js';
20
24
  import { Download } from '../../internal/icons/download.js';
21
25
  // ---------------------------------------------------------------------------
22
26
  // Lazy-load heavy renderers to keep initial bundle small
@@ -39,13 +43,42 @@ const ZipRenderer = lazy(() => import('../renderers/zip/zip-renderer.js').then((
39
43
  function RendererFallback() {
40
44
  return (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsx(Loading, { className: "text-muted-foreground/40 h-4 w-4" }) }));
41
45
  }
46
+ /** kortix-brand `motion.easing.out`: enter and exit. */
47
+ const EASE_OUT = [0, 0, 0.2, 1];
48
+ /**
49
+ * The caption's disclosure motion (kortix-brand motion.md: a disclosure is
50
+ * `duration-moderate`, ease-out; the exit runs faster, at `duration-normal`).
51
+ * Height and opacity together, so the image above shrinks and grows with it.
52
+ * Reduced motion: no transition, the caption is simply there or not.
53
+ */
54
+ export function captionMotion(reduceMotion) {
55
+ const enter = reduceMotion ? { duration: 0 } : { duration: 0.2, ease: EASE_OUT };
56
+ const exit = reduceMotion ? { duration: 0 } : { duration: 0.15, ease: EASE_OUT };
57
+ return {
58
+ expanded: { height: 'auto', opacity: 1, transition: enter },
59
+ collapsed: { height: 0, opacity: 0, transition: exit },
60
+ };
61
+ }
62
+ /**
63
+ * An image with its caption under it: a generated image's prompt and the model
64
+ * that made it, in a disclosure the viewer's details toggle (ⓘ, the trigger on
65
+ * the zoom shelf, `ImageRenderer` `info`) opens. Only an image opened with a
66
+ * caption has one; any other file renders exactly as without it.
67
+ */
68
+ function CaptionedImage({ caption, open, onOpenChange, children, }) {
69
+ const reduceMotion = !!useReducedMotion();
70
+ const variants = useMemo(() => captionMotion(reduceMotion), [reduceMotion]);
71
+ if (!caption)
72
+ return _jsx(_Fragment, { children: children });
73
+ return (_jsxs(Disclosure, { open: open, onOpenChange: onOpenChange, variants: variants, className: "flex h-full min-h-0 flex-col", children: [_jsx("div", { className: "min-h-0 flex-1", children: children }), _jsx(DisclosureContent, { className: "shrink-0", children: _jsxs("div", { "data-file-caption": "", className: "border-border max-h-32 overflow-y-auto border-t px-4 py-3", children: [_jsx("p", { className: "text-foreground text-sm text-pretty", children: caption.text }), caption.model ? _jsx("p", { className: "text-muted-foreground mt-1 font-mono text-xs", children: caption.model }) : null] }) })] }));
74
+ }
42
75
  /** Shared "file does not exist" UI shown when a file cannot be loaded. */
43
76
  function FileNotFoundState({ filePath }) {
44
77
  const tHardcodedUi = useTranslations('hardcodedUi');
45
78
  return (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx("div", { className: "bg-muted/50 flex h-12 w-12 items-center justify-center rounded-sm", children: _jsx(FileX, { className: "text-muted-foreground/40 h-6 w-6" }) }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line195JsxTextFileNotFound') }), _jsx("p", { className: "text-muted-foreground/50 max-w-sm font-mono text-xs break-all", children: filePath }), _jsx("p", { className: "text-muted-foreground/40 max-w-xs text-xs", children: tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line201JsxTextThisFileDoesNotExistOrMayHave') })] }));
46
79
  }
47
- export function FileContentRenderer({ filePath, showHeader = true, headerActions, onUnsavedChange, onSaved, className, errorFallback, targetLine, readOnly = false, markdownPreview, onMarkdownPreviewChange, onStatusChange, imageControls, fitOnOpen = false, findText, codeEditorEditorClassName, reloadKey = 0, }) {
48
- const { source, Breadcrumbs, fileContent, isLoading, error, contentRevision, fetchRevision, hasUnsavedChanges, setHasUnsavedChanges, isSaving, saveFlash, latestContentRef, discardKey, fileName, isHeicImage, language, fileCategory, isMarkdownFile, isMermaidFile, hasPreviewToggle, isJsonFile, isHtmlFile, isMarkdownPreview, setIsMarkdownPreview, isJsonTreeView, setIsJsonTreeView, isHtmlPreview, setIsHtmlPreview, fileDiagErrorCount, fileDiagWarningCount, fileDiagnostics, tHardcodedUi, tI18nHardcoded, blobUrl, rawBlob, blobLoading, blobError, heicImageUrl, heicConverting, displayContent, handleDownload, handleSave, handleDiscard, handleEditorChange, imageDataUrl, needsBlob, isContentReady, contentError, showLoadingState, isSandboxWaking, isNotFound, } = useFileContentState({
80
+ export function FileContentRenderer({ filePath, showHeader = true, headerSlot, headerActions, onUnsavedChange, onSaved, className, errorFallback, targetLine, readOnly = false, markdownPreview, onMarkdownPreviewChange, onStatusChange, imageControls, fitOnOpen = false, findText, codeEditorEditorClassName, reloadKey = 0, showCaption = false, }) {
81
+ const { source, Breadcrumbs, fileContent, isLoading, error, contentRevision, fetchRevision, hasUnsavedChanges, setHasUnsavedChanges, isSaving, saveFlash, latestContentRef, discardKey, fileName, isHeicImage, language, fileCategory, isMarkdownFile, isMermaidFile, hasPreviewToggle, isJsonFile, isHtmlFile, isMarkdownPreview, setIsMarkdownPreview, isJsonTreeView, setIsJsonTreeView, isHtmlPreview, setIsHtmlPreview, fileDiagErrorCount, fileDiagWarningCount, fileDiagnostics, tHardcodedUi, tI18nHardcoded, blobUrl, rawBlob, blobLoading, blobError, sharedImagePath, retrySharedImage, heicImageUrl, heicConverting, displayContent, handleDownload, handleSave, handleDiscard, handleEditorChange, imageDataUrl, needsBlob, isContentReady, contentError, showLoadingState, isSandboxWaking, isNotFound, } = useFileContentState({
49
82
  filePath,
50
83
  onUnsavedChange,
51
84
  onSaved,
@@ -55,6 +88,14 @@ export function FileContentRenderer({ filePath, showHeader = true, headerActions
55
88
  onStatusChange,
56
89
  targetLine,
57
90
  });
91
+ const storedCaption = useFileCaption(filePath);
92
+ const caption = showCaption ? storedCaption : undefined;
93
+ // The caption is hidden until the details toggle opens it; per file, so the next image starts closed.
94
+ const tImageGen = useTranslations('imageGen');
95
+ const [captionOpenFor, setCaptionOpenFor] = useState(null);
96
+ const captionOpen = captionOpenFor === filePath;
97
+ const onCaptionOpenChange = (open) => setCaptionOpenFor(open ? filePath : null);
98
+ const imageInfo = caption ? { pressed: captionOpen, label: tImageGen('details') } : undefined;
58
99
  const bytesOnly = !!source.bytesOnly;
59
100
  // The table has no lines: a jump to a line (a content-search match) opens
60
101
  // CSV and TSV in the code editor.
@@ -86,10 +127,12 @@ export function FileContentRenderer({ filePath, showHeader = true, headerActions
86
127
  targetLine,
87
128
  };
88
129
  const discardLabel = tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line600JsxAttrTitleDiscardChanges');
89
- return (_jsxs("div", { className: cn('flex h-full flex-col', className), children: [_jsx(FileHeader, { showHeader, Breadcrumbs, filePath, readOnly, hasUnsavedChanges, saveFlash, fileDiagErrorCount, fileDiagWarningCount, fileContent, isSaving, handleSave, latestContentRef, discardLabel, handleDiscard, isHtmlFile, isHtmlPreview, setIsHtmlPreview, isJsonFile, isJsonTreeView, setIsJsonTreeView, hasPreviewToggle, isMarkdownPreview, setIsMarkdownPreview, headerActions, handleDownload, blobUrl, rawBlob, tI18nHardcoded, tHardcodedUi }), _jsx("div", { className: cn('flex-1', readOnly ? 'overflow-auto' : 'overflow-hidden'), children: _jsxs(ErrorBoundary, { fallback: () => (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx("div", { className: "bg-destructive/10 flex h-12 w-12 items-center justify-center rounded-sm", children: _jsx(FileWarning, { className: "text-destructive/50 h-6 w-6" }) }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: tI18nHardcoded.raw('autoFeaturesFileViewerFileContentRendererJsxTextCouldnT794ae800') }), _jsx("p", { className: "text-muted-foreground/50 max-w-sm font-mono text-xs break-all", children: filePath }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleDownload, children: [_jsx(Download, { className: "mr-1.5 h-3.5 w-3.5" }), tI18nHardcoded.raw('i18nComplete.textd6eafe823591')] })] })), children: [showLoadingState && (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsx(Loading, { className: "text-muted-foreground/40 h-4 w-4" }) })), contentError && !showLoadingState && isSandboxWaking && (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx(Loading, { className: "text-muted-foreground/40 h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: tI18nHardcoded.raw('i18nComplete.text5e3de76869f3') }), _jsx("p", { className: "text-muted-foreground/50 max-w-sm font-mono text-xs break-all", children: filePath }), _jsx("p", { className: "text-muted-foreground/40 max-w-xs text-xs", children: tI18nHardcoded.raw('i18nComplete.textd4707c58c4a4') })] })), contentError &&
130
+ return (_jsxs("div", { className: cn('flex h-full flex-col', className), children: [_jsx(FileHeader, { showHeader, headerSlot, Breadcrumbs, filePath, readOnly, hasUnsavedChanges, saveFlash, fileDiagErrorCount, fileDiagWarningCount, fileContent, isSaving, handleSave, latestContentRef, discardLabel, handleDiscard, isHtmlFile, isHtmlPreview, setIsHtmlPreview, isJsonFile, isJsonTreeView, setIsJsonTreeView, hasPreviewToggle, isMarkdownPreview, setIsMarkdownPreview, headerActions, handleDownload, blobUrl, rawBlob, tI18nHardcoded, tHardcodedUi }), _jsx("div", { className: cn('flex-1', readOnly ? 'overflow-auto' : 'overflow-hidden'), children: _jsxs(ErrorBoundary, { fallback: () => (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx("div", { className: "bg-destructive/10 flex h-12 w-12 items-center justify-center rounded-sm", children: _jsx(FileWarning, { className: "text-destructive/50 h-6 w-6" }) }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: tI18nHardcoded.raw('autoFeaturesFileViewerFileContentRendererJsxTextCouldnT794ae800') }), _jsx("p", { className: "text-muted-foreground/50 max-w-sm font-mono text-xs break-all", children: filePath }), _jsxs(Button, { variant: "outline", size: "sm", onClick: handleDownload, children: [_jsx(Download, { className: "mr-1.5 h-3.5 w-3.5" }), tI18nHardcoded.raw('i18nComplete.textd6eafe823591')] })] })), children: [showLoadingState && (_jsx("div", { className: "flex h-full items-center justify-center", children: _jsx(Loading, { className: "text-muted-foreground/40 h-4 w-4" }) })), contentError && !showLoadingState && isSandboxWaking && (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx(Loading, { className: "text-muted-foreground/40 h-4 w-4" }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: tI18nHardcoded.raw('i18nComplete.text5e3de76869f3') }), _jsx("p", { className: "text-muted-foreground/50 max-w-sm font-mono text-xs break-all", children: filePath }), _jsx("p", { className: "text-muted-foreground/40 max-w-xs text-xs", children: tI18nHardcoded.raw('i18nComplete.textd4707c58c4a4') })] })), contentError &&
90
131
  !showLoadingState &&
91
132
  !isSandboxWaking &&
92
- (errorFallback ? (errorFallback(contentError, filePath)) : isNotFound ? (_jsx(FileNotFoundState, { filePath: filePath })) : (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx("div", { className: "bg-destructive/10 flex h-12 w-12 items-center justify-center rounded-sm", children: _jsx(FileWarning, { className: "text-destructive/50 h-6 w-6" }) }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line692JsxTextFailedToLoadFile') }), _jsx("p", { className: "text-muted-foreground/50 max-w-sm font-mono text-xs break-all", children: filePath }), _jsx("p", { className: "text-muted-foreground/60 max-w-sm text-xs", children: contentError })] }))), !isLoading && !error && imageDataUrl && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(ImageRenderer, { url: imageDataUrl, className: "h-full", fileName: fileName, controls: imageControls }) })), isHeicImage && (blobLoading || heicConverting) && _jsx(RendererFallback, {}), isHeicImage && !blobLoading && !blobError && heicImageUrl && !heicConverting && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(ImageRenderer, { url: heicImageUrl, className: "h-full", fileName: fileName, controls: imageControls }) })), isContentReady && fileCategory === 'pdf' && fileContent?.content && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(PdfRenderer, { fileContent: fileContent.content, fileName: fileName, className: "h-full", fitOnOpen: fitOnOpen, findText: findText, showDownload: false }) })), isContentReady && fileCategory === 'docx' && rawBlob && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(DocxRenderer, { blob: rawBlob, fileName: fileName, className: "h-full", showDownload: false }) })), isContentReady && fileCategory === 'zip' && rawBlob && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(ZipRenderer, { blob: rawBlob, fileName: fileName, className: "h-full" }, filePath) })), !isLoading && !error && !isNotFound && fileCategory === 'xlsx' && (!bytesOnly || blobUrl) && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(XlsxRenderer, {
133
+ (sharedImagePath && !isNotFound ? (
134
+ // An image never shows the read's raw error text (R38).
135
+ _jsx("div", { className: "flex h-full items-center justify-center p-8", children: _jsx(ImageUnavailable, { onRetry: retrySharedImage }) })) : errorFallback ? (errorFallback(contentError, filePath)) : isNotFound ? (_jsx(FileNotFoundState, { filePath: filePath })) : (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-3 p-8 text-center", children: [_jsx("div", { className: "bg-destructive/10 flex h-12 w-12 items-center justify-center rounded-sm", children: _jsx(FileWarning, { className: "text-destructive/50 h-6 w-6" }) }), _jsx("p", { className: "text-muted-foreground text-sm font-medium", children: tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line692JsxTextFailedToLoadFile') }), _jsx("p", { className: "text-muted-foreground/50 max-w-sm font-mono text-xs break-all", children: filePath }), _jsx("p", { className: "text-muted-foreground/60 max-w-sm text-xs", children: contentError })] }))), !isLoading && !error && imageDataUrl && (_jsx(CaptionedImage, { caption: caption, open: captionOpen, onOpenChange: onCaptionOpenChange, children: _jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(ImageRenderer, { url: imageDataUrl, className: "h-full", fileName: fileName, controls: imageControls, info: imageInfo }) }) })), isHeicImage && (blobLoading || heicConverting) && _jsx(RendererFallback, {}), isHeicImage && !blobLoading && !blobError && heicImageUrl && !heicConverting && (_jsx(CaptionedImage, { caption: caption, open: captionOpen, onOpenChange: onCaptionOpenChange, children: _jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(ImageRenderer, { url: heicImageUrl, className: "h-full", fileName: fileName, controls: imageControls, info: imageInfo }) }) })), isContentReady && fileCategory === 'pdf' && fileContent?.content && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(PdfRenderer, { fileContent: fileContent.content, fileName: fileName, className: "h-full", fitOnOpen: fitOnOpen, findText: findText, showDownload: false }) })), isContentReady && fileCategory === 'docx' && rawBlob && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(DocxRenderer, { blob: rawBlob, fileName: fileName, className: "h-full", showDownload: false }) })), isContentReady && fileCategory === 'zip' && rawBlob && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(ZipRenderer, { blob: rawBlob, fileName: fileName, className: "h-full" }, filePath) })), !isLoading && !error && !isNotFound && fileCategory === 'xlsx' && (!bytesOnly || blobUrl) && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(XlsxRenderer, {
93
136
  // A bytes-only source hands the workbook over as a blob URL;
94
137
  // the renderer reads a sandbox path only for a session.
95
138
  filePath: bytesOnly ? blobUrl : filePath, fileName: fileName, className: "h-full", showDownload: false }, `xlsx-${filePath}-${contentRevision}-${reloadKey}`) })), !isLoading && !error && !isNotFound && fileCategory === 'sqlite' && (!bytesOnly || rawBlob) && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(SqliteRenderer, { filePath: filePath, fileName: fileName, className: "h-full", readOnly: readOnly, ...(bytesOnly ? { bytes: rawBlob, onSave: saveThroughSource } : {}) }, `sqlite-${filePath}-${contentRevision}-${reloadKey}`) })), !isLoading && !error && csvAsTable && fileContent && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(CsvRenderer, { content: fileContent.content, fileName: fileName, className: "h-full", showDownload: false }) })), isContentReady && fileCategory === 'video' && blobUrl && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(VideoRenderer, { url: blobUrl, className: "h-full" }) })), isContentReady && fileCategory === 'audio' && blobUrl && (_jsxs("div", { className: "flex h-full flex-col items-center justify-center gap-5 p-8", children: [_jsx("div", { className: "bg-muted/50 flex h-14 w-14 items-center justify-center rounded-sm", children: _jsxs("svg", { className: "text-muted-foreground/40 h-6 w-6", viewBox: "0 0 24 24", fill: "none", stroke: "currentColor", strokeWidth: "1.5", children: [_jsx("path", { d: "M9 18V5l12-2v13" }), _jsx("circle", { cx: "6", cy: "18", r: "3" }), _jsx("circle", { cx: "18", cy: "16", r: "3" })] }) }), _jsx("p", { className: "text-muted-foreground/60 text-sm", children: fileName }), _jsx("audio", { controls: true, src: blobUrl, className: "w-full max-w-sm" })] })), isContentReady && fileCategory === 'pptx' && rawBlob && (_jsx(Suspense, { fallback: _jsx(RendererFallback, {}), children: _jsx(PptxRenderer, { blob: rawBlob, binaryUrl: blobUrl, filePath: filePath, fileName: fileName, className: "h-full", onDownload: handleDownload }) })), isHtmlFile && isHtmlPreview && (_jsx(HtmlPreview, { path: toSandboxAbsolutePath(filePath), reloadKey: `${fetchRevision}-${reloadKey}`, fileName: fileName, pendingLabel: tHardcodedUi.raw('featuresFilesComponentsFileContentRenderer.line805JsxTextStartingPreviewServer') }, `html-preview-${filePath}`)), isHtmlFile && !isHtmlPreview && !isLoading && !error && fileContent?.type === 'text' && (_jsx(CodeEditor, { editorClassName: codeEditorEditorClassName, ...codeEditorProps, className: readOnly ? 'min-h-full' : 'h-full' }, `html-source-${filePath}-${discardKey}`)), !isLoading &&
@@ -114,7 +157,7 @@ export function FileContentRenderer({ filePath, showHeader = true, headerActions
114
157
  // below, so an edit in Source shows up here before saving.
115
158
  _jsx(MermaidDiagram, { source: hasUnsavedChanges ? latestContentRef.current : displayContent, fileName: fileName, onShowSource: () => setIsMarkdownPreview(false), className: "h-full" }, filePath)) : isMarkdownPreview && isMarkdownFile ? (
116
159
  // Markdown is prose, so it gets a measure. The markdown root
117
- // renders at text-[15px]; full-bleed on a wide viewport that
160
+ // renders at text-base (16px); full-bleed on a wide viewport that
118
161
  // is ~190 characters per line, well past the comfortable
119
162
  // 65-90. `max-w-2xl` is the same reading column the customize
120
163
  // sections use, and it is a no-op in panels already narrower