@yadsh/dsh-qa-surface 0.13.0 → 0.14.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 (336) 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 +279 -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 +64 -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 +66 -6
  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 +133 -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 +7228 -4002
  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/qa-tools/docs-tools.js +1 -1
  252. package/lib/qa-tools/docs-tools.js.map +1 -1
  253. package/lib/qa-tools/lifecycle.js +6 -3
  254. package/lib/qa-tools/lifecycle.js.map +1 -1
  255. package/lib/request-queue.js +32 -0
  256. package/lib/request-queue.js.map +1 -0
  257. package/lib/resolve-config.js +2 -0
  258. package/lib/resolve-config.js.map +1 -1
  259. package/lib/secure-session.js +7 -2
  260. package/lib/secure-session.js.map +1 -1
  261. package/lib/shared/settings.js +19 -2
  262. package/lib/shared/settings.js.map +1 -1
  263. package/lib/turn-failure-log.js +67 -0
  264. package/lib/turn-failure-log.js.map +1 -0
  265. package/lib/typert.host.js +1398 -651
  266. package/lib/typert.remote-client.d.ts +15 -1
  267. package/lib/typert.remote-client.js +1315 -645
  268. package/lib/types/access/capability-catalog.d.ts +20 -4
  269. package/lib/types/access/model.d.ts +23 -0
  270. package/lib/types/access/service.d.ts +2 -3
  271. package/lib/types/account-remotes.d.ts +8 -1
  272. package/lib/types/accounts/database.d.ts +13 -3
  273. package/lib/types/accounts/errors.d.ts +9 -1
  274. package/lib/types/accounts/file.d.ts +12 -0
  275. package/lib/types/accounts/store.d.ts +30 -2
  276. package/lib/types/admin/quality-store.d.ts +33 -0
  277. package/lib/types/admin/service.d.ts +60 -1
  278. package/lib/types/client/QaAccountsController.d.ts +25 -2
  279. package/lib/types/client/QaConfigController.d.ts +2 -2
  280. package/lib/types/client/QaSessionController.d.ts +199 -6
  281. package/lib/types/client/QaSurface.d.ts +6 -1
  282. package/lib/types/client/admin/pages/ExpertMemory.d.ts +19 -0
  283. package/lib/types/client/admin/routes.d.ts +3 -1
  284. package/lib/types/client/admin/shared.d.ts +4 -0
  285. package/lib/types/client/components/Markdown.d.ts +8 -1
  286. package/lib/types/client/components/QaHeader.d.ts +3 -1
  287. package/lib/types/client/components/QaModal.d.ts +7 -0
  288. package/lib/types/client/components/QaQueueDock.d.ts +27 -0
  289. package/lib/types/client/components/QaRequestQueue.d.ts +20 -0
  290. package/lib/types/client/components/QaThemeSwitcher.d.ts +14 -0
  291. package/lib/types/client/components/QaTurnNotice.d.ts +55 -0
  292. package/lib/types/client/focus-ring.d.ts +65 -0
  293. package/lib/types/client/lineage.d.ts +3 -13
  294. package/lib/types/client/markdown/blocks.d.ts +30 -2
  295. package/lib/types/client/markdown/render.d.ts +4 -1
  296. package/lib/types/client/notifications/notification-dispatcher.d.ts +68 -0
  297. package/lib/types/client/notifications/preferences.d.ts +44 -0
  298. package/lib/types/client/notifications/turn-completion-source.d.ts +114 -0
  299. package/lib/types/client/notifications/use-turn-notifications.d.ts +51 -0
  300. package/lib/types/client/project-session-state.d.ts +35 -1
  301. package/lib/types/client/role/RoleSelector.d.ts +7 -1
  302. package/lib/types/client/settings/card.d.ts +36 -5
  303. package/lib/types/client/settings/fields.d.ts +24 -0
  304. package/lib/types/client/settings/format.d.ts +0 -5
  305. package/lib/types/client/settings/sections/common.d.ts +3 -1
  306. package/lib/types/client/settings/sections/session.d.ts +2 -1
  307. package/lib/types/client/settings/styles.d.ts +9 -4
  308. package/lib/types/client/settings-extensions/user-session.d.ts +1 -1
  309. package/lib/types/client/theme-preference.d.ts +69 -0
  310. package/lib/types/client/types.d.ts +25 -1
  311. package/lib/types/client/use-session-ui-state.d.ts +6 -4
  312. package/lib/types/client/user-settings/GeneralSettingsPage.d.ts +5 -5
  313. package/lib/types/client/user-settings/NotificationSettingsPage.d.ts +40 -0
  314. package/lib/types/client/user-settings/UserSettingsDialog.d.ts +12 -2
  315. package/lib/types/client/user-settings/draft.d.ts +4 -1
  316. package/lib/types/client/user-settings/fields.d.ts +7 -0
  317. package/lib/types/config-resolvers/notifications.d.ts +10 -0
  318. package/lib/types/config-resolvers/session.d.ts +6 -0
  319. package/lib/types/config.d.ts +20 -1
  320. package/lib/types/index.d.ts +53 -5
  321. package/lib/types/integration/answer.d.ts +27 -0
  322. package/lib/types/integration/expert-memory.d.ts +49 -0
  323. package/lib/types/notifications.d.ts +32 -0
  324. package/lib/types/personal-skills/errors.d.ts +1 -1
  325. package/lib/types/personal-skills/service.d.ts +6 -0
  326. package/lib/types/prompt-notes.d.ts +17 -4
  327. package/lib/types/request-queue.d.ts +32 -0
  328. package/lib/types/shared/settings.d.ts +19 -2
  329. package/lib/types/turn-failure-log.d.ts +18 -0
  330. package/lib/types/types.d.ts +180 -4
  331. package/package.json +64 -63
  332. package/scripts/attach-workspace-sessions.mjs +24 -5
  333. package/scripts/repair-session-events.mjs +25 -5
  334. package/lib/client/markdown/MermaidBlock.js +0 -80
  335. package/lib/client/markdown/MermaidBlock.js.map +0 -1
  336. 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(() => {
@@ -372,6 +492,10 @@ export function QaSurface(props) {
372
492
  void controller?.deleteChat(sessionId);
373
493
  }, [controller]);
374
494
  const handleSend = useCallback((text, attachments, pick) => controller?.send(text, attachments, pick) ?? Promise.resolve(false), [controller]);
495
+ // Closing the queue notice only uncovers the composer: the question the stand
496
+ // had no room for is still the text in there, and asking it again is a
497
+ // keystroke the visitor takes when a place frees up.
498
+ const dismissRequestQueue = useCallback(() => controller?.dismissRequestQueueNotice(), [controller]);
375
499
  // Cheap refresh on every palette opening: the skill registry has no browser
376
500
  // change event, and the controller skips the round-trip while its answer is
377
501
  // still fresh.
@@ -379,6 +503,9 @@ export function QaSurface(props) {
379
503
  void controller?.refreshSlashCatalog();
380
504
  }, [controller]);
