@flamingo-stack/openframe-frontend-core 0.0.703 → 0.0.704-snapshot.20261006133653

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 (336) hide show
  1. package/dist/{chunk-3L7KCOGO.cjs → chunk-2QEUFXNS.cjs} +30 -30
  2. package/dist/{chunk-BMJUKS6S.cjs → chunk-3256UJXS.cjs} +47 -47
  3. package/dist/{chunk-GZYLZ4MB.js → chunk-4S7TJYRQ.js} +3 -3
  4. package/dist/{chunk-ENGBNKUJ.js → chunk-5EFZO5EB.js} +675 -569
  5. package/dist/{chunk-TCZCMD5Q.js → chunk-5HBHO5TB.js} +1 -1
  6. package/dist/{chunk-BOUPNYUV.js → chunk-64HOMVYC.js} +5 -5
  7. package/dist/{chunk-YR6JPJF4.js → chunk-6XIF2Z7I.js} +4 -4
  8. package/dist/{chunk-NU724ZVN.cjs → chunk-7GHB2IK6.cjs} +121 -121
  9. package/dist/{chunk-GX3OUTFO.cjs → chunk-7ZYIC7NA.cjs} +69 -55
  10. package/dist/{chunk-53AC23ZN.js → chunk-A6WVQKOZ.js} +24 -24
  11. package/dist/{chunk-4OGAXAEG.cjs → chunk-ABU3TGXA.cjs} +3 -3
  12. package/dist/{chunk-RTXTGLGB.cjs → chunk-ACV4DFQ3.cjs} +4 -4
  13. package/dist/{chunk-RLDAYA2A.cjs → chunk-BH6PJP66.cjs} +29 -29
  14. package/dist/{chunk-BRXOGCH3.cjs → chunk-BIZNSF3U.cjs} +3 -3
  15. package/dist/{chunk-2A4XES7R.cjs → chunk-BM5MVGH3.cjs} +3 -3
  16. package/dist/{chunk-6YCXA7CN.js → chunk-CH5232HV.js} +20 -19
  17. package/dist/{chunk-UIBSSVON.cjs → chunk-D7BTSVMS.cjs} +2 -2
  18. package/dist/{chunk-YBGEPKC2.cjs → chunk-F7Y2ZS5U.cjs} +6 -6
  19. package/dist/{chunk-5V6O73EH.js → chunk-FLKJYMVB.js} +3 -3
  20. package/dist/{chunk-NMGX2ZTU.js → chunk-IFUQVZF4.js} +17 -17
  21. package/dist/{chunk-2NE3DNXY.js → chunk-IKCQK67U.js} +2 -2
  22. package/dist/{chunk-KFDP3DUL.cjs → chunk-IXPUM3AW.cjs} +98 -98
  23. package/dist/{chunk-QGTDWFT6.js → chunk-IZHZMBIX.js} +2469 -1928
  24. package/dist/{chunk-67CXGPTW.cjs → chunk-JKEEFQOX.cjs} +505 -72
  25. package/dist/{chunk-NYSYSKSH.cjs → chunk-M6HTJTFH.cjs} +15 -15
  26. package/dist/{chunk-OGLCD3UL.js → chunk-MFFCUVWG.js} +6 -6
  27. package/dist/{chunk-KJ5LWATV.js → chunk-MKXO4Q5P.js} +7 -7
  28. package/dist/{chunk-EMCQGFXD.cjs → chunk-MMOODW57.cjs} +28 -27
  29. package/dist/{chunk-Q76ZTHNV.js → chunk-MP2C3LJ4.js} +1 -1
  30. package/dist/{chunk-J4K5JOLI.js → chunk-MV4HYG3B.js} +25 -11
  31. package/dist/{chunk-2T6QWNXU.js → chunk-NDESEPW5.js} +99 -11
  32. package/dist/{chunk-HHFPQNP3.js → chunk-NJDHFC55.js} +15 -15
  33. package/dist/{chunk-HUOLB7ZN.cjs → chunk-OHFS7VOU.cjs} +9 -9
  34. package/dist/{chunk-7632TNE7.js → chunk-OSNIZNVH.js} +7 -7
  35. package/dist/{chunk-UTW4SQDI.js → chunk-OUHMD6EU.js} +10 -10
  36. package/dist/{chunk-MXHJROXJ.cjs → chunk-P23CRLU4.cjs} +1245 -1139
  37. package/dist/{chunk-64O4L7WN.js → chunk-PDWVPI3S.js} +11 -55
  38. package/dist/{chunk-DAW6TDZB.js → chunk-PFC2HWQA.js} +30 -16
  39. package/dist/{chunk-G55W76S6.js → chunk-POZ7F77D.js} +12 -13
  40. package/dist/{chunk-PJP5SMOU.js → chunk-PPEZGUL7.js} +500 -67
  41. package/dist/{chunk-SFGXIFYO.cjs → chunk-QC7Q3XCE.cjs} +24 -24
  42. package/dist/{chunk-MDBHZBW7.cjs → chunk-RKV5B7EU.cjs} +92 -4
  43. package/dist/{chunk-5VPIC5MD.cjs → chunk-SAAZEWUR.cjs} +12 -12
  44. package/dist/{chunk-IIMICN7L.cjs → chunk-TYWLNCNH.cjs} +4 -48
  45. package/dist/{chunk-HYLIQMZH.cjs → chunk-UBP5RNGK.cjs} +33 -33
  46. package/dist/{chunk-HUMWQMCU.js → chunk-UT4YHQZW.js} +3 -3
  47. package/dist/{chunk-BSEIEWM5.cjs → chunk-V3H26CNS.cjs} +28 -14
  48. package/dist/{chunk-NGJCWEYX.cjs → chunk-VHCACDKP.cjs} +46 -46
  49. package/dist/{chunk-SL2FOFT2.js → chunk-VMAZCLX4.js} +7 -7
  50. package/dist/{chunk-5FFNNJM7.js → chunk-VR2ZLFCP.js} +1 -1
  51. package/dist/{chunk-NCTYD7HL.js → chunk-VYWMEL5F.js} +14 -14
  52. package/dist/{chunk-HY5YL3U5.cjs → chunk-XBWE423R.cjs} +1994 -1453
  53. package/dist/{chunk-XCIFHW4E.cjs → chunk-XOYH2YQE.cjs} +2 -2
  54. package/dist/{chunk-S6V7A5FS.cjs → chunk-ZQZFIE2K.cjs} +32 -33
  55. package/dist/components/case-studies/index.cjs +24 -24
  56. package/dist/components/case-studies/index.js +10 -10
  57. package/dist/components/chat/chat-archive-page.d.ts +3 -3
  58. package/dist/components/chat/chat-archive-page.d.ts.map +1 -1
  59. package/dist/components/chat/chat-input.d.ts.map +1 -1
  60. package/dist/components/chat/chat-nav-sidebar.d.ts +4 -0
  61. package/dist/components/chat/chat-nav-sidebar.d.ts.map +1 -1
  62. package/dist/components/chat/chat-panel-header.d.ts +3 -2
  63. package/dist/components/chat/chat-panel-header.d.ts.map +1 -1
  64. package/dist/components/chat/embeddable-chat.d.ts +36 -2
  65. package/dist/components/chat/embeddable-chat.d.ts.map +1 -1
  66. package/dist/components/chat/index.cjs +14 -6
  67. package/dist/components/chat/index.d.ts +4 -1
  68. package/dist/components/chat/index.d.ts.map +1 -1
  69. package/dist/components/chat/index.js +13 -5
  70. package/dist/components/chat/mingo-archive-list.d.ts +17 -0
  71. package/dist/components/chat/mingo-archive-list.d.ts.map +1 -0
  72. package/dist/components/chat/mingo-chat-header.d.ts +31 -0
  73. package/dist/components/chat/mingo-chat-header.d.ts.map +1 -0
  74. package/dist/components/chat/mingo-chat-rail.d.ts +54 -0
  75. package/dist/components/chat/mingo-chat-rail.d.ts.map +1 -0
  76. package/dist/components/chat/model-display.d.ts.map +1 -1
  77. package/dist/components/contact/contact-form.d.ts.map +1 -1
  78. package/dist/components/contact/index.cjs +11 -11
  79. package/dist/components/contact/index.js +10 -10
  80. package/dist/components/content-loading-container.d.ts +1 -1
  81. package/dist/components/content-loading-container.d.ts.map +1 -1
  82. package/dist/components/docs/index.cjs +14 -14
  83. package/dist/components/docs/index.js +13 -13
  84. package/dist/components/docs/skeletons.d.ts.map +1 -1
  85. package/dist/components/embeds/index.cjs +11 -11
  86. package/dist/components/embeds/index.js +10 -10
  87. package/dist/components/faq/index.cjs +12 -12
  88. package/dist/components/faq/index.js +11 -11
  89. package/dist/components/features/figma-prototype-viewer.d.ts.map +1 -1
  90. package/dist/components/features/index.cjs +10 -10
  91. package/dist/components/features/index.js +9 -9
  92. package/dist/components/features/media-gallery-manager.d.ts.map +1 -1
  93. package/dist/components/features/parallax-image-showcase.d.ts.map +1 -1
  94. package/dist/components/features/policy-configuration-panel.d.ts.map +1 -1
  95. package/dist/components/features/section-selector.d.ts.map +1 -1
  96. package/dist/components/help-center-pages/index.cjs +119 -119
  97. package/dist/components/help-center-pages/index.js +39 -39
  98. package/dist/components/help-center-pages/trust-center-sections.d.ts +2 -2
  99. package/dist/components/help-center-pages/trust-center-sections.d.ts.map +1 -1
  100. package/dist/components/index.cjs +94 -86
  101. package/dist/components/index.js +32 -24
  102. package/dist/components/layout/article-detail-layout.d.ts +1 -1
  103. package/dist/components/layout/article-detail-layout.d.ts.map +1 -1
  104. package/dist/components/layout/page-container.d.ts.map +1 -1
  105. package/dist/components/layout/title-block.d.ts +2 -2
  106. package/dist/components/layout/title-block.d.ts.map +1 -1
  107. package/dist/components/loading/page-layout-skeleton.d.ts.map +1 -1
  108. package/dist/components/loading/unified-skeleton.d.ts.map +1 -1
  109. package/dist/components/meeting-scheduler/booking-form.d.ts +4 -4
  110. package/dist/components/meeting-scheduler/booking-form.d.ts.map +1 -1
  111. package/dist/components/meeting-scheduler/context-panel.d.ts.map +1 -1
  112. package/dist/components/meeting-scheduler/index.cjs +127 -127
  113. package/dist/components/meeting-scheduler/index.d.ts +2 -2
  114. package/dist/components/meeting-scheduler/index.d.ts.map +1 -1
  115. package/dist/components/meeting-scheduler/index.js +50 -50
  116. package/dist/components/navigation/app-header.d.ts +4 -0
  117. package/dist/components/navigation/app-header.d.ts.map +1 -1
  118. package/dist/components/navigation/app-layout-side-panel.d.ts +142 -0
  119. package/dist/components/navigation/app-layout-side-panel.d.ts.map +1 -0
  120. package/dist/components/navigation/app-layout.d.ts +11 -1
  121. package/dist/components/navigation/app-layout.d.ts.map +1 -1
  122. package/dist/components/navigation/header-mingo-button.d.ts +4 -1
  123. package/dist/components/navigation/header-mingo-button.d.ts.map +1 -1
  124. package/dist/components/navigation/index.cjs +13 -13
  125. package/dist/components/navigation/index.d.ts +1 -0
  126. package/dist/components/navigation/index.d.ts.map +1 -1
  127. package/dist/components/navigation/index.js +12 -12
  128. package/dist/components/navigation/navigation-sidebar.d.ts +8 -1
  129. package/dist/components/navigation/navigation-sidebar.d.ts.map +1 -1
  130. package/dist/components/navigation/sliding-sidebar.d.ts.map +1 -1
  131. package/dist/components/onboarding-guides/index.cjs +14 -14
  132. package/dist/components/onboarding-guides/index.js +13 -13
  133. package/dist/components/persistent-filter-controls.d.ts.map +1 -1
  134. package/dist/components/related-content/index.cjs +7 -7
  135. package/dist/components/related-content/index.js +6 -6
  136. package/dist/components/shared/detail-page-skeleton.d.ts.map +1 -1
  137. package/dist/components/tickets/help-center-create-form.d.ts +4 -4
  138. package/dist/components/tickets/index.cjs +15 -15
  139. package/dist/components/tickets/index.js +14 -14
  140. package/dist/components/ui/announcement-bar-view.d.ts.map +1 -1
  141. package/dist/components/ui/breadcrumb.d.ts.map +1 -1
  142. package/dist/components/ui/checkbox-block.d.ts.map +1 -1
  143. package/dist/components/ui/dashboard-info-card.d.ts +1 -1
  144. package/dist/components/ui/dashboard-info-card.d.ts.map +1 -1
  145. package/dist/components/ui/data-table/data-table-body.d.ts +2 -2
  146. package/dist/components/ui/data-table/data-table-skeleton.d.ts +2 -2
  147. package/dist/components/ui/data-table/data-table-skeleton.d.ts.map +1 -1
  148. package/dist/components/ui/data-table/utils.d.ts +2 -2
  149. package/dist/components/ui/dialog.d.ts.map +1 -1
  150. package/dist/components/ui/drawer.d.ts.map +1 -1
  151. package/dist/components/ui/entity-image.d.ts +2 -2
  152. package/dist/components/ui/file-manager/index.cjs +36 -36
  153. package/dist/components/ui/file-manager/index.js +3 -3
  154. package/dist/components/ui/filter-modal.d.ts.map +1 -1
  155. package/dist/components/ui/index.cjs +18 -10
  156. package/dist/components/ui/index.js +17 -9
  157. package/dist/components/ui/info-card-row.d.ts.map +1 -1
  158. package/dist/components/ui/menubar.d.ts +1 -1
  159. package/dist/components/ui/menubar.d.ts.map +1 -1
  160. package/dist/components/ui/modal-v2.d.ts.map +1 -1
  161. package/dist/components/ui/modal.d.ts.map +1 -1
  162. package/dist/components/ui/no-data/no-data-actions.d.ts.map +1 -1
  163. package/dist/components/ui/no-data/no-data-styles.d.ts +1 -1
  164. package/dist/components/ui/no-data/no-data-styles.d.ts.map +1 -1
  165. package/dist/components/ui/page-actions.d.ts.map +1 -1
  166. package/dist/components/ui/sheet.d.ts.map +1 -1
  167. package/dist/components/ui/table/table-skeleton.d.ts +1 -1
  168. package/dist/components/ui/table/table-skeleton.d.ts.map +1 -1
  169. package/dist/hooks/index.cjs +23 -9
  170. package/dist/hooks/index.js +24 -10
  171. package/dist/hooks/ui/index.d.ts +1 -0
  172. package/dist/hooks/ui/index.d.ts.map +1 -1
  173. package/dist/hooks/ui/use-content-breakpoint.d.ts +32 -0
  174. package/dist/hooks/ui/use-content-breakpoint.d.ts.map +1 -0
  175. package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts +2 -2
  176. package/dist/hooks/ui/use-isomorphic-layout-effect.d.ts.map +1 -1
  177. package/dist/hooks/ui/use-media-query.d.ts.map +1 -1
  178. package/dist/index.cjs +39 -17
  179. package/dist/index.js +41 -19
  180. package/dist/styles/layout-steps.d.ts +30 -0
  181. package/dist/styles/layout-steps.d.ts.map +1 -0
  182. package/dist/tailwind.config.cjs +36 -11
  183. package/dist/tailwind.config.js +36 -11
  184. package/eslint-config/README.md +9 -0
  185. package/eslint-config/base.js +17 -1
  186. package/eslint-config/layout-variants.js +57 -0
  187. package/package.json +3 -1
  188. package/src/ODS_TOKEN_RULES.md +40 -0
  189. package/src/components/chat/chat-archive-page.tsx +9 -7
  190. package/src/components/chat/chat-input.tsx +5 -1
  191. package/src/components/chat/chat-nav-sidebar.tsx +10 -4
  192. package/src/components/chat/chat-panel-header.tsx +8 -5
  193. package/src/components/chat/embeddable-chat.tsx +675 -485
  194. package/src/components/chat/index.ts +4 -0
  195. package/src/components/chat/mingo-archive-list.tsx +108 -0
  196. package/src/components/chat/mingo-chat-header.tsx +104 -0
  197. package/src/components/chat/mingo-chat-rail.tsx +361 -0
  198. package/src/components/chat/model-display.tsx +8 -1
  199. package/src/components/contact/contact-form.tsx +11 -9
  200. package/src/components/content-loading-container.tsx +4 -2
  201. package/src/components/docs/doc-viewer.tsx +7 -7
  202. package/src/components/docs/skeletons.tsx +7 -4
  203. package/src/components/dynamic-skeleton.tsx +1 -1
  204. package/src/components/embeds/claude-embed.tsx +1 -1
  205. package/src/components/embeds/embed-viewer-frame.tsx +4 -4
  206. package/src/components/embeds/figma-embed.tsx +2 -2
  207. package/src/components/embeds/google-sheets-viewer.tsx +1 -1
  208. package/src/components/embeds/pdf-viewer.tsx +3 -3
  209. package/src/components/empty-state.tsx +6 -6
  210. package/src/components/faq/faq-section.tsx +2 -2
  211. package/src/components/faq-accordion.tsx +1 -1
  212. package/src/components/features/board/board-column-header.tsx +1 -1
  213. package/src/components/features/command-box.tsx +3 -3
  214. package/src/components/features/entity-video-section.tsx +2 -2
  215. package/src/components/features/figma-prototype-viewer.tsx +4 -1
  216. package/src/components/features/media-gallery-manager.tsx +3 -1
  217. package/src/components/features/parallax-image-showcase.tsx +9 -6
  218. package/src/components/features/policy-configuration-panel.tsx +9 -7
  219. package/src/components/features/provider-button.tsx +1 -1
  220. package/src/components/features/section-selector.tsx +11 -6
  221. package/src/components/features/select-button.tsx +4 -4
  222. package/src/components/features/seo-editor-preview.tsx +2 -2
  223. package/src/components/features/video-ratio-tabs.tsx +3 -3
  224. package/src/components/features/waitlist-form.tsx +1 -1
  225. package/src/components/help-center-pages/trust-center-page.tsx +1 -1
  226. package/src/components/help-center-pages/trust-center-sections.tsx +14 -14
  227. package/src/components/icon-display.tsx +1 -1
  228. package/src/components/layout/article-detail-layout.tsx +4 -2
  229. package/src/components/layout/list-page-layout.tsx +2 -2
  230. package/src/components/layout/page-container.tsx +26 -14
  231. package/src/components/layout/page-heading.tsx +1 -1
  232. package/src/components/layout/page-layout.tsx +1 -1
  233. package/src/components/layout/title-block.tsx +13 -13
  234. package/src/components/loading/card-skeleton.tsx +11 -11
  235. package/src/components/loading/category-vendor-selector-skeleton.tsx +1 -1
  236. package/src/components/loading/content-skeleton.tsx +6 -6
  237. package/src/components/loading/device-card-skeleton.tsx +1 -1
  238. package/src/components/loading/margin-report-skeleton.tsx +7 -7
  239. package/src/components/loading/msp-profile-form-skeleton.tsx +3 -3
  240. package/src/components/loading/organization-card-skeleton.tsx +2 -2
  241. package/src/components/loading/page-layout-skeleton.tsx +80 -68
  242. package/src/components/loading/unified-skeleton.tsx +16 -6
  243. package/src/components/media-carousel.tsx +3 -3
  244. package/src/components/meeting-scheduler/booking-form.tsx +9 -8
  245. package/src/components/meeting-scheduler/context-panel.tsx +8 -6
  246. package/src/components/meeting-scheduler/directory.tsx +4 -4
  247. package/src/components/meeting-scheduler/index.tsx +15 -15
  248. package/src/components/meeting-scheduler/slot-picker.tsx +12 -12
  249. package/src/components/navigation/app-header.tsx +6 -0
  250. package/src/components/navigation/app-layout-side-panel.tsx +594 -0
  251. package/src/components/navigation/app-layout.tsx +69 -4
  252. package/src/components/navigation/header-mingo-button.tsx +13 -3
  253. package/src/components/navigation/index.ts +5 -0
  254. package/src/components/navigation/navigation-sidebar.tsx +17 -3
  255. package/src/components/navigation/sliding-sidebar.tsx +2 -1
  256. package/src/components/pagination.tsx +3 -3
  257. package/src/components/persistent-filter-controls.tsx +6 -2
  258. package/src/components/platform/ScriptArguments.tsx +1 -1
  259. package/src/components/platform/ScriptInfoSection.tsx +4 -4
  260. package/src/components/platform/SoftwareSourceBadge.tsx +1 -1
  261. package/src/components/shared/article-author-byline.tsx +1 -1
  262. package/src/components/shared/delivery/delivery-lists.tsx +2 -2
  263. package/src/components/shared/delivery/delivery-row.tsx +7 -7
  264. package/src/components/shared/delivery/delivery-table.tsx +3 -3
  265. package/src/components/shared/detail-page-skeleton.tsx +11 -5
  266. package/src/components/shared/dev-section/dev-card-row.tsx +6 -6
  267. package/src/components/shared/dev-section/dev-section-view.tsx +1 -1
  268. package/src/components/shared/product-release/product-release-card-skeleton.tsx +8 -8
  269. package/src/components/shared/product-release/product-release-card.tsx +9 -9
  270. package/src/components/shared/product-release/product-releases-view.tsx +1 -1
  271. package/src/components/shared/product-release/release-detail-page.tsx +5 -5
  272. package/src/components/shared/roadmap/roadmap-grid-skeleton.tsx +1 -1
  273. package/src/components/shared/roadmap/roadmap-grid.tsx +3 -3
  274. package/src/components/tickets/help-center-card.tsx +1 -1
  275. package/src/components/tickets/help-center-create-form.tsx +12 -12
  276. package/src/components/ui/announcement-bar-view.tsx +2 -10
  277. package/src/components/ui/benefit-card.tsx +18 -18
  278. package/src/components/ui/brand-association-grid.tsx +4 -4
  279. package/src/components/ui/breadcrumb.tsx +4 -1
  280. package/src/components/ui/card.tsx +1 -1
  281. package/src/components/ui/checkbox-block.tsx +9 -7
  282. package/src/components/ui/content-loader.tsx +3 -3
  283. package/src/components/ui/cursor-pagination.tsx +3 -3
  284. package/src/components/ui/dashboard-info-card.tsx +13 -9
  285. package/src/components/ui/data-attribution.tsx +2 -2
  286. package/src/components/ui/data-table/data-table-body.tsx +2 -2
  287. package/src/components/ui/data-table/data-table-header.tsx +15 -15
  288. package/src/components/ui/data-table/data-table-skeleton.tsx +6 -6
  289. package/src/components/ui/data-table/utils.ts +5 -5
  290. package/src/components/ui/dialog.tsx +4 -1
  291. package/src/components/ui/drawer.tsx +2 -1
  292. package/src/components/ui/duration-input.tsx +1 -1
  293. package/src/components/ui/entity-image.tsx +3 -3
  294. package/src/components/ui/feature-card.tsx +10 -10
  295. package/src/components/ui/file-manager/file-manager-action-bar.tsx +1 -1
  296. package/src/components/ui/file-manager/file-manager.tsx +1 -1
  297. package/src/components/ui/filter-modal.tsx +8 -1
  298. package/src/components/ui/highlight-card.tsx +7 -7
  299. package/src/components/ui/info-card-row.tsx +10 -6
  300. package/src/components/ui/modal-v2.tsx +7 -2
  301. package/src/components/ui/modal.tsx +3 -2
  302. package/src/components/ui/no-data/no-data-actions.tsx +10 -4
  303. package/src/components/ui/no-data/no-data-styles.ts +1 -1
  304. package/src/components/ui/no-data/no-data.tsx +1 -1
  305. package/src/components/ui/organization-card.tsx +1 -1
  306. package/src/components/ui/page-actions.tsx +34 -5
  307. package/src/components/ui/progress-bar.tsx +2 -2
  308. package/src/components/ui/query-report-table/query-report-table.tsx +1 -1
  309. package/src/components/ui/search-input.tsx +2 -2
  310. package/src/components/ui/service-card.tsx +2 -2
  311. package/src/components/ui/sheet.tsx +2 -1
  312. package/src/components/ui/skeleton.tsx +3 -3
  313. package/src/components/ui/stacked-rows-panel.tsx +4 -4
  314. package/src/components/ui/tab-navigation.tsx +2 -2
  315. package/src/components/ui/tab-selector.tsx +2 -2
  316. package/src/components/ui/table/table-header.tsx +3 -3
  317. package/src/components/ui/table/table-skeleton.tsx +3 -3
  318. package/src/components/ui/table/utils.ts +5 -5
  319. package/src/components/ui/tag-search-input.tsx +3 -3
  320. package/src/components/ui/tags-manager.tsx +5 -5
  321. package/src/components/ui/test-run.tsx +2 -2
  322. package/src/components/ui/ticket-info-section.tsx +1 -1
  323. package/src/components/ui/ticket-notes-section.tsx +1 -1
  324. package/src/components/ui/ticket-status-config-row.tsx +15 -15
  325. package/src/hooks/ui/index.ts +1 -0
  326. package/src/hooks/ui/use-content-breakpoint.ts +58 -0
  327. package/src/hooks/ui/use-media-query.ts +3 -2
  328. package/src/styles/app-globals.css +13 -0
  329. package/src/styles/index.css +2 -0
  330. package/src/styles/layout-steps.ts +14 -0
  331. package/src/styles/ods-colors.css +15 -0
  332. package/src/styles/ods-content-area-tokens.css +27 -0
  333. package/src/styles/ods-content-area.css +31 -0
  334. package/src/styles/ods-effects.css +4 -2
  335. package/src/styles/ods-responsive-tokens.css +14 -4
  336. package/tailwind.config.ts +42 -7
