@hotmeshio/long-tail 0.7.5 → 0.8.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/build/api/workflows/config.d.ts +7 -1
- package/build/api/workflows/config.js +7 -1
- package/build/api/workflows/discovery.js +1 -4
- package/build/lib/db/schemas/020_workflow_certified.sql +34 -0
- package/build/modules/ltconfig.d.ts +3 -2
- package/build/modules/ltconfig.js +4 -4
- package/build/routes/workflows/config.js +1 -0
- package/build/services/config/cache.js +1 -0
- package/build/services/config/read.js +2 -0
- package/build/services/config/sql.d.ts +2 -2
- package/build/services/config/sql.js +6 -5
- package/build/services/config/write.js +13 -0
- package/build/services/escalation/bulk.d.ts +3 -1
- package/build/services/escalation/bulk.js +3 -1
- package/build/services/escalation/crud.d.ts +4 -3
- package/build/services/escalation/crud.js +4 -3
- package/build/start/workers.js +1 -0
- package/build/system/mcp-servers/admin/schemas.d.ts +3 -0
- package/build/system/mcp-servers/admin/schemas.js +3 -1
- package/build/system/mcp-servers/admin/workflow-config.js +1 -0
- package/build/types/config.d.ts +16 -0
- package/build/types/startup.d.ts +11 -1
- package/dashboard/dist/assets/{AdminDashboard-Bgomc_jH.js → AdminDashboard-9JLg41f-.js} +2 -2
- package/dashboard/dist/assets/{AdminDashboard-Bgomc_jH.js.map → AdminDashboard-9JLg41f-.js.map} +1 -1
- package/dashboard/dist/assets/{AgentConfigPage-C6gDgz21.js → AgentConfigPage-D5P1hcOc.js} +2 -2
- package/dashboard/dist/assets/{AgentConfigPage-C6gDgz21.js.map → AgentConfigPage-D5P1hcOc.js.map} +1 -1
- package/dashboard/dist/assets/{AgentDetailPage-0rrH5FmV.js → AgentDetailPage-DrijeAV8.js} +2 -2
- package/dashboard/dist/assets/{AgentDetailPage-0rrH5FmV.js.map → AgentDetailPage-DrijeAV8.js.map} +1 -1
- package/dashboard/dist/assets/{AgentsPage-DkIU2zed.js → AgentsPage-BW5d0JOt.js} +2 -2
- package/dashboard/dist/assets/{AgentsPage-DkIU2zed.js.map → AgentsPage-BW5d0JOt.js.map} +1 -1
- package/dashboard/dist/assets/{AvailableEscalationsPage-C9DAtNGp.js → AvailableEscalationsPage-CoZ4Lufr.js} +2 -2
- package/dashboard/dist/assets/{AvailableEscalationsPage-C9DAtNGp.js.map → AvailableEscalationsPage-CoZ4Lufr.js.map} +1 -1
- package/dashboard/dist/assets/{BotPicker-MnI3OyE7.js → BotPicker-xGUCxJo-.js} +2 -2
- package/dashboard/dist/assets/{BotPicker-MnI3OyE7.js.map → BotPicker-xGUCxJo-.js.map} +1 -1
- package/dashboard/dist/assets/{CapabilitiesPage-Cv_AD_5i.js → CapabilitiesPage-ChaRJiyg.js} +2 -2
- package/dashboard/dist/assets/{CapabilitiesPage-Cv_AD_5i.js.map → CapabilitiesPage-ChaRJiyg.js.map} +1 -1
- package/dashboard/dist/assets/{CollapsibleSection-Ne-Q3QFM.js → CollapsibleSection-CXMPecjj.js} +2 -2
- package/dashboard/dist/assets/{CollapsibleSection-Ne-Q3QFM.js.map → CollapsibleSection-CXMPecjj.js.map} +1 -1
- package/dashboard/dist/assets/{CredentialsPage-B3Mfwl22.js → CredentialsPage-BbdfZh1D.js} +2 -2
- package/dashboard/dist/assets/{CredentialsPage-B3Mfwl22.js.map → CredentialsPage-BbdfZh1D.js.map} +1 -1
- package/dashboard/dist/assets/{CronLabel-CtjUBRPo.js → CronLabel-CJUL50eO.js} +2 -2
- package/dashboard/dist/assets/{CronLabel-CtjUBRPo.js.map → CronLabel-CJUL50eO.js.map} +1 -1
- package/dashboard/dist/assets/{CustomDurationPicker-Cp1fgzPX.js → CustomDurationPicker-id6Tq64R.js} +2 -2
- package/dashboard/dist/assets/{CustomDurationPicker-Cp1fgzPX.js.map → CustomDurationPicker-id6Tq64R.js.map} +1 -1
- package/dashboard/dist/assets/{ElapsedCell-Gr5UlUbn.js → ElapsedCell-B8TrnyT5.js} +2 -2
- package/dashboard/dist/assets/{ElapsedCell-Gr5UlUbn.js.map → ElapsedCell-B8TrnyT5.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationSchemaPage-C5tuV6SJ.js → EscalationSchemaPage-C6UuZgCD.js} +2 -2
- package/dashboard/dist/assets/{EscalationSchemaPage-C5tuV6SJ.js.map → EscalationSchemaPage-C6UuZgCD.js.map} +1 -1
- package/dashboard/dist/assets/{EscalationsOverview-BIZVnSBF.js → EscalationsOverview-CjLyujJ3.js} +2 -2
- package/dashboard/dist/assets/{EscalationsOverview-BIZVnSBF.js.map → EscalationsOverview-CjLyujJ3.js.map} +1 -1
- package/dashboard/dist/assets/{EventTable-90aUfsNr.js → EventTable-D_rNmyGG.js} +2 -2
- package/dashboard/dist/assets/{EventTable-90aUfsNr.js.map → EventTable-D_rNmyGG.js.map} +1 -1
- package/dashboard/dist/assets/{GraphInvokePage-Io5ZucT8.js → GraphInvokePage-BMyrTnI6.js} +2 -2
- package/dashboard/dist/assets/{GraphInvokePage-Io5ZucT8.js.map → GraphInvokePage-BMyrTnI6.js.map} +1 -1
- package/dashboard/dist/assets/{HomePage-Cu0vUzTF.js → HomePage-MBv6pwk4.js} +2 -2
- package/dashboard/dist/assets/{HomePage-Cu0vUzTF.js.map → HomePage-MBv6pwk4.js.map} +1 -1
- package/dashboard/dist/assets/{ListToolbar-CdmIvEIF.js → ListToolbar--FkPpYDY.js} +2 -2
- package/dashboard/dist/assets/{ListToolbar-CdmIvEIF.js.map → ListToolbar--FkPpYDY.js.map} +1 -1
- package/dashboard/dist/assets/{McpOverview-C-Ypezuv.js → McpOverview-CtAFXv-T.js} +2 -2
- package/dashboard/dist/assets/{McpOverview-C-Ypezuv.js.map → McpOverview-CtAFXv-T.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryDetailPage-DdontKcj.js → McpQueryDetailPage-5Jzz2q2g.js} +2 -2
- package/dashboard/dist/assets/{McpQueryDetailPage-DdontKcj.js.map → McpQueryDetailPage-5Jzz2q2g.js.map} +1 -1
- package/dashboard/dist/assets/{McpQueryPage-D-1TaOGG.js → McpQueryPage-DveeRF5I.js} +2 -2
- package/dashboard/dist/assets/{McpQueryPage-D-1TaOGG.js.map → McpQueryPage-DveeRF5I.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunDetailPage-B1sCxKLD.js → McpRunDetailPage-BYIxBcA7.js} +2 -2
- package/dashboard/dist/assets/{McpRunDetailPage-B1sCxKLD.js.map → McpRunDetailPage-BYIxBcA7.js.map} +1 -1
- package/dashboard/dist/assets/{McpRunsPage-eWJ7cDa3.js → McpRunsPage-Cgke6ask.js} +2 -2
- package/dashboard/dist/assets/{McpRunsPage-eWJ7cDa3.js.map → McpRunsPage-Cgke6ask.js.map} +1 -1
- package/dashboard/dist/assets/OperationsPage-D2i7xrAI.js +2 -0
- package/dashboard/dist/assets/OperationsPage-D2i7xrAI.js.map +1 -0
- package/dashboard/dist/assets/{OperatorDashboard-YP6kP-f_.js → OperatorDashboard-DMQwodfo.js} +2 -2
- package/dashboard/dist/assets/{OperatorDashboard-YP6kP-f_.js.map → OperatorDashboard-DMQwodfo.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessDetailPage-C_BXQvBs.js → ProcessDetailPage-Dodrk5-z.js} +2 -2
- package/dashboard/dist/assets/{ProcessDetailPage-C_BXQvBs.js.map → ProcessDetailPage-Dodrk5-z.js.map} +1 -1
- package/dashboard/dist/assets/{ProcessesListPage-BNhFwTLl.js → ProcessesListPage-BVtkWVLY.js} +2 -2
- package/dashboard/dist/assets/{ProcessesListPage-BNhFwTLl.js.map → ProcessesListPage-BVtkWVLY.js.map} +1 -1
- package/dashboard/dist/assets/{RoleDetailPage-8GLNjj9I.js → RoleDetailPage-8NldZI49.js} +2 -2
- package/dashboard/dist/assets/{RoleDetailPage-8GLNjj9I.js.map → RoleDetailPage-8NldZI49.js.map} +1 -1
- package/dashboard/dist/assets/{RolesPage-B59tE2lZ.js → RolesPage-BJBjtFNY.js} +2 -2
- package/dashboard/dist/assets/{RolesPage-B59tE2lZ.js.map → RolesPage-BJBjtFNY.js.map} +1 -1
- package/dashboard/dist/assets/{RunAsSelector-CokM3Ifp.js → RunAsSelector-Cc1i017Z.js} +2 -2
- package/dashboard/dist/assets/{RunAsSelector-CokM3Ifp.js.map → RunAsSelector-Cc1i017Z.js.map} +1 -1
- package/dashboard/dist/assets/{StickyPagination-_H2j2CAH.js → StickyPagination-GQKFFTjL.js} +2 -2
- package/dashboard/dist/assets/{StickyPagination-_H2j2CAH.js.map → StickyPagination-GQKFFTjL.js.map} +1 -1
- package/dashboard/dist/assets/{StreamMessageDetail-sSfHDYIa.js → StreamMessageDetail-BYzfjWe_.js} +2 -2
- package/dashboard/dist/assets/{StreamMessageDetail-sSfHDYIa.js.map → StreamMessageDetail-BYzfjWe_.js.map} +1 -1
- package/dashboard/dist/assets/{SwimlaneTimeline-LZWtfBK3.js → SwimlaneTimeline-DLVmRrd-.js} +2 -2
- package/dashboard/dist/assets/{SwimlaneTimeline-LZWtfBK3.js.map → SwimlaneTimeline-DLVmRrd-.js.map} +1 -1
- package/dashboard/dist/assets/{TaskDetailPage-CHBV2V6R.js → TaskDetailPage-HzQpzp6D.js} +2 -2
- package/dashboard/dist/assets/{TaskDetailPage-CHBV2V6R.js.map → TaskDetailPage-HzQpzp6D.js.map} +1 -1
- package/dashboard/dist/assets/{TasksListPage-B71FxvrY.js → TasksListPage-DpqevgTz.js} +2 -2
- package/dashboard/dist/assets/{TasksListPage-B71FxvrY.js.map → TasksListPage-DpqevgTz.js.map} +1 -1
- package/dashboard/dist/assets/{TimeAgo-JgT0U_sd.js → TimeAgo-CftB8aJE.js} +2 -2
- package/dashboard/dist/assets/{TimeAgo-JgT0U_sd.js.map → TimeAgo-CftB8aJE.js.map} +1 -1
- package/dashboard/dist/assets/{TimestampCell-gEreK8Nn.js → TimestampCell-BSKj1jqT.js} +2 -2
- package/dashboard/dist/assets/{TimestampCell-gEreK8Nn.js.map → TimestampCell-BSKj1jqT.js.map} +1 -1
- package/dashboard/dist/assets/{ToolTestPanel-ChfHw9Aq.js → ToolTestPanel-DVQICZUQ.js} +2 -2
- package/dashboard/dist/assets/{ToolTestPanel-ChfHw9Aq.js.map → ToolTestPanel-DVQICZUQ.js.map} +1 -1
- package/dashboard/dist/assets/{TopicDetailPage-DigI0HGt.js → TopicDetailPage-B2LAQDEg.js} +2 -2
- package/dashboard/dist/assets/{TopicDetailPage-DigI0HGt.js.map → TopicDetailPage-B2LAQDEg.js.map} +1 -1
- package/dashboard/dist/assets/{TopicsPage-BHjlFpPN.js → TopicsPage-SwPAnN2C.js} +2 -2
- package/dashboard/dist/assets/{TopicsPage-BHjlFpPN.js.map → TopicsPage-SwPAnN2C.js.map} +1 -1
- package/dashboard/dist/assets/{UserName-uncbaKYf.js → UserName-Ce9gvlT5.js} +2 -2
- package/dashboard/dist/assets/{UserName-uncbaKYf.js.map → UserName-Ce9gvlT5.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowExecutionPage-DwzLKTkt.js → WorkflowExecutionPage-BI9NzeaH.js} +2 -2
- package/dashboard/dist/assets/{WorkflowExecutionPage-DwzLKTkt.js.map → WorkflowExecutionPage-BI9NzeaH.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowPill-BegV-iRF.js → WorkflowPill-BuNkWUVM.js} +2 -2
- package/dashboard/dist/assets/WorkflowPill-BuNkWUVM.js.map +1 -0
- package/dashboard/dist/assets/{WorkflowsDashboard-rpnGhGnl.js → WorkflowsDashboard-CvWSgQdE.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsDashboard-rpnGhGnl.js.map → WorkflowsDashboard-CvWSgQdE.js.map} +1 -1
- package/dashboard/dist/assets/{WorkflowsOverview-ihhW8DiS.js → WorkflowsOverview-CjBWlwlH.js} +2 -2
- package/dashboard/dist/assets/{WorkflowsOverview-ihhW8DiS.js.map → WorkflowsOverview-CjBWlwlH.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-C61-8y-E.js → YamlWorkflowDetailPage-Ds5o1UK9.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowDetailPage-C61-8y-E.js.map → YamlWorkflowDetailPage-Ds5o1UK9.js.map} +1 -1
- package/dashboard/dist/assets/{YamlWorkflowsPage-CMpgsLEo.js → YamlWorkflowsPage-ChwvZS4Z.js} +2 -2
- package/dashboard/dist/assets/{YamlWorkflowsPage-CMpgsLEo.js.map → YamlWorkflowsPage-ChwvZS4Z.js.map} +1 -1
- package/dashboard/dist/assets/{agents-27sErp5R.js → agents-A1U43853.js} +2 -2
- package/dashboard/dist/assets/{agents-27sErp5R.js.map → agents-A1U43853.js.map} +1 -1
- package/dashboard/dist/assets/{bots-CPHuAvjW.js → bots-CyzN6Uxi.js} +2 -2
- package/dashboard/dist/assets/{bots-CPHuAvjW.js.map → bots-CyzN6Uxi.js.map} +1 -1
- package/dashboard/dist/assets/{capabilities-BBTPqF-V.js → capabilities-BE1X5FVI.js} +2 -2
- package/dashboard/dist/assets/{capabilities-BBTPqF-V.js.map → capabilities-BE1X5FVI.js.map} +1 -1
- package/dashboard/dist/assets/{controlplane-C9j2mwX8.js → controlplane-BrwK_MT8.js} +2 -2
- package/dashboard/dist/assets/{controlplane-C9j2mwX8.js.map → controlplane-BrwK_MT8.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-CyJ_wz-f.js → escalation-DDdt7UdU.js} +2 -2
- package/dashboard/dist/assets/{escalation-CyJ_wz-f.js.map → escalation-DDdt7UdU.js.map} +1 -1
- package/dashboard/dist/assets/{escalation-columns-DU4Jyxxo.js → escalation-columns-D_zFY1zF.js} +2 -2
- package/dashboard/dist/assets/{escalation-columns-DU4Jyxxo.js.map → escalation-columns-D_zFY1zF.js.map} +1 -1
- package/dashboard/dist/assets/{index-BVHXREYZ.js → index-6MALVAEY.js} +2 -2
- package/dashboard/dist/assets/{index-BVHXREYZ.js.map → index-6MALVAEY.js.map} +1 -1
- package/dashboard/dist/assets/{index-CCHGKErT.js → index-BNWKksZ6.js} +2 -2
- package/dashboard/dist/assets/{index-CCHGKErT.js.map → index-BNWKksZ6.js.map} +1 -1
- package/dashboard/dist/assets/{index-D9pi0fs6.js → index-BtrNxebh.js} +2 -2
- package/dashboard/dist/assets/{index-D9pi0fs6.js.map → index-BtrNxebh.js.map} +1 -1
- package/dashboard/dist/assets/{index-Dzqt3g-K.js → index-C-rJQ-vB.js} +2 -2
- package/dashboard/dist/assets/{index-Dzqt3g-K.js.map → index-C-rJQ-vB.js.map} +1 -1
- package/dashboard/dist/assets/{index-BiLZJ43R.js → index-CGUBekEU.js} +2 -2
- package/dashboard/dist/assets/{index-BiLZJ43R.js.map → index-CGUBekEU.js.map} +1 -1
- package/dashboard/dist/assets/{index-BoZydcaW.js → index-CKXSKsVZ.js} +3 -3
- package/dashboard/dist/assets/{index-BoZydcaW.js.map → index-CKXSKsVZ.js.map} +1 -1
- package/dashboard/dist/assets/{index-CjXzZ19T.js → index-D2dDPGNk.js} +2 -2
- package/dashboard/dist/assets/{index-CjXzZ19T.js.map → index-D2dDPGNk.js.map} +1 -1
- package/dashboard/dist/assets/{index-CNprOGuS.js → index-D4U--3Xo.js} +2 -2
- package/dashboard/dist/assets/{index-CNprOGuS.js.map → index-D4U--3Xo.js.map} +1 -1
- package/dashboard/dist/assets/{index-BBQggGcf.js → index-D4gAsCGh.js} +2 -2
- package/dashboard/dist/assets/{index-BBQggGcf.js.map → index-D4gAsCGh.js.map} +1 -1
- package/dashboard/dist/assets/{index-DoivEaUZ.js → index-DEXouwBQ.js} +2 -2
- package/dashboard/dist/assets/{index-DoivEaUZ.js.map → index-DEXouwBQ.js.map} +1 -1
- package/dashboard/dist/assets/{index-CgeFUhUr.js → index-NnGsWGNS.js} +2 -2
- package/dashboard/dist/assets/{index-CgeFUhUr.js.map → index-NnGsWGNS.js.map} +1 -1
- package/dashboard/dist/assets/index-U27KBbTV.js +9 -0
- package/dashboard/dist/assets/index-U27KBbTV.js.map +1 -0
- package/dashboard/dist/assets/index-fwHQSJCp.js +6 -0
- package/dashboard/dist/assets/index-fwHQSJCp.js.map +1 -0
- package/dashboard/dist/assets/{knowledge-BErJA3Je.js → knowledge-B3ZIRTeu.js} +2 -2
- package/dashboard/dist/assets/{knowledge-BErJA3Je.js.map → knowledge-B3ZIRTeu.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-Cx9L42EN.js → mcp-BJjWiHdc.js} +2 -2
- package/dashboard/dist/assets/{mcp-Cx9L42EN.js.map → mcp-BJjWiHdc.js.map} +1 -1
- package/dashboard/dist/assets/{mcp-query-Cf6kcBoW.js → mcp-query-ksjBztwq.js} +2 -2
- package/dashboard/dist/assets/{mcp-query-Cf6kcBoW.js.map → mcp-query-ksjBztwq.js.map} +1 -1
- package/dashboard/dist/assets/{pipelines-CFctrv71.js → pipelines-DT1astmP.js} +2 -2
- package/dashboard/dist/assets/{pipelines-CFctrv71.js.map → pipelines-DT1astmP.js.map} +1 -1
- package/dashboard/dist/assets/{roles-CCXZtqDr.js → roles-UboiQxXT.js} +2 -2
- package/dashboard/dist/assets/{roles-CCXZtqDr.js.map → roles-UboiQxXT.js.map} +1 -1
- package/dashboard/dist/assets/{tasks-BPNj8ZWL.js → tasks-aL0K8WW3.js} +2 -2
- package/dashboard/dist/assets/{tasks-BPNj8ZWL.js.map → tasks-aL0K8WW3.js.map} +1 -1
- package/dashboard/dist/assets/{topics-CR4UBuSV.js → topics-CZO06VJl.js} +2 -2
- package/dashboard/dist/assets/{topics-CR4UBuSV.js.map → topics-CZO06VJl.js.map} +1 -1
- package/dashboard/dist/assets/{useEventHooks-BgdnN8Rj.js → useEventHooks-DlWkui_n.js} +2 -2
- package/dashboard/dist/assets/{useEventHooks-BgdnN8Rj.js.map → useEventHooks-DlWkui_n.js.map} +1 -1
- package/dashboard/dist/assets/{useNamespace-BDsKfT0f.js → useNamespace-BBzCmCIN.js} +2 -2
- package/dashboard/dist/assets/{useNamespace-BDsKfT0f.js.map → useNamespace-BBzCmCIN.js.map} +1 -1
- package/dashboard/dist/assets/{useYamlActivityEvents-CWZwHIlR.js → useYamlActivityEvents-ZS6wRSSc.js} +2 -2
- package/dashboard/dist/assets/{useYamlActivityEvents-CWZwHIlR.js.map → useYamlActivityEvents-ZS6wRSSc.js.map} +1 -1
- package/dashboard/dist/assets/{users-BqmKVXR1.js → users-Dzh04pRB.js} +2 -2
- package/dashboard/dist/assets/{users-BqmKVXR1.js.map → users-Dzh04pRB.js.map} +1 -1
- package/dashboard/dist/assets/{workflows-Dps45zjk.js → workflows-BTkHs6VT.js} +2 -2
- package/dashboard/dist/assets/workflows-BTkHs6VT.js.map +1 -0
- package/dashboard/dist/assets/{yaml-workflows-DhlTkbvY.js → yaml-workflows-Df8u3lgx.js} +2 -2
- package/dashboard/dist/assets/{yaml-workflows-DhlTkbvY.js.map → yaml-workflows-Df8u3lgx.js.map} +1 -1
- package/dashboard/dist/index.html +1 -1
- package/docs/api/http/escalations.md +3 -3
- package/docs/api/http/workflows.md +2 -1
- package/docs/api/mcp/admin.md +13 -7
- package/docs/api/sdk/escalations.md +3 -1
- package/docs/api/sdk/workflows.md +3 -2
- package/docs/architecture.md +2 -2
- package/docs/dashboard.md +6 -5
- package/docs/iam.md +3 -3
- package/docs/workflows.md +7 -5
- package/package.json +2 -2
- package/dashboard/dist/assets/OperationsPage-C5phWL0O.js +0 -2
- package/dashboard/dist/assets/OperationsPage-C5phWL0O.js.map +0 -1
- package/dashboard/dist/assets/WorkflowPill-BegV-iRF.js.map +0 -1
- package/dashboard/dist/assets/index-BHS-ubrT.js +0 -6
- package/dashboard/dist/assets/index-BHS-ubrT.js.map +0 -1
- package/dashboard/dist/assets/index-ChniBGqD.js +0 -9
- package/dashboard/dist/assets/index-ChniBGqD.js.map +0 -1
- package/dashboard/dist/assets/workflows-Dps45zjk.js.map +0 -1
package/docs/api/mcp/admin.md
CHANGED
|
@@ -15,7 +15,7 @@ Each tool below is marked **Read-safe**. A service-account key scoped `mcp:read`
|
|
|
15
15
|
|
|
16
16
|
## Compile Hints
|
|
17
17
|
|
|
18
|
-
Admin tools modify system configuration.
|
|
18
|
+
Admin tools modify system configuration. upsert_workflow_config's `certified` flag and delete_workflow_config change interceptor behavior.
|
|
19
19
|
|
|
20
20
|
## Tasks
|
|
21
21
|
|
|
@@ -231,7 +231,10 @@ Release all escalation claims that exceeded their lock duration.
|
|
|
231
231
|
|
|
232
232
|
### bulk_triage
|
|
233
233
|
|
|
234
|
-
Resolve escalations for triage and start mcpTriage workflows.
|
|
234
|
+
Resolve escalations for triage and start mcpTriage workflows. Rows backing a
|
|
235
|
+
live `condition()` waiter (`signal_key` set) stay `pending` and are excluded
|
|
236
|
+
from the result — settle those with the targeted resolve, which carries the
|
|
237
|
+
workflow's wake.
|
|
235
238
|
|
|
236
239
|
| | |
|
|
237
240
|
|---|---|
|
|
@@ -400,11 +403,12 @@ Create or replace a workflow configuration (certify). Activates the interceptor
|
|
|
400
403
|
|-------|------|----------|-------------|
|
|
401
404
|
| workflow_type | string | Yes | Workflow type identifier |
|
|
402
405
|
| invocable | boolean | No | Whether the workflow can be invoked externally |
|
|
406
|
+
| certified | boolean | No | Explicit HITL certification (interceptor treatment). Omitted → derived from roles/consumes presence. |
|
|
403
407
|
| task_queue | string | No | HotMesh task queue |
|
|
404
408
|
| default_role | string | No | Default escalation role |
|
|
405
409
|
| description | string | No | Workflow description |
|
|
406
410
|
| execute_as | string | No | Execution identity |
|
|
407
|
-
| roles | string[] | No |
|
|
411
|
+
| roles | string[] | No | Interceptor default for who resolves interceptor-raised escalations |
|
|
408
412
|
| invocation_roles | string[] | No | Roles allowed to invoke |
|
|
409
413
|
| consumes | string[] | No | Event topics consumed |
|
|
410
414
|
| tool_tags | string[] | No | Tool tags for routing |
|
|
@@ -412,7 +416,7 @@ Create or replace a workflow configuration (certify). Activates the interceptor
|
|
|
412
416
|
|
|
413
417
|
### delete_workflow_config
|
|
414
418
|
|
|
415
|
-
|
|
419
|
+
Unregister a workflow by deleting its config entry. To demote certified → registered while keeping the registration, use `upsert_workflow_config` with `certified: false`.
|
|
416
420
|
|
|
417
421
|
| | |
|
|
418
422
|
|---|---|
|
|
@@ -1160,10 +1164,12 @@ happened and where to look, not how to "fix" a job.
|
|
|
1160
1164
|
**Read this first — what is and isn't a problem.** A workflow suspended at a
|
|
1161
1165
|
`condition()` / `waitFor()` / `sleepFor()` can sit idle for days legitimately,
|
|
1162
1166
|
and HotMesh only bumps `updated_at` when a job's status changes. A frozen
|
|
1163
|
-
`updated_at` is therefore the *normal* signature of a wait, **not** a fault.
|
|
1167
|
+
`updated_at` is therefore the *normal* signature of a wait, **not** a fault. A
|
|
1168
|
+
reservation whose holder died self-heals: the message redelivers once the
|
|
1169
|
+
reclaim window lapses (~90s worst case) and the activity re-executes. The
|
|
1164
1170
|
genuinely broken signals are: a dead-lettered message (retries exhausted), a
|
|
1165
|
-
reservation
|
|
1166
|
-
|
|
1171
|
+
reservation still held past several reclaim windows, and a suspended waiter
|
|
1172
|
+
with **no** escalation row.
|
|
1167
1173
|
|
|
1168
1174
|
**Recommended flow:** start fleet-wide with `find_orphaned_signals` (the
|
|
1169
1175
|
genuinely-broken case) or `find_stalled_jobs` (candidates worth a look), then run
|
|
@@ -635,6 +635,8 @@ Trigger AI triage for multiple escalations.
|
|
|
635
635
|
|
|
636
636
|
Resolves each escalation and starts a triage workflow that uses MCP tools to analyze and potentially auto-resolve the issue.
|
|
637
637
|
|
|
638
|
+
Rows backing a live `condition()` waiter (`signal_key` set) stay `pending` and are excluded from `triaged` — their resolution must carry the workflow's wake, which only the targeted `resolve` delivers. Settle those individually with `resolve` or `cancel`.
|
|
639
|
+
|
|
638
640
|
```typescript
|
|
639
641
|
const result = await lt.escalations.bulkTriage({
|
|
640
642
|
ids: ['esc_1', 'esc_2'],
|
|
@@ -803,7 +805,7 @@ const result = await lt.escalations.resolveByMetadata({
|
|
|
803
805
|
|
|
804
806
|
## resolveByIds
|
|
805
807
|
|
|
806
|
-
Resolve a set of escalations by id in one guarded statement (the set-based sibling of `resolve`). For bookkeeping rows woken collectively — it does not deliver a per-row signal. RBAC: a scoped caller may only resolve rows whose role they hold.
|
|
808
|
+
Resolve a set of escalations by id in one guarded statement (the set-based sibling of `resolve`). For bookkeeping rows woken collectively — it does not deliver a per-row signal, and the store enforces that: rows backing a live `condition()` waiter (`signal_key` set) stay `pending` and are excluded from the result. Settle waiter rows individually with `resolve`, which carries the wake. RBAC: a scoped caller may only resolve rows whose role they hold.
|
|
807
809
|
|
|
808
810
|
```typescript
|
|
809
811
|
const result = await lt.escalations.resolveByIds({
|
|
@@ -152,7 +152,7 @@ const result = await lt.workflows.listDiscovered({ include_system: false });
|
|
|
152
152
|
|-------|------|----------|-------------|
|
|
153
153
|
| `include_system` | `boolean` | No | Include system workflows (default: false) |
|
|
154
154
|
|
|
155
|
-
**Returns:** `LTApiResult<{ workflows: [{ workflow_type, task_queue, registered, active, invocable, system, description, roles, invocation_roles, execute_as }] }>`
|
|
155
|
+
**Returns:** `LTApiResult<{ workflows: [{ workflow_type, task_queue, tier, registered, active, invocable, system, description, roles, invocation_roles, execute_as }] }>` — `tier` is `certified`, `registered`, or `durable`.
|
|
156
156
|
|
|
157
157
|
**Auth:** Not required
|
|
158
158
|
|
|
@@ -232,11 +232,12 @@ const result = await lt.workflows.upsertConfig({
|
|
|
232
232
|
|-------|------|----------|-------------|
|
|
233
233
|
| `type` | `string` | Yes | Workflow type name |
|
|
234
234
|
| `invocable` | `boolean` | No | Whether the workflow can be started via the API |
|
|
235
|
+
| `certified` | `boolean` | No | Explicit HITL certification (interceptor treatment). Omitted → derived from roles/consumes presence. |
|
|
235
236
|
| `task_queue` | `string \| null` | No | HotMesh task queue |
|
|
236
237
|
| `default_role` | `string` | No | Default escalation role |
|
|
237
238
|
| `description` | `string \| null` | No | Human-readable description |
|
|
238
239
|
| `execute_as` | `string \| null` | No | Service account for proxy invocation |
|
|
239
|
-
| `roles` | `string[]` | No |
|
|
240
|
+
| `roles` | `string[]` | No | Interceptor default for who resolves interceptor-raised escalations |
|
|
240
241
|
| `invocation_roles` | `string[]` | No | Roles that can invoke this workflow |
|
|
241
242
|
| `consumes` | `string[]` | No | Workflow types whose data this workflow consumes |
|
|
242
243
|
| `tool_tags` | `string[]` | No | MCP tool tags for discovery |
|
package/docs/architecture.md
CHANGED
|
@@ -112,7 +112,7 @@ A durable workflow has no entry in `lt_config_workflows`. It runs, but without i
|
|
|
112
112
|
|
|
113
113
|
### Certified
|
|
114
114
|
|
|
115
|
-
A certified workflow is durable plus the full Long Tail control plane. It has an entry in `lt_config_workflows`, which activates:
|
|
115
|
+
A certified workflow is durable plus the full Long Tail control plane. It has an entry in `lt_config_workflows` with `certified: true`, which activates:
|
|
116
116
|
|
|
117
117
|
- **Interceptor wrapping** — every execution is tracked as a task in `lt_tasks` with full audit trail.
|
|
118
118
|
- **Escalation chains** — returning `{ type: 'escalation' }` creates a reviewable record, routes to the correct role, and triggers re-runs on resolution.
|
|
@@ -120,7 +120,7 @@ A certified workflow is durable plus the full Long Tail control plane. It has an
|
|
|
120
120
|
- **Invocation controls** — `invocable: true` exposes the workflow for external invocation via the API and dashboard.
|
|
121
121
|
- **Execution identity** — roles, default assignees, and `execute_as` overrides are defined in the config.
|
|
122
122
|
|
|
123
|
-
In the dashboard, certified workflows display a **ShieldCheck** icon with a "Certified" label in accent blue.
|
|
123
|
+
In the dashboard, certified workflows display a **ShieldCheck** icon with a "Certified" label in accent blue. A registration without certification ("Registered" tier) keeps invocation controls and schema-driven forms while skipping the interceptor. Certification is the `certified` flag on the registration: promote by checking Certify (or `certified: true` in the config), demote by unchecking it — escalation roles and dependencies stay on the row. Unregistering deletes the entry entirely. The workflow code does not change.
|
|
124
124
|
|
|
125
125
|
### Pipeline
|
|
126
126
|
|
package/docs/dashboard.md
CHANGED
|
@@ -64,15 +64,15 @@ The top navigation bar contains:
|
|
|
64
64
|
|
|
65
65
|
### Workflow Registry
|
|
66
66
|
|
|
67
|
-
Shows every workflow the system has discovered across all registered workers. Each workflow displays one of three tiers:
|
|
67
|
+
Shows every workflow the system has discovered across all registered workers. Each workflow displays one of three tiers — direct states of the registration:
|
|
68
68
|
|
|
69
|
-
- **Certified** (ShieldCheck icon) — has an `lt_config_workflows` entry with
|
|
70
|
-
- **
|
|
71
|
-
- **Durable** (Wrench icon) —
|
|
69
|
+
- **Certified** (ShieldCheck icon) — has an `lt_config_workflows` entry with `certified: true`. Full interceptor tracking, escalation defaults, and invocation controls.
|
|
70
|
+
- **Registered** (Settings icon) — has a registration with `certified: false`. Invocation controls and schema-driven forms, without interceptor wrapping.
|
|
71
|
+
- **Durable** (Wrench icon) — runs as a HotMesh worker with no registration. Checkpointed execution and retries only.
|
|
72
72
|
|
|
73
73
|
**Columns:** Workflow (pill + description), Queue (bordered pill), Tier (icon + label), Access (escalation roles with shield icon, invocation roles with user-check icon).
|
|
74
74
|
|
|
75
|
-
**Row actions** (on hover): Play (invoke), Wrench (
|
|
75
|
+
**Row actions** (on hover): Play (invoke), Wrench (register durable), ShieldPlus (certify registered), ShieldOff (unregister — deletes the registration). To demote certified → registered, open the workflow and uncheck **Certify for HITL Escalation**; escalation roles, dependencies, and schemas stay on the registration.
|
|
76
76
|
|
|
77
77
|
**Inline config via `start()`:** Developers can declare workflow profiles directly in the `start()` config by adding a `config` block to any worker entry. This auto-seeds the workflow into `lt_config_workflows` at startup — no manual API calls or dashboard wizard needed. Roles referenced in the config are auto-created.
|
|
78
78
|
|
|
@@ -84,6 +84,7 @@ workers: [
|
|
|
84
84
|
config: {
|
|
85
85
|
description: 'My workflow description',
|
|
86
86
|
invocable: true,
|
|
87
|
+
certified: true,
|
|
87
88
|
roles: ['reviewer', 'admin'],
|
|
88
89
|
envelopeSchema: { data: { field1: '', field2: 0 } },
|
|
89
90
|
resolverSchema: { approved: true, notes: '' },
|
package/docs/iam.md
CHANGED
|
@@ -51,11 +51,11 @@ Long Tail has three workflow types (see the [Workflows Guide](workflows.md#three
|
|
|
51
51
|
|
|
52
52
|
**Durable workflows** are the baseline. Every workflow registered with HotMesh is durable: checkpointed to Postgres, restartable after crashes, with full IAM context. If an activity throws, the workflow fails.
|
|
53
53
|
|
|
54
|
-
**Certified workflows** add the interceptor. The interceptor wraps every execution so that failures escalate to a human reviewer instead of throwing. A certified workflow has an entry in `lt_config_workflows` that defines its escalation
|
|
54
|
+
**Certified workflows** add the interceptor. The interceptor wraps every execution so that failures escalate to a human reviewer instead of throwing. A certified workflow has an entry in `lt_config_workflows` with `certified: true` that defines its escalation defaults, invocation roles, and optional `execute_as` service account. It gets never-fail guarantees — when an LLM call returns garbage or an API is down, the workflow pauses and creates a human task rather than dying.
|
|
55
55
|
|
|
56
56
|
**Pipeline workflows** are compiled deterministic workflows that execute tool calls without an LLM. They inherit the IAM context of the invoking workflow — the principal, credentials, and trace lineage all propagate through the YAML DAG execution.
|
|
57
57
|
|
|
58
|
-
Any durable workflow can be promoted
|
|
58
|
+
Any durable workflow can be promoted through the Workflow Registry in the dashboard: registering creates the config entry (invocation + schemas), and the Certify checkbox turns on the interceptor. Unchecking Certify demotes to registered and keeps the config; unregistering deletes it. The workflow code does not change.
|
|
59
59
|
|
|
60
60
|
## Service Accounts
|
|
61
61
|
|
|
@@ -196,7 +196,7 @@ When an activity calls `getCredential('anthropic')` and no credential exists in
|
|
|
196
196
|
|
|
197
197
|
The dashboard surfaces IAM across four pages:
|
|
198
198
|
|
|
199
|
-
**Workflow Registry** (`/workflows/registry`) — lists all discovered workflows. Certified workflows display a ShieldCheck badge in accent blue; pipeline workflows display a Wand2 icon in purple; durable workflows show the standard Workflow icon. Use ShieldPlus to certify a
|
|
199
|
+
**Workflow Registry** (`/workflows/registry`) — lists all discovered workflows. Certified workflows display a ShieldCheck badge in accent blue; pipeline workflows display a Wand2 icon in purple; durable workflows show the standard Workflow icon. Use ShieldPlus to certify a registered workflow, the Certify checkbox on the detail page to promote or demote, and ShieldOff to unregister.
|
|
200
200
|
|
|
201
201
|
**Accounts** (`/admin/users`) — unified management for User Accounts and Service Accounts via tab toggle. Create service accounts, assign roles, generate API keys. The key generation flow displays the raw key once; it cannot be retrieved after dismissal.
|
|
202
202
|
|
package/docs/workflows.md
CHANGED
|
@@ -18,7 +18,7 @@ A durable workflow is a plain async function. No config entry, no interceptor in
|
|
|
18
18
|
|
|
19
19
|
### Certified
|
|
20
20
|
|
|
21
|
-
A certified workflow is durable plus the full Long Tail control plane. It has an entry in the `lt_config_workflows` table
|
|
21
|
+
A certified workflow is durable plus the full Long Tail control plane. It has an entry in the `lt_config_workflows` table with `certified: true`, which activates:
|
|
22
22
|
|
|
23
23
|
- **Interceptor wrapping** — every execution is tracked as a task in `lt_tasks` with full audit trail.
|
|
24
24
|
- **Escalation chains** — returning `{ type: 'escalation' }` creates a reviewable record, routes to the correct role, and triggers re-runs on resolution.
|
|
@@ -28,14 +28,16 @@ A certified workflow is durable plus the full Long Tail control plane. It has an
|
|
|
28
28
|
|
|
29
29
|
In the dashboard, certified workflows display a ShieldCheck icon with a "Certified" label in accent blue. Durable workflows display a muted "Durable" badge. Both are invocable from the same **Invoke Workflow** page; the distinction is how much operational infrastructure backs them.
|
|
30
30
|
|
|
31
|
-
To certify a workflow, create a config entry:
|
|
31
|
+
To certify a workflow, create a config entry with the `certified` flag:
|
|
32
32
|
|
|
33
33
|
```
|
|
34
34
|
PUT /api/workflows/myWorkflow/config
|
|
35
|
-
{ "default_role": "reviewer", "roles": ["reviewer"], "invocable": true }
|
|
35
|
+
{ "certified": true, "default_role": "reviewer", "roles": ["reviewer"], "invocable": true }
|
|
36
36
|
```
|
|
37
37
|
|
|
38
|
-
To de-certify,
|
|
38
|
+
A registration with `certified: false` is the "Registered" tier — invocation controls and schema-driven forms, without interceptor tracking. To de-certify, save the config with `certified: false` (roles and consumes stay on the row for re-certification). To unregister entirely, delete the config; the workflow remains durable.
|
|
39
|
+
|
|
40
|
+
The `roles` list is the interceptor default for who can claim and resolve interceptor-raised escalations. Escalations raised in workflow code choose their own role at the escalation boundary, and that role's versioned escalation schema takes precedence over anything on the workflow config.
|
|
39
41
|
|
|
40
42
|
### Pipeline
|
|
41
43
|
|
|
@@ -163,7 +165,7 @@ When an activity completes, its result is checkpointed in PostgreSQL. If the pro
|
|
|
163
165
|
|
|
164
166
|
- Activities that already completed are **not re-executed** — their cached results are replayed
|
|
165
167
|
- The workflow resumes from the **last checkpoint**, not the beginning
|
|
166
|
-
-
|
|
168
|
+
- An activity that was **mid-flight** at the crash is redelivered once its stream reservation lapses (`HMSH_RESERVATION_TIMEOUT_S`, ~90s worst case) and **re-executes** — delivery is at-least-once, so activity side effects (HTTP calls, bookings, emails) should be idempotent
|
|
167
169
|
|
|
168
170
|
This is what `proxyActivities()` provides. The raw function becomes a durable checkpoint:
|
|
169
171
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@hotmeshio/long-tail",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.8.0",
|
|
4
4
|
"description": "Long Tail Workflows — Durable AI workflows with human-in-the-loop escalation. Powered by PostgreSQL.",
|
|
5
5
|
"main": "./build/index.js",
|
|
6
6
|
"types": "./build/index.d.ts",
|
|
@@ -71,7 +71,7 @@
|
|
|
71
71
|
"@anthropic-ai/sdk": "^0.92.0",
|
|
72
72
|
"@aws-sdk/client-s3": "^3.1017.0",
|
|
73
73
|
"@aws-sdk/s3-request-presigner": "^3.1045.0",
|
|
74
|
-
"@hotmeshio/hotmesh": "^0.25.
|
|
74
|
+
"@hotmeshio/hotmesh": "^0.25.6",
|
|
75
75
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
76
76
|
"@opentelemetry/exporter-trace-otlp-proto": "^0.215.0",
|
|
77
77
|
"@opentelemetry/resources": "^2.5.1",
|
|
@@ -1,2 +0,0 @@
|
|
|
1
|
-
import{a as w,j as e}from"./vendor-query-B2UbickB.js";import{b as Me}from"./roles-CCXZtqDr.js";import{K as ye}from"./index-BoZydcaW.js";import{i as _e}from"./useEventHooks-BgdnN8Rj.js";import{P as Ce}from"./PageHeader-CFta2VaS.js";import{X as Re,ae as le,au as Pe,as as Se}from"./vendor-icons-BaQqUm-e.js";import{L as B,f as Le,c as qe}from"./vendor-react-CXumBFUA.js";const ge=800,je=270,I=40,Fe=76,ie=24,Ee=72,re=ge-I-Fe,be=je-ie-Ee,$=ie+be,me=I+re,j="0.5s cubic-bezier(0.4, 0, 0.2, 1)";function Ae(t){return t.length===0?"":t.map((n,l)=>`${l===0?"M":"L"} ${n.x.toFixed(1)} ${n.y.toFixed(1)}`).join(" ")}function Q(t){if(t.length===0)return"";if(t.length===1)return`M ${t[0].x} ${t[0].y}`;const n=[];for(let l=0;l<t.length-1;l++){const i=t[Math.max(0,l-1)],c=t[l],p=t[l+1],a=t[Math.min(t.length-1,l+2)],x=(c.x+(p.x-i.x)/6).toFixed(1),u=(c.y+(p.y-i.y)/6).toFixed(1),o=(p.x-(a.x-c.x)/6).toFixed(1),d=(p.y-(a.y-c.y)/6).toFixed(1);l===0&&n.push(`M ${c.x.toFixed(1)} ${c.y.toFixed(1)}`),n.push(`C ${x} ${u}, ${o} ${d}, ${p.x.toFixed(1)} ${p.y.toFixed(1)}`)}return n.join(" ")}function Oe(t,n){if(t.length<2||n.length<2)return"";const l=Q(t),i=[...n].reverse(),c=Q(i),p=c.slice(c.indexOf("C"));return`${l} L ${i[0].x.toFixed(1)} ${i[0].y.toFixed(1)} ${p} Z`}function We(t){return t>=1e4?`${Math.round(t/1e3)}k`:t>=1e3?`${(t/1e3).toFixed(1)}k`:String(t)}function se(t){return t.length>0?t[t.length-1]:void 0}function Ie(t){return t==null?{stroke:"#cbd5e1",fill:"#f8fafc"}:t>=1?{stroke:"#10b981",fill:"#ecfdf5"}:t>=.6?{stroke:"#f59e0b",fill:"#fffbeb"}:{stroke:"#ef4444",fill:"#fef2f2"}}const F="#6366f1",T="#0ea5e9",D="#4e6a5e",ve="#b0e0e6",E="#5b93ad",Te=13;function De(t,n){const l=[...t].sort((a,x)=>a.y-x.y);let i=-1/0;const c=l.map(a=>{const x=Math.max(a.y+3,i+Te);return i=x,{...a,labelY:x}}),p=c.length>0?c[c.length-1].labelY-n:0;if(p>0)for(const a of c)a.labelY-=p;return c}function Be({stations:t,selectedRole:n,onSelect:l,onUpstreamSelect:i,onPrioritySelect:c,periodHours:p}){const[a,x]=w.useState(null),u=t.length;if(u===0)return null;const o=s=>u===1?I+re/2:I+s/(u-1)*re,d=new Map(t.map((s,h)=>[s.role,o(h)])),y=t.map((s,h)=>{var W,G,L,U;const g=s.target_per_hour??null,R=g!=null?g*p:null;return{idx:h,x:o(h),expected:R,actual:((W=s.metric)==null?void 0:W.resolved)??0,active:((G=s.metric)==null?void 0:G.claimed)??0,pending:((L=s.metric)==null?void 0:L.pending)??0,priorityCount:((U=s.metric)==null?void 0:U.priority_count)??0}}),v=y.some(s=>s.expected!=null),f=Math.max(1,...y.map(s=>s.expected??0),...y.map(s=>s.actual),...y.map(s=>s.expected!=null?s.pending:0))*1.15,C=s=>ie+be*(1-Math.max(0,Math.min(s,f))/f),N=y.filter(s=>s.expected!=null),P=N.map(s=>({...s,y:C(s.expected)})),M=N.map(s=>({...s,y:C(s.actual)})),_=N.map(s=>({...s,y:C(s.active)})),k=N.map(s=>({...s,y:C(s.pending)})),z=Ae(P),H=Q(M),V=Q(_),K=Q(k),q=se(M),X=se(P),A=se(_),Z=se(k),J=M.length>=2&&q?`${H} L ${q.x.toFixed(1)} ${$} L ${M[0].x.toFixed(1)} ${$} Z`:"",O=N.some(s=>s.pending>0||s.active>0),ee=O&&_.length>=2&&A?`${V} L ${A.x.toFixed(1)} ${$} L ${_[0].x.toFixed(1)} ${$} Z`:"",Y=O?Oe(k,_):"",r=N.some(s=>s.active>0),m=N.some(s=>s.pending>s.active),S=De([X?{key:"target",text:"target",color:"#ef4444",y:X.y}:null,q?{key:"actual",text:"actual",color:D,y:q.y}:null,r&&A?{key:"active",text:"active",color:F,y:A.y}:null,m&&Z?{key:"queued",text:"queued",color:T,y:Z.y}:null].filter(Boolean),$+4);return e.jsxs("svg",{viewBox:`0 0 ${ge} ${je}`,className:"w-full h-full",preserveAspectRatio:"xMidYMid meet",style:{display:"block"},children:[t.map(s=>{if(!s.parent_role)return null;const h=d.get(s.parent_role);if(h==null)return null;const g=o(t.findIndex(R=>R.role===s.role));return e.jsx("line",{x1:h,y1:$,x2:g,y2:$,stroke:"#e2e8f0",strokeWidth:.75},`dep-${s.role}`)}),J&&e.jsx("path",{d:J,fill:D,opacity:.05,style:{transition:`d ${j}`}}),ee&&e.jsx("path",{d:ee,fill:F,opacity:.12,style:{transition:`d ${j}`}}),Y&&e.jsx("path",{d:Y,fill:T,opacity:.12,style:{transition:`d ${j}`}}),z&&e.jsx("path",{d:z,fill:"none",stroke:"#ef4444",strokeWidth:.5,strokeLinejoin:"round",opacity:.9,style:{transition:`d ${j}`}}),P.map(s=>e.jsx("text",{x:s.x,y:s.y-3.5,textAnchor:"middle",fontSize:6.5,fill:"#ef4444",fontFamily:"ui-monospace, monospace",fontWeight:"500",opacity:.7,style:{transition:`y ${j}`},children:Math.round(s.expected)},`tval-${s.idx}`)),O&&K&&e.jsx("path",{d:K,fill:"none",stroke:T,strokeWidth:.75,strokeDasharray:"2 3",strokeLinecap:"round",opacity:.55,style:{transition:`d ${j}`}}),V&&e.jsx("path",{d:V,fill:"none",stroke:F,strokeWidth:.75,strokeDasharray:"2 3",strokeLinecap:"round",opacity:.5,style:{transition:`d ${j}`}}),H&&e.jsx("path",{d:H,fill:"none",stroke:D,strokeWidth:1,strokeLinejoin:"round",strokeLinecap:"round",opacity:.75,style:{transition:`d ${j}`}}),_.map((s,h)=>{if(s.pending<=0&&s.active<=0)return null;const g=Math.max(2.5,Math.min(5,2.5+s.active/10)),R=s.pending>s.active,W=Math.min(k[h].y-4,s.y-g-4);return e.jsxs("g",{children:[s.pending>0&&e.jsx("text",{x:s.x,y:W,textAnchor:"middle",fontSize:7.5,fill:R?T:"#64748b",fontFamily:"ui-monospace, monospace",fontWeight:"600",style:{transition:`y ${j}`},children:s.pending}),e.jsxs("g",{transform:`translate(${s.x} ${s.y})`,style:{transition:`transform ${j}`},children:[e.jsx("circle",{r:g,fill:F,opacity:.85}),s.active>0&&e.jsx("text",{y:g+9,textAnchor:"middle",fontSize:7.5,fill:F,fontFamily:"ui-monospace, monospace",fontWeight:"600",children:s.active})]})]},`active-${s.idx}`)}),y.map(s=>{var he;const h=t[s.idx],g=s.x,R=s.expected!=null?C(s.actual):$,W=s.expected&&s.expected>0?s.actual/s.expected:null,{stroke:G}=Ie(s.expected!=null?W:null),L=Math.max(3,Math.min(5.5,3+s.actual/120)),U=h.role===n,xe=a===s.idx,de=s.expected==null?"idle · set a target rate to plot pace":`${s.actual} done · target ${Math.round(s.expected)}`+(s.pending>s.active?` · ${s.pending-s.active} waiting`:"")+(s.active>0?` · ${s.active} active`:"")+(s.priorityCount>0?` · ${s.priorityCount} priority`:""),te=de.length*5.5+20,pe=Math.max(I+te/2+4,Math.min(me-te/2-4,g)),ue=R-L-10;return e.jsxs("g",{onClick:()=>l(h.role),onMouseEnter:()=>x(s.idx),onMouseLeave:()=>x(null),style:{cursor:"pointer"},children:[e.jsxs("g",{transform:`translate(${g} ${R})`,style:{transition:`transform ${j}`},children:[e.jsx("circle",{r:22,fill:"transparent"}),e.jsx("circle",{r:L,fill:G,opacity:.9,style:{transition:`r ${j}`}}),s.expected!=null&&e.jsx("text",{y:-L-5,textAnchor:"middle",fontSize:9.5,fill:D,fontFamily:"ui-monospace, monospace",fontWeight:"500",children:We(s.actual)}),xe&&!U&&e.jsx("circle",{r:L+3,fill:"none",stroke:G,strokeWidth:1,opacity:.4}),U&&e.jsx("circle",{r:L+4,fill:"none",stroke:"#6366f1",strokeWidth:2})]}),s.priorityCount>0&&e.jsxs("g",{transform:`translate(${g+15} ${Math.max(R-15,12)})`,onClick:oe=>{oe.stopPropagation(),c==null||c(h.role)},style:{cursor:"pointer",transition:`transform ${j}`},children:[e.jsx("title",{children:`${s.priorityCount} past the priority threshold — click to pull oldest first`}),e.jsx("circle",{r:6.5+(String(s.priorityCount).length-1)*2,fill:ve,opacity:.9}),e.jsx("text",{y:2.5,textAnchor:"middle",fontSize:7,fill:E,fontFamily:"ui-monospace, monospace",fontWeight:"700",children:s.priorityCount})]}),(((he=h.upstream_roles)==null?void 0:he.length)??0)>0&&e.jsxs("g",{transform:`translate(${g-20} ${$})`,onClick:oe=>{oe.stopPropagation(),i==null||i(h.upstream_roles[0])},style:{cursor:"pointer"},children:[e.jsx("title",{children:`Fed by ${h.upstream_roles.join(", ")} — click to view that sequence`}),e.jsx("circle",{r:9,cy:-6,fill:"transparent"}),e.jsx("path",{d:"M -7 -14 C -3 -14 -1 -9 0 -2",fill:"none",stroke:"#94a3b8",strokeWidth:1,strokeDasharray:"2 2",strokeLinecap:"round"}),e.jsx("path",{d:"M 0 -2 L 8 -2",stroke:"#94a3b8",strokeWidth:1,strokeLinecap:"round"}),e.jsx("path",{d:"M 8 -2 l -3.5 -2 v 4 z",fill:"#94a3b8"})]}),e.jsx("text",{x:g,y:$+14,textAnchor:"middle",fontSize:9.5,fill:"#475569",fontFamily:"ui-sans-serif, sans-serif",fontWeight:"500",children:h.title??h.role}),e.jsx("text",{x:g,y:$+26,textAnchor:"middle",fontSize:8,fill:"#94a3b8",fontFamily:"ui-monospace, monospace",children:h.role}),xe&&e.jsxs("g",{children:[e.jsx("rect",{x:pe-te/2,y:ue-16,width:te,height:19,rx:3,fill:"#0f172a",opacity:.88}),e.jsx("text",{x:pe,y:ue-3,textAnchor:"middle",fontSize:8.5,fill:"white",fontFamily:"ui-monospace, monospace",children:de})]})]},h.role)}),!v&&e.jsx("text",{x:I+re/2,y:$+48,textAnchor:"middle",fontSize:8.5,fill:"#94a3b8",fontFamily:"ui-sans-serif, sans-serif",children:"Set target_per_hour on each role in Admin → Roles to enable the pace chart"}),S.map(s=>e.jsx("text",{x:me+6,y:s.labelY,fontSize:8,fill:s.color,fontFamily:"ui-sans-serif, sans-serif",fontWeight:s.key==="actual"?700:500,opacity:s.key==="active"?.85:1,style:{transition:`y ${j}`},children:s.text},s.key))]})}function ce(t){const n=`/escalations/available?role=${encodeURIComponent(t.role)}`;if(t.priority_facet){const l=JSON.stringify([{field:`metadata.${t.priority_facet}`,direction:"asc"}]);return`${n}&orderBy=${encodeURIComponent(l)}`}return`${n}&sort_by=created_at&order=asc`}const Ne=["15m","1h","24h","7d","30d"],ze=t=>Ne.includes(t);function ne(t){if(t==null)return"—";if(t<1){const n=Math.round(t*60);return n<=0?"< 1s":`${n}s`}return t<60?`${t.toFixed(0)}m`:`${(t/60).toFixed(1)}h`}function ae({label:t,value:n}){return e.jsxs("div",{className:"flex items-center justify-between py-1",children:[e.jsx("span",{className:"text-[10px] text-text-tertiary uppercase tracking-wider",children:t}),e.jsx("span",{className:"text-xs font-mono font-medium text-text-primary tabular-nums",children:n})]})}function He({period:t,onChange:n}){return e.jsx("div",{className:"flex items-center gap-1 mb-4",children:Ne.map(l=>e.jsx("button",{onClick:()=>n(l),className:`px-2 py-0.5 text-[10px] font-mono rounded transition-colors ${t===l?"bg-accent/10 text-accent font-semibold":"text-text-quaternary hover:text-text-secondary"}`,children:l},l))})}function Ve({role:t,globalPeriod:n,onClose:l}){const[i,c]=w.useState(ze(n)?n:"24h"),{data:p}=ye(i),a=p==null?void 0:p.stations.find(d=>d.role===t.role),x=t.sla_minutes??void 0,u=t.target_per_hour??void 0,o=t.worker_count??void 0;return e.jsxs(e.Fragment,{children:[e.jsxs("div",{className:"flex items-start justify-between mb-1",children:[e.jsxs("div",{className:"min-w-0 flex-1",children:[e.jsxs("div",{className:"flex items-baseline gap-1.5 flex-wrap",children:[e.jsx("span",{className:"text-sm font-mono font-bold text-text-primary",children:t.role}),t.title&&e.jsx("span",{className:"text-[10px] text-text-secondary",children:t.title})]}),t.description&&e.jsx("p",{className:"text-[10px] text-text-tertiary mt-0.5 line-clamp-2 leading-relaxed",children:t.description})]}),e.jsx("button",{onClick:l,className:"text-text-quaternary hover:text-text-secondary transition-colors mt-0.5 shrink-0 ml-2",children:e.jsx(Re,{className:"w-4 h-4"})})]}),e.jsxs(B,{to:`/admin/roles/${encodeURIComponent(t.role)}`,className:"flex items-center gap-1 text-[10px] text-accent hover:underline mb-4",children:[e.jsx(le,{className:"w-2.5 h-2.5"}),"Edit in Roles"]}),e.jsx(He,{period:i,onChange:c}),(u||x||o)&&e.jsxs("div",{className:"flex items-center gap-4 mb-4 text-[10px]",children:[u&&e.jsxs("div",{children:[e.jsxs("div",{className:"font-mono font-semibold text-text-primary",children:[u,"/h"]}),e.jsx("div",{className:"text-text-quaternary uppercase tracking-wider text-[8.5px]",children:"target"})]}),x&&e.jsxs("div",{children:[e.jsxs("div",{className:"font-mono font-semibold text-text-primary",children:[x,"m"]}),e.jsx("div",{className:"text-text-quaternary uppercase tracking-wider text-[8.5px]",children:"SLA"})]}),o&&e.jsxs("div",{children:[e.jsx("div",{className:"font-mono font-semibold text-text-primary",children:o}),e.jsx("div",{className:"text-text-quaternary uppercase tracking-wider text-[8.5px]",children:"workers"})]})]}),e.jsxs("div",{className:"grid grid-cols-2 gap-x-3",children:[e.jsxs("div",{children:[e.jsx("p",{className:"text-[9px] text-text-quaternary uppercase tracking-wider mb-1.5",children:"Wait (queue)"}),e.jsx(ae,{label:"P99",value:ne((a==null?void 0:a.wait.p99)??null)}),e.jsx(ae,{label:"avg",value:ne((a==null?void 0:a.wait.avg)??null)})]}),e.jsxs("div",{children:[e.jsx("p",{className:"text-[9px] text-text-quaternary uppercase tracking-wider mb-1.5",children:"Work (proc.)"}),e.jsx(ae,{label:"P99",value:ne((a==null?void 0:a.work.p99)??null)}),e.jsx(ae,{label:"avg",value:ne((a==null?void 0:a.work.avg)??null)})]})]}),e.jsxs("div",{className:"border-t border-surface-border/40 pt-2 mt-3 space-y-1.5",children:[x&&e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-[10px] text-text-tertiary",children:"SLA target"}),e.jsxs("span",{className:"text-xs font-mono text-text-secondary",children:[x,"m"]})]}),a&&a.priority_count>0&&e.jsxs(B,{to:ce(t),className:"flex items-center justify-between group",children:[e.jsxs("span",{className:"text-[10px]",style:{color:E},children:[a.priority_count," priority — pull oldest first"]}),e.jsx(le,{className:"w-3 h-3 transition-colors",style:{color:E}})]})]}),e.jsx("div",{className:"mt-4 border-t border-surface-border/40 pt-3",children:e.jsxs(B,{to:`/escalations/available?role=${encodeURIComponent(t.role)}`,className:"flex items-center gap-1.5 text-[10px] text-accent hover:underline",children:["View full queue",e.jsx(le,{className:"w-2.5 h-2.5"})]})})]})}function Ye({allMetrics:t,orderedRoles:n,period:l}){const i=t.reduce((o,d)=>o+d.pending,0),c=t.reduce((o,d)=>o+d.resolved,0),p=t.reduce((o,d)=>o+d.priority_count,0),a=new Map(t.map(o=>[o.role,o])),x=t.filter(o=>o.priority_count>0).length,u=t.filter(o=>o.pending>0).length;return e.jsxs(e.Fragment,{children:[e.jsxs("p",{className:"text-[9px] text-text-quaternary uppercase tracking-wider mb-3",children:["Pipeline · ",l]}),e.jsx("div",{className:"mb-4",children:x>0?e.jsxs("p",{className:"text-[11px]",style:{color:E},children:[x," station",x>1?"s":""," with priority items"]}):u>0?e.jsxs("p",{className:"text-[11px] text-amber-400",children:[u," station",u>1?"s":""," with backlog"]}):c>0?e.jsx("p",{className:"text-[11px] text-emerald-500",children:"Flowing — queue clear"}):e.jsx("p",{className:"text-[11px] text-text-quaternary",children:"Quiet — awaiting work"})}),e.jsxs("div",{className:"space-y-1.5 mb-4",children:[e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-[9px] text-text-quaternary uppercase tracking-wider",children:"Pending"}),e.jsx("span",{className:`text-xs font-mono tabular-nums ${i>0?"text-text-primary font-semibold":"text-text-quaternary"}`,children:i})]}),e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsxs("span",{className:"text-[9px] text-text-quaternary uppercase tracking-wider",children:["Resolved · ",l]}),e.jsx("span",{className:"text-xs font-mono tabular-nums text-text-secondary",children:c})]}),p>0&&e.jsxs("div",{className:"flex items-center justify-between",children:[e.jsx("span",{className:"text-[9px] text-text-quaternary uppercase tracking-wider",children:"Priority"}),e.jsx("span",{className:"text-xs font-mono tabular-nums font-semibold",style:{color:E},children:p})]})]}),e.jsx("div",{className:"border-t border-surface-border/40 pt-2 mb-1",children:e.jsxs("div",{className:"flex items-center gap-2",children:[e.jsx("span",{className:"text-[8px] text-text-quaternary uppercase tracking-wider flex-1",children:"Station"}),e.jsx("span",{className:"text-[8px] text-text-quaternary uppercase tracking-wider w-8 text-right shrink-0",children:"pend"}),e.jsx("span",{className:"text-[8px] text-text-quaternary uppercase tracking-wider w-8 text-right shrink-0",children:"actv"}),e.jsx("span",{className:"text-[8px] text-text-quaternary uppercase tracking-wider w-8 text-right shrink-0",children:"res"})]})}),e.jsx("div",{className:"space-y-0.5",children:n.map(o=>{const d=a.get(o.role),y=(d==null?void 0:d.pending)??0,v=(d==null?void 0:d.claimed)??0,b=(d==null?void 0:d.resolved)??0,f=(d==null?void 0:d.priority_count)??0,C=o.title||o.role,N=f>0,P=y>0;return e.jsxs("div",{className:"flex items-center gap-2 py-0.5",children:[e.jsxs("span",{className:`text-[10px] font-mono flex-1 truncate ${P?"text-text-primary":"text-text-quaternary"}`,children:[C,N&&e.jsxs("span",{className:"ml-1 text-[8px] font-semibold",style:{color:E},children:[f," PRIORITY"]})]}),e.jsx("span",{className:`text-[10px] font-mono tabular-nums w-8 text-right shrink-0 ${P?"text-text-primary font-semibold":"text-text-quaternary"}`,children:y>0?y:"—"}),e.jsx("span",{className:`text-[10px] font-mono tabular-nums w-8 text-right shrink-0 ${v>0?"text-accent":"text-text-quaternary"}`,children:v>0?v:"—"}),e.jsx("span",{className:`text-[10px] font-mono tabular-nums w-8 text-right shrink-0 ${b>0?"text-text-secondary":"text-text-quaternary"}`,children:b>0?b:"—"})]},o.role)})}),e.jsx("p",{className:"text-[9px] text-text-quaternary mt-5",children:"Select a station for queue detail."})]})}function Ge({role:t,allMetrics:n,orderedRoles:l,globalPeriod:i,onClose:c}){return e.jsx("div",{className:"w-[280px] shrink-0 px-6 py-8 flex flex-col overflow-y-auto min-h-0",children:t?e.jsx(Ve,{role:t,globalPeriod:i,onClose:c}):e.jsx(Ye,{allMetrics:n,orderedRoles:l,period:i})})}const ke=`${T}14`,$e=`${F}14`,we=`${D}14`,Ue=["15m","1h","24h","7d","30d"],Qe={"15m":.25,"1h":1,"24h":24,"7d":168,"30d":720};function Ke(t){const n=t.filter(a=>a.ops_visible),l=new Map(n.map(a=>[a.role,a])),i=new Set,p=n.filter(a=>!a.parent_role||!l.has(a.parent_role)).map(a=>{const x=[],u=[{role:a,depth:0}];for(;u.length>0;){const o=u.shift();i.has(o.role.role)||(i.add(o.role.role),x.push(o),n.filter(d=>d.parent_role===o.role.role&&!i.has(d.role)).forEach(d=>u.push({role:d,depth:o.depth+1})))}return{origin:a,stations:x}});return n.filter(a=>!i.has(a.role)).forEach(a=>p.push({origin:a,stations:[{role:a,depth:0}]})),p.sort((a,x)=>x.stations.length-a.stations.length||a.origin.role.localeCompare(x.origin.role))}function fe(t){if(t==null)return"—";if(t<1){const n=Math.round(t*60);return n<=0?"< 1s":`${n}s`}return t<60?`${t.toFixed(0)}m`:`${(t/60).toFixed(1)}h`}function Xe(t,n,l,i){if(!n)return{pct:null,color:"bg-surface-border",historical:!1};if(t>0){const x=t/n,u=Math.round(x*100),o=x>1?"bg-amber-400":x<.2?"bg-orange-400":"bg-emerald-400";return{pct:u,color:o,historical:!1}}if(i===0||l==null)return{pct:null,color:"bg-surface-border",historical:!0};const c=l/100,p=Math.round(l),a=c>1?"bg-amber-400":"bg-surface-border";return{pct:p,color:a,historical:!0}}function Ze({role:t,metric:n,selected:l,onClick:i}){var b;const c=(n==null?void 0:n.pending)??0,p=(n==null?void 0:n.claimed)??0,a=(n==null?void 0:n.resolved)??0,x=(n==null?void 0:n.priority_count)??0,u=t.target_per_hour??null,{pct:o,color:d,historical:y}=Xe(c,u,(n==null?void 0:n.throughput_pct)??null,a),v=o!=null?Math.min(100,o):0;return e.jsxs(e.Fragment,{children:[e.jsxs("div",{role:"button",tabIndex:0,onClick:i,onKeyDown:f=>{f.key==="Enter"&&i()},className:`grid items-center gap-4 cursor-pointer transition-colors ${l?"border-l-2 border-accent":"pl-0.5"}`,style:{gridTemplateColumns:"1fr 64px 56px 56px 60px 72px 72px 104px"},children:[e.jsxs("div",{className:"flex items-center gap-1.5 min-w-0 py-3.5",children:[e.jsx("span",{className:"font-mono font-bold text-text-primary truncate text-[11px]",children:t.role}),t.title&&e.jsx("span",{className:"text-[10px] text-text-tertiary truncate",children:t.title}),(((b=t.upstream_roles)==null?void 0:b.length)??0)>0&&e.jsx("span",{title:`Fed by ${t.upstream_roles.join(", ")}`,className:"shrink-0 leading-none",children:e.jsx(Se,{className:"w-3 h-3 text-text-quaternary"})})]}),e.jsx("span",{className:`text-xs font-mono tabular-nums text-right py-3.5 ${u!=null?"text-text-secondary":"text-text-quaternary"}`,children:u??"—"}),e.jsx("div",{className:"self-stretch flex items-center justify-end px-1.5",style:{backgroundColor:ke},children:e.jsx("span",{className:`text-xs font-mono tabular-nums ${c>0?"text-text-primary font-semibold":"text-text-quaternary"}`,children:c})}),e.jsx("div",{className:"self-stretch flex items-center justify-end px-1.5",style:{backgroundColor:$e},children:e.jsx("span",{className:`text-xs font-mono tabular-nums ${p>0?"text-accent font-semibold":"text-text-quaternary"}`,children:p})}),e.jsx("div",{className:"self-stretch flex items-center justify-end px-1.5",style:{backgroundColor:we},children:e.jsx("span",{className:"text-xs font-mono tabular-nums text-text-quaternary",children:a})}),e.jsx("span",{className:"text-xs font-mono tabular-nums text-right py-3.5 text-text-secondary",children:fe((n==null?void 0:n.wait.p99)??null)}),e.jsx("span",{className:"text-xs font-mono tabular-nums text-right py-3.5 text-text-secondary",children:fe((n==null?void 0:n.work.p99)??null)}),e.jsxs("div",{className:"flex items-center gap-2 py-3.5",children:[e.jsx("div",{className:"w-12 h-1.5 bg-surface-sunken rounded-full overflow-hidden shrink-0",children:e.jsx("div",{className:`h-full rounded-full ${d}`,style:{width:`${v}%`}})}),e.jsx("span",{className:`text-[10px] font-mono tabular-nums ${o!=null&&o>100?"text-amber-500 font-semibold":"text-text-quaternary"}`,title:y?"Throughput efficiency for the selected period":void 0,children:o!=null?`${o}%${y?"↩":""}`:"—"})]})]}),x>0&&e.jsxs("div",{className:"flex items-center gap-1.5 pb-2 pl-1",children:[e.jsx("span",{className:"w-2.5 h-2.5 rounded-full shrink-0",style:{backgroundColor:ve}}),e.jsxs(B,{to:ce(t),className:"text-[10px] hover:underline",style:{color:E},onClick:f=>f.stopPropagation(),children:[x," priority — pull oldest first →"]})]})]})}function Je(){const t=[{label:"ROLE"},{label:"TARGET/H"},{label:"PENDING",band:ke,hue:T},{label:"ACTIVE",band:$e,hue:F},{label:"RESOLVED",band:we,hue:D},{label:"P99 WAIT"},{label:"P99 WORK"},{label:"TREND"}];return e.jsx("div",{className:"grid items-center gap-4 border-b border-surface-border mb-0.5",style:{gridTemplateColumns:"1fr 64px 56px 56px 60px 72px 72px 104px"},children:t.map((n,l)=>n.band?e.jsx("div",{className:"self-stretch flex items-center justify-end px-1.5",style:{backgroundColor:n.band},children:e.jsx("span",{className:"text-[9px] font-semibold uppercase tracking-wider",style:{color:n.hue},children:n.label})},n.label):e.jsx("span",{className:`text-[9px] font-semibold uppercase tracking-wider text-text-quaternary py-1.5 ${l>0&&l<7?"text-right":""}`,children:n.label},n.label))})}function lt(){var Y;const[t,n]=w.useState("24h"),[l,i]=w.useState(null),[c,p]=w.useState(!1);_e();const{data:a,isLoading:x,refetch:u}=Me(),{data:o,isLoading:d,refetch:y}=ye(t),v=(a==null?void 0:a.roles)??[],b=(o==null?void 0:o.stations)??[],f=w.useMemo(()=>Ke(v),[v]),[C,N]=Le(),P=C.get("fragment"),M=f.find(r=>r.origin.role===P)??f[0]??null,_=w.useCallback(r=>{i(null),N(m=>{const S=new URLSearchParams(m);return S.set("fragment",r),S})},[N]),k=(M==null?void 0:M.stations)??[],z=w.useMemo(()=>new Set(k.map(({role:r})=>r.role)),[k]),H=w.useMemo(()=>b.filter(r=>z.has(r.role)),[b,z]),V=w.useMemo(()=>k.map(({role:r})=>({role:r.role,title:r.title,parent_role:r.parent_role,target_per_hour:r.target_per_hour??null,upstream_roles:r.upstream_roles??[],metric:b.find(m=>m.role===r.role)})),[k,b]),K=w.useCallback(r=>{const m=f.find(S=>S.stations.some(({role:s})=>s.role===r));m&&_(m.origin.role)},[f,_]),q=qe(),X=w.useCallback(r=>{const m=v.find(S=>S.role===r);m&&q(ce(m))},[v,q]),A=((Y=k.find(({role:r})=>r.role===l))==null?void 0:Y.role)??null,Z=x||d,J=async()=>{p(!0),await Promise.all([u(),y()]),p(!1)},O=r=>i(m=>m===r?null:r),ee=e.jsx("div",{className:"flex items-center gap-0.5",children:Ue.map(r=>e.jsx("button",{onClick:()=>n(r),className:`px-2.5 py-1 text-[10px] font-mono rounded transition-colors ${t===r?"bg-accent/10 text-accent font-semibold":"text-text-quaternary hover:text-text-secondary"}`,children:r},r))});return e.jsxs("div",{className:"flex flex-col flex-1 min-h-0",children:[e.jsx(Ce,{title:"Pace Board",docsHash:"#docs:dashboard.md:pace-board",center:ee,actions:e.jsxs("div",{className:"flex items-center gap-3",children:[e.jsx("button",{onClick:J,disabled:c,className:"text-text-quaternary hover:text-text-secondary transition-colors disabled:opacity-40",children:e.jsx(Pe,{className:`w-3.5 h-3.5 ${c?"animate-spin":""}`})}),e.jsx(B,{to:"/admin/roles",className:"btn-secondary text-xs",children:"Configure"})]})}),Z?e.jsxs("div",{className:"animate-pulse space-y-6 mt-4",children:[e.jsx("div",{className:"h-8 bg-surface-sunken rounded w-64"}),e.jsx("div",{className:"h-64 bg-surface-sunken rounded w-full"}),e.jsx("div",{className:"space-y-3",children:[1,2,3].map(r=>e.jsx("div",{className:"h-8 bg-surface-sunken rounded w-full"},r))})]}):f.length===0?e.jsxs("div",{className:"mt-8",children:[e.jsx("p",{className:"text-sm text-text-secondary mb-2",children:"Stations appear here once roles are marked visible in Operations."}),e.jsxs("p",{className:"text-xs text-text-tertiary",children:["Go to"," ",e.jsx(B,{to:"/admin/roles",className:"text-accent hover:underline",children:"Roles"})," ","and enable ",e.jsx("strong",{children:"Visible in Operations"})," on roles that represent pipeline stations."]})]}):e.jsxs("div",{className:"flex flex-col flex-1 min-h-0",children:[f.length>1&&e.jsx("div",{className:"flex items-center gap-0.5 px-4 pt-2",children:f.map(r=>{const m=r.origin.role===(M==null?void 0:M.origin.role);return e.jsxs("button",{onClick:()=>_(r.origin.role),className:`px-2.5 py-1 text-[10px] font-mono rounded transition-colors ${m?"bg-accent/10 text-accent font-semibold":"text-text-quaternary hover:text-text-secondary"}`,children:[r.origin.title??r.origin.role,e.jsx("span",{className:`ml-1.5 tabular-nums ${m?"text-accent/60":"text-text-quaternary/60"}`,children:r.stations.length})]},r.origin.role)})}),e.jsxs("div",{className:"flex-1 min-h-0 flex items-stretch overflow-hidden",children:[e.jsx("div",{className:"flex-1 min-w-0 min-h-0 flex flex-col justify-center overflow-hidden p-4",children:e.jsx(Be,{stations:V,selectedRole:l,onSelect:O,onUpstreamSelect:K,onPrioritySelect:X,periodHours:Qe[t]})}),e.jsx("div",{className:"w-px bg-surface-border shrink-0 self-stretch"}),e.jsx(Ge,{role:A,allMetrics:H,orderedRoles:k.map(r=>r.role),globalPeriod:t,onClose:()=>i(null)})]}),e.jsxs("div",{className:"h-[30vh] flex-none flex flex-col border-t border-surface-border overflow-hidden",children:[e.jsx(Je,{}),e.jsx("div",{className:"flex-1 min-h-0 overflow-y-auto divide-y divide-surface-border/30",children:k.map(({role:r})=>e.jsx(Ze,{role:r,metric:b.find(m=>m.role===r.role),selected:l===r.role,onClick:()=>O(r.role)},r.role))})]})]})]})}export{lt as OperationsPage};
|
|
2
|
-
//# sourceMappingURL=OperationsPage-C5phWL0O.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"OperationsPage-C5phWL0O.js","sources":["../../src/pages/operations/PaceChart.tsx","../../src/pages/operations/priority-link.ts","../../src/pages/operations/StationDetailPanel.tsx","../../src/pages/operations/OperationsPage.tsx"],"sourcesContent":["import { useState } from 'react';\nimport type { StationMetric } from '../../api/escalations';\n\nexport interface ChartStation {\n role: string;\n title: string | null;\n target_per_hour: number | null;\n parent_role: string | null;\n /** Roles feeding this station from other sequences — drawn as a merge glyph, not a bend in the line. */\n upstream_roles?: string[];\n metric: StationMetric | undefined;\n}\n\ninterface PaceChartProps {\n stations: ChartStation[];\n selectedRole: string | null;\n onSelect: (role: string) => void;\n /** Merge-glyph click — jump to the sequence that feeds this station. */\n onUpstreamSelect?: (upstreamRole: string) => void;\n /** Priority-badge click — open the station's queue oldest-first by its age facet. */\n onPrioritySelect?: (role: string) => void;\n /** Selected window length in hours — target count = target_per_hour × this. */\n periodHours: number;\n}\n\n// ── SVG layout ────────────────────────────────────────────────────────────────\n\nconst W = 800;\nconst H = 270;\nconst ML = 40;\nconst MR = 76;\nconst MT = 24;\nconst MB = 72;\n\nconst chartW = W - ML - MR;\nconst chartH = H - MT - MB;\nconst bottom = MT + chartH;\nconst right = ML + chartW;\n\n// Smooth transition applied when the window changes and values re-scale.\nconst EASE = '0.5s cubic-bezier(0.4, 0, 0.2, 1)';\n\n// ── Path builders ─────────────────────────────────────────────────────────────\n\n/** Straight polyline — the target line is a discrete count per station. */\nfunction linePath(pts: { x: number; y: number }[]): string {\n if (pts.length === 0) return '';\n return pts.map((p, i) => `${i === 0 ? 'M' : 'L'} ${p.x.toFixed(1)} ${p.y.toFixed(1)}`).join(' ');\n}\n\n/** Catmull-Rom → cubic bezier — smooth curves for the actual and active lines. */\nfunction catmullPath(pts: { x: number; y: number }[]): string {\n if (pts.length === 0) return '';\n if (pts.length === 1) return `M ${pts[0].x} ${pts[0].y}`;\n const segs: string[] = [];\n for (let i = 0; i < pts.length - 1; i++) {\n const p0 = pts[Math.max(0, i - 1)];\n const p1 = pts[i];\n const p2 = pts[i + 1];\n const p3 = pts[Math.min(pts.length - 1, i + 2)];\n const cp1x = (p1.x + (p2.x - p0.x) / 6).toFixed(1);\n const cp1y = (p1.y + (p2.y - p0.y) / 6).toFixed(1);\n const cp2x = (p2.x - (p3.x - p1.x) / 6).toFixed(1);\n const cp2y = (p2.y - (p3.y - p1.y) / 6).toFixed(1);\n if (i === 0) segs.push(`M ${p1.x.toFixed(1)} ${p1.y.toFixed(1)}`);\n segs.push(`C ${cp1x} ${cp1y}, ${cp2x} ${cp2y}, ${p2.x.toFixed(1)} ${p2.y.toFixed(1)}`);\n }\n return segs.join(' ');\n}\n\n/**\n * Closed region between two curves — upper spline forward, lower spline back.\n * Used for the queue-composition bands: the vertical thickness at any station\n * is the count difference between the two curves.\n */\nfunction bandPath(upper: { x: number; y: number }[], lower: { x: number; y: number }[]): string {\n if (upper.length < 2 || lower.length < 2) return '';\n const forward = catmullPath(upper);\n const rev = [...lower].reverse();\n const back = catmullPath(rev);\n // Drop the back path's leading M — the L below connects the two curves.\n const backSegs = back.slice(back.indexOf('C'));\n return `${forward} L ${rev[0].x.toFixed(1)} ${rev[0].y.toFixed(1)} ${backSegs} Z`;\n}\n\nfunction compact(n: number): string {\n return n >= 10000 ? `${Math.round(n / 1000)}k` : n >= 1000 ? `${(n / 1000).toFixed(1)}k` : String(n);\n}\n\nfunction last<T>(a: T[]): T | undefined {\n return a.length > 0 ? a[a.length - 1] : undefined;\n}\n\n// ── Colors ────────────────────────────────────────────────────────────────────\n\n/** Circle color from actual vs the window target. Meeting or beating the target\n * is good (green); short is the concern (amber → red). */\nfunction paceColor(ratio: number | null): { stroke: string; fill: string } {\n if (ratio == null) return { stroke: '#cbd5e1', fill: '#f8fafc' };\n if (ratio >= 1.0) return { stroke: '#10b981', fill: '#ecfdf5' }; // met/beat target\n if (ratio >= 0.6) return { stroke: '#f59e0b', fill: '#fffbeb' }; // behind\n return { stroke: '#ef4444', fill: '#fef2f2' }; // well behind\n}\n\n// Shared queue-state palette — the chart bands and the station table columns\n// use the same hues so the two views read as one.\nexport const ACTIVE_COLOR = '#6366f1'; // claimed, being worked right now — indigo\nexport const QUEUED_COLOR = '#0ea5e9'; // pending and unclaimed, waiting in the queue — sky\nexport const RESOLVED_COLOR = '#4e6a5e'; // done — grey with a breath of green\n\n// Priority — unclaimed items past the role's age threshold, the count the\n// floor pulls to the front of the rack. Powder blue for the badge circle;\n// the count text uses the same hue deepened enough to read at chart sizes.\nexport const PRIORITY_COLOR = '#b0e0e6';\nexport const PRIORITY_TEXT_COLOR = '#5b93ad';\n\n// ── End-label stacking — spread close labels so text doesn't collide ────────────\n\nconst MIN_GAP = 13;\nfunction spreadLabels<T extends { y: number }>(labels: T[], maxY: number): (T & { labelY: number })[] {\n const sorted = [...labels].sort((a, b) => a.y - b.y);\n let prev = -Infinity;\n const placed = sorted.map((l) => {\n const labelY = Math.max(l.y + 3, prev + MIN_GAP);\n prev = labelY;\n return { ...l, labelY };\n });\n // On a quiet window every curve sits at zero and the stack would descend\n // past the floor into the station titles — lift the whole stack back above\n // maxY instead.\n const overflow = placed.length > 0 ? placed[placed.length - 1].labelY - maxY : 0;\n if (overflow > 0) {\n for (const l of placed) l.labelY -= overflow;\n }\n return placed;\n}\n\n// ── Chart ─────────────────────────────────────────────────────────────────────\n\nexport function PaceChart({ stations, selectedRole, onSelect, onUpstreamSelect, onPrioritySelect, periodHours }: PaceChartProps) {\n const [hoveredIdx, setHoveredIdx] = useState<number | null>(null);\n\n const n = stations.length;\n if (n === 0) return null;\n\n const xOf = (i: number) => (n === 1 ? ML + chartW / 2 : ML + (i / (n - 1)) * chartW);\n const xMap = new Map(stations.map((s, i) => [s.role, xOf(i)]));\n\n // Per-station counts for the selected window:\n // - target : expected units = target_per_hour × periodHours (e.g. 22/h · 15m ≈ 5).\n // - actual : units resolved in the window (\"the number we see\").\n // - active : units in the queue being worked right now (claimed).\n const rows = stations.map((s, i) => {\n const target = s.target_per_hour ?? null;\n const expected = target != null ? target * periodHours : null;\n return {\n idx: i,\n x: xOf(i),\n expected,\n actual: s.metric?.resolved ?? 0,\n active: s.metric?.claimed ?? 0,\n pending: s.metric?.pending ?? 0,\n priorityCount: s.metric?.priority_count ?? 0,\n };\n });\n\n const hasTargets = rows.some((r) => r.expected != null);\n\n const dataMax = Math.max(\n 1,\n ...rows.map((r) => r.expected ?? 0),\n ...rows.map((r) => r.actual),\n ...rows.map((r) => (r.expected != null ? r.pending : 0)),\n );\n const maxVal = dataMax * 1.15;\n const yScale = (v: number) => MT + chartH * (1 - Math.max(0, Math.min(v, maxVal)) / maxVal);\n\n const withTarget = rows.filter((r) => r.expected != null);\n const targetPts = withTarget.map((r) => ({ ...r, y: yScale(r.expected as number) }));\n const actualPts = withTarget.map((r) => ({ ...r, y: yScale(r.actual) }));\n const activePts = withTarget.map((r) => ({ ...r, y: yScale(r.active) }));\n const pendingPts = withTarget.map((r) => ({ ...r, y: yScale(r.pending) }));\n\n const targetLinePath = linePath(targetPts);\n const actualSplinePath = catmullPath(actualPts);\n const activeSplinePath = catmullPath(activePts);\n const pendingSplinePath = catmullPath(pendingPts);\n\n const lastActual = last(actualPts);\n const lastTarget = last(targetPts);\n const lastActive = last(activePts);\n const lastPending = last(pendingPts);\n\n const areaPath =\n actualPts.length >= 2 && lastActual\n ? `${actualSplinePath} L ${lastActual.x.toFixed(1)} ${bottom} L ${actualPts[0].x.toFixed(1)} ${bottom} Z`\n : '';\n\n // Queue composition — pending splits into two stacked bands:\n // floor → active curve = claimed, being worked (indigo)\n // active → pending curve = waiting, unclaimed (sky)\n // When workers claim everything the instant it lands, the sky band collapses\n // to zero and the whole queue reads as indigo.\n const hasQueue = withTarget.some((r) => r.pending > 0 || r.active > 0);\n const workedBandPath =\n hasQueue && activePts.length >= 2 && lastActive\n ? `${activeSplinePath} L ${lastActive.x.toFixed(1)} ${bottom} L ${activePts[0].x.toFixed(1)} ${bottom} Z`\n : '';\n const queuedBandPath = hasQueue ? bandPath(pendingPts, activePts) : '';\n\n // Quiet lines keep their curves (flat at zero) but skip their end labels —\n // a stack of names pointing at nothing is noise.\n const showActiveLabel = withTarget.some((r) => r.active > 0);\n const showQueuedLabel = withTarget.some((r) => r.pending > r.active);\n const endLabels = spreadLabels(\n [\n lastTarget ? { key: 'target', text: 'target', color: '#ef4444', y: lastTarget.y } : null,\n lastActual ? { key: 'actual', text: 'actual', color: RESOLVED_COLOR, y: lastActual.y } : null,\n showActiveLabel && lastActive\n ? { key: 'active', text: 'active', color: ACTIVE_COLOR, y: lastActive.y }\n : null,\n showQueuedLabel && lastPending\n ? { key: 'queued', text: 'queued', color: QUEUED_COLOR, y: lastPending.y }\n : null,\n ].filter(Boolean) as { key: string; text: string; color: string; y: number }[],\n bottom + 4,\n );\n\n return (\n <svg viewBox={`0 0 ${W} ${H}`} className=\"w-full h-full\" preserveAspectRatio=\"xMidYMid meet\" style={{ display: 'block' }}>\n {/* Dependency connector lines (parent → child), drawn at chart floor */}\n {stations.map((s) => {\n if (!s.parent_role) return null;\n const px = xMap.get(s.parent_role);\n if (px == null) return null;\n const cx = xOf(stations.findIndex((r) => r.role === s.role));\n return <line key={`dep-${s.role}`} x1={px} y1={bottom} x2={cx} y2={bottom} stroke=\"#e2e8f0\" strokeWidth={0.75} />;\n })}\n\n {/* Area under the actual curve */}\n {areaPath && <path d={areaPath} fill={RESOLVED_COLOR} opacity={0.05} style={{ transition: `d ${EASE}` }} />}\n\n {/* Queue composition bands — the band heights split each station's\n pending total into claimed-and-worked (indigo, floor→active) and\n waiting-unclaimed (sky, active→pending). */}\n {workedBandPath && (\n <path d={workedBandPath} fill={ACTIVE_COLOR} opacity={0.12} style={{ transition: `d ${EASE}` }} />\n )}\n {queuedBandPath && (\n <path d={queuedBandPath} fill={QUEUED_COLOR} opacity={0.12} style={{ transition: `d ${EASE}` }} />\n )}\n\n {/* Target — thin red solid reference at the window's expected count */}\n {targetLinePath && (\n <path d={targetLinePath} fill=\"none\" stroke=\"#ef4444\" strokeWidth={0.5} strokeLinejoin=\"round\" opacity={0.9} style={{ transition: `d ${EASE}` }} />\n )}\n\n {/* Per-role target for the window (target_per_hour × duration), just above the red line */}\n {targetPts.map((tp) => (\n <text\n key={`tval-${tp.idx}`}\n x={tp.x}\n y={tp.y - 3.5}\n textAnchor=\"middle\"\n fontSize={6.5}\n fill=\"#ef4444\"\n fontFamily=\"ui-monospace, monospace\"\n fontWeight=\"500\"\n opacity={0.7}\n style={{ transition: `y ${EASE}` }}\n >\n {Math.round(tp.expected as number)}\n </text>\n ))}\n\n {/* Pending — thin dotted sky edge along the queue total (top of the bands) */}\n {hasQueue && pendingSplinePath && (\n <path d={pendingSplinePath} fill=\"none\" stroke={QUEUED_COLOR} strokeWidth={0.75} strokeDasharray=\"2 3\" strokeLinecap=\"round\" opacity={0.55} style={{ transition: `d ${EASE}` }} />\n )}\n\n {/* Active — thin dotted indigo, how many are claimed and being worked */}\n {activeSplinePath && (\n <path d={activeSplinePath} fill=\"none\" stroke={ACTIVE_COLOR} strokeWidth={0.75} strokeDasharray=\"2 3\" strokeLinecap=\"round\" opacity={0.5} style={{ transition: `d ${EASE}` }} />\n )}\n\n {/* Actual — thin solid, primary line */}\n {actualSplinePath && (\n <path d={actualSplinePath} fill=\"none\" stroke={RESOLVED_COLOR} strokeWidth={1} strokeLinejoin=\"round\" strokeLinecap=\"round\" opacity={0.75} style={{ transition: `d ${EASE}` }} />\n )}\n\n {/* Queue markers — a dot on the active line per station.\n Above the dot, at the pending edge: the queue total (sky when part of\n it is still waiting, slate when everything is claimed).\n Below the dot: active (claimed, being worked right now). */}\n {activePts.map((ap, i) => {\n if (ap.pending <= 0 && ap.active <= 0) return null;\n const ar = Math.max(2.5, Math.min(5, 2.5 + ap.active / 10));\n const hasWaiting = ap.pending > ap.active;\n // Anchor the queue-total label to the pending edge so it rides the top\n // of the band; keep it clear of the dot when the two curves touch.\n const pendingLabelY = Math.min(pendingPts[i].y - 4, ap.y - ar - 4);\n return (\n <g key={`active-${ap.idx}`}>\n {ap.pending > 0 && (\n <text\n x={ap.x}\n y={pendingLabelY}\n textAnchor=\"middle\"\n fontSize={7.5}\n fill={hasWaiting ? QUEUED_COLOR : '#64748b'}\n fontFamily=\"ui-monospace, monospace\"\n fontWeight=\"600\"\n style={{ transition: `y ${EASE}` }}\n >\n {ap.pending}\n </text>\n )}\n <g transform={`translate(${ap.x} ${ap.y})`} style={{ transition: `transform ${EASE}` }}>\n <circle r={ar} fill={ACTIVE_COLOR} opacity={0.85} />\n {ap.active > 0 && (\n <text y={ar + 9} textAnchor=\"middle\" fontSize={7.5} fill={ACTIVE_COLOR} fontFamily=\"ui-monospace, monospace\" fontWeight=\"600\">\n {ap.active}\n </text>\n )}\n </g>\n </g>\n );\n })}\n\n {/* Station nodes — actual (resolved) as a dot on the actual line, styled like\n the active dot but a bit larger (actual is the most important number).\n The count sits below the dot so it stays legible as numbers grow. */}\n {rows.map((row) => {\n const s = stations[row.idx];\n const x = row.x;\n const cy = row.expected != null ? yScale(row.actual) : bottom;\n const ratio = row.expected && row.expected > 0 ? row.actual / row.expected : null;\n const { stroke } = paceColor(row.expected != null ? ratio : null);\n // Same small scale as the active dot — a hair larger.\n const r = Math.max(3, Math.min(5.5, 3 + row.actual / 120));\n const isSelected = s.role === selectedRole;\n const isHovered = hoveredIdx === row.idx;\n\n const tooltip =\n row.expected == null\n ? 'idle · set a target rate to plot pace'\n : `${row.actual} done · target ${Math.round(row.expected)}`\n + (row.pending > row.active ? ` · ${row.pending - row.active} waiting` : '')\n + (row.active > 0 ? ` · ${row.active} active` : '')\n + (row.priorityCount > 0 ? ` · ${row.priorityCount} priority` : '');\n const tipW = tooltip.length * 5.5 + 20;\n const tipX = Math.max(ML + tipW / 2 + 4, Math.min(right - tipW / 2 - 4, x));\n const tipY = cy - r - 10;\n\n return (\n <g\n key={s.role}\n onClick={() => onSelect(s.role)}\n onMouseEnter={() => setHoveredIdx(row.idx)}\n onMouseLeave={() => setHoveredIdx(null)}\n style={{ cursor: 'pointer' }}\n >\n {/* Animated marker group — slides vertically as the window rescales */}\n <g transform={`translate(${x} ${cy})`} style={{ transition: `transform ${EASE}` }}>\n <circle r={22} fill=\"transparent\" />\n <circle r={r} fill={stroke} opacity={0.9} style={{ transition: `r ${EASE}` }} />\n {row.expected != null && (\n <text y={-r - 5} textAnchor=\"middle\" fontSize={9.5} fill={RESOLVED_COLOR} fontFamily=\"ui-monospace, monospace\" fontWeight=\"500\">\n {compact(row.actual)}\n </text>\n )}\n {isHovered && !isSelected && <circle r={r + 3} fill=\"none\" stroke={stroke} strokeWidth={1} opacity={0.4} />}\n {isSelected && <circle r={r + 4} fill=\"none\" stroke=\"#6366f1\" strokeWidth={2} />}\n </g>\n\n {/* Priority badge — powder blue circle carrying the count of\n unclaimed items past the role's age threshold. Clicking opens\n the station's queue oldest-first by the priority facet so the\n runner grabs the top few and moves them to the front. */}\n {row.priorityCount > 0 && (\n <g\n transform={`translate(${x + 15} ${Math.max(cy - 15, 12)})`}\n onClick={(e) => {\n e.stopPropagation();\n onPrioritySelect?.(s.role);\n }}\n style={{ cursor: 'pointer', transition: `transform ${EASE}` }}\n >\n <title>{`${row.priorityCount} past the priority threshold — click to pull oldest first`}</title>\n <circle\n r={6.5 + (String(row.priorityCount).length - 1) * 2}\n fill={PRIORITY_COLOR}\n opacity={0.9}\n />\n <text\n y={2.5}\n textAnchor=\"middle\"\n fontSize={7}\n fill={PRIORITY_TEXT_COLOR}\n fontFamily=\"ui-monospace, monospace\"\n fontWeight=\"700\"\n >\n {row.priorityCount}\n </text>\n </g>\n )}\n\n {/* Merge affordance — this station also receives input from another\n sequence. The dashed drop into the floor says \"a side-quest\n lands here\"; clicking it opens that sequence. It is a symbol,\n deliberately not a line — the upstream is NOT a descendant. */}\n {(s.upstream_roles?.length ?? 0) > 0 && (\n <g\n transform={`translate(${x - 20} ${bottom})`}\n onClick={(e) => {\n e.stopPropagation();\n onUpstreamSelect?.(s.upstream_roles![0]);\n }}\n style={{ cursor: 'pointer' }}\n >\n <title>{`Fed by ${s.upstream_roles!.join(', ')} — click to view that sequence`}</title>\n <circle r={9} cy={-6} fill=\"transparent\" />\n <path d=\"M -7 -14 C -3 -14 -1 -9 0 -2\" fill=\"none\" stroke=\"#94a3b8\" strokeWidth={1} strokeDasharray=\"2 2\" strokeLinecap=\"round\" />\n <path d=\"M 0 -2 L 8 -2\" stroke=\"#94a3b8\" strokeWidth={1} strokeLinecap=\"round\" />\n <path d=\"M 8 -2 l -3.5 -2 v 4 z\" fill=\"#94a3b8\" />\n </g>\n )}\n\n {/* Station labels (fixed at the floor) */}\n <text x={x} y={bottom + 14} textAnchor=\"middle\" fontSize={9.5} fill=\"#475569\" fontFamily=\"ui-sans-serif, sans-serif\" fontWeight=\"500\">\n {s.title ?? s.role}\n </text>\n <text x={x} y={bottom + 26} textAnchor=\"middle\" fontSize={8} fill=\"#94a3b8\" fontFamily=\"ui-monospace, monospace\">\n {s.role}\n </text>\n\n {/* Tooltip */}\n {isHovered && (\n <g>\n <rect x={tipX - tipW / 2} y={tipY - 16} width={tipW} height={19} rx={3} fill=\"#0f172a\" opacity={0.88} />\n <text x={tipX} y={tipY - 3} textAnchor=\"middle\" fontSize={8.5} fill=\"white\" fontFamily=\"ui-monospace, monospace\">\n {tooltip}\n </text>\n </g>\n )}\n </g>\n );\n })}\n\n {/* No-target hint */}\n {!hasTargets && (\n <text x={ML + chartW / 2} y={bottom + 48} textAnchor=\"middle\" fontSize={8.5} fill=\"#94a3b8\" fontFamily=\"ui-sans-serif, sans-serif\">\n Set target_per_hour on each role in Admin → Roles to enable the pace chart\n </text>\n )}\n\n {/* End labels — text + count, rendered last so they sit above everything */}\n {endLabels.map((l) => (\n <text\n key={l.key}\n x={right + 6}\n y={l.labelY}\n fontSize={8}\n fill={l.color}\n fontFamily=\"ui-sans-serif, sans-serif\"\n fontWeight={l.key === 'actual' ? 700 : 500}\n opacity={l.key === 'active' ? 0.85 : 1}\n style={{ transition: `y ${EASE}` }}\n >\n {l.text}\n </text>\n ))}\n </svg>\n );\n}\n","import type { RoleDetail } from '../../api/roles';\n\n/**\n * Deep link to the available-escalations queue ordered oldest-first by the\n * role's priority age origin: the metadata facet when configured (faceted\n * orderBy, nulls last), else created_at. Mirrors the priority_count SQL so\n * the counted items sit at the top of the list the runner opens.\n */\nexport function priorityQueueLink(role: Pick<RoleDetail, 'role' | 'priority_facet'>): string {\n const base = `/escalations/available?role=${encodeURIComponent(role.role)}`;\n if (role.priority_facet) {\n const orderBy = JSON.stringify([{ field: `metadata.${role.priority_facet}`, direction: 'asc' }]);\n return `${base}&orderBy=${encodeURIComponent(orderBy)}`;\n }\n return `${base}&sort_by=created_at&order=asc`;\n}\n","import { useState } from 'react';\nimport { Link } from 'react-router-dom';\nimport { X, ExternalLink } from 'lucide-react';\nimport { useStationMetrics } from '../../api/escalations';\nimport type { StationMetric } from '../../api/escalations';\nimport type { RoleDetail } from '../../api/roles';\nimport { PRIORITY_TEXT_COLOR } from './PaceChart';\nimport { priorityQueueLink } from './priority-link';\n\ninterface StationDetailPanelProps {\n role: RoleDetail | null;\n allMetrics: StationMetric[];\n orderedRoles: RoleDetail[];\n globalPeriod: string;\n onClose: () => void;\n}\n\n// Mirrors OperationsPage's selector — the panel reports the same windows the\n// chart can show, and opens on whichever one the chart has selected.\nconst PERIODS = ['15m', '1h', '24h', '7d', '30d'] as const;\ntype Period = (typeof PERIODS)[number];\n\nconst isPeriod = (p: string): p is Period => (PERIODS as readonly string[]).includes(p);\n\nfunction fmt(min: number | null): string {\n if (min == null) return '—';\n if (min < 1) {\n const s = Math.round(min * 60);\n return s <= 0 ? '< 1s' : `${s}s`;\n }\n if (min < 60) return `${min.toFixed(0)}m`;\n return `${(min / 60).toFixed(1)}h`;\n}\n\nfunction MetricRow({ label, value }: { label: string; value: string }) {\n return (\n <div className=\"flex items-center justify-between py-1\">\n <span className=\"text-[10px] text-text-tertiary uppercase tracking-wider\">{label}</span>\n <span className=\"text-xs font-mono font-medium text-text-primary tabular-nums\">{value}</span>\n </div>\n );\n}\n\nfunction PeriodSelector({ period, onChange }: { period: Period; onChange: (p: Period) => void }) {\n return (\n <div className=\"flex items-center gap-1 mb-4\">\n {PERIODS.map((p) => (\n <button\n key={p}\n onClick={() => onChange(p)}\n className={`px-2 py-0.5 text-[10px] font-mono rounded transition-colors ${\n period === p\n ? 'bg-accent/10 text-accent font-semibold'\n : 'text-text-quaternary hover:text-text-secondary'\n }`}\n >\n {p}\n </button>\n ))}\n </div>\n );\n}\n\nfunction RoleView({ role, globalPeriod, onClose }: { role: RoleDetail; globalPeriod: string; onClose: () => void }) {\n // Open on the chart's selected window so the panel's numbers agree with the\n // chart beside it; the local selector still lets the viewer drill around.\n const [period, setPeriod] = useState<Period>(isPeriod(globalPeriod) ? globalPeriod : '24h');\n const { data } = useStationMetrics(period);\n const metric = data?.stations.find((s) => s.role === role.role);\n const slaMinutes = role.sla_minutes ?? undefined;\n const targetPerHour = role.target_per_hour ?? undefined;\n const workerCount = role.worker_count ?? undefined;\n\n return (\n <>\n {/* Header */}\n <div className=\"flex items-start justify-between mb-1\">\n <div className=\"min-w-0 flex-1\">\n <div className=\"flex items-baseline gap-1.5 flex-wrap\">\n <span className=\"text-sm font-mono font-bold text-text-primary\">{role.role}</span>\n {role.title && (\n <span className=\"text-[10px] text-text-secondary\">{role.title}</span>\n )}\n </div>\n {role.description && (\n <p className=\"text-[10px] text-text-tertiary mt-0.5 line-clamp-2 leading-relaxed\">\n {role.description}\n </p>\n )}\n </div>\n <button\n onClick={onClose}\n className=\"text-text-quaternary hover:text-text-secondary transition-colors mt-0.5 shrink-0 ml-2\"\n >\n <X className=\"w-4 h-4\" />\n </button>\n </div>\n\n <Link\n to={`/admin/roles/${encodeURIComponent(role.role)}`}\n className=\"flex items-center gap-1 text-[10px] text-accent hover:underline mb-4\"\n >\n <ExternalLink className=\"w-2.5 h-2.5\" />\n Edit in Roles\n </Link>\n\n <PeriodSelector period={period} onChange={setPeriod} />\n\n {/* Capacity settings */}\n {(targetPerHour || slaMinutes || workerCount) && (\n <div className=\"flex items-center gap-4 mb-4 text-[10px]\">\n {targetPerHour && (\n <div>\n <div className=\"font-mono font-semibold text-text-primary\">{targetPerHour}/h</div>\n <div className=\"text-text-quaternary uppercase tracking-wider text-[8.5px]\">target</div>\n </div>\n )}\n {slaMinutes && (\n <div>\n <div className=\"font-mono font-semibold text-text-primary\">{slaMinutes}m</div>\n <div className=\"text-text-quaternary uppercase tracking-wider text-[8.5px]\">SLA</div>\n </div>\n )}\n {workerCount && (\n <div>\n <div className=\"font-mono font-semibold text-text-primary\">{workerCount}</div>\n <div className=\"text-text-quaternary uppercase tracking-wider text-[8.5px]\">workers</div>\n </div>\n )}\n </div>\n )}\n\n {/* Two-column wait / work grid */}\n <div className=\"grid grid-cols-2 gap-x-3\">\n <div>\n <p className=\"text-[9px] text-text-quaternary uppercase tracking-wider mb-1.5\">Wait (queue)</p>\n <MetricRow label=\"P99\" value={fmt(metric?.wait.p99 ?? null)} />\n <MetricRow label=\"avg\" value={fmt(metric?.wait.avg ?? null)} />\n </div>\n <div>\n <p className=\"text-[9px] text-text-quaternary uppercase tracking-wider mb-1.5\">Work (proc.)</p>\n <MetricRow label=\"P99\" value={fmt(metric?.work.p99 ?? null)} />\n <MetricRow label=\"avg\" value={fmt(metric?.work.avg ?? null)} />\n </div>\n </div>\n\n {/* SLA + priority */}\n <div className=\"border-t border-surface-border/40 pt-2 mt-3 space-y-1.5\">\n {slaMinutes && (\n <div className=\"flex items-center justify-between\">\n <span className=\"text-[10px] text-text-tertiary\">SLA target</span>\n <span className=\"text-xs font-mono text-text-secondary\">{slaMinutes}m</span>\n </div>\n )}\n {metric && metric.priority_count > 0 && (\n <Link\n to={priorityQueueLink(role)}\n className=\"flex items-center justify-between group\"\n >\n <span className=\"text-[10px]\" style={{ color: PRIORITY_TEXT_COLOR }}>\n {metric.priority_count} priority — pull oldest first\n </span>\n <ExternalLink className=\"w-3 h-3 transition-colors\" style={{ color: PRIORITY_TEXT_COLOR }} />\n </Link>\n )}\n </div>\n\n {/* View queue — `role=` drives the visible filter bar on the escalations\n page (the JSON `roles=` facet param filters silently and leaves the\n bar reading \"all roles\"). */}\n <div className=\"mt-4 border-t border-surface-border/40 pt-3\">\n <Link\n to={`/escalations/available?role=${encodeURIComponent(role.role)}`}\n className=\"flex items-center gap-1.5 text-[10px] text-accent hover:underline\"\n >\n View full queue\n <ExternalLink className=\"w-2.5 h-2.5\" />\n </Link>\n </div>\n </>\n );\n}\n\nfunction OverviewPanel({\n allMetrics,\n orderedRoles,\n period,\n}: {\n allMetrics: StationMetric[];\n orderedRoles: RoleDetail[];\n period: string;\n}) {\n const totalPending = allMetrics.reduce((s, m) => s + m.pending, 0);\n const totalResolved = allMetrics.reduce((s, m) => s + m.resolved, 0);\n const totalPriority = allMetrics.reduce((s, m) => s + m.priority_count, 0);\n\n const metricByRole = new Map(allMetrics.map((m) => [m.role, m]));\n const stationsAtRisk = allMetrics.filter((m) => m.priority_count > 0).length;\n const stationsWithLoad = allMetrics.filter((m) => m.pending > 0).length;\n\n return (\n <>\n <p className=\"text-[9px] text-text-quaternary uppercase tracking-wider mb-3\">\n Pipeline · {period}\n </p>\n\n {/* Health headline */}\n <div className=\"mb-4\">\n {stationsAtRisk > 0 ? (\n <p className=\"text-[11px]\" style={{ color: PRIORITY_TEXT_COLOR }}>\n {stationsAtRisk} station{stationsAtRisk > 1 ? 's' : ''} with priority items\n </p>\n ) : stationsWithLoad > 0 ? (\n <p className=\"text-[11px] text-amber-400\">\n {stationsWithLoad} station{stationsWithLoad > 1 ? 's' : ''} with backlog\n </p>\n ) : totalResolved > 0 ? (\n <p className=\"text-[11px] text-emerald-500\">Flowing — queue clear</p>\n ) : (\n <p className=\"text-[11px] text-text-quaternary\">Quiet — awaiting work</p>\n )}\n </div>\n\n {/* Key counters */}\n <div className=\"space-y-1.5 mb-4\">\n <div className=\"flex items-center justify-between\">\n <span className=\"text-[9px] text-text-quaternary uppercase tracking-wider\">Pending</span>\n <span className={`text-xs font-mono tabular-nums ${totalPending > 0 ? 'text-text-primary font-semibold' : 'text-text-quaternary'}`}>\n {totalPending}\n </span>\n </div>\n <div className=\"flex items-center justify-between\">\n <span className=\"text-[9px] text-text-quaternary uppercase tracking-wider\">Resolved · {period}</span>\n <span className=\"text-xs font-mono tabular-nums text-text-secondary\">{totalResolved}</span>\n </div>\n {totalPriority > 0 && (\n <div className=\"flex items-center justify-between\">\n <span className=\"text-[9px] text-text-quaternary uppercase tracking-wider\">Priority</span>\n <span className=\"text-xs font-mono tabular-nums font-semibold\" style={{ color: PRIORITY_TEXT_COLOR }}>\n {totalPriority}\n </span>\n </div>\n )}\n </div>\n\n {/* Divider + column headers */}\n <div className=\"border-t border-surface-border/40 pt-2 mb-1\">\n <div className=\"flex items-center gap-2\">\n <span className=\"text-[8px] text-text-quaternary uppercase tracking-wider flex-1\">Station</span>\n <span className=\"text-[8px] text-text-quaternary uppercase tracking-wider w-8 text-right shrink-0\">pend</span>\n <span className=\"text-[8px] text-text-quaternary uppercase tracking-wider w-8 text-right shrink-0\">actv</span>\n <span className=\"text-[8px] text-text-quaternary uppercase tracking-wider w-8 text-right shrink-0\">res</span>\n </div>\n </div>\n\n {/* Station list in pipeline order */}\n <div className=\"space-y-0.5\">\n {orderedRoles.map((r) => {\n const m = metricByRole.get(r.role);\n const pending = m?.pending ?? 0;\n const claimed = m?.claimed ?? 0;\n const resolved = m?.resolved ?? 0;\n const priorityCount = m?.priority_count ?? 0;\n const label = r.title || r.role;\n const hasAlert = priorityCount > 0;\n const hasLoad = pending > 0;\n\n return (\n <div key={r.role} className=\"flex items-center gap-2 py-0.5\">\n <span\n className={`text-[10px] font-mono flex-1 truncate ${\n hasLoad ? 'text-text-primary' : 'text-text-quaternary'\n }`}\n >\n {label}\n {hasAlert && (\n <span className=\"ml-1 text-[8px] font-semibold\" style={{ color: PRIORITY_TEXT_COLOR }}>\n {priorityCount} PRIORITY\n </span>\n )}\n </span>\n <span\n className={`text-[10px] font-mono tabular-nums w-8 text-right shrink-0 ${\n hasLoad ? 'text-text-primary font-semibold' : 'text-text-quaternary'\n }`}\n >\n {pending > 0 ? pending : '—'}\n </span>\n <span className={`text-[10px] font-mono tabular-nums w-8 text-right shrink-0 ${claimed > 0 ? 'text-accent' : 'text-text-quaternary'}`}>\n {claimed > 0 ? claimed : '—'}\n </span>\n <span className={`text-[10px] font-mono tabular-nums w-8 text-right shrink-0 ${resolved > 0 ? 'text-text-secondary' : 'text-text-quaternary'}`}>\n {resolved > 0 ? resolved : '—'}\n </span>\n </div>\n );\n })}\n </div>\n\n <p className=\"text-[9px] text-text-quaternary mt-5\">\n Select a station for queue detail.\n </p>\n </>\n );\n}\n\nexport function StationDetailPanel({\n role,\n allMetrics,\n orderedRoles,\n globalPeriod,\n onClose,\n}: StationDetailPanelProps) {\n return (\n <div className=\"w-[280px] shrink-0 px-6 py-8 flex flex-col overflow-y-auto min-h-0\">\n {role ? (\n <RoleView role={role} globalPeriod={globalPeriod} onClose={onClose} />\n ) : (\n <OverviewPanel allMetrics={allMetrics} orderedRoles={orderedRoles} period={globalPeriod} />\n )}\n </div>\n );\n}\n","import { useState, useMemo, useCallback } from 'react';\nimport { Link, useNavigate, useSearchParams } from 'react-router-dom';\nimport { GitMerge, RefreshCw } from 'lucide-react';\nimport { useRoleDetails, type RoleDetail } from '../../api/roles';\nimport { useStationMetrics } from '../../api/escalations';\nimport type { StationMetric } from '../../api/escalations';\nimport { useStationMetricsEvents } from '../../hooks/useEventHooks';\nimport { PageHeader } from '../../components/common/layout/PageHeader';\nimport {\n PaceChart,\n ACTIVE_COLOR,\n QUEUED_COLOR,\n RESOLVED_COLOR,\n PRIORITY_COLOR,\n PRIORITY_TEXT_COLOR,\n type ChartStation,\n} from './PaceChart';\nimport { StationDetailPanel } from './StationDetailPanel';\nimport { priorityQueueLink } from './priority-link';\n\n// Column band tints — the same hues as the chart's queue-composition bands\n// (~8% alpha), so PENDING/ACTIVE/RESOLVED in the table visually continue the\n// sky/indigo/sage story told above.\nconst PENDING_BAND = `${QUEUED_COLOR}14`;\nconst ACTIVE_BAND = `${ACTIVE_COLOR}14`;\nconst RESOLVED_BAND = `${RESOLVED_COLOR}14`;\n\n// ── Types ─────────────────────────────────────────────────────────────────────\n\nconst PERIODS = ['15m', '1h', '24h', '7d', '30d'] as const;\ntype Period = (typeof PERIODS)[number];\n\n// Window length in hours — used to express the target as a count for the\n// selected window (target_per_hour × hours), e.g. 22/h over 15m ≈ 5 expected.\nconst PERIOD_HOURS: Record<Period, number> = {\n '15m': 0.25,\n '1h': 1,\n '24h': 24,\n '7d': 168,\n '30d': 720,\n};\n\ninterface OrderedStation {\n role: RoleDetail;\n depth: number;\n}\n\n/**\n * One sequence of stations, named by its origin role. Execution is a graph;\n * each fragment is the human-readable line the user composed via parent_role.\n * Cross-fragment edges live in upstream_roles and render as a merge affordance\n * on the chart, never as a bend in the line.\n */\ninterface SequenceFragment {\n origin: RoleDetail;\n stations: OrderedStation[];\n}\n\n// ── Fragment builder — BFS per parent_role root, tracking actual depth ────────\n\nfunction buildFragments(roles: RoleDetail[]): SequenceFragment[] {\n const opsRoles = roles.filter((r) => r.ops_visible);\n const roleMap = new Map(opsRoles.map((r) => [r.role, r]));\n const visited = new Set<string>();\n\n // Roots: no parent, or parent not in ops set — each roots its own fragment.\n const roots = opsRoles.filter((r) => !r.parent_role || !roleMap.has(r.parent_role));\n\n const fragments: SequenceFragment[] = roots.map((root) => {\n const stations: OrderedStation[] = [];\n const queue: OrderedStation[] = [{ role: root, depth: 0 }];\n while (queue.length > 0) {\n const item = queue.shift()!;\n if (visited.has(item.role.role)) continue;\n visited.add(item.role.role);\n stations.push(item);\n opsRoles\n .filter((c) => c.parent_role === item.role.role && !visited.has(c.role))\n .forEach((c) => queue.push({ role: c, depth: item.depth + 1 }));\n }\n return { origin: root, stations };\n });\n\n // Unreachable (cycles / dangling) — each stands alone rather than vanishing.\n opsRoles\n .filter((r) => !visited.has(r.role))\n .forEach((r) => fragments.push({ origin: r, stations: [{ role: r, depth: 0 }] }));\n\n // Longest line first — the primary sequence leads; side-quests follow.\n return fragments.sort(\n (a, b) => b.stations.length - a.stations.length || a.origin.role.localeCompare(b.origin.role),\n );\n}\n\n// ── Helpers ───────────────────────────────────────────────────────────────────\n\nfunction fmtMin(v: number | null): string {\n if (v == null) return '—';\n if (v < 1) {\n const s = Math.round(v * 60);\n return s <= 0 ? '< 1s' : `${s}s`;\n }\n if (v < 60) return `${v.toFixed(0)}m`;\n return `${(v / 60).toFixed(1)}h`;\n}\n\nfunction loadBar(\n pending: number,\n target: number | null,\n throughputPct: number | null,\n resolved: number,\n) {\n if (!target) return { pct: null, color: 'bg-surface-border', historical: false };\n\n if (pending > 0) {\n const ratio = pending / target;\n const pct = Math.round(ratio * 100);\n // Amber: backlog. Orange: some work but well under pace. Green: healthy.\n const color = ratio > 1.0 ? 'bg-amber-400' : ratio < 0.2 ? 'bg-orange-400' : 'bg-emerald-400';\n return { pct, color, historical: false };\n }\n\n // Idle: use period throughput efficiency rather than the live backlog ratio.\n // resolved=0 means nothing happened in this window → no data, not failing.\n if (resolved === 0 || throughputPct == null) {\n return { pct: null, color: 'bg-surface-border', historical: true };\n }\n const ratio = throughputPct / 100;\n const pct = Math.round(throughputPct);\n // Amber: above baseline (produced more than target). Neutral otherwise —\n // below-target throughput isn't a positive green signal, just normal data.\n const color = ratio > 1.0 ? 'bg-amber-400' : 'bg-surface-border';\n return { pct, color, historical: true };\n}\n\n// ── Station table row ─────────────────────────────────────────────────────────\n\nfunction StationRow({\n role,\n metric,\n selected,\n onClick,\n}: {\n role: RoleDetail;\n metric: StationMetric | undefined;\n selected: boolean;\n onClick: () => void;\n}) {\n const pending = metric?.pending ?? 0;\n const claimed = metric?.claimed ?? 0;\n const resolved = metric?.resolved ?? 0;\n const priorityCount = metric?.priority_count ?? 0;\n const target = role.target_per_hour ?? null;\n const { pct, color, historical } = loadBar(pending, target, metric?.throughput_pct ?? null, resolved);\n const barWidth = pct != null ? Math.min(100, pct) : 0;\n\n return (\n <>\n <div\n role=\"button\"\n tabIndex={0}\n onClick={onClick}\n onKeyDown={(e) => { if (e.key === 'Enter') onClick(); }}\n className={`grid items-center gap-4 cursor-pointer transition-colors ${\n selected ? 'border-l-2 border-accent' : 'pl-0.5'\n }`}\n style={{ gridTemplateColumns: '1fr 64px 56px 56px 60px 72px 72px 104px' }}\n >\n {/* Role name */}\n <div className=\"flex items-center gap-1.5 min-w-0 py-3.5\">\n <span\n className=\"font-mono font-bold text-text-primary truncate text-[11px]\"\n >\n {role.role}\n </span>\n {role.title && (\n <span className=\"text-[10px] text-text-tertiary truncate\">{role.title}</span>\n )}\n {(role.upstream_roles?.length ?? 0) > 0 && (\n <span title={`Fed by ${role.upstream_roles.join(', ')}`} className=\"shrink-0 leading-none\">\n <GitMerge className=\"w-3 h-3 text-text-quaternary\" />\n </span>\n )}\n </div>\n\n {/* Target / hour */}\n <span\n className={`text-xs font-mono tabular-nums text-right py-3.5 ${\n target != null ? 'text-text-secondary' : 'text-text-quaternary'\n }`}\n >\n {target != null ? target : '—'}\n </span>\n\n {/* Pending — sky band, same hue as the chart's waiting band */}\n <div\n className=\"self-stretch flex items-center justify-end px-1.5\"\n style={{ backgroundColor: PENDING_BAND }}\n >\n <span\n className={`text-xs font-mono tabular-nums ${\n pending > 0 ? 'text-text-primary font-semibold' : 'text-text-quaternary'\n }`}\n >\n {pending}\n </span>\n </div>\n\n {/* Active — indigo band, same hue as the chart's worked band */}\n <div\n className=\"self-stretch flex items-center justify-end px-1.5\"\n style={{ backgroundColor: ACTIVE_BAND }}\n >\n <span\n className={`text-xs font-mono tabular-nums ${\n claimed > 0 ? 'text-accent font-semibold' : 'text-text-quaternary'\n }`}\n >\n {claimed}\n </span>\n </div>\n\n {/* Resolved — sage band, same hue as the chart's actual curve */}\n <div\n className=\"self-stretch flex items-center justify-end px-1.5\"\n style={{ backgroundColor: RESOLVED_BAND }}\n >\n <span className=\"text-xs font-mono tabular-nums text-text-quaternary\">\n {resolved}\n </span>\n </div>\n\n {/* P99 wait */}\n <span className=\"text-xs font-mono tabular-nums text-right py-3.5 text-text-secondary\">\n {fmtMin(metric?.wait.p99 ?? null)}\n </span>\n\n {/* P99 work */}\n <span className=\"text-xs font-mono tabular-nums text-right py-3.5 text-text-secondary\">\n {fmtMin(metric?.work.p99 ?? null)}\n </span>\n\n {/* Load mini-bar */}\n <div className=\"flex items-center gap-2 py-3.5\">\n <div className=\"w-12 h-1.5 bg-surface-sunken rounded-full overflow-hidden shrink-0\">\n <div className={`h-full rounded-full ${color}`} style={{ width: `${barWidth}%` }} />\n </div>\n <span\n className={`text-[10px] font-mono tabular-nums ${\n pct != null && pct > 100\n ? 'text-amber-500 font-semibold'\n : 'text-text-quaternary'\n }`}\n title={historical ? 'Throughput efficiency for the selected period' : undefined}\n >\n {pct != null ? `${pct}%${historical ? '↩' : ''}` : '—'}\n </span>\n </div>\n </div>\n\n {/* Priority sub-row — unclaimed items past the role's age threshold */}\n {priorityCount > 0 && (\n <div className=\"flex items-center gap-1.5 pb-2 pl-1\">\n <span\n className=\"w-2.5 h-2.5 rounded-full shrink-0\"\n style={{ backgroundColor: PRIORITY_COLOR }}\n />\n <Link\n to={priorityQueueLink(role)}\n className=\"text-[10px] hover:underline\"\n style={{ color: PRIORITY_TEXT_COLOR }}\n onClick={(e) => e.stopPropagation()}\n >\n {priorityCount} priority — pull oldest first →\n </Link>\n </div>\n )}\n </>\n );\n}\n\n// ── Table header ──────────────────────────────────────────────────────────────\n\nfunction TableHead() {\n // Queue-state columns carry the chart's band hue into the table.\n const cols: { label: string; band?: string; hue?: string }[] = [\n { label: 'ROLE' },\n { label: 'TARGET/H' },\n { label: 'PENDING', band: PENDING_BAND, hue: QUEUED_COLOR },\n { label: 'ACTIVE', band: ACTIVE_BAND, hue: ACTIVE_COLOR },\n { label: 'RESOLVED', band: RESOLVED_BAND, hue: RESOLVED_COLOR },\n { label: 'P99 WAIT' },\n { label: 'P99 WORK' },\n { label: 'TREND' },\n ];\n return (\n <div\n className=\"grid items-center gap-4 border-b border-surface-border mb-0.5\"\n style={{ gridTemplateColumns: '1fr 64px 56px 56px 60px 72px 72px 104px' }}\n >\n {cols.map((col, i) =>\n col.band ? (\n <div\n key={col.label}\n className=\"self-stretch flex items-center justify-end px-1.5\"\n style={{ backgroundColor: col.band }}\n >\n <span\n className=\"text-[9px] font-semibold uppercase tracking-wider\"\n style={{ color: col.hue }}\n >\n {col.label}\n </span>\n </div>\n ) : (\n <span\n key={col.label}\n className={`text-[9px] font-semibold uppercase tracking-wider text-text-quaternary py-1.5 ${\n i > 0 && i < 7 ? 'text-right' : ''\n }`}\n >\n {col.label}\n </span>\n ),\n )}\n </div>\n );\n}\n\n\n// ── Page ──────────────────────────────────────────────────────────────────────\n\nexport function OperationsPage() {\n const [period, setPeriod] = useState<Period>('24h');\n const [selectedRole, setSelectedRole] = useState<string | null>(null);\n const [refreshing, setRefreshing] = useState(false);\n\n // Push-driven refresh: every escalation event invalidates ['stationMetrics']\n // through the central event system (debounced, transport-agnostic). The\n // header's refresh button covers user-initiated reloads.\n useStationMetricsEvents();\n\n const { data: roleData, isLoading: rolesLoading, refetch: refetchRoles } = useRoleDetails();\n const {\n data: metricsData,\n isLoading: metricsLoading,\n refetch: refetchMetrics,\n } = useStationMetrics(period);\n\n const roles = roleData?.roles ?? [];\n const metrics = metricsData?.stations ?? [];\n\n // Sequence fragments, one per parent_role root. The active one is\n // DEEP-LINKED (?fragment=<origin role>) and each switch is a history entry,\n // so a shared link opens the same sequence and back/forward walks them.\n const fragments = useMemo((): SequenceFragment[] => buildFragments(roles), [roles]);\n const [searchParams, setSearchParams] = useSearchParams();\n const fragmentParam = searchParams.get('fragment');\n const activeFragment =\n fragments.find((f) => f.origin.role === fragmentParam) ?? fragments[0] ?? null;\n const selectFragment = useCallback(\n (origin: string) => {\n setSelectedRole(null);\n setSearchParams((prev) => {\n const p = new URLSearchParams(prev);\n p.set('fragment', origin);\n return p;\n });\n },\n [setSearchParams],\n );\n\n const ordered = activeFragment?.stations ?? [];\n const fragmentRoleSet = useMemo(\n () => new Set(ordered.map(({ role }) => role.role)),\n [ordered],\n );\n const fragmentMetrics = useMemo(\n () => metrics.filter((m) => fragmentRoleSet.has(m.role)),\n [metrics, fragmentRoleSet],\n );\n\n const chartStations = useMemo(\n (): ChartStation[] =>\n ordered.map(({ role: r }) => ({\n role: r.role,\n title: r.title,\n parent_role: r.parent_role,\n target_per_hour: r.target_per_hour ?? null,\n upstream_roles: r.upstream_roles ?? [],\n metric: metrics.find((m) => m.role === r.role),\n })),\n [ordered, metrics],\n );\n\n // Jump to the sequence that feeds this station — the merge glyph's click.\n const handleUpstreamSelect = useCallback(\n (upstreamRole: string) => {\n const target = fragments.find((f) =>\n f.stations.some(({ role }) => role.role === upstreamRole),\n );\n if (target) selectFragment(target.origin.role);\n },\n [fragments, selectFragment],\n );\n\n // Priority-badge click — open the station's queue oldest-first by its facet.\n const navigate = useNavigate();\n const handlePrioritySelect = useCallback(\n (roleName: string) => {\n const detail = roles.find((r) => r.role === roleName);\n if (detail) navigate(priorityQueueLink(detail));\n },\n [roles, navigate],\n );\n\n const selectedRoleDetail =\n ordered.find(({ role }) => role.role === selectedRole)?.role ?? null;\n\n const isLoading = rolesLoading || metricsLoading;\n\n const handleRefresh = async () => {\n setRefreshing(true);\n await Promise.all([refetchRoles(), refetchMetrics()]);\n setRefreshing(false);\n };\n\n const handleSelect = (role: string) =>\n setSelectedRole((prev) => (prev === role ? null : role));\n\n const periodSelector = (\n <div className=\"flex items-center gap-0.5\">\n {PERIODS.map((p) => (\n <button\n key={p}\n onClick={() => setPeriod(p)}\n className={`px-2.5 py-1 text-[10px] font-mono rounded transition-colors ${\n period === p\n ? 'bg-accent/10 text-accent font-semibold'\n : 'text-text-quaternary hover:text-text-secondary'\n }`}\n >\n {p}\n </button>\n ))}\n </div>\n );\n\n return (\n <div className=\"flex flex-col flex-1 min-h-0\">\n <PageHeader\n title=\"Pace Board\"\n docsHash=\"#docs:dashboard.md:pace-board\"\n center={periodSelector}\n actions={\n <div className=\"flex items-center gap-3\">\n <button\n onClick={handleRefresh}\n disabled={refreshing}\n className=\"text-text-quaternary hover:text-text-secondary transition-colors disabled:opacity-40\"\n >\n <RefreshCw className={`w-3.5 h-3.5 ${refreshing ? 'animate-spin' : ''}`} />\n </button>\n <Link to=\"/admin/roles\" className=\"btn-secondary text-xs\">\n Configure\n </Link>\n </div>\n }\n />\n\n {isLoading ? (\n <div className=\"animate-pulse space-y-6 mt-4\">\n <div className=\"h-8 bg-surface-sunken rounded w-64\" />\n <div className=\"h-64 bg-surface-sunken rounded w-full\" />\n <div className=\"space-y-3\">\n {[1, 2, 3].map((i) => (\n <div key={i} className=\"h-8 bg-surface-sunken rounded w-full\" />\n ))}\n </div>\n </div>\n ) : fragments.length === 0 ? (\n <div className=\"mt-8\">\n <p className=\"text-sm text-text-secondary mb-2\">Stations appear here once roles are marked visible in Operations.</p>\n <p className=\"text-xs text-text-tertiary\">\n Go to{' '}\n <Link to=\"/admin/roles\" className=\"text-accent hover:underline\">\n Roles\n </Link>{' '}\n and enable <strong>Visible in Operations</strong> on roles that represent pipeline\n stations.\n </p>\n </div>\n ) : (\n /* Console layout: fixed header (above) → flexible middle → fixed table (30vh) */\n <div className=\"flex flex-col flex-1 min-h-0\">\n\n {/* Sequence picker — one tab per fragment, named by its origin role.\n Only rendered when there is more than one story to tell. */}\n {fragments.length > 1 && (\n <div className=\"flex items-center gap-0.5 px-4 pt-2\">\n {fragments.map((f) => {\n const isActive = f.origin.role === activeFragment?.origin.role;\n return (\n <button\n key={f.origin.role}\n onClick={() => selectFragment(f.origin.role)}\n className={`px-2.5 py-1 text-[10px] font-mono rounded transition-colors ${\n isActive\n ? 'bg-accent/10 text-accent font-semibold'\n : 'text-text-quaternary hover:text-text-secondary'\n }`}\n >\n {f.origin.title ?? f.origin.role}\n <span className={`ml-1.5 tabular-nums ${isActive ? 'text-accent/60' : 'text-text-quaternary/60'}`}>\n {f.stations.length}\n </span>\n </button>\n );\n })}\n </div>\n )}\n\n {/* Middle row: flexible height — SVG fills left, sidebar fixed-width right */}\n <div className=\"flex-1 min-h-0 flex items-stretch overflow-hidden\">\n {/* SVG chart — scales to fill available space */}\n <div className=\"flex-1 min-w-0 min-h-0 flex flex-col justify-center overflow-hidden p-4\">\n <PaceChart\n stations={chartStations}\n selectedRole={selectedRole}\n onSelect={handleSelect}\n onUpstreamSelect={handleUpstreamSelect}\n onPrioritySelect={handlePrioritySelect}\n periodHours={PERIOD_HOURS[period]}\n />\n </div>\n {/* Vertical divider */}\n <div className=\"w-px bg-surface-border shrink-0 self-stretch\" />\n {/* Right sidebar — fixed width, scrolls its own content */}\n <StationDetailPanel\n role={selectedRoleDetail}\n allMetrics={fragmentMetrics}\n orderedRoles={ordered.map((o) => o.role)}\n globalPeriod={period}\n onClose={() => setSelectedRole(null)}\n />\n </div>\n\n {/* Bottom row: fixed 30vh — table header sticky, rows scroll */}\n <div className=\"h-[30vh] flex-none flex flex-col border-t border-surface-border overflow-hidden\">\n <TableHead />\n <div className=\"flex-1 min-h-0 overflow-y-auto divide-y divide-surface-border/30\">\n {ordered.map(({ role }) => (\n <StationRow\n key={role.role}\n role={role}\n metric={metrics.find((m) => m.role === role.role)}\n selected={selectedRole === role.role}\n onClick={() => handleSelect(role.role)}\n />\n ))}\n </div>\n </div>\n\n </div>\n )}\n </div>\n );\n}\n"],"names":["W","H","ML","MR","MT","MB","chartW","chartH","bottom","right","EASE","linePath","pts","p","i","catmullPath","segs","p0","p1","p2","p3","cp1x","cp1y","cp2x","cp2y","bandPath","upper","lower","forward","rev","back","backSegs","compact","n","last","a","paceColor","ratio","ACTIVE_COLOR","QUEUED_COLOR","RESOLVED_COLOR","PRIORITY_COLOR","PRIORITY_TEXT_COLOR","MIN_GAP","spreadLabels","labels","maxY","sorted","b","prev","placed","l","labelY","overflow","PaceChart","stations","selectedRole","onSelect","onUpstreamSelect","onPrioritySelect","periodHours","hoveredIdx","setHoveredIdx","useState","xOf","xMap","rows","target","expected","_a","_b","_c","_d","hasTargets","r","maxVal","yScale","v","withTarget","targetPts","actualPts","activePts","pendingPts","targetLinePath","actualSplinePath","activeSplinePath","pendingSplinePath","lastActual","lastTarget","lastActive","lastPending","areaPath","hasQueue","workedBandPath","queuedBandPath","showActiveLabel","showQueuedLabel","endLabels","px","cx","jsx","tp","ap","ar","hasWaiting","pendingLabelY","row","s","x","cy","stroke","isSelected","isHovered","tooltip","tipW","tipX","tipY","jsxs","e","priorityQueueLink","role","base","orderBy","PERIODS","isPeriod","fmt","min","MetricRow","label","value","PeriodSelector","period","onChange","RoleView","globalPeriod","onClose","setPeriod","data","useStationMetrics","metric","slaMinutes","targetPerHour","workerCount","Fragment","X","Link","ExternalLink","OverviewPanel","allMetrics","orderedRoles","totalPending","m","totalResolved","totalPriority","metricByRole","stationsAtRisk","stationsWithLoad","pending","claimed","resolved","priorityCount","hasAlert","hasLoad","StationDetailPanel","PENDING_BAND","ACTIVE_BAND","RESOLVED_BAND","PERIOD_HOURS","buildFragments","roles","opsRoles","roleMap","visited","fragments","root","queue","item","c","fmtMin","loadBar","throughputPct","pct","color","StationRow","selected","onClick","historical","barWidth","GitMerge","TableHead","cols","col","OperationsPage","setSelectedRole","refreshing","setRefreshing","useStationMetricsEvents","roleData","rolesLoading","refetchRoles","useRoleDetails","metricsData","metricsLoading","refetchMetrics","metrics","useMemo","searchParams","setSearchParams","useSearchParams","fragmentParam","activeFragment","f","selectFragment","useCallback","origin","ordered","fragmentRoleSet","fragmentMetrics","chartStations","handleUpstreamSelect","upstreamRole","navigate","useNavigate","handlePrioritySelect","roleName","detail","selectedRoleDetail","isLoading","handleRefresh","handleSelect","periodSelector","PageHeader","RefreshCw","isActive","o"],"mappings":"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"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"file":"WorkflowPill-BegV-iRF.js","sources":["../../src/lib/type-color.ts","../../src/components/common/display/WorkflowPill.tsx"],"sourcesContent":["/**\n * Deterministic color assignment for workflow type names.\n *\n * Maps any string to a consistent color from a curated 24-color palette.\n * The same type name always produces the same color across the entire UI.\n * Uses a simple hash to distribute names evenly across the palette.\n *\n * Each entry provides:\n * text — icon and text color (Tailwind class)\n * bg — subtle background tint (Tailwind class)\n */\n\nconst PALETTE: Array<{ text: string; bg: string }> = [\n { text: 'text-rose-400', bg: 'bg-rose-400/[0.08]' },\n { text: 'text-pink-400', bg: 'bg-pink-400/[0.08]' },\n { text: 'text-fuchsia-400', bg: 'bg-fuchsia-400/[0.08]' },\n { text: 'text-purple-400', bg: 'bg-purple-400/[0.08]' },\n { text: 'text-violet-400', bg: 'bg-violet-400/[0.08]' },\n { text: 'text-indigo-400', bg: 'bg-indigo-400/[0.08]' },\n { text: 'text-blue-400', bg: 'bg-blue-400/[0.08]' },\n { text: 'text-sky-400', bg: 'bg-sky-400/[0.08]' },\n { text: 'text-cyan-400', bg: 'bg-cyan-400/[0.08]' },\n { text: 'text-teal-400', bg: 'bg-teal-400/[0.08]' },\n { text: 'text-emerald-400', bg: 'bg-emerald-400/[0.08]' },\n { text: 'text-green-400', bg: 'bg-green-400/[0.08]' },\n { text: 'text-lime-400', bg: 'bg-lime-400/[0.08]' },\n { text: 'text-yellow-400', bg: 'bg-yellow-400/[0.08]' },\n { text: 'text-amber-400', bg: 'bg-amber-400/[0.08]' },\n { text: 'text-orange-400', bg: 'bg-orange-400/[0.08]' },\n { text: 'text-red-400', bg: 'bg-red-400/[0.08]' },\n { text: 'text-stone-400', bg: 'bg-stone-400/[0.08]' },\n { text: 'text-rose-300', bg: 'bg-rose-300/[0.08]' },\n { text: 'text-violet-300', bg: 'bg-violet-300/[0.08]' },\n { text: 'text-sky-300', bg: 'bg-sky-300/[0.08]' },\n { text: 'text-teal-300', bg: 'bg-teal-300/[0.08]' },\n { text: 'text-amber-300', bg: 'bg-amber-300/[0.08]' },\n { text: 'text-pink-300', bg: 'bg-pink-300/[0.08]' },\n];\n\nfunction hash(str: string): number {\n let h = 0;\n for (let i = 0; i < str.length; i++) {\n h = ((h << 5) - h + str.charCodeAt(i)) | 0;\n }\n return Math.abs(h);\n}\n\nexport function typeColor(typeName: string): { text: string; bg: string } {\n return PALETTE[hash(typeName) % PALETTE.length];\n}\n","import { Workflow, ShieldCheck, SlidersHorizontal, Wand2, Zap } from 'lucide-react';\nimport { typeColor } from '../../../lib/type-color';\n\ntype WorkflowVariant = 'durable' | 'configured' | 'certified' | 'pipeline' | 'capability';\n\ninterface WorkflowPillProps {\n type: string;\n size?: 'xs' | 'sm' | 'md';\n /** @deprecated Use `variant` instead */\n certified?: boolean;\n variant?: WorkflowVariant;\n}\n\nconst VARIANT_ICON: Record<WorkflowVariant, typeof Workflow> = {\n certified: ShieldCheck,\n configured: SlidersHorizontal,\n pipeline: Wand2,\n capability: Zap,\n durable: Workflow,\n};\n\nconst VARIANT_FIXED_COLOR: Record<string, { text: string; bg: string }> = {\n certified: { text: 'text-status-success', bg: 'bg-status-success/10' },\n configured: { text: 'text-amber-500/70', bg: 'bg-amber-200/[0.12]' },\n};\n\nconst SIZE_CONFIG = {\n xs: { bulb: 'w-4 h-4', icon: 'w-2 h-2', text: 'text-[9px]', pad: 'pr-1.5 pl-0.5', gap: '-ml-1' },\n sm: { bulb: 'w-5 h-5', icon: 'w-2.5 h-2.5', text: 'text-[11px]', pad: 'pr-2 pl-1', gap: '-ml-1.5' },\n md: { bulb: 'w-6 h-6', icon: 'w-3 h-3', text: 'text-[13px]', pad: 'pr-2.5 pl-1.5', gap: '-ml-2' },\n} as const;\n\nexport function WorkflowPill({ type, size = 'sm', certified, variant }: WorkflowPillProps) {\n const resolved = variant ?? (certified ? 'certified' : 'durable');\n const Icon = VARIANT_ICON[resolved];\n const s = SIZE_CONFIG[size];\n\n const fixed = VARIANT_FIXED_COLOR[resolved];\n const derived = fixed ? null : typeColor(type);\n const iconColor = fixed?.text ?? derived?.text ?? 'text-accent/75';\n const bgColor = fixed?.bg ?? derived?.bg ?? 'bg-accent/[0.08]';\n\n return (\n <span className=\"inline-flex items-center\">\n {/* Bulb — circular icon container */}\n <span className={`${s.bulb} rounded-full ${bgColor} inline-flex items-center justify-center shrink-0 z-[1]`}>\n <Icon className={`${s.icon} ${iconColor}`} />\n </span>\n {/* Label — extends right, overlapping the bulb */}\n <span\n className={`${s.gap} ${s.pad} ${bgColor} rounded-r-full font-mono text-text-secondary`}\n style={{ fontSize: s.text === 'text-[9px]' ? '9px' : s.text === 'text-[11px]' ? '11px' : '13px', lineHeight: 1.4 }}\n >\n {type}\n </span>\n </span>\n );\n}\n"],"names":["PALETTE","hash","str","h","i","typeColor","typeName","VARIANT_ICON","ShieldCheck","SlidersHorizontal","Wand2","Zap","Workflow","VARIANT_FIXED_COLOR","SIZE_CONFIG","WorkflowPill","type","size","certified","variant","resolved","Icon","s","fixed","derived","iconColor","bgColor","jsxs","jsx"],"mappings":"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"}
|