@yadsh/dsh-qa-surface 0.13.0 → 0.15.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (339) hide show
  1. package/README.md +148 -22
  2. package/compatibility.json +7 -3
  3. package/lib/access/capability-catalog.js +22 -6
  4. package/lib/access/capability-catalog.js.map +1 -1
  5. package/lib/access/model.js +36 -2
  6. package/lib/access/model.js.map +1 -1
  7. package/lib/access/service.js +19 -4
  8. package/lib/access/service.js.map +1 -1
  9. package/lib/account-remotes.js +4 -0
  10. package/lib/account-remotes.js.map +1 -1
  11. package/lib/accounts/database.js +49 -9
  12. package/lib/accounts/database.js.map +1 -1
  13. package/lib/accounts/errors.js.map +1 -1
  14. package/lib/accounts/file.js.map +1 -1
  15. package/lib/accounts/store.js +53 -8
  16. package/lib/accounts/store.js.map +1 -1
  17. package/lib/admin/permissions.js +6 -0
  18. package/lib/admin/permissions.js.map +1 -1
  19. package/lib/admin/quality-store.js +95 -19
  20. package/lib/admin/quality-store.js.map +1 -1
  21. package/lib/admin/service.js +178 -0
  22. package/lib/admin/service.js.map +1 -1
  23. package/lib/cli.js +24 -4
  24. package/lib/cli.js.map +1 -1
  25. package/lib/client/QaAccountsController.js +52 -5
  26. package/lib/client/QaAccountsController.js.map +1 -1
  27. package/lib/client/QaConfigController.js.map +1 -1
  28. package/lib/client/QaSessionController.js +626 -63
  29. package/lib/client/QaSessionController.js.map +1 -1
  30. package/lib/client/QaSurface.js +293 -43
  31. package/lib/client/QaSurface.js.map +1 -1
  32. package/lib/client/QaSurfaceGuard.js +2 -2
  33. package/lib/client/QaSurfaceGuard.js.map +1 -1
  34. package/lib/client/QaTranscriptAdapter.js +4 -2
  35. package/lib/client/QaTranscriptAdapter.js.map +1 -1
  36. package/lib/client/admin/QaAdmin.js +59 -34
  37. package/lib/client/admin/QaAdmin.js.map +1 -1
  38. package/lib/client/admin/pages/Conversations.js +12 -12
  39. package/lib/client/admin/pages/Conversations.js.map +1 -1
  40. package/lib/client/admin/pages/ExpertMemory.js +210 -0
  41. package/lib/client/admin/pages/ExpertMemory.js.map +1 -0
  42. package/lib/client/admin/pages/Overview.js +4 -4
  43. package/lib/client/admin/pages/Overview.js.map +1 -1
  44. package/lib/client/admin/pages/Quality.js +5 -5
  45. package/lib/client/admin/pages/Quality.js.map +1 -1
  46. package/lib/client/admin/pages/Review.js +5 -5
  47. package/lib/client/admin/pages/Review.js.map +1 -1
  48. package/lib/client/admin/pages/SkillFiles.js +3 -3
  49. package/lib/client/admin/pages/SkillFiles.js.map +1 -1
  50. package/lib/client/admin/pages/Users.js +12 -12
  51. package/lib/client/admin/pages/Users.js.map +1 -1
  52. package/lib/client/admin/routes.js +4 -0
  53. package/lib/client/admin/routes.js.map +1 -1
  54. package/lib/client/admin/shared.js +15 -6
  55. package/lib/client/admin/shared.js.map +1 -1
  56. package/lib/client/admin/styles.js +3 -0
  57. package/lib/client/admin/styles.js.map +1 -1
  58. package/lib/client/attestation.js +11 -2
  59. package/lib/client/attestation.js.map +1 -1
  60. package/lib/client/audit/QaAuditDialog.js +2 -2
  61. package/lib/client/audit/QaAuditDialog.js.map +1 -1
  62. package/lib/client/audit/QaAuditRowBadge.js +1 -1
  63. package/lib/client/audit/QaAuditRowBadge.js.map +1 -1
  64. package/lib/client/components/Markdown.js +7 -2
  65. package/lib/client/components/Markdown.js.map +1 -1
  66. package/lib/client/components/QaAgentsDrawer.js +7 -5
  67. package/lib/client/components/QaAgentsDrawer.js.map +1 -1
  68. package/lib/client/components/QaApproval.js +2 -2
  69. package/lib/client/components/QaApproval.js.map +1 -1
  70. package/lib/client/components/QaAuthGate.js +6 -6
  71. package/lib/client/components/QaAuthGate.js.map +1 -1
  72. package/lib/client/components/QaChangelog.js +77 -0
  73. package/lib/client/components/QaChangelog.js.map +1 -1
  74. package/lib/client/components/QaComposer.js +9 -6
  75. package/lib/client/components/QaComposer.js.map +1 -1
  76. package/lib/client/components/QaFileAttachment.js +1 -1
  77. package/lib/client/components/QaFileAttachment.js.map +1 -1
  78. package/lib/client/components/QaFilesPanel.js +7 -7
  79. package/lib/client/components/QaFilesPanel.js.map +1 -1
  80. package/lib/client/components/QaHeader.js +4 -4
  81. package/lib/client/components/QaHeader.js.map +1 -1
  82. package/lib/client/components/QaMessage.js +12 -12
  83. package/lib/client/components/QaMessage.js.map +1 -1
  84. package/lib/client/components/QaModal.js +11 -2
  85. package/lib/client/components/QaModal.js.map +1 -1
  86. package/lib/client/components/QaQuestions.js +4 -4
  87. package/lib/client/components/QaQuestions.js.map +1 -1
  88. package/lib/client/components/QaQueueDock.js +137 -0
  89. package/lib/client/components/QaQueueDock.js.map +1 -0
  90. package/lib/client/components/QaRequestQueue.js +22 -0
  91. package/lib/client/components/QaRequestQueue.js.map +1 -0
  92. package/lib/client/components/QaRightRail.js +2 -2
  93. package/lib/client/components/QaRightRail.js.map +1 -1
  94. package/lib/client/components/QaSidebar.js +98 -7
  95. package/lib/client/components/QaSidebar.js.map +1 -1
  96. package/lib/client/components/QaSourcesPanel.js +10 -8
  97. package/lib/client/components/QaSourcesPanel.js.map +1 -1
  98. package/lib/client/components/QaThemeSwitcher.js +19 -0
  99. package/lib/client/components/QaThemeSwitcher.js.map +1 -0
  100. package/lib/client/components/QaTurnNotice.js +40 -0
  101. package/lib/client/components/QaTurnNotice.js.map +1 -0
  102. package/lib/client/components/QaTurnRail.js +3 -3
  103. package/lib/client/components/QaTurnRail.js.map +1 -1
  104. package/lib/client/components/QaWelcomeNotice.js +2 -6
  105. package/lib/client/components/QaWelcomeNotice.js.map +1 -1
  106. package/lib/client/components/QaWidthHandle.js +2 -2
  107. package/lib/client/components/QaWidthHandle.js.map +1 -1
  108. package/lib/client/components/QaWorkGroup.js +5 -5
  109. package/lib/client/components/QaWorkGroup.js.map +1 -1
  110. package/lib/client/components/QaWorkspaceBrowser.js +10 -10
  111. package/lib/client/components/QaWorkspaceBrowser.js.map +1 -1
  112. package/lib/client/components/VariantSwitcher.js +1 -1
  113. package/lib/client/components/VariantSwitcher.js.map +1 -1
  114. package/lib/client/failure-copy.js +26 -8
  115. package/lib/client/failure-copy.js.map +1 -1
  116. package/lib/client/focus-ring.js +193 -0
  117. package/lib/client/focus-ring.js.map +1 -0
  118. package/lib/client/index.js +40 -20
  119. package/lib/client/index.js.map +1 -1
  120. package/lib/client/lineage.js +3 -16
  121. package/lib/client/lineage.js.map +1 -1
  122. package/lib/client/markdown/CodeBlock.js +2 -2
  123. package/lib/client/markdown/CodeBlock.js.map +1 -1
  124. package/lib/client/markdown/blocks.js +118 -31
  125. package/lib/client/markdown/blocks.js.map +1 -1
  126. package/lib/client/markdown/math.js +1 -1
  127. package/lib/client/markdown/math.js.map +1 -1
  128. package/lib/client/markdown/render.js +32 -16
  129. package/lib/client/markdown/render.js.map +1 -1
  130. package/lib/client/markdown/source-chip.js +3 -3
  131. package/lib/client/markdown/source-chip.js.map +1 -1
  132. package/lib/client/notifications/notification-dispatcher.js +90 -0
  133. package/lib/client/notifications/notification-dispatcher.js.map +1 -0
  134. package/lib/client/notifications/preferences.js +61 -0
  135. package/lib/client/notifications/preferences.js.map +1 -0
  136. package/lib/client/notifications/turn-completion-source.js +82 -0
  137. package/lib/client/notifications/turn-completion-source.js.map +1 -0
  138. package/lib/client/notifications/use-turn-notifications.js +108 -0
  139. package/lib/client/notifications/use-turn-notifications.js.map +1 -0
  140. package/lib/client/panels/PanelHost.js +5 -5
  141. package/lib/client/panels/PanelHost.js.map +1 -1
  142. package/lib/client/panels/PanelLauncher.js +2 -2
  143. package/lib/client/panels/PanelLauncher.js.map +1 -1
  144. package/lib/client/project-session-state.js +156 -4
  145. package/lib/client/project-session-state.js.map +1 -1
  146. package/lib/client/role/RoleSelector.js +9 -4
  147. package/lib/client/role/RoleSelector.js.map +1 -1
  148. package/lib/client/settings/card.js +58 -17
  149. package/lib/client/settings/card.js.map +1 -1
  150. package/lib/client/settings/fields.js +11 -10
  151. package/lib/client/settings/fields.js.map +1 -1
  152. package/lib/client/settings/format.js +0 -9
  153. package/lib/client/settings/format.js.map +1 -1
  154. package/lib/client/settings/sections/access.js +5 -5
  155. package/lib/client/settings/sections/access.js.map +1 -1
  156. package/lib/client/settings/sections/accounts.js +10 -10
  157. package/lib/client/settings/sections/accounts.js.map +1 -1
  158. package/lib/client/settings/sections/attachments.js +6 -6
  159. package/lib/client/settings/sections/attachments.js.map +1 -1
  160. package/lib/client/settings/sections/branding.js +7 -7
  161. package/lib/client/settings/sections/branding.js.map +1 -1
  162. package/lib/client/settings/sections/common.js +4 -2
  163. package/lib/client/settings/sections/common.js.map +1 -1
  164. package/lib/client/settings/sections/docs.js +4 -3
  165. package/lib/client/settings/sections/docs.js.map +1 -1
  166. package/lib/client/settings/sections/embedding.js +2 -2
  167. package/lib/client/settings/sections/embedding.js.map +1 -1
  168. package/lib/client/settings/sections/interface.js +12 -12
  169. package/lib/client/settings/sections/interface.js.map +1 -1
  170. package/lib/client/settings/sections/lockdown.js +9 -9
  171. package/lib/client/settings/sections/lockdown.js.map +1 -1
  172. package/lib/client/settings/sections/notes.js +12 -12
  173. package/lib/client/settings/sections/notes.js.map +1 -1
  174. package/lib/client/settings/sections/session.js +15 -12
  175. package/lib/client/settings/sections/session.js.map +1 -1
  176. package/lib/client/settings/sections/slash.js +9 -9
  177. package/lib/client/settings/sections/slash.js.map +1 -1
  178. package/lib/client/settings/sections/sources.js +26 -26
  179. package/lib/client/settings/sections/sources.js.map +1 -1
  180. package/lib/client/settings/sections/status.js +2 -2
  181. package/lib/client/settings/sections/status.js.map +1 -1
  182. package/lib/client/settings/styles.js +15 -9
  183. package/lib/client/settings/styles.js.map +1 -1
  184. package/lib/client/slash/SlashPalette.js +4 -4
  185. package/lib/client/slash/SlashPalette.js.map +1 -1
  186. package/lib/client/styles.js +145 -83
  187. package/lib/client/styles.js.map +1 -1
  188. package/lib/client/theme-preference.js +162 -0
  189. package/lib/client/theme-preference.js.map +1 -0
  190. package/lib/client/types.js +8 -0
  191. package/lib/client/types.js.map +1 -1
  192. package/lib/client/use-session-ui-state.js +7 -5
  193. package/lib/client/use-session-ui-state.js.map +1 -1
  194. package/lib/client/user-settings/GeneralSettingsPage.js +6 -6
  195. package/lib/client/user-settings/GeneralSettingsPage.js.map +1 -1
  196. package/lib/client/user-settings/IntegrationTokensPage.js +6 -6
  197. package/lib/client/user-settings/IntegrationTokensPage.js.map +1 -1
  198. package/lib/client/user-settings/NotificationSettingsPage.js +107 -0
  199. package/lib/client/user-settings/NotificationSettingsPage.js.map +1 -0
  200. package/lib/client/user-settings/PasswordSettingsPage.js +1 -1
  201. package/lib/client/user-settings/PasswordSettingsPage.js.map +1 -1
  202. package/lib/client/user-settings/ProfileSettingsPage.js +2 -2
  203. package/lib/client/user-settings/ProfileSettingsPage.js.map +1 -1
  204. package/lib/client/user-settings/SkillCatalog.js +3 -3
  205. package/lib/client/user-settings/SkillCatalog.js.map +1 -1
  206. package/lib/client/user-settings/SkillEditor.js +26 -8
  207. package/lib/client/user-settings/SkillEditor.js.map +1 -1
  208. package/lib/client/user-settings/SkillToolPicker.js +3 -3
  209. package/lib/client/user-settings/SkillToolPicker.js.map +1 -1
  210. package/lib/client/user-settings/SkillsSettingsPage.js +1 -1
  211. package/lib/client/user-settings/SkillsSettingsPage.js.map +1 -1
  212. package/lib/client/user-settings/StartersSettingsPage.js +6 -6
  213. package/lib/client/user-settings/StartersSettingsPage.js.map +1 -1
  214. package/lib/client/user-settings/UserSettingsDialog.js +7 -2
  215. package/lib/client/user-settings/UserSettingsDialog.js.map +1 -1
  216. package/lib/client/user-settings/copy.js +2 -0
  217. package/lib/client/user-settings/copy.js.map +1 -1
  218. package/lib/client/user-settings/draft.js +4 -1
  219. package/lib/client/user-settings/draft.js.map +1 -1
  220. package/lib/client/user-settings/fields.js +5 -5
  221. package/lib/client/user-settings/fields.js.map +1 -1
  222. package/lib/client.js +7307 -4010
  223. package/lib/client.js.map +1 -1
  224. package/lib/config-resolvers/defaults.js +7 -0
  225. package/lib/config-resolvers/defaults.js.map +1 -1
  226. package/lib/config-resolvers/lockdown.js +7 -0
  227. package/lib/config-resolvers/lockdown.js.map +1 -1
  228. package/lib/config-resolvers/notifications.js +15 -0
  229. package/lib/config-resolvers/notifications.js.map +1 -0
  230. package/lib/config-resolvers/session.js +11 -1
  231. package/lib/config-resolvers/session.js.map +1 -1
  232. package/lib/config.js +67 -18
  233. package/lib/config.js.map +1 -1
  234. package/lib/enforcement/tool-grants.js +6 -6
  235. package/lib/enforcement/tool-grants.js.map +1 -1
  236. package/lib/index.js +145 -30
  237. package/lib/index.js.map +1 -1
  238. package/lib/integration/answer.js +74 -8
  239. package/lib/integration/answer.js.map +1 -1
  240. package/lib/integration/expert-memory.js +39 -0
  241. package/lib/integration/expert-memory.js.map +1 -0
  242. package/lib/integration/host-runner.js +26 -14
  243. package/lib/integration/host-runner.js.map +1 -1
  244. package/lib/notifications.js +66 -0
  245. package/lib/notifications.js.map +1 -0
  246. package/lib/personal-skills/errors.js.map +1 -1
  247. package/lib/personal-skills/service.js +81 -7
  248. package/lib/personal-skills/service.js.map +1 -1
  249. package/lib/prompt-notes.js +2 -10
  250. package/lib/prompt-notes.js.map +1 -1
  251. package/lib/provenance/host-store.js +8 -5
  252. package/lib/provenance/host-store.js.map +1 -1
  253. package/lib/qa-tools/docs-tools.js +1 -1
  254. package/lib/qa-tools/docs-tools.js.map +1 -1
  255. package/lib/qa-tools/lifecycle.js +6 -3
  256. package/lib/qa-tools/lifecycle.js.map +1 -1
  257. package/lib/request-queue.js +32 -0
  258. package/lib/request-queue.js.map +1 -0
  259. package/lib/resolve-config.js +2 -0
  260. package/lib/resolve-config.js.map +1 -1
  261. package/lib/secure-session.js +7 -2
  262. package/lib/secure-session.js.map +1 -1
  263. package/lib/shared/settings.js +19 -2
  264. package/lib/shared/settings.js.map +1 -1
  265. package/lib/turn-failure-log.js +67 -0
  266. package/lib/turn-failure-log.js.map +1 -0
  267. package/lib/typert.host.js +1398 -651
  268. package/lib/typert.remote-client.d.ts +15 -1
  269. package/lib/typert.remote-client.js +1315 -645
  270. package/lib/types/access/capability-catalog.d.ts +20 -4
  271. package/lib/types/access/model.d.ts +23 -0
  272. package/lib/types/access/service.d.ts +2 -3
  273. package/lib/types/account-remotes.d.ts +8 -1
  274. package/lib/types/accounts/database.d.ts +13 -3
  275. package/lib/types/accounts/errors.d.ts +9 -1
  276. package/lib/types/accounts/file.d.ts +12 -0
  277. package/lib/types/accounts/store.d.ts +30 -2
  278. package/lib/types/admin/quality-store.d.ts +33 -0
  279. package/lib/types/admin/service.d.ts +60 -1
  280. package/lib/types/client/QaAccountsController.d.ts +25 -2
  281. package/lib/types/client/QaConfigController.d.ts +2 -2
  282. package/lib/types/client/QaSessionController.d.ts +199 -6
  283. package/lib/types/client/QaSurface.d.ts +6 -1
  284. package/lib/types/client/admin/pages/ExpertMemory.d.ts +19 -0
  285. package/lib/types/client/admin/routes.d.ts +3 -1
  286. package/lib/types/client/admin/shared.d.ts +4 -0
  287. package/lib/types/client/components/Markdown.d.ts +8 -1
  288. package/lib/types/client/components/QaHeader.d.ts +18 -4
  289. package/lib/types/client/components/QaModal.d.ts +7 -0
  290. package/lib/types/client/components/QaQueueDock.d.ts +27 -0
  291. package/lib/types/client/components/QaRequestQueue.d.ts +20 -0
  292. package/lib/types/client/components/QaSidebar.d.ts +14 -0
  293. package/lib/types/client/components/QaThemeSwitcher.d.ts +14 -0
  294. package/lib/types/client/components/QaTurnNotice.d.ts +55 -0
  295. package/lib/types/client/focus-ring.d.ts +65 -0
  296. package/lib/types/client/lineage.d.ts +3 -13
  297. package/lib/types/client/markdown/blocks.d.ts +30 -2
  298. package/lib/types/client/markdown/render.d.ts +4 -1
  299. package/lib/types/client/notifications/notification-dispatcher.d.ts +68 -0
  300. package/lib/types/client/notifications/preferences.d.ts +44 -0
  301. package/lib/types/client/notifications/turn-completion-source.d.ts +114 -0
  302. package/lib/types/client/notifications/use-turn-notifications.d.ts +51 -0
  303. package/lib/types/client/project-session-state.d.ts +35 -1
  304. package/lib/types/client/role/RoleSelector.d.ts +7 -1
  305. package/lib/types/client/settings/card.d.ts +36 -5
  306. package/lib/types/client/settings/fields.d.ts +24 -0
  307. package/lib/types/client/settings/format.d.ts +0 -5
  308. package/lib/types/client/settings/sections/common.d.ts +3 -1
  309. package/lib/types/client/settings/sections/session.d.ts +2 -1
  310. package/lib/types/client/settings/styles.d.ts +9 -4
  311. package/lib/types/client/settings-extensions/user-session.d.ts +1 -1
  312. package/lib/types/client/theme-preference.d.ts +69 -0
  313. package/lib/types/client/types.d.ts +25 -1
  314. package/lib/types/client/use-session-ui-state.d.ts +6 -4
  315. package/lib/types/client/user-settings/GeneralSettingsPage.d.ts +5 -5
  316. package/lib/types/client/user-settings/NotificationSettingsPage.d.ts +40 -0
  317. package/lib/types/client/user-settings/UserSettingsDialog.d.ts +12 -2
  318. package/lib/types/client/user-settings/draft.d.ts +4 -1
  319. package/lib/types/client/user-settings/fields.d.ts +7 -0
  320. package/lib/types/config-resolvers/notifications.d.ts +10 -0
  321. package/lib/types/config-resolvers/session.d.ts +6 -0
  322. package/lib/types/config.d.ts +20 -1
  323. package/lib/types/index.d.ts +53 -5
  324. package/lib/types/integration/answer.d.ts +27 -0
  325. package/lib/types/integration/expert-memory.d.ts +49 -0
  326. package/lib/types/notifications.d.ts +32 -0
  327. package/lib/types/personal-skills/errors.d.ts +1 -1
  328. package/lib/types/personal-skills/service.d.ts +6 -0
  329. package/lib/types/prompt-notes.d.ts +17 -4
  330. package/lib/types/request-queue.d.ts +32 -0
  331. package/lib/types/shared/settings.d.ts +19 -2
  332. package/lib/types/turn-failure-log.d.ts +18 -0
  333. package/lib/types/types.d.ts +180 -4
  334. package/package.json +64 -63
  335. package/scripts/attach-workspace-sessions.mjs +24 -5
  336. package/scripts/repair-session-events.mjs +25 -5
  337. package/lib/client/markdown/MermaidBlock.js +0 -80
  338. package/lib/client/markdown/MermaidBlock.js.map +0 -1
  339. package/lib/types/client/markdown/MermaidBlock.d.ts +0 -6