@@ -61,6 +61,7 @@ import { Drawer, DrawerContent } from '../ui/drawer';
61
61
  import { HoverDropdown, type HoverDropdownItem } from '../ui/hover-dropdown';
62
62
  import { CollisionBoundaryContext, PortalContainerContext } from '../ui/portal-container';
63
63
  import { SquareAvatar } from '../ui/square-avatar';
64
+ import { ChatAppearanceContext } from './chat-appearance-context';
64
65
  import { ChatArchivePage } from './chat-archive-page';
65
66
  import { ChatAttachmentChipStrip } from './chat-attachment-bar';
66
67
  import { ChatComposer, type ChatComposerLock } from './chat-composer';
@@ -85,7 +86,10 @@ import { useEmptyStateConfig } from './hooks/use-empty-state-config';
85
86
  import { fetchSlashCommands, useSlashCommandRegistry, type SlashCommandSummary } from './hooks/use-slash-commands';
86
87
  import type { ChatSource, UseSseChatAdapterOptions } from './hooks/use-sse-chat-adapter';
87
88
  import { useUnifiedChat, type ChatMode, type UseUnifiedChatModes } from './hooks/use-unified-chat';
89
+ import { MingoArchiveList } from './mingo-archive-list';
90
+ import { MingoChatHeader } from './mingo-chat-header';
88
91
  import { ChatDialogModals } from './mingo-chat-modals';
