@nextclaw/ui 0.15.21 → 0.15.23

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 (476) hide show
  1. package/CHANGELOG.md +69 -0
  2. package/dist/assets/{abnfDiagram-VRR7QNED-M4g_AyR8.js → abnfDiagram-VRR7QNED-DvTjgVMf.js} +1 -1
  3. package/dist/assets/{aiden0z-pptx-renderer.es-Bd99vut2.js → aiden0z-pptx-renderer.es-C1nfBAXR.js} +1 -1
  4. package/dist/assets/api-Glw2OGXK.js +13 -0
  5. package/dist/assets/appearance-settings-page-Bl_W_9m7.js +1 -0
  6. package/dist/assets/{arc-IO7WNWOB.js → arc-BvhODBXv.js} +1 -1
  7. package/dist/assets/architecture-TIHT7OUA-Bjp9jf0h.js +1 -0
  8. package/dist/assets/{architectureDiagram-ZJ3FMSHR-Dc-lvdUh.js → architectureDiagram-ZJ3FMSHR-BKO9RgNS.js} +1 -1
  9. package/dist/assets/{blockDiagram-677ZJIJ3-CcsoWpIY.js → blockDiagram-677ZJIJ3-DHpDXy7D.js} +1 -1
  10. package/dist/assets/{c4Diagram-LMCZKHZV-Bsrtg6b2.js → c4Diagram-LMCZKHZV-BG_iQnDr.js} +1 -1
  11. package/dist/assets/channel-5T3pdR-s.js +1 -0
  12. package/dist/assets/channels-list-page-C7E4Sxwd.js +8 -0
  13. package/dist/assets/chat-page-BT6dNI-4.js +1 -0
  14. package/dist/assets/chunk-2Q5K7J3B-DTZ3tGfE.js +1 -0
  15. package/dist/assets/{chunk-32BRIVSS-Bt08t8oG.js → chunk-32BRIVSS-DR9M0LLx.js} +1 -1
  16. package/dist/assets/{chunk-52WLFC77-Ddx68Xzx.js → chunk-52WLFC77-698iC3BB.js} +1 -1
  17. package/dist/assets/{chunk-5FCAYU7R-B7p9_q55.js → chunk-5FCAYU7R-DikTj9Ey.js} +1 -1
  18. package/dist/assets/{chunk-5HE753X5-D52KvdTm.js → chunk-5HE753X5-ko0o7_q0.js} +1 -1
  19. package/dist/assets/{chunk-5JV3BV7I-C329h8nI.js → chunk-5JV3BV7I-CEHufhgo.js} +1 -1
  20. package/dist/assets/{chunk-5TONJI2A-BZyUBxWt.js → chunk-5TONJI2A-CGgno1cu.js} +1 -1
  21. package/dist/assets/{chunk-5VM5RSS4-BUiYXZmu.js → chunk-5VM5RSS4-DkiB4cUQ.js} +1 -1
  22. package/dist/assets/{chunk-7BUUIJ7U-B7o-mn4r.js → chunk-7BUUIJ7U-DzptcytQ.js} +1 -1
  23. package/dist/assets/{chunk-BIQX33UG-Bey9GoRP.js → chunk-BIQX33UG-DMRSQJvT.js} +1 -1
  24. package/dist/assets/{chunk-C7G6YPKG-C6tosszM.js → chunk-C7G6YPKG-Bw1BwwuY.js} +1 -1
  25. package/dist/assets/{chunk-CQNSW5MT-BNrdkgGQ.js → chunk-CQNSW5MT-C0z6eHBk.js} +1 -1
  26. package/dist/assets/{chunk-CYSBUYHQ-DHODFeyB.js → chunk-CYSBUYHQ-ByPyAouO.js} +1 -1
  27. package/dist/assets/{chunk-EMLP6XTP-BcmLjrcq.js → chunk-EMLP6XTP-CJlAXWZg.js} +1 -1
  28. package/dist/assets/{chunk-EX3LRPZG-r-_eEtJI.js → chunk-EX3LRPZG-Dqq4zKJu.js} +1 -1
  29. package/dist/assets/{chunk-FWX5IMBZ-DOObDWbf.js → chunk-FWX5IMBZ-B0SNoCmi.js} +2 -2
  30. package/dist/assets/{chunk-HOUHSVGY-Bd0FnbWZ.js → chunk-HOUHSVGY-DG_GJih_.js} +1 -1
  31. package/dist/assets/{chunk-ICXQ74PX-ntNwAm4_.js → chunk-ICXQ74PX-Drxk_6bE.js} +1 -1
  32. package/dist/assets/{chunk-JG7HCLWE-YtRpblZN.js → chunk-JG7HCLWE-DdLFPpnd.js} +1 -1
  33. package/dist/assets/{chunk-JWPE2WC7-De5-D9ta.js → chunk-JWPE2WC7-B8M4G76F.js} +1 -1
  34. package/dist/assets/{chunk-MOJQB5TN-CdvKhyp2.js → chunk-MOJQB5TN-XP6FJHY_.js} +1 -1
  35. package/dist/assets/{chunk-MOZMSUNE-DJMMN_lu.js → chunk-MOZMSUNE-C_6x18Qw.js} +1 -1
  36. package/dist/assets/{chunk-OGEWGWER-DOFWVw3O.js → chunk-OGEWGWER-N25BzCmG.js} +1 -1
  37. package/dist/assets/{chunk-OSBZ3O6U-DZkrIN9r.js → chunk-OSBZ3O6U-ASDEm7y0.js} +1 -1
  38. package/dist/assets/{chunk-PUDLZKDR-D1wma2Yf.js → chunk-PUDLZKDR-CTlvAcn7.js} +1 -1
  39. package/dist/assets/{chunk-Q4XR5HBZ-CjPy94Lz.js → chunk-Q4XR5HBZ-Da9BWfB6.js} +1 -1
  40. package/dist/assets/{chunk-QBLGF6JB-Hy1bEgS-.js → chunk-QBLGF6JB-Bi6RpTJO.js} +1 -1
  41. package/dist/assets/{chunk-R7FJI6CG-CUPIHyh-.js → chunk-R7FJI6CG-DkMsSPvK.js} +1 -1
  42. package/dist/assets/{chunk-RYQCIY6F-D5Ua4pcv.js → chunk-RYQCIY6F-DZp0eU-j.js} +1 -1
  43. package/dist/assets/{chunk-U6XO7XAA-DaUQcqb7.js → chunk-U6XO7XAA-vfinDSrj.js} +1 -1
  44. package/dist/assets/{chunk-V7JOEXUC-DX2zLfQe.js → chunk-V7JOEXUC-C9Nksccs.js} +1 -1
  45. package/dist/assets/chunk-VAUOI2AC-HLGIHQ6k.js +1 -0
  46. package/dist/assets/{chunk-VR4S4FIN-CFA-WQik.js → chunk-VR4S4FIN-Xv6zfwXs.js} +1 -1
  47. package/dist/assets/{chunk-WYO6CB5R-Bk2HeDvJ.js → chunk-WYO6CB5R-mOJNzbWl.js} +2 -2
  48. package/dist/assets/chunk-XXDRQBXY-Dl0m6Ni-.js +1 -0
  49. package/dist/assets/{chunk-YOTPTUD7-DUG4FpBN.js → chunk-YOTPTUD7-1-aKSu4o.js} +1 -1
  50. package/dist/assets/{chunk-ZGVPDNZ5-70JdIGo0.js → chunk-ZGVPDNZ5-OJ8veMpK.js} +1 -1
  51. package/dist/assets/{chunk-ZIRB5QZD-XZdgBt5L.js → chunk-ZIRB5QZD-SR_Nq_PP.js} +1 -1
  52. package/dist/assets/classDiagram-OUVF2IWQ-DcDnzGNX.js +1 -0
  53. package/dist/assets/classDiagram-v2-EOCWNBFH-BKXKMzKM.js +1 -0
  54. package/dist/assets/config-split-page-D5BgtKrm.js +1 -0
  55. package/dist/assets/confirm-dialog-Bf907Azu.js +5 -0
  56. package/dist/assets/{cose-bilkent-JH36ORCC-B-PQoGX-.js → cose-bilkent-JH36ORCC-NjVgz2Bw.js} +1 -1
  57. package/dist/assets/cynefin-VYW2F7L2-BWg7U2UD.js +1 -0
  58. package/dist/assets/{cynefinDiagram-TSTJHNR4-BoLCPEM0.js → cynefinDiagram-TSTJHNR4-B_17LDqD.js} +1 -1
  59. package/dist/assets/{dagre-D7Ie4eXr.js → dagre-CMe101gk.js} +1 -1
  60. package/dist/assets/{dagre-VKFMJZFB-CKRrwZA-.js → dagre-VKFMJZFB-qo8nLb3B.js} +1 -1
  61. package/dist/assets/desktop-update-config-BwuyBPJb.js +1 -0
  62. package/dist/assets/{diagram-FQU43EPY-CASL1J3_.js → diagram-FQU43EPY-Ba3XIklC.js} +1 -1
  63. package/dist/assets/{diagram-G47NLZAW-CvLPBIzf.js → diagram-G47NLZAW-CG45TcUx.js} +1 -1
  64. package/dist/assets/{diagram-NH7WQ7WH-Ba6fGdYP.js → diagram-NH7WQ7WH-2F2HrwV-.js} +1 -1
  65. package/dist/assets/{diagram-OA4YK3LP-DkpfgEjM.js → diagram-OA4YK3LP-CXfWH9NZ.js} +1 -1
  66. package/dist/assets/{diagram-WEI45ONY-BP5UMVsN.js → diagram-WEI45ONY-CH_4yepc.js} +1 -1
  67. package/dist/assets/dist-CtLyEJeO.js +1 -0
  68. package/dist/assets/dist-dv_SUR54.js +1 -0
  69. package/dist/assets/doc-browser-C23NDGh5.js +1 -0
  70. package/dist/assets/doc-browser-DlOOhBxI.js +1 -0
  71. package/dist/assets/doc-browser-context-BIBjkwfV.js +1 -0
  72. package/dist/assets/{docx-preview-D0tp3a9T.js → docx-preview-CoM4JK7_.js} +1 -1
  73. package/dist/assets/{ebnfDiagram-CCIWWBDH-TJKbUcmc.js → ebnfDiagram-CCIWWBDH-BwiG4Ea1.js} +1 -1
  74. package/dist/assets/{erDiagram-Q63AITRT-DzGPKkKH.js → erDiagram-Q63AITRT-BML3hpnQ.js} +1 -1
  75. package/dist/assets/eventmodeling-45OFAUF4-SB-J4tpq.js +1 -0
  76. package/dist/assets/flowDiagram-23GEKE2U-BQ_u-eLM.js +1 -0
  77. package/dist/assets/{form-actions-DvvxCpL4.js → form-actions-DAzi68Cn.js} +1 -1
  78. package/dist/assets/{ganttDiagram-NO4QXBWP-JhJzsGp2.js → ganttDiagram-NO4QXBWP-80enfWbD.js} +1 -1
  79. package/dist/assets/gitGraph-TEB2WS4Q-DPxNwsFd.js +1 -0
  80. package/dist/assets/{gitGraphDiagram-IHSO6WYX-DZ1CKMj8.js → gitGraphDiagram-IHSO6WYX-CVLqXr-b.js} +1 -1
  81. package/dist/assets/host-capabilities-D9C4CBK5.js +1 -0
  82. package/dist/assets/index-BLNg2yg0.css +1 -0
  83. package/dist/assets/index-D3EKjEyK.js +107 -0
  84. package/dist/assets/info-DKCQHKI2-DNlNGH90.js +1 -0
  85. package/dist/assets/{infoDiagram-FWYZ7A6U-BNXNKfRQ.js → infoDiagram-FWYZ7A6U-Dyvs2t0i.js} +1 -1
  86. package/dist/assets/{ishikawaDiagram-FXEZZL3T-DoiEfjNw.js → ishikawaDiagram-FXEZZL3T-sSnxDKfh.js} +1 -1
  87. package/dist/assets/{journeyDiagram-5HDEW3XC-D6oK2HSy.js → journeyDiagram-5HDEW3XC-D22V71Mf.js} +1 -1
  88. package/dist/assets/{kanban-definition-HUTT4EX6-B2UnLOH-.js → kanban-definition-HUTT4EX6-D3ds0p-g.js} +1 -1
  89. package/dist/assets/{line-BM7PoxY5.js → line-CbyFs065.js} +1 -1
  90. package/dist/assets/{linear-B3DiqMjk.js → linear-4DKtnZBS.js} +1 -1
  91. package/dist/assets/loader-circle-BSg5u0r4.js +1 -0
  92. package/dist/assets/{map-BAAdHTqR.js → map-CcZzEoz6.js} +1 -1
  93. package/dist/assets/mcp-marketplace-page-ByhvQQj5.js +9 -0
  94. package/dist/assets/mcp-marketplace-page-CytXwnUs.js +1 -0
  95. package/dist/assets/{mermaid-parser.core-BeMviefY.js → mermaid-parser.core-DPmMOjXD.js} +2 -2
  96. package/dist/assets/{mermaid.core-D0Q77r9I.js → mermaid.core-BSf-ddRa.js} +3 -3
  97. package/dist/assets/{mindmap-definition-LN4V7U3C-BQ7r7bR8.js → mindmap-definition-LN4V7U3C-BUTwWPyx.js} +1 -1
  98. package/dist/assets/model-config-page-bgEx3MjQ.js +1 -0
  99. package/dist/assets/navigation-link-CB0OQbnY.js +3 -0
  100. package/dist/assets/{ordinal-Bt2N4IkA.js → ordinal-BgLYtOZE.js} +1 -1
  101. package/dist/assets/packet-7NZHBO7P-BpeiqkBZ.js +1 -0
  102. package/dist/assets/{pegDiagram-2B236MQR-B-C9-LGz.js → pegDiagram-2B236MQR-BEu_ZBjc.js} +1 -1
  103. package/dist/assets/pie-RZYD4A2V-Cy1yxsPd.js +1 -0
  104. package/dist/assets/{pieDiagram-ENE6RG2P-CtirZLtn.js → pieDiagram-ENE6RG2P-CAwAwuBd.js} +1 -1
  105. package/dist/assets/plus-CNjWdLhZ.js +1 -0
  106. package/dist/assets/provider-scoped-model-input-8F6JMd2n.js +1 -0
  107. package/dist/assets/providers-config-page-CxpTjR81.js +1 -0
  108. package/dist/assets/{quadrantDiagram-ABIIQ3AL-DiBWAzDU.js → quadrantDiagram-ABIIQ3AL-CwE6F-_2.js} +1 -1
  109. package/dist/assets/radar-I7S5WNFK-CEcPKCj8.js +1 -0
  110. package/dist/assets/railroad-3IZDKUUU-CW38eNDs.js +1 -0
  111. package/dist/assets/railroad-abnf-AHOZXSZD-ByrylcXf.js +1 -0
  112. package/dist/assets/railroad-ebnf-EBAXGLYW-CNB6GwP-.js +1 -0
  113. package/dist/assets/railroad-peg-LSFZ7HO6-x6hnqlBG.js +1 -0
  114. package/dist/assets/{railroadDiagram-RFXS5EU6-CP6b3PBI.js → railroadDiagram-RFXS5EU6-xgLekU3B.js} +1 -1
  115. package/dist/assets/react-Bk2ydNTe.js +8 -0
  116. package/dist/assets/{refresh-cw-C2e94pmp.js → refresh-cw-Ba0LZou4.js} +1 -1
  117. package/dist/assets/remote-Dv38PuMV.js +1 -0
  118. package/dist/assets/{requirementDiagram-TGXJPOKE-D8YV0-Jz.js → requirementDiagram-TGXJPOKE-CyVUzEX5.js} +1 -1
  119. package/dist/assets/rotate-cw-IkYVk9JX.js +1 -0
  120. package/dist/assets/runtime-config-page-C0ByyUVr.js +1 -0
  121. package/dist/assets/{sankeyDiagram-HTMAVEWB-C2vA4ojE.js → sankeyDiagram-HTMAVEWB-JK6bFmYB.js} +1 -1
  122. package/dist/assets/{save-CkwDOgvt.js → save-DTOAvqzt.js} +1 -1
  123. package/dist/assets/{search-CLXM2reO.js → search-D4X5DNMe.js} +1 -1
  124. package/dist/assets/search-config-page-Cr6254K5.js +1 -0
  125. package/dist/assets/{secrets-config-page-DQ_Curaj.js → secrets-config-page-W2Hx332d.js} +2 -2
  126. package/dist/assets/security-config-1E8OF4kR.js +1 -0
  127. package/dist/assets/select-CDv0g2Pj.js +41 -0
  128. package/dist/assets/{sequenceDiagram-DBY2YBRQ-B9LBmpUo.js → sequenceDiagram-DBY2YBRQ-BR5IokXV.js} +1 -1
  129. package/dist/assets/{settings-2-B9IT2HGd.js → settings-2-CoxzNABp.js} +1 -1
  130. package/dist/assets/{sizeCapture-X5ZJPWSS-9UIfya9-.js → sizeCapture-X5ZJPWSS-SxG_YnMA.js} +1 -1
  131. package/dist/assets/skeleton-DW018fJv.js +1 -0
  132. package/dist/assets/{src-DKf77Yzo.js → src-vgz8E_-O.js} +1 -1
  133. package/dist/assets/{stateDiagram-2N3HPSRC-oTW44vds.js → stateDiagram-2N3HPSRC-DD4OoYhn.js} +1 -1
  134. package/dist/assets/stateDiagram-v2-6OUMAXLB-ZC6dFORv.js +1 -0
  135. package/dist/assets/{swimlanes-5IMT3BWC-Cf7lcsay.js → swimlanes-5IMT3BWC-BWkFeyCT.js} +2 -2
  136. package/dist/assets/swimlanesDiagram-G3AALYLV-CRkh51Im.js +8 -0
  137. package/dist/assets/{tag-chip-CDkB5smU.js → tag-chip-CjkZEoS2.js} +1 -1
  138. package/dist/assets/{timeline-definition-FHXFAJF6-DoCuWKDJ.js → timeline-definition-FHXFAJF6-VTRPy6KA.js} +1 -1
  139. package/dist/assets/treeView-QDETBFTQ-Bw0DSCH7.js +1 -0
  140. package/dist/assets/treemap-6X3UGDF4-DFWBFcfh.js +1 -0
  141. package/dist/assets/use-confirm-dialog-CavtY1vJ.js +1 -0
  142. package/dist/assets/{vennDiagram-L72KCM5P-CpU0pzGe.js → vennDiagram-L72KCM5P-Bvk42Pek.js} +1 -1
  143. package/dist/assets/wardley-OPB4EBWU-CoWturk0.js +1 -0
  144. package/dist/assets/{wardleyDiagram-EHGQE667-CkN6nn6-.js → wardleyDiagram-EHGQE667-DOFjx2s3.js} +1 -1
  145. package/dist/assets/x-PTXYTEwz.js +1 -0
  146. package/dist/assets/{xychartDiagram-FW5EYKEG-DATpapm2.js → xychartDiagram-FW5EYKEG-DY5yRTUc.js} +1 -1
  147. package/dist/index.html +30 -32
  148. package/index.html +8 -0
  149. package/package.json +8 -8
  150. package/src/app/components/layout/page-layout.tsx +15 -6
  151. package/src/app/components/layout/sidebar-rail.styles.ts +0 -2
  152. package/src/app/components/layout/sidebar.tsx +2 -2
  153. package/src/app/styles/charcoal-theme.css +85 -0
  154. package/src/app/styles/design-system.css +23 -13
  155. package/src/features/agents/components/__tests__/agents-page.test.tsx +36 -24
  156. package/src/features/agents/components/agent-advanced-config-fields.tsx +7 -7
  157. package/src/features/agents/components/agent-details-dialog.tsx +8 -8
  158. package/src/features/agents/components/agent-dialogs.tsx +7 -7
  159. package/src/features/agents/components/agents-page.tsx +42 -62
  160. package/src/features/chat/components/chat-sidebar-list-mode-switch.tsx +2 -2
  161. package/src/features/chat/components/conversation/__tests__/chat-conversation-content.test.tsx +46 -4
  162. package/src/features/chat/components/conversation/__tests__/chat-conversation-header-section.test.tsx +17 -1
  163. package/src/features/chat/components/conversation/__tests__/chat-conversation-panel.test.tsx +8 -22
  164. package/src/features/chat/components/conversation/__tests__/chat-conversation-workspace-section.test.tsx +69 -3
  165. package/src/features/chat/components/conversation/chat-conversation-content.tsx +24 -11
  166. package/src/features/chat/components/conversation/chat-conversation-header-section.tsx +30 -31
  167. package/src/features/chat/components/conversation/chat-conversation-header.tsx +1 -1
  168. package/src/features/chat/components/conversation/chat-conversation-panel.tsx +1 -8
  169. package/src/features/chat/components/conversation/chat-conversation-workspace-section.tsx +16 -4
  170. package/src/features/chat/components/layout/__tests__/chat-page-shell.test.tsx +23 -0
  171. package/src/features/chat/components/layout/__tests__/chat-sidebar-read-state.test.tsx +1 -1
  172. package/src/features/chat/components/layout/__tests__/chat-sidebar-session-area.test.tsx +52 -0
  173. package/src/features/chat/components/layout/__tests__/chat-sidebar-toolbar.test.tsx +6 -0
  174. package/src/features/chat/components/layout/__tests__/chat-sidebar.test.tsx +11 -17
  175. package/src/features/chat/components/layout/chat-page-shell.tsx +7 -15
  176. package/src/features/chat/components/layout/chat-sidebar-desktop-layout.tsx +32 -42
  177. package/src/features/chat/components/layout/chat-sidebar-toolbar.tsx +16 -67
  178. package/src/features/chat/components/layout/chat-sidebar-utility-menu.tsx +1 -4
  179. package/src/features/chat/components/providers/chat-presenter.provider.tsx +2 -0
  180. package/src/features/chat/features/conversation/components/__tests__/session-conversation-area.test.tsx +84 -12
  181. package/src/features/chat/features/conversation/components/__tests__/session-conversation-input.streaming.test.tsx +258 -9
  182. package/src/features/chat/features/conversation/components/session-conversation-area.tsx +51 -34
  183. package/src/features/chat/features/conversation/components/session-conversation-input.tsx +50 -39
  184. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-controller.test.tsx +128 -1
  185. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-input-state.test.tsx +67 -1
  186. package/src/features/chat/features/conversation/hooks/__tests__/use-session-conversation-slash-commands.test.tsx +4 -0
  187. package/src/features/chat/features/conversation/hooks/__tests__/use-session-provider-model-catalog.test.tsx +109 -0
  188. package/src/features/chat/features/conversation/hooks/use-chat-composer-reference-intent.ts +46 -0
  189. package/src/features/chat/features/conversation/hooks/use-session-conversation-composer-nodes.ts +26 -0
  190. package/src/features/chat/features/conversation/hooks/use-session-conversation-controller.ts +45 -12
  191. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-attachments.ts +1 -0
  192. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-query.ts +28 -3
  193. package/src/features/chat/features/conversation/hooks/use-session-conversation-input-state.ts +91 -37
  194. package/src/features/chat/features/conversation/hooks/use-session-conversation-recovery-actions.ts +64 -0
  195. package/src/features/chat/features/conversation/hooks/use-session-conversation-slash-commands.ts +2 -0
  196. package/src/features/chat/features/conversation/hooks/use-session-provider-model-catalog.ts +84 -0
  197. package/src/features/chat/features/conversation/utils/__tests__/session-message-composer.utils.test.ts +34 -0
  198. package/src/features/chat/features/conversation/utils/session-conversation-input-toolbar.utils.ts +18 -0
  199. package/src/features/chat/features/conversation/utils/session-message-composer.utils.ts +117 -0
  200. package/src/features/chat/features/conversation/utils/session-queued-input.utils.ts +10 -97
  201. package/src/features/chat/features/input/input-surface-plugins/__tests__/slash-command-plugin.test.ts +2 -1
  202. package/src/features/chat/features/input/input-surface-plugins/slash-command-plugin.utils.ts +3 -1
  203. package/src/features/chat/features/input/utils/__tests__/chat-composer-state.utils.test.ts +22 -1
  204. package/src/features/chat/features/input/utils/__tests__/chat-inline-token.utils.test.ts +23 -0
  205. package/src/features/chat/features/input/utils/__tests__/chat-input-bar.utils.test.ts +105 -0
  206. package/src/features/chat/features/input/utils/chat-composer-state.utils.ts +27 -2
  207. package/src/features/chat/features/input/utils/chat-composer-token-protocol.utils.ts +5 -0
  208. package/src/features/chat/features/input/utils/chat-inline-token.utils.ts +97 -32
  209. package/src/features/chat/features/input/utils/chat-input-bar.utils.ts +1 -1
  210. package/src/features/chat/features/input/utils/chat-input-toolbar.utils.ts +64 -10
  211. package/src/features/chat/features/message/components/__tests__/chat-message-assistant-header.container.test.tsx +149 -0
  212. package/src/features/chat/features/message/components/__tests__/chat-message-inline-editor.test.tsx +59 -0
  213. package/src/features/chat/features/message/components/__tests__/chat-message-list.container.test.tsx +123 -90
  214. package/src/features/chat/features/message/components/__tests__/chat-message-timeline-dividers.test.tsx +43 -0
  215. package/src/features/chat/features/message/components/chat-message-inline-editor.tsx +111 -0
  216. package/src/features/chat/features/message/components/chat-message-list.container.tsx +81 -172
  217. package/src/features/chat/features/message/components/chat-message-timeline-dividers.tsx +75 -0
  218. package/src/features/chat/features/message/hooks/use-chat-inline-token-actions.ts +125 -0
  219. package/src/features/chat/features/message/hooks/use-chat-message-actions.tsx +187 -0
  220. package/src/features/chat/features/message/utils/__tests__/chat-message-action-targets.utils.test.ts +89 -0
  221. package/src/features/chat/features/message/utils/__tests__/chat-message-timeline-compaction.utils.test.ts +194 -0
  222. package/src/features/chat/features/message/utils/__tests__/chat-message-timeline-continuation-compaction.utils.test.ts +167 -0
  223. package/src/features/chat/features/message/utils/__tests__/chat-message-timeline.utils.test.ts +203 -0
  224. package/src/features/chat/features/message/utils/__tests__/chat-message.utils.test.ts +26 -0
  225. package/src/features/chat/features/message/utils/chat-message-action-targets.utils.ts +28 -0
  226. package/src/features/chat/features/message/utils/chat-message-part.utils.ts +38 -0
  227. package/src/features/chat/features/message/utils/chat-message-texts.utils.ts +1 -0
  228. package/src/features/chat/features/message/utils/chat-message-timeline.utils.ts +204 -7
  229. package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-agent-runtime.test.tsx +180 -1
  230. package/src/features/chat/features/ncp/hooks/__tests__/use-ncp-session-conversation.test.tsx +184 -10
  231. package/src/features/chat/features/ncp/hooks/use-hydrated-ncp-agent.integration.test.tsx +57 -0
  232. package/src/features/chat/features/ncp/hooks/use-ncp-session-conversation.ts +90 -1
  233. package/src/features/chat/features/ncp/utils/__tests__/ncp-chat-query-derived.utils.test.ts +110 -0
  234. package/src/features/chat/features/ncp/utils/ncp-chat-query-derived.utils.ts +65 -3
  235. package/src/features/chat/features/session/components/chat-sidebar-session-item.tsx +10 -10
  236. package/src/features/chat/features/session/components/chat-sidebar-session-list.tsx +2 -2
  237. package/src/features/chat/features/session/components/session-header/__tests__/chat-session-header-actions.test.tsx +17 -0
  238. package/src/features/chat/features/session/components/session-header/chat-session-header-actions.tsx +63 -54
  239. package/src/features/chat/features/session/components/session-header/chat-session-header-menu-item.tsx +1 -1
  240. package/src/features/chat/features/session/components/session-header/chat-session-project-badge.tsx +1 -1
  241. package/src/features/chat/features/session/components/session-header/chat-session-switcher-groups.tsx +1 -1
  242. package/src/features/chat/features/session/components/session-header/chat-session-title-switcher.tsx +1 -1
  243. package/src/features/chat/features/session/hooks/__tests__/use-selected-session-context-window-indicator.test.tsx +87 -0
  244. package/src/features/chat/features/session/utils/__tests__/chat-session-display.utils.test.ts +26 -0
  245. package/src/features/chat/features/session/utils/__tests__/ncp-session-adapter.utils.test.ts +23 -2
  246. package/src/features/chat/features/session/utils/chat-context-window-indicator.utils.ts +44 -5
  247. package/src/features/chat/features/session/utils/chat-session-display.utils.ts +48 -12
  248. package/src/features/chat/features/session/utils/ncp-session-adapter.utils.ts +23 -4
  249. package/src/features/chat/features/session/utils/ncp-session-context-metadata.utils.ts +42 -4
  250. package/src/features/chat/features/welcome/components/__tests__/chat-conversation-welcome.test.tsx +6 -0
  251. package/src/features/chat/features/welcome/components/__tests__/chat-welcome.test.tsx +27 -9
  252. package/src/features/chat/features/welcome/components/chat-welcome-agent-picker.tsx +1 -1
  253. package/src/features/chat/features/welcome/components/chat-welcome-project-picker.tsx +11 -17
  254. package/src/features/chat/features/welcome/components/chat-welcome-prompt-suggestions.tsx +37 -0
  255. package/src/features/chat/features/welcome/components/chat-welcome-session-type-picker.tsx +1 -1
  256. package/src/features/chat/features/welcome/components/chat-welcome.tsx +35 -33
  257. package/src/features/chat/features/welcome/utils/__tests__/chat-welcome-project-options.utils.test.ts +22 -6
  258. package/src/features/chat/features/welcome/utils/chat-welcome-project-options.utils.ts +13 -0
  259. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel-content.test.tsx +73 -3
  260. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-panel.test.tsx +22 -2
  261. package/src/features/chat/features/workspace/components/__tests__/chat-session-workspace-text-selection.test.tsx +75 -0
  262. package/src/features/chat/features/workspace/components/__tests__/workspace-text-selection-menu.test.tsx +102 -0
  263. package/src/features/chat/features/workspace/components/chat-session-workspace-directory-browser.tsx +145 -26
  264. package/src/features/chat/features/workspace/components/chat-session-workspace-file-preview.tsx +42 -18
  265. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-content.tsx +50 -10
  266. package/src/features/chat/features/workspace/components/chat-session-workspace-panel-nav.tsx +48 -22
  267. package/src/features/chat/features/workspace/components/chat-session-workspace-panel.tsx +13 -2
  268. package/src/features/chat/features/workspace/components/workspace-text-selection-menu.tsx +223 -0
  269. package/src/features/chat/features/workspace/utils/__tests__/chat-workspace-panel-view-model.utils.test.ts +80 -0
  270. package/src/features/chat/features/workspace/utils/__tests__/workspace-text-excerpt.utils.test.ts +54 -0
  271. package/src/features/chat/features/workspace/utils/chat-thread-workspace-session.utils.ts +47 -0
  272. package/src/features/chat/features/workspace/utils/chat-workspace-file-tab.utils.ts +16 -0
  273. package/src/features/chat/features/workspace/utils/chat-workspace-panel-view-model.utils.ts +22 -1
  274. package/src/features/chat/features/workspace/utils/workspace-text-excerpt.utils.ts +53 -0
  275. package/src/features/chat/index.ts +1 -0
  276. package/src/features/chat/managers/__tests__/chat-composer-intent.manager.test.ts +106 -0
  277. package/src/features/chat/managers/__tests__/chat-session-list.manager.test.ts +53 -5
  278. package/src/features/chat/managers/__tests__/chat-thread-workspace.manager.test.ts +93 -1
  279. package/src/features/chat/managers/__tests__/chat-thread.manager.test.ts +3 -10
  280. package/src/features/chat/managers/__tests__/provider-model-catalog-notice.manager.test.ts +57 -0
  281. package/src/features/chat/managers/chat-composer-intent.manager.ts +145 -0
  282. package/src/features/chat/managers/chat-session-list.manager.ts +14 -25
  283. package/src/features/chat/managers/chat-thread.manager.ts +32 -16
  284. package/src/features/chat/managers/provider-model-catalog-notice.manager.ts +121 -0
  285. package/src/features/chat/pages/__tests__/ncp-chat-page.test.tsx +3 -9
  286. package/src/features/chat/pages/ncp-chat-page.tsx +0 -1
  287. package/src/features/chat/presenters/chat.presenter.ts +3 -0
  288. package/src/features/chat/stores/__tests__/chat-composer-draft.store.test.ts +55 -0
  289. package/src/features/chat/stores/chat-composer-draft.store.ts +92 -0
  290. package/src/features/chat/stores/chat-thread.store.ts +2 -4
  291. package/src/features/chat/types/chat-input-bar.types.ts +6 -0
  292. package/src/features/chat/types/chat-input.types.ts +5 -0
  293. package/src/features/chat/types/chat-message.types.ts +5 -0
  294. package/src/features/cron/components/__tests__/cron-config.test.tsx +100 -19
  295. package/src/features/cron/components/cron-config.tsx +19 -33
  296. package/src/features/cron/components/cron-job-detail-dialog.tsx +42 -16
  297. package/src/features/cron/components/cron-job-row.tsx +18 -1
  298. package/src/features/cron/components/cron-task-discovery.tsx +1 -1
  299. package/src/features/inbox/components/inbox-delivery-content.test.tsx +8 -2
  300. package/src/features/inbox/components/inbox-delivery-content.tsx +1 -1
  301. package/src/features/inbox/components/inbox-reader-dialog.test.tsx +10 -4
  302. package/src/features/inbox/components/inbox-reader-dialog.tsx +37 -61
  303. package/src/features/inbox/pages/inbox-page.tsx +90 -98
  304. package/src/features/marketplace/components/marketplace-page-parts.tsx +1 -1
  305. package/src/features/notifications/components/__tests__/app-notification-toast.test.tsx +31 -6
  306. package/src/features/notifications/components/app-notification-toast.tsx +54 -15
  307. package/src/features/notifications/managers/__tests__/app-notification.manager.test.ts +2 -0
  308. package/src/features/notifications/managers/app-notification.manager.ts +3 -0
  309. package/src/features/panel-apps/components/panel-app-list-item.tsx +1 -1
  310. package/src/features/panel-apps/components/panel-app-toolbar.tsx +2 -2
  311. package/src/features/panel-apps/components/panel-apps-list.tsx +4 -4
  312. package/src/features/panel-apps/utils/panel-app-doc-browser.utils.tsx +1 -0
  313. package/src/features/pwa/managers/__tests__/pwa-shell-theme.manager.test.ts +9 -0
  314. package/src/features/pwa/managers/pwa-shell-theme.manager.ts +1 -0
  315. package/src/features/service-apps/components/__tests__/service-apps-panel.test.tsx +5 -4
  316. package/src/features/service-apps/components/service-app-list-item.tsx +470 -0
  317. package/src/features/service-apps/components/service-apps-panel.tsx +76 -490
  318. package/src/features/settings/components/config/__tests__/provider-models-section.test.tsx +239 -0
  319. package/src/features/settings/components/config/provider-auth-section.tsx +90 -59
  320. package/src/features/settings/components/config/provider-form-detail-pane.tsx +307 -0
  321. package/src/features/settings/components/config/provider-form.tsx +149 -367
  322. package/src/features/settings/components/config/provider-model-list.tsx +293 -0
  323. package/src/features/settings/components/config/provider-model-suggestions-panel.tsx +151 -0
  324. package/src/features/settings/components/config/provider-models-section.tsx +178 -149
  325. package/src/features/settings/components/config/provider-status-badge.tsx +4 -2
  326. package/src/features/settings/hooks/__tests__/use-provider-connectivity.test.tsx +98 -0
  327. package/src/features/settings/hooks/__tests__/use-provider-model-suggestions.test.tsx +107 -0
  328. package/src/features/settings/hooks/use-provider-connectivity.ts +132 -0
  329. package/src/features/settings/hooks/use-provider-model-suggestions.ts +51 -0
  330. package/src/features/settings/pages/__tests__/appearance-settings-page.test.tsx +2 -2
  331. package/src/features/settings/pages/__tests__/providers-config-page.test.tsx +12 -0
  332. package/src/features/settings/pages/__tests__/search-config-page.test.tsx +48 -0
  333. package/src/features/settings/pages/providers-config-page.tsx +2 -1
  334. package/src/features/settings/pages/search-config-page.tsx +17 -8
  335. package/src/features/settings/utils/__tests__/provider-form-support.utils.test.ts +42 -0
  336. package/src/features/settings/utils/provider-form-context.utils.ts +2 -0
  337. package/src/features/settings/utils/provider-form-model.utils.ts +25 -0
  338. package/src/features/settings/utils/provider-form-support.utils.ts +21 -5
  339. package/src/index.css +1 -1
  340. package/src/platforms/desktop/types/desktop-update.types.ts +5 -0
  341. package/src/platforms/mobile/components/__tests__/mobile-app-shell.test.tsx +13 -7
  342. package/src/platforms/mobile/components/__tests__/mobile-bottom-nav.test.tsx +18 -20
  343. package/src/shared/components/actions/__tests__/navigation-link.test.tsx +14 -3
  344. package/src/shared/components/actions/navigation-link.tsx +35 -15
  345. package/src/shared/components/common/__tests__/agent-avatar.test.tsx +3 -2
  346. package/src/shared/components/common/agent-avatar.tsx +3 -3
  347. package/src/shared/components/doc-browser/__tests__/doc-browser.test.tsx +66 -0
  348. package/src/shared/components/doc-browser/doc-browser-panel-parts.tsx +5 -2
  349. package/src/shared/components/doc-browser/doc-browser-renderer.types.ts +1 -0
  350. package/src/shared/components/doc-browser/doc-browser.tsx +9 -0
  351. package/src/shared/components/doc-browser/hooks/use-doc-browser-scroll-restoration.ts +126 -0
  352. package/src/shared/components/path-picker/server-path-picker-directory-list.tsx +1 -1
  353. package/src/shared/components/path-picker/server-path-picker-locations.tsx +1 -1
  354. package/src/shared/components/path-picker/server-path-picker-navigation-bar.tsx +3 -3
  355. package/src/shared/components/settings/settings-page.tsx +1 -1
  356. package/src/shared/components/ui/actions/__tests__/icon-action-button.test.tsx +1 -1
  357. package/src/shared/components/ui/actions/icon-action-button.tsx +1 -1
  358. package/src/shared/components/ui/button.tsx +4 -4
  359. package/src/shared/components/ui/context-menu/__tests__/context-menu.test.tsx +103 -0
  360. package/src/shared/components/ui/context-menu/context-menu.tsx +263 -0
  361. package/src/shared/components/ui/dialog.tsx +1 -1
  362. package/src/shared/components/ui/input.tsx +1 -1
  363. package/src/shared/components/ui/scroll-area.tsx +2 -0
  364. package/src/shared/components/ui/select.tsx +1 -1
  365. package/src/shared/components/ui/switch.tsx +1 -1
  366. package/src/shared/components/ui/tab-strip/__tests__/compact-tab-strip.test.tsx +19 -6
  367. package/src/shared/components/ui/tab-strip/compact-tab-strip.tsx +28 -67
  368. package/src/shared/components/ui/tabs.tsx +1 -1
  369. package/src/shared/components/ui/textarea.tsx +1 -1
  370. package/src/shared/hooks/use-config.ts +33 -1
  371. package/src/shared/lib/api/__tests__/ncp-session-query-cache.utils.test.ts +26 -0
  372. package/src/shared/lib/api/ncp-session-query-cache.utils.ts +7 -10
  373. package/src/shared/lib/api/ncp-session.types.ts +7 -0
  374. package/src/shared/lib/api/types.ts +38 -1
  375. package/src/shared/lib/api/utils/config.utils.ts +14 -0
  376. package/src/shared/lib/cron/cron-job-view.utils.ts +4 -0
  377. package/src/shared/lib/host-capabilities/__tests__/host-capability.manager.test.ts +26 -0
  378. package/src/shared/lib/host-capabilities/host-capability.manager.ts +28 -0
  379. package/src/shared/lib/i18n/locales/en-US/chat.json +45 -17
  380. package/src/shared/lib/i18n/locales/en-US/core.json +26 -0
  381. package/src/shared/lib/i18n/locales/en-US/doc-browser.json +1 -0
  382. package/src/shared/lib/i18n/locales/en-US/search.json +1 -0
  383. package/src/shared/lib/i18n/locales/zh-CN/chat.json +45 -17
  384. package/src/shared/lib/i18n/locales/zh-CN/core.json +26 -0
  385. package/src/shared/lib/i18n/locales/zh-CN/doc-browser.json +1 -0
  386. package/src/shared/lib/i18n/locales/zh-CN/search.json +1 -0
  387. package/src/shared/lib/provider-models/__tests__/anonymous-provider-configuration.test.ts +40 -0
  388. package/src/shared/lib/provider-models/index.ts +4 -3
  389. package/src/shared/lib/session-project/__tests__/workspace-file-breadcrumb.test.ts +28 -1
  390. package/src/shared/lib/session-project/workspace-file-breadcrumb.utils.ts +23 -0
  391. package/src/shared/lib/theme/index.test.ts +11 -0
  392. package/src/shared/lib/theme/index.ts +2 -0
  393. package/dist/assets/api-CHo2gekJ.js +0 -13
  394. package/dist/assets/app-presenter-provider-2_DmWYT9.js +0 -108
  395. package/dist/assets/app-presenter-provider-D3BuuOyZ.css +0 -1
  396. package/dist/assets/appearance-settings-page-BEy2UE7R.js +0 -1
  397. package/dist/assets/architecture-TIHT7OUA-Cii4JyA2.js +0 -1
  398. package/dist/assets/book-open-BrZL4o9L.js +0 -1
  399. package/dist/assets/card-B5rRLu_6.js +0 -1
  400. package/dist/assets/channel-BSKkTPe8.js +0 -1
  401. package/dist/assets/channels-list-page-C1dnm68I.js +0 -8
  402. package/dist/assets/chat-page-Dz6tvZ91.js +0 -1
  403. package/dist/assets/chunk-2Q5K7J3B-CDMOR1xZ.js +0 -1
  404. package/dist/assets/chunk-VAUOI2AC-BRvqNW76.js +0 -1
  405. package/dist/assets/chunk-XXDRQBXY-CwX_mqJ3.js +0 -1
  406. package/dist/assets/classDiagram-OUVF2IWQ-RzbGxG77.js +0 -1
  407. package/dist/assets/classDiagram-v2-EOCWNBFH-C_gWB7WT.js +0 -1
  408. package/dist/assets/config-split-page-6y6W6dz4.js +0 -1
  409. package/dist/assets/confirm-dialog-q1CpEmR8.js +0 -5
  410. package/dist/assets/cynefin-VYW2F7L2-fSLIS0X3.js +0 -1
  411. package/dist/assets/desktop-update-config-DrdlwP4j.js +0 -1
  412. package/dist/assets/dist-C9EX46wM.js +0 -41
  413. package/dist/assets/doc-browser-BfttDSti.js +0 -1
  414. package/dist/assets/doc-browser-DH-3hDnI.js +0 -1
  415. package/dist/assets/doc-browser-context-TU2heon0.js +0 -1
  416. package/dist/assets/eventmodeling-45OFAUF4-XVcQGgha.js +0 -1
  417. package/dist/assets/eye-B-3dyZNA.js +0 -1
  418. package/dist/assets/flowDiagram-23GEKE2U-BiFSctqz.js +0 -1
  419. package/dist/assets/gitGraph-TEB2WS4Q-C014uXqS.js +0 -1
  420. package/dist/assets/host-capabilities-B-TtABsP.js +0 -1
  421. package/dist/assets/index-CXDPLDFD.css +0 -1
  422. package/dist/assets/index-k_eWScvN.js +0 -2
  423. package/dist/assets/info-DKCQHKI2-CQ2qjGhU.js +0 -1
  424. package/dist/assets/loader-circle-B35NlHTI.js +0 -1
  425. package/dist/assets/logo-badge-DB51c0cS.js +0 -1
  426. package/dist/assets/mcp-marketplace-page-CkPtwPBk.js +0 -1
  427. package/dist/assets/mcp-marketplace-page-DWtsIqml.js +0 -9
  428. package/dist/assets/model-config-page-D6cGNnMM.js +0 -1
  429. package/dist/assets/navigation-link-BSJEUiwD.js +0 -1
  430. package/dist/assets/packet-7NZHBO7P-6IFyydij.js +0 -1
  431. package/dist/assets/pie-RZYD4A2V-spXQC_wr.js +0 -1
  432. package/dist/assets/plus-BRVe5jSJ.js +0 -1
  433. package/dist/assets/popover-Bp9IpE_N.js +0 -1
  434. package/dist/assets/provider-models-CXUgDxGR.js +0 -1
  435. package/dist/assets/provider-scoped-model-input-dk7fz9NL.js +0 -1
  436. package/dist/assets/providers-config-page-DTP2YfxA.js +0 -1
  437. package/dist/assets/radar-I7S5WNFK-DWVA8lvY.js +0 -1
  438. package/dist/assets/railroad-3IZDKUUU-Ce51u8y1.js +0 -1
  439. package/dist/assets/railroad-abnf-AHOZXSZD-D7i7cqI_.js +0 -1
  440. package/dist/assets/railroad-ebnf-EBAXGLYW-BEg2hcPs.js +0 -1
  441. package/dist/assets/railroad-peg-LSFZ7HO6-CxROSzbi.js +0 -1
  442. package/dist/assets/react-D6ShNq95.js +0 -8
  443. package/dist/assets/remote-FllhXW3o.js +0 -1
  444. package/dist/assets/rotate-cw-CTC-Qszi.js +0 -1
  445. package/dist/assets/runtime-config-page-C57OLdPm.js +0 -1
  446. package/dist/assets/search-config-page-tnJXaaSr.js +0 -1
  447. package/dist/assets/security-config-S00DjFfO.js +0 -1
  448. package/dist/assets/select-CNFBf00l.js +0 -1
  449. package/dist/assets/skeleton-Dc06gatK.js +0 -1
  450. package/dist/assets/stateDiagram-v2-6OUMAXLB-Bui4RXQK.js +0 -1
  451. package/dist/assets/swimlanesDiagram-G3AALYLV-DTwUsCBz.js +0 -8
  452. package/dist/assets/tabs-BuNr8LSO.js +0 -1
  453. package/dist/assets/trash-2-GchaJuEM.js +0 -1
  454. package/dist/assets/treeView-QDETBFTQ-D5zsTiuL.js +0 -1
  455. package/dist/assets/treemap-6X3UGDF4-CR3dxqNs.js +0 -1
  456. package/dist/assets/use-config-C69rR15i.js +0 -1
  457. package/dist/assets/use-confirm-dialog-DF_EgPYx.js +0 -1
  458. package/dist/assets/use-viewport-layout-BACNZyMY.js +0 -1
  459. package/dist/assets/wardley-OPB4EBWU-LatRT_Lx.js +0 -1
  460. package/dist/assets/x-BXFquY9e.js +0 -1
  461. package/src/features/chat/features/welcome/components/chat-welcome-capability-grid.tsx +0 -59
  462. /package/dist/assets/{array-CAkJT3no.js → array-NADKd9aU.js} +0 -0
  463. /package/dist/assets/{chunk-KEIR6QF5-BgdDoWEH.js → chunk-KEIR6QF5-BuyuzJJL.js} +0 -0
  464. /package/dist/assets/{chunk-Y2CYZVJY-pTllD0P3.js → chunk-Y2CYZVJY-D_B2ZRNt.js} +0 -0
  465. /package/dist/assets/{config-hints-S3g-UVZb.js → config-hints-BcfVUMij.js} +0 -0
  466. /package/dist/assets/{cytoscape.esm-DmURt-5G.js → cytoscape.esm-BANTlgza.js} +0 -0
  467. /package/dist/assets/{defaultLocale-BKy_KP3O.js → defaultLocale-C4_L0aEs.js} +0 -0
  468. /package/dist/assets/{dist-IgNNB7K9.js → dist-DAPEIJbz.js} +0 -0
  469. /package/dist/assets/{graphlib-xw_TBHyH.js → graphlib-ChqXg0dI.js} +0 -0
  470. /package/dist/assets/{init-C9xlLIAu.js → init-D8wZg9xq.js} +0 -0
  471. /package/dist/assets/{jszip.min-Das2IAnb.js → jszip.min-CkbBfwAQ.js} +0 -0
  472. /package/dist/assets/{katex-DSy1eD2d.js → katex-TZJFa0J_.js} +0 -0
  473. /package/dist/assets/{middleware-Df-a4x-q.js → middleware-B3k-TSmZ.js} +0 -0
  474. /package/dist/assets/{path-DB0USYHi.js → path-DodfxcX7.js} +0 -0
  475. /package/dist/assets/{rough.esm-B1B52ZQS.js → rough.esm-CUaa1Rbe.js} +0 -0
  476. /package/dist/assets/{xlsx-DfjC_iml.js → xlsx-CxByi7bR.js} +0 -0