@@ -8,21 +8,29 @@ import { QA_SESSION_IDLE_STATE } from "./types.js";
8
8
  import { QaAuthGate } from "./components/QaAuthGate.js";
9
9
  import { QaApproval } from "./components/QaApproval.js";
10
10
  import { QaQuestions } from "./components/QaQuestions.js";
11
+ import { QaRequestQueue } from "./components/QaRequestQueue.js";
11
12
  import { QaComposer } from "./components/QaComposer.js";
13
+ import { QaQueueDock } from "./components/QaQueueDock.js";
12
14
  import { QaHeader, QaSubagentBanner } from "./components/QaHeader.js";
15
+ import { QaThemeSwitcher } from "./components/QaThemeSwitcher.js";
16
+ import { useQaThemePreference } from "./theme-preference.js";
13
17
  import { QaMessage } from "./components/QaMessage.js";
14
18
  import { buildChatRows, QaSidebar } from "./components/QaSidebar.js";
15
19
  import { QaAgentsDrawer, collectSubagents, } from "./components/QaAgentsDrawer.js";
16
20
  import { collectChatFiles, countChatAttachments } from "./chat-files.js";
17
21
  import { QaFilesPanel } from "./components/QaFilesPanel.js";
22
+ import { isQaModalOpen } from "./components/QaModal.js";
18
23
  import { QaRightRail } from "./components/QaRightRail.js";