381
505
  const handleStop = useCallback(() => controller?.stop() ?? Promise.resolve(), [controller]);
506
+ const handleQueueEdit = useCallback((id, text) => controller?.queueAction(id, "edit", text) ?? Promise.resolve(null), [controller]);
507
+ const handleQueueSendNow = useCallback((id) => controller?.queueAction(id, "steer") ?? Promise.resolve(null), [controller]);
508
+ const handleQueueRemove = useCallback((id) => controller?.queueAction(id, "remove") ?? Promise.resolve(null), [controller]);
382
509
  const handleAnswerApproval = useCallback((requestId, decision) => controller?.answerApproval(requestId, decision) ?? Promise.resolve(), [controller]);
383
510
  const handleAnswerQuestion = useCallback((requestId, answers) => controller?.answerQuestion(requestId, answers) ?? Promise.resolve(), [controller]);
384
511
  const handleCancelQuestion = useCallback((requestId) => controller?.cancelQuestion(requestId) ?? Promise.resolve(), [controller]);
@@ -540,15 +667,122 @@ export function QaSurface(props) {
540
667
  onSave: (input) => accounts.updateStarters(input),
541
668
  }
542
669
  : undefined;
670
+ // One object serves both halves of the same decision: the form that edits
671
+ // it and the notices that obey it. A reader cannot be looking at one and
672
+ // waiting on the other, and a memo keeps the identity stable for the
673
+ // watcher that reads the channels out of it.
674
+ const notifications = {
675
+ notifications: accountsSnapshot.user.notifications,
676
+ switches: config.notifications,
677
+ onSave: (input) => accounts.updateNotifications(input),
678
+ };
543
679
  const skills = config.accounts.skills.enabled && boundSkillApi !== undefined
