@saluzi/saluzi-edu 0.2.59 → 0.2.61

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (152) hide show
  1. package/dist/cli.js +362 -120
  2. package/dist/guide/guide-data.json +5 -1
  3. package/dist/parade/web/cube.js +403 -81
  4. package/dist/parade/web/index.html +172 -41
  5. package/dist/parade/web/menu.html +195 -0
  6. package/dist/rcs-web/assets/{AdminPanel-CbcZ7Vnr.js → AdminPanel-Xlza8Stt.js} +1 -1
  7. package/dist/rcs-web/assets/Claim-BMWwu213.js +1 -0
  8. package/{packages/remote-control-server/web/dist/assets/Dashboard-BZGcSCFY.js → dist/rcs-web/assets/Dashboard-D5awDzM3.js} +2 -2
  9. package/{packages/remote-control-server/web/dist/assets/Join-B_e3BKa3.js → dist/rcs-web/assets/Join-ok37ggfZ.js} +1 -1
  10. package/dist/rcs-web/assets/{Login-u6CGGkoD.js → Login-5qo97rq-.js} +1 -1
  11. package/dist/rcs-web/assets/{Register-C4Fk0ibc.js → Register-D_UOG8ar.js} +1 -1
  12. package/dist/rcs-web/assets/SessionDetail-BbHrR4Qu.js +65 -0
  13. package/dist/rcs-web/assets/{SessionVisibilityBadge-BHKxlh8U.js → SessionVisibilityBadge-Dy4FwTzo.js} +1 -1
  14. package/dist/rcs-web/assets/{Setup-DReCE0zx.js → Setup-GnPeNyH9.js} +1 -1
  15. package/dist/rcs-web/assets/{ShareLanding-qE1U0WKZ.js → ShareLanding-CUjQydLL.js} +1 -1
  16. package/dist/rcs-web/assets/{TeamCreate-BgwgZP4a.js → TeamCreate-Ck2fEoUW.js} +1 -1
  17. package/dist/rcs-web/assets/{TeamDetail-BHCvnI2t.js → TeamDetail-De6EPND0.js} +2 -2
  18. package/dist/rcs-web/assets/{TeamList-B7uC8lDV.js → TeamList-C-0LJRxd.js} +1 -1
  19. package/dist/rcs-web/assets/{TeamSettings-DtgvrKEE.js → TeamSettings-BszOrXVW.js} +1 -1
  20. package/dist/rcs-web/assets/{UserSearchInput-dgxhGWfJ.js → UserSearchInput-CyQWtLL-.js} +1 -1
  21. package/dist/rcs-web/assets/{UserSettings-Zyjam23-.js → UserSettings-DgyYp-1y.js} +1 -1
  22. package/dist/rcs-web/assets/{arc-DgC_wPAg.js → arc-BaOPt7k-.js} +1 -1
  23. package/dist/rcs-web/assets/{architectureDiagram-3BPJPVTR-BnnCRJZs.js → architectureDiagram-3BPJPVTR-SvIakXbT.js} +1 -1
  24. package/{packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-CvpYdvcP.js → dist/rcs-web/assets/blockDiagram-GPEHLZMM-CiDp9J7i.js} +1 -1
  25. package/dist/rcs-web/assets/{c4Diagram-AAUBKEIU-DNK__-3d.js → c4Diagram-AAUBKEIU-D9wzA9ur.js} +1 -1
  26. package/dist/rcs-web/assets/channel-CirgRB_G.js +1 -0
  27. package/dist/rcs-web/assets/{chunk-2J33WTMH-5JQBz2na.js → chunk-2J33WTMH-qfYueRAP.js} +1 -1
  28. package/dist/rcs-web/assets/{chunk-4BX2VUAB-YJh8d73x.js → chunk-4BX2VUAB-D1V_UCiK.js} +1 -1
  29. package/dist/rcs-web/assets/{chunk-55IACEB6-U5c5GQZN.js → chunk-55IACEB6-C67QiGmo.js} +1 -1
  30. package/{packages/remote-control-server/web/dist/assets/chunk-727SXJPM-ifGCid_3.js → dist/rcs-web/assets/chunk-727SXJPM-D2Wln4TT.js} +1 -1
  31. package/dist/rcs-web/assets/{chunk-AQP2D5EJ-DxqPH2_Q.js → chunk-AQP2D5EJ-03h8hZhw.js} +1 -1
  32. package/dist/rcs-web/assets/{chunk-FMBD7UC4-dDtjQRPo.js → chunk-FMBD7UC4-7zas8pDp.js} +1 -1
  33. package/dist/rcs-web/assets/{chunk-ND2GUHAM-0UF6vhhE.js → chunk-ND2GUHAM-PUXucyY6.js} +1 -1
  34. package/dist/rcs-web/assets/{chunk-QZHKN3VN-CfZ4xcBz.js → chunk-QZHKN3VN-M3U5l4yW.js} +1 -1
  35. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-Cq12UEz9.js +1 -0
  36. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-Cq12UEz9.js +1 -0
  37. package/dist/rcs-web/assets/{code-block-IT6T5CEO-B4B0UrXo.js → code-block-IT6T5CEO-BgUazuuS.js} +1 -1
  38. package/dist/rcs-web/assets/{copy-_OW0eGyp.js → copy-DjaSB1Rw.js} +1 -1
  39. package/dist/rcs-web/assets/{cose-bilkent-S5V4N54A-B19xQvnh.js → cose-bilkent-S5V4N54A-zPupX74N.js} +1 -1
  40. package/dist/rcs-web/assets/{dagre-BM42HDAG-5JJnjI1B.js → dagre-BM42HDAG-B_EJ050J.js} +1 -1
  41. package/dist/rcs-web/assets/{diagram-2AECGRRQ-BY2NR7vr.js → diagram-2AECGRRQ-dnnxPH7w.js} +1 -1
  42. package/dist/rcs-web/assets/{diagram-5GNKFQAL-3zC3GraP.js → diagram-5GNKFQAL-CLEOqCLZ.js} +1 -1
  43. package/dist/rcs-web/assets/{diagram-KO2AKTUF-T1Jt5zH3.js → diagram-KO2AKTUF-DKBGpiRW.js} +1 -1
  44. package/dist/rcs-web/assets/{diagram-LMA3HP47-ehrkQG1e.js → diagram-LMA3HP47-BAilhXiA.js} +1 -1
  45. package/dist/rcs-web/assets/{diagram-OG6HWLK6-D8ZJlGs0.js → diagram-OG6HWLK6-C3CdmOxC.js} +1 -1
  46. package/dist/rcs-web/assets/{erDiagram-TEJ5UH35-Ckn1pY7G.js → erDiagram-TEJ5UH35-D1yv-3Jv.js} +1 -1
  47. package/dist/rcs-web/assets/{flowDiagram-I6XJVG4X-C1xnAOfH.js → flowDiagram-I6XJVG4X-DpW11XWj.js} +1 -1
  48. package/{packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-Dw8qOfCT.js → dist/rcs-web/assets/ganttDiagram-6RSMTGT7-CWr8suZm.js} +1 -1
  49. package/{packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-Cg3La45a.js → dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-BspxLSNV.js} +1 -1
  50. package/dist/rcs-web/assets/{infoDiagram-5YYISTIA-CGH2aXpd.js → infoDiagram-5YYISTIA-Bow20s-Q.js} +1 -1
  51. package/dist/rcs-web/assets/{ishikawaDiagram-YF4QCWOH-x89F9Igr.js → ishikawaDiagram-YF4QCWOH-BESvGYGI.js} +1 -1
  52. package/{packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-Ckd_pKi0.js → dist/rcs-web/assets/journeyDiagram-JHISSGLW-DZ6gzC7H.js} +1 -1
  53. package/dist/rcs-web/assets/{kanban-definition-UN3LZRKU-D2dEXdru.js → kanban-definition-UN3LZRKU-DIQo2GiM.js} +1 -1
  54. package/dist/rcs-web/assets/{linear-CexxJrg8.js → linear-CdXALiZ8.js} +1 -1
  55. package/dist/rcs-web/assets/{main-L9iyf39T.js → main-CwLwGVDc.js} +7 -7
  56. package/dist/rcs-web/assets/{mermaid-VLURNSYL-BomP37Br.js → mermaid-VLURNSYL-C0ZKM74W.js} +4 -4
  57. package/dist/rcs-web/assets/{mermaid.core-BxkfDFm_.js → mermaid.core-eEtoQhrQ.js} +5 -5
  58. package/dist/rcs-web/assets/{mindmap-definition-RKZ34NQL-HVsZx8Je.js → mindmap-definition-RKZ34NQL-CcW3H_cH.js} +1 -1
  59. package/dist/rcs-web/assets/{pieDiagram-4H26LBE5-D0U9q74D.js → pieDiagram-4H26LBE5-DpXUpJMl.js} +1 -1
  60. package/{packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-CWPBZMPj.js → dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-DOwMRzzP.js} +1 -1
  61. package/dist/rcs-web/assets/{requirementDiagram-4Y6WPE33-CiQbIN_P.js → requirementDiagram-4Y6WPE33-CBi_6NcW.js} +1 -1
  62. package/{packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-Bw66fXM0.js → dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-CKbAoTHm.js} +1 -1
  63. package/dist/rcs-web/assets/{sequenceDiagram-3UESZ5HK-lv5WhGb6.js → sequenceDiagram-3UESZ5HK-a3rYCe-w.js} +1 -1
  64. package/dist/rcs-web/assets/{stateDiagram-AJRCARHV-BLRRpX3g.js → stateDiagram-AJRCARHV-B6R_Dt84.js} +1 -1
  65. package/dist/rcs-web/assets/{stateDiagram-v2-BHNVJYJU-B-5gzDu6.js → stateDiagram-v2-BHNVJYJU-Cq935fvz.js} +1 -1
  66. package/{packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-DO8oVMVA.js → dist/rcs-web/assets/timeline-definition-PNZ67QCA-8DXPKq1k.js} +1 -1
  67. package/dist/rcs-web/assets/{vennDiagram-CIIHVFJN-D2RbeAHj.js → vennDiagram-CIIHVFJN-BfDEkku5.js} +1 -1
  68. package/dist/rcs-web/assets/{wardleyDiagram-YWT4CUSO-D8D1cIXp.js → wardleyDiagram-YWT4CUSO-Dvp2XZVf.js} +1 -1
  69. package/{packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-Bzk5AxMb.js → dist/rcs-web/assets/xychartDiagram-2RQKCTM6-DiPjQLUx.js} +1 -1
  70. package/dist/rcs-web/index.html +1 -1
  71. package/package.json +1 -1
  72. package/packages/remote-control-server/src/__tests__/event-bus.test.ts +50 -0
  73. package/packages/remote-control-server/src/__tests__/routes.test.ts +115 -5
  74. package/packages/remote-control-server/src/__tests__/turn-end-status.test.ts +205 -0
  75. package/packages/remote-control-server/src/routes/v2/worker-events.ts +22 -0
  76. package/packages/remote-control-server/src/routes/web/sessions.ts +64 -1
  77. package/packages/remote-control-server/src/transport/event-bus.ts +9 -0
  78. package/packages/remote-control-server/web/dist/assets/{AdminPanel-CbcZ7Vnr.js → AdminPanel-Xlza8Stt.js} +1 -1
  79. package/packages/remote-control-server/web/dist/assets/Claim-BMWwu213.js +1 -0
  80. package/{dist/rcs-web/assets/Dashboard-BZGcSCFY.js → packages/remote-control-server/web/dist/assets/Dashboard-D5awDzM3.js} +2 -2
  81. package/{dist/rcs-web/assets/Join-B_e3BKa3.js → packages/remote-control-server/web/dist/assets/Join-ok37ggfZ.js} +1 -1
  82. package/packages/remote-control-server/web/dist/assets/{Login-u6CGGkoD.js → Login-5qo97rq-.js} +1 -1
  83. package/packages/remote-control-server/web/dist/assets/{Register-C4Fk0ibc.js → Register-D_UOG8ar.js} +1 -1
  84. package/packages/remote-control-server/web/dist/assets/SessionDetail-BbHrR4Qu.js +65 -0
  85. package/packages/remote-control-server/web/dist/assets/{SessionVisibilityBadge-BHKxlh8U.js → SessionVisibilityBadge-Dy4FwTzo.js} +1 -1
  86. package/packages/remote-control-server/web/dist/assets/{Setup-DReCE0zx.js → Setup-GnPeNyH9.js} +1 -1
  87. package/packages/remote-control-server/web/dist/assets/{ShareLanding-qE1U0WKZ.js → ShareLanding-CUjQydLL.js} +1 -1
  88. package/packages/remote-control-server/web/dist/assets/{TeamCreate-BgwgZP4a.js → TeamCreate-Ck2fEoUW.js} +1 -1
  89. package/packages/remote-control-server/web/dist/assets/{TeamDetail-BHCvnI2t.js → TeamDetail-De6EPND0.js} +2 -2
  90. package/packages/remote-control-server/web/dist/assets/{TeamList-B7uC8lDV.js → TeamList-C-0LJRxd.js} +1 -1
  91. package/packages/remote-control-server/web/dist/assets/{TeamSettings-DtgvrKEE.js → TeamSettings-BszOrXVW.js} +1 -1
  92. package/packages/remote-control-server/web/dist/assets/{UserSearchInput-dgxhGWfJ.js → UserSearchInput-CyQWtLL-.js} +1 -1
  93. package/packages/remote-control-server/web/dist/assets/{UserSettings-Zyjam23-.js → UserSettings-DgyYp-1y.js} +1 -1
  94. package/packages/remote-control-server/web/dist/assets/{arc-DgC_wPAg.js → arc-BaOPt7k-.js} +1 -1
  95. package/packages/remote-control-server/web/dist/assets/{architectureDiagram-3BPJPVTR-BnnCRJZs.js → architectureDiagram-3BPJPVTR-SvIakXbT.js} +1 -1
  96. package/{dist/rcs-web/assets/blockDiagram-GPEHLZMM-CvpYdvcP.js → packages/remote-control-server/web/dist/assets/blockDiagram-GPEHLZMM-CiDp9J7i.js} +1 -1
  97. package/packages/remote-control-server/web/dist/assets/{c4Diagram-AAUBKEIU-DNK__-3d.js → c4Diagram-AAUBKEIU-D9wzA9ur.js} +1 -1
  98. package/packages/remote-control-server/web/dist/assets/channel-CirgRB_G.js +1 -0
  99. package/packages/remote-control-server/web/dist/assets/{chunk-2J33WTMH-5JQBz2na.js → chunk-2J33WTMH-qfYueRAP.js} +1 -1
  100. package/packages/remote-control-server/web/dist/assets/{chunk-4BX2VUAB-YJh8d73x.js → chunk-4BX2VUAB-D1V_UCiK.js} +1 -1
  101. package/packages/remote-control-server/web/dist/assets/{chunk-55IACEB6-U5c5GQZN.js → chunk-55IACEB6-C67QiGmo.js} +1 -1
  102. package/{dist/rcs-web/assets/chunk-727SXJPM-ifGCid_3.js → packages/remote-control-server/web/dist/assets/chunk-727SXJPM-D2Wln4TT.js} +1 -1
  103. package/packages/remote-control-server/web/dist/assets/{chunk-AQP2D5EJ-DxqPH2_Q.js → chunk-AQP2D5EJ-03h8hZhw.js} +1 -1
  104. package/packages/remote-control-server/web/dist/assets/{chunk-FMBD7UC4-dDtjQRPo.js → chunk-FMBD7UC4-7zas8pDp.js} +1 -1
  105. package/packages/remote-control-server/web/dist/assets/{chunk-ND2GUHAM-0UF6vhhE.js → chunk-ND2GUHAM-PUXucyY6.js} +1 -1
  106. package/packages/remote-control-server/web/dist/assets/{chunk-QZHKN3VN-CfZ4xcBz.js → chunk-QZHKN3VN-M3U5l4yW.js} +1 -1
  107. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-Cq12UEz9.js +1 -0
  108. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-Cq12UEz9.js +1 -0
  109. package/packages/remote-control-server/web/dist/assets/{code-block-IT6T5CEO-B4B0UrXo.js → code-block-IT6T5CEO-BgUazuuS.js} +1 -1
  110. package/packages/remote-control-server/web/dist/assets/{copy-_OW0eGyp.js → copy-DjaSB1Rw.js} +1 -1
  111. package/packages/remote-control-server/web/dist/assets/{cose-bilkent-S5V4N54A-B19xQvnh.js → cose-bilkent-S5V4N54A-zPupX74N.js} +1 -1
  112. package/packages/remote-control-server/web/dist/assets/{dagre-BM42HDAG-5JJnjI1B.js → dagre-BM42HDAG-B_EJ050J.js} +1 -1
  113. package/packages/remote-control-server/web/dist/assets/{diagram-2AECGRRQ-BY2NR7vr.js → diagram-2AECGRRQ-dnnxPH7w.js} +1 -1
  114. package/packages/remote-control-server/web/dist/assets/{diagram-5GNKFQAL-3zC3GraP.js → diagram-5GNKFQAL-CLEOqCLZ.js} +1 -1
  115. package/packages/remote-control-server/web/dist/assets/{diagram-KO2AKTUF-T1Jt5zH3.js → diagram-KO2AKTUF-DKBGpiRW.js} +1 -1
  116. package/packages/remote-control-server/web/dist/assets/{diagram-LMA3HP47-ehrkQG1e.js → diagram-LMA3HP47-BAilhXiA.js} +1 -1
  117. package/packages/remote-control-server/web/dist/assets/{diagram-OG6HWLK6-D8ZJlGs0.js → diagram-OG6HWLK6-C3CdmOxC.js} +1 -1
  118. package/packages/remote-control-server/web/dist/assets/{erDiagram-TEJ5UH35-Ckn1pY7G.js → erDiagram-TEJ5UH35-D1yv-3Jv.js} +1 -1
  119. package/packages/remote-control-server/web/dist/assets/{flowDiagram-I6XJVG4X-C1xnAOfH.js → flowDiagram-I6XJVG4X-DpW11XWj.js} +1 -1
  120. package/{dist/rcs-web/assets/ganttDiagram-6RSMTGT7-Dw8qOfCT.js → packages/remote-control-server/web/dist/assets/ganttDiagram-6RSMTGT7-CWr8suZm.js} +1 -1
  121. package/{dist/rcs-web/assets/gitGraphDiagram-PVQCEYII-Cg3La45a.js → packages/remote-control-server/web/dist/assets/gitGraphDiagram-PVQCEYII-BspxLSNV.js} +1 -1
  122. package/packages/remote-control-server/web/dist/assets/{infoDiagram-5YYISTIA-CGH2aXpd.js → infoDiagram-5YYISTIA-Bow20s-Q.js} +1 -1
  123. package/packages/remote-control-server/web/dist/assets/{ishikawaDiagram-YF4QCWOH-x89F9Igr.js → ishikawaDiagram-YF4QCWOH-BESvGYGI.js} +1 -1
  124. package/{dist/rcs-web/assets/journeyDiagram-JHISSGLW-Ckd_pKi0.js → packages/remote-control-server/web/dist/assets/journeyDiagram-JHISSGLW-DZ6gzC7H.js} +1 -1
  125. package/packages/remote-control-server/web/dist/assets/{kanban-definition-UN3LZRKU-D2dEXdru.js → kanban-definition-UN3LZRKU-DIQo2GiM.js} +1 -1
  126. package/packages/remote-control-server/web/dist/assets/{linear-CexxJrg8.js → linear-CdXALiZ8.js} +1 -1
  127. package/packages/remote-control-server/web/dist/assets/{main-L9iyf39T.js → main-CwLwGVDc.js} +7 -7
  128. package/packages/remote-control-server/web/dist/assets/{mermaid-VLURNSYL-BomP37Br.js → mermaid-VLURNSYL-C0ZKM74W.js} +4 -4
  129. package/packages/remote-control-server/web/dist/assets/{mermaid.core-BxkfDFm_.js → mermaid.core-eEtoQhrQ.js} +5 -5
  130. package/packages/remote-control-server/web/dist/assets/{mindmap-definition-RKZ34NQL-HVsZx8Je.js → mindmap-definition-RKZ34NQL-CcW3H_cH.js} +1 -1
  131. package/packages/remote-control-server/web/dist/assets/{pieDiagram-4H26LBE5-D0U9q74D.js → pieDiagram-4H26LBE5-DpXUpJMl.js} +1 -1
  132. package/{dist/rcs-web/assets/quadrantDiagram-W4KKPZXB-CWPBZMPj.js → packages/remote-control-server/web/dist/assets/quadrantDiagram-W4KKPZXB-DOwMRzzP.js} +1 -1
  133. package/packages/remote-control-server/web/dist/assets/{requirementDiagram-4Y6WPE33-CiQbIN_P.js → requirementDiagram-4Y6WPE33-CBi_6NcW.js} +1 -1
  134. package/{dist/rcs-web/assets/sankeyDiagram-5OEKKPKP-Bw66fXM0.js → packages/remote-control-server/web/dist/assets/sankeyDiagram-5OEKKPKP-CKbAoTHm.js} +1 -1
  135. package/packages/remote-control-server/web/dist/assets/{sequenceDiagram-3UESZ5HK-lv5WhGb6.js → sequenceDiagram-3UESZ5HK-a3rYCe-w.js} +1 -1
  136. package/packages/remote-control-server/web/dist/assets/{stateDiagram-AJRCARHV-BLRRpX3g.js → stateDiagram-AJRCARHV-B6R_Dt84.js} +1 -1
  137. package/packages/remote-control-server/web/dist/assets/{stateDiagram-v2-BHNVJYJU-B-5gzDu6.js → stateDiagram-v2-BHNVJYJU-Cq935fvz.js} +1 -1
  138. package/{dist/rcs-web/assets/timeline-definition-PNZ67QCA-DO8oVMVA.js → packages/remote-control-server/web/dist/assets/timeline-definition-PNZ67QCA-8DXPKq1k.js} +1 -1
  139. package/packages/remote-control-server/web/dist/assets/{vennDiagram-CIIHVFJN-D2RbeAHj.js → vennDiagram-CIIHVFJN-BfDEkku5.js} +1 -1
  140. package/packages/remote-control-server/web/dist/assets/{wardleyDiagram-YWT4CUSO-D8D1cIXp.js → wardleyDiagram-YWT4CUSO-Dvp2XZVf.js} +1 -1
  141. package/{dist/rcs-web/assets/xychartDiagram-2RQKCTM6-Bzk5AxMb.js → packages/remote-control-server/web/dist/assets/xychartDiagram-2RQKCTM6-DiPjQLUx.js} +1 -1
  142. package/packages/remote-control-server/web/dist/index.html +1 -1
  143. package/dist/rcs-web/assets/Claim-CN3D3Ku6.js +0 -1
  144. package/dist/rcs-web/assets/SessionDetail-Bd1YgozL.js +0 -65
  145. package/dist/rcs-web/assets/channel-DB6b62-p.js +0 -1
  146. package/dist/rcs-web/assets/classDiagram-4FO5ZUOK-DkXLqlKv.js +0 -1
  147. package/dist/rcs-web/assets/classDiagram-v2-Q7XG4LA2-DkXLqlKv.js +0 -1
  148. package/packages/remote-control-server/web/dist/assets/Claim-CN3D3Ku6.js +0 -1
  149. package/packages/remote-control-server/web/dist/assets/SessionDetail-Bd1YgozL.js +0 -65
  150. package/packages/remote-control-server/web/dist/assets/channel-DB6b62-p.js +0 -1
  151. package/packages/remote-control-server/web/dist/assets/classDiagram-4FO5ZUOK-DkXLqlKv.js +0 -1
  152. package/packages/remote-control-server/web/dist/assets/classDiagram-v2-Q7XG4LA2-DkXLqlKv.js +0 -1