19
24
  import { QaSourcesPanel } from "./components/QaSourcesPanel.js";
25
+ import { QA_TURN_NOTICE_LINE_SELECTOR, QA_TURN_NOTICE_OFFER_SELECTOR, QaTurnNotice, } from "./components/QaTurnNotice.js";
26
+ import { useQaTurnNotifications } from "./notifications/use-turn-notifications.js";
20
27
  import { QA_TURN_FOLLOW_PX, QaTurnRail, computeActiveTurn, scrollToTranscriptAnchor, } from "./components/QaTurnRail.js";
21
28
  import { QaWidthHandle, useQaContentWidth, } from "./components/QaWidthHandle.js";
22
29
  import { VariantSwitcher } from "./components/VariantSwitcher.js";
23
30
  import { QaWelcomeNotice } from "./components/QaWelcomeNotice.js";
24
31
  import { statusText, titleFromMessages } from "./components/surface-utils.js";
25
32
  import { useThinkingPhrase } from "./components/thinking-phrases.js";
33
+ import { focusFirst, focusRing, focusable, isInert, trapKeys, } from "./focus-ring.js";
26
34
  import { QaUserSettingsDialog, } from "./user-settings/UserSettingsDialog.js";
27
35
  import { useTranscriptView } from "./use-transcript-view.js";