92
+ import { MingoChatRail, type MingoDialogStatus } from './mingo-chat-rail';
89
93
  import { MingoHistoryRail } from './mingo-history-rail';
90
94
  import { MingoOnboardingCard } from './mingo-onboarding-card';
91
95
  import { MingoOnboardingCardSkeleton } from './mingo-onboarding-card-skeleton';
@@ -93,7 +97,8 @@ import { MingoWelcome, type MingoWelcomeProps } from './mingo-welcome';
93
97
  import { NavLinkAnchorViaRuntime } from './nav-link-anchor-via-runtime';
94
98
  import { accentFromIdentityIcon, getAgentAccent } from './quick-action-chip';
95
99
  import { SourceActionButton } from './source-action-button';
96
- import type { ChatInputRef, SlashCommandActionId } from './types/component.types';
100
+ import { CHAT_APPEARANCE, type ChatAppearance } from './types/chat.types';
101
+ import type { ChatInputRef, DialogItem, SlashCommandActionId } from './types/component.types';
97
102
  import type { ChatContextItem, ChatContextPickerConfig } from './types/context-item.types';
98
103
  import type { MessageSegment, Message } from './types/message.types';
99
104
  import type {
@@ -123,6 +128,8 @@ import { resolveSourceRowCTA, sourceRowCtxFromRuntime } from './utils/source-row
123
128
  * self-contained — the host wires nothing.
124
129
  */
125
130
  const HISTORY_RAIL_WIDTH = 320;
131
+ /** The v2 chat list (`MingoChatRail`, Figma `chat-sidebar`): the narrowest the v2 chat draws. */
132
+ export const MINGO_V2_RAIL_WIDTH = 296;
126
133
  const CHAT_BLOCK_MIN_WIDTH = 400;
127
134
  const SPLIT_MIN_WIDTH = HISTORY_RAIL_WIDTH + CHAT_BLOCK_MIN_WIDTH;
128
135
 
@@ -324,9 +331,42 @@ export interface EmbeddableChatProps {
324
331
  * suppressed — those are Drawer-shell concerns. The consumer is
325
332
  * responsible for mount/unmount and for opening/closing via the
326
333
  * `open` / `onOpenChange` props (which the in-body close button still
327
- * drives).
334
+ * drives). The host must provide the Radix Dialog it sits in.
335
+ * - `'inline'`: like `'none'`, for a host that is NOT a dialog (a panel
336
+ * docked into the page layout): no Radix Dialog is expected, so the
337
+ * panel names itself through the host's region rather than a
338
+ * `Dialog.Title`.
328
339
  */
329
- shell?: 'drawer' | 'none';
340
+ shell?: 'drawer' | 'none' | 'inline';
341
+
342
+ /**
343
+ * Whether the panel offers a close button. `false` for a chat that is part
344
+ * of the layout rather than an overlay (docked beside the page): every
345
+ * header leaves its close control out. Default `true`.
346
+ */
347
+ closable?: boolean;
348
+
349
+ /**
350
+ * `'v2'`: the Mingo v2 layout (Figma openframe - mingo) — the chat list as a
351
+ * full-height `MingoChatRail` beside the chat, the `MingoChatHeader` over the
352
+ * chat only, Mingo's cyan-tinted surface. Default `'classic'`.
353
+ */
354
+ appearance?: ChatAppearance;
355
+
356
+ /**
357
+ * v2: the →| control, shown while the list and the chat both fit. The host
358
+ * steps its panel back one size (see `collapseTo`).
359
+ */
360
+ onCollapse?: () => void;
361
+ /**
362
+ * v2: what `onCollapse` leaves room for. `list` (default): the list alone, so
363
+ * an open chat is closed first. `column`: a narrower panel that still shows
364
+ * the open chat, which stays open.
365
+ */
366
+ collapseTo?: 'list' | 'column';
367
+
368
+ /** v2: the status glyph at the end of a chat's row (working / unread / approval). */
369
+ dialogStatusOf?: (dialog: DialogItem) => MingoDialogStatus | undefined;
330
370
 
331
371
  /**
332
372
  * Display name of the signed-in user, shown as the sub-line under the chat
@@ -529,22 +569,29 @@ function useRailPresence(open: boolean): { mounted: boolean; active: boolean } {
529
569
  function RailSlot({
530
570
  active,
531
571
  innerClassName,
572
+ className,
573
+ narrow = false,
532
574
  children,
533
575
  }: {
534
576
  active: boolean;
535
577
  innerClassName?: string;
578
+ className?: string;
579
+ /** The v2 rail's 296px instead of 320px. */
580
+ narrow?: boolean;
536
581
  children: React.ReactNode;
537
582
  }) {
583
+ const width = narrow ? 'w-[296px]' : 'w-80';
538
584
  return (
539
585
  <div
540
586
  className={cn(
541
587
  // Border on the OUTER so the rail/chat divider tracks the animating
542
588
  // boundary (and fades out with the panel) instead of vanishing early.
543
589
  'shrink-0 overflow-hidden border-r border-ods-border transition-[width,opacity] ease-out',
544
- active ? 'duration-[240ms] w-80 opacity-100' : 'duration-[300ms] w-0 opacity-0',
590
+ active ? cn('duration-[240ms] opacity-100', width) : 'duration-[300ms] w-0 opacity-0',
591
+ className,
545
592
  )}
546
593
  >
547
- <div className={cn('h-full w-80', innerClassName)}>{children}</div>
594
+ <div className={cn('h-full', width, innerClassName)}>{children}</div>
548
595
  </div>
549
596
  );
550
597
  }