544
680
  ? boundSkillApi
545
681
  : undefined;
546
- return { profile, password, starters, skills, integrationTokens };
682
+ return {
683
+ profile,
684
+ password,
685
+ starters,
686
+ notifications,
687
+ skills,
688
+ integrationTokens,
689
+ };
547
690
  }, [accounts, accountsSnapshot, config, boundSkillApi, integrationTokens]);
548
691
  const busyTurn = state.phase === "running" ? (railItems.at(-1)?.turn ?? null) : null;
549
692
  // Projected even while the sidebar is hidden: the account settings report
550
693
  // the same chat count, and one projection cannot disagree with itself.
551
694
  const chatRows = useMemo(() => buildChatRows(controller?.chatIds() ?? [], listState.byId, activeSessionId, (id) => ownerNames?.get(id)), [controller, listState, activeSessionId, ownerNames, state.chatsRevision]);
695
+ // A turn that ends in one of this browser's own chats gets a notice: the
696
+ // sidebar dot going out is otherwise the only sign, and it is easy to miss
697
+ // from behind another window. The account's own list, not the sidebar's, is
698
+ // what bounds it: an admin's shared history is a wider read than ownership,
699
+ // and another account's turn is not this reader's news.
700
+ const turnNotices = useQaTurnNotifications({
701
+ chats: chatRows,
702
+ ownChatIds: accountsSnapshot.stage === "authed" ? accountsSnapshot.ownIds : undefined,
703
+ notifications: config.notifications,
704
+ storage: window.localStorage,
705
+ storageKey: `${stateKey}:notifications`,
706
+ // Signed in, the account's own channels decide and the settings form is
707
+ // what writes them; anonymously, the hook falls back to this browser.
708
+ account: settingsDialog?.notifications,
709
+ paused: state.phase === "reconnecting",
710
+ activeSessionId,
711
+ onSwitch: handleSwitch,
712
+ });
713
+ /**
714
+ * A line leaving the stack takes the control the reader was working out of the
715
+ * page, and a page with nothing focused answers Tab with the browser's own
716
+ * order — which leaves the QA interface. Most of the ways a line goes leave
717
+ * the stack standing and its count of lines where it was: waving one of the
718
+ * three off, a fourth turn pushing the oldest out of a full stack, the opt-in
719
+ * going away once the browser has answered the permission question. So the
720
+ * loss is read where it happens — the control the ring held is out of the page
721
+ * and nothing of the ring holds the focus — and the reader is handed to the
722
+ * line that took the lost one's place, on the same control of it: a cross
723
+ * given back as a cross. The anchor goes only with the focus it hands back: a
724
+ * page takes the focus out of a removed control on its own moment, not in step
725
+ * with this render. A dialog that holds the keyboard is not the ring's
726
+ * business, and is left to put the focus wherever it thinks the reader
727
+ * belongs.
728
+ *
729
+ * A page the reader is not looking at keeps its keyboard where it is: while
730
+ * `document.hasFocus()` is false nothing here is moved. The hand-off is owed
731
+ * rather than skipped — the anchor stands until the window comes back, and the
732
+ * `focus` event of this page is what pays it — because the reader who returns
733
+ * to a stack that dropped a line under them is exactly the reader this place
734
+ * was remembered for, while a control of a page no one is looking at has no
735
+ * claim on the keyboard of the page they are working in.
736
+ *
737
+ * Opening a chat from a line is the one path where a line goes and the chat
738
+ * changes in the same moment. The keyboard stays on the stack — on a
739
+ * neighbouring line, or back inside `<main>` once none is left — rather than
740
+ * following the switch: the composer the switch remounts is disabled while the
741
+ * chat is still being bound, so it can take no focus, and the surface moves
742
+ * the keyboard on a switch no further than it does anywhere else.
743
+ */
744
+ const restoreRingFocus = useCallback(() => {
745
+ const anchor = ringAnchor.current;
746
+ if (anchor === null || anchor.element.isConnected)
747
+ return;
748
+ if (dialogOwnsKeyboard()) {
749
+ ringAnchor.current = null;
750
+ return;
751
+ }
752
+ if (!document.hasFocus())
753
+ return;
754
+ const ring = focusRing(ringRoots());
755
+ const active = document.activeElement;
756
+ if (active !== null && ring.includes(active))
757
+ return;
758
+ ringAnchor.current = null;
759
+ // The line itself first, for a control that left while its line stayed:
760
+ // then whichever neighbour is still on screen, the one after the reader
761
+ // taking the place the stack still lists in that order.
762
+ const candidates = [];
763
+ for (const line of [anchor.line, anchor.after, anchor.before]) {
764
+ if (line === null || !line.isConnected)
765
+ continue;
766
+ const controls = focusable(line);
767
+ candidates.push(controls[Math.min(anchor.step, controls.length - 1)]);
768
+ }
769
+ // The stack has no line left to stand on: back into the interface, onto the
770
+ // control the ring reaches last, and past the first candidate of it that
771
+ // refuses the keyboard.
772
+ candidates.push(...[...ring].reverse(), surface.current ?? undefined);
773
+ focusFirst(candidates);
774
+ }, [dialogOwnsKeyboard, ringRoots]);
775
+ useEffect(() => {
776
+ restoreRingFocus();
777
+ });
778
+ useEffect(() => {
779
+ // The hand-off a blurred page owed, read again at the moment the reader
780
+ // comes back: whoever holds the keyboard then keeps it, and a control of the
781
+ // ring they reached on their own is not taken away from them.
782
+ const onWindowFocus = () => restoreRingFocus();
783
+ window.addEventListener("focus", onWindowFocus);
784
+ return () => window.removeEventListener("focus", onWindowFocus);
785
+ }, [restoreRingFocus]);
552
786
  // The audit provider is optional: `auditSnapshot.api` is null until the