28
36
  import { useSessionUiState } from "./use-session-ui-state.js";
@@ -51,37 +59,42 @@ const QA_SURFACE_CLASS = qaKioskMode()
51
59
  const NO_QUESTIONS = Object.freeze([]);
52
60
  const ACCOUNTS_CHECKING_SNAPSHOT = { stage: "checking" };
53
61
  const noopAccountsSnapshot = () => ACCOUNTS_CHECKING_SNAPSHOT;
54
- function focusable(root) {
55
- return [
56
- ...root.querySelectorAll("button:not([disabled]), textarea:not([disabled]), a[href], [tabindex]:not([tabindex='-1'])"),
57
- ].filter((element) => !element.hidden);
58
- }
59
- function trapKeys(event) {
60
- event.stopPropagation();
61
- if (event.key !== "Tab")
62
- return;
63
- const items = focusable(event.currentTarget);
64
- if (items.length === 0) {
65
- event.preventDefault();
66
- event.currentTarget.focus();
67
- return;
68
- }
69
- const first = items[0];
70
- const last = items.at(-1);
71
- if (event.shiftKey && document.activeElement === first) {
72
- event.preventDefault();
73
- last?.focus();
74
- }
75
- else if (!event.shiftKey && document.activeElement === last) {
76
- event.preventDefault();
77
- first?.focus();
78
- }
79
- }
80
62
  /** Same follow threshold the rail uses: this close to the floor is "at bottom". */
81
63
  function isNearBottom(element) {
82
64
  return (element.scrollHeight - element.scrollTop - element.clientHeight <
83
65
  QA_TURN_FOLLOW_PX);
84
66
  }
