@hotmeshio/long-tail 0.16.0 → 0.16.2

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 (184) hide show
  1. package/build/shared/form-validation/field-validator.d.ts +12 -0
  2. package/build/shared/form-validation/field-validator.js +31 -3
  3. package/build/shared/form-validation/x-lt-options.d.ts +7 -2
  4. package/build/shared/form-validation/x-lt-options.js +32 -10
  5. package/dashboard/dist/assets/{AdminDashboard-BVgjY4zN.js → AdminDashboard-BrOvvDHF.js} +2 -2
  6. package/dashboard/dist/assets/{AdminDashboard-BVgjY4zN.js.map → AdminDashboard-BrOvvDHF.js.map} +1 -1
  7. package/dashboard/dist/assets/{AgentConfigPage-ChL59YL5.js → AgentConfigPage-CtVq8J0L.js} +2 -2
  8. package/dashboard/dist/assets/{AgentConfigPage-ChL59YL5.js.map → AgentConfigPage-CtVq8J0L.js.map} +1 -1
  9. package/dashboard/dist/assets/{AgentDetailPage-IJfgCOIF.js → AgentDetailPage-DTfTQDug.js} +2 -2
  10. package/dashboard/dist/assets/{AgentDetailPage-IJfgCOIF.js.map → AgentDetailPage-DTfTQDug.js.map} +1 -1
  11. package/dashboard/dist/assets/{AgentsPage-C7PnPXz_.js → AgentsPage-vCTc3Qvj.js} +2 -2
  12. package/dashboard/dist/assets/{AgentsPage-C7PnPXz_.js.map → AgentsPage-vCTc3Qvj.js.map} +1 -1
  13. package/dashboard/dist/assets/{AvailableEscalationsPage-C5f2rqHa.js → AvailableEscalationsPage-DJODShd7.js} +2 -2
  14. package/dashboard/dist/assets/{AvailableEscalationsPage-C5f2rqHa.js.map → AvailableEscalationsPage-DJODShd7.js.map} +1 -1
  15. package/dashboard/dist/assets/{BotPicker-CBf6bHLV.js → BotPicker-GU44inXA.js} +2 -2
  16. package/dashboard/dist/assets/{BotPicker-CBf6bHLV.js.map → BotPicker-GU44inXA.js.map} +1 -1
  17. package/dashboard/dist/assets/{BulkAssignModal-CkxoPByA.js → BulkAssignModal-ChqzG6FY.js} +2 -2
  18. package/dashboard/dist/assets/{BulkAssignModal-CkxoPByA.js.map → BulkAssignModal-ChqzG6FY.js.map} +1 -1
  19. package/dashboard/dist/assets/{CapabilitiesPage-D7USh3F0.js → CapabilitiesPage-Y2hycdPY.js} +2 -2
  20. package/dashboard/dist/assets/{CapabilitiesPage-D7USh3F0.js.map → CapabilitiesPage-Y2hycdPY.js.map} +1 -1
  21. package/dashboard/dist/assets/{CollapsibleSection-DfPpAIxa.js → CollapsibleSection-CmCwt_Rs.js} +2 -2
  22. package/dashboard/dist/assets/{CollapsibleSection-DfPpAIxa.js.map → CollapsibleSection-CmCwt_Rs.js.map} +1 -1
  23. package/dashboard/dist/assets/{CountdownTimer-DVL7WIcE.js → CountdownTimer-Bx2XXJOd.js} +2 -2
  24. package/dashboard/dist/assets/{CountdownTimer-DVL7WIcE.js.map → CountdownTimer-Bx2XXJOd.js.map} +1 -1
  25. package/dashboard/dist/assets/{CredentialsPage-BHZ5SKIu.js → CredentialsPage-CoX_PNBe.js} +2 -2
  26. package/dashboard/dist/assets/{CredentialsPage-BHZ5SKIu.js.map → CredentialsPage-CoX_PNBe.js.map} +1 -1
  27. package/dashboard/dist/assets/{CronLabel-wE8Hms6c.js → CronLabel-CVYKRipP.js} +2 -2
  28. package/dashboard/dist/assets/{CronLabel-wE8Hms6c.js.map → CronLabel-CVYKRipP.js.map} +1 -1
  29. package/dashboard/dist/assets/{ElapsedCell-B4VxKHpw.js → ElapsedCell-jRURiCRW.js} +2 -2
  30. package/dashboard/dist/assets/{ElapsedCell-B4VxKHpw.js.map → ElapsedCell-jRURiCRW.js.map} +1 -1
  31. package/dashboard/dist/assets/{EntityTimelinePanel-CFbQDHOj.js → EntityTimelinePanel-G6nzvgpl.js} +2 -2
  32. package/dashboard/dist/assets/{EntityTimelinePanel-CFbQDHOj.js.map → EntityTimelinePanel-G6nzvgpl.js.map} +1 -1
  33. package/dashboard/dist/assets/{EscalationListSchemaPage-YTgexl4f.js → EscalationListSchemaPage-D-rVTQ1m.js} +2 -2
  34. package/dashboard/dist/assets/{EscalationListSchemaPage-YTgexl4f.js.map → EscalationListSchemaPage-D-rVTQ1m.js.map} +1 -1
  35. package/dashboard/dist/assets/{EscalationSchemaPage-Sjk05eHP.js → EscalationSchemaPage-CV0R_xY9.js} +2 -2
  36. package/dashboard/dist/assets/{EscalationSchemaPage-Sjk05eHP.js.map → EscalationSchemaPage-CV0R_xY9.js.map} +1 -1
  37. package/dashboard/dist/assets/{EscalationsOverview-gHWPjUpZ.js → EscalationsOverview-DMdx15CQ.js} +2 -2
  38. package/dashboard/dist/assets/{EscalationsOverview-gHWPjUpZ.js.map → EscalationsOverview-DMdx15CQ.js.map} +1 -1
  39. package/dashboard/dist/assets/{EventTable-C9lHQ57t.js → EventTable-CIvsdt-q.js} +2 -2
  40. package/dashboard/dist/assets/{EventTable-C9lHQ57t.js.map → EventTable-CIvsdt-q.js.map} +1 -1
  41. package/dashboard/dist/assets/{FilterBar-BY--ANxv.js → FilterBar-DWYGkVRH.js} +2 -2
  42. package/dashboard/dist/assets/{FilterBar-BY--ANxv.js.map → FilterBar-DWYGkVRH.js.map} +1 -1
  43. package/dashboard/dist/assets/{GraphInvokePage-B-Oo5s-W.js → GraphInvokePage-BGHlXh4r.js} +2 -2
  44. package/dashboard/dist/assets/{GraphInvokePage-B-Oo5s-W.js.map → GraphInvokePage-BGHlXh4r.js.map} +1 -1
  45. package/dashboard/dist/assets/{HomePage-BrcjKOaJ.js → HomePage-BHOnp8SK.js} +2 -2
  46. package/dashboard/dist/assets/{HomePage-BrcjKOaJ.js.map → HomePage-BHOnp8SK.js.map} +1 -1
  47. package/dashboard/dist/assets/{ListToolbar-D5TQ4X_M.js → ListToolbar-iOgn_db5.js} +2 -2
  48. package/dashboard/dist/assets/{ListToolbar-D5TQ4X_M.js.map → ListToolbar-iOgn_db5.js.map} +1 -1
  49. package/dashboard/dist/assets/{McpOverview-jlukVWQ7.js → McpOverview-Be9sMKCA.js} +2 -2
  50. package/dashboard/dist/assets/{McpOverview-jlukVWQ7.js.map → McpOverview-Be9sMKCA.js.map} +1 -1
  51. package/dashboard/dist/assets/{McpQueryDetailPage-BWKbvJSd.js → McpQueryDetailPage-BIgpmGJN.js} +2 -2
  52. package/dashboard/dist/assets/{McpQueryDetailPage-BWKbvJSd.js.map → McpQueryDetailPage-BIgpmGJN.js.map} +1 -1
  53. package/dashboard/dist/assets/{McpQueryPage-BH2Pon4M.js → McpQueryPage-Okcxw_Um.js} +2 -2
  54. package/dashboard/dist/assets/{McpQueryPage-BH2Pon4M.js.map → McpQueryPage-Okcxw_Um.js.map} +1 -1
  55. package/dashboard/dist/assets/{McpRunDetailPage-kUnq2QSO.js → McpRunDetailPage-DT8kOfih.js} +2 -2
  56. package/dashboard/dist/assets/{McpRunDetailPage-kUnq2QSO.js.map → McpRunDetailPage-DT8kOfih.js.map} +1 -1
  57. package/dashboard/dist/assets/{McpRunsPage-BTucgt64.js → McpRunsPage-BXhoP77D.js} +2 -2
  58. package/dashboard/dist/assets/{McpRunsPage-BTucgt64.js.map → McpRunsPage-BXhoP77D.js.map} +1 -1
  59. package/dashboard/dist/assets/{OperationsPage-Cp8WL-ld.js → OperationsPage-rVhC8JPv.js} +2 -2
  60. package/dashboard/dist/assets/{OperationsPage-Cp8WL-ld.js.map → OperationsPage-rVhC8JPv.js.map} +1 -1
  61. package/dashboard/dist/assets/{OperatorDashboard-wHyeLWzX.js → OperatorDashboard-CIt061hu.js} +2 -2
  62. package/dashboard/dist/assets/{OperatorDashboard-wHyeLWzX.js.map → OperatorDashboard-CIt061hu.js.map} +1 -1
  63. package/dashboard/dist/assets/{PersonaDetailPage-BXlTqLDK.js → PersonaDetailPage-r46uNF4E.js} +2 -2
  64. package/dashboard/dist/assets/{PersonaDetailPage-BXlTqLDK.js.map → PersonaDetailPage-r46uNF4E.js.map} +1 -1
  65. package/dashboard/dist/assets/{PersonasPage-DlmdJ3Dm.js → PersonasPage-CayYv9-j.js} +2 -2
  66. package/dashboard/dist/assets/{PersonasPage-DlmdJ3Dm.js.map → PersonasPage-CayYv9-j.js.map} +1 -1
  67. package/dashboard/dist/assets/{ProcessDetailPage-D88ocvmT.js → ProcessDetailPage-dn7lkM4c.js} +2 -2
  68. package/dashboard/dist/assets/{ProcessDetailPage-D88ocvmT.js.map → ProcessDetailPage-dn7lkM4c.js.map} +1 -1
  69. package/dashboard/dist/assets/{ProcessesListPage-DpXm-KtK.js → ProcessesListPage-DOp5J6Be.js} +2 -2
  70. package/dashboard/dist/assets/{ProcessesListPage-DpXm-KtK.js.map → ProcessesListPage-DOp5J6Be.js.map} +1 -1
  71. package/dashboard/dist/assets/{RoleDetailPage-DE2Rj2QE.js → RoleDetailPage-DOlNoiAK.js} +2 -2
  72. package/dashboard/dist/assets/{RoleDetailPage-DE2Rj2QE.js.map → RoleDetailPage-DOlNoiAK.js.map} +1 -1
  73. package/dashboard/dist/assets/{RolesPage-CZSvJlsz.js → RolesPage-DPppT6Zl.js} +2 -2
  74. package/dashboard/dist/assets/{RolesPage-CZSvJlsz.js.map → RolesPage-DPppT6Zl.js.map} +1 -1
  75. package/dashboard/dist/assets/{RunAsSelector-BawZcqAk.js → RunAsSelector-BgntKfSA.js} +2 -2
  76. package/dashboard/dist/assets/{RunAsSelector-BawZcqAk.js.map → RunAsSelector-BgntKfSA.js.map} +1 -1
  77. package/dashboard/dist/assets/{ScanCodesPage-DSlpA9kK.js → ScanCodesPage-DPm1lG79.js} +2 -2
  78. package/dashboard/dist/assets/{ScanCodesPage-DSlpA9kK.js.map → ScanCodesPage-DPm1lG79.js.map} +1 -1
  79. package/dashboard/dist/assets/{ScanSchemeDetailPage-BK4hisGD.js → ScanSchemeDetailPage-CuoG89q1.js} +2 -2
  80. package/dashboard/dist/assets/{ScanSchemeDetailPage-BK4hisGD.js.map → ScanSchemeDetailPage-CuoG89q1.js.map} +1 -1
  81. package/dashboard/dist/assets/{ScanStationPage-D3QjajFD.js → ScanStationPage-2SLXqqs0.js} +2 -2
  82. package/dashboard/dist/assets/{ScanStationPage-D3QjajFD.js.map → ScanStationPage-2SLXqqs0.js.map} +1 -1
  83. package/dashboard/dist/assets/{StickyPagination-BfZwU2NR.js → StickyPagination-Bc_Mzu_R.js} +2 -2
  84. package/dashboard/dist/assets/{StickyPagination-BfZwU2NR.js.map → StickyPagination-Bc_Mzu_R.js.map} +1 -1
  85. package/dashboard/dist/assets/{StreamMessageDetail-C7zIHX5D.js → StreamMessageDetail-S1cnLGkf.js} +2 -2
  86. package/dashboard/dist/assets/{StreamMessageDetail-C7zIHX5D.js.map → StreamMessageDetail-S1cnLGkf.js.map} +1 -1
  87. package/dashboard/dist/assets/{SwimlaneTimeline-DNSo8AT8.js → SwimlaneTimeline-BUu2sPmY.js} +2 -2
  88. package/dashboard/dist/assets/{SwimlaneTimeline-DNSo8AT8.js.map → SwimlaneTimeline-BUu2sPmY.js.map} +1 -1
  89. package/dashboard/dist/assets/{TaskDetailPage-BOJ8pwh3.js → TaskDetailPage-BMfNNtbN.js} +2 -2
  90. package/dashboard/dist/assets/{TaskDetailPage-BOJ8pwh3.js.map → TaskDetailPage-BMfNNtbN.js.map} +1 -1
  91. package/dashboard/dist/assets/{TasksListPage-C4CYqZf8.js → TasksListPage-BmNnopOU.js} +2 -2
  92. package/dashboard/dist/assets/{TasksListPage-C4CYqZf8.js.map → TasksListPage-BmNnopOU.js.map} +1 -1
  93. package/dashboard/dist/assets/{TimeAgo-JRPi9IEd.js → TimeAgo-Dip7gQQx.js} +2 -2
  94. package/dashboard/dist/assets/{TimeAgo-JRPi9IEd.js.map → TimeAgo-Dip7gQQx.js.map} +1 -1
  95. package/dashboard/dist/assets/{TimestampCell-BVYyU-nl.js → TimestampCell-DRAYnIrx.js} +2 -2
  96. package/dashboard/dist/assets/{TimestampCell-BVYyU-nl.js.map → TimestampCell-DRAYnIrx.js.map} +1 -1
  97. package/dashboard/dist/assets/{ToolTestPanel-IGrd44Au.js → ToolTestPanel-CQVYC3Ba.js} +2 -2
  98. package/dashboard/dist/assets/{ToolTestPanel-IGrd44Au.js.map → ToolTestPanel-CQVYC3Ba.js.map} +1 -1
  99. package/dashboard/dist/assets/{TopicDetailPage-DhianNey.js → TopicDetailPage-Bmtwuksg.js} +2 -2
  100. package/dashboard/dist/assets/{TopicDetailPage-DhianNey.js.map → TopicDetailPage-Bmtwuksg.js.map} +1 -1
  101. package/dashboard/dist/assets/{TopicsPage-Glhqz_Nv.js → TopicsPage-DIgF7-mJ.js} +2 -2
  102. package/dashboard/dist/assets/{TopicsPage-Glhqz_Nv.js.map → TopicsPage-DIgF7-mJ.js.map} +1 -1
  103. package/dashboard/dist/assets/{UserName-CD9OqhNI.js → UserName-DNKFz7X9.js} +2 -2
  104. package/dashboard/dist/assets/{UserName-CD9OqhNI.js.map → UserName-DNKFz7X9.js.map} +1 -1
  105. package/dashboard/dist/assets/{WorkflowExecutionPage-8Q2438Ry.js → WorkflowExecutionPage-DraIBNpl.js} +2 -2
  106. package/dashboard/dist/assets/{WorkflowExecutionPage-8Q2438Ry.js.map → WorkflowExecutionPage-DraIBNpl.js.map} +1 -1
  107. package/dashboard/dist/assets/{WorkflowsDashboard-CkAoYT86.js → WorkflowsDashboard-BH7kmFjM.js} +2 -2
  108. package/dashboard/dist/assets/{WorkflowsDashboard-CkAoYT86.js.map → WorkflowsDashboard-BH7kmFjM.js.map} +1 -1
  109. package/dashboard/dist/assets/{WorkflowsOverview-Zh_EidMp.js → WorkflowsOverview-C98pCjQX.js} +2 -2
  110. package/dashboard/dist/assets/{WorkflowsOverview-Zh_EidMp.js.map → WorkflowsOverview-C98pCjQX.js.map} +1 -1
  111. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BKJbFT8C.js → YamlWorkflowDetailPage-BWQjXenZ.js} +2 -2
  112. package/dashboard/dist/assets/{YamlWorkflowDetailPage-BKJbFT8C.js.map → YamlWorkflowDetailPage-BWQjXenZ.js.map} +1 -1
  113. package/dashboard/dist/assets/{YamlWorkflowsPage-DK1z6iFt.js → YamlWorkflowsPage-BGTI_sdP.js} +2 -2
  114. package/dashboard/dist/assets/{YamlWorkflowsPage-DK1z6iFt.js.map → YamlWorkflowsPage-BGTI_sdP.js.map} +1 -1
  115. package/dashboard/dist/assets/{agents-BEASyYpg.js → agents-DfMrlw54.js} +2 -2
  116. package/dashboard/dist/assets/{agents-BEASyYpg.js.map → agents-DfMrlw54.js.map} +1 -1
  117. package/dashboard/dist/assets/{bots-CaXqSHat.js → bots-B9zS0z7D.js} +2 -2
  118. package/dashboard/dist/assets/{bots-CaXqSHat.js.map → bots-B9zS0z7D.js.map} +1 -1
  119. package/dashboard/dist/assets/{capabilities-Cijq-qev.js → capabilities-DkLM1xgH.js} +2 -2
  120. package/dashboard/dist/assets/{capabilities-Cijq-qev.js.map → capabilities-DkLM1xgH.js.map} +1 -1
  121. package/dashboard/dist/assets/{controlplane-HGuRC7AW.js → controlplane-SJK1yvbm.js} +2 -2
  122. package/dashboard/dist/assets/{controlplane-HGuRC7AW.js.map → controlplane-SJK1yvbm.js.map} +1 -1
  123. package/dashboard/dist/assets/{escalation-columns-BLVz8XCP.js → escalation-columns-BvAylNM1.js} +2 -2
  124. package/dashboard/dist/assets/{escalation-columns-BLVz8XCP.js.map → escalation-columns-BvAylNM1.js.map} +1 -1
  125. package/dashboard/dist/assets/{index-CEIp6WWg.js → index-CLxuBb17.js} +2 -2
  126. package/dashboard/dist/assets/{index-CEIp6WWg.js.map → index-CLxuBb17.js.map} +1 -1
  127. package/dashboard/dist/assets/{index-ClY0ihQ5.js → index-CXe2d5kg.js} +2 -2
  128. package/dashboard/dist/assets/{index-ClY0ihQ5.js.map → index-CXe2d5kg.js.map} +1 -1
  129. package/dashboard/dist/assets/{index-IkgBE7e_.js → index-ClVNU3G3.js} +2 -2
  130. package/dashboard/dist/assets/{index-IkgBE7e_.js.map → index-ClVNU3G3.js.map} +1 -1
  131. package/dashboard/dist/assets/{index-D2OU2MJb.js → index-Cw-V0VhS.js} +2 -2
  132. package/dashboard/dist/assets/{index-D2OU2MJb.js.map → index-Cw-V0VhS.js.map} +1 -1
  133. package/dashboard/dist/assets/{index-Xi3LO4Ye.js → index-D2IRxjSf.js} +2 -2
  134. package/dashboard/dist/assets/{index-Xi3LO4Ye.js.map → index-D2IRxjSf.js.map} +1 -1
  135. package/dashboard/dist/assets/{index-t1JXcmBG.js → index-DKXOIcO3.js} +2 -2
  136. package/dashboard/dist/assets/{index-t1JXcmBG.js.map → index-DKXOIcO3.js.map} +1 -1
  137. package/dashboard/dist/assets/{index-YMd3ZiPd.js → index-DSfY8oDp.js} +2 -2
  138. package/dashboard/dist/assets/{index-YMd3ZiPd.js.map → index-DSfY8oDp.js.map} +1 -1
  139. package/dashboard/dist/assets/index-DTZxHZJO.js +3 -0
  140. package/dashboard/dist/assets/index-DTZxHZJO.js.map +1 -0
  141. package/dashboard/dist/assets/{index-tCDjx1kF.js → index-DamcWcqG.js} +2 -2
  142. package/dashboard/dist/assets/{index-tCDjx1kF.js.map → index-DamcWcqG.js.map} +1 -1
  143. package/dashboard/dist/assets/{index-Cg0vCZng.js → index-DazfFRUx.js} +2 -2
  144. package/dashboard/dist/assets/{index-Cg0vCZng.js.map → index-DazfFRUx.js.map} +1 -1
  145. package/dashboard/dist/assets/{index-BJDAXUzO.js → index-DvmyIGeZ.js} +2 -2
  146. package/dashboard/dist/assets/{index-BJDAXUzO.js.map → index-DvmyIGeZ.js.map} +1 -1
  147. package/dashboard/dist/assets/{index-CLQ0HBHR.js → index-PjiXOVdw.js} +2 -2
  148. package/dashboard/dist/assets/{index-CLQ0HBHR.js.map → index-PjiXOVdw.js.map} +1 -1
  149. package/dashboard/dist/assets/{index-CgXVDPW-.js → index-zrTQcPjB.js} +3 -3
  150. package/dashboard/dist/assets/{index-CgXVDPW-.js.map → index-zrTQcPjB.js.map} +1 -1
  151. package/dashboard/dist/assets/{knowledge-DpaUJtVW.js → knowledge-BNp6MH-4.js} +2 -2
  152. package/dashboard/dist/assets/{knowledge-DpaUJtVW.js.map → knowledge-BNp6MH-4.js.map} +1 -1
  153. package/dashboard/dist/assets/{mcp-BkkLq6OE.js → mcp-Bb1k5rv-.js} +2 -2
  154. package/dashboard/dist/assets/{mcp-BkkLq6OE.js.map → mcp-Bb1k5rv-.js.map} +1 -1
  155. package/dashboard/dist/assets/{mcp-query-DeXu3La5.js → mcp-query-QVakMQnS.js} +2 -2
  156. package/dashboard/dist/assets/{mcp-query-DeXu3La5.js.map → mcp-query-QVakMQnS.js.map} +1 -1
  157. package/dashboard/dist/assets/{personas-BRbKVVko.js → personas-DV2JUjVj.js} +2 -2
  158. package/dashboard/dist/assets/{personas-BRbKVVko.js.map → personas-DV2JUjVj.js.map} +1 -1
  159. package/dashboard/dist/assets/{pipelines-B7xo92sK.js → pipelines-BkVB7Q0p.js} +2 -2
  160. package/dashboard/dist/assets/{pipelines-B7xo92sK.js.map → pipelines-BkVB7Q0p.js.map} +1 -1
  161. package/dashboard/dist/assets/{tasks-BGyqM1Ho.js → tasks-DvYWcASM.js} +2 -2
  162. package/dashboard/dist/assets/{tasks-BGyqM1Ho.js.map → tasks-DvYWcASM.js.map} +1 -1
  163. package/dashboard/dist/assets/{topics-I81rKc75.js → topics-B9zXFuVG.js} +2 -2
  164. package/dashboard/dist/assets/{topics-I81rKc75.js.map → topics-B9zXFuVG.js.map} +1 -1
  165. package/dashboard/dist/assets/{useEventHooks-CjV3uNxt.js → useEventHooks-JDpifqGZ.js} +2 -2
  166. package/dashboard/dist/assets/{useEventHooks-CjV3uNxt.js.map → useEventHooks-JDpifqGZ.js.map} +1 -1
  167. package/dashboard/dist/assets/{useNamespace-B-HMdRka.js → useNamespace-Bqr-LdWx.js} +2 -2
  168. package/dashboard/dist/assets/{useNamespace-B-HMdRka.js.map → useNamespace-Bqr-LdWx.js.map} +1 -1
  169. package/dashboard/dist/assets/{useYamlActivityEvents-CNsAlsgz.js → useYamlActivityEvents-n3naCEbw.js} +2 -2
  170. package/dashboard/dist/assets/{useYamlActivityEvents-CNsAlsgz.js.map → useYamlActivityEvents-n3naCEbw.js.map} +1 -1
  171. package/dashboard/dist/assets/{users-DiTqBepm.js → users-BNXLRVTS.js} +2 -2
  172. package/dashboard/dist/assets/{users-DiTqBepm.js.map → users-BNXLRVTS.js.map} +1 -1
  173. package/dashboard/dist/assets/{workflows-TA0p_3B8.js → workflows-D8uMBPvQ.js} +2 -2
  174. package/dashboard/dist/assets/{workflows-TA0p_3B8.js.map → workflows-D8uMBPvQ.js.map} +1 -1
  175. package/dashboard/dist/assets/{yaml-workflows-DN9-DBnQ.js → yaml-workflows-DVDOQM3Y.js} +2 -2
  176. package/dashboard/dist/assets/{yaml-workflows-DN9-DBnQ.js.map → yaml-workflows-DVDOQM3Y.js.map} +1 -1
  177. package/dashboard/dist/index.html +1 -1
  178. package/docs/hitl/lookups.md +13 -0
  179. package/docs/hitl/x-lt-validation.md +2 -1
  180. package/docs/hitl/x-lt-widget.md +8 -1
  181. package/docs/hitl-guide.md +2 -1
  182. package/package.json +1 -1
  183. package/dashboard/dist/assets/index-CK_ohh6Q.js +0 -3
  184. package/dashboard/dist/assets/index-CK_ohh6Q.js.map +0 -1