@@ -89,7 +89,7 @@ export function InboxReaderDialog() {
89
89
  });
90
90
  };
91
91
 
92
- const positionControls = (
92
+ const positionControls = unreadDeliveries.length > 1 ? (
93
93
  <div className="flex shrink-0 items-center gap-1 pr-1">
94
94
  <span className="mr-1 text-[11px] tabular-nums text-muted-foreground">
95
95
  {formatPosition(activeIndex + 1, unreadDeliveries.length)}
@@ -107,7 +107,7 @@ export function InboxReaderDialog() {
107
107
  onClick={() => selectAt(activeIndex + 1)}
108
108
  />
109
109
  </div>
110
- );
110
+ ) : null;
111
111
 
112
112
  return (
113
113
  <Dialog
@@ -127,64 +127,40 @@ export function InboxReaderDialog() {
127
127
  >
128
128
  {activeDelivery ? (
129
129
  <>
130
- {isHtml ? (
131
- <header className="shrink-0 border-b border-border/60 px-6 py-3 pr-14 sm:px-8">
132
- <div className="flex min-h-8 items-center justify-between gap-4">
133
- <div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
134
- <span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-muted">
135
- <Inbox className="h-3.5 w-3.5" />
136
- </span>
137
- <DialogTitle
138
- ref={titleRef}
139
- tabIndex={-1}
140
- className="truncate text-sm font-semibold text-foreground outline-none"
141
- >
142
- {activeDelivery.title}
143
- </DialogTitle>
144
- <span aria-hidden="true">·</span>
145
- <time className="shrink-0" dateTime={activeDelivery.createdAt}>
146
- {formatDateTime(activeDelivery.createdAt)}
147
- </time>
148
- </div>
149
- {positionControls}
150
- </div>
151
- <DialogDescription className="sr-only">
152
- {activeDelivery.summary ?? t("inboxNoSummary")}
153
- </DialogDescription>
154
- </header>
155
- ) : (
156
- <header className="shrink-0 border-b border-border/60 px-6 pb-4 pt-5 pr-14 sm:px-8 sm:pb-5 sm:pt-6">
157
- <div className="mb-5 flex min-h-8 items-center justify-between gap-4">
158
- <div className="flex min-w-0 items-center gap-2 text-xs text-muted-foreground">
159
- <span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-muted">
160
- <Inbox className="h-3.5 w-3.5" />
161
- </span>
162
- <span className="truncate">{t("inboxDeliveredBy")}</span>
163
- <span aria-hidden="true">·</span>
164
- <time dateTime={activeDelivery.createdAt}>
165
- {formatDateTime(activeDelivery.createdAt)}
166
- </time>
167
- </div>
168
- {positionControls}
130
+ <header className="shrink-0 border-b border-border/50 px-5 py-3 pr-14 sm:px-6">
131
+ <div className="flex min-h-7 items-center justify-between gap-3">
132
+ <div className="flex min-w-0 items-center gap-2">
133
+ <span className="flex h-7 w-7 shrink-0 items-center justify-center rounded-lg bg-muted/70 text-muted-foreground">
134
+ <Inbox className="h-3.5 w-3.5" />
135
+ </span>
136
+ <DialogTitle
137
+ ref={titleRef}
138
+ tabIndex={-1}
139
+ title={activeDelivery.title}
140
+ className="min-w-0 truncate text-sm font-medium text-foreground outline-none"
141
+ >
142
+ {activeDelivery.title}
143
+ </DialogTitle>
144
+ <span className="hidden shrink-0 text-muted-foreground sm:inline" aria-hidden="true">·</span>
145
+ <time
146
+ className="hidden shrink-0 text-[11px] tabular-nums text-muted-foreground sm:block"
147
+ dateTime={activeDelivery.createdAt}
148
+ >
149
+ {formatDateTime(activeDelivery.createdAt)}
150
+ </time>
169
151
  </div>
170
- <DialogTitle
171
- ref={titleRef}
172
- tabIndex={-1}
173
- className="max-w-[680px] text-balance text-2xl font-semibold leading-tight tracking-[-0.02em] outline-none sm:text-[28px]"
174
- >
175
- {activeDelivery.title}
176
- </DialogTitle>
177
- <DialogDescription className="mt-2 max-w-[680px] text-sm leading-6 text-muted-foreground">
178
- {activeDelivery.summary ?? t("inboxNoSummary")}
179
- </DialogDescription>
180
- </header>
181
- )}
152
+ {positionControls}
153
+ </div>
154
+ <DialogDescription className="sr-only">
155
+ {activeDelivery.summary ?? t("inboxNoSummary")}
156
+ </DialogDescription>
157
+ </header>
182
158
 
183
159
  <div className={cn(
184
160
  "min-h-0 flex-1",
185
161
  isHtml
186
- ? "p-4 sm:p-5"
187
- : "custom-scrollbar overflow-y-auto px-6 py-6 sm:px-8 sm:py-7",
162
+ ? "p-3 sm:p-4"
163
+ : "custom-scrollbar overflow-y-auto px-6 py-5 sm:px-8 sm:py-6",
188
164
  )}>
189
165
  <InboxDeliveryContent
190
166
  className={isHtml ? "h-full" : undefined}
@@ -195,23 +171,23 @@ export function InboxReaderDialog() {
195
171
  />
196
172
  </div>
197
173
 
198
- <footer className="shrink-0 border-t border-border/60 bg-background/95 px-6 py-4 backdrop-blur sm:px-8">
174
+ <footer className="shrink-0 border-t border-border/50 bg-background px-5 py-3 sm:px-6">
199
175
  {error ? (
200
176
  <p role="alert" className="mb-3 text-sm text-destructive">{error}</p>
201
177
  ) : null}
202
178
  <div className="flex flex-col-reverse gap-2 sm:flex-row sm:items-center sm:justify-between">
203
- <div className="flex flex-col-reverse gap-2 sm:flex-row">
204
- <Button variant="ghost" onClick={inboxManager.closeReader}>
179
+ <div className="flex flex-col-reverse gap-1 sm:flex-row sm:flex-wrap">
180
+ <Button size="sm" variant="ghost" onClick={inboxManager.closeReader}>
205
181
  {t("inboxReadLater")}
206
182
  </Button>
207
- <Button variant="outline" disabled={pendingAction !== null} onClick={markRead}>
183
+ <Button size="sm" variant="outline" disabled={pendingAction !== null} onClick={markRead}>
208
184
  {t("inboxMarkRead")}
209
185
  </Button>
210
- <Button variant="outline" disabled={pendingAction !== null} onClick={openInbox}>
186
+ <Button size="sm" variant="ghost" disabled={pendingAction !== null} onClick={openInbox}>
211
187
  {t("inboxOpenInbox")}
212
188
  </Button>
213
189
  </div>
214
- <Button disabled={pendingAction !== null} onClick={continueInChat}>
190
+ <Button size="sm" disabled={pendingAction !== null} onClick={continueInChat}>
215
191
  {t("inboxContinueChat")}
216
192
  </Button>
217
193
  </div>
@@ -3,12 +3,13 @@ import type { InboxDelivery } from "@nextclaw/shared";
3
3
  import { Archive, ArrowLeft, Inbox, MessageCircle, RotateCcw, Trash2 } from "lucide-react";
4
4
  import { Link, useNavigate, useParams } from "react-router-dom";
5
5
  import { useAppPresenter } from "@/app/components/app-presenter-provider";
6
+ import { PageHeader } from "@/app/components/layout/page-layout";
6
7
  import { useViewportLayout } from "@/app/hooks/use-viewport-layout";
7
8
  import { InboxDeliveryContent } from "@/features/inbox/components/inbox-delivery-content";
8
9
  import { useInboxDeliveries } from "@/features/inbox/hooks/use-inbox-deliveries";
9
10
  import { Button } from "@/shared/components/ui/button";
10
11
  import { useConfirmDialog } from "@/shared/hooks/use-confirm-dialog";
11
- import { formatDateTime, t } from "@/shared/lib/i18n";
12
+ import { formatDateShort, formatDateTime, t } from "@/shared/lib/i18n";
12
13
  import { cn } from "@/shared/lib/utils";
13
14
 
14
15
  type InboxFilter = "unread" | "all" | "archived";
@@ -51,26 +52,6 @@ function InboxEmptyState({ selection = false }: { selection?: boolean }) {
51
52
  );
52
53
  }
53
54
 
54
- function InboxPageHeader({ unreadCount }: { unreadCount: number }) {
55
- return (
56
- <header className="shrink-0 px-4 pb-4 pt-1 sm:px-0">
57
- <div className="flex items-end justify-between gap-4">
58
- <div>
59
- <h1 className="text-xl font-semibold tracking-tight text-foreground sm:text-2xl">
60
- {t("inboxTitle")}
61
- </h1>
62
- <p className="mt-1 text-sm text-muted-foreground">{t("inboxDescription")}</p>
63
- </div>
64
- {unreadCount > 0 ? (
65
- <span className="shrink-0 rounded-full bg-primary/10 px-2.5 py-1 text-xs font-medium text-primary">
66
- {t("inboxUnreadCount").replace("{count}", String(unreadCount))}
67
- </span>
68
- ) : null}
69
- </div>
70
- </header>
71
- );
72
- }
73
-
74
55
  function InboxListPane({
75
56
  activeDeliveryId,
76
57
  deliveries,
@@ -82,69 +63,76 @@ function InboxListPane({
82
63
  filter: InboxFilter;
83
64
  onFilterChange: (filter: InboxFilter) => void;
84
65
  }) {
85
- const filterItems: Array<{ id: InboxFilter; label: string }> = [
86
- { id: "unread", label: t("inboxUnread") },
87
- { id: "all", label: t("inboxAll") },
88
- { id: "archived", label: t("inboxArchived") },
66
+ const filterItems: Array<{ id: InboxFilter; label: string; count: number }> = [
67
+ {
68
+ id: "unread",
69
+ label: t("inboxUnread"),
70
+ count: filterDeliveries(deliveries, "unread").length,
71
+ },
72
+ { id: "all", label: t("inboxAll"), count: filterDeliveries(deliveries, "all").length },
73
+ {
74
+ id: "archived",
75
+ label: t("inboxArchived"),
76
+ count: filterDeliveries(deliveries, "archived").length,
77
+ },
89
78
  ];
90
79
  const filteredDeliveries = filterDeliveries(deliveries, filter);
91
80
  return (
92
- <aside className="flex min-h-0 flex-col border-border/60 md:border-r">
93
- <div className="flex shrink-0 gap-1 border-b border-border/60 p-3">
81
+ <aside className="flex min-h-0 flex-col border-border/60 bg-muted/20 md:border-r">
82
+ <div className="flex shrink-0 gap-1 border-b border-border/50 px-3 py-2">
94
83
  {filterItems.map((item) => (
95
84
  <button
96
85
  key={item.id}
97
86
  type="button"
98
87
  onClick={() => onFilterChange(item.id)}
99
88
  className={cn(
100
- "rounded-full px-3 py-1.5 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
89
+ "inline-flex items-center gap-1.5 rounded-md px-2.5 py-1.5 text-xs font-medium transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
101
90
  filter === item.id
102
- ? "bg-foreground text-background"
103
- : "text-muted-foreground hover:bg-muted hover:text-foreground",
91
+ ? "bg-[var(--interaction-selection)] text-foreground"
92
+ : "text-muted-foreground hover:bg-[var(--interaction-hover)] hover:text-foreground",
104
93
  )}
105
94
  >
106
95
  {item.label}
96
+ <span aria-hidden="true" className="text-[10px] font-normal tabular-nums opacity-60">
97
+ {item.count}
98
+ </span>
107
99
  </button>
108
100
  ))}
109
101
  </div>
110
102
  <div className="custom-scrollbar min-h-0 flex-1 overflow-y-auto p-2">
111
103
  {filteredDeliveries.length > 0 ? (
112
- <ul className="space-y-1">
104
+ <ul className="space-y-0.5">
113
105
  {filteredDeliveries.map((delivery) => (
114
106
  <li key={delivery.id}>
115
107
  <Link
116
108
  to={`/inbox/${encodeURIComponent(delivery.id)}`}
117
109
  className={cn(
118
- "block rounded-xl px-3 py-3 transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
119
- activeDeliveryId === delivery.id ? "bg-muted" : "hover:bg-muted/70",
110
+ "block rounded-lg px-2.5 py-2 text-[13px] transition-colors focus-visible:outline-none focus-visible:ring-1 focus-visible:ring-border",
111
+ activeDeliveryId === delivery.id
112
+ ? "bg-[var(--interaction-selection)] text-foreground"
113
+ : "text-foreground/80 hover:bg-[var(--interaction-hover)] hover:text-foreground",
120
114
  )}
121
115
  >
122
- <div className="flex items-start gap-2">
123
- <span
124
- className={cn(
125
- "mt-1.5 h-2 w-2 shrink-0 rounded-full",
126
- !delivery.readAt && "bg-primary",
127
- )}
128
- aria-label={!delivery.readAt ? t("inboxUnread") : undefined}
129
- aria-hidden={delivery.readAt ? "true" : undefined}
130
- />
131
- <div className="min-w-0 flex-1">
132
- <h2 className={cn(
133
- "line-clamp-2 text-sm leading-5 text-foreground",
134
- !delivery.readAt && "font-semibold",
135
- )}>
136
- {delivery.title}
137
- </h2>
138
- <p className="mt-1 line-clamp-2 text-xs leading-5 text-muted-foreground">
139
- {delivery.summary ?? t("inboxNoSummary")}
140
- </p>
141
- <time
142
- className="mt-2 block text-[11px] text-muted-foreground/80"
143
- dateTime={delivery.createdAt}
144
- >
145
- {formatDateTime(delivery.createdAt)}
116
+ <h2 className={cn(
117
+ "truncate font-medium leading-5",
118
+ !delivery.readAt && "font-semibold text-foreground",
119
+ )}>
120
+ {delivery.title}
121
+ </h2>
122
+ <div className="mt-0.5 flex items-center gap-2 text-[11px] leading-4 text-muted-foreground/65">
123
+ <p className="min-w-0 flex-1 truncate">
124
+ {delivery.summary ?? t("inboxNoSummary")}
125
+ </p>
126
+ {!delivery.readAt ? (
127
+ <span
128
+ className="h-2 w-2 shrink-0 rounded-full bg-primary"
129
+ aria-label={t("inboxUnread")}
130
+ />
131
+ ) : (
132
+ <time className="shrink-0 tabular-nums" dateTime={delivery.createdAt}>
133
+ {formatDateShort(delivery.createdAt)}
146
134
  </time>
147
- </div>
135
+ )}
148
136
  </div>
149
137
  </Link>
150
138
  </li>
@@ -183,42 +171,40 @@ function InboxDetailPane({
183
171
  const isHtml = delivery.contentType === "html";
184
172
  return (
185
173
  <main className="flex min-h-0 flex-col">
186
- <div className={cn(
187
- "shrink-0 border-b border-border/60 px-5 sm:px-8",
188
- isHtml ? "py-3 sm:py-4" : "py-4 sm:py-6",
189
- )}>
174
+ <div className="shrink-0 border-b border-border/50 px-5 py-3 sm:px-6">
190
175
  {isMobile ? (
191
176
  <button
192
177
  type="button"
193
178
  onClick={onBack}
194
- className="mb-4 inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
179
+ className="mb-3 inline-flex items-center gap-1.5 text-sm text-muted-foreground hover:text-foreground"
195
180
  >
196
181
  <ArrowLeft className="h-4 w-4" />
197
182
  {t("inboxTitle")}
198
183
  </button>
199
184
  ) : null}
200
- <div className="text-xs text-muted-foreground">
201
- {t("inboxDeliveredBy")} · {formatDateTime(delivery.createdAt)}
185
+ <div className="flex min-h-7 min-w-0 items-center gap-2">
186
+ <h2
187
+ title={delivery.title}
188
+ className="min-w-0 truncate text-sm font-medium text-foreground"
189
+ >
190
+ {delivery.title}
191
+ </h2>
192
+ <span className="hidden shrink-0 text-muted-foreground sm:inline" aria-hidden="true">·</span>
193
+ <time
194
+ className="hidden shrink-0 text-[11px] tabular-nums text-muted-foreground sm:block"
195
+ dateTime={delivery.createdAt}
196
+ >
197
+ {formatDateTime(delivery.createdAt)}
198
+ </time>
202
199
  </div>
203
- <h2 className={cn(
204
- "text-balance font-semibold leading-tight tracking-[-0.02em] text-foreground",
205
- isHtml ? "mt-2 text-lg sm:text-xl" : "mt-3 text-2xl",
206
- )}>
207
- {delivery.title}
208
- </h2>
209
- {delivery.summary && !isHtml ? (
210
- <p className="mt-2 max-w-3xl text-sm leading-6 text-muted-foreground">
211
- {delivery.summary}
212
- </p>
213
- ) : null}
214
200
  </div>
215
201
  <div className={cn(
216
202
  "min-h-0 flex-1",
217
203
  isHtml
218
- ? "p-4 sm:p-5"
219
- : "custom-scrollbar overflow-y-auto px-5 py-6 sm:px-8 sm:py-8",
204
+ ? "p-3 sm:p-4"
205
+ : "custom-scrollbar overflow-y-auto px-5 py-5 sm:px-6 sm:py-6",
220
206
  )}>
221
- <div className={cn("mx-auto", isHtml ? "h-full max-w-5xl" : "max-w-3xl")}>
207
+ <div className={cn("mx-auto max-w-5xl", isHtml && "h-full")}>
222
208
  <InboxDeliveryContent
223
209
  className={isHtml ? "h-full" : undefined}
224
210
  content={delivery.content}
@@ -228,26 +214,28 @@ function InboxDetailPane({
228
214
  />
229
215
  </div>
230
216
  </div>
231
- <div className="shrink-0 border-t border-border/60 px-5 py-3 sm:px-8">
217
+ <div className="shrink-0 border-t border-border/50 px-5 py-3 sm:px-6">
232
218
  {error ? <p role="alert" className="mb-2 text-sm text-destructive">{error}</p> : null}
233
- <div className="flex flex-wrap items-center gap-2">
234
- <Button disabled={pending} onClick={onContinue}>
219
+ <div className="flex flex-col gap-2 sm:flex-row sm:items-center sm:justify-between">
220
+ <div className="flex flex-wrap items-center gap-1">
221
+ <Button size="sm" variant="outline" disabled={pending} onClick={onReadToggle}>
222
+ {delivery.readAt ? t("inboxMarkUnread") : t("inboxMarkRead")}
223
+ </Button>
224
+ <Button size="sm" variant="ghost" disabled={pending} onClick={onArchiveToggle}>
225
+ {delivery.archivedAt
226
+ ? <RotateCcw className="mr-2 h-4 w-4" />
227
+ : <Archive className="mr-2 h-4 w-4" />}
228
+ {delivery.archivedAt ? t("inboxRestore") : t("inboxArchive")}
229
+ </Button>
230
+ <Button size="sm" variant="ghost" disabled={pending} onClick={onDelete}>
231
+ <Trash2 className="mr-2 h-4 w-4" />
232
+ {t("inboxDelete")}
233
+ </Button>
234
+ </div>
235
+ <Button size="sm" disabled={pending} onClick={onContinue}>
235
236
  <MessageCircle className="mr-2 h-4 w-4" />
236
237
  {t("inboxContinueChat")}
237
238
  </Button>
238
- <Button variant="outline" disabled={pending} onClick={onReadToggle}>
239
- {delivery.readAt ? t("inboxMarkUnread") : t("inboxMarkRead")}
240
- </Button>
241
- <Button variant="ghost" disabled={pending} onClick={onArchiveToggle}>
242
- {delivery.archivedAt
243
- ? <RotateCcw className="mr-2 h-4 w-4" />
244
- : <Archive className="mr-2 h-4 w-4" />}
245
- {delivery.archivedAt ? t("inboxRestore") : t("inboxArchive")}
246
- </Button>
247
- <Button variant="ghost" disabled={pending} onClick={onDelete}>
248
- <Trash2 className="mr-2 h-4 w-4" />
249
- {t("inboxDelete")}
250
- </Button>
251
239
  </div>
252
240
  </div>
253
241
  </main>
@@ -351,14 +339,18 @@ export function InboxPage() {
351
339
  const showDetail = !isMobile || Boolean(activeDelivery);
352
340
 
353
341
  return (
354
- <div className="flex h-full min-h-0 flex-col">
355
- <InboxPageHeader unreadCount={deliveriesQuery.data?.unreadCount ?? 0} />
342
+ <div className="flex h-full min-h-0 flex-col gap-6">
343
+ <PageHeader
344
+ headingLevel={1}
345
+ title={t("inboxTitle")}
346
+ className="px-4 sm:px-0"
347
+ />
356
348
 
357
349
  <div className="min-h-0 flex-1 overflow-hidden border-y border-border/60 bg-background sm:rounded-2xl sm:border">
358
350
  {deliveriesQuery.isError ? (
359
351
  <div role="alert" className="p-6 text-sm text-destructive">{t("inboxLoadError")}</div>
360
352
  ) : (
361
- <div className="grid h-full min-h-0 grid-cols-1 md:grid-cols-[320px_minmax(0,1fr)]">
353
+ <div className="grid h-full min-h-0 grid-cols-1 md:grid-cols-[300px_minmax(0,1fr)]">
362
354
  {showList ? <InboxListPane
363
355
  activeDeliveryId={activeDelivery?.id ?? null}
364
356
  deliveries={deliveries}
@@ -37,7 +37,7 @@ export function FilterPanel({
37
37
  value={searchText}
38
38
  onChange={(event) => onSearchTextChange(event.target.value)}
39
39
  placeholder={searchPlaceholder}
40
- className="h-9 w-full rounded-xl border border-border/75 bg-card pl-9 pr-9 text-sm transition-colors focus:outline-none focus:ring-0 focus:border-border"
40
+ className="h-9 w-full rounded-xl border border-border/75 bg-card pl-9 pr-9 text-sm transition-colors focus:outline-none focus:ring-0"
41
41
  />
42
42
  {isRefreshing && (
43
43
  <Loader2
@@ -1,22 +1,28 @@
1
1
  import { render, screen } from "@testing-library/react";
2
2
  import userEvent from "@testing-library/user-event";
3
- import { MemoryRouter } from "react-router-dom";
3
+ import { MemoryRouter, useLocation } from "react-router-dom";
4
4
  import { describe, expect, it, vi } from "vitest";
5
5
  import { AppNotificationToast } from "@/features/notifications";
6
6
 
7
+ function CurrentPathname() {
8
+ return <span data-testid="current-pathname">{useLocation().pathname}</span>;
9
+ }
10
+
7
11
  describe("AppNotificationToast", () => {
8
- it("renders a keyboard-accessible route notification and dismisses it on activation", async () => {
12
+ it("keeps opening and dismissing a route notification as separate actions", async () => {
9
13
  const onDismiss = vi.fn();
10
14
  const user = userEvent.setup();
11
15
  render(
12
- <MemoryRouter>
16
+ <MemoryRouter initialEntries={["/chat/current"]}>
13
17
  <AppNotificationToast
14
18
  title="Background task"
15
19
  description="The requested summary is ready."
16
20
  href="/chat/session-1"
17
21
  ariaLabel="Open the background task reply"
22
+ dismissLabel="Dismiss notification"
18
23
  onDismiss={onDismiss}
19
24
  />
25
+ <CurrentPathname />
20
26
  </MemoryRouter>,
21
27
  );
22
28
 
@@ -24,25 +30,44 @@ describe("AppNotificationToast", () => {
24
30
  name: "Open the background task reply",
25
31
  });
26
32
  expect(notification.getAttribute("href")).toBe("/chat/session-1");
33
+ expect(notification.className).not.toMatch(
34
+ /(?:hover|active):bg-\S*\/\d+/,
35
+ );
27
36
  expect(screen.getByText("Background task")).toBeTruthy();
28
37
  expect(screen.getByText("The requested summary is ready.")).toBeTruthy();
38
+ expect(notification.querySelector("button")).toBeNull();
29
39
 
30
- await user.click(notification);
40
+ const dismiss = screen.getByRole("button", { name: "Dismiss notification" });
41
+ await user.tab();
42
+ expect(document.activeElement).toBe(notification);
43
+ await user.tab();
44
+ expect(document.activeElement).toBe(dismiss);
45
+ await user.click(dismiss);
31
46
  expect(onDismiss).toHaveBeenCalledOnce();
47
+ expect(screen.getByTestId("current-pathname").textContent).toBe("/chat/current");
48
+
49
+ await user.click(notification);
50
+ expect(onDismiss).toHaveBeenCalledTimes(2);
51
+ expect(screen.getByTestId("current-pathname").textContent).toBe("/chat/session-1");
32
52
  });
33
53
 
34
- it("keeps informational notifications non-interactive when no route exists", () => {
54
+ it("lets users dismiss an informational notification without making its content interactive", async () => {
55
+ const onDismiss = vi.fn();
56
+ const user = userEvent.setup();
35
57
  render(
36
58
  <MemoryRouter>
37
59
  <AppNotificationToast
38
60
  title="All caught up"
39
61
  description="There is nothing else to review."
40
- onDismiss={vi.fn()}
62
+ dismissLabel="Dismiss notification"
63
+ onDismiss={onDismiss}
41
64
  />
42
65
  </MemoryRouter>,
43
66
  );
44
67
 
45
68
  expect(screen.getByRole("status")).toBeTruthy();
46
69
  expect(screen.queryByRole("link")).toBeNull();
70
+ await user.click(screen.getByRole("button", { name: "Dismiss notification" }));
71
+ expect(onDismiss).toHaveBeenCalledOnce();
47
72
  });
48
73
  });
@@ -1,4 +1,6 @@
1
+ import { X } from "lucide-react";
1
2
  import { Link } from "react-router-dom";
3
+ import { IconActionButton } from "@/shared/components/ui/actions/icon-action-button";
2
4
 
3
5
  export type AppNotificationToastProps = {
4
6
  title: string;
@@ -6,11 +8,15 @@ export type AppNotificationToastProps = {
6
8
  href?: string;
7
9
  iconSrc?: string;
8
10
  ariaLabel?: string;
11
+ dismissLabel: string;
9
12
  onDismiss: () => void;
10
13
  };
11
14
 
12
15
  const NOTIFICATION_CARD_CLASS =
13
- "group ml-auto flex min-h-[74px] w-[320px] max-w-[calc(100vw-2rem)] items-center gap-3 rounded-[20px] border border-border/80 bg-background px-[18px] py-3 text-left text-foreground shadow-[0_8px_18px_rgba(0,0,0,0.12),0_2px_5px_rgba(0,0,0,0.06)] transition-colors duration-150 hover:bg-muted/10 focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-primary/50 focus-visible:ring-offset-2 focus-visible:ring-offset-background active:bg-muted/20 motion-reduce:transition-none";
16
+ "relative ml-auto flex min-h-[74px] w-[320px] max-w-[calc(100vw-2rem)] rounded-[20px] border border-border/80 bg-background text-left text-foreground shadow-[0_8px_18px_rgba(0,0,0,0.12),0_2px_5px_rgba(0,0,0,0.06)]";
17
+
18
+ const NOTIFICATION_CONTENT_CLASS =
19
+ "flex min-h-[72px] min-w-0 flex-1 items-center gap-3 rounded-[inherit] py-3 pl-[18px] pr-[52px]";
14
20
 
15
21
  function AppNotificationContent({
16
22
  title,
@@ -41,39 +47,72 @@ function AppNotificationContent({
41
47
  );
42
48
  }
43
49
 
50
+ function AppNotificationDismissButton({
51
+ dismissLabel,
52
+ onDismiss,
53
+ }: Pick<AppNotificationToastProps, "dismissLabel" | "onDismiss">) {
54
+ return (
55
+ <IconActionButton
56
+ className="absolute right-2.5 top-2.5 z-10"
57
+ icon={<X className="h-4 w-4" aria-hidden="true" />}
58
+ label={dismissLabel}
59
+ onClick={onDismiss}
60
+ size="lg"
61
+ tooltipSide="left"
62
+ />
63
+ );
64
+ }
65
+
44
66
  export function AppNotificationToast({
45
67
  title,
46
68
  description,
47
69
  href,
48
70
  iconSrc,
49
71
  ariaLabel,
72
+ dismissLabel,
50
73
  onDismiss,
51
74
  }: AppNotificationToastProps) {
52
75
  const accessibleLabel = ariaLabel ?? [title, description].filter(Boolean).join(": ");
53
76
 
54
77
  if (href) {
55
78
  return (
56
- <Link
57
- to={href}
79
+ <div className={NOTIFICATION_CARD_CLASS}>
80
+ <Link
81
+ to={href}
82
+ aria-label={accessibleLabel}
83
+ className={`${NOTIFICATION_CONTENT_CLASS} focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-inset focus-visible:ring-primary/50`}
84
+ onClick={onDismiss}
85
+ >
86
+ <AppNotificationContent
87
+ title={title}
88
+ description={description}
89
+ iconSrc={iconSrc}
90
+ />
91
+ </Link>
92
+ <AppNotificationDismissButton
93
+ dismissLabel={dismissLabel}
94
+ onDismiss={onDismiss}
95
+ />
96
+ </div>
97
+ );
98
+ }
99
+
100
+ return (
101
+ <div className={NOTIFICATION_CARD_CLASS}>
102
+ <div
103
+ role="status"
58
104
  aria-label={accessibleLabel}
59
- className={NOTIFICATION_CARD_CLASS}
60
- onClick={onDismiss}
105
+ className={NOTIFICATION_CONTENT_CLASS}
61
106
  >
62
107
  <AppNotificationContent
63
108
  title={title}
64
109
  description={description}
65
110
  iconSrc={iconSrc}
66
111
  />
67
- </Link>
68
- );
69
- }
70
-
71
- return (
72
- <div role="status" aria-label={accessibleLabel} className={NOTIFICATION_CARD_CLASS}>
73
- <AppNotificationContent
74
- title={title}
75
- description={description}
76
- iconSrc={iconSrc}
112
+ </div>
113
+ <AppNotificationDismissButton
114
+ dismissLabel={dismissLabel}
115
+ onDismiss={onDismiss}
77
116
  />
78
117
  </div>
79
118
  );
@@ -5,6 +5,7 @@ import {
5
5
  AppNotificationToast,
6
6
  type AppNotificationToastProps,
7
7
  } from "@/features/notifications";
8
+ import { t } from "@/shared/lib/i18n";
8
9
 
9
10
  const mocks = vi.hoisted(() => ({
10
11
  custom: vi.fn(),
@@ -54,6 +55,7 @@ describe("AppNotificationManager", () => {
54
55
  title: "Research complete",
55
56
  description: "The findings are ready.",
56
57
  href: "/chat/session-1",
58
+ dismissLabel: t("notificationDismiss"),
57
59
  }));
58
60
 
59
61
  element.props.onDismiss();