67
+ /**
68
+ * The stack line the reader's control stands on, or null when the ring has no
69
+ * place to keep for it. The desktop opt-in is not a line: it is what the stack
70
+ * ends with, so losing it is losing the last control of the last line, and that
71
+ * line is taken as its place. The opt-in is read by its own block rather than as
72
+ * whatever the stack holds that is not a line — the next block the stack grows
73
+ * (a heading, a «скрыть все») is neither, and handing the reader to a line they
74
+ * never stood on is worse than handing them nowhere.
75
+ */
76
+ function noticeLineOf(target, stack) {
77
+ const line = target.closest(QA_TURN_NOTICE_LINE_SELECTOR);
78
+ if (line !== null)
79
+ return line;
80
+ if (target.closest(QA_TURN_NOTICE_OFFER_SELECTOR) === null) {
81
+ return null;
82
+ }
83
+ const lines = stack.querySelectorAll(QA_TURN_NOTICE_LINE_SELECTOR);
84
+ return lines.item(lines.length - 1);
85
+ }
86
+ /**
87
+ * The line the stack lists on this side of `line`, or null when there is none:
88
+ * the opt-in block shares the stack with the lines but is not one of them, so it
89
+ * is not a place a reader can be handed back to.
90
+ */
91
+ function adjacentNoticeLine(line, side) {
92
+ const sibling = side === "previous" ? line.previousElementSibling : line.nextElementSibling;
93
+ return sibling instanceof HTMLElement &&
94
+ sibling.matches(QA_TURN_NOTICE_LINE_SELECTOR)
95
+ ? sibling
96
+ : null;
97
+ }
85
98
  export function QaSurface(props) {
86
99
  const route = useSyncExternalStore(props.route.subscribe, props.route.getSnapshot, props.route.getSnapshot);
87
100
  const configState = useSyncExternalStore(props.config.subscribe, props.config.getSnapshot, props.config.getSnapshot);
@@ -106,10 +119,113 @@ export function QaSurface(props) {
106
119
  const transcript = useRef(null);
107
120
  const chat = useRef(null);
108
121
  const nearBottom = useRef(true);
122
+ const surface = useRef(null);
123
+ /** The notice stack: after `<main>` in the page, inside the ring with it. */
124
+ const noticeStack = useRef(null);
125
+ /**
126
+ * Whether a dialog, rather than this ring, owns the keyboard: the onboarding
127
+ * gate holds the page inert, and a `QaModal` stands over it with the Escape of
128
+ * its own. Neither paints the notice stack away — a dialog is a neighbour of
129
+ * it, not an ancestor — so the ring has to step back of its own accord, and
130
+ * step back to the subtree it counted before the stack was ever part of it.
131
+ */
132
+ const dialogOwnsKeyboard = useCallback(() => {
133
+ const root = surface.current;
134
+ return isQaModalOpen() || (root !== null && isInert(root));
135
+ }, []);
136
+ /** The roots the Tab ring is drawn around, in the order the ring walks them. */
137
+ const ringRoots = useCallback(() => dialogOwnsKeyboard()
138
+ ? [surface.current]
139
+ : [surface.current, noticeStack.current], [dialogOwnsKeyboard]);
140
+ /**
141
+ * Where the keyboard last stood in the notice stack: the control, the line it
142
+ * stood on, the lines the stack listed beside it, and the control's step
143
+ * within the line. The stack alone is remembered: a control of `<main>` that
144
+ * leaves the page with its row — a queue dock entry, a transcript action, a
145
+ * rebuilt chat — is the surface's own business, and this ring hands nothing
146
+ * back there. Read as focus moves, so it names where the reader stands rather
147
+ * than where they once stood: a focus landing anywhere else lets the place go.
148
+ *
149
+ * The place is held as lines, not as a number counted along the ring, because
150
+ * a counted place is not a stable coordinate here. A finished turn is put in
151
+ * front of the lines the reader already sees (`use-turn-notifications.ts`), so
152
+ * a step counted from the front of the stack slides by as many controls as the
153
+ * batch brought, and `<main>`, which the ring counts first, grows and shrinks
154
+ * for reasons of its own. The line the reader stood on and its neighbours keep
155
+ * the one claim the restore has to make — hand the keyboard to the line that
156
+ * took their place — whatever else arrived or left in the same moment.
157
+ */
158
+ const ringAnchor = useRef(null);
159
+ useEffect(() => {
160
+ const onFocusIn = (event) => {
161
+ const target = event.target;
162
+ const stack = noticeStack.current;
163
+ if (!(target instanceof HTMLElement) ||
164
+ stack === null ||
165
+ !stack.contains(target)) {
166
+ ringAnchor.current = null;
167
+ return;
168
+ }
169
+ const line = noticeLineOf(target, stack);
170
+ if (line === null) {
171
+ ringAnchor.current = null;
172
+ return;
173
+ }
174
+ const controls = focusable(line);
175
+ const step = controls.indexOf(target);
176
+ ringAnchor.current = {
177
+ element: target,
178
+ line,
179
+ // A control outside every line is the desktop opt-in, and the place it
180
+ // stands for is the front of the line the stack keeps last. Not its end:
181
+ // the last control is the cross, which answers the Enter the reader gave
182
+ // the offer by waving a notification they never aimed at.
183
+ step: step < 0 ? 0 : step,
184
+ before: adjacentNoticeLine(line, "previous"),
185
+ after: adjacentNoticeLine(line, "next"),
186
+ };
187
+ };
188
+ document.addEventListener("focusin", onFocusIn);
189
+ return () => document.removeEventListener("focusin", onFocusIn);
190
+ }, []);
191
+ /**
192
+ * Keys typed in the surface: Tab stays in the ring, and nothing else of the
193
+ * key reaches the harness the overlay is built on.
194
+ */
195
+ const trapSurfaceKeys = useCallback((event) => {
196
+ trapKeys(event, ringRoots());
197
+ }, [ringRoots]);
198
+ /**
199
+ * Keys typed in the notice stack, where no ancestor `<main>` can hear them.
200
+ *
201
+ * Only the Tab this ring answers is taken; every other key is left to bubble
202
+ * as the browser would carry it, so a dialog that listens on the window —
203
+ * `QaModal`, and the settings dialog built on it — still hears the Escape the
204
+ * reader gives it. A dialog is also why that Tab is left alone: the stack is
205
+ * the dialog's neighbour rather than its content, so a ring closed around both
206
+ * would take the reader out of the dialog they are working in and put them
207
+ * back on a page held under the scrim.
208
+ */
209
+ const trapNoticeKeys = useCallback((event) => {
210
+ if (event.key !== "Tab" || dialogOwnsKeyboard())
211
+ return;
212
+ trapKeys(event, ringRoots());
213
+ }, [dialogOwnsKeyboard, ringRoots]);
109
214
  /** Turn marks of the visible transcript, kept in a ref for stable callbacks. */
110
215
  const railItemsRef = useRef([]);
111
216
  const activeTurnFrame = useRef(null);
112
217
  const stateKey = qaStorageNamespace(config);
218
+ // The palette belongs to the surface, so it is only painted while the
219
+ // surface is what the visitor is looking at: outside its own route the
220
+ // overlay is mounted and invisible, and flipping the Host document's
221
+ // attributes from there would be the QA stand restyling the harness. What the
222
+ // surface borrowed for the visit it hands back on the way out, for the same
223
+ // reason — see `useQaThemePreference`.
224
+ const theme = useQaThemePreference({
225
+ active: route.active,
226
+ storage: window.localStorage,
227
+ storageKey: `${stateKey}:theme`,
228
+ });
113
229
  const isAdmin = accountsSnapshot.stage === "authed" &&
114
230
  accountsSnapshot.user.role === "admin";
115
231
  // The preview belongs to the history entry that asked for it, so the
@@ -214,6 +330,9 @@ export function QaSurface(props) {
214
330
  ? {}
215
331
  : { questionApi: props.questionApi }),
216
332
  ...(props.slashApi === undefined ? {} : { slashApi: props.slashApi }),
333
+ ...(props.queueStatus === undefined
334
+ ? {}
335
+ : { queueStatus: props.queueStatus }),
217
336
  config,
218
337
  initialSubrole: selectedSubrole,
219
338
  adminPreview: previewing,
@@ -240,6 +359,7 @@ export function QaSurface(props) {
240
359
  props.fileUpload,
241
360
  props.approvalApi,
242
361
  props.questionApi,
362
+ props.queueStatus,
243
363
  props.secureSession,
244
364
  props.sourceApi,
245
365
  props.sessions,
@@ -327,9 +447,9 @@ export function QaSurface(props) {
327
447
  ]);
328
448
  const listState = useSyncExternalStore(props.sessions.list.subscribe, props.sessions.list.getSnapshot, props.sessions.list.getSnapshot);
329
449
  // Draft text, attachments, variant offsets and drawers are chat-local; the
330
- // hook clears them whenever the bound session changes. The rail controller
331
- // takes the drawer slice; the surface keeps the composer and marks state.
332
- const ui = useSessionUiState(state.sessionId);
450
+ // hook clears them whenever the chat changes. The rail controller takes the
451
+ // drawer slice; the surface keeps the composer and marks state.
452
+ const ui = useSessionUiState(state.chatKey);
333
453
  const rail = useRightRail(ui);
334
454
  const { activeTurn, setActiveTurn, variantOffsets, setVariantOffsets, agentsOpen, setAgentsOpen, pendingAttachments, setPendingAttachments, } = ui;
335
455
  useEffect(() => {
@@ -361,6 +481,15 @@ export function QaSurface(props) {
361
481
  const handleNewChat = useCallback(() => {
362
482
  void controller?.startDraft();
363
483
  }, [controller]);
484
+ // The phone layout's history drawer. Its state is the surface's, not the
485
+ // sidebar's, because the control that opens it stands in the header: the narrow
486
+ // sheet switches the whole sidebar subtree off, and an opener inside it is no
487
+ // opener at all. The sidebar keeps its own collapsed rail, which is a
488
+ // wide-layout choice and a different thing — a phone that opens the drawer gets
489
+ // the list whatever that rail remembers.
490
+ const [historyOpen, setHistoryOpen] = useState(false);
491
+ const toggleHistory = useCallback(() => setHistoryOpen((open) => !open), []);
492
+ const closeHistory = useCallback(() => setHistoryOpen(false), []);
364
493
  // The audit dialog's target, not its state: opening it is a view choice, and
365
494
  // closing it must not disturb the chat underneath.
366
495
  const [auditTarget, setAuditTarget] = useState(null);
@@ -372,6 +501,10 @@ export function QaSurface(props) {
372
501
  void controller?.deleteChat(sessionId);
373
502
  }, [controller]);
374
503
  const handleSend = useCallback((text, attachments, pick) => controller?.send(text, attachments, pick) ?? Promise.resolve(false), [controller]);
504
+ // Closing the queue notice only uncovers the composer: the question the stand
505
+ // had no room for is still the text in there, and asking it again is a
506
+ // keystroke the visitor takes when a place frees up.
507
+ const dismissRequestQueue = useCallback(() => controller?.dismissRequestQueueNotice(), [controller]);
375
508
  // Cheap refresh on every palette opening: the skill registry has no browser
376
509
  // change event, and the controller skips the round-trip while its answer is
377
510
  // still fresh.
@@ -379,6 +512,9 @@ export function QaSurface(props) {
379
512
  void controller?.refreshSlashCatalog();
380
513
  }, [controller]);
381
514
  const handleStop = useCallback(() => controller?.stop() ?? Promise.resolve(), [controller]);
515
+ const handleQueueEdit = useCallback((id, text) => controller?.queueAction(id, "edit", text) ?? Promise.resolve(null), [controller]);
516
+ const handleQueueSendNow = useCallback((id) => controller?.queueAction(id, "steer") ?? Promise.resolve(null), [controller]);
517
+ const handleQueueRemove = useCallback((id) => controller?.queueAction(id, "remove") ?? Promise.resolve(null), [controller]);
382
518
  const handleAnswerApproval = useCallback((requestId, decision) => controller?.answerApproval(requestId, decision) ?? Promise.resolve(), [controller]);
383
519
  const handleAnswerQuestion = useCallback((requestId, answers) => controller?.answerQuestion(requestId, answers) ?? Promise.resolve(), [controller]);
384
520
  const handleCancelQuestion = useCallback((requestId) => controller?.cancelQuestion(requestId) ?? Promise.resolve(), [controller]);
@@ -540,15 +676,122 @@ export function QaSurface(props) {
540
676
  onSave: (input) => accounts.updateStarters(input),
541
677
  }
542
678
  : undefined;
679
+ // One object serves both halves of the same decision: the form that edits
680
+ // it and the notices that obey it. A reader cannot be looking at one and
681
+ // waiting on the other, and a memo keeps the identity stable for the
682
+ // watcher that reads the channels out of it.
683
+ const notifications = {
684
+ notifications: accountsSnapshot.user.notifications,
685
+ switches: config.notifications,
686
+ onSave: (input) => accounts.updateNotifications(input),
687
+ };
543
688
  const skills = config.accounts.skills.enabled && boundSkillApi !== undefined
544
689
  ? boundSkillApi
545
690
  : undefined;
546
- return { profile, password, starters, skills, integrationTokens };
691
+ return {
692
+ profile,
693
+ password,
694
+ starters,
695
+ notifications,
696
+ skills,
697
+ integrationTokens,
698
+ };
547
699
  }, [accounts, accountsSnapshot, config, boundSkillApi, integrationTokens]);
548
700
  const busyTurn = state.phase === "running" ? (railItems.at(-1)?.turn ?? null) : null;
549
701
  // Projected even while the sidebar is hidden: the account settings report
550
702
  // the same chat count, and one projection cannot disagree with itself.
551
703
  const chatRows = useMemo(() => buildChatRows(controller?.chatIds() ?? [], listState.byId, activeSessionId, (id) => ownerNames?.get(id)), [controller, listState, activeSessionId, ownerNames, state.chatsRevision]);
704
+ // A turn that ends in one of this browser's own chats gets a notice: the
705
+ // sidebar dot going out is otherwise the only sign, and it is easy to miss
706
+ // from behind another window. The account's own list, not the sidebar's, is
707
+ // what bounds it: an admin's shared history is a wider read than ownership,
708
+ // and another account's turn is not this reader's news.
709
+ const turnNotices = useQaTurnNotifications({
710
+ chats: chatRows,
711
+ ownChatIds: accountsSnapshot.stage === "authed" ? accountsSnapshot.ownIds : undefined,
712
+ notifications: config.notifications,
713
+ storage: window.localStorage,
714
+ storageKey: `${stateKey}:notifications`,
715
+ // Signed in, the account's own channels decide and the settings form is
716
+ // what writes them; anonymously, the hook falls back to this browser.
717
+ account: settingsDialog?.notifications,
718
+ paused: state.phase === "reconnecting",
719
+ activeSessionId,
720
+ onSwitch: handleSwitch,
721
+ });
722
+ /**
723
+ * A line leaving the stack takes the control the reader was working out of the
724
+ * page, and a page with nothing focused answers Tab with the browser's own
725
+ * order — which leaves the QA interface. Most of the ways a line goes leave
726
+ * the stack standing and its count of lines where it was: waving one of the
727
+ * three off, a fourth turn pushing the oldest out of a full stack, the opt-in
728
+ * going away once the browser has answered the permission question. So the
729
+ * loss is read where it happens — the control the ring held is out of the page
730
+ * and nothing of the ring holds the focus — and the reader is handed to the
731
+ * line that took the lost one's place, on the same control of it: a cross
732
+ * given back as a cross. The anchor goes only with the focus it hands back: a
733
+ * page takes the focus out of a removed control on its own moment, not in step
734
+ * with this render. A dialog that holds the keyboard is not the ring's
735
+ * business, and is left to put the focus wherever it thinks the reader
736
+ * belongs.
737
+ *
738
+ * A page the reader is not looking at keeps its keyboard where it is: while
739
+ * `document.hasFocus()` is false nothing here is moved. The hand-off is owed
740
+ * rather than skipped — the anchor stands until the window comes back, and the
741
+ * `focus` event of this page is what pays it — because the reader who returns
742
+ * to a stack that dropped a line under them is exactly the reader this place
743
+ * was remembered for, while a control of a page no one is looking at has no
744
+ * claim on the keyboard of the page they are working in.
745
+ *
746
+ * Opening a chat from a line is the one path where a line goes and the chat
747
+ * changes in the same moment. The keyboard stays on the stack — on a
748
+ * neighbouring line, or back inside `<main>` once none is left — rather than
749
+ * following the switch: the composer the switch remounts is disabled while the
750
+ * chat is still being bound, so it can take no focus, and the surface moves
751
+ * the keyboard on a switch no further than it does anywhere else.
752
+ */
753
+ const restoreRingFocus = useCallback(() => {
754
+ const anchor = ringAnchor.current;
755
+ if (anchor === null || anchor.element.isConnected)
756
+ return;
757
+ if (dialogOwnsKeyboard()) {
758
+ ringAnchor.current = null;
759
+ return;
760
+ }
761
+ if (!document.hasFocus())
762
+ return;
763
+ const ring = focusRing(ringRoots());
764
+ const active = document.activeElement;
765
+ if (active !== null && ring.includes(active))
766
+ return;
767
+ ringAnchor.current = null;
768
+ // The line itself first, for a control that left while its line stayed:
769
+ // then whichever neighbour is still on screen, the one after the reader
770
+ // taking the place the stack still lists in that order.
771
+ const candidates = [];
772
+ for (const line of [anchor.line, anchor.after, anchor.before]) {
773
+ if (line === null || !line.isConnected)
774
+ continue;
775
+ const controls = focusable(line);
776
+ candidates.push(controls[Math.min(anchor.step, controls.length - 1)]);
777
+ }
778
+ // The stack has no line left to stand on: back into the interface, onto the
779
+ // control the ring reaches last, and past the first candidate of it that
780
+ // refuses the keyboard.
781
+ candidates.push(...[...ring].reverse(), surface.current ?? undefined);
782
+ focusFirst(candidates);
783
+ }, [dialogOwnsKeyboard, ringRoots]);
784
+ useEffect(() => {
785
+ restoreRingFocus();
786
+ });
787
+ useEffect(() => {
788
+ // The hand-off a blurred page owed, read again at the moment the reader
789
+ // comes back: whoever holds the keyboard then keeps it, and a control of the
790
+ // ring they reached on their own is not taken away from them.
791
+ const onWindowFocus = () => restoreRingFocus();
792
+ window.addEventListener("focus", onWindowFocus);
793
+ return () => window.removeEventListener("focus", onWindowFocus);
794
+ }, [restoreRingFocus]);
552
795
  // The audit provider is optional: `auditSnapshot.api` is null until the
553
796
  // audit plugin's client bundle is loaded, and the badge is absent until
554
797
  // then. Chat ids rather than rows feed the poll, so a re-render does not
@@ -594,7 +837,7 @@ export function QaSurface(props) {
594
837
  const welcomeNotice = (_jsx(QaWelcomeNotice, { storage: window.localStorage, storageKey: `${stateKey}:welcome-notice` }, stateKey));
595
838
  if (config.accounts.enabled && accounts !== undefined) {
596
839
  if (accountsStage === "checking") {
597
- return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx("main", { className: QA_SURFACE_CLASS, "aria-busy": "true", "aria-label": config.branding.title, tabIndex: -1 })] }));
840
+ return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx("main", { className: QA_SURFACE_CLASS, "data-testid": "qa-surface-loading", "aria-busy": "true", "aria-label": config.branding.title, tabIndex: -1 })] }));
598
841
  }