553
787
  // audit plugin's client bundle is loaded, and the badge is absent until
554
788
  // then. Chat ids rather than rows feed the poll, so a re-render does not
@@ -594,7 +828,7 @@ export function QaSurface(props) {
594
828
  const welcomeNotice = (_jsx(QaWelcomeNotice, { storage: window.localStorage, storageKey: `${stateKey}:welcome-notice` }, stateKey));
595
829
  if (config.accounts.enabled && accounts !== undefined) {
596
830
  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 })] }));
831
+ 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
832
  }
599
833
  if (accountsStage === "gate") {
600
834
  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 +884,11 @@ export function QaSurface(props) {
650
884
  } }));
651
885
  }
652
886
  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" })] }) }));
887
+ 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
888
  }
655
- return (_jsxs(_Fragment, { children: [welcomeNotice, settingsDialog === undefined ? null : (_jsx(QaUserSettingsDialog, { open: settingsOpen, initialSection: (config.accounts.profile.enabled
889
+ return (_jsxs(_Fragment, { children: [welcomeNotice, _jsx(QaTurnNotice, { items: turnNotices.items, onOpen: turnNotices.onOpen, onDismiss: turnNotices.onDismiss, rootRef: noticeStack, onKeyDown: trapNoticeKeys, ...(turnNotices.onEnableDesktop === undefined
890
+ ? {}
891
+ : { onEnableDesktop: turnNotices.onEnableDesktop }) }), settingsDialog === undefined ? null : (_jsx(QaUserSettingsDialog, { open: settingsOpen, initialSection: (config.accounts.profile.enabled
656
892
  ? "profile"
657
893
  : "general"), email: accountsSnapshot.stage === "authed"
658
894
  ? accountsSnapshot.user.email
@@ -664,15 +900,15 @@ export function QaSurface(props) {
664
900
  ? {}
665
901
  : { password: settingsDialog.password }), ...(settingsDialog.starters === undefined
666
902
  ? {}
667
- : { starters: settingsDialog.starters }), ...(settingsDialog.integrationTokens === undefined
903
+ : { starters: settingsDialog.starters }), notifications: settingsDialog.notifications, ...(settingsDialog.integrationTokens === undefined
668
904
  ? {}
669
905
  : { integrationTokens: settingsDialog.integrationTokens }), ...(settingsDialog.skills === undefined
670
906
  ? {}
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 ??
907
+ : { 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
908
  sessionRole?.name ??
673
909
  sessionRole?.id ??
674
910
  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 &&
911
+ "", ...(previewing ? { onExit: exitPreview } : {}), disabled: state.phase === "creating" })) : 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 &&
676
912
  accounts !== undefined &&
677
913
  accountsStage === "authed"
678
914
  ? {
@@ -683,7 +919,7 @@ export function QaSurface(props) {
683
919
  ? {}
684
920
  : { settings: settingsEntry }),
685
921
  }
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 ||
922
+ : 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
923
  selectedSubrole === null ||
688
924
  previewing ? undefined : (_jsx(QaRoleSelector, { roles: access.subroles, selected: selectedSubrole, conversationStarted: !empty, disabled: config.session.policy === "fixed" ||
689
925
  state.phase === "creating" ||
@@ -694,7 +930,7 @@ export function QaSurface(props) {
694
930
  setSelectedSubrole(id);
695
931
  setSessionRole(access.subroles.find((role) => role.id === id));
696
932
  void controller?.selectSubrole(id, false);
697
- } })), administration: accountsSnapshot.stage === "authed" &&
933
+ } })), themeSwitcher: _jsx(QaThemeSwitcher, { preference: theme.preference, onSelect: theme.select }), administration: accountsSnapshot.stage === "authed" &&
698
934
  accountsSnapshot.user.role === "admin"
699
935
  ? {
700
936
  onOpen: () => window.history.pushState(null, "", `${config.route.path === "/" ? "" : config.route.path}/admin`),
@@ -702,13 +938,13 @@ export function QaSurface(props) {
702
938
  : 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 }),
703
939
  // The sidebar carries this entry next to the account name, so
704
940
  // 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: {
941
+ 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
942
  "--dsh-qa-content-width": `${config.ui.minContentWidth}px`,
707
- }, children: [_jsxs("div", { ref: transcript, className: "dsh-qa-transcript", onScroll: (event) => {
943
+ }, children: [_jsxs("div", { ref: transcript, className: "dsh-qa-transcript", "data-testid": "qa-surface-transcript", onScroll: (event) => {
708
944
  const element = event.currentTarget;
709
945
  nearBottom.current = isNearBottom(element);
710
946
  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) => {
947
+ }, 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
948
  const group = message.role === "user"
713
949
  ? view.groupByPromptId.get(message.id)
714
950
  : undefined;
@@ -727,8 +963,8 @@ export function QaSurface(props) {
727
963
  ...offsets,
728
964
  [group.groupId]: step,
729
965
  })) })) : 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
966
+ })), 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
967
  ? ["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] })] }));
968
+ : 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
969
  }
734
970
  //# sourceMappingURL=QaSurface.js.map