@@ -1,2 +1,2 @@
1
- import{a as c,u as R,j as t}from"./vendor-query-RUZdMvN6.js";import{a7 as J,g as W,I as H,q as ee,m as te,U as Y,am as P}from"./index-CgXVDPW-.js";import{i as se}from"./useEventHooks-CjV3uNxt.js";import{ag as re,ah as ae,aj as ne,X as oe,aU as le}from"./vendor-icons-h-jlRSb9.js";import{L as ce}from"./vendor-react-CdqEC2zv.js";function G(e){return JSON.stringify(e,(s,r)=>r&&typeof r=="object"&&!Array.isArray(r)?Object.fromEntries(Object.entries(r).sort(([n],[a])=>n<a?-1:1)):r)}function k(e,s=Date.now()){const r=Math.ceil(s/6e4)*6e4;return{from:new Date(r-e*36e5).toISOString(),to:new Date(r).toISOString()}}function ie(e){const[s,r]=c.useState(()=>k(e));return c.useEffect(()=>{r(k(e));let n;const a=()=>{n=setTimeout(()=>{r(k(e)),a()},6e4-Date.now()%6e4+250)};return a(),()=>clearTimeout(n)},[e]),s}function X(e,s){return s instanceof J&&s.status===403?!1:e<2}function _(e,s){return R({queryKey:["escAggregate",e?G(e):"off"],queryFn:()=>W("/escalations/aggregate-by-facets",{method:"POST",body:JSON.stringify(e)}),staleTime:15e3,enabled:((s==null?void 0:s.enabled)??!0)&&!!e,retry:X})}function z(e){return W("/escalations/timeline-by-facet",{method:"POST",body:JSON.stringify(e)})}function de(e,s){return R({queryKey:["escTimeline",e?G(e):"off"],queryFn:()=>z(e),staleTime:15e3,enabled:!!e,retry:X})}function ue(e){return e instanceof J&&e.status===403}const D=["rgb(var(--lt-status-queued-graphic))","rgb(var(--lt-status-claimed-graphic))","rgb(var(--lt-status-success-graphic))","rgb(var(--lt-status-pending-graphic))","rgb(var(--lt-status-error))","rgb(var(--lt-accent))"],me="∅",xe="no subtype";function fe(e){return e??me}function Ee(e){return e??xe}function A(e){const s=new Map;for(const a of e)s.set(a.label,(s.get(a.label)??0)+a.weight);const r=[...s.entries()].sort(([a,d],[u,o])=>o-d||(a<u?-1:1)),n=new Map;return r.forEach(([a],d)=>n.set(a,D[d%D.length])),n}function ke(e){return A(e.map(s=>({label:fe(s.subtype),weight:s.dwellSeconds??s.count??0})))}function pe(e,{enabled:s=!0,periodHours:r=720}={}){const n=ie(r),a=e.entity?{entity:e.entity}:e.role?{roles:[e.role]}:null,d=s&&!!a,u=_(d?{query:a,groupBy:{state:!0},measure:{kind:"dwell",window:n},orderBy:[{field:"dwellSeconds",direction:"desc"}]}:null),o=_(d?{query:a,groupBy:{state:!0},measure:{kind:"membership"}}:null),{data:m}=H({enabled:d}),b=c.useMemo(()=>new Set(((m==null?void 0:m.roles)??[]).filter(i=>i.entity_state_source==="subtype").map(i=>i.role)),[m]),j=c.useMemo(()=>{var i,p;return A([...(((i=u.data)==null?void 0:i.groups)??[]).filter(f=>(f.dwellSeconds??0)>0),...((p=o.data)==null?void 0:p.groups)??[]].map(f=>({label:f.state??"—",weight:f.dwellSeconds??f.count??0})))},[u.data,o.data]);return c.useCallback(i=>{const p=b.has(i.role)?i.subtype??i.role:i.role;return j.get(p)},[j,b])}const v=e=>e.subtype??e.role,M=100;function ve({facetKey:e,value:s,role:r,entity:n,intervalColor:a}){var I,B;se();const{closePanel:d}=ee(),u=n?{query:{entity:n}}:r?{query:{roles:[r]}}:{},{data:o,error:m,isLoading:b}=de({facet:{key:e,value:s},...u,order:"desc",limit:M}),j=pe({entity:n,role:r},{enabled:!a}),i=a??j,p=[...(o==null?void 0:o.intervals)??[]].reverse(),[f,T]=c.useState([]),[C,L]=c.useState(!1),[N,O]=c.useState(!1),[q,S]=c.useState(null),E=o?`${s}|${o.intervals.length}|${((I=o.intervals[0])==null?void 0:I.startedAt)??""}|${((B=o.intervals[o.intervals.length-1])==null?void 0:B.startedAt)??""}`:null,$=c.useRef(null);c.useEffect(()=>{E!==$.current&&($.current=E,T([]),L((o==null?void 0:o.overflow)??!1),S(null))},[E,o]);const x=[...f,...p],[h,Q]=c.useState("desc"),y=h==="desc"?[...x].reverse():x,Z=l=>h==="desc"?{prev:y[l],next:y[l-1]}:{prev:y[l-1],next:y[l]},K=A(x.map(l=>({label:v(l),weight:l.durationSeconds}))),F=async()=>{var g;const l=(g=x[0])==null?void 0:g.startedAt;if(!(!l||N)){O(!0),S(null);try{const w=await z({facet:{key:e,value:s},...u,order:"desc",before:l,limit:M});T(V=>[...[...w.intervals].reverse(),...V]),L(w.overflow)}catch(w){S(w.message)}finally{O(!1)}}};return t.jsxs("div",{className:"h-full flex flex-col",children:[t.jsxs("div",{className:"flex items-start justify-between px-5 pt-5 pb-3 border-b border-surface-border/40",children:[t.jsxs("div",{className:"min-w-0",children:[t.jsxs("p",{className:"text-2xs font-semibold uppercase tracking-widest text-text-tertiary",children:["Timeline · ",t.jsx("span",{className:"normal-case font-mono",children:e})]}),t.jsx("p",{className:"text-sm font-mono text-text-primary truncate mt-0.5",title:s,children:s}),x.length>0&&t.jsxs("p",{className:"flex items-center gap-2 text-2xs text-text-quaternary mt-0.5",children:[t.jsxs("span",{children:[x.length," interval",x.length===1?"":"s"]}),t.jsx("span",{className:"h-2.5 w-px bg-surface-border"}),t.jsx("button",{onClick:()=>Q(l=>l==="desc"?"asc":"desc"),className:"inline-flex items-center gap-0.5 icon-link",title:h==="desc"?"Newest first — click for oldest first":"Oldest first — click for newest first","data-testid":"timeline-sort-toggle",children:h==="desc"?t.jsxs(t.Fragment,{children:[t.jsx(re,{className:"w-2.5 h-2.5"})," newest first"]}):t.jsxs(t.Fragment,{children:[t.jsx(ae,{className:"w-2.5 h-2.5"})," oldest first"]})})]})]}),t.jsxs("span",{className:"flex items-center gap-1 mt-0.5 shrink-0 ml-2",children:[n&&t.jsx(he,{entity:n,value:s}),t.jsx(ce,{to:te(e,s),className:"icon-link",title:`Search every queue for ${e} ${s}`,"data-testid":"timeline-facet-search",children:t.jsx(ne,{className:"w-3.5 h-3.5"})}),t.jsx("button",{onClick:()=>d("entity-timeline"),className:"icon-link",children:t.jsx(oe,{className:"w-4 h-4"})})]})]}),t.jsx("div",{className:"flex-1 overflow-y-auto px-5 py-4",children:b?t.jsx("p",{className:"text-2xs text-text-tertiary italic",children:"Loading timeline…"}):ue(m)?t.jsx("p",{className:"text-2xs text-text-tertiary",children:"Timeline requires full read access to this station's queue."}):m?t.jsx("p",{className:"text-2xs text-status-error",children:m.message}):x.length===0?t.jsx("p",{className:"text-2xs text-text-quaternary",children:"No escalation has carried this value."}):t.jsxs("div",{className:"relative",children:[h==="asc"&&t.jsx(U,{hasEarlier:C,loading:N,error:q,onLoad:F,edge:"top"}),t.jsx("div",{className:"absolute left-[4px] top-2 bottom-2 w-px bg-surface-border"}),t.jsx("div",{className:"space-y-0",children:y.map((l,g)=>t.jsxs(c.Fragment,{children:[g>0&&t.jsx(ye,{...Z(g)}),t.jsx(ge,{interval:l,color:i(l)??K.get(v(l))})]},`${l.startedAt}-${g}`))}),h==="desc"&&t.jsx(U,{hasEarlier:C,loading:N,error:q,onLoad:F,edge:"bottom"})]})})]})}function U({hasEarlier:e,loading:s,error:r,onLoad:n,edge:a}){return!e&&!r?null:t.jsxs("div",{className:`${a==="top"?"pb-2":"pt-2"} pl-5`,children:[e&&t.jsx("button",{onClick:n,disabled:s,className:"text-2xs text-text-tertiary hover:text-accent transition-colors disabled:opacity-40",children:s?"Loading older…":"Load older"}),r&&t.jsx("p",{className:"text-2xs text-status-error mt-1",children:r})]})}function he({entity:e,value:s}){const[r,n]=c.useState(!1),a=c.useRef(void 0);c.useEffect(()=>()=>clearTimeout(a.current),[]);const d=async()=>{const u=`${window.location.origin}/operations?lens=${encodeURIComponent(e)}&entity=${encodeURIComponent(s)}`;await navigator.clipboard.writeText(u),n(!0),clearTimeout(a.current),a.current=setTimeout(()=>n(!1),1500)};return t.jsxs("span",{className:"flex items-center gap-1",children:[r&&t.jsx("span",{className:"text-2xs text-text-quaternary",children:"copied"}),t.jsx("button",{onClick:d,className:"icon-link",title:"Copy link to this timeline",children:t.jsx(le,{className:"w-3.5 h-3.5"})})]})}function ge({interval:e,color:s}){return t.jsxs("div",{className:"relative flex gap-3 py-2",children:[t.jsx("span",{className:"w-[9px] h-[9px] rounded-full shrink-0 mt-1 relative z-10",style:{backgroundColor:s??"rgb(var(--lt-surface-border))"},title:v(e)}),t.jsxs("div",{className:"min-w-0 flex-1",children:[t.jsxs("div",{className:"flex items-baseline gap-2",children:[t.jsxs("span",{className:"text-xs font-mono text-text-primary truncate",children:[e.role,e.subtype?t.jsxs("span",{className:"text-text-tertiary",children:[" · ",e.subtype]}):null]}),t.jsx("span",{className:"text-2xs font-mono tabular-nums text-text-secondary ml-auto shrink-0",children:Y(e.durationSeconds*1e3)})]}),t.jsxs("p",{className:"text-2xs font-mono text-text-tertiary mt-0.5",children:[P(e.startedAt)," → ",e.endedAt?P(e.endedAt):"now"]})]})]})}function ye({prev:e,next:s}){if(!e.endedAt)return null;const r=Date.parse(s.startedAt)-Date.parse(e.endedAt);return r<=1e3?null:t.jsxs("div",{className:"relative flex items-center gap-3 py-1",children:[t.jsx("span",{className:"w-[9px] shrink-0"}),t.jsx("div",{className:"flex-1 border-t border-dashed border-surface-border/60"}),t.jsxs("span",{className:"text-2xs text-text-quaternary shrink-0",children:["untracked · ",Y(r)]}),t.jsx("div",{className:"flex-1 border-t border-dashed border-surface-border/60"})]})}export{ve as E,fe as a,ie as b,A as c,ke as d,ue as i,Ee as s,_ as u};
2
- //# sourceMappingURL=EntityTimelinePanel-CFbQDHOj.js.map
1
+ import{a as c,u as R,j as t}from"./vendor-query-RUZdMvN6.js";import{a7 as J,g as W,I as H,q as ee,m as te,U as Y,am as P}from"./index-zrTQcPjB.js";import{i as se}from"./useEventHooks-JDpifqGZ.js";import{ag as re,ah as ae,aj as ne,X as oe,aU as le}from"./vendor-icons-h-jlRSb9.js";import{L as ce}from"./vendor-react-CdqEC2zv.js";function G(e){return JSON.stringify(e,(s,r)=>r&&typeof r=="object"&&!Array.isArray(r)?Object.fromEntries(Object.entries(r).sort(([n],[a])=>n<a?-1:1)):r)}function k(e,s=Date.now()){const r=Math.ceil(s/6e4)*6e4;return{from:new Date(r-e*36e5).toISOString(),to:new Date(r).toISOString()}}function ie(e){const[s,r]=c.useState(()=>k(e));return c.useEffect(()=>{r(k(e));let n;const a=()=>{n=setTimeout(()=>{r(k(e)),a()},6e4-Date.now()%6e4+250)};return a(),()=>clearTimeout(n)},[e]),s}function X(e,s){return s instanceof J&&s.status===403?!1:e<2}function _(e,s){return R({queryKey:["escAggregate",e?G(e):"off"],queryFn:()=>W("/escalations/aggregate-by-facets",{method:"POST",body:JSON.stringify(e)}),staleTime:15e3,enabled:((s==null?void 0:s.enabled)??!0)&&!!e,retry:X})}function z(e){return W("/escalations/timeline-by-facet",{method:"POST",body:JSON.stringify(e)})}function de(e,s){return R({queryKey:["escTimeline",e?G(e):"off"],queryFn:()=>z(e),staleTime:15e3,enabled:!!e,retry:X})}function ue(e){return e instanceof J&&e.status===403}const D=["rgb(var(--lt-status-queued-graphic))","rgb(var(--lt-status-claimed-graphic))","rgb(var(--lt-status-success-graphic))","rgb(var(--lt-status-pending-graphic))","rgb(var(--lt-status-error))","rgb(var(--lt-accent))"],me="∅",xe="no subtype";function fe(e){return e??me}function Ee(e){return e??xe}function A(e){const s=new Map;for(const a of e)s.set(a.label,(s.get(a.label)??0)+a.weight);const r=[...s.entries()].sort(([a,d],[u,o])=>o-d||(a<u?-1:1)),n=new Map;return r.forEach(([a],d)=>n.set(a,D[d%D.length])),n}function ke(e){return A(e.map(s=>({label:fe(s.subtype),weight:s.dwellSeconds??s.count??0})))}function pe(e,{enabled:s=!0,periodHours:r=720}={}){const n=ie(r),a=e.entity?{entity:e.entity}:e.role?{roles:[e.role]}:null,d=s&&!!a,u=_(d?{query:a,groupBy:{state:!0},measure:{kind:"dwell",window:n},orderBy:[{field:"dwellSeconds",direction:"desc"}]}:null),o=_(d?{query:a,groupBy:{state:!0},measure:{kind:"membership"}}:null),{data:m}=H({enabled:d}),b=c.useMemo(()=>new Set(((m==null?void 0:m.roles)??[]).filter(i=>i.entity_state_source==="subtype").map(i=>i.role)),[m]),j=c.useMemo(()=>{var i,p;return A([...(((i=u.data)==null?void 0:i.groups)??[]).filter(f=>(f.dwellSeconds??0)>0),...((p=o.data)==null?void 0:p.groups)??[]].map(f=>({label:f.state??"—",weight:f.dwellSeconds??f.count??0})))},[u.data,o.data]);return c.useCallback(i=>{const p=b.has(i.role)?i.subtype??i.role:i.role;return j.get(p)},[j,b])}const v=e=>e.subtype??e.role,M=100;function ve({facetKey:e,value:s,role:r,entity:n,intervalColor:a}){var I,B;se();const{closePanel:d}=ee(),u=n?{query:{entity:n}}:r?{query:{roles:[r]}}:{},{data:o,error:m,isLoading:b}=de({facet:{key:e,value:s},...u,order:"desc",limit:M}),j=pe({entity:n,role:r},{enabled:!a}),i=a??j,p=[...(o==null?void 0:o.intervals)??[]].reverse(),[f,T]=c.useState([]),[C,L]=c.useState(!1),[N,O]=c.useState(!1),[q,S]=c.useState(null),E=o?`${s}|${o.intervals.length}|${((I=o.intervals[0])==null?void 0:I.startedAt)??""}|${((B=o.intervals[o.intervals.length-1])==null?void 0:B.startedAt)??""}`:null,$=c.useRef(null);c.useEffect(()=>{E!==$.current&&($.current=E,T([]),L((o==null?void 0:o.overflow)??!1),S(null))},[E,o]);const x=[...f,...p],[h,Q]=c.useState("desc"),y=h==="desc"?[...x].reverse():x,Z=l=>h==="desc"?{prev:y[l],next:y[l-1]}:{prev:y[l-1],next:y[l]},K=A(x.map(l=>({label:v(l),weight:l.durationSeconds}))),F=async()=>{var g;const l=(g=x[0])==null?void 0:g.startedAt;if(!(!l||N)){O(!0),S(null);try{const w=await z({facet:{key:e,value:s},...u,order:"desc",before:l,limit:M});T(V=>[...[...w.intervals].reverse(),...V]),L(w.overflow)}catch(w){S(w.message)}finally{O(!1)}}};return t.jsxs("div",{className:"h-full flex flex-col",children:[t.jsxs("div",{className:"flex items-start justify-between px-5 pt-5 pb-3 border-b border-surface-border/40",children:[t.jsxs("div",{className:"min-w-0",children:[t.jsxs("p",{className:"text-2xs font-semibold uppercase tracking-widest text-text-tertiary",children:["Timeline · ",t.jsx("span",{className:"normal-case font-mono",children:e})]}),t.jsx("p",{className:"text-sm font-mono text-text-primary truncate mt-0.5",title:s,children:s}),x.length>0&&t.jsxs("p",{className:"flex items-center gap-2 text-2xs text-text-quaternary mt-0.5",children:[t.jsxs("span",{children:[x.length," interval",x.length===1?"":"s"]}),t.jsx("span",{className:"h-2.5 w-px bg-surface-border"}),t.jsx("button",{onClick:()=>Q(l=>l==="desc"?"asc":"desc"),className:"inline-flex items-center gap-0.5 icon-link",title:h==="desc"?"Newest first — click for oldest first":"Oldest first — click for newest first","data-testid":"timeline-sort-toggle",children:h==="desc"?t.jsxs(t.Fragment,{children:[t.jsx(re,{className:"w-2.5 h-2.5"})," newest first"]}):t.jsxs(t.Fragment,{children:[t.jsx(ae,{className:"w-2.5 h-2.5"})," oldest first"]})})]})]}),t.jsxs("span",{className:"flex items-center gap-1 mt-0.5 shrink-0 ml-2",children:[n&&t.jsx(he,{entity:n,value:s}),t.jsx(ce,{to:te(e,s),className:"icon-link",title:`Search every queue for ${e} ${s}`,"data-testid":"timeline-facet-search",children:t.jsx(ne,{className:"w-3.5 h-3.5"})}),t.jsx("button",{onClick:()=>d("entity-timeline"),className:"icon-link",children:t.jsx(oe,{className:"w-4 h-4"})})]})]}),t.jsx("div",{className:"flex-1 overflow-y-auto px-5 py-4",children:b?t.jsx("p",{className:"text-2xs text-text-tertiary italic",children:"Loading timeline…"}):ue(m)?t.jsx("p",{className:"text-2xs text-text-tertiary",children:"Timeline requires full read access to this station's queue."}):m?t.jsx("p",{className:"text-2xs text-status-error",children:m.message}):x.length===0?t.jsx("p",{className:"text-2xs text-text-quaternary",children:"No escalation has carried this value."}):t.jsxs("div",{className:"relative",children:[h==="asc"&&t.jsx(U,{hasEarlier:C,loading:N,error:q,onLoad:F,edge:"top"}),t.jsx("div",{className:"absolute left-[4px] top-2 bottom-2 w-px bg-surface-border"}),t.jsx("div",{className:"space-y-0",children:y.map((l,g)=>t.jsxs(c.Fragment,{children:[g>0&&t.jsx(ye,{...Z(g)}),t.jsx(ge,{interval:l,color:i(l)??K.get(v(l))})]},`${l.startedAt}-${g}`))}),h==="desc"&&t.jsx(U,{hasEarlier:C,loading:N,error:q,onLoad:F,edge:"bottom"})]})})]})}function U({hasEarlier:e,loading:s,error:r,onLoad:n,edge:a}){return!e&&!r?null:t.jsxs("div",{className:`${a==="top"?"pb-2":"pt-2"} pl-5`,children:[e&&t.jsx("button",{onClick:n,disabled:s,className:"text-2xs text-text-tertiary hover:text-accent transition-colors disabled:opacity-40",children:s?"Loading older…":"Load older"}),r&&t.jsx("p",{className:"text-2xs text-status-error mt-1",children:r})]})}function he({entity:e,value:s}){const[r,n]=c.useState(!1),a=c.useRef(void 0);c.useEffect(()=>()=>clearTimeout(a.current),[]);const d=async()=>{const u=`${window.location.origin}/operations?lens=${encodeURIComponent(e)}&entity=${encodeURIComponent(s)}`;await navigator.clipboard.writeText(u),n(!0),clearTimeout(a.current),a.current=setTimeout(()=>n(!1),1500)};return t.jsxs("span",{className:"flex items-center gap-1",children:[r&&t.jsx("span",{className:"text-2xs text-text-quaternary",children:"copied"}),t.jsx("button",{onClick:d,className:"icon-link",title:"Copy link to this timeline",children:t.jsx(le,{className:"w-3.5 h-3.5"})})]})}function ge({interval:e,color:s}){return t.jsxs("div",{className:"relative flex gap-3 py-2",children:[t.jsx("span",{className:"w-[9px] h-[9px] rounded-full shrink-0 mt-1 relative z-10",style:{backgroundColor:s??"rgb(var(--lt-surface-border))"},title:v(e)}),t.jsxs("div",{className:"min-w-0 flex-1",children:[t.jsxs("div",{className:"flex items-baseline gap-2",children:[t.jsxs("span",{className:"text-xs font-mono text-text-primary truncate",children:[e.role,e.subtype?t.jsxs("span",{className:"text-text-tertiary",children:[" · ",e.subtype]}):null]}),t.jsx("span",{className:"text-2xs font-mono tabular-nums text-text-secondary ml-auto shrink-0",children:Y(e.durationSeconds*1e3)})]}),t.jsxs("p",{className:"text-2xs font-mono text-text-tertiary mt-0.5",children:[P(e.startedAt)," → ",e.endedAt?P(e.endedAt):"now"]})]})]})}function ye({prev:e,next:s}){if(!e.endedAt)return null;const r=Date.parse(s.startedAt)-Date.parse(e.endedAt);return r<=1e3?null:t.jsxs("div",{className:"relative flex items-center gap-3 py-1",children:[t.jsx("span",{className:"w-[9px] shrink-0"}),t.jsx("div",{className:"flex-1 border-t border-dashed border-surface-border/60"}),t.jsxs("span",{className:"text-2xs text-text-quaternary shrink-0",children:["untracked · ",Y(r)]}),t.jsx("div",{className:"flex-1 border-t border-dashed border-surface-border/60"})]})}export{ve as E,fe as a,ie as b,A as c,ke as d,ue as i,Ee as s,_ as u};
2
+ //# sourceMappingURL=EntityTimelinePanel-G6nzvgpl.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EntityTimelinePanel-CFbQDHOj.js","sources":["../../src/api/escalation-analytics.ts","../../src/pages/operations/mix-colors.ts","../../src/pages/operations/useEntityStateColors.ts","../../src/components/escalation/EntityTimelinePanel.tsx"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Escalation analytics hooks — grouped membership/dwell aggregates and\n// per-entity interval timelines (POST /escalations/aggregate-by-facets and\n// /timeline-by-facet). Every escalation is one open interval\n// [created_at, ended_at); these hooks read that time-series.\n//\n// Query-key discipline: inputs are canonicalized (key-sorted stringify) and\n// windows are quantized to the minute — the window end rounds UP to the next\n// minute boundary so (a) keys stay stable across renders within the minute\n// and (b) `to >= now` keeps the query in the server's now-anchored cache\n// (invalidated on escalation writes) instead of the 10-minute past cache.\n// Refresh is push-driven (useEscalationAnalyticsEvents) — never polling.\n// ---------------------------------------------------------------------------\n\nimport { useEffect, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { apiFetch, ApiError } from './client';\n\n// Wire types — mirrors of types/analytics.ts on the server.\n\nexport interface AnalyticsWindow {\n from: string;\n to: string;\n}\n\nexport type AggregateMeasure =\n | { kind: 'membership'; asOf?: string }\n | { kind: 'dwell'; window: AnalyticsWindow };\n\nexport interface AnalyticsFilter {\n role?: string;\n roles?: string[];\n /** Entity facet key — resolves server-side to every role declaring it (the entity's system). */\n entity?: string;\n facets?: Record<string, unknown>;\n /** Case-insensitive facet-value prefix match, keyed by facet. */\n prefix?: Record<string, string>;\n /**\n * Exact facet-value match via the text projection (metadata->>key = value),\n * keyed by facet. Compares as text, so a categorical value the aggregate\n * returned (bool/enum/number, all surfaced as text) round-trips as a filter.\n */\n equals?: Record<string, string>;\n block?: Record<string, unknown>[];\n /** Row carries ANY of these facet sets (max 200) — the positive mirror of block. */\n anyOf?: Record<string, unknown>[];\n range?: { facet: string; op: '<' | '<=' | '>' | '>=' | '='; value: number }[];\n exists?: string[];\n}\n\nexport interface AggregateByFacetsInput {\n query: AnalyticsFilter;\n groupBy: { columns?: ('role' | 'subtype' | 'status')[]; facets?: string[]; state?: boolean };\n measure: AggregateMeasure;\n distinctBy?: string;\n orderBy?: { field: string; direction?: 'asc' | 'desc' }[];\n limit?: number;\n offset?: number;\n}\n\nexport interface AggregateRow {\n role?: string;\n subtype?: string | null;\n status?: string;\n facets: Record<string, string | null>;\n state?: string;\n count?: number;\n dwellSeconds?: number;\n sampleCount: number;\n}\n\nexport interface AggregateByFacetsResult {\n groups: AggregateRow[];\n overflow: boolean;\n}\n\nexport interface TimelineByFacetInput {\n facet: { key: string; value: string };\n query?: AnalyticsFilter;\n window?: AnalyticsWindow;\n select?: { columns?: ('role' | 'subtype' | 'status')[]; facets?: string[] };\n /** Interval order by startedAt (default asc). */\n order?: 'asc' | 'desc';\n /** Strict startedAt upper bound — the \"load earlier\" cursor. */\n before?: string;\n limit?: number;\n}\n\nexport interface TimelineInterval {\n role: string;\n subtype?: string | null;\n status?: string;\n facets: Record<string, string | null>;\n startedAt: string;\n endedAt: string | null;\n durationSeconds: number;\n}\n\nexport interface TimelineByFacetResult {\n intervals: TimelineInterval[];\n overflow: boolean;\n}\n\n// ── Key + window helpers (exported for tests) ────────────────────────────────\n\n/** Key-order-independent stringify so `{a,b}` and `{b,a}` share one query key. */\nexport function canonicalStringify(value: unknown): string {\n return JSON.stringify(value, (_key, v) =>\n v && typeof v === 'object' && !Array.isArray(v)\n ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => (a < b ? -1 : 1)))\n : v,\n );\n}\n\n/** The trailing window for a period: `to` = next minute boundary, `from` = to − hours. */\nexport function analyticsWindow(periodHours: number, now = Date.now()): AnalyticsWindow {\n const to = Math.ceil(now / 60_000) * 60_000;\n return {\n from: new Date(to - periodHours * 3_600_000).toISOString(),\n to: new Date(to).toISOString(),\n };\n}\n\n/**\n * A live trailing window that rolls forward on the minute. Without the tick a\n * quiet tab's `to` falls into the past and the server reclassifies the query\n * fully-past (10-minute cache) — a \"live\" board frozen for 10 minutes.\n */\nexport function useAnalyticsWindow(periodHours: number): AnalyticsWindow {\n const [window, setWindow] = useState(() => analyticsWindow(periodHours));\n useEffect(() => {\n setWindow(analyticsWindow(periodHours));\n let timer: ReturnType<typeof setTimeout>;\n const schedule = () => {\n // Fire just past the minute boundary so ceil() lands on the NEXT one.\n timer = setTimeout(() => {\n setWindow(analyticsWindow(periodHours));\n schedule();\n }, 60_000 - (Date.now() % 60_000) + 250);\n };\n schedule();\n return () => clearTimeout(timer);\n }, [periodHours]);\n return window;\n}\n\n/** 403 is a scope answer, not a transient fault — never retry it. */\nfunction retryUnlessForbidden(failureCount: number, error: unknown): boolean {\n if (error instanceof ApiError && error.status === 403) return false;\n return failureCount < 2;\n}\n\n// ── Hooks ─────────────────────────────────────────────────────────────────────\n\nexport function useAggregateByFacets(\n input: AggregateByFacetsInput | null,\n opts?: { enabled?: boolean },\n) {\n return useQuery<AggregateByFacetsResult>({\n queryKey: ['escAggregate', input ? canonicalStringify(input) : 'off'],\n queryFn: () =>\n apiFetch('/escalations/aggregate-by-facets', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n staleTime: 15_000,\n enabled: (opts?.enabled ?? true) && !!input,\n retry: retryUnlessForbidden,\n });\n}\n\n/** One-shot timeline fetch — cursor pages (\"load earlier\") outside the query cache. */\nexport function fetchTimelineByFacet(input: TimelineByFacetInput): Promise<TimelineByFacetResult> {\n return apiFetch('/escalations/timeline-by-facet', {\n method: 'POST',\n body: JSON.stringify(input),\n });\n}\n\nexport function useTimelineByFacet(\n input: TimelineByFacetInput | null,\n opts?: { enabled?: boolean },\n) {\n return useQuery<TimelineByFacetResult>({\n queryKey: ['escTimeline', input ? canonicalStringify(input) : 'off'],\n queryFn: () => fetchTimelineByFacet(input!),\n staleTime: 15_000,\n enabled: (opts?.enabled ?? true) && !!input,\n retry: retryUnlessForbidden,\n });\n}\n\n/** True when the query failed the analytics read gate (render the scope notice). */\nexport function isForbidden(error: unknown): boolean {\n return error instanceof ApiError && error.status === 403;\n}\n","// ---------------------------------------------------------------------------\n// State-mix color assignment — a small ordered palette of the -graphic status\n// tokens (chart-grade contrast) assigned to subtypes by dwell rank. The null\n// subtype is a REAL group (rows created without one): it gets a key, a color,\n// and a label — dropping it would misstate the total tracked time.\n// ---------------------------------------------------------------------------\n\nexport const MIX_PALETTE = [\n 'rgb(var(--lt-status-queued-graphic))', // sky\n 'rgb(var(--lt-status-claimed-graphic))', // orange\n 'rgb(var(--lt-status-success-graphic))', // green\n 'rgb(var(--lt-status-pending-graphic))', // amber\n 'rgb(var(--lt-status-error))', // red\n 'rgb(var(--lt-accent))',\n] as const;\n\nexport const NO_SUBTYPE_KEY = '∅';\nexport const NO_SUBTYPE_LABEL = 'no subtype';\n\n/** Map key for a subtype group (null/undefined collapse to the ∅ group). */\nexport function subtypeKey(subtype: string | null | undefined): string {\n return subtype ?? NO_SUBTYPE_KEY;\n}\n\nexport function subtypeLabel(subtype: string | null | undefined): string {\n return subtype ?? NO_SUBTYPE_LABEL;\n}\n\n/**\n * Stable ordered assignment over arbitrary labels: sorted by total weight\n * descending (alphabetical tiebreak so equal ranks never swap), palette\n * wrapping beyond its length. Deterministic for a given entry set.\n */\nexport function assignLabelColors(\n entries: Array<{ label: string; weight: number }>,\n): Map<string, string> {\n const totals = new Map<string, number>();\n for (const e of entries) totals.set(e.label, (totals.get(e.label) ?? 0) + e.weight);\n const ranked = [...totals.entries()].sort(\n ([ak, av], [bk, bv]) => bv - av || (ak < bk ? -1 : 1),\n );\n const colors = new Map<string, string>();\n ranked.forEach(([key], i) => colors.set(key, MIX_PALETTE[i % MIX_PALETTE.length]));\n return colors;\n}\n\n/** The subtype-keyed form used by the station MIX surfaces. */\nexport function assignMixColors(\n groups: { subtype?: string | null; dwellSeconds?: number; count?: number }[],\n): Map<string, string> {\n return assignLabelColors(\n groups.map((g) => ({ label: subtypeKey(g.subtype), weight: g.dwellSeconds ?? g.count ?? 0 })),\n );\n}\n","import { useCallback, useMemo } from 'react';\nimport {\n useAggregateByFacets,\n useAnalyticsWindow,\n type TimelineInterval,\n} from '../../api/escalation-analytics';\nimport { useRoleDetails } from '../../api/roles';\nimport { assignLabelColors } from './mix-colors';\n\n/**\n * A category-color resolver for an entity system's timeline intervals — the\n * same palette the entity-lens band uses, so a timeline opened anywhere (the\n * lens, the station detail panel, an escalation cell popover) colors its dots\n * by state identically. Each interval's state is derived exactly as the server\n * does (subtype-sourced roles → COALESCE(subtype, role), everyone else → role),\n * then mapped through colors assigned over the system's state dwell.\n *\n * A stable reference window (not the user's selected period) keeps the color\n * assignment consistent across contexts. Pass `enabled: false` when the opener\n * already supplies its own resolver, so this does no redundant fetching.\n */\nexport function useEntityStateColors(\n scope: { entity?: string; role?: string },\n { enabled = true, periodHours = 720 }: { enabled?: boolean; periodHours?: number } = {},\n): (interval: TimelineInterval) => string | undefined {\n const window = useAnalyticsWindow(periodHours);\n const query = scope.entity\n ? { entity: scope.entity }\n : scope.role\n ? { roles: [scope.role] }\n : null;\n const active = enabled && !!query;\n\n const dwell = useAggregateByFacets(\n active\n ? {\n query: query!,\n groupBy: { state: true },\n measure: { kind: 'dwell', window },\n orderBy: [{ field: 'dwellSeconds', direction: 'desc' }],\n }\n : null,\n );\n const now = useAggregateByFacets(\n active ? { query: query!, groupBy: { state: true }, measure: { kind: 'membership' } } : null,\n );\n const { data: roleData } = useRoleDetails({ enabled: active });\n\n const subtypeSourced = useMemo(\n () =>\n new Set(\n (roleData?.roles ?? [])\n .filter((r) => r.entity_state_source === 'subtype')\n .map((r) => r.role),\n ),\n [roleData],\n );\n const colors = useMemo(\n () =>\n assignLabelColors(\n [\n ...(dwell.data?.groups ?? []).filter((g) => (g.dwellSeconds ?? 0) > 0),\n ...(now.data?.groups ?? []),\n ].map((g) => ({ label: g.state ?? '—', weight: g.dwellSeconds ?? g.count ?? 0 })),\n ),\n [dwell.data, now.data],\n );\n\n return useCallback(\n (interval: TimelineInterval) => {\n const state = subtypeSourced.has(interval.role)\n ? (interval.subtype ?? interval.role)\n : interval.role;\n return colors.get(state);\n },\n [colors, subtypeSourced],\n );\n}\n","import { Fragment, useEffect, useRef, useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { X, Link as LinkIcon, Search, ArrowDown, ArrowUp } from 'lucide-react';\nimport {\n useTimelineByFacet,\n fetchTimelineByFacet,\n isForbidden,\n type AnalyticsFilter,\n type TimelineInterval,\n} from '../../api/escalation-analytics';\nimport { useEscalationAnalyticsEvents } from '../../hooks/useEventHooks';\nimport { useShellPanel } from '../../hooks/useShellPanel';\nimport { formatDateTime, formatDurationCompact } from '../../lib/format';\nimport { metadataFacetUrl } from '../../lib/facet-url';\nimport { assignLabelColors } from '../../pages/operations/mix-colors';\nimport { useEntityStateColors } from '../../pages/operations/useEntityStateColors';\n\n/** The category a row belongs to — its state within the process (subtype),\n * falling back to the role. This is the axis the timeline graphic colors by,\n * NOT the escalation lifecycle (resolved/expired) which would be near-uniform\n * across a mostly-completed history. */\nconst categoryOf = (iv: TimelineInterval): string => iv.subtype ?? iv.role;\n\n// Newest page size — the head query and every \"load earlier\" page.\nconst TIMELINE_PAGE_SIZE = 100;\n\n/**\n * One entity's escalation-interval timeline — every station the facet value\n * has moved through, as [started, ended) spans rendered chronologically. Gaps\n * between consecutive intervals are untracked time and render as explicit\n * separators: their size is the digital/physical settle latency, a health\n * signal, never hidden.\n *\n * The head query fetches the NEWEST page (order desc) so push-driven\n * invalidation keeps the recent end fresh; older pages accumulate locally via\n * the \"Load earlier\" cursor (`before` = oldest loaded startedAt) and reset\n * whenever the head page changes.\n *\n * Rendered in the global shell right panel (key 'entity-timeline'). Passing\n * `role` keeps the query inside the pond gate for read_all members; without\n * it the query is cross-role and needs a global principal — a 403 degrades to\n * a quiet scope notice.\n */\nexport function EntityTimelinePanel({\n facetKey,\n value,\n role,\n entity,\n intervalColor,\n}: {\n facetKey: string;\n value: string;\n role?: string;\n /** Scope to the derived entity system (every role declaring this entity facet). */\n entity?: string;\n /**\n * Optional color resolver from the opener (the entity lens): maps an interval\n * to the EXACT color the timeline graphic uses for its category, so the list\n * and the band agree. Absent (standalone opens), the panel colors from its\n * own intervals — consistent, but not cross-component identical.\n */\n intervalColor?: (interval: TimelineInterval) => string | undefined;\n}) {\n useEscalationAnalyticsEvents();\n const { closePanel } = useShellPanel();\n const scope: { query?: AnalyticsFilter } = entity\n ? { query: { entity } }\n : role\n ? { query: { roles: [role] } }\n : {};\n const { data, error, isLoading } = useTimelineByFacet({\n facet: { key: facetKey, value },\n ...scope,\n order: 'desc',\n limit: TIMELINE_PAGE_SIZE,\n });\n\n // Color resolution: the opener's resolver (the lens, period-matched to its\n // band) wins; otherwise derive the entity system's colors here so station\n // detail and cell popovers color by the same scheme. When a resolver is\n // supplied, the fallback does no fetching.\n const systemColor = useEntityStateColors({ entity, role }, { enabled: !intervalColor });\n const resolveColor = intervalColor ?? systemColor;\n\n // Head page, chronological. Older pages prepend before it.\n const headIntervals = [...(data?.intervals ?? [])].reverse();\n const [older, setOlder] = useState<TimelineInterval[]>([]);\n const [hasEarlier, setHasEarlier] = useState(false);\n const [loadingEarlier, setLoadingEarlier] = useState(false);\n const [earlierError, setEarlierError] = useState<string | null>(null);\n\n // Reset accumulated pages when the newest interval set changes — a new facet\n // value, or a push-driven head refetch that shifted the recent end.\n const headSignature = data\n ? `${value}|${data.intervals.length}|${data.intervals[0]?.startedAt ?? ''}|${data.intervals[data.intervals.length - 1]?.startedAt ?? ''}`\n : null;\n const appliedSignature = useRef<string | null>(null);\n useEffect(() => {\n if (headSignature === appliedSignature.current) return;\n appliedSignature.current = headSignature;\n setOlder([]);\n setHasEarlier(data?.overflow ?? false);\n setEarlierError(null);\n }, [headSignature, data]);\n\n const intervals = [...older, ...headIntervals];\n\n // Display order — the dashboard convention is most-recent first, so the\n // default is descending; the toggle is pure presentation over the loaded\n // window. Paging is unchanged either way: the head is the newest page and\n // the strict `before` cursor walks OLDER intervals — sound at thousands of\n // rows (a printer's lifetime runs to ~4500 intervals), no offsets to drift.\n const [sortOrder, setSortOrder] = useState<'desc' | 'asc'>('desc');\n const displayed = sortOrder === 'desc' ? [...intervals].reverse() : intervals;\n // The chronologically earlier/later pair for a rendered neighbor gap.\n const gapPair = (i: number): { prev: TimelineInterval; next: TimelineInterval } =>\n sortOrder === 'desc'\n ? { prev: displayed[i], next: displayed[i - 1] }\n : { prev: displayed[i - 1], next: displayed[i] };\n\n // Category colors — the same palette generator the timeline graphic uses,\n // keyed by each row's category and weighted by dwell so the dominant states\n // take the lead palette slots. The dot reads as \"what this was,\" matching the\n // band segments on the left.\n const categoryColors = assignLabelColors(\n intervals.map((iv) => ({ label: categoryOf(iv), weight: iv.durationSeconds })),\n );\n\n const loadEarlier = async () => {\n const oldest = intervals[0]?.startedAt;\n if (!oldest || loadingEarlier) return;\n setLoadingEarlier(true);\n setEarlierError(null);\n try {\n const page = await fetchTimelineByFacet({\n facet: { key: facetKey, value },\n ...scope,\n order: 'desc',\n before: oldest,\n limit: TIMELINE_PAGE_SIZE,\n });\n setOlder((prev) => [...[...page.intervals].reverse(), ...prev]);\n setHasEarlier(page.overflow);\n } catch (err) {\n setEarlierError((err as Error).message);\n } finally {\n setLoadingEarlier(false);\n }\n };\n\n return (\n <div className=\"h-full flex flex-col\">\n <div className=\"flex items-start justify-between px-5 pt-5 pb-3 border-b border-surface-border/40\">\n <div className=\"min-w-0\">\n <p className=\"text-2xs font-semibold uppercase tracking-widest text-text-tertiary\">\n Timeline · <span className=\"normal-case font-mono\">{facetKey}</span>\n </p>\n <p className=\"text-sm font-mono text-text-primary truncate mt-0.5\" title={value}>\n {value}\n </p>\n {intervals.length > 0 && (\n <p className=\"flex items-center gap-2 text-2xs text-text-quaternary mt-0.5\">\n <span>{intervals.length} interval{intervals.length === 1 ? '' : 's'}</span>\n <span className=\"h-2.5 w-px bg-surface-border\" />\n <button\n onClick={() => setSortOrder((o) => (o === 'desc' ? 'asc' : 'desc'))}\n className=\"inline-flex items-center gap-0.5 icon-link\"\n title={sortOrder === 'desc' ? 'Newest first — click for oldest first' : 'Oldest first — click for newest first'}\n data-testid=\"timeline-sort-toggle\"\n >\n {sortOrder === 'desc'\n ? <><ArrowDown className=\"w-2.5 h-2.5\" /> newest first</>\n : <><ArrowUp className=\"w-2.5 h-2.5\" /> oldest first</>}\n </button>\n </p>\n )}\n </div>\n <span className=\"flex items-center gap-1 mt-0.5 shrink-0 ml-2\">\n {entity && <CopyLinkButton entity={entity} value={value} />}\n {/* Search every queue for this facet value — the all-roles metadata search. */}\n <Link\n to={metadataFacetUrl(facetKey, value)}\n className=\"icon-link\"\n title={`Search every queue for ${facetKey} ${value}`}\n data-testid=\"timeline-facet-search\"\n >\n <Search className=\"w-3.5 h-3.5\" />\n </Link>\n <button onClick={() => closePanel('entity-timeline')} className=\"icon-link\">\n <X className=\"w-4 h-4\" />\n </button>\n </span>\n </div>\n\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n {isLoading ? (\n <p className=\"text-2xs text-text-tertiary italic\">Loading timeline…</p>\n ) : isForbidden(error) ? (\n <p className=\"text-2xs text-text-tertiary\">\n Timeline requires full read access to this station's queue.\n </p>\n ) : error ? (\n <p className=\"text-2xs text-status-error\">{(error as Error).message}</p>\n ) : intervals.length === 0 ? (\n <p className=\"text-2xs text-text-quaternary\">\n No escalation has carried this value.\n </p>\n ) : (\n <div className=\"relative\">\n {/* Older pages continue in the reading direction: at the TOP when\n oldest-first, at the BOTTOM when newest-first. */}\n {sortOrder === 'asc' && (\n <LoadOlderControl\n hasEarlier={hasEarlier}\n loading={loadingEarlier}\n error={earlierError}\n onLoad={loadEarlier}\n edge=\"top\"\n />\n )}\n {/* Left rail behind the dots */}\n <div className=\"absolute left-[4px] top-2 bottom-2 w-px bg-surface-border\" />\n <div className=\"space-y-0\">\n {displayed.map((interval, i) => (\n <Fragment key={`${interval.startedAt}-${i}`}>\n {i > 0 && <GapRow {...gapPair(i)} />}\n <IntervalRow\n interval={interval}\n color={resolveColor(interval) ?? categoryColors.get(categoryOf(interval))}\n />\n </Fragment>\n ))}\n </div>\n {sortOrder === 'desc' && (\n <LoadOlderControl\n hasEarlier={hasEarlier}\n loading={loadingEarlier}\n error={earlierError}\n onLoad={loadEarlier}\n edge=\"bottom\"\n />\n )}\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/** The cursor pager — walks strictly-older intervals a page at a time. */\nfunction LoadOlderControl({\n hasEarlier,\n loading,\n error,\n onLoad,\n edge,\n}: {\n hasEarlier: boolean;\n loading: boolean;\n error: string | null;\n onLoad: () => void;\n edge: 'top' | 'bottom';\n}) {\n if (!hasEarlier && !error) return null;\n return (\n <div className={`${edge === 'top' ? 'pb-2' : 'pt-2'} pl-5`}>\n {hasEarlier && (\n <button\n onClick={onLoad}\n disabled={loading}\n className=\"text-2xs text-text-tertiary hover:text-accent transition-colors disabled:opacity-40\"\n >\n {loading ? 'Loading older…' : 'Load older'}\n </button>\n )}\n {error && <p className=\"text-2xs text-status-error mt-1\">{error}</p>}\n </div>\n );\n}\n\n/** Copies the entity-lens deep link (?lens= + ?entity=) with a transient note. */\nfunction CopyLinkButton({ entity, value }: { entity: string; value: string }) {\n const [copied, setCopied] = useState(false);\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n useEffect(() => () => clearTimeout(timer.current), []);\n\n const copy = async () => {\n const url = `${window.location.origin}/operations?lens=${encodeURIComponent(entity)}&entity=${encodeURIComponent(value)}`;\n await navigator.clipboard.writeText(url);\n setCopied(true);\n clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), 1500);\n };\n\n return (\n <span className=\"flex items-center gap-1\">\n {copied && <span className=\"text-2xs text-text-quaternary\">copied</span>}\n <button onClick={copy} className=\"icon-link\" title=\"Copy link to this timeline\">\n <LinkIcon className=\"w-3.5 h-3.5\" />\n </button>\n </span>\n );\n}\n\nfunction IntervalRow({ interval, color }: { interval: TimelineInterval; color?: string }) {\n return (\n <div className=\"relative flex gap-3 py-2\">\n {/* Filled dot in the row's CATEGORY color — matches the graphic's segments,\n so the list reads by state the same way the band does. */}\n <span\n className=\"w-[9px] h-[9px] rounded-full shrink-0 mt-1 relative z-10\"\n style={{ backgroundColor: color ?? 'rgb(var(--lt-surface-border))' }}\n title={categoryOf(interval)}\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-xs font-mono text-text-primary truncate\">\n {interval.role}\n {interval.subtype ? <span className=\"text-text-tertiary\"> · {interval.subtype}</span> : null}\n </span>\n <span className=\"text-2xs font-mono tabular-nums text-text-secondary ml-auto shrink-0\">\n {formatDurationCompact(interval.durationSeconds * 1000)}\n </span>\n </div>\n <p className=\"text-2xs font-mono text-text-tertiary mt-0.5\">\n {formatDateTime(interval.startedAt)} → {interval.endedAt ? formatDateTime(interval.endedAt) : 'now'}\n </p>\n </div>\n </div>\n );\n}\n\n/** Untracked time between two intervals — the settle latency between queues, surfaced. */\nfunction GapRow({ prev, next }: { prev: TimelineInterval; next: TimelineInterval }) {\n if (!prev.endedAt) return null;\n const gapMs = Date.parse(next.startedAt) - Date.parse(prev.endedAt);\n if (gapMs <= 1000) return null; // contiguous handoff\n return (\n <div className=\"relative flex items-center gap-3 py-1\">\n <span className=\"w-[9px] shrink-0\" />\n <div className=\"flex-1 border-t border-dashed border-surface-border/60\" />\n <span className=\"text-2xs text-text-quaternary shrink-0\">\n untracked · {formatDurationCompact(gapMs)}\n </span>\n <div className=\"flex-1 border-t border-dashed border-surface-border/60\" />\n </div>\n );\n}\n"],"names":["canonicalStringify","value","_key","v","a","b","analyticsWindow","periodHours","now","to","useAnalyticsWindow","window","setWindow","useState","useEffect","timer","schedule","retryUnlessForbidden","failureCount","error","ApiError","useAggregateByFacets","input","opts","useQuery","apiFetch","fetchTimelineByFacet","useTimelineByFacet","isForbidden","MIX_PALETTE","NO_SUBTYPE_KEY","NO_SUBTYPE_LABEL","subtypeKey","subtype","subtypeLabel","assignLabelColors","entries","totals","e","ranked","ak","av","bk","bv","colors","key","i","assignMixColors","groups","g","useEntityStateColors","scope","enabled","query","active","dwell","roleData","useRoleDetails","subtypeSourced","useMemo","r","_a","_b","useCallback","interval","state","categoryOf","iv","TIMELINE_PAGE_SIZE","EntityTimelinePanel","facetKey","role","entity","intervalColor","useEscalationAnalyticsEvents","closePanel","useShellPanel","data","isLoading","systemColor","resolveColor","headIntervals","older","setOlder","hasEarlier","setHasEarlier","loadingEarlier","setLoadingEarlier","earlierError","setEarlierError","headSignature","appliedSignature","useRef","intervals","sortOrder","setSortOrder","displayed","gapPair","categoryColors","loadEarlier","oldest","page","prev","err","jsxs","jsx","o","Fragment","ArrowDown","ArrowUp","CopyLinkButton","Link","metadataFacetUrl","Search","X","LoadOlderControl","GapRow","IntervalRow","loading","onLoad","edge","copied","setCopied","copy","url","LinkIcon","color","formatDateTime","next","gapMs","formatDurationCompact"],"mappings":"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"}
1
+ {"version":3,"file":"EntityTimelinePanel-G6nzvgpl.js","sources":["../../src/api/escalation-analytics.ts","../../src/pages/operations/mix-colors.ts","../../src/pages/operations/useEntityStateColors.ts","../../src/components/escalation/EntityTimelinePanel.tsx"],"sourcesContent":["// ---------------------------------------------------------------------------\n// Escalation analytics hooks — grouped membership/dwell aggregates and\n// per-entity interval timelines (POST /escalations/aggregate-by-facets and\n// /timeline-by-facet). Every escalation is one open interval\n// [created_at, ended_at); these hooks read that time-series.\n//\n// Query-key discipline: inputs are canonicalized (key-sorted stringify) and\n// windows are quantized to the minute — the window end rounds UP to the next\n// minute boundary so (a) keys stay stable across renders within the minute\n// and (b) `to >= now` keeps the query in the server's now-anchored cache\n// (invalidated on escalation writes) instead of the 10-minute past cache.\n// Refresh is push-driven (useEscalationAnalyticsEvents) — never polling.\n// ---------------------------------------------------------------------------\n\nimport { useEffect, useState } from 'react';\nimport { useQuery } from '@tanstack/react-query';\nimport { apiFetch, ApiError } from './client';\n\n// Wire types — mirrors of types/analytics.ts on the server.\n\nexport interface AnalyticsWindow {\n from: string;\n to: string;\n}\n\nexport type AggregateMeasure =\n | { kind: 'membership'; asOf?: string }\n | { kind: 'dwell'; window: AnalyticsWindow };\n\nexport interface AnalyticsFilter {\n role?: string;\n roles?: string[];\n /** Entity facet key — resolves server-side to every role declaring it (the entity's system). */\n entity?: string;\n facets?: Record<string, unknown>;\n /** Case-insensitive facet-value prefix match, keyed by facet. */\n prefix?: Record<string, string>;\n /**\n * Exact facet-value match via the text projection (metadata->>key = value),\n * keyed by facet. Compares as text, so a categorical value the aggregate\n * returned (bool/enum/number, all surfaced as text) round-trips as a filter.\n */\n equals?: Record<string, string>;\n block?: Record<string, unknown>[];\n /** Row carries ANY of these facet sets (max 200) — the positive mirror of block. */\n anyOf?: Record<string, unknown>[];\n range?: { facet: string; op: '<' | '<=' | '>' | '>=' | '='; value: number }[];\n exists?: string[];\n}\n\nexport interface AggregateByFacetsInput {\n query: AnalyticsFilter;\n groupBy: { columns?: ('role' | 'subtype' | 'status')[]; facets?: string[]; state?: boolean };\n measure: AggregateMeasure;\n distinctBy?: string;\n orderBy?: { field: string; direction?: 'asc' | 'desc' }[];\n limit?: number;\n offset?: number;\n}\n\nexport interface AggregateRow {\n role?: string;\n subtype?: string | null;\n status?: string;\n facets: Record<string, string | null>;\n state?: string;\n count?: number;\n dwellSeconds?: number;\n sampleCount: number;\n}\n\nexport interface AggregateByFacetsResult {\n groups: AggregateRow[];\n overflow: boolean;\n}\n\nexport interface TimelineByFacetInput {\n facet: { key: string; value: string };\n query?: AnalyticsFilter;\n window?: AnalyticsWindow;\n select?: { columns?: ('role' | 'subtype' | 'status')[]; facets?: string[] };\n /** Interval order by startedAt (default asc). */\n order?: 'asc' | 'desc';\n /** Strict startedAt upper bound — the \"load earlier\" cursor. */\n before?: string;\n limit?: number;\n}\n\nexport interface TimelineInterval {\n role: string;\n subtype?: string | null;\n status?: string;\n facets: Record<string, string | null>;\n startedAt: string;\n endedAt: string | null;\n durationSeconds: number;\n}\n\nexport interface TimelineByFacetResult {\n intervals: TimelineInterval[];\n overflow: boolean;\n}\n\n// ── Key + window helpers (exported for tests) ────────────────────────────────\n\n/** Key-order-independent stringify so `{a,b}` and `{b,a}` share one query key. */\nexport function canonicalStringify(value: unknown): string {\n return JSON.stringify(value, (_key, v) =>\n v && typeof v === 'object' && !Array.isArray(v)\n ? Object.fromEntries(Object.entries(v).sort(([a], [b]) => (a < b ? -1 : 1)))\n : v,\n );\n}\n\n/** The trailing window for a period: `to` = next minute boundary, `from` = to − hours. */\nexport function analyticsWindow(periodHours: number, now = Date.now()): AnalyticsWindow {\n const to = Math.ceil(now / 60_000) * 60_000;\n return {\n from: new Date(to - periodHours * 3_600_000).toISOString(),\n to: new Date(to).toISOString(),\n };\n}\n\n/**\n * A live trailing window that rolls forward on the minute. Without the tick a\n * quiet tab's `to` falls into the past and the server reclassifies the query\n * fully-past (10-minute cache) — a \"live\" board frozen for 10 minutes.\n */\nexport function useAnalyticsWindow(periodHours: number): AnalyticsWindow {\n const [window, setWindow] = useState(() => analyticsWindow(periodHours));\n useEffect(() => {\n setWindow(analyticsWindow(periodHours));\n let timer: ReturnType<typeof setTimeout>;\n const schedule = () => {\n // Fire just past the minute boundary so ceil() lands on the NEXT one.\n timer = setTimeout(() => {\n setWindow(analyticsWindow(periodHours));\n schedule();\n }, 60_000 - (Date.now() % 60_000) + 250);\n };\n schedule();\n return () => clearTimeout(timer);\n }, [periodHours]);\n return window;\n}\n\n/** 403 is a scope answer, not a transient fault — never retry it. */\nfunction retryUnlessForbidden(failureCount: number, error: unknown): boolean {\n if (error instanceof ApiError && error.status === 403) return false;\n return failureCount < 2;\n}\n\n// ── Hooks ─────────────────────────────────────────────────────────────────────\n\nexport function useAggregateByFacets(\n input: AggregateByFacetsInput | null,\n opts?: { enabled?: boolean },\n) {\n return useQuery<AggregateByFacetsResult>({\n queryKey: ['escAggregate', input ? canonicalStringify(input) : 'off'],\n queryFn: () =>\n apiFetch('/escalations/aggregate-by-facets', {\n method: 'POST',\n body: JSON.stringify(input),\n }),\n staleTime: 15_000,\n enabled: (opts?.enabled ?? true) && !!input,\n retry: retryUnlessForbidden,\n });\n}\n\n/** One-shot timeline fetch — cursor pages (\"load earlier\") outside the query cache. */\nexport function fetchTimelineByFacet(input: TimelineByFacetInput): Promise<TimelineByFacetResult> {\n return apiFetch('/escalations/timeline-by-facet', {\n method: 'POST',\n body: JSON.stringify(input),\n });\n}\n\nexport function useTimelineByFacet(\n input: TimelineByFacetInput | null,\n opts?: { enabled?: boolean },\n) {\n return useQuery<TimelineByFacetResult>({\n queryKey: ['escTimeline', input ? canonicalStringify(input) : 'off'],\n queryFn: () => fetchTimelineByFacet(input!),\n staleTime: 15_000,\n enabled: (opts?.enabled ?? true) && !!input,\n retry: retryUnlessForbidden,\n });\n}\n\n/** True when the query failed the analytics read gate (render the scope notice). */\nexport function isForbidden(error: unknown): boolean {\n return error instanceof ApiError && error.status === 403;\n}\n","// ---------------------------------------------------------------------------\n// State-mix color assignment — a small ordered palette of the -graphic status\n// tokens (chart-grade contrast) assigned to subtypes by dwell rank. The null\n// subtype is a REAL group (rows created without one): it gets a key, a color,\n// and a label — dropping it would misstate the total tracked time.\n// ---------------------------------------------------------------------------\n\nexport const MIX_PALETTE = [\n 'rgb(var(--lt-status-queued-graphic))', // sky\n 'rgb(var(--lt-status-claimed-graphic))', // orange\n 'rgb(var(--lt-status-success-graphic))', // green\n 'rgb(var(--lt-status-pending-graphic))', // amber\n 'rgb(var(--lt-status-error))', // red\n 'rgb(var(--lt-accent))',\n] as const;\n\nexport const NO_SUBTYPE_KEY = '∅';\nexport const NO_SUBTYPE_LABEL = 'no subtype';\n\n/** Map key for a subtype group (null/undefined collapse to the ∅ group). */\nexport function subtypeKey(subtype: string | null | undefined): string {\n return subtype ?? NO_SUBTYPE_KEY;\n}\n\nexport function subtypeLabel(subtype: string | null | undefined): string {\n return subtype ?? NO_SUBTYPE_LABEL;\n}\n\n/**\n * Stable ordered assignment over arbitrary labels: sorted by total weight\n * descending (alphabetical tiebreak so equal ranks never swap), palette\n * wrapping beyond its length. Deterministic for a given entry set.\n */\nexport function assignLabelColors(\n entries: Array<{ label: string; weight: number }>,\n): Map<string, string> {\n const totals = new Map<string, number>();\n for (const e of entries) totals.set(e.label, (totals.get(e.label) ?? 0) + e.weight);\n const ranked = [...totals.entries()].sort(\n ([ak, av], [bk, bv]) => bv - av || (ak < bk ? -1 : 1),\n );\n const colors = new Map<string, string>();\n ranked.forEach(([key], i) => colors.set(key, MIX_PALETTE[i % MIX_PALETTE.length]));\n return colors;\n}\n\n/** The subtype-keyed form used by the station MIX surfaces. */\nexport function assignMixColors(\n groups: { subtype?: string | null; dwellSeconds?: number; count?: number }[],\n): Map<string, string> {\n return assignLabelColors(\n groups.map((g) => ({ label: subtypeKey(g.subtype), weight: g.dwellSeconds ?? g.count ?? 0 })),\n );\n}\n","import { useCallback, useMemo } from 'react';\nimport {\n useAggregateByFacets,\n useAnalyticsWindow,\n type TimelineInterval,\n} from '../../api/escalation-analytics';\nimport { useRoleDetails } from '../../api/roles';\nimport { assignLabelColors } from './mix-colors';\n\n/**\n * A category-color resolver for an entity system's timeline intervals — the\n * same palette the entity-lens band uses, so a timeline opened anywhere (the\n * lens, the station detail panel, an escalation cell popover) colors its dots\n * by state identically. Each interval's state is derived exactly as the server\n * does (subtype-sourced roles → COALESCE(subtype, role), everyone else → role),\n * then mapped through colors assigned over the system's state dwell.\n *\n * A stable reference window (not the user's selected period) keeps the color\n * assignment consistent across contexts. Pass `enabled: false` when the opener\n * already supplies its own resolver, so this does no redundant fetching.\n */\nexport function useEntityStateColors(\n scope: { entity?: string; role?: string },\n { enabled = true, periodHours = 720 }: { enabled?: boolean; periodHours?: number } = {},\n): (interval: TimelineInterval) => string | undefined {\n const window = useAnalyticsWindow(periodHours);\n const query = scope.entity\n ? { entity: scope.entity }\n : scope.role\n ? { roles: [scope.role] }\n : null;\n const active = enabled && !!query;\n\n const dwell = useAggregateByFacets(\n active\n ? {\n query: query!,\n groupBy: { state: true },\n measure: { kind: 'dwell', window },\n orderBy: [{ field: 'dwellSeconds', direction: 'desc' }],\n }\n : null,\n );\n const now = useAggregateByFacets(\n active ? { query: query!, groupBy: { state: true }, measure: { kind: 'membership' } } : null,\n );\n const { data: roleData } = useRoleDetails({ enabled: active });\n\n const subtypeSourced = useMemo(\n () =>\n new Set(\n (roleData?.roles ?? [])\n .filter((r) => r.entity_state_source === 'subtype')\n .map((r) => r.role),\n ),\n [roleData],\n );\n const colors = useMemo(\n () =>\n assignLabelColors(\n [\n ...(dwell.data?.groups ?? []).filter((g) => (g.dwellSeconds ?? 0) > 0),\n ...(now.data?.groups ?? []),\n ].map((g) => ({ label: g.state ?? '—', weight: g.dwellSeconds ?? g.count ?? 0 })),\n ),\n [dwell.data, now.data],\n );\n\n return useCallback(\n (interval: TimelineInterval) => {\n const state = subtypeSourced.has(interval.role)\n ? (interval.subtype ?? interval.role)\n : interval.role;\n return colors.get(state);\n },\n [colors, subtypeSourced],\n );\n}\n","import { Fragment, useEffect, useRef, useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { X, Link as LinkIcon, Search, ArrowDown, ArrowUp } from 'lucide-react';\nimport {\n useTimelineByFacet,\n fetchTimelineByFacet,\n isForbidden,\n type AnalyticsFilter,\n type TimelineInterval,\n} from '../../api/escalation-analytics';\nimport { useEscalationAnalyticsEvents } from '../../hooks/useEventHooks';\nimport { useShellPanel } from '../../hooks/useShellPanel';\nimport { formatDateTime, formatDurationCompact } from '../../lib/format';\nimport { metadataFacetUrl } from '../../lib/facet-url';\nimport { assignLabelColors } from '../../pages/operations/mix-colors';\nimport { useEntityStateColors } from '../../pages/operations/useEntityStateColors';\n\n/** The category a row belongs to — its state within the process (subtype),\n * falling back to the role. This is the axis the timeline graphic colors by,\n * NOT the escalation lifecycle (resolved/expired) which would be near-uniform\n * across a mostly-completed history. */\nconst categoryOf = (iv: TimelineInterval): string => iv.subtype ?? iv.role;\n\n// Newest page size — the head query and every \"load earlier\" page.\nconst TIMELINE_PAGE_SIZE = 100;\n\n/**\n * One entity's escalation-interval timeline — every station the facet value\n * has moved through, as [started, ended) spans rendered chronologically. Gaps\n * between consecutive intervals are untracked time and render as explicit\n * separators: their size is the digital/physical settle latency, a health\n * signal, never hidden.\n *\n * The head query fetches the NEWEST page (order desc) so push-driven\n * invalidation keeps the recent end fresh; older pages accumulate locally via\n * the \"Load earlier\" cursor (`before` = oldest loaded startedAt) and reset\n * whenever the head page changes.\n *\n * Rendered in the global shell right panel (key 'entity-timeline'). Passing\n * `role` keeps the query inside the pond gate for read_all members; without\n * it the query is cross-role and needs a global principal — a 403 degrades to\n * a quiet scope notice.\n */\nexport function EntityTimelinePanel({\n facetKey,\n value,\n role,\n entity,\n intervalColor,\n}: {\n facetKey: string;\n value: string;\n role?: string;\n /** Scope to the derived entity system (every role declaring this entity facet). */\n entity?: string;\n /**\n * Optional color resolver from the opener (the entity lens): maps an interval\n * to the EXACT color the timeline graphic uses for its category, so the list\n * and the band agree. Absent (standalone opens), the panel colors from its\n * own intervals — consistent, but not cross-component identical.\n */\n intervalColor?: (interval: TimelineInterval) => string | undefined;\n}) {\n useEscalationAnalyticsEvents();\n const { closePanel } = useShellPanel();\n const scope: { query?: AnalyticsFilter } = entity\n ? { query: { entity } }\n : role\n ? { query: { roles: [role] } }\n : {};\n const { data, error, isLoading } = useTimelineByFacet({\n facet: { key: facetKey, value },\n ...scope,\n order: 'desc',\n limit: TIMELINE_PAGE_SIZE,\n });\n\n // Color resolution: the opener's resolver (the lens, period-matched to its\n // band) wins; otherwise derive the entity system's colors here so station\n // detail and cell popovers color by the same scheme. When a resolver is\n // supplied, the fallback does no fetching.\n const systemColor = useEntityStateColors({ entity, role }, { enabled: !intervalColor });\n const resolveColor = intervalColor ?? systemColor;\n\n // Head page, chronological. Older pages prepend before it.\n const headIntervals = [...(data?.intervals ?? [])].reverse();\n const [older, setOlder] = useState<TimelineInterval[]>([]);\n const [hasEarlier, setHasEarlier] = useState(false);\n const [loadingEarlier, setLoadingEarlier] = useState(false);\n const [earlierError, setEarlierError] = useState<string | null>(null);\n\n // Reset accumulated pages when the newest interval set changes — a new facet\n // value, or a push-driven head refetch that shifted the recent end.\n const headSignature = data\n ? `${value}|${data.intervals.length}|${data.intervals[0]?.startedAt ?? ''}|${data.intervals[data.intervals.length - 1]?.startedAt ?? ''}`\n : null;\n const appliedSignature = useRef<string | null>(null);\n useEffect(() => {\n if (headSignature === appliedSignature.current) return;\n appliedSignature.current = headSignature;\n setOlder([]);\n setHasEarlier(data?.overflow ?? false);\n setEarlierError(null);\n }, [headSignature, data]);\n\n const intervals = [...older, ...headIntervals];\n\n // Display order — the dashboard convention is most-recent first, so the\n // default is descending; the toggle is pure presentation over the loaded\n // window. Paging is unchanged either way: the head is the newest page and\n // the strict `before` cursor walks OLDER intervals — sound at thousands of\n // rows (a printer's lifetime runs to ~4500 intervals), no offsets to drift.\n const [sortOrder, setSortOrder] = useState<'desc' | 'asc'>('desc');\n const displayed = sortOrder === 'desc' ? [...intervals].reverse() : intervals;\n // The chronologically earlier/later pair for a rendered neighbor gap.\n const gapPair = (i: number): { prev: TimelineInterval; next: TimelineInterval } =>\n sortOrder === 'desc'\n ? { prev: displayed[i], next: displayed[i - 1] }\n : { prev: displayed[i - 1], next: displayed[i] };\n\n // Category colors — the same palette generator the timeline graphic uses,\n // keyed by each row's category and weighted by dwell so the dominant states\n // take the lead palette slots. The dot reads as \"what this was,\" matching the\n // band segments on the left.\n const categoryColors = assignLabelColors(\n intervals.map((iv) => ({ label: categoryOf(iv), weight: iv.durationSeconds })),\n );\n\n const loadEarlier = async () => {\n const oldest = intervals[0]?.startedAt;\n if (!oldest || loadingEarlier) return;\n setLoadingEarlier(true);\n setEarlierError(null);\n try {\n const page = await fetchTimelineByFacet({\n facet: { key: facetKey, value },\n ...scope,\n order: 'desc',\n before: oldest,\n limit: TIMELINE_PAGE_SIZE,\n });\n setOlder((prev) => [...[...page.intervals].reverse(), ...prev]);\n setHasEarlier(page.overflow);\n } catch (err) {\n setEarlierError((err as Error).message);\n } finally {\n setLoadingEarlier(false);\n }\n };\n\n return (\n <div className=\"h-full flex flex-col\">\n <div className=\"flex items-start justify-between px-5 pt-5 pb-3 border-b border-surface-border/40\">\n <div className=\"min-w-0\">\n <p className=\"text-2xs font-semibold uppercase tracking-widest text-text-tertiary\">\n Timeline · <span className=\"normal-case font-mono\">{facetKey}</span>\n </p>\n <p className=\"text-sm font-mono text-text-primary truncate mt-0.5\" title={value}>\n {value}\n </p>\n {intervals.length > 0 && (\n <p className=\"flex items-center gap-2 text-2xs text-text-quaternary mt-0.5\">\n <span>{intervals.length} interval{intervals.length === 1 ? '' : 's'}</span>\n <span className=\"h-2.5 w-px bg-surface-border\" />\n <button\n onClick={() => setSortOrder((o) => (o === 'desc' ? 'asc' : 'desc'))}\n className=\"inline-flex items-center gap-0.5 icon-link\"\n title={sortOrder === 'desc' ? 'Newest first — click for oldest first' : 'Oldest first — click for newest first'}\n data-testid=\"timeline-sort-toggle\"\n >\n {sortOrder === 'desc'\n ? <><ArrowDown className=\"w-2.5 h-2.5\" /> newest first</>\n : <><ArrowUp className=\"w-2.5 h-2.5\" /> oldest first</>}\n </button>\n </p>\n )}\n </div>\n <span className=\"flex items-center gap-1 mt-0.5 shrink-0 ml-2\">\n {entity && <CopyLinkButton entity={entity} value={value} />}\n {/* Search every queue for this facet value — the all-roles metadata search. */}\n <Link\n to={metadataFacetUrl(facetKey, value)}\n className=\"icon-link\"\n title={`Search every queue for ${facetKey} ${value}`}\n data-testid=\"timeline-facet-search\"\n >\n <Search className=\"w-3.5 h-3.5\" />\n </Link>\n <button onClick={() => closePanel('entity-timeline')} className=\"icon-link\">\n <X className=\"w-4 h-4\" />\n </button>\n </span>\n </div>\n\n <div className=\"flex-1 overflow-y-auto px-5 py-4\">\n {isLoading ? (\n <p className=\"text-2xs text-text-tertiary italic\">Loading timeline…</p>\n ) : isForbidden(error) ? (\n <p className=\"text-2xs text-text-tertiary\">\n Timeline requires full read access to this station's queue.\n </p>\n ) : error ? (\n <p className=\"text-2xs text-status-error\">{(error as Error).message}</p>\n ) : intervals.length === 0 ? (\n <p className=\"text-2xs text-text-quaternary\">\n No escalation has carried this value.\n </p>\n ) : (\n <div className=\"relative\">\n {/* Older pages continue in the reading direction: at the TOP when\n oldest-first, at the BOTTOM when newest-first. */}\n {sortOrder === 'asc' && (\n <LoadOlderControl\n hasEarlier={hasEarlier}\n loading={loadingEarlier}\n error={earlierError}\n onLoad={loadEarlier}\n edge=\"top\"\n />\n )}\n {/* Left rail behind the dots */}\n <div className=\"absolute left-[4px] top-2 bottom-2 w-px bg-surface-border\" />\n <div className=\"space-y-0\">\n {displayed.map((interval, i) => (\n <Fragment key={`${interval.startedAt}-${i}`}>\n {i > 0 && <GapRow {...gapPair(i)} />}\n <IntervalRow\n interval={interval}\n color={resolveColor(interval) ?? categoryColors.get(categoryOf(interval))}\n />\n </Fragment>\n ))}\n </div>\n {sortOrder === 'desc' && (\n <LoadOlderControl\n hasEarlier={hasEarlier}\n loading={loadingEarlier}\n error={earlierError}\n onLoad={loadEarlier}\n edge=\"bottom\"\n />\n )}\n </div>\n )}\n </div>\n </div>\n );\n}\n\n/** The cursor pager — walks strictly-older intervals a page at a time. */\nfunction LoadOlderControl({\n hasEarlier,\n loading,\n error,\n onLoad,\n edge,\n}: {\n hasEarlier: boolean;\n loading: boolean;\n error: string | null;\n onLoad: () => void;\n edge: 'top' | 'bottom';\n}) {\n if (!hasEarlier && !error) return null;\n return (\n <div className={`${edge === 'top' ? 'pb-2' : 'pt-2'} pl-5`}>\n {hasEarlier && (\n <button\n onClick={onLoad}\n disabled={loading}\n className=\"text-2xs text-text-tertiary hover:text-accent transition-colors disabled:opacity-40\"\n >\n {loading ? 'Loading older…' : 'Load older'}\n </button>\n )}\n {error && <p className=\"text-2xs text-status-error mt-1\">{error}</p>}\n </div>\n );\n}\n\n/** Copies the entity-lens deep link (?lens= + ?entity=) with a transient note. */\nfunction CopyLinkButton({ entity, value }: { entity: string; value: string }) {\n const [copied, setCopied] = useState(false);\n const timer = useRef<ReturnType<typeof setTimeout> | undefined>(undefined);\n useEffect(() => () => clearTimeout(timer.current), []);\n\n const copy = async () => {\n const url = `${window.location.origin}/operations?lens=${encodeURIComponent(entity)}&entity=${encodeURIComponent(value)}`;\n await navigator.clipboard.writeText(url);\n setCopied(true);\n clearTimeout(timer.current);\n timer.current = setTimeout(() => setCopied(false), 1500);\n };\n\n return (\n <span className=\"flex items-center gap-1\">\n {copied && <span className=\"text-2xs text-text-quaternary\">copied</span>}\n <button onClick={copy} className=\"icon-link\" title=\"Copy link to this timeline\">\n <LinkIcon className=\"w-3.5 h-3.5\" />\n </button>\n </span>\n );\n}\n\nfunction IntervalRow({ interval, color }: { interval: TimelineInterval; color?: string }) {\n return (\n <div className=\"relative flex gap-3 py-2\">\n {/* Filled dot in the row's CATEGORY color — matches the graphic's segments,\n so the list reads by state the same way the band does. */}\n <span\n className=\"w-[9px] h-[9px] rounded-full shrink-0 mt-1 relative z-10\"\n style={{ backgroundColor: color ?? 'rgb(var(--lt-surface-border))' }}\n title={categoryOf(interval)}\n />\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-baseline gap-2\">\n <span className=\"text-xs font-mono text-text-primary truncate\">\n {interval.role}\n {interval.subtype ? <span className=\"text-text-tertiary\"> · {interval.subtype}</span> : null}\n </span>\n <span className=\"text-2xs font-mono tabular-nums text-text-secondary ml-auto shrink-0\">\n {formatDurationCompact(interval.durationSeconds * 1000)}\n </span>\n </div>\n <p className=\"text-2xs font-mono text-text-tertiary mt-0.5\">\n {formatDateTime(interval.startedAt)} → {interval.endedAt ? formatDateTime(interval.endedAt) : 'now'}\n </p>\n </div>\n </div>\n );\n}\n\n/** Untracked time between two intervals — the settle latency between queues, surfaced. */\nfunction GapRow({ prev, next }: { prev: TimelineInterval; next: TimelineInterval }) {\n if (!prev.endedAt) return null;\n const gapMs = Date.parse(next.startedAt) - Date.parse(prev.endedAt);\n if (gapMs <= 1000) return null; // contiguous handoff\n return (\n <div className=\"relative flex items-center gap-3 py-1\">\n <span className=\"w-[9px] shrink-0\" />\n <div className=\"flex-1 border-t border-dashed border-surface-border/60\" />\n <span className=\"text-2xs text-text-quaternary shrink-0\">\n untracked · {formatDurationCompact(gapMs)}\n </span>\n <div className=\"flex-1 border-t border-dashed border-surface-border/60\" />\n </div>\n );\n}\n"],"names":["canonicalStringify","value","_key","v","a","b","analyticsWindow","periodHours","now","to","useAnalyticsWindow","window","setWindow","useState","useEffect","timer","schedule","retryUnlessForbidden","failureCount","error","ApiError","useAggregateByFacets","input","opts","useQuery","apiFetch","fetchTimelineByFacet","useTimelineByFacet","isForbidden","MIX_PALETTE","NO_SUBTYPE_KEY","NO_SUBTYPE_LABEL","subtypeKey","subtype","subtypeLabel","assignLabelColors","entries","totals","e","ranked","ak","av","bk","bv","colors","key","i","assignMixColors","groups","g","useEntityStateColors","scope","enabled","query","active","dwell","roleData","useRoleDetails","subtypeSourced","useMemo","r","_a","_b","useCallback","interval","state","categoryOf","iv","TIMELINE_PAGE_SIZE","EntityTimelinePanel","facetKey","role","entity","intervalColor","useEscalationAnalyticsEvents","closePanel","useShellPanel","data","isLoading","systemColor","resolveColor","headIntervals","older","setOlder","hasEarlier","setHasEarlier","loadingEarlier","setLoadingEarlier","earlierError","setEarlierError","headSignature","appliedSignature","useRef","intervals","sortOrder","setSortOrder","displayed","gapPair","categoryColors","loadEarlier","oldest","page","prev","err","jsxs","jsx","o","Fragment","ArrowDown","ArrowUp","CopyLinkButton","Link","metadataFacetUrl","Search","X","LoadOlderControl","GapRow","IntervalRow","loading","onLoad","edge","copied","setCopied","copy","url","LinkIcon","color","formatDateTime","next","gapMs","formatDurationCompact"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{a as l,j as e}from"./vendor-query-RUZdMvN6.js";import{I as w,ae as S,an as _,O as L,J as E}from"./index-CgXVDPW-.js";import{am as v,B as O,a8 as C,at as R}from"./vendor-icons-h-jlRSb9.js";import{e as q}from"./vendor-react-CdqEC2zv.js";const V=`{
1
+ import{a as l,j as e}from"./vendor-query-RUZdMvN6.js";import{I as w,ae as S,an as _,O as L,J as E}from"./index-zrTQcPjB.js";import{am as v,B as O,a8 as C,at as R}from"./vendor-icons-h-jlRSb9.js";import{e as q}from"./vendor-react-CdqEC2zv.js";const V=`{
2
2
  "x-lt-layout": "active-history",
3
3
  "x-lt-help": "# {{escalation.type}}\\nThe live item plus its history.",
4
4
  "x-lt-active": {
@@ -17,4 +17,4 @@ import{a as l,j as e}from"./vendor-query-RUZdMvN6.js";import{I as w,ae as S,an a
17
17
  "limit": 25
18
18
  }
19
19
  }`;function j(i){const t=i.trim();if(!t)return{ok:!0,value:null};try{const r=JSON.parse(t);return typeof r!="object"||Array.isArray(r)||r===null?{ok:!1}:{ok:!0,value:r}}catch{return{ok:!1}}}function I(){const{role:i}=q(),{data:t,isLoading:r}=w(),n=S(),s=((t==null?void 0:t.roles)??[]).find(a=>a.role===i),[c,o]=l.useState(""),[h,u]=l.useState(""),[y,d]=l.useState(!1),[N,x]=l.useState(!1),[p,m]=l.useState(!1),f=l.useRef(null);l.useEffect(()=>{!s||f.current===s.role||(f.current=s.role,o(s.list_schema?JSON.stringify(s.list_schema,null,2):""),u(""),d(!1),m(!1))},[s]);const g=a=>{o(a),d(!0),x(!1),m(!j(a).ok)},b=()=>{const a=j(c);if(!a.ok||!s){m(!a.ok);return}n.mutate({role:s.role,list_schema:a.value??null,change_summary:h.trim()||void 0},{onSuccess:()=>{d(!1),x(!0),u(""),window.setTimeout(()=>x(!1),2500)}})},k=a=>{o(a?JSON.stringify(a,null,2):""),d(!0),x(!1),m(!1)};return r?e.jsxs("div",{className:"animate-pulse space-y-4",children:[e.jsx("div",{className:"h-8 bg-surface-sunken rounded w-64"}),e.jsx("div",{className:"h-96 bg-surface-sunken rounded"})]}):s?e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-start justify-between mb-10",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-3 mb-1",children:[e.jsx(v,{className:"w-5 h-5 text-accent",strokeWidth:1.5}),e.jsx("h1",{className:"heading-3",children:"Escalations List Schema"}),e.jsx("button",{onClick:()=>{window.location.hash="#docs:dashboard.md:escalations-list-schema"},className:"text-text-quaternary hover:text-accent transition-colors",title:"Open docs for this page",children:e.jsx(O,{className:"w-4 h-4",strokeWidth:1.5})}),e.jsx("span",{className:"font-mono text-sm text-text-tertiary",children:s.role}),s.current_list_schema_version!=null&&e.jsxs("span",{className:"text-2xs font-mono text-text-quaternary",children:["v",s.current_list_schema_version," in use"]})]}),e.jsx("button",{onClick:()=>{window.location.hash="#docs:hitl-guide.md:escalations-list-schema"},className:"text-2xs text-accent hover:underline pl-8",children:"List authoring reference — layout modes, the active card, history, and token binding →"})]}),e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[N&&e.jsxs("span",{className:"flex items-center gap-1 text-xs text-status-success animate-page-enter",children:[e.jsx(C,{className:"w-3 h-3"})," Saved"]}),n.error&&e.jsx("span",{className:"text-xs text-status-error max-w-[220px] truncate",children:n.error.message}),e.jsx("button",{onClick:b,disabled:!y||p||n.isPending,className:"px-3 py-1.5 text-xs rounded-md bg-accent text-text-inverse hover:bg-accent-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:n.isPending?"Saving…":"Save Version"})]})]}),e.jsxs("div",{className:"grid grid-cols-3 gap-16 items-start",children:[e.jsxs("div",{className:"col-span-2 space-y-6",children:[e.jsx("textarea",{value:c,onChange:a=>g(a.target.value),rows:28,spellCheck:!1,className:"input text-xs font-mono w-full resize-y leading-relaxed",placeholder:V}),p&&e.jsx("p",{className:"text-2xs text-status-error -mt-4",children:"Invalid JSON"}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1.5",children:"Change Summary"}),e.jsx("input",{type:"text",value:h,onChange:a=>u(a.target.value),placeholder:"e.g., Added the history column",className:"input text-sm w-full"}),e.jsx("p",{className:"text-2xs text-text-quaternary mt-1.5",children:"Recorded on the version this save creates. Versions independently of the resolve form schema."})]})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3 pb-2 border-b border-surface-border",children:[e.jsx(R,{className:"w-4 h-4 text-text-quaternary",strokeWidth:1.5}),e.jsx("h2",{className:"section-h2",children:"Versions"})]}),e.jsx(J,{role:s.role,onLoad:k})]})]})]}):e.jsxs("div",{className:"flex flex-col items-center justify-center py-20 text-center",children:[e.jsx(v,{className:"w-12 h-12 text-text-quaternary mb-4",strokeWidth:1}),e.jsx("h2",{className:"heading-3 mb-2",children:"Role not found"})]})}function J({role:i,onLoad:t}){const{data:r}=_(i),n=(r==null?void 0:r.versions)??[];return n.length===0?e.jsx("p",{className:"text-2xs text-text-tertiary leading-relaxed",children:"Versions appear here after the first save. Each list-schema change adds one; earlier versions stay viewable and loadable as an editing base."}):e.jsx("div",{className:"divide-y divide-surface-border/40",children:n.map(s=>e.jsx(P,{role:i,summary:s,onLoad:t},s.version))})}function P({role:i,summary:t,onLoad:r}){const[n,s]=l.useState(!1),c=L(i,t.version,n);return e.jsxs("div",{className:"py-2",children:[e.jsxs("button",{onClick:()=>s(o=>!o),className:"w-full text-left group",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("span",{className:"text-xs font-mono text-text-secondary",children:["v",t.version]}),t.is_current&&e.jsx("span",{className:"text-2xs font-bold uppercase tracking-wider text-accent",children:"current"}),e.jsx("span",{className:"flex-1"}),e.jsx("span",{className:"text-2xs text-text-quaternary shrink-0",children:new Date(t.created_at).toLocaleDateString()})]}),t.change_summary&&e.jsx("p",{className:"mt-0.5 text-2xs text-text-tertiary truncate",children:t.change_summary})]}),n&&e.jsx("div",{className:"mt-2 space-y-2",children:c.data?e.jsxs(e.Fragment,{children:[c.data.list_schema?e.jsx(E,{data:c.data.list_schema,defaultCollapsed:!0}):e.jsx("p",{className:"text-2xs text-text-quaternary",children:"This version carries no list schema."}),e.jsx("button",{onClick:()=>r(c.data.list_schema??null),className:"text-2xs text-accent hover:underline",children:"Load into editor"})]}):e.jsx("p",{className:"text-2xs text-text-quaternary",children:"Loading…"})})]})}export{I as EscalationListSchemaPage};
20
- //# sourceMappingURL=EscalationListSchemaPage-YTgexl4f.js.map
20
+ //# sourceMappingURL=EscalationListSchemaPage-D-rVTQ1m.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EscalationListSchemaPage-YTgexl4f.js","sources":["../../src/pages/admin/roles/EscalationListSchemaPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { LayoutList, Check, History, BookOpen } from 'lucide-react';\nimport {\n useRoleDetails,\n useRoleListSchema,\n useRoleListSchemaVersions,\n useUpdateRole,\n type RoleListSchemaVersionSummary,\n} from '../../../api/roles';\nimport { JsonViewer } from '../../../components/common/data/JsonViewer';\n\n/**\n * The escalation LIST schema, on its own page (/admin/roles/:role/list-schema).\n * This is the rich view a role's list page renders when scoped to just this\n * role — the list-page analog of the resolve form schema. Versioned\n * INDEPENDENTLY of the form schema: saving here writes ONLY list_schema and\n * advances only current_list_schema_version, leaving the form version alone.\n */\n\nconst PLACEHOLDER = `{\n \"x-lt-layout\": \"active-history\",\n \"x-lt-help\": \"# {{escalation.type}}\\\\nThe live item plus its history.\",\n \"x-lt-active\": {\n \"title\": \"{{metadata.title}}\",\n \"subtitle\": \"Updated {{escalation.updated_at}}\",\n \"body\": \"{{payload.document_markdown}}\",\n \"fields\": [\n { \"label\": \"Owner\", \"value\": \"{{metadata.owner}}\" }\n ]\n },\n \"x-lt-history\": {\n \"row\": {\n \"title\": \"{{metadata.title}} rev {{metadata.revision}}\",\n \"subtitle\": \"{{escalation.status}} · {{escalation.resolved_at}}\"\n },\n \"limit\": 25\n }\n}`;\n\nfunction safeParseObject(text: string): { ok: boolean; value?: Record<string, unknown> | null } {\n const trimmed = text.trim();\n if (!trimmed) return { ok: true, value: null };\n try {\n const parsed = JSON.parse(trimmed);\n if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) return { ok: false };\n return { ok: true, value: parsed };\n } catch {\n return { ok: false };\n }\n}\n\nexport function EscalationListSchemaPage() {\n const { role: roleKey } = useParams<{ role: string }>();\n const { data, isLoading } = useRoleDetails();\n const updateRole = useUpdateRole();\n\n const role = (data?.roles ?? []).find((r) => r.role === roleKey);\n\n const [text, setText] = useState('');\n const [summary, setSummary] = useState('');\n const [dirty, setDirty] = useState(false);\n const [savedOk, setSavedOk] = useState(false);\n const [jsonError, setJsonError] = useState(false);\n\n // Seed once per role — refetches must not clobber in-progress edits.\n const seededFor = useRef<string | null>(null);\n useEffect(() => {\n if (!role || seededFor.current === role.role) return;\n seededFor.current = role.role;\n setText(role.list_schema ? JSON.stringify(role.list_schema, null, 2) : '');\n setSummary('');\n setDirty(false);\n setJsonError(false);\n }, [role]);\n\n const handleEdit = (value: string) => {\n setText(value);\n setDirty(true);\n setSavedOk(false);\n setJsonError(!safeParseObject(value).ok);\n };\n\n const handleSave = () => {\n const parsed = safeParseObject(text);\n if (!parsed.ok || !role) { setJsonError(!parsed.ok); return; }\n updateRole.mutate(\n { role: role.role, list_schema: parsed.value ?? null, change_summary: summary.trim() || undefined },\n {\n onSuccess: () => {\n setDirty(false);\n setSavedOk(true);\n setSummary('');\n window.setTimeout(() => setSavedOk(false), 2500);\n },\n },\n );\n };\n\n const loadIntoEditor = (schema: Record<string, unknown> | null) => {\n setText(schema ? JSON.stringify(schema, null, 2) : '');\n setDirty(true);\n setSavedOk(false);\n setJsonError(false);\n };\n\n if (isLoading) {\n return (\n <div className=\"animate-pulse space-y-4\">\n <div className=\"h-8 bg-surface-sunken rounded w-64\" />\n <div className=\"h-96 bg-surface-sunken rounded\" />\n </div>\n );\n }\n\n if (!role) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20 text-center\">\n <LayoutList className=\"w-12 h-12 text-text-quaternary mb-4\" strokeWidth={1} />\n <h2 className=\"heading-3 mb-2\">Role not found</h2>\n </div>\n );\n }\n\n return (\n <div>\n {/* ── Header ── */}\n <div className=\"flex items-start justify-between mb-10\">\n <div>\n <div className=\"flex items-center gap-3 mb-1\">\n <LayoutList className=\"w-5 h-5 text-accent\" strokeWidth={1.5} />\n <h1 className=\"heading-3\">Escalations List Schema</h1>\n <button\n onClick={() => { window.location.hash = '#docs:dashboard.md:escalations-list-schema'; }}\n className=\"text-text-quaternary hover:text-accent transition-colors\"\n title=\"Open docs for this page\"\n >\n <BookOpen className=\"w-4 h-4\" strokeWidth={1.5} />\n </button>\n <span className=\"font-mono text-sm text-text-tertiary\">{role.role}</span>\n {role.current_list_schema_version != null && (\n <span className=\"text-2xs font-mono text-text-quaternary\">\n v{role.current_list_schema_version} in use\n </span>\n )}\n </div>\n <button\n onClick={() => { window.location.hash = '#docs:hitl-guide.md:escalations-list-schema'; }}\n className=\"text-2xs text-accent hover:underline pl-8\"\n >\n List authoring reference — layout modes, the active card, history, and token binding →\n </button>\n </div>\n\n <div className=\"flex items-center gap-2 shrink-0\">\n {savedOk && (\n <span className=\"flex items-center gap-1 text-xs text-status-success animate-page-enter\">\n <Check className=\"w-3 h-3\" /> Saved\n </span>\n )}\n {updateRole.error && (\n <span className=\"text-xs text-status-error max-w-[220px] truncate\">\n {(updateRole.error as Error).message}\n </span>\n )}\n <button\n onClick={handleSave}\n disabled={!dirty || jsonError || updateRole.isPending}\n className=\"px-3 py-1.5 text-xs rounded-md bg-accent text-text-inverse hover:bg-accent-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {updateRole.isPending ? 'Saving…' : 'Save Version'}\n </button>\n </div>\n </div>\n\n {/* ── Editor + version rail ── */}\n <div className=\"grid grid-cols-3 gap-16 items-start\">\n <div className=\"col-span-2 space-y-6\">\n <textarea\n value={text}\n onChange={(e) => handleEdit(e.target.value)}\n rows={28}\n spellCheck={false}\n className=\"input text-xs font-mono w-full resize-y leading-relaxed\"\n placeholder={PLACEHOLDER}\n />\n {jsonError && <p className=\"text-2xs text-status-error -mt-4\">Invalid JSON</p>}\n\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1.5\">\n Change Summary\n </label>\n <input\n type=\"text\"\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"e.g., Added the history column\"\n className=\"input text-sm w-full\"\n />\n <p className=\"text-2xs text-text-quaternary mt-1.5\">\n Recorded on the version this save creates. Versions independently of the resolve form schema.\n </p>\n </div>\n </div>\n\n {/* Version history — view any snapshot, load one as the editing base */}\n <div>\n <div className=\"flex items-center gap-2 mb-3 pb-2 border-b border-surface-border\">\n <History className=\"w-4 h-4 text-text-quaternary\" strokeWidth={1.5} />\n <h2 className=\"section-h2\">Versions</h2>\n </div>\n <VersionRail role={role.role} onLoad={loadIntoEditor} />\n </div>\n </div>\n </div>\n );\n}\n\nfunction VersionRail({\n role,\n onLoad,\n}: {\n role: string;\n onLoad: (schema: Record<string, unknown> | null) => void;\n}) {\n const { data } = useRoleListSchemaVersions(role);\n const versions = data?.versions ?? [];\n\n if (versions.length === 0) {\n return (\n <p className=\"text-2xs text-text-tertiary leading-relaxed\">\n Versions appear here after the first save. Each list-schema change adds one;\n earlier versions stay viewable and loadable as an editing base.\n </p>\n );\n }\n\n return (\n <div className=\"divide-y divide-surface-border/40\">\n {versions.map((v) => (\n <VersionEntry key={v.version} role={role} summary={v} onLoad={onLoad} />\n ))}\n </div>\n );\n}\n\nfunction VersionEntry({\n role,\n summary,\n onLoad,\n}: {\n role: string;\n summary: RoleListSchemaVersionSummary;\n onLoad: (schema: Record<string, unknown> | null) => void;\n}) {\n const [open, setOpen] = useState(false);\n const snapshot = useRoleListSchema(role, summary.version, open);\n\n return (\n <div className=\"py-2\">\n <button onClick={() => setOpen((o) => !o)} className=\"w-full text-left group\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-mono text-text-secondary\">v{summary.version}</span>\n {summary.is_current && (\n <span className=\"text-2xs font-bold uppercase tracking-wider text-accent\">current</span>\n )}\n <span className=\"flex-1\" />\n <span className=\"text-2xs text-text-quaternary shrink-0\">\n {new Date(summary.created_at).toLocaleDateString()}\n </span>\n </div>\n {summary.change_summary && (\n <p className=\"mt-0.5 text-2xs text-text-tertiary truncate\">{summary.change_summary}</p>\n )}\n </button>\n {open && (\n <div className=\"mt-2 space-y-2\">\n {snapshot.data ? (\n <>\n {snapshot.data.list_schema\n ? <JsonViewer data={snapshot.data.list_schema} defaultCollapsed />\n : <p className=\"text-2xs text-text-quaternary\">This version carries no list schema.</p>}\n <button\n onClick={() => onLoad((snapshot.data!.list_schema ?? null) as Record<string, unknown> | null)}\n className=\"text-2xs text-accent hover:underline\"\n >\n Load into editor\n </button>\n </>\n ) : (\n <p className=\"text-2xs text-text-quaternary\">Loading…</p>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"names":["PLACEHOLDER","safeParseObject","text","trimmed","parsed","EscalationListSchemaPage","roleKey","useParams","data","isLoading","useRoleDetails","updateRole","useUpdateRole","role","r","setText","useState","summary","setSummary","dirty","setDirty","savedOk","setSavedOk","jsonError","setJsonError","seededFor","useRef","useEffect","handleEdit","value","handleSave","loadIntoEditor","schema","jsxs","jsx","LayoutList","BookOpen","Check","e","History","VersionRail","onLoad","useRoleListSchemaVersions","versions","v","VersionEntry","open","setOpen","snapshot","useRoleListSchema","Fragment","JsonViewer"],"mappings":"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"}
1
+ {"version":3,"file":"EscalationListSchemaPage-D-rVTQ1m.js","sources":["../../src/pages/admin/roles/EscalationListSchemaPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { LayoutList, Check, History, BookOpen } from 'lucide-react';\nimport {\n useRoleDetails,\n useRoleListSchema,\n useRoleListSchemaVersions,\n useUpdateRole,\n type RoleListSchemaVersionSummary,\n} from '../../../api/roles';\nimport { JsonViewer } from '../../../components/common/data/JsonViewer';\n\n/**\n * The escalation LIST schema, on its own page (/admin/roles/:role/list-schema).\n * This is the rich view a role's list page renders when scoped to just this\n * role — the list-page analog of the resolve form schema. Versioned\n * INDEPENDENTLY of the form schema: saving here writes ONLY list_schema and\n * advances only current_list_schema_version, leaving the form version alone.\n */\n\nconst PLACEHOLDER = `{\n \"x-lt-layout\": \"active-history\",\n \"x-lt-help\": \"# {{escalation.type}}\\\\nThe live item plus its history.\",\n \"x-lt-active\": {\n \"title\": \"{{metadata.title}}\",\n \"subtitle\": \"Updated {{escalation.updated_at}}\",\n \"body\": \"{{payload.document_markdown}}\",\n \"fields\": [\n { \"label\": \"Owner\", \"value\": \"{{metadata.owner}}\" }\n ]\n },\n \"x-lt-history\": {\n \"row\": {\n \"title\": \"{{metadata.title}} rev {{metadata.revision}}\",\n \"subtitle\": \"{{escalation.status}} · {{escalation.resolved_at}}\"\n },\n \"limit\": 25\n }\n}`;\n\nfunction safeParseObject(text: string): { ok: boolean; value?: Record<string, unknown> | null } {\n const trimmed = text.trim();\n if (!trimmed) return { ok: true, value: null };\n try {\n const parsed = JSON.parse(trimmed);\n if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) return { ok: false };\n return { ok: true, value: parsed };\n } catch {\n return { ok: false };\n }\n}\n\nexport function EscalationListSchemaPage() {\n const { role: roleKey } = useParams<{ role: string }>();\n const { data, isLoading } = useRoleDetails();\n const updateRole = useUpdateRole();\n\n const role = (data?.roles ?? []).find((r) => r.role === roleKey);\n\n const [text, setText] = useState('');\n const [summary, setSummary] = useState('');\n const [dirty, setDirty] = useState(false);\n const [savedOk, setSavedOk] = useState(false);\n const [jsonError, setJsonError] = useState(false);\n\n // Seed once per role — refetches must not clobber in-progress edits.\n const seededFor = useRef<string | null>(null);\n useEffect(() => {\n if (!role || seededFor.current === role.role) return;\n seededFor.current = role.role;\n setText(role.list_schema ? JSON.stringify(role.list_schema, null, 2) : '');\n setSummary('');\n setDirty(false);\n setJsonError(false);\n }, [role]);\n\n const handleEdit = (value: string) => {\n setText(value);\n setDirty(true);\n setSavedOk(false);\n setJsonError(!safeParseObject(value).ok);\n };\n\n const handleSave = () => {\n const parsed = safeParseObject(text);\n if (!parsed.ok || !role) { setJsonError(!parsed.ok); return; }\n updateRole.mutate(\n { role: role.role, list_schema: parsed.value ?? null, change_summary: summary.trim() || undefined },\n {\n onSuccess: () => {\n setDirty(false);\n setSavedOk(true);\n setSummary('');\n window.setTimeout(() => setSavedOk(false), 2500);\n },\n },\n );\n };\n\n const loadIntoEditor = (schema: Record<string, unknown> | null) => {\n setText(schema ? JSON.stringify(schema, null, 2) : '');\n setDirty(true);\n setSavedOk(false);\n setJsonError(false);\n };\n\n if (isLoading) {\n return (\n <div className=\"animate-pulse space-y-4\">\n <div className=\"h-8 bg-surface-sunken rounded w-64\" />\n <div className=\"h-96 bg-surface-sunken rounded\" />\n </div>\n );\n }\n\n if (!role) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20 text-center\">\n <LayoutList className=\"w-12 h-12 text-text-quaternary mb-4\" strokeWidth={1} />\n <h2 className=\"heading-3 mb-2\">Role not found</h2>\n </div>\n );\n }\n\n return (\n <div>\n {/* ── Header ── */}\n <div className=\"flex items-start justify-between mb-10\">\n <div>\n <div className=\"flex items-center gap-3 mb-1\">\n <LayoutList className=\"w-5 h-5 text-accent\" strokeWidth={1.5} />\n <h1 className=\"heading-3\">Escalations List Schema</h1>\n <button\n onClick={() => { window.location.hash = '#docs:dashboard.md:escalations-list-schema'; }}\n className=\"text-text-quaternary hover:text-accent transition-colors\"\n title=\"Open docs for this page\"\n >\n <BookOpen className=\"w-4 h-4\" strokeWidth={1.5} />\n </button>\n <span className=\"font-mono text-sm text-text-tertiary\">{role.role}</span>\n {role.current_list_schema_version != null && (\n <span className=\"text-2xs font-mono text-text-quaternary\">\n v{role.current_list_schema_version} in use\n </span>\n )}\n </div>\n <button\n onClick={() => { window.location.hash = '#docs:hitl-guide.md:escalations-list-schema'; }}\n className=\"text-2xs text-accent hover:underline pl-8\"\n >\n List authoring reference — layout modes, the active card, history, and token binding →\n </button>\n </div>\n\n <div className=\"flex items-center gap-2 shrink-0\">\n {savedOk && (\n <span className=\"flex items-center gap-1 text-xs text-status-success animate-page-enter\">\n <Check className=\"w-3 h-3\" /> Saved\n </span>\n )}\n {updateRole.error && (\n <span className=\"text-xs text-status-error max-w-[220px] truncate\">\n {(updateRole.error as Error).message}\n </span>\n )}\n <button\n onClick={handleSave}\n disabled={!dirty || jsonError || updateRole.isPending}\n className=\"px-3 py-1.5 text-xs rounded-md bg-accent text-text-inverse hover:bg-accent-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {updateRole.isPending ? 'Saving…' : 'Save Version'}\n </button>\n </div>\n </div>\n\n {/* ── Editor + version rail ── */}\n <div className=\"grid grid-cols-3 gap-16 items-start\">\n <div className=\"col-span-2 space-y-6\">\n <textarea\n value={text}\n onChange={(e) => handleEdit(e.target.value)}\n rows={28}\n spellCheck={false}\n className=\"input text-xs font-mono w-full resize-y leading-relaxed\"\n placeholder={PLACEHOLDER}\n />\n {jsonError && <p className=\"text-2xs text-status-error -mt-4\">Invalid JSON</p>}\n\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1.5\">\n Change Summary\n </label>\n <input\n type=\"text\"\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"e.g., Added the history column\"\n className=\"input text-sm w-full\"\n />\n <p className=\"text-2xs text-text-quaternary mt-1.5\">\n Recorded on the version this save creates. Versions independently of the resolve form schema.\n </p>\n </div>\n </div>\n\n {/* Version history — view any snapshot, load one as the editing base */}\n <div>\n <div className=\"flex items-center gap-2 mb-3 pb-2 border-b border-surface-border\">\n <History className=\"w-4 h-4 text-text-quaternary\" strokeWidth={1.5} />\n <h2 className=\"section-h2\">Versions</h2>\n </div>\n <VersionRail role={role.role} onLoad={loadIntoEditor} />\n </div>\n </div>\n </div>\n );\n}\n\nfunction VersionRail({\n role,\n onLoad,\n}: {\n role: string;\n onLoad: (schema: Record<string, unknown> | null) => void;\n}) {\n const { data } = useRoleListSchemaVersions(role);\n const versions = data?.versions ?? [];\n\n if (versions.length === 0) {\n return (\n <p className=\"text-2xs text-text-tertiary leading-relaxed\">\n Versions appear here after the first save. Each list-schema change adds one;\n earlier versions stay viewable and loadable as an editing base.\n </p>\n );\n }\n\n return (\n <div className=\"divide-y divide-surface-border/40\">\n {versions.map((v) => (\n <VersionEntry key={v.version} role={role} summary={v} onLoad={onLoad} />\n ))}\n </div>\n );\n}\n\nfunction VersionEntry({\n role,\n summary,\n onLoad,\n}: {\n role: string;\n summary: RoleListSchemaVersionSummary;\n onLoad: (schema: Record<string, unknown> | null) => void;\n}) {\n const [open, setOpen] = useState(false);\n const snapshot = useRoleListSchema(role, summary.version, open);\n\n return (\n <div className=\"py-2\">\n <button onClick={() => setOpen((o) => !o)} className=\"w-full text-left group\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-mono text-text-secondary\">v{summary.version}</span>\n {summary.is_current && (\n <span className=\"text-2xs font-bold uppercase tracking-wider text-accent\">current</span>\n )}\n <span className=\"flex-1\" />\n <span className=\"text-2xs text-text-quaternary shrink-0\">\n {new Date(summary.created_at).toLocaleDateString()}\n </span>\n </div>\n {summary.change_summary && (\n <p className=\"mt-0.5 text-2xs text-text-tertiary truncate\">{summary.change_summary}</p>\n )}\n </button>\n {open && (\n <div className=\"mt-2 space-y-2\">\n {snapshot.data ? (\n <>\n {snapshot.data.list_schema\n ? <JsonViewer data={snapshot.data.list_schema} defaultCollapsed />\n : <p className=\"text-2xs text-text-quaternary\">This version carries no list schema.</p>}\n <button\n onClick={() => onLoad((snapshot.data!.list_schema ?? null) as Record<string, unknown> | null)}\n className=\"text-2xs text-accent hover:underline\"\n >\n Load into editor\n </button>\n </>\n ) : (\n <p className=\"text-2xs text-text-quaternary\">Loading…</p>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"names":["PLACEHOLDER","safeParseObject","text","trimmed","parsed","EscalationListSchemaPage","roleKey","useParams","data","isLoading","useRoleDetails","updateRole","useUpdateRole","role","r","setText","useState","summary","setSummary","dirty","setDirty","savedOk","setSavedOk","jsonError","setJsonError","seededFor","useRef","useEffect","handleEdit","value","handleSave","loadIntoEditor","schema","jsxs","jsx","LayoutList","BookOpen","Check","e","History","VersionRail","onLoad","useRoleListSchemaVersions","versions","v","VersionEntry","open","setOpen","snapshot","useRoleListSchema","Fragment","JsonViewer"],"mappings":"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"}
@@ -1,4 +1,4 @@
1
- import{a as l,j as e}from"./vendor-query-RUZdMvN6.js";import{I as w,ae as S,af as _,Q as E,J as C}from"./index-CgXVDPW-.js";import{n as j,B as O,a8 as R,at as q}from"./vendor-icons-h-jlRSb9.js";import{e as J}from"./vendor-react-CdqEC2zv.js";const L=`{
1
+ import{a as l,j as e}from"./vendor-query-RUZdMvN6.js";import{I as w,ae as S,af as _,Q as E,J as C}from"./index-zrTQcPjB.js";import{n as j,B as O,a8 as R,at as q}from"./vendor-icons-h-jlRSb9.js";import{e as J}from"./vendor-react-CdqEC2zv.js";const L=`{
2
2
  "type": "object",
3
3
  "properties": {
4
4
  "approved": { "type": "boolean", "title": "Approve?" },
@@ -6,4 +6,4 @@ import{a as l,j as e}from"./vendor-query-RUZdMvN6.js";import{I as w,ae as S,af a
6
6
  },
7
7
  "required": ["approved"]
8
8
  }`;function v(o){const t=o.trim();if(!t)return{ok:!0,value:null};try{const r=JSON.parse(t);return typeof r!="object"||Array.isArray(r)||r===null?{ok:!1}:{ok:!0,value:r}}catch{return{ok:!1}}}function F(){const{role:o}=J(),{data:t,isLoading:r}=w(),n=S(),s=((t==null?void 0:t.roles)??[]).find(a=>a.role===o),[c,i]=l.useState(""),[h,u]=l.useState(""),[N,d]=l.useState(!1),[g,x]=l.useState(!1),[p,m]=l.useState(!1),f=l.useRef(null);l.useEffect(()=>{!s||f.current===s.role||(f.current=s.role,i(s.form_schema?JSON.stringify(s.form_schema,null,2):""),u(""),d(!1),m(!1))},[s]);const y=a=>{i(a),d(!0),x(!1),m(!v(a).ok)},b=()=>{const a=v(c);if(!a.ok||!s){m(!a.ok);return}n.mutate({role:s.role,form_schema:a.value??null,change_summary:h.trim()||void 0},{onSuccess:()=>{d(!1),x(!0),u(""),window.setTimeout(()=>x(!1),2500)}})},k=a=>{i(a?JSON.stringify(a,null,2):""),d(!0),x(!1),m(!1)};return r?e.jsxs("div",{className:"animate-pulse space-y-4",children:[e.jsx("div",{className:"h-8 bg-surface-sunken rounded w-64"}),e.jsx("div",{className:"h-96 bg-surface-sunken rounded"})]}):s?e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-start justify-between mb-10",children:[e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-3 mb-1",children:[e.jsx(j,{className:"w-5 h-5 text-accent",strokeWidth:1.5}),e.jsx("h1",{className:"heading-3",children:"Escalation Schema"}),e.jsx("button",{onClick:()=>{window.location.hash="#docs:dashboard.md:escalation-schema"},className:"text-text-quaternary hover:text-accent transition-colors",title:"Open docs for this page",children:e.jsx(O,{className:"w-4 h-4",strokeWidth:1.5})}),e.jsx("span",{className:"font-mono text-sm text-text-tertiary",children:s.role}),s.current_schema_version!=null&&e.jsxs("span",{className:"text-2xs font-mono text-text-quaternary",children:["v",s.current_schema_version," in use"]})]}),e.jsx("button",{onClick:()=>{window.location.hash="#docs:hitl-guide.md:json-schema-form-authoring"},className:"text-2xs text-accent hover:underline pl-8",children:"Form authoring reference — field types, widgets, and every x-lt-* keyword →"})]}),e.jsxs("div",{className:"flex items-center gap-2 shrink-0",children:[g&&e.jsxs("span",{className:"flex items-center gap-1 text-xs text-status-success animate-page-enter",children:[e.jsx(R,{className:"w-3 h-3"})," Saved"]}),n.error&&e.jsx("span",{className:"text-xs text-status-error max-w-[220px] truncate",children:n.error.message}),e.jsx("button",{onClick:b,disabled:!N||p||n.isPending,className:"px-3 py-1.5 text-xs rounded-md bg-accent text-text-inverse hover:bg-accent-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed",children:n.isPending?"Saving…":"Save Version"})]})]}),e.jsxs("div",{className:"grid grid-cols-3 gap-16 items-start",children:[e.jsxs("div",{className:"col-span-2 space-y-6",children:[e.jsx("textarea",{value:c,onChange:a=>y(a.target.value),rows:28,spellCheck:!1,className:"input text-xs font-mono w-full resize-y leading-relaxed",placeholder:L}),p&&e.jsx("p",{className:"text-2xs text-status-error -mt-4",children:"Invalid JSON"}),e.jsxs("div",{children:[e.jsx("label",{className:"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1.5",children:"Change Summary"}),e.jsx("input",{type:"text",value:h,onChange:a=>u(a.target.value),placeholder:"e.g., Added lotNumber field for shoe orders",className:"input text-sm w-full"}),e.jsx("p",{className:"text-2xs text-text-quaternary mt-1.5",children:"Recorded on the version this save creates."})]})]}),e.jsxs("div",{children:[e.jsxs("div",{className:"flex items-center gap-2 mb-3 pb-2 border-b border-surface-border",children:[e.jsx(q,{className:"w-4 h-4 text-text-quaternary",strokeWidth:1.5}),e.jsx("h2",{className:"section-h2",children:"Versions"})]}),e.jsx(V,{role:s.role,onLoad:k})]})]})]}):e.jsxs("div",{className:"flex flex-col items-center justify-center py-20 text-center",children:[e.jsx(j,{className:"w-12 h-12 text-text-quaternary mb-4",strokeWidth:1}),e.jsx("h2",{className:"heading-3 mb-2",children:"Role not found"})]})}function V({role:o,onLoad:t}){const{data:r}=_(o),n=(r==null?void 0:r.versions)??[];return n.length===0?e.jsx("p",{className:"text-2xs text-text-tertiary leading-relaxed",children:"Versions appear here after the first save. Each schema change adds one; earlier versions stay viewable and loadable as an editing base."}):e.jsx("div",{className:"divide-y divide-surface-border/40",children:n.map(s=>e.jsx(P,{role:o,summary:s,onLoad:t},s.version))})}function P({role:o,summary:t,onLoad:r}){const[n,s]=l.useState(!1),c=E(o,t.version,n);return e.jsxs("div",{className:"py-2",children:[e.jsxs("button",{onClick:()=>s(i=>!i),className:"w-full text-left group",children:[e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsxs("span",{className:"text-xs font-mono text-text-secondary",children:["v",t.version]}),t.is_current&&e.jsx("span",{className:"text-2xs font-bold uppercase tracking-wider text-accent",children:"current"}),e.jsx("span",{className:"flex-1"}),e.jsx("span",{className:"text-2xs text-text-quaternary shrink-0",children:new Date(t.created_at).toLocaleDateString()})]}),t.change_summary&&e.jsx("p",{className:"mt-0.5 text-2xs text-text-tertiary truncate",children:t.change_summary})]}),n&&e.jsx("div",{className:"mt-2 space-y-2",children:c.data?e.jsxs(e.Fragment,{children:[c.data.form_schema?e.jsx(C,{data:c.data.form_schema,defaultCollapsed:!0}):e.jsx("p",{className:"text-2xs text-text-quaternary",children:"This version carries no form schema."}),e.jsx("button",{onClick:()=>r(c.data.form_schema??null),className:"text-2xs text-accent hover:underline",children:"Load into editor"})]}):e.jsx("p",{className:"text-2xs text-text-quaternary",children:"Loading…"})})]})}export{F as EscalationSchemaPage};
9
- //# sourceMappingURL=EscalationSchemaPage-Sjk05eHP.js.map
9
+ //# sourceMappingURL=EscalationSchemaPage-CV0R_xY9.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"EscalationSchemaPage-Sjk05eHP.js","sources":["../../src/pages/admin/roles/EscalationSchemaPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Braces, Check, History, BookOpen } from 'lucide-react';\nimport {\n useRoleDetails,\n useRoleSchema,\n useRoleSchemaVersions,\n useUpdateRole,\n type RoleSchemaVersionSummary,\n} from '../../../api/roles';\nimport { JsonViewer } from '../../../components/common/data/JsonViewer';\n\n/**\n * The escalation form schema, on its own page (/admin/roles/:role/schema).\n * This is the form a person completes to resolve the role's escalations —\n * versioned, pinnable by workflows (conditional schemaVersion), and worth a\n * full editing surface. Saving here writes ONLY the schema: one PATCH with\n * form_schema (+ optional change summary); every actual change appends the\n * next immutable version.\n */\n\nconst PLACEHOLDER = `{\n \"type\": \"object\",\n \"properties\": {\n \"approved\": { \"type\": \"boolean\", \"title\": \"Approve?\" },\n \"notes\": { \"type\": \"string\", \"title\": \"Notes\" }\n },\n \"required\": [\"approved\"]\n}`;\n\nfunction safeParseObject(text: string): { ok: boolean; value?: Record<string, unknown> | null } {\n const trimmed = text.trim();\n if (!trimmed) return { ok: true, value: null };\n try {\n const parsed = JSON.parse(trimmed);\n if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) return { ok: false };\n return { ok: true, value: parsed };\n } catch {\n return { ok: false };\n }\n}\n\nexport function EscalationSchemaPage() {\n const { role: roleKey } = useParams<{ role: string }>();\n const { data, isLoading } = useRoleDetails();\n const updateRole = useUpdateRole();\n\n const role = (data?.roles ?? []).find((r) => r.role === roleKey);\n\n const [text, setText] = useState('');\n const [summary, setSummary] = useState('');\n const [dirty, setDirty] = useState(false);\n const [savedOk, setSavedOk] = useState(false);\n const [jsonError, setJsonError] = useState(false);\n\n // Seed the editor from the live schema exactly once per role — refetches\n // (cache invalidations, saves) must not clobber in-progress edits.\n const seededFor = useRef<string | null>(null);\n useEffect(() => {\n if (!role || seededFor.current === role.role) return;\n seededFor.current = role.role;\n setText(role.form_schema ? JSON.stringify(role.form_schema, null, 2) : '');\n setSummary('');\n setDirty(false);\n setJsonError(false);\n }, [role]);\n\n const handleEdit = (value: string) => {\n setText(value);\n setDirty(true);\n setSavedOk(false);\n setJsonError(!safeParseObject(value).ok);\n };\n\n const handleSave = () => {\n const parsed = safeParseObject(text);\n if (!parsed.ok || !role) { setJsonError(!parsed.ok); return; }\n // Saving just the schema — nothing else rides this PATCH.\n updateRole.mutate(\n { role: role.role, form_schema: parsed.value ?? null, change_summary: summary.trim() || undefined },\n {\n onSuccess: () => {\n setDirty(false);\n setSavedOk(true);\n setSummary('');\n window.setTimeout(() => setSavedOk(false), 2500);\n },\n },\n );\n };\n\n const loadIntoEditor = (schema: Record<string, unknown> | null) => {\n setText(schema ? JSON.stringify(schema, null, 2) : '');\n setDirty(true);\n setSavedOk(false);\n setJsonError(false);\n };\n\n if (isLoading) {\n return (\n <div className=\"animate-pulse space-y-4\">\n <div className=\"h-8 bg-surface-sunken rounded w-64\" />\n <div className=\"h-96 bg-surface-sunken rounded\" />\n </div>\n );\n }\n\n if (!role) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20 text-center\">\n <Braces className=\"w-12 h-12 text-text-quaternary mb-4\" strokeWidth={1} />\n <h2 className=\"heading-3 mb-2\">Role not found</h2>\n </div>\n );\n }\n\n return (\n <div>\n {/* ── Header ── */}\n <div className=\"flex items-start justify-between mb-10\">\n <div>\n <div className=\"flex items-center gap-3 mb-1\">\n <Braces className=\"w-5 h-5 text-accent\" strokeWidth={1.5} />\n <h1 className=\"heading-3\">Escalation Schema</h1>\n <button\n onClick={() => { window.location.hash = '#docs:dashboard.md:escalation-schema'; }}\n className=\"text-text-quaternary hover:text-accent transition-colors\"\n title=\"Open docs for this page\"\n >\n <BookOpen className=\"w-4 h-4\" strokeWidth={1.5} />\n </button>\n <span className=\"font-mono text-sm text-text-tertiary\">{role.role}</span>\n {role.current_schema_version != null && (\n <span className=\"text-2xs font-mono text-text-quaternary\">\n v{role.current_schema_version} in use\n </span>\n )}\n </div>\n <button\n onClick={() => { window.location.hash = '#docs:hitl-guide.md:json-schema-form-authoring'; }}\n className=\"text-2xs text-accent hover:underline pl-8\"\n >\n Form authoring reference — field types, widgets, and every x-lt-* keyword →\n </button>\n </div>\n\n <div className=\"flex items-center gap-2 shrink-0\">\n {savedOk && (\n <span className=\"flex items-center gap-1 text-xs text-status-success animate-page-enter\">\n <Check className=\"w-3 h-3\" /> Saved\n </span>\n )}\n {updateRole.error && (\n <span className=\"text-xs text-status-error max-w-[220px] truncate\">\n {(updateRole.error as Error).message}\n </span>\n )}\n <button\n onClick={handleSave}\n disabled={!dirty || jsonError || updateRole.isPending}\n className=\"px-3 py-1.5 text-xs rounded-md bg-accent text-text-inverse hover:bg-accent-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {updateRole.isPending ? 'Saving…' : 'Save Version'}\n </button>\n </div>\n </div>\n\n {/* ── Editor + version rail ── */}\n <div className=\"grid grid-cols-3 gap-16 items-start\">\n <div className=\"col-span-2 space-y-6\">\n <textarea\n value={text}\n onChange={(e) => handleEdit(e.target.value)}\n rows={28}\n spellCheck={false}\n className=\"input text-xs font-mono w-full resize-y leading-relaxed\"\n placeholder={PLACEHOLDER}\n />\n {jsonError && <p className=\"text-2xs text-status-error -mt-4\">Invalid JSON</p>}\n\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1.5\">\n Change Summary\n </label>\n <input\n type=\"text\"\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"e.g., Added lotNumber field for shoe orders\"\n className=\"input text-sm w-full\"\n />\n <p className=\"text-2xs text-text-quaternary mt-1.5\">\n Recorded on the version this save creates.\n </p>\n </div>\n </div>\n\n {/* Version history — view any snapshot, load one as the editing base */}\n <div>\n <div className=\"flex items-center gap-2 mb-3 pb-2 border-b border-surface-border\">\n <History className=\"w-4 h-4 text-text-quaternary\" strokeWidth={1.5} />\n <h2 className=\"section-h2\">Versions</h2>\n </div>\n <VersionRail role={role.role} onLoad={loadIntoEditor} />\n </div>\n </div>\n </div>\n );\n}\n\nfunction VersionRail({\n role,\n onLoad,\n}: {\n role: string;\n onLoad: (schema: Record<string, unknown> | null) => void;\n}) {\n const { data } = useRoleSchemaVersions(role);\n const versions = data?.versions ?? [];\n\n if (versions.length === 0) {\n return (\n <p className=\"text-2xs text-text-tertiary leading-relaxed\">\n Versions appear here after the first save. Each schema change adds one;\n earlier versions stay viewable and loadable as an editing base.\n </p>\n );\n }\n\n return (\n <div className=\"divide-y divide-surface-border/40\">\n {versions.map((v) => (\n <VersionEntry key={v.version} role={role} summary={v} onLoad={onLoad} />\n ))}\n </div>\n );\n}\n\nfunction VersionEntry({\n role,\n summary,\n onLoad,\n}: {\n role: string;\n summary: RoleSchemaVersionSummary;\n onLoad: (schema: Record<string, unknown> | null) => void;\n}) {\n const [open, setOpen] = useState(false);\n // Snapshot fetch is lazy — only when the entry is expanded.\n const snapshot = useRoleSchema(role, summary.version, open);\n\n return (\n <div className=\"py-2\">\n <button onClick={() => setOpen((o) => !o)} className=\"w-full text-left group\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-mono text-text-secondary\">v{summary.version}</span>\n {summary.is_current && (\n <span className=\"text-2xs font-bold uppercase tracking-wider text-accent\">current</span>\n )}\n <span className=\"flex-1\" />\n <span className=\"text-2xs text-text-quaternary shrink-0\">\n {new Date(summary.created_at).toLocaleDateString()}\n </span>\n </div>\n {summary.change_summary && (\n <p className=\"mt-0.5 text-2xs text-text-tertiary truncate\">{summary.change_summary}</p>\n )}\n </button>\n {open && (\n <div className=\"mt-2 space-y-2\">\n {snapshot.data ? (\n <>\n {snapshot.data.form_schema\n ? <JsonViewer data={snapshot.data.form_schema} defaultCollapsed />\n : <p className=\"text-2xs text-text-quaternary\">This version carries no form schema.</p>}\n <button\n onClick={() => onLoad((snapshot.data!.form_schema ?? null) as Record<string, unknown> | null)}\n className=\"text-2xs text-accent hover:underline\"\n >\n Load into editor\n </button>\n </>\n ) : (\n <p className=\"text-2xs text-text-quaternary\">Loading…</p>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"names":["PLACEHOLDER","safeParseObject","text","trimmed","parsed","EscalationSchemaPage","roleKey","useParams","data","isLoading","useRoleDetails","updateRole","useUpdateRole","role","r","setText","useState","summary","setSummary","dirty","setDirty","savedOk","setSavedOk","jsonError","setJsonError","seededFor","useRef","useEffect","handleEdit","value","handleSave","loadIntoEditor","schema","jsxs","jsx","Braces","BookOpen","Check","e","History","VersionRail","onLoad","useRoleSchemaVersions","versions","v","VersionEntry","open","setOpen","snapshot","useRoleSchema","o","Fragment","JsonViewer"],"mappings":"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"}
1
+ {"version":3,"file":"EscalationSchemaPage-CV0R_xY9.js","sources":["../../src/pages/admin/roles/EscalationSchemaPage.tsx"],"sourcesContent":["import { useState, useEffect, useRef } from 'react';\nimport { useParams } from 'react-router-dom';\nimport { Braces, Check, History, BookOpen } from 'lucide-react';\nimport {\n useRoleDetails,\n useRoleSchema,\n useRoleSchemaVersions,\n useUpdateRole,\n type RoleSchemaVersionSummary,\n} from '../../../api/roles';\nimport { JsonViewer } from '../../../components/common/data/JsonViewer';\n\n/**\n * The escalation form schema, on its own page (/admin/roles/:role/schema).\n * This is the form a person completes to resolve the role's escalations —\n * versioned, pinnable by workflows (conditional schemaVersion), and worth a\n * full editing surface. Saving here writes ONLY the schema: one PATCH with\n * form_schema (+ optional change summary); every actual change appends the\n * next immutable version.\n */\n\nconst PLACEHOLDER = `{\n \"type\": \"object\",\n \"properties\": {\n \"approved\": { \"type\": \"boolean\", \"title\": \"Approve?\" },\n \"notes\": { \"type\": \"string\", \"title\": \"Notes\" }\n },\n \"required\": [\"approved\"]\n}`;\n\nfunction safeParseObject(text: string): { ok: boolean; value?: Record<string, unknown> | null } {\n const trimmed = text.trim();\n if (!trimmed) return { ok: true, value: null };\n try {\n const parsed = JSON.parse(trimmed);\n if (typeof parsed !== 'object' || Array.isArray(parsed) || parsed === null) return { ok: false };\n return { ok: true, value: parsed };\n } catch {\n return { ok: false };\n }\n}\n\nexport function EscalationSchemaPage() {\n const { role: roleKey } = useParams<{ role: string }>();\n const { data, isLoading } = useRoleDetails();\n const updateRole = useUpdateRole();\n\n const role = (data?.roles ?? []).find((r) => r.role === roleKey);\n\n const [text, setText] = useState('');\n const [summary, setSummary] = useState('');\n const [dirty, setDirty] = useState(false);\n const [savedOk, setSavedOk] = useState(false);\n const [jsonError, setJsonError] = useState(false);\n\n // Seed the editor from the live schema exactly once per role — refetches\n // (cache invalidations, saves) must not clobber in-progress edits.\n const seededFor = useRef<string | null>(null);\n useEffect(() => {\n if (!role || seededFor.current === role.role) return;\n seededFor.current = role.role;\n setText(role.form_schema ? JSON.stringify(role.form_schema, null, 2) : '');\n setSummary('');\n setDirty(false);\n setJsonError(false);\n }, [role]);\n\n const handleEdit = (value: string) => {\n setText(value);\n setDirty(true);\n setSavedOk(false);\n setJsonError(!safeParseObject(value).ok);\n };\n\n const handleSave = () => {\n const parsed = safeParseObject(text);\n if (!parsed.ok || !role) { setJsonError(!parsed.ok); return; }\n // Saving just the schema — nothing else rides this PATCH.\n updateRole.mutate(\n { role: role.role, form_schema: parsed.value ?? null, change_summary: summary.trim() || undefined },\n {\n onSuccess: () => {\n setDirty(false);\n setSavedOk(true);\n setSummary('');\n window.setTimeout(() => setSavedOk(false), 2500);\n },\n },\n );\n };\n\n const loadIntoEditor = (schema: Record<string, unknown> | null) => {\n setText(schema ? JSON.stringify(schema, null, 2) : '');\n setDirty(true);\n setSavedOk(false);\n setJsonError(false);\n };\n\n if (isLoading) {\n return (\n <div className=\"animate-pulse space-y-4\">\n <div className=\"h-8 bg-surface-sunken rounded w-64\" />\n <div className=\"h-96 bg-surface-sunken rounded\" />\n </div>\n );\n }\n\n if (!role) {\n return (\n <div className=\"flex flex-col items-center justify-center py-20 text-center\">\n <Braces className=\"w-12 h-12 text-text-quaternary mb-4\" strokeWidth={1} />\n <h2 className=\"heading-3 mb-2\">Role not found</h2>\n </div>\n );\n }\n\n return (\n <div>\n {/* ── Header ── */}\n <div className=\"flex items-start justify-between mb-10\">\n <div>\n <div className=\"flex items-center gap-3 mb-1\">\n <Braces className=\"w-5 h-5 text-accent\" strokeWidth={1.5} />\n <h1 className=\"heading-3\">Escalation Schema</h1>\n <button\n onClick={() => { window.location.hash = '#docs:dashboard.md:escalation-schema'; }}\n className=\"text-text-quaternary hover:text-accent transition-colors\"\n title=\"Open docs for this page\"\n >\n <BookOpen className=\"w-4 h-4\" strokeWidth={1.5} />\n </button>\n <span className=\"font-mono text-sm text-text-tertiary\">{role.role}</span>\n {role.current_schema_version != null && (\n <span className=\"text-2xs font-mono text-text-quaternary\">\n v{role.current_schema_version} in use\n </span>\n )}\n </div>\n <button\n onClick={() => { window.location.hash = '#docs:hitl-guide.md:json-schema-form-authoring'; }}\n className=\"text-2xs text-accent hover:underline pl-8\"\n >\n Form authoring reference — field types, widgets, and every x-lt-* keyword →\n </button>\n </div>\n\n <div className=\"flex items-center gap-2 shrink-0\">\n {savedOk && (\n <span className=\"flex items-center gap-1 text-xs text-status-success animate-page-enter\">\n <Check className=\"w-3 h-3\" /> Saved\n </span>\n )}\n {updateRole.error && (\n <span className=\"text-xs text-status-error max-w-[220px] truncate\">\n {(updateRole.error as Error).message}\n </span>\n )}\n <button\n onClick={handleSave}\n disabled={!dirty || jsonError || updateRole.isPending}\n className=\"px-3 py-1.5 text-xs rounded-md bg-accent text-text-inverse hover:bg-accent-hover transition-colors disabled:opacity-40 disabled:cursor-not-allowed\"\n >\n {updateRole.isPending ? 'Saving…' : 'Save Version'}\n </button>\n </div>\n </div>\n\n {/* ── Editor + version rail ── */}\n <div className=\"grid grid-cols-3 gap-16 items-start\">\n <div className=\"col-span-2 space-y-6\">\n <textarea\n value={text}\n onChange={(e) => handleEdit(e.target.value)}\n rows={28}\n spellCheck={false}\n className=\"input text-xs font-mono w-full resize-y leading-relaxed\"\n placeholder={PLACEHOLDER}\n />\n {jsonError && <p className=\"text-2xs text-status-error -mt-4\">Invalid JSON</p>}\n\n <div>\n <label className=\"block text-2xs font-semibold uppercase tracking-widest text-text-tertiary mb-1.5\">\n Change Summary\n </label>\n <input\n type=\"text\"\n value={summary}\n onChange={(e) => setSummary(e.target.value)}\n placeholder=\"e.g., Added lotNumber field for shoe orders\"\n className=\"input text-sm w-full\"\n />\n <p className=\"text-2xs text-text-quaternary mt-1.5\">\n Recorded on the version this save creates.\n </p>\n </div>\n </div>\n\n {/* Version history — view any snapshot, load one as the editing base */}\n <div>\n <div className=\"flex items-center gap-2 mb-3 pb-2 border-b border-surface-border\">\n <History className=\"w-4 h-4 text-text-quaternary\" strokeWidth={1.5} />\n <h2 className=\"section-h2\">Versions</h2>\n </div>\n <VersionRail role={role.role} onLoad={loadIntoEditor} />\n </div>\n </div>\n </div>\n );\n}\n\nfunction VersionRail({\n role,\n onLoad,\n}: {\n role: string;\n onLoad: (schema: Record<string, unknown> | null) => void;\n}) {\n const { data } = useRoleSchemaVersions(role);\n const versions = data?.versions ?? [];\n\n if (versions.length === 0) {\n return (\n <p className=\"text-2xs text-text-tertiary leading-relaxed\">\n Versions appear here after the first save. Each schema change adds one;\n earlier versions stay viewable and loadable as an editing base.\n </p>\n );\n }\n\n return (\n <div className=\"divide-y divide-surface-border/40\">\n {versions.map((v) => (\n <VersionEntry key={v.version} role={role} summary={v} onLoad={onLoad} />\n ))}\n </div>\n );\n}\n\nfunction VersionEntry({\n role,\n summary,\n onLoad,\n}: {\n role: string;\n summary: RoleSchemaVersionSummary;\n onLoad: (schema: Record<string, unknown> | null) => void;\n}) {\n const [open, setOpen] = useState(false);\n // Snapshot fetch is lazy — only when the entry is expanded.\n const snapshot = useRoleSchema(role, summary.version, open);\n\n return (\n <div className=\"py-2\">\n <button onClick={() => setOpen((o) => !o)} className=\"w-full text-left group\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-xs font-mono text-text-secondary\">v{summary.version}</span>\n {summary.is_current && (\n <span className=\"text-2xs font-bold uppercase tracking-wider text-accent\">current</span>\n )}\n <span className=\"flex-1\" />\n <span className=\"text-2xs text-text-quaternary shrink-0\">\n {new Date(summary.created_at).toLocaleDateString()}\n </span>\n </div>\n {summary.change_summary && (\n <p className=\"mt-0.5 text-2xs text-text-tertiary truncate\">{summary.change_summary}</p>\n )}\n </button>\n {open && (\n <div className=\"mt-2 space-y-2\">\n {snapshot.data ? (\n <>\n {snapshot.data.form_schema\n ? <JsonViewer data={snapshot.data.form_schema} defaultCollapsed />\n : <p className=\"text-2xs text-text-quaternary\">This version carries no form schema.</p>}\n <button\n onClick={() => onLoad((snapshot.data!.form_schema ?? null) as Record<string, unknown> | null)}\n className=\"text-2xs text-accent hover:underline\"\n >\n Load into editor\n </button>\n </>\n ) : (\n <p className=\"text-2xs text-text-quaternary\">Loading…</p>\n )}\n </div>\n )}\n </div>\n );\n}\n"],"names":["PLACEHOLDER","safeParseObject","text","trimmed","parsed","EscalationSchemaPage","roleKey","useParams","data","isLoading","useRoleDetails","updateRole","useUpdateRole","role","r","setText","useState","summary","setSummary","dirty","setDirty","savedOk","setSavedOk","jsonError","setJsonError","seededFor","useRef","useEffect","handleEdit","value","handleSave","loadIntoEditor","schema","jsxs","jsx","Braces","BookOpen","Check","e","History","VersionRail","onLoad","useRoleSchemaVersions","versions","v","VersionEntry","open","setOpen","snapshot","useRoleSchema","o","Fragment","JsonViewer"],"mappings":"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"}
@@ -1,2 +1,2 @@
1
- import{a as m,j as e}from"./vendor-query-RUZdMvN6.js";import{k as u}from"./index-CgXVDPW-.js";import{e as p}from"./useEventHooks-CjV3uNxt.js";import{P as b}from"./PageHeader-CFVw67M6.js";import{S as n}from"./StatCard-BXQ8dD9Y.js";import{u as j}from"./vendor-react-CdqEC2zv.js";import"./vendor-icons-h-jlRSb9.js";const v=[{label:"1h",value:"1h"},{label:"24h",value:"24h"},{label:"7d",value:"7d"},{label:"30d",value:"30d"}];function c({value:i,colorClass:r,onClick:o}){return i===0?e.jsx("span",{className:"text-text-tertiary",children:"0"}):e.jsx("button",{onClick:o,className:`${r} hover:underline tabular-nums font-medium`,children:i})}function $(){var d,x;p();const i=j(),[r,o]=m.useState("24h"),{data:s}=u(r),l=t=>{const h=new URLSearchParams(t).toString();i(`/escalations/available${h?`?${h}`:""}`)},a="pb-2 text-2xs font-semibold uppercase tracking-widest text-text-tertiary";return e.jsxs("div",{children:[e.jsx(b,{title:"Escalations",docsHash:"#docs:dashboard.md:escalations-overview"}),e.jsx("div",{className:"flex items-center gap-1 mb-6",children:v.map(t=>e.jsx("button",{onClick:()=>o(t.value),className:`px-3 py-1 text-xs rounded-full transition-colors ${r===t.value?"bg-accent text-text-inverse":"text-text-tertiary hover:text-text-primary hover:bg-surface-hover"}`,children:t.label},t.value))}),e.jsxs("div",{className:"grid grid-cols-4 gap-4 mb-8",children:[e.jsx(n,{label:"Open",value:(s==null?void 0:s.pending)??"—",colorClass:"text-status-pending",onClick:()=>l()}),e.jsx(n,{label:"Claimed",value:(s==null?void 0:s.claimed)??"—",colorClass:"text-status-active",onClick:()=>l()}),e.jsx(n,{label:"Created",value:(s==null?void 0:s.created)??"—",onClick:()=>l()}),e.jsx(n,{label:"Resolved",value:(s==null?void 0:s.resolved)??"—",colorClass:"text-status-success",onClick:()=>l()})]}),(((d=s==null?void 0:s.by_role)==null?void 0:d.length)??0)>0&&e.jsx("div",{className:"mb-8",children:e.jsxs("table",{className:"w-full text-left",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-surface-border",children:[e.jsx("th",{className:a,children:"Role"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Pending"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Claimed"})]})}),e.jsx("tbody",{children:s.by_role.map(t=>e.jsxs("tr",{className:"border-b border-surface-border last:border-b-0",children:[e.jsx("td",{className:"py-3 text-sm font-mono text-text-primary",children:e.jsx("button",{onClick:()=>l({role:t.role}),className:"hover:text-accent hover:underline",children:t.role})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.pending,colorClass:"text-status-pending",onClick:()=>l({role:t.role})})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.claimed,colorClass:"text-status-active",onClick:()=>l({role:t.role})})})]},t.role))})]})}),(((x=s==null?void 0:s.by_type)==null?void 0:x.length)??0)>0&&e.jsx("div",{children:e.jsxs("table",{className:"w-full text-left",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-surface-border",children:[e.jsx("th",{className:a,children:"Type"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Pending"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Claimed"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Resolved"})]})}),e.jsx("tbody",{children:s.by_type.map(t=>e.jsxs("tr",{className:"border-b border-surface-border last:border-b-0",children:[e.jsx("td",{className:"py-3 text-sm font-mono text-text-primary",children:e.jsx("button",{onClick:()=>l({type:t.type}),className:"hover:text-accent hover:underline",children:t.type})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.pending,colorClass:"text-status-pending",onClick:()=>l({type:t.type})})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.claimed,colorClass:"text-status-active",onClick:()=>l({type:t.type})})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.resolved,colorClass:"text-status-success",onClick:()=>l({type:t.type})})})]},t.type))})]})}),s&&s.by_role.length===0&&s.by_type.length===0&&e.jsx("div",{className:"py-16 text-center",children:e.jsxs("p",{className:"text-sm text-text-tertiary",children:["No escalation activity in the last ",r]})})]})}export{$ as EscalationsOverview};
2
- //# sourceMappingURL=EscalationsOverview-gHWPjUpZ.js.map
1
+ import{a as m,j as e}from"./vendor-query-RUZdMvN6.js";import{k as u}from"./index-zrTQcPjB.js";import{e as p}from"./useEventHooks-JDpifqGZ.js";import{P as b}from"./PageHeader-CFVw67M6.js";import{S as n}from"./StatCard-BXQ8dD9Y.js";import{u as j}from"./vendor-react-CdqEC2zv.js";import"./vendor-icons-h-jlRSb9.js";const v=[{label:"1h",value:"1h"},{label:"24h",value:"24h"},{label:"7d",value:"7d"},{label:"30d",value:"30d"}];function c({value:i,colorClass:r,onClick:o}){return i===0?e.jsx("span",{className:"text-text-tertiary",children:"0"}):e.jsx("button",{onClick:o,className:`${r} hover:underline tabular-nums font-medium`,children:i})}function $(){var d,x;p();const i=j(),[r,o]=m.useState("24h"),{data:s}=u(r),l=t=>{const h=new URLSearchParams(t).toString();i(`/escalations/available${h?`?${h}`:""}`)},a="pb-2 text-2xs font-semibold uppercase tracking-widest text-text-tertiary";return e.jsxs("div",{children:[e.jsx(b,{title:"Escalations",docsHash:"#docs:dashboard.md:escalations-overview"}),e.jsx("div",{className:"flex items-center gap-1 mb-6",children:v.map(t=>e.jsx("button",{onClick:()=>o(t.value),className:`px-3 py-1 text-xs rounded-full transition-colors ${r===t.value?"bg-accent text-text-inverse":"text-text-tertiary hover:text-text-primary hover:bg-surface-hover"}`,children:t.label},t.value))}),e.jsxs("div",{className:"grid grid-cols-4 gap-4 mb-8",children:[e.jsx(n,{label:"Open",value:(s==null?void 0:s.pending)??"—",colorClass:"text-status-pending",onClick:()=>l()}),e.jsx(n,{label:"Claimed",value:(s==null?void 0:s.claimed)??"—",colorClass:"text-status-active",onClick:()=>l()}),e.jsx(n,{label:"Created",value:(s==null?void 0:s.created)??"—",onClick:()=>l()}),e.jsx(n,{label:"Resolved",value:(s==null?void 0:s.resolved)??"—",colorClass:"text-status-success",onClick:()=>l()})]}),(((d=s==null?void 0:s.by_role)==null?void 0:d.length)??0)>0&&e.jsx("div",{className:"mb-8",children:e.jsxs("table",{className:"w-full text-left",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-surface-border",children:[e.jsx("th",{className:a,children:"Role"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Pending"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Claimed"})]})}),e.jsx("tbody",{children:s.by_role.map(t=>e.jsxs("tr",{className:"border-b border-surface-border last:border-b-0",children:[e.jsx("td",{className:"py-3 text-sm font-mono text-text-primary",children:e.jsx("button",{onClick:()=>l({role:t.role}),className:"hover:text-accent hover:underline",children:t.role})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.pending,colorClass:"text-status-pending",onClick:()=>l({role:t.role})})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.claimed,colorClass:"text-status-active",onClick:()=>l({role:t.role})})})]},t.role))})]})}),(((x=s==null?void 0:s.by_type)==null?void 0:x.length)??0)>0&&e.jsx("div",{children:e.jsxs("table",{className:"w-full text-left",children:[e.jsx("thead",{children:e.jsxs("tr",{className:"border-b border-surface-border",children:[e.jsx("th",{className:a,children:"Type"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Pending"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Claimed"}),e.jsx("th",{className:`${a} text-right w-24`,children:"Resolved"})]})}),e.jsx("tbody",{children:s.by_type.map(t=>e.jsxs("tr",{className:"border-b border-surface-border last:border-b-0",children:[e.jsx("td",{className:"py-3 text-sm font-mono text-text-primary",children:e.jsx("button",{onClick:()=>l({type:t.type}),className:"hover:text-accent hover:underline",children:t.type})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.pending,colorClass:"text-status-pending",onClick:()=>l({type:t.type})})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.claimed,colorClass:"text-status-active",onClick:()=>l({type:t.type})})}),e.jsx("td",{className:"py-3 text-sm text-right",children:e.jsx(c,{value:t.resolved,colorClass:"text-status-success",onClick:()=>l({type:t.type})})})]},t.type))})]})}),s&&s.by_role.length===0&&s.by_type.length===0&&e.jsx("div",{className:"py-16 text-center",children:e.jsxs("p",{className:"text-sm text-text-tertiary",children:["No escalation activity in the last ",r]})})]})}export{$ as EscalationsOverview};
2
+ //# sourceMappingURL=EscalationsOverview-DMdx15CQ.js.map