599
842
  if (accountsStage === "gate") {
600
843
  return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx(QaAuthGate, { accounts: accounts, snapshot: accountsSnapshot, title: config.branding.title, logoUrl: config.branding.logoUrl, allowRegistration: config.accounts.allowRegistration })] }));
@@ -650,9 +893,11 @@ export function QaSurface(props) {
650
893
  } }));
651
894
  }
652
895
  if (adminRoute) {
653
- return (_jsx("main", { className: "dsh-qa-admin", "aria-label": "\u0410\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 QA", children: _jsxs("div", { className: "dsh-qa-admin__loading", children: [_jsx("p", { children: "\u042D\u0442\u043E\u0442 \u0440\u0430\u0437\u0434\u0435\u043B \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0443." }), _jsx("button", { type: "button", onClick: () => window.history.pushState(null, "", config.route.path), children: "\u0412\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u0432 \u0447\u0430\u0442" })] }) }));
896
+ return (_jsx("main", { className: "dsh-qa-admin", "data-testid": "qa-surface-admin-denied", "aria-label": "\u0410\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 QA", children: _jsxs("div", { className: "dsh-qa-admin__loading", children: [_jsx("p", { children: "\u042D\u0442\u043E\u0442 \u0440\u0430\u0437\u0434\u0435\u043B \u0434\u043E\u0441\u0442\u0443\u043F\u0435\u043D \u0442\u043E\u043B\u044C\u043A\u043E \u0430\u0434\u043C\u0438\u043D\u0438\u0441\u0442\u0440\u0430\u0442\u043E\u0440\u0443." }), _jsx("button", { type: "button", "data-testid": "qa-surface-admin-return", onClick: () => window.history.pushState(null, "", config.route.path), children: "\u0412\u0435\u0440\u043D\u0443\u0442\u044C\u0441\u044F \u0432 \u0447\u0430\u0442" })] }) }));
654
897
  }