@@ -973,6 +1020,11 @@ function EmbeddableChatInner({
973
1020
  aiAgentConfigUrl: aiAgentConfigUrlProp,
974
1021
  defaultActiveMode,
975
1022
  shell = 'drawer',
1023
+ closable = true,
1024
+ appearance = CHAT_APPEARANCE.CLASSIC,
1025
+ onCollapse,
1026
+ collapseTo = 'list',
1027
+ dialogStatusOf,
976
1028
  userDisplayName,
977
1029
  userAvatarUrl,
978
1030
  mingoWelcome,
@@ -985,11 +1037,12 @@ function EmbeddableChatInner({
985
1037
  contextMemory,
986
1038
  handleRef,
987
1039
  }: EmbeddableChatProps & { handleRef?: React.Ref<EmbeddableChatHandle> }) {
988
- // `shell === 'none'` means the consumer hosts us inside their own panel
1040
+ // `shell` 'none' / 'inline' means the consumer hosts us inside their own panel
989
1041
  // (e.g. AppLayoutDrawer in openframe-frontend). Several drawer-shell
990
1042
  // concerns are unconditional in this codebase — gate them off here so
991
1043
  // we don't double-up with the host's behaviour.
992
- const shellLess = shell === 'none';
1044
+ const shellLess = shell !== 'drawer';
1045
+ const isV2 = appearance === CHAT_APPEARANCE.V2;
993
1046
  const runtime = useRequiredChatRuntime();
994
1047
  // Optional on embedders (platform-agnostic); '' is a harmless sentinel for the
995
1048
  // `ask-ai:open-with-ref` event filter below. Deliberately NOT the hub's
@@ -1526,6 +1579,8 @@ function EmbeddableChatInner({
1526
1579
  const resolvedBaseRoute = baseRoute || (source === 'flamingo' ? '/knowledge-base' : '/data-room');
1527
1580
 
1528
1581
  const handleClose = useCallback(() => setIsOpen(false), [setIsOpen]);
1582
+ // What the header close controls call; absent on a panel that cannot close.
1583
+ const closeControl = closable ? handleClose : undefined;
1529
1584
 
1530
1585
  const handleNavigationClose = useCallback(() => {
1531
1586
  navigatingAwayRef.current = true;
@@ -1942,16 +1997,17 @@ function EmbeddableChatInner({
1942
1997
  // Only the split threshold is state, not the width: the observer fires on
1943
1998
  // every pixel of a drawer drag, and a width in state re-rendered this whole
1944
1999
  // component each frame for a value that changes at one width.
2000
+ const splitMinWidth = (isV2 ? MINGO_V2_RAIL_WIDTH : HISTORY_RAIL_WIDTH) + CHAT_BLOCK_MIN_WIDTH;
1945
2001
  const [canSplit, setCanSplit] = useState(false);
1946
2002
  useEffect(() => {
1947
2003
  if (!panelBoundary || typeof ResizeObserver === 'undefined') return undefined;
1948
2004
  const ro = new ResizeObserver(entries => {
1949
2005
  const w = entries[0]?.contentRect?.width;
1950
- if (typeof w === 'number') setCanSplit(w >= SPLIT_MIN_WIDTH);
2006
+ if (typeof w === 'number') setCanSplit(w >= splitMinWidth);
1951
2007
  });
1952
2008
  ro.observe(panelBoundary);
1953
2009
  return () => ro.disconnect();
1954
- }, [panelBoundary]);
2010
+ }, [panelBoundary, splitMinWidth]);
1955
2011
 
1956
2012
  // ONE node for two consumers: the width measurement above and the
1957
2013
  // collision-boundary context. State, not a ref, so both re-run when the node
@@ -1963,10 +2019,13 @@ function EmbeddableChatInner({
1963
2019
  // width 0 before flipping. Seeding in the effect instead is what the
1964
2020
  // pre-refactor code did, but `react-hooks/set-state-in-effect` rejects it in
1965
2021
  // a reactive (non-mount-only) effect.
1966
- const setPanelNode = useCallback((node: HTMLDivElement | null) => {
1967
- setPanelBoundary(node);
1968
- if (node) setCanSplit(node.clientWidth >= SPLIT_MIN_WIDTH);
1969
- }, []);
2022
+ const setPanelNode = useCallback(
2023
+ (node: HTMLDivElement | null) => {
2024
+ setPanelBoundary(node);
2025
+ if (node) setCanSplit(node.clientWidth >= splitMinWidth);
2026
+ },
2027
+ [splitMinWidth],
2028
+ );
1970
2029
 
1971
2030
  // Rail collapse toggle (Figma ⟶| control), persisted so it survives the
1972
2031
  // drawer remounting on close.
@@ -2205,12 +2264,12 @@ function EmbeddableChatInner({
2205
2264
  avatar: headerAvatar,
2206
2265
  backAriaLabel: 'Back to chats',
2207
2266
  onBack: () => setComposeOpen(false),
2208
- onClose: handleClose,
2267
+ onClose: closeControl,
2209
2268
  }
2210
2269
  : {
2211
2270
  showBack: false,
2212
2271
  title: 'Current Chats',
2213
- onClose: handleClose,
2272
+ onClose: closeControl,
2214
2273
  onOpenArchive: headerOnOpenArchive,
2215
2274
  onToggleSearch: mingoCaps.onSearchChange ? () => setRailSearchOpen(o => !o) : undefined,
2216
2275
  searchActive: railSearchOpen,
@@ -2225,7 +2284,7 @@ function EmbeddableChatInner({
2225
2284
  backAriaLabel: headerBackAriaLabel,
2226
2285
  isArchivedView: isViewingArchived,
2227
2286
  onBack: headerOnBack,
2228
- onClose: handleClose,
2287
+ onClose: closeControl,
2229
2288
  onRestore: headerOnRestore,
2230
2289
  onRename: headerOnRename,
2231
2290
  onArchive: headerOnArchive,
@@ -2234,15 +2293,87 @@ function EmbeddableChatInner({
2234
2293
  onOpenArchive: headerOnOpenArchive,
2235
2294
  };
2236
2295
 
2296
+ // v2 list + header (see `appearance`). The list stays on screen while the
2297
+ // archive opens beside it, so picking a chat from it leaves the archive.
2298
+ const leaveArchiveThen =
2299
+ (action: () => void): (() => void) =>
2300
+ () => {
2301
+ if (archiveOpen) closeArchive();
2302
+ action();
2303
+ };
2304
+ const mingoRailProps = {
2305
+ dialogs,
2306
+ activeDialogId: activeDialogId ?? undefined,
2307
+ // Beside a new chat (wide only: narrow, the list is its own screen).
2308
+ draftTitle: wideMingo && !hasConversation && !archiveOpen ? freshConversationTitle : undefined,
2309
+ onSelectDialog: (id: string) => leaveArchiveThen(() => handleSelectDialog(id))(),
2310
+ onNewChat: leaveArchiveThen(handleNewChat),
2311
+ onOpenArchive: headerOnOpenArchive && (archiveOpen ? closeArchive : headerOnOpenArchive),
2312
+ archiveActive: archiveOpen,
2313
+ // The suggestions a new chat opens on.
2314
+ onWhatToAsk: leaveArchiveThen(handleNewChat),
2315
+ statusOf: dialogStatusOf,
2316
+ onRequestRename: mingoCaps.canRename ? setRenameTarget : undefined,
2317
+ onRequestArchive: mingoCaps.canArchive ? setArchiveTarget : undefined,
2318
+ onRequestCopyLink: mingoCaps.onCopyLink,
2319
+ onRequestCompact: mingoCaps.compactDialog,
2320
+ scope: dialogScope,
2321
+ onScopeChange: setDialogScope,
2322
+ searchQuery: mingoCaps.searchQuery,
2323
+ onSearchChange: mingoCaps.onSearchChange,
2324
+ hasMore: hasMoreDialogs,
2325
+ isLoadingMore: isDialogsLoading && dialogs.length > 0,
2326
+ onLoadMore: () => {
2327
+ void loadMoreDialogs();
2328
+ },
2329
+ isLoadingHistory: dialogsInitialLoading,
2330
+ loadError: dialogsLoadError,
2331
+ onRetry: reloadDialogs,
2332
+ };
2333
+ // Over the chat column only; none on the narrow list (it is its own screen)
2334
+ // or over the archive (it brings its own back bar).
2335
+ const v2Header =
2336
+ isV2 && !stackedListView ? (
2337
+ <div className="col-start-2 row-start-1 min-w-0">
2338
+ <MingoChatHeader
2339
+ title={headerShowBack || isGuideEmpty ? headerTitle : freshConversationTitle}
2340
+ subtitle={headerPersonName}
2341
+ avatar={headerAvatar}
2342
+ // Wide: shows / hides the list beside the chat. Narrow: back to the
2343
+ // list, the only one of the two that fits.
2344
+ onToggleList={wideMingo ? toggleRailCollapsed : headerShowBack ? headerOnBack : () => setComposeOpen(false)}
2345
+ listOpen={splitActive}
2346
+ menuItems={headerShowBack && !isViewingArchived ? splitHeaderMenuItems : []}
2347
+ onRestore={isViewingArchived ? headerOnRestore : undefined}
2348
+ // Only while the list and the chat both fit: narrow, the toggle is
2349
+ // already the way back to the list.
2350
+ onCollapse={
2351
+ wideMingo && onCollapse
2352
+ ? () => {
2353
+ // The minimum is the list alone: a conversation left open
2354
+ // would take the narrow panel instead.
2355
+ if (collapseTo === 'list' && hasConversation) headerOnBack();
2356
+ onCollapse();
2357
+ }
2358
+ : undefined
2359
+ }
2360
+ onClose={closeControl}
2361
+ />
2362
+ </div>
2363
+ ) : null;
2364
+
2237
2365
  // Chat body — defined once, then rendered inside whichever shell applies.
2238
2366
  // Radix overlays (⋯ menus, tooltips) portal into `portalHost` — a node inside
2239
2367
  // this panel — so they inherit the drawer's stacking context and need only a
2240
2368
  // small, local z-index instead of escalating to beat the drawer at the
2241
2369
  // document root. See `PortalContainerContext`.
2242
2370
  const body = (
2243
- <PortalContainerContext.Provider value={portalHost}>
2244
- <CollisionBoundaryContext.Provider value={panelBoundary}>
2245
- {/* Panel surface depends on state (Figma):
2371
+ // The whole panel reads the appearance (the composer's footer too), not
2372
+ // only the thread `ChatMessageList` provides it to.
2373
+ <ChatAppearanceContext.Provider value={appearance}>
2374
+ <PortalContainerContext.Provider value={portalHost}>
2375
+ <CollisionBoundaryContext.Provider value={panelBoundary}>
2376
+ {/* Panel surface depends on state (Figma):
2246
2377
  • Narrow "Current Chats" LIST + FULL-PANEL archive page → grey
2247
2378
  `ods-card` (#212121) — matching the grey rail those lists live
2248
2379
  in when the panel is wide,
@@ -2255,209 +2386,286 @@ function EmbeddableChatInner({
2255
2386
  — in the split layout it lives in the rail (which owns its own
2256
2387
  grey), so the chat block on the right must keep the conversation's
2257
2388
  dark surface instead of following the archive. */}
2258
- <div
2259
- ref={setPanelNode}
2260
- className={`flex h-full flex-col overflow-hidden transition-colors duration-200 ${
2261
- (archiveOpen && !splitActive) || stackedListView ? 'bg-ods-card' : 'bg-ods-bg'
2262
- } ${previewMode ? 'pointer-events-none select-none' : ''}`}
2263
- >
2264
- {/* Archive-page ↔ chat-panel swap fades in (200ms) to match the
2389
+ <div
2390
+ ref={setPanelNode}
2391
+ // v2: one surface for the list and the chat, Mingo's cyan-tinted bg.
2392
+ data-surface={isV2 ? 'mingo' : undefined}
2393
+ className={`flex h-full flex-col overflow-hidden transition-colors duration-200 ${
2394
+ !isV2 && ((archiveOpen && !splitActive) || stackedListView) ? 'bg-ods-card' : 'bg-ods-bg'
2395
+ } ${previewMode ? 'pointer-events-none select-none' : ''}`}
2396
+ >
2397
+ {/* Archive-page ↔ chat-panel swap fades in (200ms) to match the
2265
2398
  surface flip. The full-panel archive is used only when NOT in
2266
2399
  the wide split layout — there the archive opens inside the left
2267
2400
  rail instead (see the rail column below). */}
2268
- {archiveOpen && !splitActive ? (
2269
- <div key="archive-view" className="flex min-h-0 flex-1 flex-col duration-200 animate-in fade-in-0">
2270
- <ChatArchivePage
2271
- dialogs={archivedDialogs}
2272
- onSelectDialog={handleArchivedSelect}
2273
- onBack={closeArchive}
2274
- onClose={handleClose}
2275
- isLoading={archivedLoading}
2276
- isFetching={archivedPending}
2277
- hasMore={archivedCursor != null}
2278
- onLoadMore={() => {
2279
- void loadArchivedPage(archivedCursor ?? undefined);
2280
- }}
2281
- />
2282
- </div>
2283
- ) : (
2284
- <div key="chat-view" className="flex min-h-0 flex-1 flex-col duration-200 animate-in fade-in-0">
2285
- {showWideHeader ? (
2286
- // Wide Mingo header (Figma 113:60931 / 113:63630). Split: a
2287
- // two-cell bar — the "Current Chats" rail header (search /
2288
- // archive / collapse) over the 320px rail + the chat-block header
2289
- // (title / ⋯ / close) over the fill column. Collapsed
2290
- // (`wideCollapsed`): the rail cell is gone, leaving the corner
2291
- // expand toggle + the chat-block header (the fill column shows
2292
- // the chat / new-chat welcome). No back chevron — chat-list
2293
- // navigation is the rail toggle. Shown whenever the panel is wide
2294
- // enough to split (width ≥ `SPLIT_MIN_WIDTH`), NOT gated on
2295
- // the viewport `md` breakpoint — otherwise a 720–799px panel on a
2296
- // sub-`md` viewport would split the body yet drop to the mobile
2297
- // single-bar header (no rail search / collapse controls).
2298
- <div className="flex h-14 w-full flex-shrink-0 overflow-hidden border-b border-ods-border bg-ods-card">
2299
- {railPresence.mounted && (
2300
- <RailSlot active={railPresence.active} innerClassName="flex overflow-hidden">
2301
- {archiveOpen ? (
2302
- // Archive open in the wide layout — the rail header
2303
- // turns into a "Chat Archive" back bar (the list itself
2304
- // renders in the rail below); the chat block is untouched.
2305
- // Back is a full-height leading cell, matching the other
2306
- // header cells (not a small inline chevron).
2307
- <>
2308
- <ChatHeaderIconButton divider="right" onClick={closeArchive} aria-label="Back to chats">
2309
- <Chevron02LeftIcon size={24} />
2310
- </ChatHeaderIconButton>
2311
- <div className="flex min-w-0 flex-1 items-center px-[var(--spacing-system-mf)]">
2312
- <span className="min-w-0 flex-1 truncate text-ods-text-primary text-h3">Chat Archive</span>
2313
- </div>
2314
- </>
2315
- ) : (
2316
- // "Current Chats" rail header — title fills, then the
2317
- // search + archive controls. When search is open the
2318
- // inline field takes over the title area in place
2319
- // (Figma 116:51217) and the magnifier toggle is hidden.
2320
- <>
2321
- {railSearchOpen && mingoCaps.onSearchChange ? (
2322
- <ChatHeaderSearchField
2323
- initialValue={mingoCaps.searchQuery}
2324
- onSearchChange={mingoCaps.onSearchChange}
2325
- onCollapse={() => setRailSearchOpen(false)}
2326
- />
2327
- ) : (
2328
- <>
2329
- <div className="flex min-w-0 flex-1 items-center px-[var(--spacing-system-mf)]">
2330
- <span className="min-w-0 flex-1 truncate text-ods-text-primary text-h3">
2331
- Current Chats
2332
- </span>
2333
- </div>
2334
- {mingoCaps.onSearchChange && (
2335
- <ChatHeaderIconButton
2336
- onClick={() => setRailSearchOpen(o => !o)}
2337
- aria-label="Search chats"
2338
- aria-pressed={railSearchOpen}
2339
- >
2340
- <SearchIcon size={24} />
2341
- </ChatHeaderIconButton>
2342
- )}
2343
- </>
2344
- )}
2345
- {headerOnOpenArchive && (
2346
- <ChatHeaderIconButton onClick={headerOnOpenArchive} aria-label="Chat archive">
2347
- <ClockHistoryIcon size={24} />
2401
+ {archiveOpen && !splitActive ? (
2402
+ <div key="archive-view" className="flex min-h-0 flex-1 flex-col duration-200 animate-in fade-in-0">
2403
+ <ChatArchivePage
2404
+ dialogs={archivedDialogs}
2405
+ onSelectDialog={handleArchivedSelect}
2406
+ onBack={closeArchive}
2407
+ onClose={closeControl}
2408
+ isLoading={archivedLoading}
2409
+ isFetching={archivedPending}
2410
+ hasMore={archivedCursor != null}
2411
+ onLoadMore={() => {
2412
+ void loadArchivedPage(archivedCursor ?? undefined);
2413
+ }}
2414
+ />
2415
+ </div>
2416
+ ) : (
2417
+ <div
2418
+ key="chat-view"
2419
+ className={cn(
2420
+ 'min-h-0 flex-1 duration-200 animate-in fade-in-0',
2421
+ // v2: the list runs the full height beside a column of header,
2422
+ // banner and chat; each child below takes its cell explicitly.
2423
+ isV2 ? 'grid grid-cols-[auto_minmax(0,1fr)] grid-rows-[auto_auto_minmax(0,1fr)]' : 'flex flex-col',
2424
+ )}
2425
+ >
2426
+ {isV2 && railPresence.mounted && (
2427
+ <RailSlot
2428
+ active={railPresence.active}
2429
+ narrow
2430
+ className="col-start-1 row-span-3 row-start-1"
2431
+ innerClassName="flex min-h-0 flex-col"
2432
+ >
2433
+ <MingoChatRail {...mingoRailProps} className="w-full border-r-0" />
2434
+ </RailSlot>
2435
+ )}
2436
+ {isV2 ? (
2437
+ v2Header
2438
+ ) : showWideHeader ? (
2439
+ // Wide Mingo header (Figma 113:60931 / 113:63630). Split: a
2440
+ // two-cell bar — the "Current Chats" rail header (search /
2441
+ // archive / collapse) over the 320px rail + the chat-block header
2442
+ // (title / ⋯ / close) over the fill column. Collapsed
2443
+ // (`wideCollapsed`): the rail cell is gone, leaving the corner
2444
+ // expand toggle + the chat-block header (the fill column shows
2445
+ // the chat / new-chat welcome). No back chevron — chat-list
2446
+ // navigation is the rail toggle. Shown whenever the panel is wide
2447
+ // enough to split (width ≥ `SPLIT_MIN_WIDTH`), NOT gated on
2448
+ // the viewport `md` breakpoint — otherwise a 720–799px panel on a
2449
+ // sub-`md` viewport would split the body yet drop to the mobile
2450
+ // single-bar header (no rail search / collapse controls).
2451
+ <div className="flex h-14 w-full flex-shrink-0 overflow-hidden border-b border-ods-border bg-ods-card">
2452
+ {railPresence.mounted && (
2453
+ <RailSlot active={railPresence.active} innerClassName="flex overflow-hidden">
2454
+ {archiveOpen ? (
2455
+ // Archive open in the wide layout — the rail header
2456
+ // turns into a "Chat Archive" back bar (the list itself
2457
+ // renders in the rail below); the chat block is untouched.
2458
+ // Back is a full-height leading cell, matching the other
2459
+ // header cells (not a small inline chevron).
2460
+ <>
2461
+ <ChatHeaderIconButton divider="right" onClick={closeArchive} aria-label="Back to chats">
2462
+ <Chevron02LeftIcon size={24} />
2348
2463
  </ChatHeaderIconButton>
2349
- )}
2350
- </>
2351
- )}
2352
- </RailSlot>
2353
- )}
2464
+ <div className="flex min-w-0 flex-1 items-center px-[var(--spacing-system-mf)]">
2465
+ <span className="min-w-0 flex-1 truncate text-ods-text-primary text-h3">
2466
+ Chat Archive
2467
+ </span>
2468
+ </div>
2469
+ </>
2470
+ ) : (
2471
+ // "Current Chats" rail header — title fills, then the
2472
+ // search + archive controls. When search is open the
2473
+ // inline field takes over the title area in place
2474
+ // (Figma 116:51217) and the magnifier toggle is hidden.
2475
+ <>
2476
+ {railSearchOpen && mingoCaps.onSearchChange ? (
2477
+ <ChatHeaderSearchField
2478
+ initialValue={mingoCaps.searchQuery}
2479
+ onSearchChange={mingoCaps.onSearchChange}
2480
+ onCollapse={() => setRailSearchOpen(false)}
2481
+ />
2482
+ ) : (
2483
+ <>
2484
+ <div className="flex min-w-0 flex-1 items-center px-[var(--spacing-system-mf)]">
2485
+ <span className="min-w-0 flex-1 truncate text-ods-text-primary text-h3">
2486
+ Current Chats
2487
+ </span>
2488
+ </div>
2489
+ {mingoCaps.onSearchChange && (
2490
+ <ChatHeaderIconButton
2491
+ onClick={() => setRailSearchOpen(o => !o)}
2492
+ aria-label="Search chats"
2493
+ aria-pressed={railSearchOpen}
2494
+ >
2495
+ <SearchIcon size={24} />
2496
+ </ChatHeaderIconButton>
2497
+ )}
2498
+ </>
2499
+ )}
2500
+ {headerOnOpenArchive && (
2501
+ <ChatHeaderIconButton onClick={headerOnOpenArchive} aria-label="Chat archive">
2502
+ <ClockHistoryIcon size={24} />
2503
+ </ChatHeaderIconButton>
2504
+ )}
2505
+ </>
2506
+ )}
2507
+ </RailSlot>
2508
+ )}
2354
2509
 
2355
- <div className="flex min-w-0 flex-1">
2356
- {/* Rail collapse / expand toggle — lives on the chat side,
2510
+ <div className="flex min-w-0 flex-1">
2511
+ {/* Rail collapse / expand toggle — lives on the chat side,
2357
2512
  at its left edge (Figma 259:90465). Right arrow collapses
2358
2513
  the list; left arrow brings it back. */}
2359
- <button
2360
- type="button"
2361
- onClick={toggleRailCollapsed}
2362
- aria-label={splitActive ? 'Collapse chat list' : 'Show chat list'}
2363
- aria-pressed={!splitActive}
2364
- className="flex size-14 shrink-0 items-center justify-center border-r border-ods-border text-ods-text-secondary transition-colors hover:bg-ods-bg-hover hover:text-ods-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
2365
- >
2366
- {splitActive ? <Arrow02RightIcon size={24} /> : <Arrow02LeftIcon size={24} />}
2367
- </button>
2368
- {/* No back cell in the wide layout — the left rail (and
2514
+ <button
2515
+ type="button"
2516
+ onClick={toggleRailCollapsed}
2517
+ aria-label={splitActive ? 'Collapse chat list' : 'Show chat list'}
2518
+ aria-pressed={!splitActive}
2519
+ className="flex size-14 shrink-0 items-center justify-center border-r border-ods-border text-ods-text-secondary transition-colors hover:bg-ods-bg-hover hover:text-ods-text-primary focus:outline-none focus-visible:ring-2 focus-visible:ring-ods-accent"
2520
+ >
2521
+ {splitActive ? <Arrow02RightIcon size={24} /> : <Arrow02LeftIcon size={24} />}
2522
+ </button>
2523
+ {/* No back cell in the wide layout — the left rail (and
2369
2524
  its collapse/expand toggle) already provides chat-list
2370
2525
  navigation, so a back chevron would be redundant. */}
2371
- <div className="flex min-w-0 flex-1 items-center gap-[var(--spacing-system-m)] px-[var(--spacing-system-mf)] py-[var(--spacing-system-sf)]">
2372
- <div className="flex min-w-0 flex-col">
2373
- <p className="truncate leading-tight text-ods-text-primary text-h3">
2374
- {headerShowBack || isGuideEmpty ? headerTitle : freshConversationTitle}
2375
- </p>
2376
- {headerPersonName && (
2377
- <p className="truncate leading-tight text-ods-text-secondary text-h6">{headerPersonName}</p>
2378
- )}
2526
+ <div className="flex min-w-0 flex-1 items-center gap-[var(--spacing-system-m)] px-[var(--spacing-system-mf)] py-[var(--spacing-system-sf)]">
2527
+ <div className="flex min-w-0 flex-col">
2528
+ <p className="truncate leading-tight text-ods-text-primary text-h3">
2529
+ {headerShowBack || isGuideEmpty ? headerTitle : freshConversationTitle}
2530
+ </p>
2531
+ {headerPersonName && (
2532
+ <p className="truncate leading-tight text-ods-text-secondary text-h6">{headerPersonName}</p>
2533
+ )}
2534
+ </div>
2535
+ {headerAvatar ? (
2536
+ // Owner avatar at the title cell's right edge (Figma 113:63273).
2537
+ <SquareAvatar
2538
+ variant="round"
2539
+ sizePx={32}
2540
+ className="ml-auto"
2541
+ src={headerAvatar.avatarUrl || undefined}
2542
+ alt={headerAvatar.name || undefined}
2543
+ fallback={headerAvatar.name || undefined}
2544
+ initialsClassName="text-[11px] text-ods-text-secondary"
2545
+ title={headerAvatar.name || undefined}
2546
+ />
2547
+ ) : null}
2379
2548
  </div>
2380
- {headerAvatar ? (
2381
- // Owner avatar at the title cell's right edge (Figma 113:63273).
2382
- <SquareAvatar
2383
- variant="round"
2384
- sizePx={32}
2385
- className="ml-auto"
2386
- src={headerAvatar.avatarUrl || undefined}
2387
- alt={headerAvatar.name || undefined}
2388
- fallback={headerAvatar.name || undefined}
2389
- initialsClassName="text-[11px] text-ods-text-secondary"
2390
- title={headerAvatar.name || undefined}
2549
+
2550
+ {isViewingArchived && headerOnRestore && (
2551
+ <ChatHeaderIconButton onClick={headerOnRestore} aria-label="Unarchive chat">
2552
+ <Refresh01LeftIcon size={24} />
2553
+ </ChatHeaderIconButton>
2554
+ )}
2555
+ {headerShowBack && !isViewingArchived && splitHeaderMenuItems.length > 0 && (
2556
+ <ActionsMenuDropdown
2557
+ triggerAriaLabel="Chat actions"
2558
+ onCloseAutoFocus={e => e.preventDefault()}
2559
+ groups={[{ items: splitHeaderMenuItems }]}
2560
+ customTrigger={
2561
+ <ChatHeaderIconButton aria-label="Chat actions">
2562
+ <Ellipsis01Icon size={24} />
2563
+ </ChatHeaderIconButton>
2564
+ }
2391
2565
  />
2392
- ) : null}
2566
+ )}
2567
+ {closeControl && (
2568
+ <ChatHeaderIconButton onClick={closeControl} aria-label="Close">
2569
+ <XmarkIcon size={24} />
2570
+ </ChatHeaderIconButton>
2571
+ )}
2393
2572
  </div>
2394
-
2395
- {isViewingArchived && headerOnRestore && (
2396
- <ChatHeaderIconButton onClick={headerOnRestore} aria-label="Unarchive chat">
2397
- <Refresh01LeftIcon size={24} />
2398
- </ChatHeaderIconButton>
2399
- )}
2400
- {headerShowBack && !isViewingArchived && splitHeaderMenuItems.length > 0 && (
2401
- <ActionsMenuDropdown
2402
- triggerAriaLabel="Chat actions"
2403
- onCloseAutoFocus={e => e.preventDefault()}
2404
- groups={[{ items: splitHeaderMenuItems }]}
2405
- customTrigger={
2406
- <ChatHeaderIconButton aria-label="Chat actions">
2407
- <Ellipsis01Icon size={24} />
2408
- </ChatHeaderIconButton>
2409
- }
2410
- />
2411
- )}
2412
- <ChatHeaderIconButton onClick={handleClose} aria-label="Close">
2413
- <XmarkIcon size={24} />
2414
- </ChatHeaderIconButton>
2415
2573
  </div>
2416
- </div>
2417
- ) : (
2418
- // Narrow single-column header — list / compose / conversation /
2419
- // guide, resolved into `narrowHeaderProps` above. (Wide layouts,
2420
- // including rail-collapsed, use the wide header above.) Embedded
2421
- // previews (hero demo tabs) force the compact bar via `compact`.
2422
- <ChatPanelHeader {...narrowHeaderProps} compact={previewMode} />
2423
- )}
2424
-
2425
- {/* Guide-mode indicator banner (Figma node 7532:328222) —
2574
+ ) : (
2575
+ // Narrow single-column header — list / compose / conversation /
2576
+ // guide, resolved into `narrowHeaderProps` above. (Wide layouts,
2577
+ // including rail-collapsed, use the wide header above.) Embedded
2578
+ // previews (hero demo tabs) force the compact bar via `compact`.
2579
+ <ChatPanelHeader {...narrowHeaderProps} compact={previewMode} />
2580
+ )}
2581
+
2582
+ {/* Guide-mode indicator banner (Figma node 7532:328222) —
2426
2583
  full-bleed accent strip below the header. Shown only when
2427
2584
  Guide mode is active AND the default (Mingo) mode also exists:
2428
2585
  the banner contrasts the temporary Guide session against the
2429
2586
  default chat, so it's meaningless in a guide-only setup. */}
2430
- {activeMode === 'guide' && hasMingoMode && (
2431
- <GuideModeBanner className="duration-200 animate-in fade-in-0" />
2432
- )}
2587
+ {activeMode === 'guide' && hasMingoMode && (
2588
+ <GuideModeBanner
2589
+ className={cn('duration-200 animate-in fade-in-0', isV2 && 'col-start-2 row-start-2')}
2590
+ />
2591
+ )}
2433
2592
 
2434
- {/* Chat-panel row. In the wide Mingo layout (`splitActive`) the
2593
+ {/* Chat-panel row. In the wide Mingo layout (`splitActive`) the
2435
2594
  dialog history is hoisted into a fixed 320px "Current Chats"
2436
2595
  rail on the left; the stacked layout keeps it inline in the
2437
2596
  Mingo empty state (`<MingoChatHistory>` via `<MingoWelcome>`). */}
2438
- <div className="flex min-h-0 flex-1 overflow-hidden">
2439
- {railPresence.mounted && (
2440
- <RailSlot active={railPresence.active} innerClassName="flex flex-col min-h-0 bg-ods-card">
2441
- {archiveOpen ? (
2442
- // Archive list inside the rail (header lives in the split
2443
- // header's left cell above). Right chat block stays put.
2444
- <ChatArchivePage
2445
- embedded
2597
+ <div className={cn('flex min-h-0 flex-1 overflow-hidden', isV2 && 'col-start-2 row-start-3 min-w-0')}>
2598
+ {!isV2 && railPresence.mounted && (
2599
+ <RailSlot active={railPresence.active} innerClassName="flex flex-col min-h-0 bg-ods-card">
2600
+ {archiveOpen ? (
2601
+ // Archive list inside the rail (header lives in the split
2602
+ // header's left cell above). Right chat block stays put.
2603
+ <ChatArchivePage
2604
+ embedded
2605
+ dialogs={archivedDialogs}
2606
+ onSelectDialog={handleArchivedSelect}
2607
+ isLoading={archivedLoading}
2608
+ isFetching={archivedPending}
2609
+ hasMore={archivedCursor != null}
2610
+ onLoadMore={() => {
2611
+ void loadArchivedPage(archivedCursor ?? undefined);
2612
+ }}
2613
+ />
2614
+ ) : (
2615
+ <MingoHistoryRail
2616
+ dialogs={dialogs}
2617
+ activeDialogId={activeDialogId ?? undefined}
2618
+ onSelectDialog={handleSelectDialog}
2619
+ onNewChat={handleNewChat}
2620
+ onRequestRename={mingoCaps.canRename ? setRenameTarget : undefined}
2621
+ onRequestArchive={mingoCaps.canArchive ? setArchiveTarget : undefined}
2622
+ onRequestCopyLink={mingoCaps.onCopyLink}
2623
+ onRequestCompact={mingoCaps.compactDialog}
2624
+ scope={dialogScope}
2625
+ onScopeChange={setDialogScope}
2626
+ searchQuery={mingoCaps.searchQuery}
2627
+ hasMore={hasMoreDialogs}
2628
+ isLoadingMore={isDialogsLoading && dialogs.length > 0}
2629
+ onLoadMore={() => {
2630
+ void loadMoreDialogs();
2631
+ }}
2632
+ isLoadingHistory={dialogsInitialLoading}
2633
+ loadError={dialogsLoadError}
2634
+ onRetry={reloadDialogs}
2635
+ />
2636
+ )}
2637
+ </RailSlot>
2638
+ )}
2639
+ <div className={cn('flex min-h-0 min-w-0 flex-1 flex-col', isV2 && 'ods-glow-accent-corner')}>
2640
+ {isV2 && archiveOpen ? (
2641
+ // v2: the archive opens in the chat column under the chat's
2642
+ // header; the list stays, and "Chat Archive" there toggles back.
2643
+ <MingoArchiveList
2446
2644
  dialogs={archivedDialogs}
2447
2645
  onSelectDialog={handleArchivedSelect}
2448
2646
  isLoading={archivedLoading}
2449
- isFetching={archivedPending}
2450
2647
  hasMore={archivedCursor != null}
2648
+ isLoadingMore={archivedPending}
2451
2649
  onLoadMore={() => {
2452
2650
  void loadArchivedPage(archivedCursor ?? undefined);
2453
2651
  }}
2454
2652
  />
2455
- ) : (
2653
+ ) : stackedListView && isV2 ? (
2654
+ <MingoChatRail
2655
+ {...mingoRailProps}
2656
+ onNewChat={() => setComposeOpen(true)}
2657
+ className="w-full border-r-0 duration-200 animate-in fade-in-0"
2658
+ />
2659
+ ) : stackedListView ? (
2660
+ // Narrow "Current Chats" list (Figma 341:36190) — the default
2661
+ // single-column view. No composer here; "Start New Chat" opens
2662
+ // the compose view.
2456
2663
  <MingoHistoryRail
2664
+ className="duration-200 animate-in fade-in-0"
2457
2665
  dialogs={dialogs}
2458
2666
  activeDialogId={activeDialogId ?? undefined}
2459
2667
  onSelectDialog={handleSelectDialog}
2460
- onNewChat={handleNewChat}
2668
+ onNewChat={() => setComposeOpen(true)}
2461
2669
  onRequestRename={mingoCaps.canRename ? setRenameTarget : undefined}
2462
2670
  onRequestArchive={mingoCaps.canArchive ? setArchiveTarget : undefined}
2463
2671
  onRequestCopyLink={mingoCaps.onCopyLink}
@@ -2474,317 +2682,297 @@ function EmbeddableChatInner({
2474
2682
  loadError={dialogsLoadError}
2475
2683
  onRetry={reloadDialogs}
2476
2684
  />
2477
- )}
2478
- </RailSlot>
2479
- )}
2480
- <div className="flex min-h-0 min-w-0 flex-1 flex-col">
2481
- {stackedListView ? (
2482
- // Narrow "Current Chats" list (Figma 341:36190) — the default
2483
- // single-column view. No composer here; "Start New Chat" opens
2484
- // the compose view.
2485
- <MingoHistoryRail
2486
- className="duration-200 animate-in fade-in-0"
2487
- dialogs={dialogs}
2488
- activeDialogId={activeDialogId ?? undefined}
2489
- onSelectDialog={handleSelectDialog}
2490
- onNewChat={() => setComposeOpen(true)}
2491
- onRequestRename={mingoCaps.canRename ? setRenameTarget : undefined}
2492
- onRequestArchive={mingoCaps.canArchive ? setArchiveTarget : undefined}
2493
- onRequestCopyLink={mingoCaps.onCopyLink}
2494
- onRequestCompact={mingoCaps.compactDialog}
2495
- scope={dialogScope}
2496
- onScopeChange={setDialogScope}
2497
- searchQuery={mingoCaps.searchQuery}
2498
- hasMore={hasMoreDialogs}
2499
- isLoadingMore={isDialogsLoading && dialogs.length > 0}
2500
- onLoadMore={() => {
2501
- void loadMoreDialogs();
2502
- }}
2503
- isLoadingHistory={dialogsInitialLoading}
2504
- loadError={dialogsLoadError}
2505
- onRetry={reloadDialogs}
2506
- />
2507
- ) : (
2508
- <>
2509
- <div ref={galleryPanelRef} className="flex min-h-0 flex-1 flex-col p-[var(--spacing-system-m)]">
2510
- <div
2511
- key={contentViewKey}
2512
- className="flex min-h-0 flex-1 flex-col duration-200 animate-in fade-in-0"
2513
- >
2514
- {hasConversation ? (
2515
- <div className="flex min-h-0 flex-1 flex-col">
2516
- {messagesInitialLoading ? (
2517
- // Match the real list's `fullWidth` layout so swapping
2518
- // skeleton → bubbles doesn't shift the column. The panel
2519
- // wrapper above already pads (`p-[var(--spacing-system-m)]`),
2520
- // so the skeleton sits flush (no inner px/pb).
2521
- //
2522
- // In `previewMode` the chat lives in a fixed-height host box
2523
- // (e.g. the Mingo hero demo). The default bottom-anchored
2524
- // skeleton overflows a short panel and grows its OWN
2525
- // scrollbar — ugly. `fill` top-anchors + clips overflow so
2526
- // it reads as "the whole panel is loading" and never scrolls.
2527
- <ChatMessageListSkeleton fullWidth fill={previewMode} className="flex-1" />
2528
- ) : (
2529
- <ChatMessageList
2530
- messages={messages}
2531
- isTyping={chatLoading}
2532
- // The stage the turn reports while the user waits
2533
- // ("Searching 28 sources"); the generic phrase otherwise.
2534
- typingMessage={streamingProgress ? chatProgressLabel(streamingProgress) : undefined}
2535
- // Sticky footer for approvals the host lifted out of the
2536
- // thread — see the prop's docblock.
2537
- pendingApprovals={pendingApprovals}
2538
- // Real drawer: the library's smart follow. Passive in-page
2539
- // demo (previewMode): deterministic hard pin instead — a
2540
- // scripted assistant-only stream from a cold mount never
2541
- // satisfies the library's "at bottom" gate, so the reply
2542
- // landed below the fold. `pinBottom` snaps to bottom on
2543
- // every frame; identical mechanism to the Fae demo box, so
2544
- // both surfaces behave 1:1.
2545
- autoScroll={!previewMode}
2546
- pinBottom={previewMode}
2547
- // Passive in-page demos (previewMode) let the surrounding
2548
- // page scroll over the thread; the real drawer keeps
2549
- // containment (deck slide-scroll fix).
2550
- overscrollContain={!previewMode}
2551
- assistantType="mingo"
2552
- assistantIcon={mingoAssistantIcon}
2553
- renderEntityCard={renderEntityCard}
2554
- resolveContextIcon={resolveContextIcon}
2555
- renderContextItem={renderContextItem}
2556
- renderMention={renderMention}
2557
- renderAfterMessage={renderMessageSources}
2558
- // Gated on `chatLoading` for the same reason the composer
2559
- // is: no second send while a turn is in flight. Passive
2560
- // demo hosts (previewMode) stay read-only.
2561
- onAskSelect={chatLoading || previewMode ? undefined : handleAskSelect}
2562
- NavLinkAnchor={NavLinkAnchorViaRuntime}
2563
- // Real Mingo drawer: hide the message-list scrollbar
2564
- // (scroll stays functional). Scoped here via `className`
2565
- // instead of `ChatMessageList` itself, so other list
2566
- // consumers (host chat, tickets) keep their thin bar.
2685
+ ) : (
2686
+ <>
2687
+ <div ref={galleryPanelRef} className="flex min-h-0 flex-1 flex-col p-[var(--spacing-system-m)]">
2688
+ <div
2689
+ key={contentViewKey}
2690
+ className="flex min-h-0 flex-1 flex-col duration-200 animate-in fade-in-0"
2691
+ >
2692
+ {hasConversation ? (
2693
+ <div className="flex min-h-0 flex-1 flex-col">
2694
+ {messagesInitialLoading ? (
2695
+ // Match the real list's `fullWidth` layout so swapping
2696
+ // skeleton → bubbles doesn't shift the column. The panel
2697
+ // wrapper above already pads (`p-[var(--spacing-system-m)]`),
2698
+ // so the skeleton sits flush (no inner px/pb).
2567
2699
  //
2568
- // `previewMode` (the passive in-page demo) instead:
2569
- // • KEEP the default thin scrollbar — the "elevator" —
2570
- // so the Mingo demo's scroll controls match the Fae
2571
- // demo box (which renders the default ChatMessageList
2572
- // scrollbar) 1:1.
2573
- // • Re-enable pointer events on the scroller: previewMode
2574
- // puts `pointer-events-none` on the whole panel (line
2575
- // ~1981) so the demo doesn't trap clicks, but that also
2576
- // killed wheel/touch scroll on the thread (the Mingo
2577
- // embed couldn't scroll while the Fae box could). Only
2578
- // the scroller re-enables; composer + chrome stay inert.
2579
- className={
2580
- previewMode
2581
- ? 'pointer-events-auto flex-1'
2582
- : 'flex-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'
2583
- }
2584
- // No inner `px`/`pb`: the panel wrapper already pads with
2585
- // `p-[var(--spacing-system-m)]`. The default content class
2586
- // adds `px-[var(--spacing-system-m)]` + `pb-…xs`, which
2587
- // double-inset the thread; `pb-0` overrides via twMerge.
2588
- contentClassName="max-w-none pb-0"
2589
- fullWidth
2590
- hasNextPage={hasMoreMessages}
2591
- isFetchingNextPage={isMessagesLoading}
2592
- onLoadMore={loadMoreMessages}
2593
- />
2594
- )}
2595
- </div>
2596
- ) : activeMode === 'mingo' ? (
2597
- /* Figma node 7532:222444 — default (Mingo-mode) empty state:
2700
+ // In `previewMode` the chat lives in a fixed-height host box
2701
+ // (e.g. the Mingo hero demo). The default bottom-anchored
2702
+ // skeleton overflows a short panel and grows its OWN
2703
+ // scrollbar — ugly. `fill` top-anchors + clips overflow so
2704
+ // it reads as "the whole panel is loading" and never scrolls.
2705
+ <ChatMessageListSkeleton fullWidth fill={previewMode} className="flex-1" />
2706
+ ) : (
2707
+ <ChatMessageList
2708
+ messages={messages}
2709
+ // v2 thread: names without the colon, 24px
2710
+ // faces, 16px between messages (shared with fae v2).
2711
+ appearance={appearance}
2712
+ isTyping={chatLoading}
2713
+ // The stage the turn reports while the user waits
2714
+ // ("Searching 28 sources"); the generic phrase otherwise.
2715
+ typingMessage={streamingProgress ? chatProgressLabel(streamingProgress) : undefined}
2716
+ // Sticky footer for approvals the host lifted out of the
2717
+ // thread — see the prop's docblock.
2718
+ pendingApprovals={pendingApprovals}
2719
+ // Real drawer: the library's smart follow. Passive in-page
2720
+ // demo (previewMode): deterministic hard pin instead — a
2721
+ // scripted assistant-only stream from a cold mount never
2722
+ // satisfies the library's "at bottom" gate, so the reply
2723
+ // landed below the fold. `pinBottom` snaps to bottom on
2724
+ // every frame; identical mechanism to the Fae demo box, so
2725
+ // both surfaces behave 1:1.
2726
+ autoScroll={!previewMode}
2727
+ pinBottom={previewMode}
2728
+ // Passive in-page demos (previewMode) let the surrounding
2729
+ // page scroll over the thread; the real drawer keeps
2730
+ // containment (deck slide-scroll fix).
2731
+ overscrollContain={!previewMode}
2732
+ assistantType="mingo"
2733
+ assistantIcon={mingoAssistantIcon}
2734
+ renderEntityCard={renderEntityCard}
2735
+ resolveContextIcon={resolveContextIcon}
2736
+ renderContextItem={renderContextItem}
2737
+ renderMention={renderMention}
2738
+ renderAfterMessage={renderMessageSources}
2739
+ // Gated on `chatLoading` for the same reason the composer
2740
+ // is: no second send while a turn is in flight. Passive
2741
+ // demo hosts (previewMode) stay read-only.
2742
+ onAskSelect={chatLoading || previewMode ? undefined : handleAskSelect}
2743
+ NavLinkAnchor={NavLinkAnchorViaRuntime}
2744
+ // Real Mingo drawer: hide the message-list scrollbar
2745
+ // (scroll stays functional). Scoped here via `className`
2746
+ // instead of `ChatMessageList` itself, so other list
2747
+ // consumers (host chat, tickets) keep their thin bar.
2748
+ //
2749
+ // `previewMode` (the passive in-page demo) instead:
2750
+ // • KEEP the default thin scrollbar — the "elevator" —
2751
+ // so the Mingo demo's scroll controls match the Fae
2752
+ // demo box (which renders the default ChatMessageList
2753
+ // scrollbar) 1:1.
2754
+ // • Re-enable pointer events on the scroller: previewMode
2755
+ // puts `pointer-events-none` on the whole panel (line
2756
+ // ~1981) so the demo doesn't trap clicks, but that also
2757
+ // killed wheel/touch scroll on the thread (the Mingo
2758
+ // embed couldn't scroll while the Fae box could). Only
2759
+ // the scroller re-enables; composer + chrome stay inert.
2760
+ className={
2761
+ previewMode
2762
+ ? 'pointer-events-auto flex-1'
2763
+ : 'flex-1 [scrollbar-width:none] [&::-webkit-scrollbar]:hidden'
2764
+ }
2765
+ // No inner `px`/`pb`: the panel wrapper already pads with
2766
+ // `p-[var(--spacing-system-m)]`. The default content class
2767
+ // adds `px-[var(--spacing-system-m)]` + `pb-…xs`, which
2768
+ // double-inset the thread; `pb-0` overrides via twMerge.
2769
+ contentClassName="max-w-none pb-0"
2770
+ fullWidth
2771
+ hasNextPage={hasMoreMessages}
2772
+ isFetchingNextPage={isMessagesLoading}
2773
+ onLoadMore={loadMoreMessages}
2774
+ />
2775
+ )}
2776
+ </div>
2777
+ ) : activeMode === 'mingo' ? (
2778
+ /* Figma node 7532:222444 — default (Mingo-mode) empty state:
2598
2779
  centred greeting + capability grid + Guide-chat promo +
2599
2780
  quick-action chips. Guide mode keeps the slash-command
2600
2781
  onboarding list below. */
2601
- <MingoWelcome
2602
- onStartGuideChat={
2603
- effectiveModes.guide ? () => handleActiveModeChange('guide') : undefined
2604
- }
2605
- {...mingoWelcome}
2606
- // History now lives in its own list view (the wide rail or
2607
- // the narrow "Current Chats" list), so the welcome is always
2608
- // the new-user compose surface: greeting + grid + promo +
2609
- // primary Guide chip.
2610
- hasExistingChats={false}
2611
- // Hover/focus PREVIEWS the action's full prompt as ghost text
2612
- // in the empty composer (see `quickActionPreview` /
2613
- // `ChatInput.previewText`). Wired after the `{...mingoWelcome}`
2614
- // spread so the host can't override it.
2615
- onQuickActionHover={action => setQuickActionPreview(action.prompt ?? action.label)}
2616
- onQuickActionHoverEnd={() => setQuickActionPreview(null)}
2617
- // A built-in agent caps the quick-action wall at 2 rows;
2618
- // this is the Mingo surface, so fall back to 'mingo'.
2619
- agentSlug={activeAgentSlug ?? 'mingo'}
2620
- />
2621
- ) : (
2622
- /* Figma node 7532:328214 — Guide-mode empty state: greeting
2782
+ <MingoWelcome
2783
+ onStartGuideChat={
2784
+ effectiveModes.guide ? () => handleActiveModeChange('guide') : undefined
2785
+ }
2786
+ {...mingoWelcome}
2787
+ // History now lives in its own list view (the wide rail or
2788
+ // the narrow "Current Chats" list), so the welcome is always
2789
+ // the new-user compose surface: greeting + grid + promo +
2790
+ // primary Guide chip.
2791
+ hasExistingChats={false}
2792
+ // Hover/focus PREVIEWS the action's full prompt as ghost text
2793
+ // in the empty composer (see `quickActionPreview` /
2794
+ // `ChatInput.previewText`). Wired after the `{...mingoWelcome}`
2795
+ // spread so the host can't override it.
2796
+ onQuickActionHover={action => setQuickActionPreview(action.prompt ?? action.label)}
2797
+ onQuickActionHoverEnd={() => setQuickActionPreview(null)}
2798
+ // A built-in agent caps the quick-action wall at 2 rows;
2799
+ // this is the Mingo surface, so fall back to 'mingo'.
2800
+ agentSlug={activeAgentSlug ?? 'mingo'}
2801
+ />
2802
+ ) : (
2803
+ /* Figma node 7532:328214 — Guide-mode empty state: greeting
2623
2804
  + slash-command onboarding list share one scroll region,
2624
2805
  with a pinned quick-action chip row above the composer. */
2625
- <GuideWelcome
2626
- // Admin/host greeting customises the guide subtitle; an
2627
- // explicit `guideWelcome.subtitle` still wins (spread below).
2628
- subtitle={effectiveGreeting ?? undefined}
2629
- // While the admin greeting is still being fetched, render a
2630
- // subtitle skeleton instead of flashing empty → text.
2631
- subtitleLoading={emptyStateLoading}
2632
- {...guideWelcome}
2633
- // Agent identity (name → title, icon → empty-state glyph)
2634
- // wins over the host `guideWelcome` spread + built-in
2635
- // defaults; falls back to the host value when no agent is
2636
- // active. Placed after the spread so resolution is
2637
- // deterministic.
2638
- title={effectiveAssistantName ?? guideWelcome?.title}
2639
- icon={effectiveAssistantIcon ?? guideWelcome?.icon}
2640
- // Admin "try-asking chips" → Guide quick-action chips.
2641
- // Precedence: when an AGENT is active and its fetched chips
2642
- // arrived, the agent's chips win — the host array is the
2643
- // platform default, not the agent's. (`agentConfigUrl` is
2644
- // the same derived value that drives the config fetch, so
2645
- // this can never disagree with what was fetched; while the
2646
- // fetch is in-flight the fetched list is [] and host chips
2647
- // show.) Outside agent mode the host-provided
2648
- // `guideWelcome.quickActions` wins as before.
2649
- quickActions={
2650
- agentConfigUrl && guideSuggestedActions.length > 0
2651
- ? guideSuggestedActions
2652
- : (guideWelcome?.quickActions ?? guideSuggestedActions)
2653
- }
2654
- // Quick-action chips SEND the prompt immediately on click.
2655
- onQuickAction={action => {
2656
- setQuickActionPreview(null);
2657
- handleSend(action.prompt ?? action.label);
2658
- }}
2659
- // Hover/focus PREVIEWS the action's full prompt as ghost text
2660
- // in the empty composer (the chip label is short; this reveals
2661
- // what will be sent). Declarative + non-destructive — see
2662
- // `quickActionPreview` / `ChatInput.previewText`.
2663
- onQuickActionHover={action => setQuickActionPreview(action.prompt ?? action.label)}
2664
- onQuickActionHoverEnd={() => setQuickActionPreview(null)}
2665
- // In agent mode a built-in agent (fae/mingo) caps the wall at
2666
- // 2 rows; host/guide mode (no active agent) keeps the default.
2667
- agentSlug={activeAgentSlug}
2668
- >
2669
- {/* Figma node 7363:205938 — single-column slash-command
2806
+ <GuideWelcome
2807
+ // Admin/host greeting customises the guide subtitle; an
2808
+ // explicit `guideWelcome.subtitle` still wins (spread below).
2809
+ subtitle={effectiveGreeting ?? undefined}
2810
+ // While the admin greeting is still being fetched, render a
2811
+ // subtitle skeleton instead of flashing empty → text.
2812
+ subtitleLoading={emptyStateLoading}
2813
+ {...guideWelcome}
2814
+ // Agent identity (name → title, icon → empty-state glyph)
2815
+ // wins over the host `guideWelcome` spread + built-in
2816
+ // defaults; falls back to the host value when no agent is
2817
+ // active. Placed after the spread so resolution is
2818
+ // deterministic.
2819
+ title={effectiveAssistantName ?? guideWelcome?.title}
2820
+ icon={effectiveAssistantIcon ?? guideWelcome?.icon}
2821
+ // Admin "try-asking chips" → Guide quick-action chips.
2822
+ // Precedence: when an AGENT is active and its fetched chips
2823
+ // arrived, the agent's chips win — the host array is the
2824
+ // platform default, not the agent's. (`agentConfigUrl` is
2825
+ // the same derived value that drives the config fetch, so
2826
+ // this can never disagree with what was fetched; while the
2827
+ // fetch is in-flight the fetched list is [] and host chips
2828
+ // show.) Outside agent mode the host-provided
2829
+ // `guideWelcome.quickActions` wins as before.
2830
+ quickActions={
2831
+ agentConfigUrl && guideSuggestedActions.length > 0
2832
+ ? guideSuggestedActions
2833
+ : (guideWelcome?.quickActions ?? guideSuggestedActions)
2834
+ }
2835
+ // Quick-action chips SEND the prompt immediately on click.
2836
+ onQuickAction={action => {
2837
+ setQuickActionPreview(null);
2838
+ handleSend(action.prompt ?? action.label);
2839
+ }}
2840
+ // Hover/focus PREVIEWS the action's full prompt as ghost text
2841
+ // in the empty composer (the chip label is short; this reveals
2842
+ // what will be sent). Declarative + non-destructive — see
2843
+ // `quickActionPreview` / `ChatInput.previewText`.
2844
+ onQuickActionHover={action => setQuickActionPreview(action.prompt ?? action.label)}
2845
+ onQuickActionHoverEnd={() => setQuickActionPreview(null)}
2846
+ // In agent mode a built-in agent (fae/mingo) caps the wall at
2847
+ // 2 rows; host/guide mode (no active agent) keeps the default.
2848
+ agentSlug={activeAgentSlug}
2849
+ >
2850
+ {/* Figma node 7363:205938 — single-column slash-command
2670
2851
  list. No own scroll (GuideWelcome's region scrolls); the
2671
2852
  rounded-md frame holds the cards on the dark surface. */}
2672
- {(chipCommands.length > 0 || !commandsLoaded) && (
2673
- <div className="shrink-0 overflow-hidden rounded-md border border-ods-border">
2674
- {!commandsLoaded &&
2675
- chipCommands.length === 0 &&
2676
- SKELETON_ROW_VARIANTS.map((variant, i) => (
2677
- <MingoOnboardingCardSkeleton
2678
- key={`chip-skeleton-${i}`}
2679
- titleWidth={variant.titleWidth}
2680
- slashWidth={variant.slashWidth}
2681
- descriptionLines={variant.descriptionLines}
2682
- />
2683
- ))}
2684
- {chipCommands.map(cmd => {
2685
- const Icon = resolveIcon(cmd.iconName);
2686
- const cmdId = cmd.id;
2687
- const label = cmd.label ?? `/${cmdId}`;
2688
- const cardActions = cmd.actions.map(action => ({
2689
- id: action.id,
2690
- label: action.label,
2691
- onClick: () => dispatchSlashCommandAction(action.id, cmdId, chatInputRef),
2692
- }));
2693
- return (
2694
- <MingoOnboardingCard
2695
- key={cmdId}
2696
- icon={<Icon size={16} />}
2697
- title={label}
2698
- slashCommand={`/${cmdId}`}
2699
- description={cmd.description}
2700
- actions={cardActions}
2701
- />
2702
- );
2703
- })}
2704
- </div>
2705
- )}
2706
- </GuideWelcome>
2707
- )}
2853
+ {(chipCommands.length > 0 || !commandsLoaded) && (
2854
+ <div className="shrink-0 overflow-hidden rounded-md border border-ods-border">
2855
+ {!commandsLoaded &&
2856
+ chipCommands.length === 0 &&
2857
+ SKELETON_ROW_VARIANTS.map((variant, i) => (
2858
+ <MingoOnboardingCardSkeleton
2859
+ key={`chip-skeleton-${i}`}
2860
+ titleWidth={variant.titleWidth}
2861
+ slashWidth={variant.slashWidth}
2862
+ descriptionLines={variant.descriptionLines}
2863
+ />
2864
+ ))}
2865
+ {chipCommands.map(cmd => {
2866
+ const Icon = resolveIcon(cmd.iconName);
2867
+ const cmdId = cmd.id;
2868
+ const label = cmd.label ?? `/${cmdId}`;
2869
+ const cardActions = cmd.actions.map(action => ({
2870
+ id: action.id,
2871
+ label: action.label,
2872
+ onClick: () => dispatchSlashCommandAction(action.id, cmdId, chatInputRef),
2873
+ }));
2874
+ return (
2875
+ <MingoOnboardingCard
2876
+ key={cmdId}
2877
+ icon={<Icon size={16} />}
2878
+ title={label}
2879
+ slashCommand={`/${cmdId}`}
2880
+ description={cmd.description}
2881
+ actions={cardActions}
2882
+ />
2883
+ );
2884
+ })}
2885
+ </div>
2886
+ )}
2887
+ </GuideWelcome>
2888
+ )}
2889
+ </div>
2708
2890
  </div>
2709
- </div>
2710
2891
 
2711
- <ChatAttachmentChipStrip
2712
- attachments={stagedAttachments}
2713
- onRemove={removeAttachment}
2714
- disabled={chatLoading}
2715
- />
2892
+ <ChatAttachmentChipStrip
2893
+ attachments={stagedAttachments}
2894
+ onRemove={removeAttachment}
2895
+ disabled={chatLoading}
2896
+ />
2716
2897
 
2717
- <ChatComposer
2718
- key={composerKey}
2719
- archived={isViewingArchived}
2720
- lock={composerLock}
2721
- inputRef={chatInputRef}
2722
- onSend={handleSend}
2723
- onStop={stopMessage}
2724
- sending={chatLoading || hasInflightUploads}
2725
- placeholder={hasInflightUploads ? 'Waiting for uploads to finish…' : 'Ask a question...'}
2726
- autoFocus={previewMode ? false : autoFocusInput}
2727
- slashCommands={slashCommandsProp}
2728
- previewText={quickActionPreview ?? undefined}
2729
- showAttachmentButton={attachmentsEnabled && activeMode === 'guide'}
2730
- attachmentsCount={stagedAttachments.length}
2731
- onAddFiles={addAttachmentFiles}
2732
- attachmentsDisabled={chatLoading}
2733
- contextPicker={contextPickerForMode}
2734
- selectedContextItems={contextItems}
2735
- onToggleContextItem={toggleContextItem}
2736
- onRemoveContextItem={removeContextItem}
2737
- contextMemoryItems={contextMemory?.items}
2738
- onRemoveContextMemoryItem={contextMemory?.onRemove}
2739
- contextPickerOpen={contextPickerOpen}
2740
- onOpenContextPicker={openContextPicker}
2741
- onCloseContextPicker={closeContextPicker}
2742
- mentionQuery={mentionQuery}
2743
- onMentionQueryChange={handleMentionQueryChange}
2744
- onValueChange={handleContextValueChange}
2745
- model={{
2746
- provider: currentProvider ?? 'anthropic',
2747
- modelName: currentModelLabel ?? 'Claude',
2748
- usedTokens:
2749
- currentInputTokens != null
2750
- ? (currentInputTokens ?? 0) + (currentOutputTokens ?? 0)
2751
- : undefined,
2752
- contextWindow: currentContextWindowMaxTokens ?? undefined,
2753
- inputTokens: currentInputTokens ?? undefined,
2754
- outputTokens: currentOutputTokens ?? undefined,
2755
- hitRatePct: currentCacheHitRatePct ?? undefined,
2756
- breakdown: currentUsageBreakdown ?? undefined,
2757
- }}
2758
- />
2759
- </>
2760
- )}
2898
+ <ChatComposer
2899
+ key={composerKey}
2900
+ archived={isViewingArchived}
2901
+ lock={composerLock}
2902
+ inputRef={chatInputRef}
2903
+ onSend={handleSend}
2904
+ onStop={stopMessage}
2905
+ sending={chatLoading || hasInflightUploads}
2906
+ placeholder={
2907
+ hasInflightUploads
2908
+ ? 'Waiting for uploads to finish…'
2909
+ : isV2
2910
+ ? 'Enter your Request...'
2911
+ : 'Ask a question...'
2912
+ }
2913
+ autoFocus={previewMode ? false : autoFocusInput}
2914
+ slashCommands={slashCommandsProp}
2915
+ previewText={quickActionPreview ?? undefined}
2916
+ showAttachmentButton={attachmentsEnabled && activeMode === 'guide'}
2917
+ attachmentsCount={stagedAttachments.length}
2918
+ onAddFiles={addAttachmentFiles}
2919
+ attachmentsDisabled={chatLoading}
2920
+ contextPicker={contextPickerForMode}
2921
+ selectedContextItems={contextItems}
2922
+ onToggleContextItem={toggleContextItem}
2923
+ onRemoveContextItem={removeContextItem}
2924
+ contextMemoryItems={contextMemory?.items}
2925
+ onRemoveContextMemoryItem={contextMemory?.onRemove}
2926
+ contextPickerOpen={contextPickerOpen}
2927
+ onOpenContextPicker={openContextPicker}
2928
+ onCloseContextPicker={closeContextPicker}
2929
+ mentionQuery={mentionQuery}
2930
+ onMentionQueryChange={handleMentionQueryChange}
2931
+ onValueChange={handleContextValueChange}
2932
+ model={{
2933
+ provider: currentProvider ?? 'anthropic',
2934
+ modelName: currentModelLabel ?? 'Claude',
2935
+ usedTokens:
2936
+ currentInputTokens != null
2937
+ ? (currentInputTokens ?? 0) + (currentOutputTokens ?? 0)
2938
+ : undefined,
2939
+ contextWindow: currentContextWindowMaxTokens ?? undefined,
2940
+ inputTokens: currentInputTokens ?? undefined,
2941
+ outputTokens: currentOutputTokens ?? undefined,
2942
+ hitRatePct: currentCacheHitRatePct ?? undefined,
2943
+ breakdown: currentUsageBreakdown ?? undefined,
2944
+ }}
2945
+ />
2946
+ </>
2947
+ )}
2948
+ </div>
2761
2949
  </div>
2762
2950
  </div>
2763
- </div>
2764
- )}
2765
- </div>
2766
- {galleryModal}
2951
+ )}
2952
+ </div>
2953
+ {galleryModal}
2767
2954
 
2768
- {/* Rename / Archive / Unarchive chat modals (Figma 7592:225962,
2955
+ {/* Rename / Archive / Unarchive chat modals (Figma 7592:225962,
2769
2956
  7592:226181). Triggered from the header ⋯ and the dialog-history
2770
2957
  rows; rendered inside the panel so they overlay the chat. */}
2771
- <ChatDialogModals
2772
- renameTarget={renameTarget}
2773
- setRenameTarget={setRenameTarget}
2774
- onConfirmRename={handleConfirmRename}
2775
- archiveTarget={archiveTarget}
2776
- setArchiveTarget={setArchiveTarget}
2777
- onConfirmArchive={handleConfirmArchive}
2778
- restoreTarget={restoreTarget}
2779
- setRestoreTarget={setRestoreTarget}
2780
- onConfirmRestore={handleConfirmRestore}
2781
- />
2958
+ <ChatDialogModals
2959
+ renameTarget={renameTarget}
2960
+ setRenameTarget={setRenameTarget}
2961
+ onConfirmRename={handleConfirmRename}
2962
+ archiveTarget={archiveTarget}
2963
+ setArchiveTarget={setArchiveTarget}
2964
+ onConfirmArchive={handleConfirmArchive}
2965
+ restoreTarget={restoreTarget}
2966
+ setRestoreTarget={setRestoreTarget}
2967
+ onConfirmRestore={handleConfirmRestore}
2968
+ />
2782
2969
 
2783
- {/* Portal target for in-panel Radix overlays — `display: contents`
2970
+ {/* Portal target for in-panel Radix overlays — `display: contents`
2784
2971
  so it adds no box; content is positioned `fixed` by Radix. */}
2785
- <div ref={setPortalHost} style={{ display: 'contents' }} />
2786
- </CollisionBoundaryContext.Provider>
2787
- </PortalContainerContext.Provider>
2972
+ <div ref={setPortalHost} style={{ display: 'contents' }} />
2973
+ </CollisionBoundaryContext.Provider>
2974
+ </PortalContainerContext.Provider>
2975
+ </ChatAppearanceContext.Provider>
2788
2976
  );
2789
2977
 
2790
2978
  // Conditional shell: inline (shell-less host, e.g. AppLayoutDrawer, which
@@ -2796,9 +2984,11 @@ function EmbeddableChatInner({
2796
2984
  accessible name — Radix warns when a Dialog.Content has no Title. The
2797
2985
  panel supplies its own visually-hidden title here, mirroring the
2798
2986
  drawer-shell branch below, so it's accessible regardless of host. */}
2799
- <VisuallyHidden>
2800
- <DialogPrimitive.Title>{sourceLabel} AI Assistant</DialogPrimitive.Title>
2801
- </VisuallyHidden>
2987
+ {shell === 'none' && (
2988
+ <VisuallyHidden>
2989
+ <DialogPrimitive.Title>{sourceLabel} AI Assistant</DialogPrimitive.Title>
2990
+ </VisuallyHidden>
2991
+ )}
2802
2992
  {body}
2803
2993
  </ChatPanelContext.Provider>
2804
2994
  ) : (