@@ -6066,6 +6066,10 @@
6066
6066
  "category": "bridge",
6067
6067
  "description": "worker 连接的 RCS 地址"
6068
6068
  },
6069
+ {
6070
+ "name": "SALUZI_BRIDGE_CLAIM_TIMEOUT_MS",
6071
+ "category": "other"
6072
+ },
6069
6073
  {
6070
6074
  "name": "SALUZI_BRIDGE_MAX_SESSIONS",
6071
6075
  "category": "other"
@@ -7144,7 +7148,7 @@
7144
7148
  "可视化"
7145
7149
  ]
7146
7150
  },
7147
- "content": "\n## 什么是 /parade\n\nParade 是 Saluzi 的桌面悬浮提示功能。它在桌面显示一个小型悬浮窗口(基于 Electron),实时反映:\n\n- **运行中会话数**:当前正在执行工具调用的会话\n- **等待中会话数**:等待用户输入或权限确认的会话\n- **连接状态**:WebSocket 连接状态指示灯\n\n外观示意(运行中 2 / 等待 1):\n\n::parade-preview::\n\n## 启用 Parade\n\n```\n> /parade\n```\n\n首次启用会自动检测并安装 Electron(若未安装),然后启动悬浮窗。悬浮窗默认定位在**主显示器顶部居中**,220×130 像素,无边框始终置顶。\n\n## 关闭 Parade\n\n```\n> /parade off\n```\n\n## 多会话并行\n\n多个终端各自运行 Saluzi 会话时,所有 CLI 实例共享同一个 Parade 窗口(通过 PID 锁文件确保单例)。悬浮窗显示所有已连接会话的运行/等待计数。\n\nParade 服务器以**脱离模式**运行——即使启动它的 CLI 关闭,悬浮窗仍然存活,直到用 `/parade off` 显式关闭。\n\n## 平台支持\n\n| 平台 | 状态 | 说明 |\n|------|------|------|\n| macOS | 完全支持 | 原生 Electron |\n| Linux | 支持 | 需 X11 或 Wayland |\n| Windows | 支持 | Electron |\n| WSL | 支持 | 通过 /etc/resolv.conf 解析主机 IP |\n\n## 故障排查\n\n- **悬浮窗不显示**:检查 Electron 是否安装(`/parade` 会自动安装,支持 `ELECTRON_MIRROR` 环境变量配置镜像)\n- **Electron 安装路径**:`~/.saluzi/parade/`\n- **多显示器**:悬浮窗默认在主显示器顶部居中\n"
7151
+ "content": "\n## 什么是 /parade\n\nParade 是 Saluzi 的桌面悬浮提示功能。它在桌面显示一个小型悬浮窗口(基于 Electron),实时反映:\n\n- **运行中会话数**:当前正在执行工具调用的会话\n- **等待中会话数**:等待用户输入或权限确认的会话\n- **连接状态**:WebSocket 连接状态指示灯\n\n外观示意(运行中 2 / 等待 1):\n\n::parade-preview::\n\n## 启用 Parade\n\n```\n> /parade\n```\n\n首次启用会自动检测并安装 Electron(若未安装),然后启动悬浮窗。悬浮窗默认定位在**主显示器右下角**(避开任务栏),220×130 像素,无边框始终置顶。\n\n## 关闭 Parade\n\n```\n> /parade off\n```\n\n## 多会话并行\n\n多个终端各自运行 Saluzi 会话时,所有 CLI 实例共享同一个 Parade 窗口(通过 PID 锁文件确保单例)。悬浮窗显示所有已连接会话的运行/等待计数。\n\nParade 服务器以**脱离模式**运行——即使启动它的 CLI 关闭,悬浮窗仍然存活,直到用 `/parade off` 显式关闭。\n\n## 显示模式\n\n右键悬浮窗(或在左侧立方体区域滚动滚轮)可在 4 种显示模式间切换,按信息完整度排序:\n\n| 模式 | 显示内容 | 默认窗口尺寸 |\n|------|----------|--------------|\n| Cube only | 仅左侧立方体图案 | 130×130 |\n| Cube + counts | 立方体 + 运行/等待计数 | 176×130 |\n| Standard(默认) | 立方体 + 计数 + 会话列表 | 220×130 |\n| Detail | 大窗口详情:全部会话、状态徽章(RUN/BLOCK/IDLE)、更完整的 prompt,整行可点击跳转 | 460×340 |\n\n- 当前模式在右键菜单中以实心圆点标记,切换后持久化,重启悬浮窗后仍生效\n- 窗口尺寸随模式自动调整;手动拖拽调整过的尺寸按模式记忆(下次进入同一模式时恢复)\n- 放大窗口时,立方体图案与字体大小按比例缩放\n- 菜单底部的 **Rotation: Dynamic/Static** 控制旋转时机:运行时旋转(默认)或空闲时旋转\n\n## 跳转终端\n\n会话列表每行左侧的指示点(悬停变为 ▸)可跳转到该会话所在的终端窗口;Detail 模式下点击整行即可。\n\n跨边界场景(WSL 内的 CLI 向 Windows 侧悬浮窗上报)中,WSL 进程在 Windows 侧没有对应进程 ID,Parade 会退而激活检测到的终端窗口(Windows Terminal 等)。存在多个终端窗口时聚焦第一个匹配窗口,不保证定位到具体标签页。\n\n## 平台支持\n\n| 平台 | 状态 | 说明 |\n|------|------|------|\n| macOS | 完全支持 | 原生 Electron |\n| Linux | 支持 | 需 X11 或 Wayland |\n| Windows | 支持 | Electron |\n| WSL | 支持 | 通过 /etc/resolv.conf 解析主机 IP |\n\n## 故障排查\n\n- **悬浮窗不显示**:检查 Electron 是否安装(`/parade` 会自动安装,支持 `ELECTRON_MIRROR` 环境变量配置镜像)\n- **Electron 安装路径**:`~/.saluzi/parade/`\n- **多显示器**:悬浮窗默认在主显示器顶部居中\n"
7148
7152
  },