655
- return (_jsxs(_Fragment, { children: [welcomeNotice, settingsDialog === undefined ? null : (_jsx(QaUserSettingsDialog, { open: settingsOpen, initialSection: (config.accounts.profile.enabled
898
+ return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx(QaTurnNotice, { items: turnNotices.items, onOpen: turnNotices.onOpen, onDismiss: turnNotices.onDismiss, rootRef: noticeStack, onKeyDown: trapNoticeKeys, ...(turnNotices.onEnableDesktop === undefined
899
+ ? {}
900
+ : { onEnableDesktop: turnNotices.onEnableDesktop }) }), settingsDialog === undefined ? null : (_jsx(QaUserSettingsDialog, { open: settingsOpen, initialSection: (config.accounts.profile.enabled
656
901
  ? "profile"
657
902
  : "general"), email: accountsSnapshot.stage === "authed"
658
903
  ? accountsSnapshot.user.email
@@ -664,15 +909,15 @@ export function QaSurface(props) {
664
909
  ? {}
665
910
  : { password: settingsDialog.password }), ...(settingsDialog.starters === undefined
666
911
  ? {}
667
- : { starters: settingsDialog.starters }), ...(settingsDialog.integrationTokens === undefined
912
+ : { starters: settingsDialog.starters }), notifications: settingsDialog.notifications, ...(settingsDialog.integrationTokens === undefined
668
913
  ? {}
669
914
  : { integrationTokens: settingsDialog.integrationTokens }), ...(settingsDialog.skills === undefined
670
915
  ? {}
671
- : { skills: settingsDialog.skills }) })), _jsxs("main", { className: QA_SURFACE_CLASS, "data-phase": state.phase, "aria-label": config.branding.title, tabIndex: -1, onKeyDown: trapKeys, children: [previewing || sessionPreview ? (_jsx(QaAdminPreviewBanner, { role: preview?.name ??
916
+ : { skills: settingsDialog.skills }) })), _jsxs("main", { ref: surface, className: QA_SURFACE_CLASS, "data-testid": "qa-surface-root", "data-phase": state.phase, "aria-label": config.branding.title, tabIndex: -1, onKeyDown: trapSurfaceKeys, children: [previewing || sessionPreview ? (_jsx(QaAdminPreviewBanner, { role: preview?.name ??
672
917
  sessionRole?.name ??
673
918
  sessionRole?.id ??
674
919
  selectedSubrole ??
675
- "", ...(previewing ? { onExit: exitPreview } : {}) })) : null, showSidebar ? (_jsx(QaSidebar, { rows: chatRows, groupByOwner: ownerNames !== undefined, title: config.branding.title, logoUrl: config.branding.logoUrl, stateKey: stateKey, showNewChat: allowNewChat, busy: state.phase === "creating", onSwitch: handleSwitch, onNewChat: handleNewChat, onDelete: handleDelete, audits: chatAudits, ...(auditSnapshot.api === null ? {} : { onAudit: openAudit }), account: config.accounts.enabled &&
920
+ "", ...(previewing ? { onExit: exitPreview } : {}), disabled: state.phase === "creating" })) : null, showSidebar && historyOpen ? (_jsx("div", { className: "dsh-qa-sidebar__scrim", "data-testid": "qa-surface-sidebar-scrim", "aria-hidden": "true", onClick: closeHistory })) : null, showSidebar ? (_jsx(QaSidebar, { rows: chatRows, groupByOwner: ownerNames !== undefined, title: config.branding.title, logoUrl: config.branding.logoUrl, stateKey: stateKey, showNewChat: allowNewChat, busy: state.phase === "creating", drawerOpen: historyOpen, onDrawerClose: closeHistory, onSwitch: handleSwitch, onNewChat: handleNewChat, onDelete: handleDelete, audits: chatAudits, ...(auditSnapshot.api === null ? {} : { onAudit: openAudit }), account: config.accounts.enabled &&
676
921
  accounts !== undefined &&
677
922
  accountsStage === "authed"
678
923
  ? {
@@ -683,7 +928,7 @@ export function QaSurface(props) {
683
928
  ? {}
684
929
  : { settings: settingsEntry }),
685
930
  }
686
- : undefined })) : null, _jsx(QaAuditDialog, { open: auditTarget !== null, sessionId: auditTarget, title: "\u0410\u0443\u0434\u0438\u0442 \u0447\u0430\u0442\u0430", api: auditSnapshot.api, summary: auditTarget === null ? undefined : chatAudits.get(auditTarget), onClose: closeAudit }), _jsxs("div", { className: "dsh-qa-body", children: [state.viewingSubagent !== null && !config.ui.showHeader ? (_jsx(QaSubagentBanner, { onClose: handleCloseSubagent })) : null, config.ui.showHeader ? (_jsx(QaHeader, { logoUrl: config.branding.logoUrl, title: conversationTitle, viewingSubagent: state.viewingSubagent !== null, onCloseSubagent: handleCloseSubagent, roleSelector: access === undefined ||
931
+ : undefined })) : null, _jsx(QaAuditDialog, { open: auditTarget !== null, sessionId: auditTarget, title: "\u0410\u0443\u0434\u0438\u0442 \u0447\u0430\u0442\u0430", api: auditSnapshot.api, summary: auditTarget === null ? undefined : chatAudits.get(auditTarget), onClose: closeAudit }), _jsxs("div", { className: "dsh-qa-body", "data-testid": "qa-surface-body", children: [state.viewingSubagent !== null && !config.ui.showHeader ? (_jsx(QaSubagentBanner, { onClose: handleCloseSubagent })) : null, config.ui.showHeader ? (_jsx(QaHeader, { logoUrl: config.branding.logoUrl, title: conversationTitle, viewingSubagent: state.viewingSubagent !== null, onCloseSubagent: handleCloseSubagent, roleSelector: access === undefined ||
687
932
  selectedSubrole === null ||
688
933
  previewing ? undefined : (_jsx(QaRoleSelector, { roles: access.subroles, selected: selectedSubrole, conversationStarted: !empty, disabled: config.session.policy === "fixed" ||
689
934
  state.phase === "creating" ||
@@ -694,21 +939,26 @@ export function QaSurface(props) {
694
939
  setSelectedSubrole(id);
695
940
  setSessionRole(access.subroles.find((role) => role.id === id));
696
941
  void controller?.selectSubrole(id, false);
697
- } })), administration: accountsSnapshot.stage === "authed" &&
942
+ } })), themeSwitcher: _jsx(QaThemeSwitcher, { preference: theme.preference, onSelect: theme.select }), administration: accountsSnapshot.stage === "authed" &&
698
943
  accountsSnapshot.user.role === "admin"
699
944
  ? {
700
945
  onOpen: () => window.history.pushState(null, "", `${config.route.path === "/" ? "" : config.route.path}/admin`),
701
946
  }
702
947
  : undefined, agentCount: agentRows.length, agentsOpen: agentsOpen, onToggleAgents: rail.toggleAgents, sourcesVisible: config.sources.enabled && config.sources.display.sidebar, sourcesCount: state.sources.length, sourcesComplete: state.sourcesComplete, sourcesOpen: rail.railOpen && rail.railTab === "sources", onOpenSources: () => rail.openTab("sources"), fileCount: attachmentCount, filesEnabled: filesEnabled, filesOpen: rail.railOpen && rail.railTab === "files", onOpenFiles: () => rail.openTab("files"), panelLauncher: _jsx(QaPanelLauncher, { panels: props.panels }),
948
+ // The narrow layout switches the sidebar off, so the history is
949
+ // opened from here — and only where there is a sidebar to open.
950
+ history: showSidebar
951
+ ? { open: historyOpen, onToggle: toggleHistory }
952
+ : undefined,
703
953
  // The sidebar carries this entry next to the account name, so
704
954
  // the header takes it over exactly when there is no sidebar.
705
- settings: showSidebar ? undefined : settingsEntry, showReset: showResetButton, resetDisabled: controller === undefined || state.phase === "creating", onReset: handleNewChat })) : null, _jsxs("div", { className: "dsh-qa-workspace", children: [_jsxs("div", { ref: chat, className: "dsh-qa-chat", style: {
955
+ settings: showSidebar ? undefined : settingsEntry, showReset: showResetButton, resetDisabled: controller === undefined || state.phase === "creating", onReset: handleNewChat })) : null, _jsxs("div", { className: "dsh-qa-workspace", "data-testid": "qa-surface-workspace-split", children: [_jsxs("div", { ref: chat, className: "dsh-qa-chat", "data-testid": "qa-surface-chat", style: {
706
956
  "--dsh-qa-content-width": `${config.ui.minContentWidth}px`,
707
- }, children: [_jsxs("div", { ref: transcript, className: "dsh-qa-transcript", onScroll: (event) => {
957
+ }, children: [_jsxs("div", { ref: transcript, className: "dsh-qa-transcript", "data-testid": "qa-surface-transcript", onScroll: (event) => {
708
958
  const element = event.currentTarget;
709
959
  nearBottom.current = isNearBottom(element);
710
960
  scheduleActiveTurnSync();
711
- }, children: [empty ? null : (_jsx(QaTurnRail, { items: railItems, activeTurn: activeTurn, busyTurn: busyTurn, scrollerRef: transcript, onNavigate: handleTurnNavigate })), _jsxs("div", { className: "dsh-qa-transcript__inner", children: [empty ? (_jsxs("section", { className: "dsh-qa-welcome", "aria-labelledby": "dsh-qa-welcome-title", children: [_jsx("h2", { id: "dsh-qa-welcome-title", children: config.branding.welcomeMessage }), config.branding.subtitle === "" ? null : (_jsx("p", { children: config.branding.subtitle }))] })) : (visibleMessages.map((message, index) => {
961
+ }, children: [empty ? null : (_jsx(QaTurnRail, { items: railItems, activeTurn: activeTurn, busyTurn: busyTurn, scrollerRef: transcript, onNavigate: handleTurnNavigate })), _jsxs("div", { className: "dsh-qa-transcript__inner", children: [empty ? (_jsxs("section", { className: "dsh-qa-welcome", "data-testid": "qa-surface-transcript-welcome", "aria-labelledby": "dsh-qa-welcome-title", children: [_jsx("h2", { id: "dsh-qa-welcome-title", children: config.branding.welcomeMessage }), config.branding.subtitle === "" ? null : (_jsx("p", { children: config.branding.subtitle }))] })) : (visibleMessages.map((message, index) => {
712
962
  const group = message.role === "user"
713
963
  ? view.groupByPromptId.get(message.id)
714
964
  : undefined;
@@ -727,8 +977,8 @@ export function QaSurface(props) {
727
977
  ...offsets,
728
978
  [group.groupId]: step,
729
979
  })) })) : null] }, message.id));
730
- })), state.pendingMessage === null ? null : (_jsx("div", { className: "dsh-qa-message-slot", children: _jsx(QaMessage, { message: state.pendingMessage, renderMarkdown: false, showTimestamp: false, thinkingPhrases: config.thinkingPhrases }) })), state.compatibilityReadOnly === true ? (_jsx("div", { className: "dsh-qa-compatibility", role: "status", children: "\u042D\u0442\u043E\u0442 \u0447\u0430\u0442 \u0441\u043E\u0437\u0434\u0430\u043D \u043F\u0440\u0438 \u0434\u0440\u0443\u0433\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0435\u043D\u0434\u0430 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0447\u0442\u0435\u043D\u0438\u044F. \u0418\u0441\u0442\u043E\u0440\u0438\u044F \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0430; \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u043C\u0438, \u0441\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442." })) : null, state.error === null ? null : (_jsxs("div", { className: "dsh-qa-error", role: "alert", children: [_jsx("span", { children: state.error }), state.phase === "error" ? (_jsx("button", { type: "button", onClick: () => void controller?.ensureSession(), children: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C" })) : null] }))] })] }), _jsx("footer", { className: "dsh-qa-footer", children: _jsxs("div", { className: "dsh-qa-footer__inner", children: [_jsx(QaApproval, { approvals: state.approvals, onAnswer: handleAnswerApproval }), _jsx(QaQuestions, { questions: state.questions, onAnswer: handleAnswerQuestion, onCancel: handleCancelQuestion, onStop: handleStop, canStop: state.canStop }), _jsx("div", { className: "dsh-qa-composer-slot", hidden: state.questions.length > 0, children: _jsx(QaComposer, { placeholder: config.branding.placeholder, quickQuestions: empty ? quickQuestions : NO_QUESTIONS, canSend: state.canSend, canStop: state.canStop, running: state.phase === "running", showStop: config.ui.showStop, status: status, attachments: pendingAttachments, limits: limits, slash: state.slash, slashPolicy: config.slashCommands.palette, onAttachmentsChange: setPendingAttachments, onSend: handleSend, onSlashOpen: handleSlashOpen, onStop: handleStop }, state.sessionId ?? "draft") }), config.branding.disclaimer === "" ? null : (_jsxs("p", { className: "dsh-qa-footer__disclaimer", children: [_jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("circle", { cx: "8", cy: "8", r: "5.75" }), _jsx("path", { d: "M8 7.25v3.5m0-5.25v.5" })] }), config.branding.disclaimer] }))] }) }), !empty
980
+ })), state.pendingMessage === null ? null : (_jsx("div", { className: "dsh-qa-message-slot", children: _jsx(QaMessage, { message: state.pendingMessage, renderMarkdown: false, showTimestamp: false, thinkingPhrases: config.thinkingPhrases }) })), state.compatibilityReadOnly === true ? (_jsx("div", { className: "dsh-qa-compatibility", "data-testid": "qa-surface-compatibility", role: "status", children: "\u042D\u0442\u043E\u0442 \u0447\u0430\u0442 \u0441\u043E\u0437\u0434\u0430\u043D \u043F\u0440\u0438 \u0434\u0440\u0443\u0433\u043E\u0439 \u043A\u043E\u043D\u0444\u0438\u0433\u0443\u0440\u0430\u0446\u0438\u0438 \u0441\u0442\u0435\u043D\u0434\u0430 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u0434\u043B\u044F \u0447\u0442\u0435\u043D\u0438\u044F. \u0418\u0441\u0442\u043E\u0440\u0438\u044F \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0430; \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u0434\u043E\u043B\u0436\u0438\u0442\u044C \u0440\u0430\u0431\u043E\u0442\u0443 \u0441 \u0442\u0435\u043A\u0443\u0449\u0438\u043C\u0438 \u043D\u0430\u0441\u0442\u0440\u043E\u0439\u043A\u0430\u043C\u0438, \u0441\u043E\u0437\u0434\u0430\u0439\u0442\u0435 \u043D\u043E\u0432\u044B\u0439 \u0447\u0430\u0442." })) : null, state.error === null ? null : (_jsxs("div", { className: "dsh-qa-error", "data-testid": "qa-surface-error", role: "alert", children: [_jsx("span", { children: state.error }), state.phase === "error" ? (_jsx("button", { type: "button", "data-testid": "qa-surface-error-retry", onClick: () => void controller?.ensureSession(), children: "\u041F\u043E\u0432\u0442\u043E\u0440\u0438\u0442\u044C" })) : null] }))] })] }), _jsx("footer", { className: "dsh-qa-footer", "data-testid": "qa-surface-footer", children: _jsxs("div", { className: "dsh-qa-footer__inner", children: [_jsx(QaApproval, { approvals: state.approvals, onAnswer: handleAnswerApproval }), _jsx(QaQuestions, { questions: state.questions, onAnswer: handleAnswerQuestion, onCancel: handleCancelQuestion, onStop: handleStop, canStop: state.canStop }), _jsxs("div", { className: "dsh-qa-composer-slot", "data-testid": "qa-surface-composer-slot", hidden: state.questions.length > 0, children: [_jsx(QaQueueDock, { rows: state.queue, running: state.phase === "running", canEdit: state.canEditQueue, onEdit: handleQueueEdit, onSendNow: handleQueueSendNow, onRemove: handleQueueRemove }), _jsx(QaComposer, { placeholder: config.branding.placeholder, quickQuestions: empty ? quickQuestions : NO_QUESTIONS, canSend: state.canSend, canStop: state.canStop, running: state.phase === "running", showStop: config.ui.showStop, status: status, attachments: pendingAttachments, limits: limits, slash: state.slash, slashPolicy: config.slashCommands.palette, onAttachmentsChange: setPendingAttachments, onSend: handleSend, onSlashOpen: handleSlashOpen, onStop: handleStop }, state.chatKey)] }), config.branding.disclaimer === "" ? null : (_jsxs("p", { className: "dsh-qa-footer__disclaimer", children: [_jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("circle", { cx: "8", cy: "8", r: "5.75" }), _jsx("path", { d: "M8 7.25v3.5m0-5.25v.5" })] }), config.branding.disclaimer] }))] }) }), !empty
731
981
  ? ["left", "right"].map((side) => (_jsx(QaWidthHandle, { side: side, ...widthHandlers }, side)))
732
- : null] }), _jsx(QaPanelHost, { panels: props.panels, sessionId: state.sessionId, qaToken: accounts?.token() ?? "", renderSlot: props.renderSlot })] })] }), agentsOpen && agentRows.length > 0 ? (_jsx(QaAgentsDrawer, { agents: agentRows, activeId: state.viewingSubagent?.id ?? null, onView: (id, title) => void controller?.viewSubagent(id, title), onClose: () => setAgentsOpen(false) })) : null, rail.railOpen ? (_jsx(QaRightRail, { tabs: railTabs, activeTab: rail.railTab, onTabSelect: rail.selectTab, onClose: rail.close })) : null] })] }));
982
+ : null] }), _jsx(QaPanelHost, { panels: props.panels, sessionId: state.sessionId, qaToken: accounts?.token() ?? "", renderSlot: props.renderSlot })] })] }), agentsOpen && agentRows.length > 0 ? (_jsx(QaAgentsDrawer, { agents: agentRows, activeId: state.viewingSubagent?.id ?? null, onView: (id, title) => void controller?.viewSubagent(id, title), onClose: () => setAgentsOpen(false) })) : null, rail.railOpen ? (_jsx(QaRightRail, { tabs: railTabs, activeTab: rail.railTab, onTabSelect: rail.selectTab, onClose: rail.close })) : null, _jsx(QaRequestQueue, { status: state.requestQueue, onClose: dismissRequestQueue })] })] }));
733
983
  }
734
984
  //# sourceMappingURL=QaSurface.js.map