@hotmeshio/long-tail 0.5.12 → 0.6.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 (303) hide show
  1. package/README.md +1 -0
  2. package/build/api/escalations/bulk.js +9 -1
  3. package/build/api/escalations/cancel.js +5 -7
  4. package/build/api/escalations/claim.js +18 -15
  5. package/build/api/escalations/create.d.ts +6 -0
  6. package/build/api/escalations/create.js +11 -8
  7. package/build/api/escalations/facets.d.ts +18 -0
  8. package/build/api/escalations/facets.js +115 -0
  9. package/build/api/escalations/helpers.d.ts +70 -1
  10. package/build/api/escalations/helpers.js +101 -7
  11. package/build/api/escalations/index.d.ts +3 -2
  12. package/build/api/escalations/index.js +7 -1
  13. package/build/api/escalations/list.d.ts +21 -2
  14. package/build/api/escalations/list.js +109 -10
  15. package/build/api/escalations/metadata.js +22 -27
  16. package/build/api/escalations/resolve.d.ts +19 -1
  17. package/build/api/escalations/resolve.js +111 -26
  18. package/build/api/escalations/single.js +8 -8
  19. package/build/api/users.d.ts +6 -0
  20. package/build/api/users.js +29 -1
  21. package/build/bin/ltc.js +40 -0
  22. package/build/lib/cli/commands/escalations.d.ts +36 -0
  23. package/build/lib/cli/commands/escalations.js +98 -0
  24. package/build/lib/db/schemas/001_schema.sql +10 -5
  25. package/build/lib/db/schemas/012_lt_tasks_workflow_id_unique.sql +20 -0
  26. package/build/lib/db/schemas/013_role_scope.sql +37 -0
  27. package/build/modules/version.d.ts +10 -5
  28. package/build/modules/version.js +27 -11
  29. package/build/routes/escalations/facets.d.ts +7 -0
  30. package/build/routes/escalations/facets.js +81 -0
  31. package/build/routes/escalations/index.js +3 -0
  32. package/build/routes/escalations/list.js +46 -0
  33. package/build/routes/escalations/resolve.js +22 -4
  34. package/build/routes/users.js +9 -5
  35. package/build/sdk/index.d.ts +33 -2
  36. package/build/sdk/index.js +7 -0
  37. package/build/services/escalation/crud.d.ts +56 -6
  38. package/build/services/escalation/crud.js +102 -31
  39. package/build/services/escalation/facet-sql.d.ts +18 -0
  40. package/build/services/escalation/facet-sql.js +118 -0
  41. package/build/services/escalation/facets.d.ts +45 -0
  42. package/build/services/escalation/facets.js +218 -0
  43. package/build/services/escalation/index.d.ts +1 -0
  44. package/build/services/escalation/index.js +8 -1
  45. package/build/services/escalation/queries.d.ts +45 -0
  46. package/build/services/escalation/queries.js +107 -6
  47. package/build/services/escalation/sql.d.ts +10 -4
  48. package/build/services/escalation/sql.js +36 -8
  49. package/build/services/interceptor/activities/escalation.d.ts +2 -0
  50. package/build/services/interceptor/activities/escalation.js +1 -1
  51. package/build/services/interceptor/lifecycle.js +4 -0
  52. package/build/services/mcp/server-tools.js +10 -4
  53. package/build/services/orchestrator/condition.js +6 -2
  54. package/build/services/role/index.js +2 -3
  55. package/build/services/role/sql.d.ts +8 -0
  56. package/build/services/role/sql.js +17 -1
  57. package/build/services/task/crud.js +22 -14
  58. package/build/services/task/process.js +3 -21
  59. package/build/services/task/sql.d.ts +24 -1
  60. package/build/services/task/sql.js +65 -1
  61. package/build/services/user/crud.d.ts +1 -0
  62. package/build/services/user/crud.js +29 -11
  63. package/build/services/user/index.d.ts +2 -1
  64. package/build/services/user/index.js +12 -1
  65. package/build/services/user/rbac.d.ts +18 -0
  66. package/build/services/user/rbac.js +27 -0
  67. package/build/services/user/roles.d.ts +7 -2
  68. package/build/services/user/roles.js +21 -5
  69. package/build/services/user/scope.d.ts +22 -0
  70. package/build/services/user/scope.js +50 -0
  71. package/build/services/user/seed-admin.js +34 -17
  72. package/build/services/user/sql.d.ts +18 -7
  73. package/build/services/user/sql.js +38 -13
  74. package/build/services/user/sso-provision.js +41 -24
  75. package/build/services/user/types.d.ts +9 -5
  76. package/build/system/mcp-servers/admin/escalations.js +65 -0
  77. package/build/system/mcp-servers/admin/schemas.d.ts +325 -0
  78. package/build/system/mcp-servers/admin/schemas.js +63 -2
  79. package/build/system/mcp-servers/admin/users.js +28 -2
  80. package/build/tsconfig.tsbuildinfo +1 -1
  81. package/build/types/envelope.d.ts +7 -0
  82. package/build/types/facets.d.ts +60 -0
  83. package/build/types/facets.js +10 -0
  84. package/build/types/index.d.ts +2 -1
  85. package/build/types/user.d.ts +11 -0
  86. package/dashboard/dist/assets/AdminDashboard-2CZ8py9X.js +2 -0
  87. package/dashboard/dist/assets/{AdminDashboard-LTiYLuzq.js.map → AdminDashboard-2CZ8py9X.js.map} +1 -1
  88. package/dashboard/dist/assets/{AgentConfigPage-nlOudfpZ.js → AgentConfigPage-D68sodgo.js} +2 -2
  89. package/dashboard/dist/assets/{AgentConfigPage-nlOudfpZ.js.map → AgentConfigPage-D68sodgo.js.map} +1 -1
  90. package/dashboard/dist/assets/{AgentDetailPage-BSUnDvGp.js → AgentDetailPage-BmhuWbKi.js} +2 -2
  91. package/dashboard/dist/assets/{AgentDetailPage-BSUnDvGp.js.map → AgentDetailPage-BmhuWbKi.js.map} +1 -1
  92. package/dashboard/dist/assets/{AgentsPage-DpS0cCYe.js → AgentsPage-C4SKQVeU.js} +2 -2
  93. package/dashboard/dist/assets/{AgentsPage-DpS0cCYe.js.map → AgentsPage-C4SKQVeU.js.map} +1 -1
  94. package/dashboard/dist/assets/AvailableEscalationsPage-9RBEYwkt.js +2 -0
  95. package/dashboard/dist/assets/AvailableEscalationsPage-9RBEYwkt.js.map +1 -0
  96. package/dashboard/dist/assets/{BotPicker-BhKmq7M7.js → BotPicker-D-FvXJOM.js} +2 -2
  97. package/dashboard/dist/assets/{BotPicker-BhKmq7M7.js.map → BotPicker-D-FvXJOM.js.map} +1 -1
  98. package/dashboard/dist/assets/{CapabilitiesPage-DLIleX2L.js → CapabilitiesPage-ByDf6WJr.js} +2 -2
  99. package/dashboard/dist/assets/{CapabilitiesPage-DLIleX2L.js.map → CapabilitiesPage-ByDf6WJr.js.map} +1 -1
  100. package/dashboard/dist/assets/{CollapsibleSection-D7SVwNVk.js → CollapsibleSection-BjGNYEvh.js} +2 -2
  101. package/dashboard/dist/assets/{CollapsibleSection-D7SVwNVk.js.map → CollapsibleSection-BjGNYEvh.js.map} +1 -1
  102. package/dashboard/dist/assets/CopyableId-blg-gZvT.js +2 -0
  103. package/dashboard/dist/assets/CopyableId-blg-gZvT.js.map +1 -0
  104. package/dashboard/dist/assets/{CredentialsPage-fat5Z2U9.js → CredentialsPage-DvCA-wQN.js} +2 -2
  105. package/dashboard/dist/assets/{CredentialsPage-fat5Z2U9.js.map → CredentialsPage-DvCA-wQN.js.map} +1 -1
  106. package/dashboard/dist/assets/{CronLabel-OOP4P2mJ.js → CronLabel-DBMurd_c.js} +2 -2
  107. package/dashboard/dist/assets/{CronLabel-OOP4P2mJ.js.map → CronLabel-DBMurd_c.js.map} +1 -1
  108. package/dashboard/dist/assets/{CustomDurationPicker-COacSAXO.js → CustomDurationPicker-DHflFoEa.js} +2 -2
  109. package/dashboard/dist/assets/{CustomDurationPicker-COacSAXO.js.map → CustomDurationPicker-DHflFoEa.js.map} +1 -1
  110. package/dashboard/dist/assets/{DropZone-Bfw2YLVS.js → DropZone-CUtdp99Z.js} +2 -2
  111. package/dashboard/dist/assets/{DropZone-Bfw2YLVS.js.map → DropZone-CUtdp99Z.js.map} +1 -1
  112. package/dashboard/dist/assets/{ElapsedCell-CkfCD-m7.js → ElapsedCell-CSjcVOpo.js} +2 -2
  113. package/dashboard/dist/assets/{ElapsedCell-CkfCD-m7.js.map → ElapsedCell-CSjcVOpo.js.map} +1 -1
  114. package/dashboard/dist/assets/{EscalationsOverview-BmI8jxlh.js → EscalationsOverview-BUqRNJbK.js} +2 -2
  115. package/dashboard/dist/assets/{EscalationsOverview-BmI8jxlh.js.map → EscalationsOverview-BUqRNJbK.js.map} +1 -1
  116. package/dashboard/dist/assets/{EventTable-CeKfFk3m.js → EventTable-Chz-6XPE.js} +2 -2
  117. package/dashboard/dist/assets/{EventTable-CeKfFk3m.js.map → EventTable-Chz-6XPE.js.map} +1 -1
  118. package/dashboard/dist/assets/{GraphInvokePage-BZBPBCSq.js → GraphInvokePage-BXdgciWg.js} +2 -2
  119. package/dashboard/dist/assets/{GraphInvokePage-BZBPBCSq.js.map → GraphInvokePage-BXdgciWg.js.map} +1 -1
  120. package/dashboard/dist/assets/{HomePage-dM7U2tl6.js → HomePage-CEMxnk0r.js} +2 -2
  121. package/dashboard/dist/assets/{HomePage-dM7U2tl6.js.map → HomePage-CEMxnk0r.js.map} +1 -1
  122. package/dashboard/dist/assets/{ListToolbar-BJaCzAXS.js → ListToolbar-_73JCdbB.js} +2 -2
  123. package/dashboard/dist/assets/{ListToolbar-BJaCzAXS.js.map → ListToolbar-_73JCdbB.js.map} +1 -1
  124. package/dashboard/dist/assets/{McpOverview-CvnDG5OB.js → McpOverview-DfdqxhSz.js} +2 -2
  125. package/dashboard/dist/assets/{McpOverview-CvnDG5OB.js.map → McpOverview-DfdqxhSz.js.map} +1 -1
  126. package/dashboard/dist/assets/{McpQueryDetailPage-DwmSIcIL.js → McpQueryDetailPage-DPAljieY.js} +2 -2
  127. package/dashboard/dist/assets/{McpQueryDetailPage-DwmSIcIL.js.map → McpQueryDetailPage-DPAljieY.js.map} +1 -1
  128. package/dashboard/dist/assets/{McpQueryPage-CU6FMEEk.js → McpQueryPage-D5cSziYS.js} +2 -2
  129. package/dashboard/dist/assets/{McpQueryPage-CU6FMEEk.js.map → McpQueryPage-D5cSziYS.js.map} +1 -1
  130. package/dashboard/dist/assets/{McpRunDetailPage-8a6lsehv.js → McpRunDetailPage-DwNb9wZg.js} +2 -2
  131. package/dashboard/dist/assets/{McpRunDetailPage-8a6lsehv.js.map → McpRunDetailPage-DwNb9wZg.js.map} +1 -1
  132. package/dashboard/dist/assets/{McpRunsPage-CPMYWgm_.js → McpRunsPage-O5H6yStl.js} +2 -2
  133. package/dashboard/dist/assets/{McpRunsPage-CPMYWgm_.js.map → McpRunsPage-O5H6yStl.js.map} +1 -1
  134. package/dashboard/dist/assets/{NamespacePill-D6btr6e8.js → NamespacePill-uW-xOedj.js} +2 -2
  135. package/dashboard/dist/assets/{NamespacePill-D6btr6e8.js.map → NamespacePill-uW-xOedj.js.map} +1 -1
  136. package/dashboard/dist/assets/OperatorDashboard-DYw9Ekv5.js +2 -0
  137. package/dashboard/dist/assets/{OperatorDashboard-g9o7I925.js.map → OperatorDashboard-DYw9Ekv5.js.map} +1 -1
  138. package/dashboard/dist/assets/PageHeader-DVOZjBfL.js +2 -0
  139. package/dashboard/dist/assets/PageHeader-DVOZjBfL.js.map +1 -0
  140. package/dashboard/dist/assets/{PageHeaderWithStats-DJQCZ0FR.js → PageHeaderWithStats-DtTpDHOd.js} +2 -2
  141. package/dashboard/dist/assets/{PageHeaderWithStats-DJQCZ0FR.js.map → PageHeaderWithStats-DtTpDHOd.js.map} +1 -1
  142. package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js +2 -0
  143. package/dashboard/dist/assets/PriorityBadge-B4ykBH7f.js.map +1 -0
  144. package/dashboard/dist/assets/{ProcessDetailPage-ByyWCs2A.js → ProcessDetailPage-B0Dokcx9.js} +2 -2
  145. package/dashboard/dist/assets/{ProcessDetailPage-ByyWCs2A.js.map → ProcessDetailPage-B0Dokcx9.js.map} +1 -1
  146. package/dashboard/dist/assets/{ProcessesListPage-BTsXds4l.js → ProcessesListPage-Cc87WIjL.js} +2 -2
  147. package/dashboard/dist/assets/{ProcessesListPage-BTsXds4l.js.map → ProcessesListPage-Cc87WIjL.js.map} +1 -1
  148. package/dashboard/dist/assets/RolePill-BaW-30TC.js +2 -0
  149. package/dashboard/dist/assets/RolePill-BaW-30TC.js.map +1 -0
  150. package/dashboard/dist/assets/{RolesPage-B2Kdf-L9.js → RolesPage-cp6PiF08.js} +2 -2
  151. package/dashboard/dist/assets/{RolesPage-B2Kdf-L9.js.map → RolesPage-cp6PiF08.js.map} +1 -1
  152. package/dashboard/dist/assets/RowActions-BA9L9djf.js +2 -0
  153. package/dashboard/dist/assets/RowActions-BA9L9djf.js.map +1 -0
  154. package/dashboard/dist/assets/{RunAsSelector-BzkZXpSX.js → RunAsSelector-2LWkjSnO.js} +2 -2
  155. package/dashboard/dist/assets/{RunAsSelector-BzkZXpSX.js.map → RunAsSelector-2LWkjSnO.js.map} +1 -1
  156. package/dashboard/dist/assets/{StickyPagination-BWhFSr2d.js → StickyPagination-BQEm--kI.js} +2 -2
  157. package/dashboard/dist/assets/{StickyPagination-BWhFSr2d.js.map → StickyPagination-BQEm--kI.js.map} +1 -1
  158. package/dashboard/dist/assets/{StreamMessageDetail-B6ch-6SY.js → StreamMessageDetail-BTtw3GjX.js} +2 -2
  159. package/dashboard/dist/assets/{StreamMessageDetail-B6ch-6SY.js.map → StreamMessageDetail-BTtw3GjX.js.map} +1 -1
  160. package/dashboard/dist/assets/{SwimlaneTimeline-CjvfUkls.js → SwimlaneTimeline-DdXxP4fO.js} +2 -2
  161. package/dashboard/dist/assets/{SwimlaneTimeline-CjvfUkls.js.map → SwimlaneTimeline-DdXxP4fO.js.map} +1 -1
  162. package/dashboard/dist/assets/{TagInput-CZSeyesi.js → TagInput-x9XTuqMb.js} +2 -2
  163. package/dashboard/dist/assets/{TagInput-CZSeyesi.js.map → TagInput-x9XTuqMb.js.map} +1 -1
  164. package/dashboard/dist/assets/{TaskDetailPage-BjHU6opi.js → TaskDetailPage-B8veYG4X.js} +2 -2
  165. package/dashboard/dist/assets/{TaskDetailPage-BjHU6opi.js.map → TaskDetailPage-B8veYG4X.js.map} +1 -1
  166. package/dashboard/dist/assets/{TaskQueuePill-CQrXrSxk.js → TaskQueuePill-DSEQZ-HT.js} +2 -2
  167. package/dashboard/dist/assets/{TaskQueuePill-CQrXrSxk.js.map → TaskQueuePill-DSEQZ-HT.js.map} +1 -1
  168. package/dashboard/dist/assets/{TasksListPage-Bem44zd0.js → TasksListPage-CsN1gZj5.js} +2 -2
  169. package/dashboard/dist/assets/{TasksListPage-Bem44zd0.js.map → TasksListPage-CsN1gZj5.js.map} +1 -1
  170. package/dashboard/dist/assets/{TimeAgo-CUIYqSt_.js → TimeAgo-n182EcLx.js} +2 -2
  171. package/dashboard/dist/assets/{TimeAgo-CUIYqSt_.js.map → TimeAgo-n182EcLx.js.map} +1 -1
  172. package/dashboard/dist/assets/{TimestampCell--tnreI1i.js → TimestampCell-2dlcQAfX.js} +2 -2
  173. package/dashboard/dist/assets/{TimestampCell--tnreI1i.js.map → TimestampCell-2dlcQAfX.js.map} +1 -1
  174. package/dashboard/dist/assets/{ToolPill-O09jsTQM.js → ToolPill-B_e4eT77.js} +2 -2
  175. package/dashboard/dist/assets/{ToolPill-O09jsTQM.js.map → ToolPill-B_e4eT77.js.map} +1 -1
  176. package/dashboard/dist/assets/{ToolTestPanel-OD99H7Pk.js → ToolTestPanel-Bhnncc6M.js} +2 -2
  177. package/dashboard/dist/assets/{ToolTestPanel-OD99H7Pk.js.map → ToolTestPanel-Bhnncc6M.js.map} +1 -1
  178. package/dashboard/dist/assets/{TopicDetailPage-rh_dsTJI.js → TopicDetailPage-29lsNsqI.js} +2 -2
  179. package/dashboard/dist/assets/{TopicDetailPage-rh_dsTJI.js.map → TopicDetailPage-29lsNsqI.js.map} +1 -1
  180. package/dashboard/dist/assets/{TopicsPage-CD_40_v-.js → TopicsPage-KJ7_LAvo.js} +2 -2
  181. package/dashboard/dist/assets/{TopicsPage-CD_40_v-.js.map → TopicsPage-KJ7_LAvo.js.map} +1 -1
  182. package/dashboard/dist/assets/{UserName-C__c8BN0.js → UserName-CMoFi9zb.js} +2 -2
  183. package/dashboard/dist/assets/{UserName-C__c8BN0.js.map → UserName-CMoFi9zb.js.map} +1 -1
  184. package/dashboard/dist/assets/{WorkflowExecutionPage-Bw6QdtUO.js → WorkflowExecutionPage-Cymfx-Bp.js} +2 -2
  185. package/dashboard/dist/assets/{WorkflowExecutionPage-Bw6QdtUO.js.map → WorkflowExecutionPage-Cymfx-Bp.js.map} +1 -1
  186. package/dashboard/dist/assets/{WorkflowPill-CS-IsR0U.js → WorkflowPill-B66_AnaY.js} +2 -2
  187. package/dashboard/dist/assets/{WorkflowPill-CS-IsR0U.js.map → WorkflowPill-B66_AnaY.js.map} +1 -1
  188. package/dashboard/dist/assets/{WorkflowsDashboard-BgI-iOP6.js → WorkflowsDashboard-CqLN8spd.js} +2 -2
  189. package/dashboard/dist/assets/{WorkflowsDashboard-BgI-iOP6.js.map → WorkflowsDashboard-CqLN8spd.js.map} +1 -1
  190. package/dashboard/dist/assets/{WorkflowsOverview-BRc0QbA7.js → WorkflowsOverview-0Rc2WT6q.js} +2 -2
  191. package/dashboard/dist/assets/{WorkflowsOverview-BRc0QbA7.js.map → WorkflowsOverview-0Rc2WT6q.js.map} +1 -1
  192. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DdzspNQR.js → YamlWorkflowDetailPage-CARQ7_LN.js} +2 -2
  193. package/dashboard/dist/assets/{YamlWorkflowDetailPage-DdzspNQR.js.map → YamlWorkflowDetailPage-CARQ7_LN.js.map} +1 -1
  194. package/dashboard/dist/assets/{YamlWorkflowsPage-Djw5ZbgQ.js → YamlWorkflowsPage-B-7sz1nf.js} +2 -2
  195. package/dashboard/dist/assets/{YamlWorkflowsPage-Djw5ZbgQ.js.map → YamlWorkflowsPage-B-7sz1nf.js.map} +1 -1
  196. package/dashboard/dist/assets/{agents-xstNkF6c.js → agents-zM43d5Yf.js} +2 -2
  197. package/dashboard/dist/assets/{agents-xstNkF6c.js.map → agents-zM43d5Yf.js.map} +1 -1
  198. package/dashboard/dist/assets/{bots-BztAI0s_.js → bots-D5BasOGz.js} +2 -2
  199. package/dashboard/dist/assets/{bots-BztAI0s_.js.map → bots-D5BasOGz.js.map} +1 -1
  200. package/dashboard/dist/assets/{capabilities-CEDwtxNL.js → capabilities-BT3PW9CZ.js} +2 -2
  201. package/dashboard/dist/assets/{capabilities-CEDwtxNL.js.map → capabilities-BT3PW9CZ.js.map} +1 -1
  202. package/dashboard/dist/assets/{controlplane-BlGEsRKe.js → controlplane-NpJr49rN.js} +2 -2
  203. package/dashboard/dist/assets/{controlplane-BlGEsRKe.js.map → controlplane-NpJr49rN.js.map} +1 -1
  204. package/dashboard/dist/assets/{escalation-iI4QKlP-.js → escalation-DrzQolPG.js} +2 -2
  205. package/dashboard/dist/assets/{escalation-iI4QKlP-.js.map → escalation-DrzQolPG.js.map} +1 -1
  206. package/dashboard/dist/assets/escalation-columns-QzwmtD60.js +2 -0
  207. package/dashboard/dist/assets/escalation-columns-QzwmtD60.js.map +1 -0
  208. package/dashboard/dist/assets/index-B5w4Gjyk.js +6 -0
  209. package/dashboard/dist/assets/index-B5w4Gjyk.js.map +1 -0
  210. package/dashboard/dist/assets/{index-CMviPjXQ.js → index-BCT4kqGR.js} +2 -2
  211. package/dashboard/dist/assets/{index-CMviPjXQ.js.map → index-BCT4kqGR.js.map} +1 -1
  212. package/dashboard/dist/assets/{index-B7VC0vjf.js → index-C69XvBfg.js} +2 -2
  213. package/dashboard/dist/assets/{index-B7VC0vjf.js.map → index-C69XvBfg.js.map} +1 -1
  214. package/dashboard/dist/assets/index-CHw4Fl0t.css +1 -0
  215. package/dashboard/dist/assets/{index-CUfgt3mV.js → index-C_NwHTMh.js} +2 -2
  216. package/dashboard/dist/assets/{index-CUfgt3mV.js.map → index-C_NwHTMh.js.map} +1 -1
  217. package/dashboard/dist/assets/{index-CAqEy2Db.js → index-Cq0trGqf.js} +2 -2
  218. package/dashboard/dist/assets/{index-CAqEy2Db.js.map → index-Cq0trGqf.js.map} +1 -1
  219. package/dashboard/dist/assets/{index-D1xVY4JR.js → index-DE0r9SEX.js} +22 -22
  220. package/dashboard/dist/assets/index-DE0r9SEX.js.map +1 -0
  221. package/dashboard/dist/assets/{index-DjJ6VCuc.js → index-DH_CiD0W.js} +2 -2
  222. package/dashboard/dist/assets/{index-DjJ6VCuc.js.map → index-DH_CiD0W.js.map} +1 -1
  223. package/dashboard/dist/assets/{index-DzVYjoex.js → index-K23FdS-T.js} +2 -2
  224. package/dashboard/dist/assets/{index-DzVYjoex.js.map → index-K23FdS-T.js.map} +1 -1
  225. package/dashboard/dist/assets/{index-FuhiJuHL.js → index-f6K1UNrn.js} +2 -2
  226. package/dashboard/dist/assets/{index-FuhiJuHL.js.map → index-f6K1UNrn.js.map} +1 -1
  227. package/dashboard/dist/assets/{index-B5qsp8lb.js → index-qr1fndqM.js} +2 -2
  228. package/dashboard/dist/assets/{index-B5qsp8lb.js.map → index-qr1fndqM.js.map} +1 -1
  229. package/dashboard/dist/assets/index-vKTvJ_li.js +2 -0
  230. package/dashboard/dist/assets/index-vKTvJ_li.js.map +1 -0
  231. package/dashboard/dist/assets/{index-BUmnJ4eT.js → index-y-3ODtEE.js} +2 -2
  232. package/dashboard/dist/assets/{index-BUmnJ4eT.js.map → index-y-3ODtEE.js.map} +1 -1
  233. package/dashboard/dist/assets/{index-Dsi5o7dx.js → index-y56Ds-LB.js} +2 -2
  234. package/dashboard/dist/assets/{index-Dsi5o7dx.js.map → index-y56Ds-LB.js.map} +1 -1
  235. package/dashboard/dist/assets/{knowledge-BKg0cr39.js → knowledge-DSSATOTX.js} +2 -2
  236. package/dashboard/dist/assets/{knowledge-BKg0cr39.js.map → knowledge-DSSATOTX.js.map} +1 -1
  237. package/dashboard/dist/assets/{mcp-CVdHpKlz.js → mcp-CfJFVo5k.js} +2 -2
  238. package/dashboard/dist/assets/{mcp-CVdHpKlz.js.map → mcp-CfJFVo5k.js.map} +1 -1
  239. package/dashboard/dist/assets/{mcp-query-CYxZcbwQ.js → mcp-query-DKVx2Q-X.js} +2 -2
  240. package/dashboard/dist/assets/{mcp-query-CYxZcbwQ.js.map → mcp-query-DKVx2Q-X.js.map} +1 -1
  241. package/dashboard/dist/assets/{pipelines-BOEv7P6O.js → pipelines-BSWzE-6M.js} +2 -2
  242. package/dashboard/dist/assets/{pipelines-BOEv7P6O.js.map → pipelines-BSWzE-6M.js.map} +1 -1
  243. package/dashboard/dist/assets/{roles-c5cV2QYQ.js → roles-t9HzhI5N.js} +2 -2
  244. package/dashboard/dist/assets/{roles-c5cV2QYQ.js.map → roles-t9HzhI5N.js.map} +1 -1
  245. package/dashboard/dist/assets/{tasks-JV3BjeEF.js → tasks-B7rUihoH.js} +2 -2
  246. package/dashboard/dist/assets/{tasks-JV3BjeEF.js.map → tasks-B7rUihoH.js.map} +1 -1
  247. package/dashboard/dist/assets/{topics-BDaPnhnQ.js → topics-PC2fEiyD.js} +2 -2
  248. package/dashboard/dist/assets/{topics-BDaPnhnQ.js.map → topics-PC2fEiyD.js.map} +1 -1
  249. package/dashboard/dist/assets/{useEventHooks-a7Dnh8G4.js → useEventHooks-DK9mw1cI.js} +2 -2
  250. package/dashboard/dist/assets/{useEventHooks-a7Dnh8G4.js.map → useEventHooks-DK9mw1cI.js.map} +1 -1
  251. package/dashboard/dist/assets/{useNamespace-BSwEV0e9.js → useNamespace-3IjtUtAw.js} +2 -2
  252. package/dashboard/dist/assets/{useNamespace-BSwEV0e9.js.map → useNamespace-3IjtUtAw.js.map} +1 -1
  253. package/dashboard/dist/assets/{useYamlActivityEvents-BuqvL-cn.js → useYamlActivityEvents-uxukp3gO.js} +2 -2
  254. package/dashboard/dist/assets/{useYamlActivityEvents-BuqvL-cn.js.map → useYamlActivityEvents-uxukp3gO.js.map} +1 -1
  255. package/dashboard/dist/assets/users-CC4B5Fm5.js +2 -0
  256. package/dashboard/dist/assets/users-CC4B5Fm5.js.map +1 -0
  257. package/dashboard/dist/assets/{vendor-icons-D5ycKPA2.js → vendor-icons-Dy5Jpknf.js} +2 -2
  258. package/dashboard/dist/assets/{vendor-icons-D5ycKPA2.js.map → vendor-icons-Dy5Jpknf.js.map} +1 -1
  259. package/dashboard/dist/assets/{workflows-DF-9Voyi.js → workflows-S7MF1nNk.js} +2 -2
  260. package/dashboard/dist/assets/{workflows-DF-9Voyi.js.map → workflows-S7MF1nNk.js.map} +1 -1
  261. package/dashboard/dist/assets/{yaml-workflows-2roKaSSK.js → yaml-workflows-WfdaMHNn.js} +2 -2
  262. package/dashboard/dist/assets/{yaml-workflows-2roKaSSK.js.map → yaml-workflows-WfdaMHNn.js.map} +1 -1
  263. package/dashboard/dist/index.html +3 -3
  264. package/docs/api/http/escalations.md +131 -10
  265. package/docs/api/http/roles.md +44 -9
  266. package/docs/api/http/users.md +15 -6
  267. package/docs/api/mcp/admin.md +8 -2
  268. package/docs/api/mcp/human-queue.md +34 -2
  269. package/docs/api/sdk/escalations.md +155 -2
  270. package/docs/api/sdk/roles.md +11 -0
  271. package/docs/api/sdk/users.md +26 -1
  272. package/docs/auth.md +1 -1
  273. package/docs/dashboard.md +5 -4
  274. package/docs/data.md +9 -2
  275. package/docs/faceted-routing.md +221 -0
  276. package/docs/hitl-guide.md +50 -0
  277. package/docs/iam.md +31 -0
  278. package/docs/mcp.md +8 -0
  279. package/package.json +2 -2
  280. package/dashboard/dist/assets/AdminDashboard-LTiYLuzq.js +0 -2
  281. package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js +0 -2
  282. package/dashboard/dist/assets/AvailableEscalationsPage-BEylRqjP.js.map +0 -1
  283. package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js +0 -2
  284. package/dashboard/dist/assets/CopyableId-DaT0ZRHg.js.map +0 -1
  285. package/dashboard/dist/assets/OperatorDashboard-g9o7I925.js +0 -2
  286. package/dashboard/dist/assets/PageHeader-Dk2Pe44Q.js +0 -2
  287. package/dashboard/dist/assets/PageHeader-Dk2Pe44Q.js.map +0 -1
  288. package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js +0 -2
  289. package/dashboard/dist/assets/PriorityBadge-DfQY9St9.js.map +0 -1
  290. package/dashboard/dist/assets/RolePill-Du-aNnjR.js +0 -2
  291. package/dashboard/dist/assets/RolePill-Du-aNnjR.js.map +0 -1
  292. package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js +0 -2
  293. package/dashboard/dist/assets/RowActions-Dg-Fsm5O.js.map +0 -1
  294. package/dashboard/dist/assets/escalation-columns-nVYVt7Gl.js +0 -2
  295. package/dashboard/dist/assets/escalation-columns-nVYVt7Gl.js.map +0 -1
  296. package/dashboard/dist/assets/index-CP0JEQhZ.js +0 -6
  297. package/dashboard/dist/assets/index-CP0JEQhZ.js.map +0 -1
  298. package/dashboard/dist/assets/index-ClvYSNZd.css +0 -1
  299. package/dashboard/dist/assets/index-D1xVY4JR.js.map +0 -1
  300. package/dashboard/dist/assets/index-DPLy-jwO.js +0 -2
  301. package/dashboard/dist/assets/index-DPLy-jwO.js.map +0 -1
  302. package/dashboard/dist/assets/users-Pn85K_3C.js +0 -2
  303. package/dashboard/dist/assets/users-Pn85K_3C.js.map +0 -1