7149
7153
  "docs/guide/keyboard-shortcuts": {
7150
7154
  "frontmatter": {
@@ -3,38 +3,224 @@
3
3
  *
4
4
  * Responsibilities:
5
5
  * 1. Render a purple 3D cube via CSS 3D transforms in the left stage area.
6
- * 2. Animate the cube with gyroscope-style rotation when runningCount > 0.
6
+ * 2. Animate the cube with a fast glow-pulse rotation when runningCount > 0.
7
7
  * 3. Update the info bar (right side) with running/waiting counts and
8
8
  * per-session prompt text from WebSocket pushes.
9
+ * 4. Manage the 4-level display mode (logo / mini / standard / detail) with
10
+ * window-size sync, persistence, and right-click menu switching.
9
11
  *
10
12
  * WebSocket protocol: receives JSON array of SessionEntry objects:
11
13
  * [{ pid, sessionId, state, prompt, lastHeartbeat }, ...]
14
+ *
15
+ * Unit contract (must match the electron main script in paradeWindow.ts):
16
+ * - design px: the base coordinate system. All CSS dimensions are
17
+ * calc(Npx * var(--ui-scale)); --ui-scale is derived from the window
18
+ * size relative to the current mode's base size.
19
+ * - IPC bounds (parade-set-bounds / parade-bounds-changed): design px.
20
+ * The main process multiplies/divides by its unitScale (dpiScale).
21
+ * - localStorage sizes: design px (portable across DPI settings).
12
22
  */
13
23
 
14
24
  // ---------------------------------------------------------------------------
15
25
  // Configuration
16
26
  // ---------------------------------------------------------------------------
17
27
 
18
- const MAX_PROMPT_LENGTH = 30
19
- const MAX_DISPLAYED_SESSIONS = 5
20
- const WS_RECONNECT_MS = 2000
28
+ var WS_RECONNECT_MS = 2000
29
+
30
+ // Per-mode config. win = natural window size (design px, applied on mode
31
+ // switch when no user-customized size is saved). base = scale reference
32
+ // (design px at --ui-scale 1) — detail uses a smaller base so fonts render
33
+ // large enough to read at its natural window size. maxPrompt/maxRows control
34
+ // the session list density.
35
+ var MODES = {
36
+ logo: { win: [130, 130], base: [130, 130], maxPrompt: 30, maxRows: 5 },
37
+ mini: { win: [176, 130], base: [176, 130], maxPrompt: 30, maxRows: 5 },
38
+ standard: { win: [220, 130], base: [220, 130], maxPrompt: 30, maxRows: 5 },
39
+ detail: { win: [460, 340], base: [300, 220], maxPrompt: 80, maxRows: 20 },
40
+ }
41
+ var MODE_ORDER = ['logo', 'mini', 'standard', 'detail']
42
+ var SCALE_MIN = 1
43
+ var SCALE_MAX = 3
44
+ // Window bounds clamps in design px — must match the parade-set-bounds
45
+ // clamps in the electron main script (which multiplies by unitScale).
46
+ var BOUNDS_MIN = [100, 80]
47
+ var BOUNDS_MAX = [900, 700]
48
+
49
+ // ---------------------------------------------------------------------------
50
+ // Zoom / port (needed before everything — applyScale divides by zoomFactor)
51
+ // ---------------------------------------------------------------------------
52
+
53
+ // Derive port from the current page's URL so the WebSocket targets the same
54
+ // server that served this page. Falls back to query string (?port=) for the
55
+ // file:// case (native Webview windows launched with a file URL), then to
56
+ // the default 17841.
57
+ var urlParams = new URLSearchParams(window.location.search)
58
+ var port = window.location.port || urlParams.get('port') || '17841'
59
+
60
+ // On native Win11, Electron is launched with --force-device-scale-factor=1
61
+ // to eliminate DPI conversion mismatches in getBounds/setBounds. This makes
62
+ // 1 CSS pixel = 1 physical pixel, so content renders at 1x scale (smaller
63
+ // on high-DPI displays). The main process passes the OS DPI factor as ?zoom=
64
+ // so we can CSS-zoom the content to match the intended physical size.
65
+ //
66
+ // --ui-scale must be computed against DESIGN px, not the zoomed CSS px:
67
+ // window.innerWidth counts physical px here (root zoom doesn't change the
68
+ // viewport CSS px count), so divide by zoomFactor to avoid double-scaling.
69
+ var zoomParam = parseFloat(urlParams.get('zoom')) || 0
70
+ if (zoomParam > 1) {
71
+ document.documentElement.style.zoom = String(zoomParam)
72
+ }
73
+ var zoomFactor = zoomParam > 1 ? zoomParam : 1
74
+
75
+ // ---------------------------------------------------------------------------
76
+ // Display mode state (applied as early as possible to avoid resize flash)
77
+ // ---------------------------------------------------------------------------
78
+
79
+ var displayMode = 'standard'
80
+ try {
81
+ let storedMode = localStorage.getItem('parade-display-mode')
82
+ if (storedMode && MODES[storedMode]) displayMode = storedMode
83
+ } catch (_) {
84
+ /* localStorage may be unavailable */
85
+ }
86
+ document.body.dataset.mode = displayMode
87
+
88
+ // Per-mode user-customized window size (design px). Saved on manual resize,
89
+ // preferred over the mode's natural size on mode entry / boot.
90
+ function clampSize(w, h) {
91
+ return [
92
+ Math.max(BOUNDS_MIN[0], Math.min(BOUNDS_MAX[0], Math.round(w))),
93
+ Math.max(BOUNDS_MIN[1], Math.min(BOUNDS_MAX[1], Math.round(h))),
94
+ ]
95
+ }
96
+
97
+ function savedSize(mode) {
98
+ try {
99
+ const raw = localStorage.getItem('parade-size-' + mode)
100
+ if (!raw) return null
101
+ const parts = JSON.parse(raw)
102
+ if (
103
+ Array.isArray(parts) &&
104
+ parts.length === 2 &&
105
+ Number.isFinite(parts[0]) &&
106
+ Number.isFinite(parts[1])
107
+ ) {
108
+ return clampSize(parts[0], parts[1])
109
+ }
110
+ } catch (_) {
111
+ /* ignore */
112
+ }
113
+ return null
114
+ }
115
+
116
+ function saveSize(mode, w, h) {
117
+ try {
118
+ localStorage.setItem('parade-size-' + mode, JSON.stringify(clampSize(w, h)))
119
+ } catch (_) {
120
+ /* ignore */
121
+ }
122
+ }
123
+
124
+ // Bounds echo suppression: after WE request bounds, the main process echoes
125
+ // parade-bounds-changed back — without the flag we'd persist our own mode
126
+ // size as a "user customization" (feedback loop).
127
+ var suppressBoundsSave = false
128
+
129
+ function requestWindowBounds(w, h) {
130
+ if (!window.parade || typeof window.parade.setBounds !== 'function') return
131
+ var s = clampSize(w, h)
132
+ suppressBoundsSave = true
133
+ window.parade.setBounds(s[0], s[1])
134
+ }
135
+
136
+ if (window.parade && typeof window.parade.onBoundsChanged === 'function') {
137
+ window.parade.onBoundsChanged(function (w, h) {
138
+ if (suppressBoundsSave) {
139
+ suppressBoundsSave = false
140
+ return
141
+ }
142
+ if (typeof w === 'number' && typeof h === 'number' && w > 0 && h > 0) {
143
+ saveSize(displayMode, w, h)
144
+ }
145
+ })
146
+ }
21
147
 
22
148
  // ---------------------------------------------------------------------------
23
149
  // DOM references
24
150
  // ---------------------------------------------------------------------------
25
151
 
26
- const cubeEl = document.getElementById('cube')
27
- const runningCountEl = document.getElementById('running-count')
28
- const waitingCountEl = document.getElementById('waiting-count')
29
- const waitingPillEl = document.getElementById('waiting-pill')
30
- const sessionsEl = document.getElementById('sessions-inner')
31
- const sessionsContainer = document.getElementById('sessions')
32
- const scrollbarTrack = document.getElementById('scrollbar-track')
33
- const scrollbarThumb = document.getElementById('scrollbar-thumb')
34
- const moreLineEl = document.getElementById('more-line')
35
- const ctxMenu = document.getElementById('ctx-menu')
36
- const ctxMode = document.getElementById('ctx-mode')
37
- const closeBtn = document.getElementById('close-btn')
152
+ var cubeEl = document.getElementById('cube')
153
+ var cubeStageEl = document.getElementById('cube-stage')
154
+ var runningCountEl = document.getElementById('running-count')
155
+ var waitingCountEl = document.getElementById('waiting-count')
156
+ var waitingPillEl = document.getElementById('waiting-pill')
157
+ var sessionsEl = document.getElementById('sessions-inner')
158
+ var sessionsContainer = document.getElementById('sessions')
159
+ var scrollbarTrack = document.getElementById('scrollbar-track')
160
+ var scrollbarThumb = document.getElementById('scrollbar-thumb')
161
+ var moreLineEl = document.getElementById('more-line')
162
+ var ctxMenu = document.getElementById('ctx-menu')
163
+ var ctxMode = document.getElementById('ctx-mode')
164
+ var closeBtn = document.getElementById('close-btn')
165
+
166
+ // ---------------------------------------------------------------------------
167
+ // Session state
168
+ // ---------------------------------------------------------------------------
169
+
170
+ /** @type {Array<{pid:number, sessionId:string, state:string, prompt:string, lastHeartbeat:number}>} */
171
+ var sessions = []
172
+ var runningCount = 0
173
+ var waitingCount = 0
174
+ var ws = null
175
+
176
+ // ---------------------------------------------------------------------------
177
+ // UI scale — grows the cube and fonts with the window
178
+ // ---------------------------------------------------------------------------
179
+
180
+ function applyScale() {
181
+ var mode = MODES[displayMode]
182
+ var w = window.innerWidth / zoomFactor
183
+ var h = window.innerHeight / zoomFactor
184
+ var s = Math.min(w / mode.base[0], h / mode.base[1])
185
+ s = Math.max(SCALE_MIN, Math.min(SCALE_MAX, s))
186
+ document.documentElement.style.setProperty('--ui-scale', s.toFixed(4))
187
+ // Window resizes change the session viewport — re-measure the scrollbar.
188
+ requestAnimationFrame(updateScrollbar)
189
+ }
190
+
191
+ // Re-measure on every window resize (restored — the WebGL→CSS refactor in
192
+ // 982be36 accidentally dropped this listener).
193
+ window.addEventListener('resize', applyScale)
194
+
195
+ // ---------------------------------------------------------------------------
196
+ // Display mode switching
197
+ // ---------------------------------------------------------------------------
198
+
199
+ function setMode(mode) {
200
+ if (!MODES[mode]) return
201
+ var changed = mode !== displayMode
202
+ displayMode = mode
203
+ try {
204
+ localStorage.setItem('parade-display-mode', mode)
205
+ } catch (_) {
206
+ /* ignore */
207
+ }
208
+ document.body.dataset.mode = mode
209
+ if (changed) {
210
+ // Replay the mode-swap fade animation (needs a reflow to restart).
211
+ document.body.classList.remove('mode-swap')
212
+ void document.body.offsetWidth
213
+ document.body.classList.add('mode-swap')
214
+ // Truncation/badges/row cap changed — force a row rebuild.
215
+ lastRowSig = null
216
+ }
217
+ updateViewMenu()
218
+ applyScale()
219
+ updateSessionRows()
220
+ // Resize the window to this mode's saved (or natural) size.
221
+ var size = savedSize(mode) || MODES[mode].win
222
+ requestWindowBounds(size[0], size[1])
223
+ }
38
224
 
39
225
  // ---------------------------------------------------------------------------
40
226
  // Custom scrollbar — invisible by default, fades in on hover/scroll.
@@ -161,29 +347,6 @@ if (scrollbarThumb && scrollbarTrack) {
161
347
  // WebSocket connection
162
348
  // ---------------------------------------------------------------------------
163
349
 
164
- // Derive port from the current page's URL so the WebSocket targets the same
165
- // server that served this page. Falls back to query string (?port=) for the
166
- // file:// case (native Webview windows launched with a file URL), then to
167
- // the default 17841.
168
- var urlParams = new URLSearchParams(window.location.search)
169
- var port = window.location.port || urlParams.get('port') || '17841'
170
-
171
- // On native Win11, Electron is launched with --force-device-scale-factor=1
172
- // to eliminate DPI conversion mismatches in getBounds/setBounds. This makes
173
- // 1 CSS pixel = 1 physical pixel, so content renders at 1x scale (smaller
174
- // on high-DPI displays). The main process passes the OS DPI factor as ?zoom=
175
- // so we can CSS-zoom the content to match the intended physical size.
176
- var zoomParam = parseFloat(urlParams.get('zoom')) || 0
177
- if (zoomParam > 1) {
178
- document.documentElement.style.zoom = String(zoomParam)
179
- }
180
-
181
- /** @type {Array<{pid:number, sessionId:string, state:string, prompt:string, lastHeartbeat:number}>} */
182
- let sessions = []
183
- let runningCount = 0
184
- let waitingCount = 0
185
- let ws = null
186
-
187
350
  function connectWebSocket() {
188
351
  try {
189
352
  ws = new WebSocket('ws://localhost:' + port + '/ws')
@@ -269,7 +432,33 @@ function updateCounts() {
269
432
  updateCubeRotation()
270
433
  }
271
434
 
435
+ // ---------------------------------------------------------------------------
436
+ // Session rows
437
+ // ---------------------------------------------------------------------------
438
+ // Rows are rebuilt ONLY when the displayed data actually changes. The CLI
439
+ // heartbeats every 1s and each POST broadcasts the full session list —
440
+ // rebuilding on every message would replace row elements between a user's
441
+ // mousedown and mouseup, swallowing clicks (the original jump-button bug).
442
+
443
+ var lastRowSig = null
444
+
445
+ function sessionSignature() {
446
+ return sessions
447
+ .map(function (s) {
448
+ return s.pid + '|' + s.state + '|' + (s.prompt || '')
449
+ })
450
+ .join('¦')
451
+ }
452
+
272
453
  function updateSessionRows() {
454
+ var mode = MODES[displayMode]
455
+ var sig = sessionSignature()
456
+ if (sig === lastRowSig) {
457
+ requestAnimationFrame(updateScrollbar)
458
+ return
459
+ }
460
+ lastRowSig = sig
461
+
273
462
  var runningSessions = sessions.filter(function (s) {
274
463
  return s.state === 'running'
275
464
  })
@@ -291,21 +480,23 @@ function updateSessionRows() {
291
480
  return
292
481
  }
293
482
 
483
+ var cap = mode.maxRows
294
484
  // Running sessions first (primary focus, purple dot)
295
- var displayedRunning = runningSessions.slice(0, MAX_DISPLAYED_SESSIONS)
485
+ var displayedRunning = runningSessions.slice(0, cap)
296
486
  displayedRunning.forEach(function (s) {
297
487
  sessionsEl.appendChild(buildSessionRow(s, 'running'))
298
488
  })
299
489
 
300
- // Blocked sessions next (amber dot, needs user action)
301
- var displayedBlocked = blockedSessions.slice(0, 2)
490
+ // Blocked sessions next (amber dot, needs user action). Standard modes
491
+ // keep at most 2 visible so running rows dominate; detail shows all.
492
+ var blockedCap = displayMode === 'detail' ? cap : 2
493
+ var displayedBlocked = blockedSessions.slice(0, blockedCap)
302
494
  displayedBlocked.forEach(function (s) {
303
495
  sessionsEl.appendChild(buildSessionRow(s, 'blocked'))
304
496
  })
305
497
 
306
498
  // Idle sessions last (dim, just "connected")
307
- var slotsLeft =
308
- MAX_DISPLAYED_SESSIONS - displayedRunning.length - displayedBlocked.length
499
+ var slotsLeft = cap - displayedRunning.length - displayedBlocked.length
309
500
  var displayedIdle = idleSessions.slice(0, Math.max(0, slotsLeft))
310
501
  displayedIdle.forEach(function (s) {
311
502
  sessionsEl.appendChild(buildSessionRow(s, 'idle'))
@@ -325,11 +516,13 @@ function updateSessionRows() {
325
516
  }
326
517
 
327
518
  function buildSessionRow(s, kind) {
519
+ var mode = MODES[displayMode]
328
520
  var row = document.createElement('div')
329
521
  row.className = 'session-row ' + kind
522
+ row.setAttribute('data-pid', String(s.pid))
330
523
  var promptText = s.prompt || ''
331
- if (promptText.length > MAX_PROMPT_LENGTH) {
332
- promptText = promptText.substring(0, MAX_PROMPT_LENGTH) + '...'
524
+ if (promptText.length > mode.maxPrompt) {
525
+ promptText = promptText.substring(0, mode.maxPrompt) + '...'
333
526
  }
334
527
  var text = document.createElement('span')
335
528
  text.className = 'session-text'
@@ -340,13 +533,53 @@ function buildSessionRow(s, kind) {
340
533
  } else {
341
534
  text.textContent = promptText || '(running)'
342
535
  }
343
- // Jump-to-terminal dot — placed at left indicator, morphs into ▸ on hover
536
+ // State badge — visible in detail mode only (CSS)
537
+ var badge = document.createElement('span')
538
+ badge.className =
539
+ 'session-badge ' +
540
+ (kind === 'running' ? 'run' : kind === 'blocked' ? 'block' : 'idle')
541
+ badge.textContent =
542
+ kind === 'running' ? 'RUN' : kind === 'blocked' ? 'BLOCK' : 'IDLE'
543
+ // Jump-to-terminal dot — placed at left indicator, morphs into ▸ on hover.
544
+ // Click handling is delegated (see mousedown/mouseup pairing below) so a
545
+ // rebuild between press and release can't swallow the jump.
344
546
  var jump = document.createElement('span')
345
547
  jump.className = 'session-jump'
346
- jump.addEventListener('click', function (e) {
347
- e.stopPropagation()
348
- if (ws && ws.readyState === WebSocket.OPEN) {
349
- ws.send(JSON.stringify({ type: 'focus', pid: s.pid }))
548
+ row.appendChild(text)
549
+ row.appendChild(badge)
550
+ row.insertBefore(jump, row.firstChild)
551
+
552
+ return row
553
+ }
554
+
555
+ // Jump dispatch: mousedown records the pressed row; mouseup on the SAME row
556
+ // fires the focus request. Immune to element replacement between events.
557
+ // In detail mode the whole row is the hit target; other modes require the
558
+ // jump dot.
559
+ var pressedPid = null
560
+
561
+ sessionsEl.addEventListener('mousedown', function (e) {
562
+ if (e.button !== 0) return
563
+ var row = e.target.closest('.session-row')
564
+ pressedPid = row ? row.getAttribute('data-pid') : null
565
+ })
566
+
567
+ sessionsEl.addEventListener('mouseup', function (e) {
568
+ if (e.button !== 0 || pressedPid === null) return
569
+ var pressed = pressedPid
570
+ pressedPid = null
571
+ var row = e.target.closest('.session-row')
572
+ var pid = row ? row.getAttribute('data-pid') : null
573
+ var isJumpClick = !!e.target.closest('.session-jump')
574
+ if (!row || pid === null || pid !== pressed) return
575
+ if (displayMode !== 'detail' && !isJumpClick) return
576
+ sendFocus(Number(pid), row)
577
+ })
578
+
579
+ function sendFocus(pid, row) {
580
+ if (ws && ws.readyState === WebSocket.OPEN) {
581
+ ws.send(JSON.stringify({ type: 'focus', pid: pid }))
582
+ if (row) {
350
583
  // Pulse the row to confirm jump was sent
351
584
  row.classList.remove('jumping')
352
585
  void row.offsetWidth // force reflow to restart animation
@@ -354,25 +587,21 @@ function buildSessionRow(s, kind) {
354
587
  setTimeout(function () {
355
588
  row.classList.remove('jumping')
356
589
  }, 1300)
357
- if (window.parade && typeof window.parade.debugLog === 'function') {
358
- window.parade.debugLog('[jump] click pid=' + s.pid + ' ws=OPEN sent')
359
- }
360
- } else {
361
- if (window.parade && typeof window.parade.debugLog === 'function') {
362
- window.parade.debugLog(
363
- '[jump] click pid=' +
364
- s.pid +
365
- ' ws=' +
366
- (ws ? ws.readyState : 'null') +
367
- ' NOT sent',
368
- )
369
- }
370
590
  }
371
- })
372
- row.appendChild(text)
373
- row.insertBefore(jump, row.firstChild)
374
-
375
- return row
591
+ if (window.parade && typeof window.parade.debugLog === 'function') {
592
+ window.parade.debugLog('[jump] click pid=' + pid + ' ws=OPEN sent')
593
+ }
594
+ } else {
595
+ if (window.parade && typeof window.parade.debugLog === 'function') {
596
+ window.parade.debugLog(
597
+ '[jump] click pid=' +
598
+ pid +
599
+ ' ws=' +
600
+ (ws ? ws.readyState : 'null') +
601
+ ' NOT sent',
602
+ )
603
+ }
604
+ }
376
605
  }
377
606
 
378
607
  // ---------------------------------------------------------------------------
@@ -381,12 +610,46 @@ function buildSessionRow(s, kind) {
381
610
 
382
611
  document.addEventListener('contextmenu', function (e) {
383
612
  e.preventDefault()
613
+ // Electron bridge: the menu opens as its own cursor-anchored popup window
614
+ // (see paradeWindow.ts 'parade-open-menu'), so it can extend beyond this
615
+ // window's bounds — the in-page fallback below is clipped by them, which
616
+ // truncated the menu on the small logo/mini modes.
617
+ if (window.parade && typeof window.parade.openMenu === 'function') {
618
+ window.parade.openMenu({ mode: displayMode, rot: cubeMode })
619
+ return
620
+ }
621
+ // Fallback (bridge missing — e.g. debugging the UI in a regular browser
622
+ // tab): show the in-page menu with flip/clamp positioning, accepting that
623
+ // it cannot leave the window.
384
624
  updateModeLabel()
625
+ updateViewMenu()
385
626
  ctxMenu.style.display = 'block'
386
- ctxMenu.style.left = e.clientX + 'px'
387
- ctxMenu.style.top = e.clientY + 'px'
627
+ // Flip/clamp positioning: the menu is taller than the logo/mini windows.
628
+ var mw = ctxMenu.offsetWidth
629
+ var mh = ctxMenu.offsetHeight
630
+ var x = Math.min(e.clientX, window.innerWidth - mw - 4)
631
+ var y = e.clientY
632
+ if (y + mh > window.innerHeight - 4) {
633
+ y = Math.max(4, e.clientY - mh)
634
+ }
635
+ ctxMenu.style.left = Math.max(4, x) + 'px'
636
+ ctxMenu.style.top = y + 'px'
388
637
  })
389
638
 
639
+ // Actions arriving from the popup menu window (relayed via the main process).
640
+ // The parade window stays the source of truth — the popup only reports what
641
+ // was clicked, this side applies and persists it.
642
+ if (window.parade && typeof window.parade.onMenuAction === 'function') {
643
+ window.parade.onMenuAction(function (action) {
644
+ if (!action || typeof action !== 'object') return
645
+ if (action.type === 'view' && MODES[action.value]) {
646
+ setMode(action.value)
647
+ } else if (action.type === 'rotation') {
648
+ toggleRotationMode()
649
+ }
650
+ })
651
+ }
652
+
390
653
  // Frameless window drag + corner resize via IPC (Electron). Left-click on
391
654
  // the body starts a move-drag; left-click on a .corner-handle starts a
392
655
  // proportional resize. The main process polls the cursor and moves/resizes
@@ -425,6 +688,25 @@ document.addEventListener('click', function () {
425
688
  ctxMenu.style.display = 'none'
426
689
  })
427
690
 
691
+ // View-mode radio items
692
+ var viewItems = ctxMenu.querySelectorAll('.ctx-view')
693
+ for (let vi = 0; vi < viewItems.length; vi++) {
694
+ viewItems[vi].addEventListener('click', function (e) {
695
+ e.stopPropagation()
696
+ ctxMenu.style.display = 'none'
697
+ setMode(this.getAttribute('data-view'))
698
+ })
699
+ }
700
+
701
+ function updateViewMenu() {
702
+ for (let i = 0; i < viewItems.length; i++) {
703
+ viewItems[i].classList.toggle(
704
+ 'active',
705
+ viewItems[i].getAttribute('data-view') === displayMode,
706
+ )
707
+ }
708
+ }
709
+
428
710
  // ---------------------------------------------------------------------------
429
711
  // Close button (top-right corner)
430
712
  // ---------------------------------------------------------------------------
@@ -451,7 +733,7 @@ closeBtn.addEventListener('click', function () {
451
733
  // ---------------------------------------------------------------------------
452
734
  // Dynamic (default): cube rotates when sessions are running, static when idle.
453
735
  // Static (inverse): cube static when running, rotates when idle.
454
- // The context menu shows the *switchable* label (opposite of current mode).
736
+ // The context menu shows the CURRENT mode.
455
737
 
456
738
  var cubeMode = 'dynamic'
457
739
  try {
@@ -461,11 +743,12 @@ try {
461
743
  }
462
744
 
463
745
  function updateModeLabel() {
464
- ctxMode.textContent = cubeMode === 'dynamic' ? 'Static' : 'Dynamic'
746
+ ctxMode.textContent =
747
+ 'Rotation: ' + (cubeMode === 'dynamic' ? 'Dynamic' : 'Static')
465
748
  }
466
749
 
467
- ctxMode.addEventListener('click', function () {
468
- ctxMenu.style.display = 'none'
750
+ // Shared by the in-page menu (fallback) and the popup menu action relay.
751
+ function toggleRotationMode() {
469
752
  cubeMode = cubeMode === 'dynamic' ? 'static' : 'dynamic'
470
753
  try {
471
754
  localStorage.setItem('parade-cube-mode', cubeMode)
@@ -474,16 +757,54 @@ ctxMode.addEventListener('click', function () {
474
757
  }
475
758
  updateModeLabel()
476
759
  updateCubeRotation()
760
+ }
761
+
762
+ ctxMode.addEventListener('click', function (e) {
763
+ e.stopPropagation()
764
+ ctxMenu.style.display = 'none'
765
+ toggleRotationMode()
477
766
  })
478
767
 
479
768
  updateModeLabel()
480
769
 
770
+ // ---------------------------------------------------------------------------
771
+ // Wheel-to-cycle display modes (on the cube stage)
772
+ // ---------------------------------------------------------------------------
773
+ // Accumulates wheel deltas with a threshold + cooldown so a single notch
774
+ // (or trackpad flood — the logo mode stage spans the whole window) flips
775
+ // exactly one mode.
776
+
777
+ var wheelAccum = 0
778
+ var wheelLockUntil = 0
779
+
780
+ cubeStageEl.addEventListener(
781
+ 'wheel',
782
+ function (e) {
783
+ if (!e.deltaY) return
784
+ e.preventDefault()
785
+ var now = Date.now()
786
+ if (now < wheelLockUntil) return
787
+ wheelAccum += e.deltaY
788
+ if (Math.abs(wheelAccum) >= 120) {
789
+ const dir = wheelAccum > 0 ? 1 : -1
790
+ wheelAccum = 0
791
+ wheelLockUntil = now + 500
792
+ const idx = MODE_ORDER.indexOf(displayMode)
793
+ const next = Math.min(MODE_ORDER.length - 1, Math.max(0, idx + dir))
794
+ if (next !== idx) setMode(MODE_ORDER[next])
795
+ }
796
+ },
797
+ { passive: false },
798
+ )
799
+
481
800
  // ---------------------------------------------------------------------------
482
801
  // CSS 3D Cube rotation control
483
802
  // ---------------------------------------------------------------------------
484
803
  // The cube is a CSS 3D transform element (see index.html <style>). Rotation
485
804
  // is driven by applying/removing the .rotating class, which toggles the
486
- // cube-spin @keyframes animation. Logic mirrors the previous WebGL loop:
805
+ // cube-spin @keyframes animation plus the stage-level glow/halo pulses
806
+ // (never on .cube itself — filter/opacity would flatten preserve-3d).
807
+ // Logic mirrors the previous WebGL loop:
487
808
  // Dynamic: rotate when runningCount > 0
488
809
  // Static: rotate when runningCount === 0 (inverse)
489
810
 
@@ -491,16 +812,17 @@ function updateCubeRotation() {
491
812
  if (!cubeEl) return
492
813
  var shouldRotate =
493
814
  cubeMode === 'dynamic' ? runningCount > 0 : runningCount === 0
494
- if (shouldRotate) {
495
- cubeEl.classList.add('rotating')
496
- } else {
497
- cubeEl.classList.remove('rotating')
498
- }
815
+ cubeEl.classList.toggle('rotating', shouldRotate)
816
+ if (cubeStageEl) cubeStageEl.classList.toggle('rotating', shouldRotate)
499
817
  }
500
818
 
501
819
  // Apply initial state — re-evaluated on each WS message via updateCounts
502
820
  updateCubeRotation()
503
821
 
822
+ // Apply the persisted mode/scale/window bounds as early as possible so a
823
+ // detail-mode boot doesn't flash at the default 220x130 size.
824
+ setMode(displayMode)
825
+
504
826
  // Start the WebSocket connection
505
827
  setWsStatus('connecting')
506
828
  connectWebSocket()