@@ -0,0 +1,221 @@
1
+ # Faceted Routing
2
+
3
+ The escalation queue is more than a HITL inbox — it is a durable, queryable pool of work
4
+ that any number of consumers can search and atomically claim by its **facets**. Faceted
5
+ routing is the platform surface for that: a composable query language over the queue and a
6
+ set of atomic claim primitives. The platform supplies the surface; the *policy* — what a
7
+ facet means, how capability and priority map onto it, the matching loop — is the consuming
8
+ app's. The [print-routing example](../examples/workflows/print-routing/README.md) is a full
9
+ worked policy built entirely on these primitives.
10
+
11
+ ## Contents
12
+
13
+ - [The model](#the-model)
14
+ - [The query — `FacetQuery`](#the-query--facetquery)
15
+ - [Reads](#reads)
16
+ - [Atomic claims](#atomic-claims)
17
+ - [The dispatcher pattern](#the-dispatcher-pattern)
18
+ - [Recording the outcome on resolve](#recording-the-outcome-on-resolve)
19
+ - [Safety](#safety)
20
+
21
+ ## The model
22
+
23
+ Every escalation row carries a `role` (the hard isolation boundary) and a `metadata` JSONB
24
+ object (the soft facets a late-binding consumer queries, sorts, and claims by). Routing
25
+ work is a funnel over those columns:
26
+
27
+ - **Capability** — the hard wall — is the `role`. A consumer bound to one role can never
28
+ see another role's work. Soft capability is `metadata @>` containment.
29
+ - **Preference / priority** — ordering — is `orderBy` over top-level columns and facets.
30
+ - **Capacity** — how much to take — is the page `limit` of a claim.
31
+
32
+ An **order** (or any multi-unit job) is a set of rows sharing an `origin_id`; its unit
33
+ count lives in a metadata facet so a group can be claimed all-or-nothing only when complete.
34
+
35
+ ## The query — `FacetQuery`
36
+
37
+ ```typescript
38
+ interface FacetQuery {
39
+ role?: string; // hard isolation (exact)
40
+ roles?: string[]; // role = ANY(...)
41
+ facets?: Record<string, any>; // metadata @> facets — required (AND), GIN-served
42
+ block?: Record<string, any>[]; // NOT (metadata @> ANY(block)) — exclusion list
43
+ range?: { facet: string; op: '<' | '<=' | '>' | '>=' | '='; value: number }[];
44
+ exists?: string[]; // metadata ? key — facet must be present
45
+ status?: string; // e.g. 'pending'
46
+ available?: boolean; // true = unclaimed/expired only; false = held now
47
+ orderBy?: { field: string; direction?: 'asc' | 'desc'; numeric?: boolean }[];
48
+ limit?: number;
49
+ offset?: number;
50
+ }
51
+ ```
52
+
53
+ `orderBy.field` is either a whitelisted top-level column (`priority`, `created_at`,
54
+ `updated_at`, `status`, `role`) or a metadata path written as `metadata.<key>` (extracted as
55
+ text, or numeric when `numeric` is set). Every value is parameterized; only validated column
56
+ names / metadata keys and a fixed operator set are ever interpolated.
57
+
58
+ ```typescript
59
+ // Pending, available diabetic-certified work needing PLA, soonest-deadline first.
60
+ const query: FacetQuery = {
61
+ role: 'print-farm-diabetic',
62
+ status: 'pending',
63
+ available: true,
64
+ facets: { filament: 'pla' },
65
+ orderBy: [{ field: 'metadata.mustCompleteBy', numeric: true, direction: 'asc' }],
66
+ };
67
+ ```
68
+
69
+ ## Reads
70
+
71
+ ```typescript
72
+ import { searchByFacets, searchGroups, countByFacets } from '@hotmeshio/long-tail';
73
+
74
+ // Item-level: filter/sort over columns and metadata facets.
75
+ const { escalations, total } = await searchByFacets(query);
76
+
77
+ // Order-level: each row is an origin group with its unit count, availability and
78
+ // completeness — what a batched dispatcher reads to page by capacity.
79
+ const groups = await searchGroups(query, { sizeFacet: 'orderSize', limit: 10 });
80
+
81
+ // Aggregate: capacity / in-flight soft-limit checks.
82
+ const inFlight = await countByFacets({ role: 'print-farm-diabetic', available: false });
83
+ ```
84
+
85
+ ## Atomic claims
86
+
87
+ Two claim primitives, both `FOR UPDATE SKIP LOCKED` so many consumers run without
88
+ contention. Each takes the `FacetQuery`, a `consumer` id, and options.
89
+
90
+ ```typescript
91
+ import { claimGroups, claimByFacets } from '@hotmeshio/long-tail';
92
+
93
+ // Claim whole orders (origin groups), all-or-nothing, in rank order. The page `limit`
94
+ // is how many consumers/printers are free, so a dependent grabs exactly what it can
95
+ // distribute. A group is only eligible when complete (member count = declared size).
96
+ const orders = await claimGroups(query, 'broker-1', { limit: 4, sizeFacet: 'orderSize' });
97
+
98
+ // Claim individual rows by facet — the single-row sibling. Locks up to `limit` available
99
+ // rows in rank order. With { allOrNone: true } the claim commits only when the full limit
100
+ // is acquired, otherwise it rolls back and returns [] — the all-or-none lock a dispatcher
101
+ // wants over a counted set it anticipated.
102
+ const printers = await claimByFacets(
103
+ { role: 'printer-pool-diabetic', facets: { state: 'ready' } },
104
+ 'broker-1',
105
+ { limit: 4 },
106
+ );
107
+ ```
108
+
109
+ A claim sets `assigned_to` + `assigned_until` (default 30 minutes). Availability is a
110
+ query, not a flag: a row is held iff `assigned_to` is set and `assigned_until > NOW()`.
111
+ Resolve, release, or let the claim expire to return it to the pool.
112
+
113
+ ## The dispatcher pattern
114
+
115
+ The primitives compose into a market: pull a *page* of complete orders sized to how many
116
+ consumers are free, then distribute one per consumer. Reads go through the
117
+ `public.lt_escalations` view (it adds the computed `available` flag); atomic claims run on
118
+ the shared `public.hmsh_escalations` table — the same raw-SQL-on-the-shared-table pattern as
119
+ the rest of the escalation service. Metadata facets use the GIN index (`@>`).
120
+
121
+ ```typescript
122
+ // 1. Anticipate supply.
123
+ const free = await searchByFacets({ role: 'printer-pool', available: true, facets: { state: 'ready' } });
124
+ // 2. Claim that much demand by priority.
125
+ const orders = await claimGroups(demandQuery, consumer, { limit: free.escalations.length });
126
+ // 3. Lock the printers for the claimed orders, all-or-none.
127
+ const printers = await claimByFacets(supplyQuery, consumer, { limit: orders.length, allOrNone: true });
128
+ // 4. Pair, do the work, resolve both.
129
+ ```
130
+
131
+ See the [print-routing example](../examples/workflows/print-routing/README.md) for the full
132
+ two-sided market, carry-forward under contention, and the inspection/convergence loop.
133
+
134
+ ## Recording the outcome on resolve
135
+
136
+ A row is created carrying **intent** — the facets that route and claim it. Resolving it can
137
+ record the **outcome** onto the same row: pass a `metadata` patch and it is merged (not
138
+ replaced) into the GIN-indexed metadata in the same atomic UPDATE, on the winning resolve
139
+ only. "What actually happened" becomes `@>`-queryable next to "what was asked".
140
+
141
+ ```typescript
142
+ import { resolveEscalation } from '@hotmeshio/long-tail';
143
+
144
+ // The work is done; resolve the row AND stamp the outcome onto it — one atomic call.
145
+ await resolveEscalation(rowId, { result: 'success' }, {
146
+ outcome: 'success',
147
+ unitsPrinted: 6,
148
+ });
149
+ // Duration needs no facet: the row's own created_at → resolved_at is the elapsed time.
150
+ ```
151
+
152
+ The third argument is distinct from the second: `resolverPayload` (arg 2) is delivered to a
153
+ waiting workflow as `condition()`'s return value and is **not** indexed; the `metadata` patch
154
+ (arg 3) is the durable, queryable record on the row. With both, the row alone answers what was
155
+ asked, what happened, and how long it took:
156
+
157
+ ```typescript
158
+ searchByFacets({ role: 'printer-pool-diabetic', facets: { state: 'printing', outcome: 'success' } });
159
+ // → every completed job: which consumer, which origin, how long — no side table to reconcile
160
+ ```
161
+
162
+ The print farm leans on this: a printer resolves its in-flight `printing` row with the run's
163
+ result and units in one atomic call, so the supply pond is also the production log — and the
164
+ row's `created_at` → `resolved_at` is the print duration, no stored field required.
165
+
166
+ ## The human / operations query
167
+
168
+ The same query language is also the operations team's read surface. Where the dispatcher
169
+ sections above target a **single pond** for a workflow consumer, a **person** — a superadmin, or
170
+ a member of several roles — queries "what work needs doing / was done" across **their** roles by
171
+ the same facets, through the scoped list API.
172
+
173
+ ```typescript
174
+ import { createClient } from '@hotmeshio/long-tail/sdk';
175
+ const lt = createClient({ auth }); // auth carries the caller's userId
176
+
177
+ // Faceted, role-scoped, paginated — the filter AND the count run in SQL.
178
+ const res = await lt.escalations.list({
179
+ status: 'pending',
180
+ facets: { flags: 'too_short' }, // metadata @> (GIN-served)
181
+ range: [{ facet: 'confidence', op: '<=', value: 0.7 }],
182
+ exists: ['needsReview'],
183
+ block: [{ outcome: 'success' }], // exclude completed
184
+ orderBy: [{ field: 'metadata.confidence', numeric: true, direction: 'asc' }],
185
+ limit: 50, offset: 0,
186
+ });
187
+ // res.data → { escalations, total }
188
+ ```
189
+
190
+ `list` and `listAvailable` accept the FacetQuery elements (`facets`, `block`, `range`, `exists`,
191
+ `roles`, `available`, `orderBy`) on top of the plain filters. When any are present the request
192
+ runs through the scoped faceted query; otherwise the simple list path is used.
193
+
194
+ **Role scope is enforced in SQL, not in the controller.** The result is the read-scope predicate
195
+ ANDed with the facets:
196
+
197
+ - **superadmin / admin-of-admin** (global) — every role's queue, no role filter.
198
+ - **`read_all` on a role** — the whole pond for that role.
199
+ - **`read_self` on a role** — only items assigned to the caller (`assigned_to = me`).
200
+
201
+ A caller may **narrow** within their scope (pass `role`/`roles`) but can never **widen** past it —
202
+ a `role` they don't hold simply matches nothing. The page total is computed by the same WHERE, so
203
+ pagination stays correct; the controller never filters a fetched page client-side.
204
+
205
+ Over HTTP the faceted elements are JSON-encoded query params, so the whole query rides on a GET
206
+ URL and the dashboard's copy-URL / copy-curl reproduces the exact request:
207
+
208
+ ```
209
+ GET /api/escalations?status=pending&facets=%7B%22flags%22%3A%22too_short%22%7D&range=%5B%7B%22facet%22%3A%22confidence%22%2C%22op%22%3A%22%3C%3D%22%2C%22value%22%3A0.7%7D%5D
210
+ ```
211
+
212
+ ## Safety
213
+
214
+ - **Parameterized** — every value is a bound parameter; only audited column names, metadata
215
+ keys (strict `^[a-zA-Z0-9_]+$`), and a fixed operator set are interpolated.
216
+ - **Atomic** — claims are single transactions with `FOR UPDATE SKIP LOCKED`; group claims
217
+ re-check completeness *inside* the lock, so concurrent dispatchers never split a group or
218
+ double-claim a row.
219
+ - **Durable consumers** — because the surface is a query, a consumer that holds a claim it
220
+ cannot yet place can carry it across `continueAsNew` and converge later rather than
221
+ releasing — the durable workflow is the coordinator, not a distributed transaction.
@@ -158,6 +158,29 @@ export async function reviewWorkflow(envelope: LTEnvelope) {
158
158
 
159
159
  ---
160
160
 
161
+ ## Recording the Outcome on Resolution
162
+
163
+ An escalation row is created carrying **intent** — what was asked, who it routed to. Resolving it can stamp the **outcome** onto the same row: every resolve surface takes an optional `metadata` patch, merged into the row's GIN-indexed metadata.
164
+
165
+ | Surface | How to pass it |
166
+ |---------|----------------|
167
+ | HTTP | `metadata` in the resolve body (`POST /api/escalations/:id/resolve`, `/resolve-by-signal-key`) |
168
+ | SDK facade | `lt.escalations.resolve({ id, resolverPayload, metadata })` |
169
+ | MCP | `metadata` arg on `claim_and_resolve` / `resolve_escalation` |
170
+ | In-process library | `resolveEscalation(id, payload, metadata)` / `resolveEscalationBySignalKey(signalKey, payload, metadata)` |
171
+
172
+ ```typescript
173
+ await lt.escalations.resolve({
174
+ id,
175
+ resolverPayload: { approved: true }, // resumes the workflow; not indexed
176
+ metadata: { outcome: 'approved', reviewedBy: 'alice', durationMs: elapsed },
177
+ });
178
+ ```
179
+
180
+ The patch is distinct from the resolver payload: the payload resumes the paused workflow and is not indexed; the metadata patch is the durable, queryable record on the row. Use it for the audit trail and analytics — disposition, reviewer, time-to-resolve — so the escalation table answers *what was asked, what was decided, and how long it took* without a parallel log.
181
+
182
+ ---
183
+
161
184
  ## JSON Schema Form Authoring
162
185
 
163
186
  The dashboard renders forms automatically from JSON Schema. No frontend code needed.
@@ -456,6 +479,33 @@ await ltCreateEscalation({
456
479
  });
457
480
  ```
458
481
 
482
+ ### Work-Surface Scope
483
+
484
+ A `member` of a role carries a work-surface scope that narrows what they see and act on within that queue: `read_scope` (`self` | `all`) governs which escalations they see, and `write_scope` (`none` | `self` | `all`) governs which they can claim, resolve, or cancel. `self` means escalations assigned to that member (`assigned_to = user`); `all` means the whole role queue. `admin` and `superadmin` always work the whole queue. See the [Roles API](api/http/roles.md#work-surface-scope) for the five member profiles and the assignment contract.
485
+
486
+ Scope is a property of the **membership** (`lt_user_roles`), not of the escalation. The escalation engine is unchanged: `condition()` / `conditionLT()`, `ltCreateEscalation`, and the interceptor write escalation rows (`hmsh_escalations`) exactly as before — an escalation carries a `role` and an optional `assigned_to`, with no scope column. Scope is resolved at read time, when a *user* lists or acts on the queue. Escalating to a role that does not exist yet still registers the role name only — roles are typeless; `type` and scope are set per user when the role is granted. The one place scope affects creation is the standalone `POST /api/escalations` HTTP endpoint, which requires `write_scope=all`; workflow-emitted escalations (`conditionLT`, interceptor returns, `ltCreateEscalation`) do not pass through that check.
487
+
488
+ ### One-Time and Pre-Assigned Users
489
+
490
+ To route a single item to a named person, assign the escalation to them and provision them with `read_scope=self` + `write_scope=self`. The workflow sets `assigned_to` to the person's user ID (a pre-claim) when it creates the escalation, then provisions or updates that user as a `member` with self/self scope on the target role. They land directly on that one item — a just-in-time form scoped by RBAC, with no access to the rest of the queue and no direct table access. An update or follow-up is simply another workflow firing another escalation to that same person.
491
+
492
+ ```typescript
493
+ // Pre-assign the escalation to a specific person and route a one-time form to them
494
+ await ltCreateEscalation({
495
+ role: 'customer-triage',
496
+ assigned_to: userId, // pre-claim — durable, keyed off the user, not the soft-lock TTL
497
+ description: 'Confirm your shipping address',
498
+ metadata: {
499
+ form_schema: {
500
+ title: 'Confirm Address',
501
+ properties: { address: { type: 'string' }, confirmed: { type: 'boolean' } },
502
+ required: ['confirmed'],
503
+ },
504
+ },
505
+ });
506
+ // The person is provisioned as a member of `customer-triage` with read_scope=self, write_scope=self.
507
+ ```
508
+
459
509
  ### Escalation Chains
460
510
 
461
511
  Users can escalate to other roles via the "Escalate" tab:
package/docs/iam.md CHANGED
@@ -14,6 +14,37 @@ Each workflow execution carries three pieces of identity:
14
14
 
15
15
  **Credentials** — OAuth tokens and API keys available to the principal. Resolved at runtime through a cascade: principal's stored credentials, then the initiating user's credentials, then system environment variables.
16
16
 
17
+ ## Work-Surface Scope
18
+
19
+ Identity says *who* a principal is. Work-surface scope says *what* that principal sees and does within a role's queue. It is the fourth axis, orthogonal to initiator, principal, and credentials.
20
+
21
+ A role is a task queue with four verbs — **search**, **claim**, **ack** (resolve), **delete** (cancel). A `member` grant carries two scope axes stored on `lt_user_roles` that set the breadth of those verbs:
22
+
23
+ - `read_scope` (`self` | `all`, default `all`) — governs **search**: which escalations the member sees. `self` = items assigned to the member (`assigned_to = user`); `all` = the whole role queue.
24
+ - `write_scope` (`none` | `self` | `all`, default `all`) — governs **claim / ack / delete**: which escalations the member may act on. `none` = read-only.
25
+
26
+ The constraint is **write ⊆ read** — you cannot act on what you cannot see — so `write_scope=all` requires `read_scope=all`. `admin` and `superadmin` ignore scope and always act on the whole queue. The default `all`/`all` is the full-queue worker.
27
+
28
+ The five member profiles:
29
+
30
+ | read_scope | write_scope | Profile |
31
+ |-----------|------------|---------|
32
+ | `all` | `all` | Full worker — search and act on the whole queue (default) |
33
+ | `all` | `self` | See the whole queue, act only on own items (e.g. a chat-style room) |
34
+ | `self` | `self` | Own items only — a one-time user filling in their pre-assigned form |
35
+ | `all` | `none` | Read-only observer / auditor of the queue |
36
+ | `self` | `none` | Read-only view of one's own items |
37
+
38
+ ### One-Time Users
39
+
40
+ Scope makes limited-surface users first-class. A workflow assigns an escalation to a named person — a pre-claim, setting `assigned_to` to that user — and provisions them as a `member` with `read_scope=self` and `write_scope=self`. They see and act on exactly that one item: a just-in-time form surface scoped by RBAC, with no access to the rest of the queue and no direct table access. A follow-up or update is simply another workflow firing another escalation to that same person. Because `self` scope keys off durable `assigned_to` (independent of the soft-lock claim TTL), the assignment persists until the workflow resolves it.
41
+
42
+ See the [Roles API](api/http/roles.md#work-surface-scope) for the assignment contract and the [HITL Guide](hitl-guide.md#role-based-routing) for the workflow-side flow.
43
+
44
+ ### Enforcement
45
+
46
+ Scope folds into the SQL, so it is atomic with no TOCTOU window. Read scope becomes part of the escalation search query — a member sees a row when `role ∈ allRoles OR (role ∈ selfRoles AND assigned_to = me)`. Write scope folds into the atomic resolve-by-metadata query and gates the by-id claim/resolve/cancel paths. `assigned_to` is indexed, so `self` scope scales to large queues.
47
+
17
48
  ## Workflow Types and IAM
18
49
 
19
50
  Long Tail has three workflow types (see the [Workflows Guide](workflows.md#three-workflow-types) for full details). IAM applies to all three:
package/docs/mcp.md CHANGED
@@ -354,6 +354,14 @@ Returns:
354
354
 
355
355
  Returns `isError: true` if the escalation isn't available (already claimed, already resolved, or doesn't exist).
356
356
 
357
+ ### Scope and one-time users
358
+
359
+ An escalation created here lands in the target role's queue, where each member sees and acts on it according to their work-surface scope. A role membership carries a `type` (`member`, `admin`, or `superadmin`) plus two scope axes: `read_scope` (`self` | `all`) sets which escalations a member sees, and `write_scope` (`none` | `self` | `all`) sets which they may claim and resolve, with write ⊆ read. A `read_all` member sees the whole queue; a `read_self` member sees only items assigned to them. An `admin` or `superadmin` always acts on the whole queue. The defaults, `all`/`all`, are the full-queue worker.
360
+
361
+ `get_available_work` returns the pending, unassigned items for a role — the shared pool that whole-queue (`write_all`) workers draw from. A `write_self` member does not pull from the pool; their item is pre-assigned to them.
362
+
363
+ The one-time-user pattern builds on this. A workflow routes a single escalation to a named person by setting `assigned_to` when it creates the escalation — the `assigned_to` field on `escalate_and_wait` pre-claims the item — and that person is provisioned as a `member` of the role with `read_self` + `write_self`. They then see and act on exactly that one item: a scoped, single-item surface with no access to the rest of the queue. The scoped membership is provisioned through the platform's user and escalation APIs (see the [Roles API](api/http/roles.md)); the escalate tools supply the `assigned_to` pre-claim.
364
+
357
365
  ### Connecting
358
366
 
359
367
  **In-process (testing or co-located agents):**
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@hotmeshio/long-tail",
3
- "version": "0.5.12",
3
+ "version": "0.6.0",
4
4
  "description": "Long Tail Workflows — Durable AI workflows with human-in-the-loop escalation. Powered by PostgreSQL.",
5
5
  "main": "./build/index.js",
6
6
  "types": "./build/index.d.ts",
@@ -70,7 +70,7 @@
70
70
  "@anthropic-ai/sdk": "^0.92.0",
71
71
  "@aws-sdk/client-s3": "^3.1017.0",
72
72
  "@aws-sdk/s3-request-presigner": "^3.1045.0",
73
- "@hotmeshio/hotmesh": "^0.22.8",
73
+ "@hotmeshio/hotmesh": "^0.24.1",
74
74
  "@modelcontextprotocol/sdk": "^1.27.1",
75
75
  "@opentelemetry/exporter-trace-otlp-proto": "^0.215.0",
76
76
  "@opentelemetry/resources": "^2.5.1",
@@ -1,2 +0,0 @@
1
- import{j as o}from"./vendor-query-B2UbickB.js";import{c as n}from"./tasks-JV3BjeEF.js";import{s as m}from"./index-D1xVY4JR.js";import{f as c}from"./useEventHooks-a7Dnh8G4.js";import{u}from"./mcp-CVdHpKlz.js";import{P as d}from"./PageHeaderWithStats-DJQCZ0FR.js";import"./vendor-react-CXumBFUA.js";import"./vendor-icons-D5ycKPA2.js";function j(){var a;c();const{data:t}=n({limit:1}),{data:e}=m({status:"pending",limit:1}),{data:s}=u(),r=((a=s==null?void 0:s.servers)==null?void 0:a.filter(i=>i.status==="connected").length)??0,l=s?`${r}/${s.total}`:"—";return o.jsx("div",{children:o.jsx(d,{title:"Admin",stats:[{label:"Tasks",value:(t==null?void 0:t.total)??"—"},{label:"Pending Escalations",value:(e==null?void 0:e.total)??"—",dotClass:"bg-status-pending"},{label:"MCP Servers",value:l},{label:"System",value:"Healthy",dotClass:"bg-status-success"}]})})}export{j as AdminDashboard};
2
- //# sourceMappingURL=AdminDashboard-LTiYLuzq.js.map
@@ -1,2 +0,0 @@
1
- import{j as e,a as o}from"./vendor-query-B2UbickB.js";import{j as he,g as pe,k as be,l as Ce,m as fe,n as ye,o as ve,p as je,q as ke,r as Se,s as Ne,t as Pe}from"./index-D1xVY4JR.js";import{f as Ae}from"./useEventHooks-a7Dnh8G4.js";import{u as V,C as Y,a as Ee}from"./CustomDurationPicker-COacSAXO.js";import{u as Be}from"./roles-c5cV2QYQ.js";import{u as we}from"./useFilterParams-x-Dg0Vgz.js";import{u as Me}from"./useDebouncedValue-BA5S7V54.js";import{b as Te}from"./api-path-u2Jf9QXS.js";import{D as ze}from"./DataTable-D9yuBv0w.js";import{S as Fe}from"./StickyPagination-BWhFSr2d.js";import{P as Ie}from"./PageHeader-Dk2Pe44Q.js";import{P as _e}from"./constants-BHkpVaqx.js";import{M as $}from"./Modal-CSrxpXeM.js";import{u as Le}from"./users-Pn85K_3C.js";import{E as Oe,a as Re}from"./escalation-columns-nVYVt7Gl.js";import{R as Ue,a as He}from"./RowActions-Dg-Fsm5O.js";import{L as De}from"./ListToolbar-BJaCzAXS.js";import{a3 as Ve}from"./vendor-icons-D5ycKPA2.js";import{c as $e}from"./vendor-react-CXumBFUA.js";import"./EmptyState-BcsfPq9T.js";import"./FilterBar-Ck4K4rzu.js";import"./PriorityBadge-DfQY9St9.js";import"./RolePill-Du-aNnjR.js";import"./WorkflowPill-CS-IsR0U.js";import"./TimestampCell--tnreI1i.js";import"./escalation-iI4QKlP-.js";const qe=s=>s.isPriorityPending||s.isClaimPending||s.isAssignPending||s.isEscalatePending||s.isTriagePending||s.isCancelPending;function Qe(s){const a=qe(s),r=V();return e.jsxs("div",{className:"flex flex-wrap items-center gap-3 px-4 py-3 bg-accent/5 border border-accent/20 rounded-lg mb-4",children:[e.jsxs("span",{className:"text-xs font-medium text-accent",children:[s.selectedCount," selected"]}),e.jsx("div",{className:"w-px h-5 bg-surface-border"}),e.jsxs("select",{onChange:t=>{t.target.value&&(s.onSetPriority(parseInt(t.target.value)),t.target.value="")},disabled:a,className:"select text-xs py-1.5",defaultValue:"",children:[e.jsx("option",{value:"",disabled:!0,children:"Priority..."}),_e.map(t=>e.jsx("option",{value:t.value,children:t.label},t.value))]}),e.jsxs("select",{onChange:t=>{t.target.value&&(s.onClaim(parseInt(t.target.value)),t.target.value="")},disabled:a,className:"select text-xs py-1.5",defaultValue:"",children:[e.jsx("option",{value:"",disabled:!0,children:"Claim for..."}),r.map(t=>e.jsx("option",{value:t.value,children:t.label},t.value))]}),e.jsx("button",{onClick:s.onAssign,disabled:a,className:"btn-secondary text-xs py-1.5",children:s.isAssignPending?"Assigning...":"Assign to..."}),s.availableRoles.length>0&&e.jsxs("select",{onChange:t=>{t.target.value&&(s.onEscalate(t.target.value),t.target.value="")},disabled:a,className:"select text-xs py-1.5",defaultValue:"",children:[e.jsx("option",{value:"",disabled:!0,children:"Escalate to..."}),s.availableRoles.map(t=>e.jsx("option",{value:t,children:t},t))]}),e.jsx("button",{onClick:s.onTriage,disabled:a,className:"btn-secondary text-xs py-1.5",children:s.isTriagePending?"Triaging...":"Triage"}),e.jsx("button",{onClick:s.onCancel,disabled:a,className:"text-xs py-1.5 px-3 rounded-md border border-status-error/40 text-status-error hover:bg-status-error/10 transition-colors disabled:opacity-50",children:s.isCancelPending?"Cancelling...":"Cancel"}),e.jsx("div",{className:"flex-1"}),e.jsx("button",{onClick:s.onClearSelection,className:"text-xs text-text-tertiary hover:text-text-primary transition-colors",children:"Clear"}),a&&e.jsx("span",{className:"text-xs text-text-tertiary animate-pulse",children:"Processing..."})]})}function Ge({open:s,onClose:a,selectedCount:r,selectedRoles:t,onSubmit:d,isPending:m}){const{isSuperAdmin:x}=he(),h=V(),[l,p]=o.useState("user"),[b,I]=o.useState(null),[y,B]=o.useState(""),[k,c]=o.useState("30"),[f,w]=o.useState(0),M=o.useCallback(n=>w(n),[]),H=!x&&t.length===1?t[0]:void 0,{data:g,isLoading:_}=Le({role:H,status:"active",limit:200}),S=o.useMemo(()=>{const n=(g==null?void 0:g.users)??[];if(!y.trim())return n;const N=y.toLowerCase();return n.filter(v=>{var C,P;return((C=v.display_name)==null?void 0:C.toLowerCase().includes(N))||((P=v.email)==null?void 0:P.toLowerCase().includes(N))})},[g==null?void 0:g.users,y]),T=()=>{p("user"),I(null),B(""),c("30"),w(0),a()},L=n=>{I(n),p("duration")},O=()=>{p("user")},z=()=>{if(!b)return;const n=k==="custom"?f:parseInt(k);!n||n<=0||d(b.id,n)};return e.jsx($,{open:s,onClose:T,title:"Assign Escalations",children:e.jsxs("div",{className:"space-y-4",children:[l==="user"&&e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:"text-sm text-text-secondary",children:["Select a user to assign"," ",e.jsx("span",{className:"font-medium text-text-primary",children:r})," ","escalation(s) to:"]}),e.jsx("input",{type:"text",value:y,onChange:n=>B(n.target.value),placeholder:"Search by name or email...",className:"input text-xs w-full",autoFocus:!0}),e.jsxs("div",{className:"max-h-60 overflow-y-auto border border-surface-border rounded-lg divide-y divide-surface-border",children:[_&&e.jsx("p",{className:"text-xs text-text-tertiary p-3",children:"Loading users..."}),!_&&S.length===0&&e.jsx("p",{className:"text-xs text-text-tertiary p-3",children:"No users found"}),S.map(n=>e.jsxs("button",{onClick:()=>L(n),className:"w-full text-left px-3 py-2 hover:bg-surface-hover transition-colors",children:[e.jsx("p",{className:"text-xs font-medium text-text-primary",children:n.display_name||n.external_id}),n.email&&e.jsx("p",{className:"text-[10px] text-text-tertiary",children:n.email})]},n.id))]}),e.jsx("div",{className:"flex justify-end pt-2",children:e.jsx("button",{onClick:T,className:"btn-secondary text-xs",children:"Cancel"})})]}),l==="duration"&&b&&e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:"text-sm text-text-secondary",children:["Assign to"," ",e.jsx("span",{className:"font-medium text-text-primary",children:b.display_name||b.external_id})," ","for:"]}),e.jsxs("select",{value:k,onChange:n=>{c(n.target.value),w(0)},className:"select w-full text-sm",children:[h.map(n=>e.jsx("option",{value:n.value,children:n.label},n.value)),e.jsx("option",{value:"custom",children:"Other..."})]}),k==="custom"&&e.jsx(Y,{onChange:M,autoFocus:!0}),e.jsxs("div",{className:"flex justify-end gap-3 pt-2",children:[e.jsx("button",{onClick:O,className:"btn-secondary text-xs",children:"Back"}),e.jsx("button",{onClick:z,disabled:m,className:"btn-primary text-xs",children:m?"Assigning...":"Assign"})]})]})]})})}function Ye({open:s,onClose:a,selectedCount:r,onSubmit:t,isPending:d}){const[m,x]=o.useState(""),h=()=>{t(m||void 0)};return e.jsx($,{open:s,onClose:a,title:"Submit for AI Triage",children:e.jsxs("div",{className:"space-y-4",children:[e.jsxs("p",{className:"text-sm text-text-secondary",children:["Submit ",e.jsx("span",{className:"font-medium text-text-primary",children:r})," escalation(s) for AI triage? The triage orchestrator will take over resolution."]}),e.jsxs("div",{children:[e.jsxs("label",{className:"text-xs font-medium text-text-secondary block mb-1",children:["Triage hint ",e.jsx("span",{className:"text-text-tertiary",children:"(optional)"})]}),e.jsx("input",{type:"text",value:m,onChange:l=>x(l.target.value),placeholder:"e.g., image_orientation",className:"input text-xs font-mono w-full"}),e.jsx("p",{className:"text-[10px] text-text-tertiary mt-1",children:"Guides the triage workflow on what remediation to apply"})]}),e.jsxs("div",{className:"flex justify-end gap-3 pt-2",children:[e.jsx("button",{onClick:a,className:"btn-secondary text-xs",children:"Cancel"}),e.jsx("button",{onClick:h,disabled:d,className:"btn-primary text-xs",children:d?"Submitting...":"Submit for Triage"})]})]})})}function E(s,a,r){return t=>{s.mutate(t,{onSuccess:()=>{a(),r==null||r()}})}}function Je(s){const a=()=>[...s.selectedIds];return{handleSetPriority:l=>{E(s.setPriority,s.clearSelection)({ids:a(),priority:l})},handleBulkClaim:l=>{E(s.bulkClaim,s.clearSelection)({ids:a(),durationMinutes:l})},handleBulkEscalate:l=>{E(s.bulkEscalate,s.clearSelection)({ids:a(),targetRole:l})},handleBulkTriage:l=>{E(s.bulkTriage,s.clearSelection,s.closeTriageModal)({ids:a(),hint:l})},handleBulkAssign:(l,p)=>{E(s.bulkAssign,s.clearSelection,s.closeAssignModal)({ids:a(),targetUserId:l,durationMinutes:p})},handleBulkCancel:()=>{E(s.bulkCancel,s.clearSelection,s.closeCancelModal)({ids:a()})}}}function Ke({claimTarget:s,onClose:a,claimDuration:r,onDurationChange:t,claimDurations:d,onCustomClaimChange:m,onClaim:x,isPending:h}){return e.jsx($,{open:!!s,onClose:a,title:"Claim Escalation",children:e.jsxs("div",{className:"space-y-4",children:[e.jsxs("p",{className:"text-sm text-text-secondary",children:["Claim ",e.jsx("span",{className:"font-medium text-text-primary",children:s==null?void 0:s.type})," for:"]}),e.jsxs("select",{value:r,onChange:l=>{t(l.target.value)},className:"select w-full text-sm",children:[d.map(l=>e.jsx("option",{value:l.value,children:l.label},l.value)),e.jsx("option",{value:"custom",children:"Other..."})]}),r==="custom"&&e.jsx(Y,{onChange:m,autoFocus:!0}),e.jsxs("div",{className:"flex justify-end gap-3 pt-2",children:[e.jsx("button",{onClick:a,className:"btn-secondary text-xs",children:"Cancel"}),e.jsx("button",{onClick:x,className:"btn-primary text-xs",disabled:h,children:h?"Claiming...":"Claim"})]})]})})}function ks(){Ae();const s=$e(),{filters:a,setFilter:r,pagination:t,sort:d,setSort:m}=we({filters:{role:"",type:"",priority:"",status:"available",search:""}}),x=Me(a.search,300),h=V(),[l,p]=o.useState(null),[b,I]=o.useState("30"),[y,B]=o.useState(0),k=o.useCallback(i=>B(i),[]),[c,f]=o.useState(new Set),[w,M]=o.useState(!1),[H,g]=o.useState(!1),[_,S]=o.useState(!1),T=pe(),L=be(),O=Ce(),z=fe(),n=ye(),N=ve(),v=je(),{data:C}=Be(),{data:P}=ke();o.useEffect(()=>{f(new Set)},[a.role,a.type,a.priority,a.status,x,t.page,t.pageSize]);const R=a.status||"",F=R==="available",D=R==="claimed",q=D?"pending":R==="resolved"?"resolved":R==="cancelled"?"cancelled":void 0,Q={role:a.role||void 0,type:a.type||void 0,priority:a.priority?parseInt(a.priority):void 0,limit:t.pageSize,offset:t.offset,sort_by:d.sort_by||"created_at",order:d.order||"desc",search:x||void 0},J=Se({...Q,enabled:F}),K=Ne({status:q,claimed:D||void 0,...Q,enabled:!F}),W=F?J:K,{data:A,isLoading:X,error:U,refetch:Z,isFetching:ee}=W,se=Te(`/escalations${F?"/available":""}`,{status:q,claimed:D||void 0,role:a.role||void 0,type:a.type||void 0,priority:a.priority||void 0,search:x||void 0,sort_by:d.sort_by||"created_at",order:d.order||"desc",limit:t.pageSize,offset:t.offset}),j=(A==null?void 0:A.escalations)??[],G=(A==null?void 0:A.total)??0,{canBulk:te}=Pe(),ae=o.useMemo(()=>{const i=new Set;for(const u of j)c.has(u.id)&&i.add(u.role);return[...i]},[j,c]),ne=o.useCallback(()=>f(new Set),[]),le=()=>{if(!l)return;const i=b==="custom"?y:parseInt(b);!i||i<=0||T.mutate({id:l.id,durationMinutes:i},{onSuccess:()=>{p(null),s(`/escalations/detail/${l.id}`)}})},{handleSetPriority:ie,handleBulkClaim:oe,handleBulkEscalate:re,handleBulkTriage:ce,handleBulkAssign:de,handleBulkCancel:ue}=Je({selectedIds:c,clearSelection:ne,setPriority:L,bulkClaim:O,bulkEscalate:n,bulkTriage:N,bulkAssign:z,bulkCancel:v,closeTriageModal:()=>M(!1),closeAssignModal:()=>g(!1),closeCancelModal:()=>S(!1)}),me=i=>{const u=new Set(c);u.has(i)?u.delete(i):u.add(i),f(u)},xe=()=>{c.size===j.length?f(new Set):f(new Set(j.map(i=>i.id)))},ge=[te?{key:"select",label:e.jsx("input",{type:"checkbox",checked:j.length>0&&c.size===j.length,onChange:xe,className:"rounded"}),render:i=>e.jsx("input",{type:"checkbox",checked:c.has(i.id),onChange:u=>{u.stopPropagation(),me(i.id)},onClick:u=>u.stopPropagation(),className:"rounded"}),className:"w-10"}:{key:"spacer",label:"",render:()=>null,className:"w-10"},...Oe,{key:"actions",label:"",render:i=>e.jsx(Ue,{children:e.jsx(He,{icon:Ve,title:"Claim escalation",onClick:()=>p(i)})}),className:"w-16 text-right"}];return e.jsxs("div",{children:[e.jsx(Ie,{title:"Available Escalations",docsHash:"#docs:dashboard.md:all-escalations"}),e.jsx(Re,{filters:a,setFilter:r,roles:(C==null?void 0:C.roles)??[],types:(P==null?void 0:P.types)??[],showStatus:!0,actions:e.jsx(De,{onRefresh:()=>Z(),isFetching:ee,apiPath:se})}),c.size>0&&e.jsx(Qe,{selectedCount:c.size,onClearSelection:()=>f(new Set),onSetPriority:ie,onClaim:oe,onAssign:()=>g(!0),onEscalate:re,onTriage:()=>M(!0),onCancel:()=>S(!0),isPriorityPending:L.isPending,isClaimPending:O.isPending,isAssignPending:z.isPending,isEscalatePending:n.isPending,isTriagePending:N.isPending,isCancelPending:v.isPending,availableRoles:(C==null?void 0:C.roles)??[]}),U&&e.jsx("div",{className:"mb-4 px-4 py-3 rounded-md bg-status-error/10 border border-status-error/20 text-xs text-status-error",children:U.message==="Session expired"?"Your session has expired. Please log in again.":`Failed to load escalations: ${U.message}`}),e.jsx(ze,{columns:ge,data:j,keyFn:i=>i.id,onRowClick:i=>s(`/escalations/detail/${i.id}`,{state:{from:"/escalations/available"}}),isLoading:X,emptyMessage:U?"Unable to load data":"No available escalations",sort:d,onSort:m}),e.jsx(Fe,{page:t.page,totalPages:t.totalPages(G),onPageChange:t.setPage,total:G,pageSize:t.pageSize,onPageSizeChange:t.setPageSize}),e.jsx(Ke,{claimTarget:l,onClose:()=>p(null),claimDuration:b,onDurationChange:i=>{I(i),B(0)},claimDurations:h,customClaimMinutes:y,onCustomClaimChange:k,onClaim:le,isPending:T.isPending}),e.jsx(Ye,{open:w,onClose:()=>M(!1),selectedCount:c.size,onSubmit:ce,isPending:N.isPending}),e.jsx(Ge,{open:H,onClose:()=>g(!1),selectedCount:c.size,selectedRoles:ae,onSubmit:de,isPending:z.isPending}),e.jsx(Ee,{open:_,onClose:()=>S(!1),onConfirm:ue,selectedCount:c.size,isPending:v.isPending,error:v.error})]})}export{ks as AvailableEscalationsPage};
2
- //# sourceMappingURL=AvailableEscalationsPage-BEylRqjP.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"AvailableEscalationsPage-BEylRqjP.js","sources":["../../src/components/common/modal/BulkActionBar.tsx","../../src/components/common/modal/BulkAssignModal.tsx","../../src/components/common/modal/BulkTriageModal.tsx","../../src/pages/operator/helpers.ts","../../src/pages/operator/ClaimModal.tsx","../../src/pages/operator/AvailableEscalationsPage.tsx"],"sourcesContent":["import { PRIORITY_OPTIONS } from '../../../lib/constants';\nimport { useClaimDurations } from '../../../hooks/useClaimDurations';\n\ninterface BulkActionBarProps {\n selectedCount: number;\n onClearSelection: () => void;\n onSetPriority: (priority: 1 | 2 | 3 | 4) => void;\n onClaim: (durationMinutes: number) => void;\n onAssign: () => void;\n onEscalate: (targetRole: string) => void;\n onTriage: () => void;\n onCancel: () => void;\n isPriorityPending: boolean;\n isClaimPending: boolean;\n isAssignPending: boolean;\n isEscalatePending: boolean;\n isTriagePending: boolean;\n isCancelPending: boolean;\n availableRoles: string[];\n}\n\nconst anyPending = (props: BulkActionBarProps) =>\n props.isPriorityPending || props.isClaimPending || props.isAssignPending || props.isEscalatePending || props.isTriagePending || props.isCancelPending;\n\nexport function BulkActionBar(props: BulkActionBarProps) {\n const disabled = anyPending(props);\n const claimDurations = useClaimDurations();\n\n return (\n <div className=\"flex flex-wrap items-center gap-3 px-4 py-3 bg-accent/5 border border-accent/20 rounded-lg mb-4\">\n <span className=\"text-xs font-medium text-accent\">\n {props.selectedCount} selected\n </span>\n\n <div className=\"w-px h-5 bg-surface-border\" />\n\n {/* Priority */}\n <select\n onChange={(e) => {\n if (!e.target.value) return;\n props.onSetPriority(parseInt(e.target.value) as 1 | 2 | 3 | 4);\n e.target.value = '';\n }}\n disabled={disabled}\n className=\"select text-xs py-1.5\"\n defaultValue=\"\"\n >\n <option value=\"\" disabled>Priority...</option>\n {PRIORITY_OPTIONS.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n\n {/* Claim */}\n <select\n onChange={(e) => {\n if (!e.target.value) return;\n props.onClaim(parseInt(e.target.value));\n e.target.value = '';\n }}\n disabled={disabled}\n className=\"select text-xs py-1.5\"\n defaultValue=\"\"\n >\n <option value=\"\" disabled>Claim for...</option>\n {claimDurations.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n </select>\n\n {/* Assign */}\n <button\n onClick={props.onAssign}\n disabled={disabled}\n className=\"btn-secondary text-xs py-1.5\"\n >\n {props.isAssignPending ? 'Assigning...' : 'Assign to...'}\n </button>\n\n {/* Escalate */}\n {props.availableRoles.length > 0 && (\n <select\n onChange={(e) => {\n if (!e.target.value) return;\n props.onEscalate(e.target.value);\n e.target.value = '';\n }}\n disabled={disabled}\n className=\"select text-xs py-1.5\"\n defaultValue=\"\"\n >\n <option value=\"\" disabled>Escalate to...</option>\n {props.availableRoles.map((role) => (\n <option key={role} value={role}>{role}</option>\n ))}\n </select>\n )}\n\n {/* Triage */}\n <button\n onClick={props.onTriage}\n disabled={disabled}\n className=\"btn-secondary text-xs py-1.5\"\n >\n {props.isTriagePending ? 'Triaging...' : 'Triage'}\n </button>\n\n {/* Cancel */}\n <button\n onClick={props.onCancel}\n disabled={disabled}\n className=\"text-xs py-1.5 px-3 rounded-md border border-status-error/40 text-status-error hover:bg-status-error/10 transition-colors disabled:opacity-50\"\n >\n {props.isCancelPending ? 'Cancelling...' : 'Cancel'}\n </button>\n\n <div className=\"flex-1\" />\n\n {/* Clear */}\n <button\n onClick={props.onClearSelection}\n className=\"text-xs text-text-tertiary hover:text-text-primary transition-colors\"\n >\n Clear\n </button>\n\n {disabled && (\n <span className=\"text-xs text-text-tertiary animate-pulse\">Processing...</span>\n )}\n </div>\n );\n}\n","import { useState, useMemo, useCallback } from 'react';\nimport { Modal } from './Modal';\nimport { CustomDurationPicker } from '../form/CustomDurationPicker';\nimport { useUsers } from '../../../api/users';\nimport { useAuth } from '../../../hooks/useAuth';\nimport { useClaimDurations } from '../../../hooks/useClaimDurations';\nimport type { LTUserRecord } from '../../../api/types';\n\ninterface BulkAssignModalProps {\n open: boolean;\n onClose: () => void;\n selectedCount: number;\n /** Distinct roles of the selected escalations; scopes user list for admins */\n selectedRoles: string[];\n onSubmit: (targetUserId: string, durationMinutes: number) => void;\n isPending: boolean;\n}\n\nexport function BulkAssignModal({\n open,\n onClose,\n selectedCount,\n selectedRoles,\n onSubmit,\n isPending,\n}: BulkAssignModalProps) {\n const { isSuperAdmin } = useAuth();\n const claimDurations = useClaimDurations();\n const [step, setStep] = useState<'user' | 'duration'>('user');\n const [selectedUser, setSelectedUser] = useState<LTUserRecord | null>(null);\n const [search, setSearch] = useState('');\n const [duration, setDuration] = useState('30');\n const [customMinutes, setCustomMinutes] = useState(0);\n const onCustomChange = useCallback((m: number) => setCustomMinutes(m), []);\n\n // Admins with a single role: scope to that role. Otherwise show all active users.\n const roleFilter =\n !isSuperAdmin && selectedRoles.length === 1 ? selectedRoles[0] : undefined;\n\n const { data: usersData, isLoading: usersLoading } = useUsers({\n role: roleFilter,\n status: 'active',\n limit: 200,\n });\n\n const filteredUsers = useMemo(() => {\n const users = usersData?.users ?? [];\n if (!search.trim()) return users;\n const q = search.toLowerCase();\n return users.filter(\n (u) =>\n u.display_name?.toLowerCase().includes(q) ||\n u.email?.toLowerCase().includes(q),\n );\n }, [usersData?.users, search]);\n\n const handleClose = () => {\n setStep('user');\n setSelectedUser(null);\n setSearch('');\n setDuration('30');\n setCustomMinutes(0);\n onClose();\n };\n\n const handleSelectUser = (user: LTUserRecord) => {\n setSelectedUser(user);\n setStep('duration');\n };\n\n const handleBack = () => {\n setStep('user');\n };\n\n const handleSubmit = () => {\n if (!selectedUser) return;\n const minutes = duration === 'custom' ? customMinutes : parseInt(duration);\n if (!minutes || minutes <= 0) return;\n onSubmit(selectedUser.id, minutes);\n };\n\n return (\n <Modal open={open} onClose={handleClose} title=\"Assign Escalations\">\n <div className=\"space-y-4\">\n {step === 'user' && (\n <>\n <p className=\"text-sm text-text-secondary\">\n Select a user to assign{' '}\n <span className=\"font-medium text-text-primary\">\n {selectedCount}\n </span>{' '}\n escalation(s) to:\n </p>\n\n <input\n type=\"text\"\n value={search}\n onChange={(e) => setSearch(e.target.value)}\n placeholder=\"Search by name or email...\"\n className=\"input text-xs w-full\"\n autoFocus\n />\n\n <div className=\"max-h-60 overflow-y-auto border border-surface-border rounded-lg divide-y divide-surface-border\">\n {usersLoading && (\n <p className=\"text-xs text-text-tertiary p-3\">\n Loading users...\n </p>\n )}\n {!usersLoading && filteredUsers.length === 0 && (\n <p className=\"text-xs text-text-tertiary p-3\">\n No users found\n </p>\n )}\n {filteredUsers.map((user) => (\n <button\n key={user.id}\n onClick={() => handleSelectUser(user)}\n className=\"w-full text-left px-3 py-2 hover:bg-surface-hover transition-colors\"\n >\n <p className=\"text-xs font-medium text-text-primary\">\n {user.display_name || user.external_id}\n </p>\n {user.email && (\n <p className=\"text-[10px] text-text-tertiary\">\n {user.email}\n </p>\n )}\n </button>\n ))}\n </div>\n\n <div className=\"flex justify-end pt-2\">\n <button onClick={handleClose} className=\"btn-secondary text-xs\">\n Cancel\n </button>\n </div>\n </>\n )}\n\n {step === 'duration' && selectedUser && (\n <>\n <p className=\"text-sm text-text-secondary\">\n Assign to{' '}\n <span className=\"font-medium text-text-primary\">\n {selectedUser.display_name || selectedUser.external_id}\n </span>{' '}\n for:\n </p>\n\n <select\n value={duration}\n onChange={(e) => { setDuration(e.target.value); setCustomMinutes(0); }}\n className=\"select w-full text-sm\"\n >\n {claimDurations.map((opt) => (\n <option key={opt.value} value={opt.value}>\n {opt.label}\n </option>\n ))}\n <option value=\"custom\">Other...</option>\n </select>\n {duration === 'custom' && (\n <CustomDurationPicker onChange={onCustomChange} autoFocus />\n )}\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button onClick={handleBack} className=\"btn-secondary text-xs\">\n Back\n </button>\n <button\n onClick={handleSubmit}\n disabled={isPending}\n className=\"btn-primary text-xs\"\n >\n {isPending ? 'Assigning...' : 'Assign'}\n </button>\n </div>\n </>\n )}\n </div>\n </Modal>\n );\n}\n","import { useState } from 'react';\nimport { Modal } from './Modal';\n\ninterface BulkTriageModalProps {\n open: boolean;\n onClose: () => void;\n selectedCount: number;\n onSubmit: (hint?: string) => void;\n isPending: boolean;\n}\n\nexport function BulkTriageModal({\n open,\n onClose,\n selectedCount,\n onSubmit,\n isPending,\n}: BulkTriageModalProps) {\n const [hint, setHint] = useState('');\n\n const handleSubmit = () => {\n onSubmit(hint || undefined);\n };\n\n return (\n <Modal open={open} onClose={onClose} title=\"Submit for AI Triage\">\n <div className=\"space-y-4\">\n <p className=\"text-sm text-text-secondary\">\n Submit <span className=\"font-medium text-text-primary\">{selectedCount}</span> escalation(s)\n for AI triage? The triage orchestrator will take over resolution.\n </p>\n\n <div>\n <label className=\"text-xs font-medium text-text-secondary block mb-1\">\n Triage hint <span className=\"text-text-tertiary\">(optional)</span>\n </label>\n <input\n type=\"text\"\n value={hint}\n onChange={(e) => setHint(e.target.value)}\n placeholder=\"e.g., image_orientation\"\n className=\"input text-xs font-mono w-full\"\n />\n <p className=\"text-[10px] text-text-tertiary mt-1\">\n Guides the triage workflow on what remediation to apply\n </p>\n </div>\n\n <div className=\"flex justify-end gap-3 pt-2\">\n <button onClick={onClose} className=\"btn-secondary text-xs\">\n Cancel\n </button>\n <button\n onClick={handleSubmit}\n disabled={isPending}\n className=\"btn-primary text-xs\"\n >\n {isPending ? 'Submitting...' : 'Submit for Triage'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n","import type { UseMutationResult } from '@tanstack/react-query';\n\n/**\n * Create a bulk-action handler that calls a mutation and clears selection on success.\n */\nfunction bulkHandler<TData, TVariables>(\n mutation: UseMutationResult<TData, Error, TVariables>,\n clearSelection: () => void,\n afterSuccess?: () => void,\n) {\n return (variables: TVariables) => {\n mutation.mutate(variables, {\n onSuccess: () => {\n clearSelection();\n afterSuccess?.();\n },\n });\n };\n}\n\nexport function createBulkHandlers(deps: {\n selectedIds: Set<string>;\n clearSelection: () => void;\n setPriority: UseMutationResult<{ updated: number }, Error, { ids: string[]; priority: 1 | 2 | 3 | 4 }>;\n bulkClaim: UseMutationResult<{ claimed: number; skipped: number }, Error, { ids: string[]; durationMinutes: number }>;\n bulkEscalate: UseMutationResult<{ updated: number }, Error, { ids: string[]; targetRole: string }>;\n bulkTriage: UseMutationResult<{ triaged: number }, Error, { ids: string[]; hint?: string }>;\n bulkAssign: UseMutationResult<{ assigned: number; skipped: number }, Error, { ids: string[]; targetUserId: string; durationMinutes: number }>;\n bulkCancel: UseMutationResult<{ cancelled: number; skipped: number }, Error, { ids: string[] }>;\n closeTriageModal: () => void;\n closeAssignModal: () => void;\n closeCancelModal: () => void;\n}) {\n const ids = () => [...deps.selectedIds];\n\n const handleSetPriority = (priority: 1 | 2 | 3 | 4) => {\n bulkHandler(\n deps.setPriority,\n deps.clearSelection,\n )({ ids: ids(), priority });\n };\n\n const handleBulkClaim = (durationMinutes: number) => {\n bulkHandler(\n deps.bulkClaim,\n deps.clearSelection,\n )({ ids: ids(), durationMinutes });\n };\n\n const handleBulkEscalate = (targetRole: string) => {\n bulkHandler(\n deps.bulkEscalate,\n deps.clearSelection,\n )({ ids: ids(), targetRole });\n };\n\n const handleBulkTriage = (hint?: string) => {\n bulkHandler(\n deps.bulkTriage,\n deps.clearSelection,\n deps.closeTriageModal,\n )({ ids: ids(), hint });\n };\n\n const handleBulkAssign = (targetUserId: string, durationMinutes: number) => {\n bulkHandler(\n deps.bulkAssign,\n deps.clearSelection,\n deps.closeAssignModal,\n )({ ids: ids(), targetUserId, durationMinutes });\n };\n\n const handleBulkCancel = () => {\n bulkHandler(\n deps.bulkCancel,\n deps.clearSelection,\n deps.closeCancelModal,\n )({ ids: ids() });\n };\n\n return { handleSetPriority, handleBulkClaim, handleBulkEscalate, handleBulkTriage, handleBulkAssign, handleBulkCancel };\n}\n","import { Modal } from '../../components/common/modal/Modal';\nimport { CustomDurationPicker } from '../../components/common/form/CustomDurationPicker';\nimport type { LTEscalationRecord } from '../../api/types';\n\ninterface ClaimModalProps {\n claimTarget: LTEscalationRecord | null;\n onClose: () => void;\n claimDuration: string;\n onDurationChange: (value: string) => void;\n claimDurations: Array<{ value: string; label: string }>;\n customClaimMinutes: number;\n onCustomClaimChange: (minutes: number) => void;\n onClaim: () => void;\n isPending: boolean;\n}\n\nexport function ClaimModal({\n claimTarget,\n onClose,\n claimDuration,\n onDurationChange,\n claimDurations,\n onCustomClaimChange,\n onClaim,\n isPending,\n}: ClaimModalProps) {\n return (\n <Modal\n open={!!claimTarget}\n onClose={onClose}\n title=\"Claim Escalation\"\n >\n <div className=\"space-y-4\">\n <p className=\"text-sm text-text-secondary\">\n Claim <span className=\"font-medium text-text-primary\">{claimTarget?.type}</span> for:\n </p>\n <select\n value={claimDuration}\n onChange={(e) => { onDurationChange(e.target.value); }}\n className=\"select w-full text-sm\"\n >\n {claimDurations.map((opt) => (\n <option key={opt.value} value={opt.value}>{opt.label}</option>\n ))}\n <option value=\"custom\">Other...</option>\n </select>\n {claimDuration === 'custom' && (\n <CustomDurationPicker onChange={onCustomClaimChange} autoFocus />\n )}\n <div className=\"flex justify-end gap-3 pt-2\">\n <button onClick={onClose} className=\"btn-secondary text-xs\">\n Cancel\n </button>\n <button\n onClick={onClaim}\n className=\"btn-primary text-xs\"\n disabled={isPending}\n >\n {isPending ? 'Claiming...' : 'Claim'}\n </button>\n </div>\n </div>\n </Modal>\n );\n}\n","import { useState, useEffect, useMemo, useCallback } from 'react';\nimport { useNavigate } from 'react-router-dom';\nimport { useAccess } from '../../hooks/useAccess';\nimport { useEscalationListEvents } from '../../hooks/useEventHooks';\nimport {\n useEscalations,\n useAvailableEscalations,\n useEscalationTypes,\n useClaimEscalation,\n useSetEscalationPriority,\n useBulkClaimEscalations,\n useBulkAssignEscalations,\n useBulkEscalateToRole,\n useBulkTriageEscalations,\n useBulkCancelEscalations,\n} from '../../api/escalations';\nimport { ConfirmCancelModal } from '../../components/common/modal/ConfirmCancelModal';\nimport { useRoles } from '../../api/roles';\nimport { useFilterParams } from '../../hooks/useFilterParams';\nimport { useDebouncedValue } from '../../hooks/useDebouncedValue';\nimport { buildApiPath } from '../../lib/api-path';\nimport { DataTable, type Column } from '../../components/common/data/DataTable';\nimport { StickyPagination } from '../../components/common/data/StickyPagination';\nimport { PageHeader } from '../../components/common/layout/PageHeader';\nimport { BulkActionBar } from '../../components/common/modal/BulkActionBar';\nimport { BulkAssignModal } from '../../components/common/modal/BulkAssignModal';\nimport { BulkTriageModal } from '../../components/common/modal/BulkTriageModal';\nimport { useClaimDurations } from '../../hooks/useClaimDurations';\nimport { Lock } from 'lucide-react';\nimport { ESCALATION_COLUMNS, EscalationFilterBar } from './escalation-columns';\nimport { RowAction, RowActionGroup } from '../../components/common/layout/RowActions';\nimport { ListToolbar } from '../../components/common/data/ListToolbar';\nimport { createBulkHandlers } from './helpers';\nimport { ClaimModal } from './ClaimModal';\nimport type { LTEscalationRecord } from '../../api/types';\n\nexport function AvailableEscalationsPage() {\n useEscalationListEvents();\n const navigate = useNavigate();\n const { filters, setFilter, pagination, sort, setSort } = useFilterParams({\n filters: { role: '', type: '', priority: '', status: 'available', search: '' },\n });\n // Debounce so server-side search fires once the user pauses, not per keystroke.\n const debouncedSearch = useDebouncedValue(filters.search, 300);\n const claimDurations = useClaimDurations();\n const [claimTarget, setClaimTarget] = useState<LTEscalationRecord | null>(null);\n const [claimDuration, setClaimDuration] = useState('30');\n const [customClaimMinutes, setCustomClaimMinutes] = useState(0);\n const onCustomClaimChange = useCallback((m: number) => setCustomClaimMinutes(m), []);\n const [selectedIds, setSelectedIds] = useState<Set<string>>(new Set());\n const [triageModalOpen, setTriageModalOpen] = useState(false);\n const [assignModalOpen, setAssignModalOpen] = useState(false);\n const [cancelModalOpen, setCancelModalOpen] = useState(false);\n\n const claim = useClaimEscalation();\n const setPriority = useSetEscalationPriority();\n const bulkClaim = useBulkClaimEscalations();\n const bulkAssign = useBulkAssignEscalations();\n const bulkEscalate = useBulkEscalateToRole();\n const bulkTriage = useBulkTriageEscalations();\n const bulkCancel = useBulkCancelEscalations();\n const { data: rolesData } = useRoles();\n const { data: typesData } = useEscalationTypes();\n\n // Clear selections on filter/page changes\n useEffect(() => {\n setSelectedIds(new Set());\n }, [filters.role, filters.type, filters.priority, filters.status, debouncedSearch, pagination.page, pagination.pageSize]);\n\n const statusFilter = filters.status || '';\n const isAvailable = statusFilter === 'available';\n const isClaimed = statusFilter === 'claimed';\n const apiStatus = isClaimed ? 'pending'\n : statusFilter === 'resolved' ? 'resolved'\n : statusFilter === 'cancelled' ? 'cancelled'\n : isAvailable ? undefined\n : undefined;\n\n const sharedFilters = {\n role: filters.role || undefined,\n type: filters.type || undefined,\n priority: filters.priority ? parseInt(filters.priority) : undefined,\n limit: pagination.pageSize,\n offset: pagination.offset,\n sort_by: sort.sort_by || 'created_at',\n order: sort.order || 'desc',\n search: debouncedSearch || undefined,\n };\n\n const availableQuery = useAvailableEscalations({\n ...sharedFilters,\n enabled: isAvailable,\n });\n\n const escalationsQuery = useEscalations({\n status: apiStatus,\n claimed: isClaimed || undefined,\n ...sharedFilters,\n enabled: !isAvailable,\n });\n\n const activeQuery = isAvailable ? availableQuery : escalationsQuery;\n const { data, isLoading, error: queryError, refetch, isFetching } = activeQuery;\n\n // Copy-URL/curl path built from the SAME params the active query sends, so the\n // generated command always reproduces the real request (filters + search + sort).\n const apiPath = buildApiPath(`/escalations${isAvailable ? '/available' : ''}`, {\n status: apiStatus,\n claimed: isClaimed || undefined,\n role: filters.role || undefined,\n type: filters.type || undefined,\n priority: filters.priority || undefined,\n search: debouncedSearch || undefined,\n sort_by: sort.sort_by || 'created_at',\n order: sort.order || 'desc',\n limit: pagination.pageSize,\n offset: pagination.offset,\n });\n\n // Search is server-side (full result set), so results and total come straight\n // from the query — no client-side filtering of the current page.\n const escalations = data?.escalations ?? [];\n const total = data?.total ?? 0;\n const { canBulk: canBulkManage } = useAccess();\n\n const selectedRoles = useMemo(() => {\n const roles = new Set<string>();\n for (const esc of escalations) {\n if (selectedIds.has(esc.id)) roles.add(esc.role);\n }\n return [...roles];\n }, [escalations, selectedIds]);\n\n const clearSelection = useCallback(() => setSelectedIds(new Set()), []);\n\n const handleClaim = () => {\n if (!claimTarget) return;\n const minutes = claimDuration === 'custom' ? customClaimMinutes : parseInt(claimDuration);\n if (!minutes || minutes <= 0) return;\n claim.mutate(\n { id: claimTarget.id, durationMinutes: minutes },\n {\n onSuccess: () => {\n setClaimTarget(null);\n navigate(`/escalations/detail/${claimTarget.id}`);\n },\n },\n );\n };\n\n const {\n handleSetPriority,\n handleBulkClaim,\n handleBulkEscalate,\n handleBulkTriage,\n handleBulkAssign,\n handleBulkCancel,\n } = createBulkHandlers({\n selectedIds,\n clearSelection,\n setPriority,\n bulkClaim,\n bulkEscalate,\n bulkTriage,\n bulkAssign,\n bulkCancel,\n closeTriageModal: () => setTriageModalOpen(false),\n closeAssignModal: () => setAssignModalOpen(false),\n closeCancelModal: () => setCancelModalOpen(false),\n });\n\n const toggleSelect = (id: string) => {\n const next = new Set(selectedIds);\n if (next.has(id)) next.delete(id);\n else next.add(id);\n setSelectedIds(next);\n };\n\n const toggleAll = () => {\n if (selectedIds.size === escalations.length) {\n setSelectedIds(new Set());\n } else {\n setSelectedIds(new Set(escalations.map((e) => e.id)));\n }\n };\n\n const columns: Column<LTEscalationRecord>[] = [\n canBulkManage ? {\n key: 'select',\n label: (\n <input\n type=\"checkbox\"\n checked={escalations.length > 0 && selectedIds.size === escalations.length}\n onChange={toggleAll}\n className=\"rounded\"\n />\n ) as any,\n render: (row: LTEscalationRecord) => (\n <input\n type=\"checkbox\"\n checked={selectedIds.has(row.id)}\n onChange={(e) => {\n e.stopPropagation();\n toggleSelect(row.id);\n }}\n onClick={(e) => e.stopPropagation()}\n className=\"rounded\"\n />\n ),\n className: 'w-10',\n } : {\n key: 'spacer',\n label: '',\n render: () => null,\n className: 'w-10',\n },\n ...ESCALATION_COLUMNS,\n {\n key: 'actions',\n label: '',\n render: (row) => (\n <RowActionGroup>\n <RowAction\n icon={Lock}\n title=\"Claim escalation\"\n onClick={() => setClaimTarget(row)}\n />\n </RowActionGroup>\n ),\n className: 'w-16 text-right',\n },\n ];\n\n return (\n <div>\n <PageHeader title=\"Available Escalations\" docsHash=\"#docs:dashboard.md:all-escalations\" />\n\n <EscalationFilterBar\n filters={filters}\n setFilter={setFilter}\n roles={rolesData?.roles ?? []}\n types={typesData?.types ?? []}\n showStatus\n actions={\n <ListToolbar\n onRefresh={() => refetch()}\n isFetching={isFetching}\n apiPath={apiPath}\n />\n }\n />\n\n {selectedIds.size > 0 && (\n <BulkActionBar\n selectedCount={selectedIds.size}\n onClearSelection={() => setSelectedIds(new Set())}\n onSetPriority={handleSetPriority}\n onClaim={handleBulkClaim}\n onAssign={() => setAssignModalOpen(true)}\n onEscalate={handleBulkEscalate}\n onTriage={() => setTriageModalOpen(true)}\n onCancel={() => setCancelModalOpen(true)}\n isPriorityPending={setPriority.isPending}\n isClaimPending={bulkClaim.isPending}\n isAssignPending={bulkAssign.isPending}\n isEscalatePending={bulkEscalate.isPending}\n isTriagePending={bulkTriage.isPending}\n isCancelPending={bulkCancel.isPending}\n availableRoles={rolesData?.roles ?? []}\n />\n )}\n\n {queryError && (\n <div className=\"mb-4 px-4 py-3 rounded-md bg-status-error/10 border border-status-error/20 text-xs text-status-error\">\n {(queryError as Error).message === 'Session expired'\n ? 'Your session has expired. Please log in again.'\n : `Failed to load escalations: ${(queryError as Error).message}`}\n </div>\n )}\n\n <DataTable\n columns={columns}\n data={escalations}\n keyFn={(row) => row.id}\n onRowClick={(row) => navigate(`/escalations/detail/${row.id}`, { state: { from: '/escalations/available' } })}\n isLoading={isLoading}\n emptyMessage={queryError ? 'Unable to load data' : 'No available escalations'}\n sort={sort}\n onSort={setSort}\n />\n\n <StickyPagination\n page={pagination.page}\n totalPages={pagination.totalPages(total)}\n onPageChange={pagination.setPage}\n total={total}\n pageSize={pagination.pageSize}\n onPageSizeChange={pagination.setPageSize}\n />\n\n <ClaimModal\n claimTarget={claimTarget}\n onClose={() => setClaimTarget(null)}\n claimDuration={claimDuration}\n onDurationChange={(v) => { setClaimDuration(v); setCustomClaimMinutes(0); }}\n claimDurations={claimDurations}\n customClaimMinutes={customClaimMinutes}\n onCustomClaimChange={onCustomClaimChange}\n onClaim={handleClaim}\n isPending={claim.isPending}\n />\n\n <BulkTriageModal\n open={triageModalOpen}\n onClose={() => setTriageModalOpen(false)}\n selectedCount={selectedIds.size}\n onSubmit={handleBulkTriage}\n isPending={bulkTriage.isPending}\n />\n\n <BulkAssignModal\n open={assignModalOpen}\n onClose={() => setAssignModalOpen(false)}\n selectedCount={selectedIds.size}\n selectedRoles={selectedRoles}\n onSubmit={handleBulkAssign}\n isPending={bulkAssign.isPending}\n />\n\n <ConfirmCancelModal\n open={cancelModalOpen}\n onClose={() => setCancelModalOpen(false)}\n onConfirm={handleBulkCancel}\n selectedCount={selectedIds.size}\n isPending={bulkCancel.isPending}\n error={bulkCancel.error as Error | null}\n />\n </div>\n );\n}\n"],"names":["anyPending","props","BulkActionBar","disabled","claimDurations","useClaimDurations","jsxs","jsx","e","PRIORITY_OPTIONS","opt","role","BulkAssignModal","open","onClose","selectedCount","selectedRoles","onSubmit","isPending","isSuperAdmin","useAuth","step","setStep","useState","selectedUser","setSelectedUser","search","setSearch","duration","setDuration","customMinutes","setCustomMinutes","onCustomChange","useCallback","m","roleFilter","usersData","usersLoading","useUsers","filteredUsers","useMemo","users","q","u","_a","_b","handleClose","handleSelectUser","user","handleBack","handleSubmit","minutes","Modal","Fragment","CustomDurationPicker","BulkTriageModal","hint","setHint","bulkHandler","mutation","clearSelection","afterSuccess","variables","createBulkHandlers","deps","ids","priority","durationMinutes","targetRole","targetUserId","ClaimModal","claimTarget","claimDuration","onDurationChange","onCustomClaimChange","onClaim","AvailableEscalationsPage","useEscalationListEvents","navigate","useNavigate","filters","setFilter","pagination","sort","setSort","useFilterParams","debouncedSearch","useDebouncedValue","setClaimTarget","setClaimDuration","customClaimMinutes","setCustomClaimMinutes","selectedIds","setSelectedIds","triageModalOpen","setTriageModalOpen","assignModalOpen","setAssignModalOpen","cancelModalOpen","setCancelModalOpen","claim","useClaimEscalation","setPriority","useSetEscalationPriority","bulkClaim","useBulkClaimEscalations","bulkAssign","useBulkAssignEscalations","bulkEscalate","useBulkEscalateToRole","bulkTriage","useBulkTriageEscalations","bulkCancel","useBulkCancelEscalations","rolesData","useRoles","typesData","useEscalationTypes","useEffect","statusFilter","isAvailable","isClaimed","apiStatus","sharedFilters","availableQuery","useAvailableEscalations","escalationsQuery","useEscalations","activeQuery","data","isLoading","queryError","refetch","isFetching","apiPath","buildApiPath","escalations","total","canBulkManage","useAccess","roles","esc","handleClaim","handleSetPriority","handleBulkClaim","handleBulkEscalate","handleBulkTriage","handleBulkAssign","handleBulkCancel","toggleSelect","id","next","toggleAll","columns","row","ESCALATION_COLUMNS","RowActionGroup","RowAction","Lock","PageHeader","EscalationFilterBar","ListToolbar","DataTable","StickyPagination","v","ConfirmCancelModal"],"mappings":"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"}
@@ -1,2 +0,0 @@
1
- import{a as x,j as t}from"./vendor-query-B2UbickB.js";import{c as d}from"./vendor-react-CXumBFUA.js";const u=20;function h(e,o=u){if(e.length<=o)return e;const r=Math.floor((o-1)/2);return`${e.slice(0,r)}…${e.slice(-r)}`}function j({label:e,value:o,href:r,external:c}){const[s,i]=x.useState(!1),l=d();if(!o)return null;const a=n=>{n.stopPropagation(),navigator.clipboard.writeText(o),i(!0),setTimeout(()=>i(!1),1500)},p=n=>{n.stopPropagation(),r&&(c?window.open(r,"_blank","noopener,noreferrer"):l(r))};return t.jsxs("div",{className:"text-left group relative",children:[t.jsx("span",{className:"text-[11px] font-medium text-text-secondary uppercase tracking-wide",children:e}),t.jsxs("span",{className:"flex items-center gap-1 mt-0.5",children:[t.jsx("button",{onClick:a,title:o,className:"text-[12px] font-mono text-text-primary group-hover:text-accent transition-colors",children:h(o)}),t.jsx("button",{onClick:a,title:"Copy",className:"opacity-0 group-hover:opacity-100 transition-opacity shrink-0 p-0.5",children:t.jsx("svg",{className:`w-3 h-3 transition-colors ${s?"text-status-success":"text-text-tertiary hover:text-text-primary"}`,fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:s?t.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M5 13l4 4L19 7"}):t.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3"})})}),r&&t.jsx("button",{onClick:p,title:`View ${e}`,className:"opacity-0 group-hover:opacity-100 transition-opacity shrink-0 p-0.5",children:t.jsx("svg",{className:"w-3 h-3 text-text-tertiary hover:text-accent",fill:"none",viewBox:"0 0 24 24",stroke:"currentColor",strokeWidth:2,children:t.jsx("path",{strokeLinecap:"round",strokeLinejoin:"round",d:"M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101M10.172 13.828a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1"})})})]})]})}export{j as C};
2
- //# sourceMappingURL=CopyableId-DaT0ZRHg.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"CopyableId-DaT0ZRHg.js","sources":["../../src/components/common/display/CopyableId.tsx"],"sourcesContent":["import { useState } from 'react';\nimport { useNavigate } from 'react-router-dom';\n\nconst ELLIPSIS_MAX = 20;\n\nfunction centerEllipsis(s: string, max = ELLIPSIS_MAX): string {\n if (s.length <= max) return s;\n const side = Math.floor((max - 1) / 2);\n return `${s.slice(0, side)}…${s.slice(-side)}`;\n}\n\ninterface CopyableIdProps {\n label: string;\n value: string | null | undefined;\n href?: string;\n external?: boolean;\n}\n\nexport function CopyableId({ label, value, href, external }: CopyableIdProps) {\n const [copied, setCopied] = useState(false);\n const navigate = useNavigate();\n if (!value) return null;\n\n const handleCopy = (e: React.MouseEvent) => {\n e.stopPropagation();\n navigator.clipboard.writeText(value);\n setCopied(true);\n setTimeout(() => setCopied(false), 1500);\n };\n\n const handleNavigate = (e: React.MouseEvent) => {\n e.stopPropagation();\n if (!href) return;\n if (external) {\n window.open(href, '_blank', 'noopener,noreferrer');\n } else {\n navigate(href);\n }\n };\n\n return (\n <div className=\"text-left group relative\">\n <span className=\"text-[11px] font-medium text-text-secondary uppercase tracking-wide\">{label}</span>\n <span className=\"flex items-center gap-1 mt-0.5\">\n <button\n onClick={handleCopy}\n title={value}\n className=\"text-[12px] font-mono text-text-primary group-hover:text-accent transition-colors\"\n >\n {centerEllipsis(value)}\n </button>\n <button onClick={handleCopy} title=\"Copy\" className=\"opacity-0 group-hover:opacity-100 transition-opacity shrink-0 p-0.5\">\n <svg className={`w-3 h-3 transition-colors ${copied ? 'text-status-success' : 'text-text-tertiary hover:text-text-primary'}`} fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n {copied\n ? <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M5 13l4 4L19 7\" />\n : <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M8 5H6a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2v-1M8 5a2 2 0 002 2h2a2 2 0 002-2M8 5a2 2 0 012-2h2a2 2 0 012 2m0 0h2a2 2 0 012 2v3\" />\n }\n </svg>\n </button>\n {href && (\n <button onClick={handleNavigate} title={`View ${label}`} className=\"opacity-0 group-hover:opacity-100 transition-opacity shrink-0 p-0.5\">\n <svg className=\"w-3 h-3 text-text-tertiary hover:text-accent\" fill=\"none\" viewBox=\"0 0 24 24\" stroke=\"currentColor\" strokeWidth={2}>\n <path strokeLinecap=\"round\" strokeLinejoin=\"round\" d=\"M13.828 10.172a4 4 0 00-5.656 0l-4 4a4 4 0 105.656 5.656l1.102-1.101M10.172 13.828a4 4 0 005.656 0l4-4a4 4 0 00-5.656-5.656l-1.1 1.1\" />\n </svg>\n </button>\n )}\n </span>\n </div>\n );\n}\n"],"names":["ELLIPSIS_MAX","centerEllipsis","s","max","side","CopyableId","label","value","href","external","copied","setCopied","useState","navigate","useNavigate","handleCopy","e","handleNavigate","jsxs","jsx"],"mappings":"qGAGA,MAAMA,EAAe,GAErB,SAASC,EAAeC,EAAWC,EAAMH,EAAsB,CAC7D,GAAIE,EAAE,QAAUC,EAAK,OAAOD,EAC5B,MAAME,EAAO,KAAK,OAAOD,EAAM,GAAK,CAAC,EACrC,MAAO,GAAGD,EAAE,MAAM,EAAGE,CAAI,CAAC,IAAIF,EAAE,MAAM,CAACE,CAAI,CAAC,EAC9C,CASO,SAASC,EAAW,CAAE,MAAAC,EAAO,MAAAC,EAAO,KAAAC,EAAM,SAAAC,GAA6B,CAC5E,KAAM,CAACC,EAAQC,CAAS,EAAIC,EAAAA,SAAS,EAAK,EACpCC,EAAWC,EAAA,EACjB,GAAI,CAACP,EAAO,OAAO,KAEnB,MAAMQ,EAAcC,GAAwB,CAC1CA,EAAE,gBAAA,EACF,UAAU,UAAU,UAAUT,CAAK,EACnCI,EAAU,EAAI,EACd,WAAW,IAAMA,EAAU,EAAK,EAAG,IAAI,CACzC,EAEMM,EAAkBD,GAAwB,CAC9CA,EAAE,gBAAA,EACGR,IACDC,EACF,OAAO,KAAKD,EAAM,SAAU,qBAAqB,EAEjDK,EAASL,CAAI,EAEjB,EAEA,OACEU,EAAAA,KAAC,MAAA,CAAI,UAAU,2BACb,SAAA,CAAAC,EAAAA,IAAC,OAAA,CAAK,UAAU,sEAAuE,SAAAb,EAAM,EAC7FY,EAAAA,KAAC,OAAA,CAAK,UAAU,iCACd,SAAA,CAAAC,EAAAA,IAAC,SAAA,CACC,QAASJ,EACT,MAAOR,EACP,UAAU,oFAET,WAAeA,CAAK,CAAA,CAAA,QAEtB,SAAA,CAAO,QAASQ,EAAY,MAAM,OAAO,UAAU,sEAClD,SAAAI,EAAAA,IAAC,OAAI,UAAW,6BAA6BT,EAAS,sBAAwB,4CAA4C,GAAI,KAAK,OAAO,QAAQ,YAAY,OAAO,eAAe,YAAa,EAC9L,WACGS,MAAC,OAAA,CAAK,cAAc,QAAQ,eAAe,QAAQ,EAAE,iBAAiB,EACtEA,MAAC,OAAA,CAAK,cAAc,QAAQ,eAAe,QAAQ,EAAE,uIAAuI,EAElM,CAAA,CACF,EACCX,GACCW,EAAAA,IAAC,SAAA,CAAO,QAASF,EAAgB,MAAO,QAAQX,CAAK,GAAI,UAAU,sEACjE,SAAAa,EAAAA,IAAC,MAAA,CAAI,UAAU,+CAA+C,KAAK,OAAO,QAAQ,YAAY,OAAO,eAAe,YAAa,EAC/H,SAAAA,EAAAA,IAAC,OAAA,CAAK,cAAc,QAAQ,eAAe,QAAQ,EAAE,sIAAA,CAAuI,EAC9L,CAAA,CACF,CAAA,CAAA,CAEJ,CAAA,EACF,CAEJ"}
@@ -1,2 +0,0 @@
1
- import{j as s}from"./vendor-query-B2UbickB.js";import{j as v,v as C,q as L,s as R}from"./index-D1xVY4JR.js";import{f as F}from"./useEventHooks-a7Dnh8G4.js";import{u as N}from"./roles-c5cV2QYQ.js";import{u as T}from"./useFilterParams-x-Dg0Vgz.js";import{u as _}from"./useDebouncedValue-BA5S7V54.js";import{b as k}from"./api-path-u2Jf9QXS.js";import{D as z}from"./DataTable-D9yuBv0w.js";import{P as A}from"./PageHeader-Dk2Pe44Q.js";import{S as w}from"./StickyPagination-BWhFSr2d.js";import{R as M,a as O}from"./RowActions-Dg-Fsm5O.js";import{a as I,T as q,E as U}from"./escalation-columns-nVYVt7Gl.js";import{L as D}from"./ListToolbar-BJaCzAXS.js";import{a4 as $}from"./vendor-icons-D5ycKPA2.js";import{c as B}from"./vendor-react-CXumBFUA.js";import"./EmptyState-BcsfPq9T.js";import"./FilterBar-Ck4K4rzu.js";import"./PriorityBadge-DfQY9St9.js";import"./RolePill-Du-aNnjR.js";import"./WorkflowPill-CS-IsR0U.js";import"./TimestampCell--tnreI1i.js";import"./escalation-iI4QKlP-.js";function de(){F();const d=B(),{user:i}=v(),{filters:t,setFilter:u,pagination:e,sort:n,setSort:g}=T({filters:{role:"",type:"",priority:"",search:""}}),f=_(t.search,300),x=C(),{data:l}=N(),{data:c}=L(),m={assigned_to:i==null?void 0:i.userId,status:"pending",role:t.role||void 0,type:t.type||void 0,priority:t.priority?parseInt(t.priority):void 0,search:f||void 0,sort_by:n.sort_by||"created_at",order:n.order||"desc"},{data:o,isLoading:y,error:r,refetch:h,isFetching:b}=R({...m,limit:e.pageSize,offset:e.offset}),S=(o==null?void 0:o.escalations)??[],p=(o==null?void 0:o.total)??0,E=k("/escalations",{...m,limit:e.pageSize,offset:e.offset}),P={key:"actions",label:"",render:a=>s.jsx(M,{children:s.jsx(O,{icon:$,title:"Release escalation",onClick:()=>x.mutate(a.id),colorClass:"text-text-tertiary hover:text-status-warning"})}),className:"w-16 text-right"},j=[q,...U,P];return s.jsxs("div",{children:[s.jsx(A,{title:"My Escalations"}),s.jsx(I,{filters:t,setFilter:u,roles:(l==null?void 0:l.roles)??[],types:(c==null?void 0:c.types)??[],actions:s.jsx(D,{onRefresh:()=>h(),isFetching:b,apiPath:E})}),r&&s.jsx("div",{className:"mb-4 px-4 py-3 rounded-md bg-status-error/10 border border-status-error/20 text-xs text-status-error",children:r.message==="Session expired"?"Your session has expired. Please log in again.":`Failed to load escalations: ${r.message}`}),s.jsx(z,{columns:j,data:S,keyFn:a=>a.id,onRowClick:a=>d(`/escalations/detail/${a.id}`,{state:{from:"/escalations/queue"}}),isLoading:y,emptyMessage:r?"Unable to load data":"No assigned escalations",sort:n,onSort:g}),s.jsx(w,{page:e.page,totalPages:e.totalPages(p),onPageChange:e.setPage,total:p,pageSize:e.pageSize,onPageSizeChange:e.setPageSize})]})}export{de as OperatorDashboard};
2
- //# sourceMappingURL=OperatorDashboard-g9o7I925.js.map
@@ -1,2 +0,0 @@
1
- import{j as e}from"./vendor-query-B2UbickB.js";import{B as r}from"./vendor-icons-D5ycKPA2.js";function l({title:a,actions:t,docsHash:s}){return e.jsxs("div",{className:"flex items-center gap-4 mb-10",children:[e.jsxs("div",{className:"flex items-center gap-2 flex-1",children:[e.jsx("h1",{className:"text-3xl font-light text-text-primary",children:a}),s&&e.jsx("button",{onClick:()=>{window.location.hash=s},className:"text-text-quaternary hover:text-accent transition-colors mt-1",title:"Open docs for this page",children:e.jsx(r,{className:"w-4 h-4",strokeWidth:1.5})})]}),t&&e.jsx("div",{className:"flex items-center gap-3",children:t})]})}export{l as P};
2
- //# sourceMappingURL=PageHeader-Dk2Pe44Q.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PageHeader-Dk2Pe44Q.js","sources":["../../src/components/common/layout/PageHeader.tsx"],"sourcesContent":["import type { ReactNode } from 'react';\nimport { BookOpen } from 'lucide-react';\n\ninterface PageHeaderProps {\n title: string;\n actions?: ReactNode;\n /** Hash link to open the docs drawer to a specific section, e.g. \"#docs=dashboard.md#workflow-registry\" */\n docsHash?: string;\n}\n\nexport function PageHeader({ title, actions, docsHash }: PageHeaderProps) {\n return (\n <div className=\"flex items-center gap-4 mb-10\">\n <div className=\"flex items-center gap-2 flex-1\">\n <h1 className=\"text-3xl font-light text-text-primary\">{title}</h1>\n {docsHash && (\n <button\n onClick={() => { window.location.hash = docsHash; }}\n className=\"text-text-quaternary hover:text-accent transition-colors mt-1\"\n title=\"Open docs for this page\"\n >\n <BookOpen className=\"w-4 h-4\" strokeWidth={1.5} />\n </button>\n )}\n </div>\n {actions && <div className=\"flex items-center gap-3\">{actions}</div>}\n </div>\n );\n}\n"],"names":["PageHeader","title","actions","docsHash","jsxs","jsx","BookOpen"],"mappings":"8FAUO,SAASA,EAAW,CAAE,MAAAC,EAAO,QAAAC,EAAS,SAAAC,GAA6B,CACxE,OACEC,EAAAA,KAAC,MAAA,CAAI,UAAU,gCACb,SAAA,CAAAA,EAAAA,KAAC,MAAA,CAAI,UAAU,iCACb,SAAA,CAAAC,EAAAA,IAAC,KAAA,CAAG,UAAU,wCAAyC,SAAAJ,EAAM,EAC5DE,GACCE,EAAAA,IAAC,SAAA,CACC,QAAS,IAAM,CAAE,OAAO,SAAS,KAAOF,CAAU,EAClD,UAAU,gEACV,MAAM,0BAEN,SAAAE,EAAAA,IAACC,EAAA,CAAS,UAAU,UAAU,YAAa,GAAA,CAAK,CAAA,CAAA,CAClD,EAEJ,EACCJ,GAAWG,EAAAA,IAAC,MAAA,CAAI,UAAU,0BAA2B,SAAAH,CAAA,CAAQ,CAAA,EAChE,CAEJ"}
@@ -1,2 +0,0 @@
1
- import{j as e}from"./vendor-query-B2UbickB.js";const r={1:"P1",2:"P2",3:"P3",4:"P4"},s={1:"text-text-primary font-semibold",2:"text-text-primary font-medium",3:"text-text-secondary",4:"text-text-tertiary"};function o({priority:t}){return e.jsx("span",{className:`text-xs ${s[t]??"text-text-secondary"}`,children:r[t]??`P${t}`})}export{o as P};
2
- //# sourceMappingURL=PriorityBadge-DfQY9St9.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"PriorityBadge-DfQY9St9.js","sources":["../../src/components/common/display/PriorityBadge.tsx"],"sourcesContent":["const priorityLabels: Record<number, string> = {\n 1: 'P1',\n 2: 'P2',\n 3: 'P3',\n 4: 'P4',\n};\n\nconst priorityStyles: Record<number, string> = {\n 1: 'text-text-primary font-semibold',\n 2: 'text-text-primary font-medium',\n 3: 'text-text-secondary',\n 4: 'text-text-tertiary',\n};\n\nexport function PriorityBadge({ priority }: { priority: number }) {\n return (\n <span className={`text-xs ${priorityStyles[priority] ?? 'text-text-secondary'}`}>\n {priorityLabels[priority] ?? `P${priority}`}\n </span>\n );\n}\n"],"names":["priorityLabels","priorityStyles","PriorityBadge","priority","jsx"],"mappings":"+CAAA,MAAMA,EAAyC,CAC7C,EAAG,KACH,EAAG,KACH,EAAG,KACH,EAAG,IACL,EAEMC,EAAyC,CAC7C,EAAG,kCACH,EAAG,gCACH,EAAG,sBACH,EAAG,oBACL,EAEO,SAASC,EAAc,CAAE,SAAAC,GAAkC,CAChE,OACEC,EAAAA,IAAC,OAAA,CAAK,UAAW,WAAWH,EAAeE,CAAQ,GAAK,qBAAqB,GAC1E,SAAAH,EAAeG,CAAQ,GAAK,IAAIA,CAAQ,GAC3C,CAEJ"}
@@ -1,2 +0,0 @@
1
- import{j as t}from"./vendor-query-B2UbickB.js";import{U as r}from"./vendor-icons-D5ycKPA2.js";function c({role:e,size:s="sm"}){const n=s==="md"?"py-0.5 text-[11px] gap-1.5":"py-0.5 text-[10px] gap-1",a=s==="md"?"w-3 h-3":"w-2.5 h-2.5";return t.jsxs("span",{className:`inline-flex items-center ${n} text-text-secondary`,children:[t.jsx(r,{className:`${a} shrink-0 text-accent/75`}),e]})}export{c as R};
2
- //# sourceMappingURL=RolePill-Du-aNnjR.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RolePill-Du-aNnjR.js","sources":["../../src/components/common/display/RolePill.tsx"],"sourcesContent":["import { User } from 'lucide-react';\n\ninterface RolePillProps {\n role: string;\n size?: 'sm' | 'md';\n}\n\nexport function RolePill({ role, size = 'sm' }: RolePillProps) {\n const sizeClass = size === 'md'\n ? 'py-0.5 text-[11px] gap-1.5'\n : 'py-0.5 text-[10px] gap-1';\n const iconClass = size === 'md' ? 'w-3 h-3' : 'w-2.5 h-2.5';\n\n return (\n <span className={`inline-flex items-center ${sizeClass} text-text-secondary`}>\n <User className={`${iconClass} shrink-0 text-accent/75`} />\n {role}\n </span>\n );\n}\n"],"names":["RolePill","role","size","sizeClass","iconClass","jsxs","jsx","User"],"mappings":"8FAOO,SAASA,EAAS,CAAE,KAAAC,EAAM,KAAAC,EAAO,MAAuB,CAC7D,MAAMC,EAAYD,IAAS,KACvB,6BACA,2BACEE,EAAYF,IAAS,KAAO,UAAY,cAE9C,OACEG,EAAAA,KAAC,OAAA,CAAK,UAAW,4BAA4BF,CAAS,uBACpD,SAAA,CAAAG,EAAAA,IAACC,EAAA,CAAK,UAAW,GAAGH,CAAS,2BAA4B,EACxDH,CAAA,EACH,CAEJ"}
@@ -1,2 +0,0 @@
1
- import{j as o}from"./vendor-query-B2UbickB.js";function c({icon:t,title:e,onClick:i,colorClass:n="text-text-tertiary hover:text-accent",alwaysVisible:r}){return o.jsx("button",{onClick:a=>{a.stopPropagation(),i(a)},className:`transition-opacity ${r?"opacity-100":"opacity-0 group-hover/row:opacity-100"} ${n}`,title:e,children:o.jsx(t,{className:"w-[18px] h-[18px]",strokeWidth:1.5})})}function p({children:t}){return o.jsx("span",{className:"flex items-center justify-end gap-2.5",children:t})}export{p as R,c as a};
2
- //# sourceMappingURL=RowActions-Dg-Fsm5O.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"RowActions-Dg-Fsm5O.js","sources":["../../src/components/common/layout/RowActions.tsx"],"sourcesContent":["import type { LucideIcon } from 'lucide-react';\nimport type { ReactNode } from 'react';\n\n/**\n * Ephemeral icon button that appears on row hover.\n * Uses `group-hover/row:opacity-100` to show on hover — rows must have `group/row`.\n */\nexport function RowAction({\n icon: Icon,\n title,\n onClick,\n colorClass = 'text-text-tertiary hover:text-accent',\n alwaysVisible,\n}: {\n icon: LucideIcon;\n title: string;\n onClick: (e: React.MouseEvent) => void;\n colorClass?: string;\n alwaysVisible?: boolean;\n}) {\n return (\n <button\n onClick={(e) => {\n e.stopPropagation();\n onClick(e);\n }}\n className={`transition-opacity ${\n alwaysVisible ? 'opacity-100' : 'opacity-0 group-hover/row:opacity-100'\n } ${colorClass}`}\n title={title}\n >\n <Icon className=\"w-[18px] h-[18px]\" strokeWidth={1.5} />\n </button>\n );\n}\n\n/**\n * Container for one or more RowAction icons in the last table column.\n */\nexport function RowActionGroup({ children }: { children: ReactNode }) {\n return (\n <span className=\"flex items-center justify-end gap-2.5\">\n {children}\n </span>\n );\n}\n"],"names":["RowAction","Icon","title","onClick","colorClass","alwaysVisible","jsx","e","RowActionGroup","children"],"mappings":"+CAOO,SAASA,EAAU,CACxB,KAAMC,EACN,MAAAC,EACA,QAAAC,EACA,WAAAC,EAAa,uCACb,cAAAC,CACF,EAMG,CACD,OACEC,EAAAA,IAAC,SAAA,CACC,QAAUC,GAAM,CACdA,EAAE,gBAAA,EACFJ,EAAQI,CAAC,CACX,EACA,UAAW,sBACTF,EAAgB,cAAgB,uCAClC,IAAID,CAAU,GACd,MAAAF,EAEA,SAAAI,EAAAA,IAACL,EAAA,CAAK,UAAU,oBAAoB,YAAa,GAAA,CAAK,CAAA,CAAA,CAG5D,CAKO,SAASO,EAAe,CAAE,SAAAC,GAAqC,CACpE,OACEH,EAAAA,IAAC,OAAA,CAAK,UAAU,wCACb,SAAAG,CAAA,CACH,CAEJ"}