@memberjunction/ng-core-entity-forms 6.1.4 → 6.2.0-edge.1

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 (327) hide show
  1. package/README.md +22 -0
  2. package/dist/lib/custom/AIAgentCategories/ai-agent-category-overview.panel.d.ts +1 -1
  3. package/dist/lib/custom/AIAgentCategories/ai-agent-category-overview.panel.js +2 -2
  4. package/dist/lib/custom/AIAgentCategories/ai-agent-category-overview.panel.js.map +1 -1
  5. package/dist/lib/custom/AIAgentSessions/ai-agent-session-form.component.js.map +1 -1
  6. package/dist/lib/custom/AIAgents/add-action-dialog.component.d.ts +90 -16
  7. package/dist/lib/custom/AIAgents/add-action-dialog.component.d.ts.map +1 -1
  8. package/dist/lib/custom/AIAgents/add-action-dialog.component.js +218 -70
  9. package/dist/lib/custom/AIAgents/add-action-dialog.component.js.map +1 -1
  10. package/dist/lib/custom/AIAgents/agent-prompt-advanced-settings-dialog.component.d.ts +80 -16
  11. package/dist/lib/custom/AIAgents/agent-prompt-advanced-settings-dialog.component.d.ts.map +1 -1
  12. package/dist/lib/custom/AIAgents/agent-prompt-advanced-settings-dialog.component.js +146 -46
  13. package/dist/lib/custom/AIAgents/agent-prompt-advanced-settings-dialog.component.js.map +1 -1
  14. package/dist/lib/custom/AIAgents/ai-agent-form.component.d.ts +329 -27
  15. package/dist/lib/custom/AIAgents/ai-agent-form.component.d.ts.map +1 -1
  16. package/dist/lib/custom/AIAgents/ai-agent-form.component.js +794 -262
  17. package/dist/lib/custom/AIAgents/ai-agent-form.component.js.map +1 -1
  18. package/dist/lib/custom/AIAgents/ai-agent-management.service.d.ts +76 -0
  19. package/dist/lib/custom/AIAgents/ai-agent-management.service.d.ts.map +1 -1
  20. package/dist/lib/custom/AIAgents/ai-agent-management.service.js +77 -33
  21. package/dist/lib/custom/AIAgents/ai-agent-management.service.js.map +1 -1
  22. package/dist/lib/custom/AIAgents/create-prompt-dialog.component.d.ts +69 -15
  23. package/dist/lib/custom/AIAgents/create-prompt-dialog.component.d.ts.map +1 -1
  24. package/dist/lib/custom/AIAgents/create-prompt-dialog.component.js +182 -74
  25. package/dist/lib/custom/AIAgents/create-prompt-dialog.component.js.map +1 -1
  26. package/dist/lib/custom/AIAgents/create-sub-agent-dialog.component.d.ts +105 -25
  27. package/dist/lib/custom/AIAgents/create-sub-agent-dialog.component.d.ts.map +1 -1
  28. package/dist/lib/custom/AIAgents/create-sub-agent-dialog.component.js +272 -112
  29. package/dist/lib/custom/AIAgents/create-sub-agent-dialog.component.js.map +1 -1
  30. package/dist/lib/custom/AIAgents/new-agent-dialog.component.d.ts +32 -6
  31. package/dist/lib/custom/AIAgents/new-agent-dialog.component.d.ts.map +1 -1
  32. package/dist/lib/custom/AIAgents/new-agent-dialog.component.js +71 -19
  33. package/dist/lib/custom/AIAgents/new-agent-dialog.component.js.map +1 -1
  34. package/dist/lib/custom/AIAgents/new-agent-dialog.service.d.ts +10 -0
  35. package/dist/lib/custom/AIAgents/new-agent-dialog.service.d.ts.map +1 -1
  36. package/dist/lib/custom/AIAgents/new-agent-dialog.service.js +27 -7
  37. package/dist/lib/custom/AIAgents/new-agent-dialog.service.js.map +1 -1
  38. package/dist/lib/custom/AIAgents/prompt-selector-dialog.component.d.ts +65 -15
  39. package/dist/lib/custom/AIAgents/prompt-selector-dialog.component.d.ts.map +1 -1
  40. package/dist/lib/custom/AIAgents/prompt-selector-dialog.component.js +152 -52
  41. package/dist/lib/custom/AIAgents/prompt-selector-dialog.component.js.map +1 -1
  42. package/dist/lib/custom/AIAgents/sub-agent-advanced-settings-dialog.component.d.ts +81 -15
  43. package/dist/lib/custom/AIAgents/sub-agent-advanced-settings-dialog.component.d.ts.map +1 -1
  44. package/dist/lib/custom/AIAgents/sub-agent-advanced-settings-dialog.component.js +145 -45
  45. package/dist/lib/custom/AIAgents/sub-agent-advanced-settings-dialog.component.js.map +1 -1
  46. package/dist/lib/custom/AIAgents/sub-agent-selector-dialog.component.d.ts +71 -17
  47. package/dist/lib/custom/AIAgents/sub-agent-selector-dialog.component.d.ts.map +1 -1
  48. package/dist/lib/custom/AIAgents/sub-agent-selector-dialog.component.js +175 -67
  49. package/dist/lib/custom/AIAgents/sub-agent-selector-dialog.component.js.map +1 -1
  50. package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.d.ts +173 -27
  51. package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.d.ts.map +1 -1
  52. package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.js +385 -93
  53. package/dist/lib/custom/AIPromptRuns/ai-prompt-run-form.component.js.map +1 -1
  54. package/dist/lib/custom/AIPromptRuns/chat-message-viewer.component.d.ts +64 -4
  55. package/dist/lib/custom/AIPromptRuns/chat-message-viewer.component.d.ts.map +1 -1
  56. package/dist/lib/custom/AIPromptRuns/chat-message-viewer.component.js +129 -29
  57. package/dist/lib/custom/AIPromptRuns/chat-message-viewer.component.js.map +1 -1
  58. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts +260 -27
  59. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.d.ts.map +1 -1
  60. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js +686 -238
  61. package/dist/lib/custom/AIPrompts/ai-prompt-form.component.js.map +1 -1
  62. package/dist/lib/custom/AIPrompts/ai-prompt-management.service.d.ts +10 -0
  63. package/dist/lib/custom/AIPrompts/ai-prompt-management.service.d.ts.map +1 -1
  64. package/dist/lib/custom/AIPrompts/ai-prompt-management.service.js +17 -9
  65. package/dist/lib/custom/AIPrompts/ai-prompt-management.service.js.map +1 -1
  66. package/dist/lib/custom/AIPrompts/template-selector-dialog.component.d.ts +74 -16
  67. package/dist/lib/custom/AIPrompts/template-selector-dialog.component.d.ts.map +1 -1
  68. package/dist/lib/custom/AIPrompts/template-selector-dialog.component.js +174 -58
  69. package/dist/lib/custom/AIPrompts/template-selector-dialog.component.js.map +1 -1
  70. package/dist/lib/custom/Actions/action-execution-log-form.component.d.ts +108 -14
  71. package/dist/lib/custom/Actions/action-execution-log-form.component.d.ts.map +1 -1
  72. package/dist/lib/custom/Actions/action-execution-log-form.component.js +258 -70
  73. package/dist/lib/custom/Actions/action-execution-log-form.component.js.map +1 -1
  74. package/dist/lib/custom/Actions/action-form.component.d.ts +198 -14
  75. package/dist/lib/custom/Actions/action-form.component.d.ts.map +1 -1
  76. package/dist/lib/custom/Actions/action-form.component.js +387 -107
  77. package/dist/lib/custom/Actions/action-form.component.js.map +1 -1
  78. package/dist/lib/custom/BridgeProviders/bridge-provider-features.d.ts +12 -6
  79. package/dist/lib/custom/BridgeProviders/bridge-provider-features.d.ts.map +1 -1
  80. package/dist/lib/custom/BridgeProviders/bridge-provider-features.js +41 -29
  81. package/dist/lib/custom/BridgeProviders/bridge-provider-features.js.map +1 -1
  82. package/dist/lib/custom/BridgeProviders/bridge-provider-form.component.js +14 -14
  83. package/dist/lib/custom/BridgeProviders/bridge-provider-form.component.js.map +1 -1
  84. package/dist/lib/custom/Companies/company-overview.panel.d.ts +1 -1
  85. package/dist/lib/custom/Companies/company-overview.panel.js +2 -2
  86. package/dist/lib/custom/Companies/company-overview.panel.js.map +1 -1
  87. package/dist/lib/custom/Conversations/conversation-overview.panel.d.ts +1 -1
  88. package/dist/lib/custom/Conversations/conversation-overview.panel.js +2 -2
  89. package/dist/lib/custom/Conversations/conversation-overview.panel.js.map +1 -1
  90. package/dist/lib/custom/Employees/employee-overview.panel.d.ts +1 -1
  91. package/dist/lib/custom/Employees/employee-overview.panel.js +2 -2
  92. package/dist/lib/custom/Employees/employee-overview.panel.js.map +1 -1
  93. package/dist/lib/custom/Entities/entity-form.component.d.ts +209 -28
  94. package/dist/lib/custom/Entities/entity-form.component.d.ts.map +1 -1
  95. package/dist/lib/custom/Entities/entity-form.component.js +494 -138
  96. package/dist/lib/custom/Entities/entity-form.component.js.map +1 -1
  97. package/dist/lib/custom/EntityActions/entityaction.form.component.d.ts +5 -1
  98. package/dist/lib/custom/EntityActions/entityaction.form.component.d.ts.map +1 -1
  99. package/dist/lib/custom/EntityActions/entityaction.form.component.js +10 -2
  100. package/dist/lib/custom/EntityActions/entityaction.form.component.js.map +1 -1
  101. package/dist/lib/custom/Files/file-form.component.d.ts +1 -1
  102. package/dist/lib/custom/Files/file-form.component.js +2 -2
  103. package/dist/lib/custom/Files/file-form.component.js.map +1 -1
  104. package/dist/lib/custom/Lists/list-form.component.d.ts +300 -36
  105. package/dist/lib/custom/Lists/list-form.component.d.ts.map +1 -1
  106. package/dist/lib/custom/Lists/list-form.component.js +664 -208
  107. package/dist/lib/custom/Lists/list-form.component.js.map +1 -1
  108. package/dist/lib/custom/MLModels/ml-model-form.component.d.ts +27 -0
  109. package/dist/lib/custom/MLModels/ml-model-form.component.d.ts.map +1 -0
  110. package/dist/lib/custom/MLModels/ml-model-form.component.js +193 -0
  111. package/dist/lib/custom/MLModels/ml-model-form.component.js.map +1 -0
  112. package/dist/lib/custom/MLModels/ml-model-view-models.d.ts +75 -0
  113. package/dist/lib/custom/MLModels/ml-model-view-models.d.ts.map +1 -0
  114. package/dist/lib/custom/MLModels/ml-model-view-models.js +244 -0
  115. package/dist/lib/custom/MLModels/ml-model-view-models.js.map +1 -0
  116. package/dist/lib/custom/MLModels/ps-model-detail.component.d.ts +173 -0
  117. package/dist/lib/custom/MLModels/ps-model-detail.component.d.ts.map +1 -0
  118. package/dist/lib/custom/MLModels/ps-model-detail.component.js +883 -0
  119. package/dist/lib/custom/MLModels/ps-model-detail.component.js.map +1 -0
  120. package/dist/lib/custom/Queries/query-form.component.d.ts +201 -26
  121. package/dist/lib/custom/Queries/query-form.component.d.ts.map +1 -1
  122. package/dist/lib/custom/Queries/query-form.component.js +429 -109
  123. package/dist/lib/custom/Queries/query-form.component.js.map +1 -1
  124. package/dist/lib/custom/Queries/query-run-dialog.component.d.ts +107 -14
  125. package/dist/lib/custom/Queries/query-run-dialog.component.d.ts.map +1 -1
  126. package/dist/lib/custom/Queries/query-run-dialog.component.js +249 -79
  127. package/dist/lib/custom/Queries/query-run-dialog.component.js.map +1 -1
  128. package/dist/lib/custom/RecordProcesses/record-process-form.component.d.ts +133 -7
  129. package/dist/lib/custom/RecordProcesses/record-process-form.component.d.ts.map +1 -1
  130. package/dist/lib/custom/RecordProcesses/record-process-form.component.js +1268 -19
  131. package/dist/lib/custom/RecordProcesses/record-process-form.component.js.map +1 -1
  132. package/dist/lib/custom/SearchScopes/searchscope-form.component.d.ts +1 -1
  133. package/dist/lib/custom/SearchScopes/searchscope-form.component.js +4 -4
  134. package/dist/lib/custom/SearchScopes/searchscope-form.component.js.map +1 -1
  135. package/dist/lib/custom/SessionBridges/session-bridge-form.component.js.map +1 -1
  136. package/dist/lib/custom/Templates/template-param-dialog.component.d.ts +77 -13
  137. package/dist/lib/custom/Templates/template-param-dialog.component.d.ts.map +1 -1
  138. package/dist/lib/custom/Templates/template-param-dialog.component.js +196 -78
  139. package/dist/lib/custom/Templates/template-param-dialog.component.js.map +1 -1
  140. package/dist/lib/custom/Templates/template-params-grid.component.d.ts +49 -7
  141. package/dist/lib/custom/Templates/template-params-grid.component.d.ts.map +1 -1
  142. package/dist/lib/custom/Templates/template-params-grid.component.js +132 -44
  143. package/dist/lib/custom/Templates/template-params-grid.component.js.map +1 -1
  144. package/dist/lib/custom/Templates/templates-form.component.d.ts +142 -17
  145. package/dist/lib/custom/Templates/templates-form.component.d.ts.map +1 -1
  146. package/dist/lib/custom/Templates/templates-form.component.js +333 -113
  147. package/dist/lib/custom/Templates/templates-form.component.js.map +1 -1
  148. package/dist/lib/custom/Tests/entity-link-pill.component.d.ts +24 -4
  149. package/dist/lib/custom/Tests/entity-link-pill.component.d.ts.map +1 -1
  150. package/dist/lib/custom/Tests/entity-link-pill.component.js +64 -20
  151. package/dist/lib/custom/Tests/entity-link-pill.component.js.map +1 -1
  152. package/dist/lib/custom/Tests/test-form.component.d.ts +217 -27
  153. package/dist/lib/custom/Tests/test-form.component.d.ts.map +1 -1
  154. package/dist/lib/custom/Tests/test-form.component.js +549 -169
  155. package/dist/lib/custom/Tests/test-form.component.js.map +1 -1
  156. package/dist/lib/custom/Tests/test-run-checks.d.ts +2 -0
  157. package/dist/lib/custom/Tests/test-run-checks.d.ts.map +1 -1
  158. package/dist/lib/custom/Tests/test-run-checks.js +5 -1
  159. package/dist/lib/custom/Tests/test-run-checks.js.map +1 -1
  160. package/dist/lib/custom/Tests/test-run-feedback-form.component.d.ts +2 -0
  161. package/dist/lib/custom/Tests/test-run-feedback-form.component.d.ts.map +1 -1
  162. package/dist/lib/custom/Tests/test-run-feedback-form.component.js +5 -1
  163. package/dist/lib/custom/Tests/test-run-feedback-form.component.js.map +1 -1
  164. package/dist/lib/custom/Tests/test-run-form.component.d.ts +146 -18
  165. package/dist/lib/custom/Tests/test-run-form.component.d.ts.map +1 -1
  166. package/dist/lib/custom/Tests/test-run-form.component.js +362 -106
  167. package/dist/lib/custom/Tests/test-run-form.component.js.map +1 -1
  168. package/dist/lib/custom/Tests/test-suite-form.component.d.ts +390 -47
  169. package/dist/lib/custom/Tests/test-suite-form.component.d.ts.map +1 -1
  170. package/dist/lib/custom/Tests/test-suite-form.component.js +1015 -343
  171. package/dist/lib/custom/Tests/test-suite-form.component.js.map +1 -1
  172. package/dist/lib/custom/Tests/test-suite-run-form.component.d.ts +214 -26
  173. package/dist/lib/custom/Tests/test-suite-run-form.component.d.ts.map +1 -1
  174. package/dist/lib/custom/Tests/test-suite-run-form.component.js +535 -159
  175. package/dist/lib/custom/Tests/test-suite-run-form.component.js.map +1 -1
  176. package/dist/lib/custom/Users/user-overview.panel.d.ts +1 -1
  177. package/dist/lib/custom/Users/user-overview.panel.js +2 -2
  178. package/dist/lib/custom/Users/user-overview.panel.js.map +1 -1
  179. package/dist/lib/custom/ai-agent-run/ai-agent-run-analytics.component.d.ts +208 -29
  180. package/dist/lib/custom/ai-agent-run/ai-agent-run-analytics.component.d.ts.map +1 -1
  181. package/dist/lib/custom/ai-agent-run/ai-agent-run-analytics.component.js +446 -138
  182. package/dist/lib/custom/ai-agent-run/ai-agent-run-analytics.component.js.map +1 -1
  183. package/dist/lib/custom/ai-agent-run/ai-agent-run-cost.service.d.ts +6 -0
  184. package/dist/lib/custom/ai-agent-run/ai-agent-run-cost.service.d.ts.map +1 -1
  185. package/dist/lib/custom/ai-agent-run/ai-agent-run-cost.service.js +16 -4
  186. package/dist/lib/custom/ai-agent-run/ai-agent-run-cost.service.js.map +1 -1
  187. package/dist/lib/custom/ai-agent-run/ai-agent-run-data.service.d.ts +50 -6
  188. package/dist/lib/custom/ai-agent-run/ai-agent-run-data.service.d.ts.map +1 -1
  189. package/dist/lib/custom/ai-agent-run/ai-agent-run-data.service.js +84 -12
  190. package/dist/lib/custom/ai-agent-run/ai-agent-run-data.service.js.map +1 -1
  191. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-detail.component.d.ts +89 -5
  192. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-detail.component.d.ts.map +1 -1
  193. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-detail.component.js +216 -70
  194. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-detail.component.js.map +1 -1
  195. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-node.component.d.ts +62 -3
  196. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-node.component.d.ts.map +1 -1
  197. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-node.component.js +140 -42
  198. package/dist/lib/custom/ai-agent-run/ai-agent-run-step-node.component.js.map +1 -1
  199. package/dist/lib/custom/ai-agent-run/ai-agent-run-timeline.component.d.ts +81 -10
  200. package/dist/lib/custom/ai-agent-run/ai-agent-run-timeline.component.d.ts.map +1 -1
  201. package/dist/lib/custom/ai-agent-run/ai-agent-run-timeline.component.js +165 -37
  202. package/dist/lib/custom/ai-agent-run/ai-agent-run-timeline.component.js.map +1 -1
  203. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts +121 -14
  204. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.d.ts.map +1 -1
  205. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js +287 -79
  206. package/dist/lib/custom/ai-agent-run/ai-agent-run.component.js.map +1 -1
  207. package/dist/lib/custom/ai-agent-run/flow/agent-run-flow.component.d.ts +101 -15
  208. package/dist/lib/custom/ai-agent-run/flow/agent-run-flow.component.d.ts.map +1 -1
  209. package/dist/lib/custom/ai-agent-run/flow/agent-run-flow.component.js +281 -97
  210. package/dist/lib/custom/ai-agent-run/flow/agent-run-flow.component.js.map +1 -1
  211. package/dist/lib/custom/ai-agent-run/flow/agent-run-flow.model.d.ts +43 -25
  212. package/dist/lib/custom/ai-agent-run/flow/agent-run-flow.model.d.ts.map +1 -1
  213. package/dist/lib/custom/ai-agent-run/flow/agent-run-flow.model.js +100 -64
  214. package/dist/lib/custom/ai-agent-run/flow/agent-run-flow.model.js.map +1 -1
  215. package/dist/lib/custom/ai-agent-run/flow/constellation.component.d.ts +14 -2
  216. package/dist/lib/custom/ai-agent-run/flow/constellation.component.d.ts.map +1 -1
  217. package/dist/lib/custom/ai-agent-run/flow/constellation.component.js +71 -53
  218. package/dist/lib/custom/ai-agent-run/flow/constellation.component.js.map +1 -1
  219. package/dist/lib/custom/ai-agent-run/flow/flame-cascade.component.d.ts +14 -2
  220. package/dist/lib/custom/ai-agent-run/flow/flame-cascade.component.d.ts.map +1 -1
  221. package/dist/lib/custom/ai-agent-run/flow/flame-cascade.component.js +70 -52
  222. package/dist/lib/custom/ai-agent-run/flow/flame-cascade.component.js.map +1 -1
  223. package/dist/lib/custom/ai-agent-run/flow/flow-svg.util.d.ts +8 -0
  224. package/dist/lib/custom/ai-agent-run/flow/flow-svg.util.d.ts.map +1 -1
  225. package/dist/lib/custom/ai-agent-run/flow/flow-svg.util.js +22 -6
  226. package/dist/lib/custom/ai-agent-run/flow/flow-svg.util.js.map +1 -1
  227. package/dist/lib/custom/ai-agent-run/flow/flowchart.component.d.ts +24 -2
  228. package/dist/lib/custom/ai-agent-run/flow/flowchart.component.d.ts.map +1 -1
  229. package/dist/lib/custom/ai-agent-run/flow/flowchart.component.js +91 -59
  230. package/dist/lib/custom/ai-agent-run/flow/flowchart.component.js.map +1 -1
  231. package/dist/lib/custom/ai-agent-run/flow/pan-zoom.d.ts +10 -2
  232. package/dist/lib/custom/ai-agent-run/flow/pan-zoom.d.ts.map +1 -1
  233. package/dist/lib/custom/ai-agent-run/flow/pan-zoom.js +33 -17
  234. package/dist/lib/custom/ai-agent-run/flow/pan-zoom.js.map +1 -1
  235. package/dist/lib/custom/ai-agent-run/flow/run-tree-flow-projection.d.ts +2 -0
  236. package/dist/lib/custom/ai-agent-run/flow/run-tree-flow-projection.d.ts.map +1 -1
  237. package/dist/lib/custom/ai-agent-run/flow/run-tree-flow-projection.js +34 -30
  238. package/dist/lib/custom/ai-agent-run/flow/run-tree-flow-projection.js.map +1 -1
  239. package/dist/lib/custom/ai-agent-run/flow/subway-lines.component.d.ts +14 -2
  240. package/dist/lib/custom/ai-agent-run/flow/subway-lines.component.d.ts.map +1 -1
  241. package/dist/lib/custom/ai-agent-run/flow/subway-lines.component.js +69 -51
  242. package/dist/lib/custom/ai-agent-run/flow/subway-lines.component.js.map +1 -1
  243. package/dist/lib/custom/custom-forms.module.d.ts +32 -30
  244. package/dist/lib/custom/custom-forms.module.d.ts.map +1 -1
  245. package/dist/lib/custom/custom-forms.module.js +21 -4
  246. package/dist/lib/custom/custom-forms.module.js.map +1 -1
  247. package/dist/lib/custom/shared/entity-selector-dialog.component.d.ts +38 -12
  248. package/dist/lib/custom/shared/entity-selector-dialog.component.d.ts.map +1 -1
  249. package/dist/lib/custom/shared/entity-selector-dialog.component.js +103 -51
  250. package/dist/lib/custom/shared/entity-selector-dialog.component.js.map +1 -1
  251. package/dist/lib/generated/Entities/MJAIPrompt/mjaiprompt.form.component.d.ts.map +1 -1
  252. package/dist/lib/generated/Entities/MJAIPrompt/mjaiprompt.form.component.js +62 -26
  253. package/dist/lib/generated/Entities/MJAIPrompt/mjaiprompt.form.component.js.map +1 -1
  254. package/dist/lib/generated/Entities/MJAIPromptRun/mjaipromptrun.form.component.d.ts.map +1 -1
  255. package/dist/lib/generated/Entities/MJAIPromptRun/mjaipromptrun.form.component.js +59 -51
  256. package/dist/lib/generated/Entities/MJAIPromptRun/mjaipromptrun.form.component.js.map +1 -1
  257. package/dist/lib/generated/Entities/MJAIVendor/mjaivendor.form.component.js +76 -74
  258. package/dist/lib/generated/Entities/MJAIVendor/mjaivendor.form.component.js.map +1 -1
  259. package/dist/lib/generated/Entities/MJCredential/mjcredential.form.component.d.ts.map +1 -1
  260. package/dist/lib/generated/Entities/MJCredential/mjcredential.form.component.js +33 -15
  261. package/dist/lib/generated/Entities/MJCredential/mjcredential.form.component.js.map +1 -1
  262. package/dist/lib/generated/Entities/MJEntity/mjentity.form.component.d.ts.map +1 -1
  263. package/dist/lib/generated/Entities/MJEntity/mjentity.form.component.js +100 -82
  264. package/dist/lib/generated/Entities/MJEntity/mjentity.form.component.js.map +1 -1
  265. package/dist/lib/generated/Entities/MJEntityField/mjentityfield.form.component.js +24 -20
  266. package/dist/lib/generated/Entities/MJEntityField/mjentityfield.form.component.js.map +1 -1
  267. package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.d.ts +10 -0
  268. package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.d.ts.map +1 -0
  269. package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.js +122 -0
  270. package/dist/lib/generated/Entities/MJFeatureValue/mjfeaturevalue.form.component.js.map +1 -0
  271. package/dist/lib/generated/Entities/MJFeatureValueCache/mjfeaturevaluecache.form.component.d.ts +10 -0
  272. package/dist/lib/generated/Entities/MJFeatureValueCache/mjfeaturevaluecache.form.component.d.ts.map +1 -0
  273. package/dist/lib/generated/Entities/MJFeatureValueCache/mjfeaturevaluecache.form.component.js +125 -0
  274. package/dist/lib/generated/Entities/MJFeatureValueCache/mjfeaturevaluecache.form.component.js.map +1 -0
  275. package/dist/lib/generated/Entities/MJProcessRun/mjprocessrun.form.component.d.ts.map +1 -1
  276. package/dist/lib/generated/Entities/MJProcessRun/mjprocessrun.form.component.js +23 -5
  277. package/dist/lib/generated/Entities/MJProcessRun/mjprocessrun.form.component.js.map +1 -1
  278. package/dist/lib/generated/Entities/MJProcessRunDetail/mjprocessrundetail.form.component.d.ts.map +1 -1
  279. package/dist/lib/generated/Entities/MJProcessRunDetail/mjprocessrundetail.form.component.js +25 -6
  280. package/dist/lib/generated/Entities/MJProcessRunDetail/mjprocessrundetail.form.component.js.map +1 -1
  281. package/dist/lib/generated/Entities/MJProject/mjproject.form.component.js +27 -25
  282. package/dist/lib/generated/Entities/MJProject/mjproject.form.component.js.map +1 -1
  283. package/dist/lib/generated/Entities/MJQuery/mjquery.form.component.d.ts.map +1 -1
  284. package/dist/lib/generated/Entities/MJQuery/mjquery.form.component.js +11 -15
  285. package/dist/lib/generated/Entities/MJQuery/mjquery.form.component.js.map +1 -1
  286. package/dist/lib/generated/Entities/MJRecordProcess/mjrecordprocess.form.component.d.ts.map +1 -1
  287. package/dist/lib/generated/Entities/MJRecordProcess/mjrecordprocess.form.component.js +43 -7
  288. package/dist/lib/generated/Entities/MJRecordProcess/mjrecordprocess.form.component.js.map +1 -1
  289. package/dist/lib/generated/Entities/MJUser/mjuser.form.component.d.ts.map +1 -1
  290. package/dist/lib/generated/Entities/MJUser/mjuser.form.component.js +155 -119
  291. package/dist/lib/generated/Entities/MJUser/mjuser.form.component.js.map +1 -1
  292. package/dist/lib/generated/Entities/MJWebSearchProvider/mjwebsearchprovider.form.component.d.ts +10 -0
  293. package/dist/lib/generated/Entities/MJWebSearchProvider/mjwebsearchprovider.form.component.d.ts.map +1 -0
  294. package/dist/lib/generated/Entities/MJWebSearchProvider/mjwebsearchprovider.form.component.js +78 -0
  295. package/dist/lib/generated/Entities/MJWebSearchProvider/mjwebsearchprovider.form.component.js.map +1 -0
  296. package/dist/lib/generated/generated-forms.module.d.ts +240 -237
  297. package/dist/lib/generated/generated-forms.module.d.ts.map +1 -1
  298. package/dist/lib/generated/generated-forms.module.js +11 -2
  299. package/dist/lib/generated/generated-forms.module.js.map +1 -1
  300. package/dist/lib/panels/ai-skill-sharing/ai-skill-sharing-panel.component.d.ts +5 -1
  301. package/dist/lib/panels/ai-skill-sharing/ai-skill-sharing-panel.component.d.ts.map +1 -1
  302. package/dist/lib/panels/ai-skill-sharing/ai-skill-sharing-panel.component.js +11 -3
  303. package/dist/lib/panels/ai-skill-sharing/ai-skill-sharing-panel.component.js.map +1 -1
  304. package/dist/lib/panels/content-sources/tag-pipeline-configuration.panel.d.ts +2 -0
  305. package/dist/lib/panels/content-sources/tag-pipeline-configuration.panel.d.ts.map +1 -1
  306. package/dist/lib/panels/content-sources/tag-pipeline-configuration.panel.js +16 -12
  307. package/dist/lib/panels/content-sources/tag-pipeline-configuration.panel.js.map +1 -1
  308. package/dist/lib/panels/content-sources/website-crawler-settings.panel.d.ts +1 -1
  309. package/dist/lib/panels/content-sources/website-crawler-settings.panel.js +2 -2
  310. package/dist/lib/panels/content-sources/website-crawler-settings.panel.js.map +1 -1
  311. package/dist/lib/panels/model-predictions/model-prediction.logic.d.ts +122 -1
  312. package/dist/lib/panels/model-predictions/model-prediction.logic.d.ts.map +1 -1
  313. package/dist/lib/panels/model-predictions/model-prediction.logic.js +236 -11
  314. package/dist/lib/panels/model-predictions/model-prediction.logic.js.map +1 -1
  315. package/dist/lib/panels/model-predictions/model-prediction.panel.d.ts +72 -13
  316. package/dist/lib/panels/model-predictions/model-prediction.panel.d.ts.map +1 -1
  317. package/dist/lib/panels/model-predictions/model-prediction.panel.js +577 -78
  318. package/dist/lib/panels/model-predictions/model-prediction.panel.js.map +1 -1
  319. package/dist/lib/shared/components/template-editor.component.d.ts +133 -14
  320. package/dist/lib/shared/components/template-editor.component.d.ts.map +1 -1
  321. package/dist/lib/shared/components/template-editor.component.js +322 -124
  322. package/dist/lib/shared/components/template-editor.component.js.map +1 -1
  323. package/dist/public-api.d.ts +4 -0
  324. package/dist/public-api.d.ts.map +1 -1
  325. package/dist/public-api.js +4 -0
  326. package/dist/public-api.js.map +1 -1
  327. package/package.json +43 -41
@@ -2008,78 +2008,450 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2008
2008
  super(...arguments);
2009
2009
  this.destroy$ = new Subject();
2010
2010
  // UI state
2011
- this.activeTab = 'overview';
2012
- this.loading = false;
2013
- this.loadingTests = false;
2014
- this.loadingRuns = false;
2015
- this.loadingAnalytics = false;
2016
- this.loadingCompare = false;
2017
- this.testsLoaded = false;
2018
- this.runsLoaded = false;
2019
- this.analyticsLoaded = false;
2020
- this.isRefreshing = false;
2011
+ this.ActiveTab = 'overview';
2012
+ this.Loading = false;
2013
+ this.LoadingTests = false;
2014
+ this.LoadingRuns = false;
2015
+ this.LoadingAnalytics = false;
2016
+ this.LoadingCompare = false;
2017
+ this.TestsLoaded = false;
2018
+ this.RunsLoaded = false;
2019
+ this.AnalyticsLoaded = false;
2020
+ this.isRefreshing = false; // case-violation-ok-legacy-back-compat: an ancestor class already declares the PascalCase name
2021
2021
  this.error = null;
2022
2022
  // Related data
2023
- this.suiteTests = [];
2024
- this.suiteRuns = [];
2023
+ this.SuiteTests = [];
2024
+ this.SuiteRuns = [];
2025
2025
  // Analytics data
2026
- this.analyticsData = [];
2027
- this.uniqueTags = [];
2028
- this.selectedTags = []; // Multi-select: empty array means "All Tags"
2029
- this.analyticsTimeRange = '30d';
2030
- this.analyticsView = 'summary';
2031
- this.matrixData = [];
2032
- this.loadingMatrix = false;
2033
- this.matrixLoaded = false;
2026
+ this.AnalyticsData = [];
2027
+ this.UniqueTags = [];
2028
+ this.SelectedTags = [];
2029
+ this.AnalyticsTimeRange = '30d';
2030
+ this.AnalyticsView = 'summary';
2031
+ this.MatrixData = [];
2032
+ this.LoadingMatrix = false;
2033
+ this.MatrixLoaded = false;
2034
2034
  this.chartRendered = false;
2035
2035
  // Compare data
2036
- this.compareRunA = null;
2037
- this.compareRunB = null;
2038
- this.compareResults = [];
2039
- this.compareRunATests = [];
2040
- this.compareRunBTests = [];
2036
+ this.CompareRunA = null;
2037
+ this.CompareRunB = null;
2038
+ this.CompareResults = [];
2039
+ this.CompareRunATests = [];
2040
+ this.CompareRunBTests = [];
2041
2041
  // Keyboard shortcuts
2042
- this.keyboardShortcutsEnabled = true;
2043
- this.showShortcuts = false; // Hidden by default
2042
+ this.KeyboardShortcutsEnabled = true;
2043
+ this.ShowShortcuts = false;
2044
2044
  this.shortcutsSettingEntity = null;
2045
2045
  // Evaluation preferences
2046
- this.evalPreferences = { showExecution: true, showHuman: true, showAuto: false };
2046
+ this.EvalPreferences = { showExecution: true, showHuman: true, showAuto: false };
2047
2047
  // Filter collapse state
2048
- this.filtersCollapsed = false;
2048
+ this.FiltersCollapsed = false;
2049
2049
  // Matrix sorting
2050
- this.matrixSortBy = 'sequence';
2051
- this.matrixSortAsc = true;
2050
+ this.MatrixSortBy = 'sequence';
2051
+ this.MatrixSortAsc = true;
2052
2052
  // Matrix row selection
2053
- this.selectedMatrixTestId = null;
2053
+ this.SelectedMatrixTestId = null;
2054
2054
  // Matrix test name filter
2055
- this.matrixTestFilter = '';
2055
+ this.MatrixTestFilter = '';
2056
2056
  this.matrixFilterSubject$ = new Subject();
2057
2057
  // Edit state
2058
- this.isSaving = false;
2059
- this.parentSuiteOptions = [];
2060
- this.tagDraft = '';
2061
- this.statusOptions = ['Active', 'Pending', 'Disabled'];
2058
+ this.IsSaving = false;
2059
+ this.ParentSuiteOptions = [];
2060
+ this.TagDraft = '';
2061
+ this.StatusOptions = ['Active', 'Pending', 'Disabled'];
2062
2062
  // Add Tests picker state
2063
- this.showAddTestsDialog = false;
2064
- this.availableTests = [];
2065
- this.loadingAvailableTests = false;
2066
- this.selectedTestIdsToAdd = new Set();
2067
- this.addTestsSearch = '';
2068
- this.isAddingTests = false;
2063
+ this.ShowAddTestsDialog = false;
2064
+ this.AvailableTests = [];
2065
+ this.LoadingAvailableTests = false;
2066
+ this.SelectedTestIdsToAdd = new Set();
2067
+ this.AddTestsSearch = '';
2068
+ this.IsAddingTests = false;
2069
2069
  // Remove-confirm state (per-row inline confirm)
2070
- this.confirmingRemoveSuiteTestId = null;
2071
- this.isRemovingTest = false;
2070
+ this.ConfirmingRemoveSuiteTestId = null;
2071
+ this.IsRemovingTest = false;
2072
2072
  // Reorder state
2073
- this.isReorderingTests = false;
2073
+ this.IsReorderingTests = false;
2074
2074
  // Service injections
2075
2075
  this.navigationService = inject(NavigationService);
2076
- this.testingDialogService = inject(TestingDialogService);
2076
+ this.TestingDialogService = inject(TestingDialogService);
2077
2077
  this.evalPrefsService = inject(EvaluationPreferencesService);
2078
2078
  this.viewContainerRef = inject(ViewContainerRef);
2079
2079
  this.appManager = inject(ApplicationManager);
2080
2080
  this.confirmService = inject(MJConfirmService);
2081
2081
  }
2082
+ /** @deprecated Use {@link ActiveTab}. */
2083
+ get activeTab() {
2084
+ return this.ActiveTab;
2085
+ }
2086
+ /** @deprecated Use {@link ActiveTab}. */
2087
+ set activeTab(value) {
2088
+ this.ActiveTab = value;
2089
+ }
2090
+ /** @deprecated Use {@link Loading}. */
2091
+ get loading() {
2092
+ return this.Loading;
2093
+ }
2094
+ /** @deprecated Use {@link Loading}. */
2095
+ set loading(value) {
2096
+ this.Loading = value;
2097
+ }
2098
+ /** @deprecated Use {@link LoadingTests}. */
2099
+ get loadingTests() {
2100
+ return this.LoadingTests;
2101
+ }
2102
+ /** @deprecated Use {@link LoadingTests}. */
2103
+ set loadingTests(value) {
2104
+ this.LoadingTests = value;
2105
+ }
2106
+ /** @deprecated Use {@link LoadingRuns}. */
2107
+ get loadingRuns() {
2108
+ return this.LoadingRuns;
2109
+ }
2110
+ /** @deprecated Use {@link LoadingRuns}. */
2111
+ set loadingRuns(value) {
2112
+ this.LoadingRuns = value;
2113
+ }
2114
+ /** @deprecated Use {@link LoadingAnalytics}. */
2115
+ get loadingAnalytics() {
2116
+ return this.LoadingAnalytics;
2117
+ }
2118
+ /** @deprecated Use {@link LoadingAnalytics}. */
2119
+ set loadingAnalytics(value) {
2120
+ this.LoadingAnalytics = value;
2121
+ }
2122
+ /** @deprecated Use {@link LoadingCompare}. */
2123
+ get loadingCompare() {
2124
+ return this.LoadingCompare;
2125
+ }
2126
+ /** @deprecated Use {@link LoadingCompare}. */
2127
+ set loadingCompare(value) {
2128
+ this.LoadingCompare = value;
2129
+ }
2130
+ /** @deprecated Use {@link TestsLoaded}. */
2131
+ get testsLoaded() {
2132
+ return this.TestsLoaded;
2133
+ }
2134
+ /** @deprecated Use {@link TestsLoaded}. */
2135
+ set testsLoaded(value) {
2136
+ this.TestsLoaded = value;
2137
+ }
2138
+ /** @deprecated Use {@link RunsLoaded}. */
2139
+ get runsLoaded() {
2140
+ return this.RunsLoaded;
2141
+ }
2142
+ /** @deprecated Use {@link RunsLoaded}. */
2143
+ set runsLoaded(value) {
2144
+ this.RunsLoaded = value;
2145
+ }
2146
+ /** @deprecated Use {@link AnalyticsLoaded}. */
2147
+ get analyticsLoaded() {
2148
+ return this.AnalyticsLoaded;
2149
+ }
2150
+ /** @deprecated Use {@link AnalyticsLoaded}. */
2151
+ set analyticsLoaded(value) {
2152
+ this.AnalyticsLoaded = value;
2153
+ }
2154
+ /** @deprecated Use {@link SuiteTests}. */
2155
+ get suiteTests() {
2156
+ return this.SuiteTests;
2157
+ }
2158
+ /** @deprecated Use {@link SuiteTests}. */
2159
+ set suiteTests(value) {
2160
+ this.SuiteTests = value;
2161
+ }
2162
+ /** @deprecated Use {@link SuiteRuns}. */
2163
+ get suiteRuns() {
2164
+ return this.SuiteRuns;
2165
+ }
2166
+ /** @deprecated Use {@link SuiteRuns}. */
2167
+ set suiteRuns(value) {
2168
+ this.SuiteRuns = value;
2169
+ }
2170
+ /** @deprecated Use {@link AnalyticsData}. */
2171
+ get analyticsData() {
2172
+ return this.AnalyticsData;
2173
+ }
2174
+ /** @deprecated Use {@link AnalyticsData}. */
2175
+ set analyticsData(value) {
2176
+ this.AnalyticsData = value;
2177
+ }
2178
+ /** @deprecated Use {@link UniqueTags}. */
2179
+ get uniqueTags() {
2180
+ return this.UniqueTags;
2181
+ }
2182
+ /** @deprecated Use {@link UniqueTags}. */
2183
+ set uniqueTags(value) {
2184
+ this.UniqueTags = value;
2185
+ }
2186
+ /** @deprecated Use {@link SelectedTags}. */
2187
+ get selectedTags() {
2188
+ return this.SelectedTags;
2189
+ }
2190
+ /** @deprecated Use {@link SelectedTags}. */
2191
+ set selectedTags(value) {
2192
+ this.SelectedTags = value;
2193
+ } // Multi-select: empty array means "All Tags"
2194
+ /** @deprecated Use {@link AnalyticsTimeRange}. */
2195
+ get analyticsTimeRange() {
2196
+ return this.AnalyticsTimeRange;
2197
+ }
2198
+ /** @deprecated Use {@link AnalyticsTimeRange}. */
2199
+ set analyticsTimeRange(value) {
2200
+ this.AnalyticsTimeRange = value;
2201
+ }
2202
+ /** @deprecated Use {@link AnalyticsView}. */
2203
+ get analyticsView() {
2204
+ return this.AnalyticsView;
2205
+ }
2206
+ /** @deprecated Use {@link AnalyticsView}. */
2207
+ set analyticsView(value) {
2208
+ this.AnalyticsView = value;
2209
+ }
2210
+ /** @deprecated Use {@link MatrixData}. */
2211
+ get matrixData() {
2212
+ return this.MatrixData;
2213
+ }
2214
+ /** @deprecated Use {@link MatrixData}. */
2215
+ set matrixData(value) {
2216
+ this.MatrixData = value;
2217
+ }
2218
+ /** @deprecated Use {@link LoadingMatrix}. */
2219
+ get loadingMatrix() {
2220
+ return this.LoadingMatrix;
2221
+ }
2222
+ /** @deprecated Use {@link LoadingMatrix}. */
2223
+ set loadingMatrix(value) {
2224
+ this.LoadingMatrix = value;
2225
+ }
2226
+ /** @deprecated Use {@link MatrixLoaded}. */
2227
+ get matrixLoaded() {
2228
+ return this.MatrixLoaded;
2229
+ }
2230
+ /** @deprecated Use {@link MatrixLoaded}. */
2231
+ set matrixLoaded(value) {
2232
+ this.MatrixLoaded = value;
2233
+ }
2234
+ /** @deprecated Use {@link ChartContainer}. */
2235
+ get chartContainer() {
2236
+ return this.ChartContainer;
2237
+ }
2238
+ /** @deprecated Use {@link ChartContainer}. */
2239
+ set chartContainer(value) {
2240
+ this.ChartContainer = value;
2241
+ }
2242
+ /** @deprecated Use {@link CompareRunA}. */
2243
+ get compareRunA() {
2244
+ return this.CompareRunA;
2245
+ }
2246
+ /** @deprecated Use {@link CompareRunA}. */
2247
+ set compareRunA(value) {
2248
+ this.CompareRunA = value;
2249
+ }
2250
+ /** @deprecated Use {@link CompareRunB}. */
2251
+ get compareRunB() {
2252
+ return this.CompareRunB;
2253
+ }
2254
+ /** @deprecated Use {@link CompareRunB}. */
2255
+ set compareRunB(value) {
2256
+ this.CompareRunB = value;
2257
+ }
2258
+ /** @deprecated Use {@link CompareResults}. */
2259
+ get compareResults() {
2260
+ return this.CompareResults;
2261
+ }
2262
+ /** @deprecated Use {@link CompareResults}. */
2263
+ set compareResults(value) {
2264
+ this.CompareResults = value;
2265
+ }
2266
+ /** @deprecated Use {@link CompareRunATests}. */
2267
+ get compareRunATests() {
2268
+ return this.CompareRunATests;
2269
+ }
2270
+ /** @deprecated Use {@link CompareRunATests}. */
2271
+ set compareRunATests(value) {
2272
+ this.CompareRunATests = value;
2273
+ }
2274
+ /** @deprecated Use {@link CompareRunBTests}. */
2275
+ get compareRunBTests() {
2276
+ return this.CompareRunBTests;
2277
+ }
2278
+ /** @deprecated Use {@link CompareRunBTests}. */
2279
+ set compareRunBTests(value) {
2280
+ this.CompareRunBTests = value;
2281
+ }
2282
+ /** @deprecated Use {@link KeyboardShortcutsEnabled}. */
2283
+ get keyboardShortcutsEnabled() {
2284
+ return this.KeyboardShortcutsEnabled;
2285
+ }
2286
+ /** @deprecated Use {@link KeyboardShortcutsEnabled}. */
2287
+ set keyboardShortcutsEnabled(value) {
2288
+ this.KeyboardShortcutsEnabled = value;
2289
+ }
2290
+ /** @deprecated Use {@link ShowShortcuts}. */
2291
+ get showShortcuts() {
2292
+ return this.ShowShortcuts;
2293
+ }
2294
+ /** @deprecated Use {@link ShowShortcuts}. */
2295
+ set showShortcuts(value) {
2296
+ this.ShowShortcuts = value;
2297
+ } // Hidden by default
2082
2298
  get metadata() { return this.ProviderToUse; }
2299
+ /** @deprecated Use {@link EvalPreferences}. */
2300
+ get evalPreferences() {
2301
+ return this.EvalPreferences;
2302
+ }
2303
+ /** @deprecated Use {@link EvalPreferences}. */
2304
+ set evalPreferences(value) {
2305
+ this.EvalPreferences = value;
2306
+ }
2307
+ /** @deprecated Use {@link FiltersCollapsed}. */
2308
+ get filtersCollapsed() {
2309
+ return this.FiltersCollapsed;
2310
+ }
2311
+ /** @deprecated Use {@link FiltersCollapsed}. */
2312
+ set filtersCollapsed(value) {
2313
+ this.FiltersCollapsed = value;
2314
+ }
2315
+ /** @deprecated Use {@link MatrixSortBy}. */
2316
+ get matrixSortBy() {
2317
+ return this.MatrixSortBy;
2318
+ }
2319
+ /** @deprecated Use {@link MatrixSortBy}. */
2320
+ set matrixSortBy(value) {
2321
+ this.MatrixSortBy = value;
2322
+ }
2323
+ /** @deprecated Use {@link MatrixSortAsc}. */
2324
+ get matrixSortAsc() {
2325
+ return this.MatrixSortAsc;
2326
+ }
2327
+ /** @deprecated Use {@link MatrixSortAsc}. */
2328
+ set matrixSortAsc(value) {
2329
+ this.MatrixSortAsc = value;
2330
+ }
2331
+ /** @deprecated Use {@link SelectedMatrixTestId}. */
2332
+ get selectedMatrixTestId() {
2333
+ return this.SelectedMatrixTestId;
2334
+ }
2335
+ /** @deprecated Use {@link SelectedMatrixTestId}. */
2336
+ set selectedMatrixTestId(value) {
2337
+ this.SelectedMatrixTestId = value;
2338
+ }
2339
+ /** @deprecated Use {@link MatrixTestFilter}. */
2340
+ get matrixTestFilter() {
2341
+ return this.MatrixTestFilter;
2342
+ }
2343
+ /** @deprecated Use {@link MatrixTestFilter}. */
2344
+ set matrixTestFilter(value) {
2345
+ this.MatrixTestFilter = value;
2346
+ }
2347
+ /** @deprecated Use {@link IsSaving}. */
2348
+ get isSaving() {
2349
+ return this.IsSaving;
2350
+ }
2351
+ /** @deprecated Use {@link IsSaving}. */
2352
+ set isSaving(value) {
2353
+ this.IsSaving = value;
2354
+ }
2355
+ /** @deprecated Use {@link ParentSuiteOptions}. */
2356
+ get parentSuiteOptions() {
2357
+ return this.ParentSuiteOptions;
2358
+ }
2359
+ /** @deprecated Use {@link ParentSuiteOptions}. */
2360
+ set parentSuiteOptions(value) {
2361
+ this.ParentSuiteOptions = value;
2362
+ }
2363
+ /** @deprecated Use {@link TagDraft}. */
2364
+ get tagDraft() {
2365
+ return this.TagDraft;
2366
+ }
2367
+ /** @deprecated Use {@link TagDraft}. */
2368
+ set tagDraft(value) {
2369
+ this.TagDraft = value;
2370
+ }
2371
+ /** @deprecated Use {@link StatusOptions}. */
2372
+ get statusOptions() {
2373
+ return this.StatusOptions;
2374
+ }
2375
+ /** @deprecated Use {@link ShowAddTestsDialog}. */
2376
+ get showAddTestsDialog() {
2377
+ return this.ShowAddTestsDialog;
2378
+ }
2379
+ /** @deprecated Use {@link ShowAddTestsDialog}. */
2380
+ set showAddTestsDialog(value) {
2381
+ this.ShowAddTestsDialog = value;
2382
+ }
2383
+ /** @deprecated Use {@link AvailableTests}. */
2384
+ get availableTests() {
2385
+ return this.AvailableTests;
2386
+ }
2387
+ /** @deprecated Use {@link AvailableTests}. */
2388
+ set availableTests(value) {
2389
+ this.AvailableTests = value;
2390
+ }
2391
+ /** @deprecated Use {@link LoadingAvailableTests}. */
2392
+ get loadingAvailableTests() {
2393
+ return this.LoadingAvailableTests;
2394
+ }
2395
+ /** @deprecated Use {@link LoadingAvailableTests}. */
2396
+ set loadingAvailableTests(value) {
2397
+ this.LoadingAvailableTests = value;
2398
+ }
2399
+ /** @deprecated Use {@link SelectedTestIdsToAdd}. */
2400
+ get selectedTestIdsToAdd() {
2401
+ return this.SelectedTestIdsToAdd;
2402
+ }
2403
+ /** @deprecated Use {@link SelectedTestIdsToAdd}. */
2404
+ set selectedTestIdsToAdd(value) {
2405
+ this.SelectedTestIdsToAdd = value;
2406
+ }
2407
+ /** @deprecated Use {@link AddTestsSearch}. */
2408
+ get addTestsSearch() {
2409
+ return this.AddTestsSearch;
2410
+ }
2411
+ /** @deprecated Use {@link AddTestsSearch}. */
2412
+ set addTestsSearch(value) {
2413
+ this.AddTestsSearch = value;
2414
+ }
2415
+ /** @deprecated Use {@link IsAddingTests}. */
2416
+ get isAddingTests() {
2417
+ return this.IsAddingTests;
2418
+ }
2419
+ /** @deprecated Use {@link IsAddingTests}. */
2420
+ set isAddingTests(value) {
2421
+ this.IsAddingTests = value;
2422
+ }
2423
+ /** @deprecated Use {@link ConfirmingRemoveSuiteTestId}. */
2424
+ get confirmingRemoveSuiteTestId() {
2425
+ return this.ConfirmingRemoveSuiteTestId;
2426
+ }
2427
+ /** @deprecated Use {@link ConfirmingRemoveSuiteTestId}. */
2428
+ set confirmingRemoveSuiteTestId(value) {
2429
+ this.ConfirmingRemoveSuiteTestId = value;
2430
+ }
2431
+ /** @deprecated Use {@link IsRemovingTest}. */
2432
+ get isRemovingTest() {
2433
+ return this.IsRemovingTest;
2434
+ }
2435
+ /** @deprecated Use {@link IsRemovingTest}. */
2436
+ set isRemovingTest(value) {
2437
+ this.IsRemovingTest = value;
2438
+ }
2439
+ /** @deprecated Use {@link IsReorderingTests}. */
2440
+ get isReorderingTests() {
2441
+ return this.IsReorderingTests;
2442
+ }
2443
+ /** @deprecated Use {@link IsReorderingTests}. */
2444
+ set isReorderingTests(value) {
2445
+ this.IsReorderingTests = value;
2446
+ }
2447
+ /** @deprecated Use {@link TestingDialogService}. */
2448
+ get testingDialogService() {
2449
+ return this.TestingDialogService;
2450
+ }
2451
+ /** @deprecated Use {@link TestingDialogService}. */
2452
+ set testingDialogService(value) {
2453
+ this.TestingDialogService = value;
2454
+ }
2083
2455
  async ngOnInit() {
2084
2456
  await super.ngOnInit();
2085
2457
  this.loadShortcutsSetting();
@@ -2089,10 +2461,10 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2089
2461
  this.evalPrefsService.preferences$
2090
2462
  .pipe(takeUntil(this.destroy$))
2091
2463
  .subscribe(prefs => {
2092
- this.evalPreferences = prefs;
2464
+ this.EvalPreferences = prefs;
2093
2465
  this.cdr.markForCheck();
2094
2466
  // Re-render chart when preferences change (D3 chart needs manual update)
2095
- if (this.chartRendered && this.analyticsView === 'chart') {
2467
+ if (this.chartRendered && this.AnalyticsView === 'chart') {
2096
2468
  this.renderChart();
2097
2469
  }
2098
2470
  });
@@ -2100,11 +2472,11 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2100
2472
  this.matrixFilterSubject$
2101
2473
  .pipe(debounceTime(300), distinctUntilChanged(), takeUntil(this.destroy$))
2102
2474
  .subscribe(value => {
2103
- this.matrixTestFilter = value;
2475
+ this.MatrixTestFilter = value;
2104
2476
  this.cdr.markForCheck();
2105
2477
  });
2106
2478
  // Subscribe to panel state changes so the slide panel renders in this form
2107
- this.testingDialogService.PanelStateChanged$
2479
+ this.TestingDialogService.PanelStateChanged$
2108
2480
  .pipe(takeUntil(this.destroy$))
2109
2481
  .subscribe(() => {
2110
2482
  this.cdr.detectChanges();
@@ -2119,52 +2491,52 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2119
2491
  }
2120
2492
  // Keyboard shortcuts
2121
2493
  handleKeyboardShortcut(event) {
2122
- if (!this.keyboardShortcutsEnabled)
2494
+ if (!this.KeyboardShortcutsEnabled)
2123
2495
  return;
2124
2496
  // Cmd/Ctrl + S: Save (if dirty)
2125
2497
  if ((event.metaKey || event.ctrlKey) && event.key === 's' && !event.shiftKey) {
2126
- if (this.isDirty) {
2498
+ if (this.IsDirty) {
2127
2499
  event.preventDefault();
2128
- this.saveChanges();
2500
+ this.SaveChanges();
2129
2501
  }
2130
2502
  return;
2131
2503
  }
2132
2504
  // Cmd/Ctrl + R: Refresh
2133
2505
  if ((event.metaKey || event.ctrlKey) && event.key === 'r' && !event.shiftKey) {
2134
2506
  event.preventDefault();
2135
- this.refresh();
2507
+ this.Refresh();
2136
2508
  return;
2137
2509
  }
2138
2510
  // Cmd/Ctrl + Enter: Run suite
2139
2511
  if ((event.metaKey || event.ctrlKey) && event.key === 'Enter') {
2140
2512
  event.preventDefault();
2141
- this.runSuite();
2513
+ this.RunSuite();
2142
2514
  return;
2143
2515
  }
2144
2516
  // Number keys for tabs (1-5) — skip when typing into form inputs
2145
2517
  if (!event.metaKey && !event.ctrlKey && !event.altKey && !this.isTextInputFocused()) {
2146
2518
  switch (event.key) {
2147
2519
  case '1':
2148
- this.changeTab('overview');
2520
+ this.ChangeTab('overview');
2149
2521
  break;
2150
2522
  case '2':
2151
- this.changeTab('tests');
2523
+ this.ChangeTab('tests');
2152
2524
  break;
2153
2525
  case '3':
2154
- this.changeTab('runs');
2526
+ this.ChangeTab('runs');
2155
2527
  break;
2156
2528
  case '4':
2157
- this.changeTab('analytics');
2529
+ this.ChangeTab('analytics');
2158
2530
  break;
2159
2531
  case '5':
2160
- this.changeTab('compare');
2532
+ this.ChangeTab('compare');
2161
2533
  break;
2162
2534
  }
2163
2535
  }
2164
2536
  }
2165
2537
  // Warn before tab close / hard navigation when there are unsaved changes
2166
2538
  handleBeforeUnload(event) {
2167
- if (this.isDirty) {
2539
+ if (this.IsDirty) {
2168
2540
  event.preventDefault();
2169
2541
  event.returnValue = '';
2170
2542
  }
@@ -2176,20 +2548,24 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2176
2548
  const tag = el.tagName;
2177
2549
  return tag === 'INPUT' || tag === 'TEXTAREA' || tag === 'SELECT' || el.isContentEditable;
2178
2550
  }
2179
- changeTab(tab) {
2180
- this.activeTab = tab;
2181
- if (tab === 'tests' && !this.testsLoaded)
2551
+ ChangeTab(tab) {
2552
+ this.ActiveTab = tab;
2553
+ if (tab === 'tests' && !this.TestsLoaded)
2182
2554
  this.loadTests();
2183
- if (tab === 'runs' && !this.runsLoaded)
2555
+ if (tab === 'runs' && !this.RunsLoaded)
2184
2556
  this.loadRuns();
2185
- if (tab === 'analytics' && !this.analyticsLoaded)
2557
+ if (tab === 'analytics' && !this.AnalyticsLoaded)
2186
2558
  this.loadAnalytics();
2187
2559
  this.cdr.markForCheck();
2188
2560
  }
2561
+ /** @deprecated Use {@link ChangeTab}. */
2562
+ changeTab(tab) {
2563
+ return this.ChangeTab(tab);
2564
+ }
2189
2565
  async loadTests() {
2190
- if (this.testsLoaded)
2566
+ if (this.TestsLoaded)
2191
2567
  return;
2192
- this.loadingTests = true;
2568
+ this.LoadingTests = true;
2193
2569
  this.cdr.markForCheck();
2194
2570
  try {
2195
2571
  const rv = RunView.FromMetadataProvider(this.ProviderToUse);
@@ -2200,22 +2576,22 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2200
2576
  ResultType: 'entity_object'
2201
2577
  });
2202
2578
  if (result.Success)
2203
- this.suiteTests = result.Results || [];
2204
- this.testsLoaded = true;
2579
+ this.SuiteTests = result.Results || [];
2580
+ this.TestsLoaded = true;
2205
2581
  }
2206
2582
  catch (error) {
2207
2583
  console.error('Error loading tests:', error);
2208
2584
  SharedService.Instance.CreateSimpleNotification('Failed to load tests', 'error', 3000);
2209
2585
  }
2210
2586
  finally {
2211
- this.loadingTests = false;
2587
+ this.LoadingTests = false;
2212
2588
  this.cdr.markForCheck();
2213
2589
  }
2214
2590
  }
2215
2591
  async loadRuns() {
2216
- if (this.runsLoaded)
2592
+ if (this.RunsLoaded)
2217
2593
  return;
2218
- this.loadingRuns = true;
2594
+ this.LoadingRuns = true;
2219
2595
  this.cdr.markForCheck();
2220
2596
  try {
2221
2597
  const rv = RunView.FromMetadataProvider(this.ProviderToUse);
@@ -2227,19 +2603,19 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2227
2603
  ResultType: 'entity_object'
2228
2604
  });
2229
2605
  if (result.Success)
2230
- this.suiteRuns = result.Results || [];
2231
- this.runsLoaded = true;
2606
+ this.SuiteRuns = result.Results || [];
2607
+ this.RunsLoaded = true;
2232
2608
  }
2233
2609
  catch (error) {
2234
2610
  console.error('Error loading runs:', error);
2235
2611
  SharedService.Instance.CreateSimpleNotification('Failed to load runs', 'error', 3000);
2236
2612
  }
2237
2613
  finally {
2238
- this.loadingRuns = false;
2614
+ this.LoadingRuns = false;
2239
2615
  this.cdr.markForCheck();
2240
2616
  }
2241
2617
  }
2242
- getStatusColor() {
2618
+ GetStatusColor() {
2243
2619
  switch (this.record.Status) {
2244
2620
  case 'Active': return '#10b981';
2245
2621
  case 'Disabled': return '#6b7280';
@@ -2247,10 +2623,18 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2247
2623
  default: return '#9ca3af';
2248
2624
  }
2249
2625
  }
2250
- getStatusClass() {
2626
+ /** @deprecated Use {@link GetStatusColor}. */
2627
+ getStatusColor() {
2628
+ return this.GetStatusColor();
2629
+ }
2630
+ GetStatusClass() {
2251
2631
  return `status-${this.record.Status?.toLowerCase() || 'unknown'}`;
2252
2632
  }
2253
- getRunStatusColor(status) {
2633
+ /** @deprecated Use {@link GetStatusClass}. */
2634
+ getStatusClass() {
2635
+ return this.GetStatusClass();
2636
+ }
2637
+ GetRunStatusColor(status) {
2254
2638
  switch (status) {
2255
2639
  case 'Completed': return '#10b981';
2256
2640
  case 'Failed': return '#ef4444';
@@ -2260,7 +2644,11 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2260
2644
  default: return '#9ca3af';
2261
2645
  }
2262
2646
  }
2263
- formatTimeout(ms) {
2647
+ /** @deprecated Use {@link GetRunStatusColor}. */
2648
+ getRunStatusColor(status) {
2649
+ return this.GetRunStatusColor(status);
2650
+ }
2651
+ FormatTimeout(ms) {
2264
2652
  if (ms === null || ms === undefined)
2265
2653
  return 'Default (5 min)';
2266
2654
  if (ms < 1000)
@@ -2276,7 +2664,11 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2276
2664
  const mins = Math.floor((ms % 3600000) / 60000);
2277
2665
  return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
2278
2666
  }
2279
- getRelativeTime(date) {
2667
+ /** @deprecated Use {@link FormatTimeout}. */
2668
+ formatTimeout(ms) {
2669
+ return this.FormatTimeout(ms);
2670
+ }
2671
+ GetRelativeTime(date) {
2280
2672
  if (!date)
2281
2673
  return 'N/A';
2282
2674
  const d = new Date(date);
@@ -2295,59 +2687,83 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2295
2687
  return `${diffDays}d ago`;
2296
2688
  return d.toLocaleDateString();
2297
2689
  }
2298
- getPassRate(run) {
2690
+ /** @deprecated Use {@link GetRelativeTime}. */
2691
+ getRelativeTime(date) {
2692
+ return this.GetRelativeTime(date);
2693
+ }
2694
+ GetPassRate(run) {
2299
2695
  const total = run.TotalTests || 0;
2300
2696
  const passed = run.PassedTests || 0;
2301
2697
  if (total === 0)
2302
2698
  return 0;
2303
2699
  return (passed / total) * 100;
2304
2700
  }
2305
- openTest(testId) {
2701
+ /** @deprecated Use {@link GetPassRate}. */
2702
+ getPassRate(run) {
2703
+ return this.GetPassRate(run);
2704
+ }
2705
+ OpenTest(testId) {
2306
2706
  SharedService.Instance.OpenEntityRecord('MJ: Tests', CompositeKey.FromID(testId));
2307
2707
  }
2308
- openSuiteRun(runId) {
2708
+ /** @deprecated Use {@link OpenTest}. */
2709
+ openTest(testId) {
2710
+ return this.OpenTest(testId);
2711
+ }
2712
+ OpenSuiteRun(runId) {
2309
2713
  SharedService.Instance.OpenEntityRecord('MJ: Test Suite Runs', CompositeKey.FromID(runId));
2310
2714
  }
2311
- navigateToTestingDashboard() {
2715
+ /** @deprecated Use {@link OpenSuiteRun}. */
2716
+ openSuiteRun(runId) {
2717
+ return this.OpenSuiteRun(runId);
2718
+ }
2719
+ NavigateToTestingDashboard() {
2312
2720
  const testingApp = this.appManager.GetAppByName('Testing');
2313
2721
  if (testingApp) {
2314
2722
  this.navigationService.SwitchToApp(testingApp.ID);
2315
2723
  }
2316
2724
  }
2317
- async runSuite() {
2725
+ /** @deprecated Use {@link NavigateToTestingDashboard}. */
2726
+ navigateToTestingDashboard() {
2727
+ return this.NavigateToTestingDashboard();
2728
+ }
2729
+ async RunSuite() {
2318
2730
  if (this.record?.ID) {
2319
- this.testingDialogService.OpenSuitePanel(this.record.ID);
2731
+ this.TestingDialogService.OpenSuitePanel(this.record.ID);
2320
2732
  }
2321
2733
  }
2734
+ /** @deprecated Use {@link RunSuite}. */
2735
+ async runSuite() {
2736
+ return this.RunSuite();
2737
+ }
2322
2738
  OnPanelClosed() {
2323
- this.testingDialogService.ClosePanel();
2739
+ this.TestingDialogService.ClosePanel();
2324
2740
  this.cdr.markForCheck();
2325
2741
  }
2326
- async refresh() {
2742
+ async Refresh() {
2327
2743
  this.isRefreshing = true;
2328
2744
  this.cdr.markForCheck();
2329
2745
  try {
2330
2746
  await this.record.Load(this.record.ID);
2331
2747
  // Reset lazy-loaded data
2332
- if (this.testsLoaded) {
2333
- this.testsLoaded = false;
2334
- this.suiteTests = [];
2748
+ if (this.TestsLoaded) {
2749
+ this.TestsLoaded = false;
2750
+ this.SuiteTests = [];
2335
2751
  await this.loadTests();
2336
2752
  }
2337
- if (this.runsLoaded) {
2338
- this.runsLoaded = false;
2339
- this.suiteRuns = [];
2753
+ if (this.RunsLoaded) {
2754
+ this.RunsLoaded = false;
2755
+ this.SuiteRuns = [];
2340
2756
  await this.loadRuns();
2341
2757
  }
2342
- if (this.analyticsLoaded) {
2343
- this.analyticsLoaded = false;
2344
- this.analyticsData = [];
2758
+ if (this.AnalyticsLoaded) {
2759
+ this.AnalyticsLoaded = false;
2760
+ this.AnalyticsData = [];
2345
2761
  // Also reset matrix data so it reloads with fresh data
2346
- this.matrixLoaded = false;
2347
- this.matrixData = [];
2762
+ this.MatrixLoaded = false;
2763
+ this.MatrixData = [];
2348
2764
  await this.loadAnalytics();
2349
2765
  // Reload matrix if currently viewing matrix or chart view
2350
- if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
2766
+ if (this.AnalyticsView === 'matrix' || this.AnalyticsView === 'chart') {
2351
2767
  await this.loadMatrixData();
2352
2768
  }
2353
2769
  }
@@ -2361,13 +2777,17 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2361
2777
  this.cdr.markForCheck();
2362
2778
  }
2363
2779
  }
2780
+ /** @deprecated Use {@link Refresh}. */
2781
+ async refresh() {
2782
+ return this.Refresh();
2783
+ }
2364
2784
  // ==========================================
2365
2785
  // Analytics Tab Methods
2366
2786
  // ==========================================
2367
2787
  async loadAnalytics() {
2368
- if (this.analyticsLoaded)
2788
+ if (this.AnalyticsLoaded)
2369
2789
  return;
2370
- this.loadingAnalytics = true;
2790
+ this.LoadingAnalytics = true;
2371
2791
  this.cdr.markForCheck();
2372
2792
  try {
2373
2793
  // Load all runs for analytics (not just recent 50)
@@ -2381,23 +2801,23 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2381
2801
  });
2382
2802
  if (result.Success && result.Results) {
2383
2803
  // Process runs into analytics data points
2384
- this.analyticsData = result.Results.map(run => this.runToDataPoint(run));
2804
+ this.AnalyticsData = result.Results.map(run => this.runToDataPoint(run));
2385
2805
  // Extract unique tags
2386
- this.uniqueTags = TagsHelper.getUniqueTags(result.Results.map(r => r.Tags));
2806
+ this.UniqueTags = TagsHelper.getUniqueTags(result.Results.map(r => r.Tags));
2387
2807
  // Also populate suiteRuns if not already loaded
2388
- if (!this.runsLoaded) {
2389
- this.suiteRuns = result.Results.slice(0, 50);
2390
- this.runsLoaded = true;
2808
+ if (!this.RunsLoaded) {
2809
+ this.SuiteRuns = result.Results.slice(0, 50);
2810
+ this.RunsLoaded = true;
2391
2811
  }
2392
2812
  }
2393
- this.analyticsLoaded = true;
2813
+ this.AnalyticsLoaded = true;
2394
2814
  }
2395
2815
  catch (error) {
2396
2816
  console.error('Error loading analytics:', error);
2397
2817
  SharedService.Instance.CreateSimpleNotification('Failed to load analytics data', 'error', 3000);
2398
2818
  }
2399
2819
  finally {
2400
- this.loadingAnalytics = false;
2820
+ this.LoadingAnalytics = false;
2401
2821
  this.cdr.markForCheck();
2402
2822
  }
2403
2823
  }
@@ -2419,12 +2839,12 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2419
2839
  status: run.Status || 'Unknown'
2420
2840
  };
2421
2841
  }
2422
- getFilteredAnalyticsData() {
2423
- let data = this.analyticsData;
2842
+ GetFilteredAnalyticsData() {
2843
+ let data = this.AnalyticsData;
2424
2844
  // Apply time range filter
2425
2845
  const now = new Date();
2426
2846
  let cutoffDate = null;
2427
- switch (this.analyticsTimeRange) {
2847
+ switch (this.AnalyticsTimeRange) {
2428
2848
  case '7d':
2429
2849
  cutoffDate = new Date(now.getTime() - 7 * 24 * 60 * 60 * 1000);
2430
2850
  break;
@@ -2439,76 +2859,96 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2439
2859
  data = data.filter(d => d.date >= cutoffDate);
2440
2860
  }
2441
2861
  // Apply tag filter (multi-select: empty array means all tags)
2442
- if (this.selectedTags.length > 0) {
2443
- data = data.filter(d => this.selectedTags.some(tag => d.tags.includes(tag)));
2862
+ if (this.SelectedTags.length > 0) {
2863
+ data = data.filter(d => this.SelectedTags.some(tag => d.tags.includes(tag)));
2444
2864
  }
2445
2865
  return data;
2446
2866
  }
2447
- setTimeRange(range) {
2448
- this.analyticsTimeRange = range;
2867
+ /** @deprecated Use {@link GetFilteredAnalyticsData}. */
2868
+ getFilteredAnalyticsData() {
2869
+ return this.GetFilteredAnalyticsData();
2870
+ }
2871
+ SetTimeRange(range) {
2872
+ this.AnalyticsTimeRange = range;
2449
2873
  // Reload matrix data when time range changes (if currently viewing matrix or chart)
2450
- if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
2874
+ if (this.AnalyticsView === 'matrix' || this.AnalyticsView === 'chart') {
2451
2875
  this.reloadMatrixData();
2452
2876
  }
2453
2877
  this.cdr.markForCheck();
2454
2878
  }
2879
+ /** @deprecated Use {@link SetTimeRange}. */
2880
+ setTimeRange(range) {
2881
+ return this.SetTimeRange(range);
2882
+ }
2455
2883
  /**
2456
2884
  * Toggle a tag in the multi-select filter.
2457
2885
  * If tag is null, clear all selections (show all tags).
2458
2886
  */
2459
- toggleTagFilter(tag) {
2887
+ ToggleTagFilter(tag) {
2460
2888
  if (tag === null) {
2461
2889
  // Clear all - show all tags
2462
- this.selectedTags = [];
2890
+ this.SelectedTags = [];
2463
2891
  }
2464
2892
  else {
2465
2893
  // Toggle the tag
2466
- const index = this.selectedTags.indexOf(tag);
2894
+ const index = this.SelectedTags.indexOf(tag);
2467
2895
  if (index >= 0) {
2468
- this.selectedTags = this.selectedTags.filter(t => t !== tag);
2896
+ this.SelectedTags = this.SelectedTags.filter(t => t !== tag);
2469
2897
  }
2470
2898
  else {
2471
- this.selectedTags = [...this.selectedTags, tag];
2899
+ this.SelectedTags = [...this.SelectedTags, tag];
2472
2900
  }
2473
2901
  }
2474
2902
  // Reload matrix data when tag filter changes (if currently viewing matrix or chart)
2475
- if (this.analyticsView === 'matrix' || this.analyticsView === 'chart') {
2903
+ if (this.AnalyticsView === 'matrix' || this.AnalyticsView === 'chart') {
2476
2904
  this.reloadMatrixData();
2477
2905
  }
2478
2906
  this.cdr.markForCheck();
2479
2907
  }
2908
+ /** @deprecated Use {@link ToggleTagFilter}. */
2909
+ toggleTagFilter(tag) {
2910
+ return this.ToggleTagFilter(tag);
2911
+ }
2480
2912
  /**
2481
2913
  * Check if a tag is currently selected in the filter
2482
2914
  */
2915
+ IsTagSelected(tag) {
2916
+ return this.SelectedTags.includes(tag);
2917
+ }
2918
+ /** @deprecated Use {@link IsTagSelected}. */
2483
2919
  isTagSelected(tag) {
2484
- return this.selectedTags.includes(tag);
2920
+ return this.IsTagSelected(tag);
2485
2921
  }
2486
- setAnalyticsView(view) {
2487
- this.analyticsView = view;
2488
- if ((view === 'matrix' || view === 'chart') && !this.matrixLoaded) {
2922
+ SetAnalyticsView(view) {
2923
+ this.AnalyticsView = view;
2924
+ if ((view === 'matrix' || view === 'chart') && !this.MatrixLoaded) {
2489
2925
  this.loadMatrixData();
2490
2926
  }
2491
2927
  // Render chart when switching to chart view
2492
- if (view === 'chart' && this.matrixLoaded) {
2928
+ if (view === 'chart' && this.MatrixLoaded) {
2493
2929
  setTimeout(() => this.renderChart(), 100);
2494
2930
  }
2495
2931
  this.cdr.markForCheck();
2496
2932
  }
2933
+ /** @deprecated Use {@link SetAnalyticsView}. */
2934
+ setAnalyticsView(view) {
2935
+ return this.SetAnalyticsView(view);
2936
+ }
2497
2937
  /**
2498
2938
  * Force reload of matrix data (used when filters change)
2499
2939
  */
2500
2940
  reloadMatrixData() {
2501
- this.matrixLoaded = false;
2941
+ this.MatrixLoaded = false;
2502
2942
  this.loadMatrixData();
2503
2943
  }
2504
2944
  async loadMatrixData() {
2505
- if (this.loadingMatrix)
2945
+ if (this.LoadingMatrix)
2506
2946
  return;
2507
- this.loadingMatrix = true;
2947
+ this.LoadingMatrix = true;
2508
2948
  this.cdr.markForCheck();
2509
2949
  try {
2510
2950
  const rv = RunView.FromMetadataProvider(this.ProviderToUse);
2511
- const filteredRuns = this.getFilteredAnalyticsData();
2951
+ const filteredRuns = this.GetFilteredAnalyticsData();
2512
2952
  // Load test runs for each suite run (limit to most recent 10 for performance)
2513
2953
  const runsToLoad = filteredRuns.slice(0, 10);
2514
2954
  const matrixData = [];
@@ -2567,10 +3007,10 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2567
3007
  });
2568
3008
  }
2569
3009
  }
2570
- this.matrixData = matrixData;
2571
- this.matrixLoaded = true;
3010
+ this.MatrixData = matrixData;
3011
+ this.MatrixLoaded = true;
2572
3012
  // Render chart if currently on chart view
2573
- if (this.analyticsView === 'chart') {
3013
+ if (this.AnalyticsView === 'chart') {
2574
3014
  setTimeout(() => this.renderChart(), 100);
2575
3015
  }
2576
3016
  }
@@ -2579,7 +3019,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2579
3019
  SharedService.Instance.CreateSimpleNotification('Failed to load matrix data', 'error', 3000);
2580
3020
  }
2581
3021
  finally {
2582
- this.loadingMatrix = false;
3022
+ this.LoadingMatrix = false;
2583
3023
  this.cdr.markForCheck();
2584
3024
  }
2585
3025
  }
@@ -2616,9 +3056,9 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2616
3056
  }
2617
3057
  return feedbackMap;
2618
3058
  }
2619
- getUniqueTestsFromMatrix() {
3059
+ GetUniqueTestsFromMatrix() {
2620
3060
  const testsMap = new Map();
2621
- for (const runData of this.matrixData) {
3061
+ for (const runData of this.MatrixData) {
2622
3062
  for (const [testId, testResult] of runData.testResults) {
2623
3063
  if (!testsMap.has(testId)) {
2624
3064
  testsMap.set(testId, { testName: testResult.testName, sequence: testResult.sequence });
@@ -2631,64 +3071,88 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2631
3071
  sequence: data.sequence
2632
3072
  }));
2633
3073
  // Apply test name filter if set
2634
- if (this.matrixTestFilter.trim()) {
2635
- const filterLower = this.matrixTestFilter.toLowerCase().trim();
3074
+ if (this.MatrixTestFilter.trim()) {
3075
+ const filterLower = this.MatrixTestFilter.toLowerCase().trim();
2636
3076
  tests = tests.filter(t => t.testName.toLowerCase().includes(filterLower));
2637
3077
  }
2638
3078
  // Apply sorting
2639
- if (this.matrixSortBy === 'sequence') {
2640
- tests = tests.sort((a, b) => this.matrixSortAsc ? a.sequence - b.sequence : b.sequence - a.sequence);
3079
+ if (this.MatrixSortBy === 'sequence') {
3080
+ tests = tests.sort((a, b) => this.MatrixSortAsc ? a.sequence - b.sequence : b.sequence - a.sequence);
2641
3081
  }
2642
3082
  else {
2643
3083
  tests = tests.sort((a, b) => {
2644
3084
  const cmp = a.testName.localeCompare(b.testName);
2645
- return this.matrixSortAsc ? cmp : -cmp;
3085
+ return this.MatrixSortAsc ? cmp : -cmp;
2646
3086
  });
2647
3087
  }
2648
3088
  return tests;
2649
3089
  }
3090
+ /** @deprecated Use {@link GetUniqueTestsFromMatrix}. */
3091
+ getUniqueTestsFromMatrix() {
3092
+ return this.GetUniqueTestsFromMatrix();
3093
+ }
2650
3094
  /**
2651
3095
  * Toggle matrix sort column
2652
3096
  */
2653
- toggleMatrixSort(column) {
2654
- if (this.matrixSortBy === column) {
2655
- this.matrixSortAsc = !this.matrixSortAsc;
3097
+ ToggleMatrixSort(column) {
3098
+ if (this.MatrixSortBy === column) {
3099
+ this.MatrixSortAsc = !this.MatrixSortAsc;
2656
3100
  }
2657
3101
  else {
2658
- this.matrixSortBy = column;
2659
- this.matrixSortAsc = true;
3102
+ this.MatrixSortBy = column;
3103
+ this.MatrixSortAsc = true;
2660
3104
  }
2661
3105
  this.cdr.markForCheck();
2662
3106
  }
3107
+ /** @deprecated Use {@link ToggleMatrixSort}. */
3108
+ toggleMatrixSort(column) {
3109
+ return this.ToggleMatrixSort(column);
3110
+ }
2663
3111
  /**
2664
3112
  * Select/deselect a matrix row for highlighting
2665
3113
  */
2666
- selectMatrixRow(testId) {
2667
- this.selectedMatrixTestId = this.selectedMatrixTestId === testId ? null : testId;
3114
+ SelectMatrixRow(testId) {
3115
+ this.SelectedMatrixTestId = this.SelectedMatrixTestId === testId ? null : testId;
2668
3116
  this.cdr.markForCheck();
2669
3117
  }
3118
+ /** @deprecated Use {@link SelectMatrixRow}. */
3119
+ selectMatrixRow(testId) {
3120
+ return this.SelectMatrixRow(testId);
3121
+ }
2670
3122
  /**
2671
3123
  * Handle test name filter input - uses Subject for debounce
2672
3124
  */
2673
- onMatrixFilterInput(event) {
3125
+ OnMatrixFilterInput(event) {
2674
3126
  const value = event.target.value;
2675
3127
  this.matrixFilterSubject$.next(value);
2676
3128
  }
3129
+ /** @deprecated Use {@link OnMatrixFilterInput}. */
3130
+ onMatrixFilterInput(event) {
3131
+ return this.OnMatrixFilterInput(event);
3132
+ }
2677
3133
  /**
2678
3134
  * Clear the matrix test name filter
2679
3135
  */
2680
- clearMatrixFilter() {
2681
- this.matrixTestFilter = '';
3136
+ ClearMatrixFilter() {
3137
+ this.MatrixTestFilter = '';
2682
3138
  this.matrixFilterSubject$.next('');
2683
3139
  this.cdr.markForCheck();
2684
3140
  }
2685
- getTestResultForRun(runId, testId) {
2686
- const runData = this.matrixData.find(r => r.runId === runId);
3141
+ /** @deprecated Use {@link ClearMatrixFilter}. */
3142
+ clearMatrixFilter() {
3143
+ return this.ClearMatrixFilter();
3144
+ }
3145
+ GetTestResultForRun(runId, testId) {
3146
+ const runData = this.MatrixData.find(r => r.runId === runId);
2687
3147
  if (!runData)
2688
3148
  return null;
2689
3149
  return runData.testResults.get(testId) || null;
2690
3150
  }
2691
- getMatrixCellClass(result) {
3151
+ /** @deprecated Use {@link GetTestResultForRun}. */
3152
+ getTestResultForRun(runId, testId) {
3153
+ return this.GetTestResultForRun(runId, testId);
3154
+ }
3155
+ GetMatrixCellClass(result) {
2692
3156
  if (!result)
2693
3157
  return 'cell-none cell-not-run';
2694
3158
  switch (result.status) {
@@ -2701,10 +3165,14 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2701
3165
  default: return 'cell-pending';
2702
3166
  }
2703
3167
  }
3168
+ /** @deprecated Use {@link GetMatrixCellClass}. */
3169
+ getMatrixCellClass(result) {
3170
+ return this.GetMatrixCellClass(result);
3171
+ }
2704
3172
  /**
2705
3173
  * Get descriptive tooltip for execution status
2706
3174
  */
2707
- getStatusTooltip(status) {
3175
+ GetStatusTooltip(status) {
2708
3176
  switch (status) {
2709
3177
  case 'Passed': return 'Status: Passed - Test completed without error';
2710
3178
  case 'Failed': return 'Status: Failed - Test assertions did not pass';
@@ -2716,10 +3184,14 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2716
3184
  default: return `Status: ${status}`;
2717
3185
  }
2718
3186
  }
3187
+ /** @deprecated Use {@link GetStatusTooltip}. */
3188
+ getStatusTooltip(status) {
3189
+ return this.GetStatusTooltip(status);
3190
+ }
2719
3191
  /**
2720
3192
  * Get tooltip for human review with rating and optional comments
2721
3193
  */
2722
- getHumanTooltip(rating, comments) {
3194
+ GetHumanTooltip(rating, comments) {
2723
3195
  let tooltip = `Human Review: ${rating}/10 rating`;
2724
3196
  if (comments) {
2725
3197
  const truncated = comments.length > 200 ? comments.substring(0, 200) + '...' : comments;
@@ -2727,26 +3199,34 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2727
3199
  }
2728
3200
  return tooltip;
2729
3201
  }
3202
+ /** @deprecated Use {@link GetHumanTooltip}. */
3203
+ getHumanTooltip(rating, comments) {
3204
+ return this.GetHumanTooltip(rating, comments);
3205
+ }
2730
3206
  /**
2731
3207
  * Get the count of enabled evaluation types for matrix cell layout
2732
3208
  */
2733
- getEvalCount() {
3209
+ GetEvalCount() {
2734
3210
  let count = 0;
2735
- if (this.evalPreferences.showExecution)
3211
+ if (this.EvalPreferences.showExecution)
2736
3212
  count++;
2737
- if (this.evalPreferences.showHuman)
3213
+ if (this.EvalPreferences.showHuman)
2738
3214
  count++;
2739
- if (this.evalPreferences.showAuto)
3215
+ if (this.EvalPreferences.showAuto)
2740
3216
  count++;
2741
3217
  return count;
2742
3218
  }
3219
+ /** @deprecated Use {@link GetEvalCount}. */
3220
+ getEvalCount() {
3221
+ return this.GetEvalCount();
3222
+ }
2743
3223
  // ===========================
2744
3224
  // Matrix Totals Row Methods
2745
3225
  // ===========================
2746
3226
  /**
2747
3227
  * Get count of passed tests for a run
2748
3228
  */
2749
- getRunPassedCount(run) {
3229
+ GetRunPassedCount(run) {
2750
3230
  let count = 0;
2751
3231
  run.testResults.forEach(result => {
2752
3232
  if (result.status === 'Passed')
@@ -2754,16 +3234,24 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2754
3234
  });
2755
3235
  return count;
2756
3236
  }
3237
+ /** @deprecated Use {@link GetRunPassedCount}. */
3238
+ getRunPassedCount(run) {
3239
+ return this.GetRunPassedCount(run);
3240
+ }
2757
3241
  /**
2758
3242
  * Get total count of tests for a run
2759
3243
  */
2760
- getRunTotalCount(run) {
3244
+ GetRunTotalCount(run) {
2761
3245
  return run.testResults.size;
2762
3246
  }
3247
+ /** @deprecated Use {@link GetRunTotalCount}. */
3248
+ getRunTotalCount(run) {
3249
+ return this.GetRunTotalCount(run);
3250
+ }
2763
3251
  /**
2764
3252
  * Get average human rating for a run (only from tests that have ratings)
2765
3253
  */
2766
- getRunHumanAvg(run) {
3254
+ GetRunHumanAvg(run) {
2767
3255
  let sum = 0;
2768
3256
  let count = 0;
2769
3257
  run.testResults.forEach(result => {
@@ -2774,10 +3262,14 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2774
3262
  });
2775
3263
  return count > 0 ? sum / count : null;
2776
3264
  }
3265
+ /** @deprecated Use {@link GetRunHumanAvg}. */
3266
+ getRunHumanAvg(run) {
3267
+ return this.GetRunHumanAvg(run);
3268
+ }
2777
3269
  /**
2778
3270
  * Get count of tests with human ratings for a run
2779
3271
  */
2780
- getRunHumanCount(run) {
3272
+ GetRunHumanCount(run) {
2781
3273
  let count = 0;
2782
3274
  run.testResults.forEach(result => {
2783
3275
  if (result.humanRating != null)
@@ -2785,10 +3277,14 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2785
3277
  });
2786
3278
  return count;
2787
3279
  }
3280
+ /** @deprecated Use {@link GetRunHumanCount}. */
3281
+ getRunHumanCount(run) {
3282
+ return this.GetRunHumanCount(run);
3283
+ }
2788
3284
  /**
2789
3285
  * Get average auto score for a run (only from tests that have scores)
2790
3286
  */
2791
- getRunAutoAvg(run) {
3287
+ GetRunAutoAvg(run) {
2792
3288
  let sum = 0;
2793
3289
  let count = 0;
2794
3290
  run.testResults.forEach(result => {
@@ -2799,10 +3295,14 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2799
3295
  });
2800
3296
  return count > 0 ? sum / count : null;
2801
3297
  }
3298
+ /** @deprecated Use {@link GetRunAutoAvg}. */
3299
+ getRunAutoAvg(run) {
3300
+ return this.GetRunAutoAvg(run);
3301
+ }
2802
3302
  /**
2803
3303
  * Get count of tests with auto scores for a run
2804
3304
  */
2805
- getRunAutoCount(run) {
3305
+ GetRunAutoCount(run) {
2806
3306
  let count = 0;
2807
3307
  run.testResults.forEach(result => {
2808
3308
  if (result.score != null)
@@ -2810,21 +3310,33 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2810
3310
  });
2811
3311
  return count;
2812
3312
  }
3313
+ /** @deprecated Use {@link GetRunAutoCount}. */
3314
+ getRunAutoCount(run) {
3315
+ return this.GetRunAutoCount(run);
3316
+ }
2813
3317
  /**
2814
3318
  * Navigate to a test run when clicking a matrix cell
2815
3319
  */
2816
- openTestRun(testRunId) {
3320
+ OpenTestRun(testRunId) {
2817
3321
  SharedService.Instance.OpenEntityRecord('MJ: Test Runs', CompositeKey.FromID(testRunId));
2818
3322
  }
3323
+ /** @deprecated Use {@link OpenTestRun}. */
3324
+ openTestRun(testRunId) {
3325
+ return this.OpenTestRun(testRunId);
3326
+ }
2819
3327
  /**
2820
3328
  * Handle matrix cell click - navigate to the test run
2821
3329
  */
2822
- onMatrixCellClick(result, event) {
3330
+ OnMatrixCellClick(result, event) {
2823
3331
  event.stopPropagation(); // Prevent row click from also firing
2824
3332
  if (result?.testRunId) {
2825
- this.openTestRun(result.testRunId);
3333
+ this.OpenTestRun(result.testRunId);
2826
3334
  }
2827
3335
  }
3336
+ /** @deprecated Use {@link OnMatrixCellClick}. */
3337
+ onMatrixCellClick(result, event) {
3338
+ return this.OnMatrixCellClick(result, event);
3339
+ }
2828
3340
  // ===========================
2829
3341
  // D3 Chart Rendering
2830
3342
  // ===========================
@@ -2833,15 +3345,15 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2833
3345
  * with trend lines and better visual hierarchy
2834
3346
  */
2835
3347
  renderChart() {
2836
- if (!this.chartContainer?.nativeElement || this.matrixData.length === 0) {
3348
+ if (!this.ChartContainer?.nativeElement || this.MatrixData.length === 0) {
2837
3349
  return;
2838
3350
  }
2839
- const container = this.chartContainer.nativeElement;
2840
- const tests = this.getUniqueTestsFromMatrix();
2841
- const runs = this.matrixData;
3351
+ const container = this.ChartContainer.nativeElement;
3352
+ const tests = this.GetUniqueTestsFromMatrix();
3353
+ const runs = this.MatrixData;
2842
3354
  // Dynamic sizing based on content and evaluation preferences
2843
3355
  const width = container.clientWidth || 900;
2844
- const evalCount = this.getEvalCount();
3356
+ const evalCount = this.GetEvalCount();
2845
3357
  // Adjust row height and column width based on how many eval types are shown
2846
3358
  const rowHeight = evalCount >= 3 ? 34 : evalCount === 2 ? 30 : 28;
2847
3359
  const minColWidth = evalCount >= 3 ? 65 : evalCount === 2 ? 55 : 50;
@@ -2918,9 +3430,9 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2918
3430
  .attr('fill', '#475569')
2919
3431
  .attr('font-size', '10px')
2920
3432
  .attr('font-weight', '500')
2921
- .text(this.getRelativeTime(run.date))
3433
+ .text(this.GetRelativeTime(run.date))
2922
3434
  .style('cursor', 'pointer')
2923
- .on('click', () => this.openSuiteRun(run.runId));
3435
+ .on('click', () => this.OpenSuiteRun(run.runId));
2924
3436
  // Pass rate badge
2925
3437
  const passRateColor = run.passRate >= 80 ? '#22c55e' : run.passRate >= 50 ? '#f97316' : '#ef4444';
2926
3438
  const badgeWidth = 36;
@@ -2963,7 +3475,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2963
3475
  .attr('stroke', '#93c5fd')
2964
3476
  .attr('stroke-width', 1)
2965
3477
  .style('cursor', 'pointer')
2966
- .on('click', () => this.openSuiteRun(run.runId));
3478
+ .on('click', () => this.OpenSuiteRun(run.runId));
2967
3479
  // Tag text
2968
3480
  chart.append('text')
2969
3481
  .attr('x', tagX + tagPillWidth / 2)
@@ -2974,7 +3486,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
2974
3486
  .attr('font-weight', '600')
2975
3487
  .text(tag.length > 8 ? tag.substring(0, 7) + '…' : tag)
2976
3488
  .style('cursor', 'pointer')
2977
- .on('click', () => this.openSuiteRun(run.runId));
3489
+ .on('click', () => this.OpenSuiteRun(run.runId));
2978
3490
  });
2979
3491
  // Show +N indicator if more tags
2980
3492
  if (run.tags.length > 2) {
@@ -3061,20 +3573,20 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3061
3573
  }
3062
3574
  // Build tooltip content based on evaluation preferences
3063
3575
  const tooltipParts = [];
3064
- if (this.evalPreferences.showExecution) {
3576
+ if (this.EvalPreferences.showExecution) {
3065
3577
  tooltipParts.push(`<div style="display:inline-block; padding:2px 8px; border-radius:4px; background:${statusColors[result.status]}; color:white; font-size:11px; font-weight:600">${result.status}</div>`);
3066
3578
  }
3067
- if (this.evalPreferences.showHuman) {
3579
+ if (this.EvalPreferences.showHuman) {
3068
3580
  tooltipParts.push(`<div style="margin-top:4px"><span style="color:var(--mj-status-warning)">👤</span> <strong>Human:</strong> <span style="color:var(--mj-text-disabled)">Needs review</span></div>`);
3069
3581
  }
3070
- if (this.evalPreferences.showAuto && result.score != null) {
3582
+ if (this.EvalPreferences.showAuto && result.score != null) {
3071
3583
  tooltipParts.push(`<div style="margin-top:4px"><span style="color:var(--mj-status-info)">🤖</span> <strong>Auto:</strong> ${(result.score * 100).toFixed(1)}%</div>`);
3072
3584
  }
3073
3585
  const durationText = result.duration != null ? `<div><strong>Duration:</strong> ${result.duration.toFixed(2)}s</div>` : '';
3074
3586
  const cellGroup = chart.append('g')
3075
3587
  .attr('class', 'result-cell')
3076
3588
  .style('cursor', 'pointer')
3077
- .on('click', () => this.openTestRun(result.testRunId))
3589
+ .on('click', () => this.OpenTestRun(result.testRunId))
3078
3590
  .on('mouseover', (event) => {
3079
3591
  d3.select(event.currentTarget).select('rect.cell-bg')
3080
3592
  .attr('stroke', '#1e40af')
@@ -3085,7 +3597,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3085
3597
  <div style="font-weight:600; margin-bottom:6px; color:#f1f5f9">${result.testName}</div>
3086
3598
  ${tooltipParts.join('')}
3087
3599
  ${durationText}
3088
- <div style="margin-top:6px; color:#94a3b8; font-size:10px">${this.getRelativeTime(run.date)} • Click to view</div>
3600
+ <div style="margin-top:6px; color:#94a3b8; font-size:10px">${this.GetRelativeTime(run.date)} • Click to view</div>
3089
3601
  `)
3090
3602
  .style('left', `${event.offsetX + 15}px`)
3091
3603
  .style('top', `${event.offsetY - 10}px`);
@@ -3099,11 +3611,11 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3099
3611
  // Determine cell background color based on eval preferences
3100
3612
  let cellBgColor = '#f1f5f9';
3101
3613
  let cellBgGradient = '';
3102
- if (this.evalPreferences.showExecution) {
3614
+ if (this.EvalPreferences.showExecution) {
3103
3615
  // Use status-based gradient
3104
3616
  cellBgGradient = `url(#gradient-${result.status.toLowerCase()})`;
3105
3617
  }
3106
- else if (this.evalPreferences.showAuto && result.score != null) {
3618
+ else if (this.EvalPreferences.showAuto && result.score != null) {
3107
3619
  // Use score-based color
3108
3620
  const scoreColor = result.score >= 0.8 ? '#22c55e' : result.score >= 0.5 ? '#f97316' : '#ef4444';
3109
3621
  cellBgColor = scoreColor;
@@ -3131,7 +3643,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3131
3643
  const startX = x + cellWidth / 2 - ((evalCount - 1) * iconSpacing) / 2;
3132
3644
  let iconIndex = 0;
3133
3645
  // Status icon (execution)
3134
- if (this.evalPreferences.showExecution) {
3646
+ if (this.EvalPreferences.showExecution) {
3135
3647
  const iconText = {
3136
3648
  'Passed': '✓',
3137
3649
  'Failed': '✕',
@@ -3162,7 +3674,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3162
3674
  iconIndex++;
3163
3675
  }
3164
3676
  // Human icon
3165
- if (this.evalPreferences.showHuman) {
3677
+ if (this.EvalPreferences.showHuman) {
3166
3678
  const iconX = startX + iconIndex * iconSpacing;
3167
3679
  // Human evaluation indicator (clock icon for pending)
3168
3680
  cellGroup.append('circle')
@@ -3183,7 +3695,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3183
3695
  iconIndex++;
3184
3696
  }
3185
3697
  // Auto score icon/indicator
3186
- if (this.evalPreferences.showAuto) {
3698
+ if (this.EvalPreferences.showAuto) {
3187
3699
  const iconX = startX + iconIndex * iconSpacing;
3188
3700
  if (result.score != null) {
3189
3701
  const scorePercent = Math.round(result.score * 100);
@@ -3315,7 +3827,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3315
3827
  .text('SHOWING:');
3316
3828
  let xOffset = 55;
3317
3829
  // Status indicator
3318
- if (this.evalPreferences.showExecution) {
3830
+ if (this.EvalPreferences.showExecution) {
3319
3831
  const statusGroup = legendGroup.append('g')
3320
3832
  .attr('transform', `translate(${xOffset}, -4)`);
3321
3833
  statusGroup.append('circle')
@@ -3341,7 +3853,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3341
3853
  xOffset += 60;
3342
3854
  }
3343
3855
  // Human indicator
3344
- if (this.evalPreferences.showHuman) {
3856
+ if (this.EvalPreferences.showHuman) {
3345
3857
  const humanGroup = legendGroup.append('g')
3346
3858
  .attr('transform', `translate(${xOffset}, -4)`);
3347
3859
  humanGroup.append('circle')
@@ -3368,7 +3880,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3368
3880
  xOffset += 60;
3369
3881
  }
3370
3882
  // Auto indicator
3371
- if (this.evalPreferences.showAuto) {
3883
+ if (this.EvalPreferences.showAuto) {
3372
3884
  const autoGroup = legendGroup.append('g')
3373
3885
  .attr('transform', `translate(${xOffset}, -4)`);
3374
3886
  autoGroup.append('rect')
@@ -3395,26 +3907,42 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3395
3907
  .text('Auto');
3396
3908
  }
3397
3909
  }
3398
- getAveragePassRate() {
3399
- const data = this.getFilteredAnalyticsData();
3910
+ GetAveragePassRate() {
3911
+ const data = this.GetFilteredAnalyticsData();
3400
3912
  if (data.length === 0)
3401
3913
  return 0;
3402
3914
  return data.reduce((sum, d) => sum + d.passRate, 0) / data.length;
3403
3915
  }
3916
+ /** @deprecated Use {@link GetAveragePassRate}. */
3917
+ getAveragePassRate() {
3918
+ return this.GetAveragePassRate();
3919
+ }
3920
+ GetTotalRuns() {
3921
+ return this.GetFilteredAnalyticsData().length;
3922
+ }
3923
+ /** @deprecated Use {@link GetTotalRuns}. */
3404
3924
  getTotalRuns() {
3405
- return this.getFilteredAnalyticsData().length;
3925
+ return this.GetTotalRuns();
3406
3926
  }
3407
- getAverageDuration() {
3408
- const data = this.getFilteredAnalyticsData();
3927
+ GetAverageDuration() {
3928
+ const data = this.GetFilteredAnalyticsData();
3409
3929
  if (data.length === 0)
3410
3930
  return 0;
3411
3931
  return data.reduce((sum, d) => sum + d.duration, 0) / data.length;
3412
3932
  }
3933
+ /** @deprecated Use {@link GetAverageDuration}. */
3934
+ getAverageDuration() {
3935
+ return this.GetAverageDuration();
3936
+ }
3937
+ GetTotalCost() {
3938
+ return this.GetFilteredAnalyticsData().reduce((sum, d) => sum + d.cost, 0);
3939
+ }
3940
+ /** @deprecated Use {@link GetTotalCost}. */
3413
3941
  getTotalCost() {
3414
- return this.getFilteredAnalyticsData().reduce((sum, d) => sum + d.cost, 0);
3942
+ return this.GetTotalCost();
3415
3943
  }
3416
- getPassRateTrend() {
3417
- const data = this.getFilteredAnalyticsData();
3944
+ GetPassRateTrend() {
3945
+ const data = this.GetFilteredAnalyticsData();
3418
3946
  if (data.length < 2)
3419
3947
  return { direction: 'stable', value: 0 };
3420
3948
  // Compare recent half to older half
@@ -3428,6 +3956,10 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3428
3956
  return { direction: 'stable', value: diff };
3429
3957
  return { direction: diff > 0 ? 'up' : 'down', value: Math.abs(diff) };
3430
3958
  }
3959
+ /** @deprecated Use {@link GetPassRateTrend}. */
3960
+ getPassRateTrend() {
3961
+ return this.GetPassRateTrend();
3962
+ }
3431
3963
  formatDuration(seconds) {
3432
3964
  if (seconds < 60)
3433
3965
  return `${seconds.toFixed(1)}s`;
@@ -3440,39 +3972,55 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3440
3972
  const mins = Math.floor((seconds % 3600) / 60);
3441
3973
  return mins > 0 ? `${hours}h ${mins}m` : `${hours}h`;
3442
3974
  }
3443
- formatCost(cost) {
3975
+ FormatCost(cost) {
3444
3976
  if (cost < 0.01)
3445
3977
  return `$${cost.toFixed(6)}`;
3446
3978
  if (cost < 1)
3447
3979
  return `$${cost.toFixed(4)}`;
3448
3980
  return `$${cost.toFixed(2)}`;
3449
3981
  }
3982
+ /** @deprecated Use {@link FormatCost}. */
3983
+ formatCost(cost) {
3984
+ return this.FormatCost(cost);
3985
+ }
3450
3986
  // ==========================================
3451
3987
  // Compare Tab Methods
3452
3988
  // ==========================================
3989
+ async SelectCompareRunA(run) {
3990
+ this.CompareRunA = run;
3991
+ await this.loadCompareData();
3992
+ }
3993
+ /** @deprecated Use {@link SelectCompareRunA}. */
3453
3994
  async selectCompareRunA(run) {
3454
- this.compareRunA = run;
3995
+ return this.SelectCompareRunA(run);
3996
+ }
3997
+ async SelectCompareRunB(run) {
3998
+ this.CompareRunB = run;
3455
3999
  await this.loadCompareData();
3456
4000
  }
4001
+ /** @deprecated Use {@link SelectCompareRunB}. */
3457
4002
  async selectCompareRunB(run) {
3458
- this.compareRunB = run;
3459
- await this.loadCompareData();
4003
+ return this.SelectCompareRunB(run);
3460
4004
  }
3461
- clearCompareSelection() {
3462
- this.compareRunA = null;
3463
- this.compareRunB = null;
3464
- this.compareResults = [];
3465
- this.compareRunATests = [];
3466
- this.compareRunBTests = [];
4005
+ ClearCompareSelection() {
4006
+ this.CompareRunA = null;
4007
+ this.CompareRunB = null;
4008
+ this.CompareResults = [];
4009
+ this.CompareRunATests = [];
4010
+ this.CompareRunBTests = [];
3467
4011
  this.cdr.markForCheck();
3468
4012
  }
4013
+ /** @deprecated Use {@link ClearCompareSelection}. */
4014
+ clearCompareSelection() {
4015
+ return this.ClearCompareSelection();
4016
+ }
3469
4017
  async loadCompareData() {
3470
- if (!this.compareRunA || !this.compareRunB) {
3471
- this.compareResults = [];
4018
+ if (!this.CompareRunA || !this.CompareRunB) {
4019
+ this.CompareResults = [];
3472
4020
  this.cdr.markForCheck();
3473
4021
  return;
3474
4022
  }
3475
- this.loadingCompare = true;
4023
+ this.LoadingCompare = true;
3476
4024
  this.cdr.markForCheck();
3477
4025
  try {
3478
4026
  const rv = RunView.FromMetadataProvider(this.ProviderToUse);
@@ -3480,28 +4028,28 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3480
4028
  const [resultA, resultB] = await Promise.all([
3481
4029
  rv.RunView({
3482
4030
  EntityName: 'MJ: Test Runs',
3483
- ExtraFilter: `TestSuiteRunID='${this.compareRunA.ID}'`,
4031
+ ExtraFilter: `TestSuiteRunID='${this.CompareRunA.ID}'`,
3484
4032
  OrderBy: 'Sequence ASC',
3485
4033
  ResultType: 'entity_object'
3486
4034
  }),
3487
4035
  rv.RunView({
3488
4036
  EntityName: 'MJ: Test Runs',
3489
- ExtraFilter: `TestSuiteRunID='${this.compareRunB.ID}'`,
4037
+ ExtraFilter: `TestSuiteRunID='${this.CompareRunB.ID}'`,
3490
4038
  OrderBy: 'Sequence ASC',
3491
4039
  ResultType: 'entity_object'
3492
4040
  })
3493
4041
  ]);
3494
- this.compareRunATests = resultA.Success ? resultA.Results || [] : [];
3495
- this.compareRunBTests = resultB.Success ? resultB.Results || [] : [];
4042
+ this.CompareRunATests = resultA.Success ? resultA.Results || [] : [];
4043
+ this.CompareRunBTests = resultB.Success ? resultB.Results || [] : [];
3496
4044
  // Build comparison results
3497
- this.compareResults = this.buildComparisonResults(this.compareRunATests, this.compareRunBTests);
4045
+ this.CompareResults = this.buildComparisonResults(this.CompareRunATests, this.CompareRunBTests);
3498
4046
  }
3499
4047
  catch (error) {
3500
4048
  console.error('Error loading comparison data:', error);
3501
4049
  SharedService.Instance.CreateSimpleNotification('Failed to load comparison data', 'error', 3000);
3502
4050
  }
3503
4051
  finally {
3504
- this.loadingCompare = false;
4052
+ this.LoadingCompare = false;
3505
4053
  this.cdr.markForCheck();
3506
4054
  }
3507
4055
  }
@@ -3547,48 +4095,72 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3547
4095
  }
3548
4096
  return results;
3549
4097
  }
3550
- getComparePassRateDiff() {
3551
- if (!this.compareRunA || !this.compareRunB)
4098
+ GetComparePassRateDiff() {
4099
+ if (!this.CompareRunA || !this.CompareRunB)
3552
4100
  return null;
3553
- const rateA = this.getPassRate(this.compareRunA);
3554
- const rateB = this.getPassRate(this.compareRunB);
4101
+ const rateA = this.GetPassRate(this.CompareRunA);
4102
+ const rateB = this.GetPassRate(this.CompareRunB);
3555
4103
  return rateB - rateA;
3556
4104
  }
3557
- getCompareDurationDiff() {
3558
- if (!this.compareRunA || !this.compareRunB)
4105
+ /** @deprecated Use {@link GetComparePassRateDiff}. */
4106
+ getComparePassRateDiff() {
4107
+ return this.GetComparePassRateDiff();
4108
+ }
4109
+ GetCompareDurationDiff() {
4110
+ if (!this.CompareRunA || !this.CompareRunB)
3559
4111
  return null;
3560
- const durA = this.compareRunA.TotalDurationSeconds || 0;
3561
- const durB = this.compareRunB.TotalDurationSeconds || 0;
4112
+ const durA = this.CompareRunA.TotalDurationSeconds || 0;
4113
+ const durB = this.CompareRunB.TotalDurationSeconds || 0;
3562
4114
  return durB - durA;
3563
4115
  }
3564
- getAbsCompareDurationDiff() {
3565
- const diff = this.getCompareDurationDiff();
4116
+ /** @deprecated Use {@link GetCompareDurationDiff}. */
4117
+ getCompareDurationDiff() {
4118
+ return this.GetCompareDurationDiff();
4119
+ }
4120
+ GetAbsCompareDurationDiff() {
4121
+ const diff = this.GetCompareDurationDiff();
3566
4122
  return diff != null ? Math.abs(diff) : 0;
3567
4123
  }
3568
- getCompareCostDiff() {
3569
- if (!this.compareRunA || !this.compareRunB)
4124
+ /** @deprecated Use {@link GetAbsCompareDurationDiff}. */
4125
+ getAbsCompareDurationDiff() {
4126
+ return this.GetAbsCompareDurationDiff();
4127
+ }
4128
+ GetCompareCostDiff() {
4129
+ if (!this.CompareRunA || !this.CompareRunB)
3570
4130
  return null;
3571
- const costA = this.compareRunA.TotalCostUSD || 0;
3572
- const costB = this.compareRunB.TotalCostUSD || 0;
4131
+ const costA = this.CompareRunA.TotalCostUSD || 0;
4132
+ const costB = this.CompareRunB.TotalCostUSD || 0;
3573
4133
  return costB - costA;
3574
4134
  }
3575
- getCompareImprovedCount() {
3576
- return this.compareResults.filter(r => r.runA && r.runB &&
4135
+ /** @deprecated Use {@link GetCompareCostDiff}. */
4136
+ getCompareCostDiff() {
4137
+ return this.GetCompareCostDiff();
4138
+ }
4139
+ GetCompareImprovedCount() {
4140
+ return this.CompareResults.filter(r => r.runA && r.runB &&
3577
4141
  r.runA.status !== 'Passed' && r.runB.status === 'Passed').length;
3578
4142
  }
3579
- getCompareRegressedCount() {
3580
- return this.compareResults.filter(r => r.runA && r.runB &&
4143
+ /** @deprecated Use {@link GetCompareImprovedCount}. */
4144
+ getCompareImprovedCount() {
4145
+ return this.GetCompareImprovedCount();
4146
+ }
4147
+ GetCompareRegressedCount() {
4148
+ return this.CompareResults.filter(r => r.runA && r.runB &&
3581
4149
  r.runA.status === 'Passed' && r.runB.status !== 'Passed').length;
3582
4150
  }
4151
+ /** @deprecated Use {@link GetCompareRegressedCount}. */
4152
+ getCompareRegressedCount() {
4153
+ return this.GetCompareRegressedCount();
4154
+ }
3583
4155
  // ==========================================
3584
4156
  // Excel Export Methods
3585
4157
  // ==========================================
3586
- async exportToExcel() {
4158
+ async ExportToExcel() {
3587
4159
  try {
3588
4160
  // Ensure runs are loaded
3589
- if (!this.runsLoaded)
4161
+ if (!this.RunsLoaded)
3590
4162
  await this.loadRuns();
3591
- const data = this.suiteRuns.map(run => ({
4163
+ const data = this.SuiteRuns.map(run => ({
3592
4164
  'Run ID': run.ID,
3593
4165
  'Status': run.Status,
3594
4166
  'Started At': run.StartedAt ? new Date(run.StartedAt).toLocaleString() : 'N/A',
@@ -3614,6 +4186,10 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3614
4186
  SharedService.Instance.CreateSimpleNotification('Export failed', 'error', 3000);
3615
4187
  }
3616
4188
  }
4189
+ /** @deprecated Use {@link ExportToExcel}. */
4190
+ async exportToExcel() {
4191
+ return this.ExportToExcel();
4192
+ }
3617
4193
  downloadAsCSV(data, filename) {
3618
4194
  if (data.length === 0)
3619
4195
  return;
@@ -3637,20 +4213,24 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3637
4213
  link.click();
3638
4214
  URL.revokeObjectURL(link.href);
3639
4215
  }
3640
- getRunTags(run) {
4216
+ GetRunTags(run) {
3641
4217
  return TagsHelper.parseTags(run.Tags);
3642
4218
  }
4219
+ /** @deprecated Use {@link GetRunTags}. */
4220
+ getRunTags(run) {
4221
+ return this.GetRunTags(run);
4222
+ }
3643
4223
  /**
3644
4224
  * Export matrix view data to CSV
3645
4225
  */
3646
- exportMatrixToCSV() {
3647
- if (this.matrixData.length === 0) {
4226
+ ExportMatrixToCSV() {
4227
+ if (this.MatrixData.length === 0) {
3648
4228
  SharedService.Instance.CreateSimpleNotification('No data to export', 'warning', 2000);
3649
4229
  return;
3650
4230
  }
3651
4231
  try {
3652
- const tests = this.getUniqueTestsFromMatrix();
3653
- const runs = this.matrixData;
4232
+ const tests = this.GetUniqueTestsFromMatrix();
4233
+ const runs = this.MatrixData;
3654
4234
  // Build CSV rows
3655
4235
  const rows = [];
3656
4236
  for (const test of tests) {
@@ -3662,16 +4242,16 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3662
4242
  for (const run of runs) {
3663
4243
  const result = run.testResults.get(test.testId);
3664
4244
  const runLabel = run.tags.length > 0
3665
- ? `${run.tags.slice(0, 2).join('/')} (${this.getRelativeTime(run.date)})`
3666
- : this.getRelativeTime(run.date);
4245
+ ? `${run.tags.slice(0, 2).join('/')} (${this.GetRelativeTime(run.date)})`
4246
+ : this.GetRelativeTime(run.date);
3667
4247
  if (result) {
3668
4248
  // Build cell value based on what's shown
3669
4249
  const parts = [];
3670
- if (this.evalPreferences.showExecution)
4250
+ if (this.EvalPreferences.showExecution)
3671
4251
  parts.push(result.status);
3672
- if (this.evalPreferences.showHuman)
4252
+ if (this.EvalPreferences.showHuman)
3673
4253
  parts.push(result.humanRating != null ? `H:${result.humanRating}` : 'H:-');
3674
- if (this.evalPreferences.showAuto)
4254
+ if (this.EvalPreferences.showAuto)
3675
4255
  parts.push(result.score != null ? `A:${Math.round(result.score * 100)}%` : 'A:-');
3676
4256
  row[runLabel] = parts.join(' | ');
3677
4257
  }
@@ -3689,6 +4269,10 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3689
4269
  SharedService.Instance.CreateSimpleNotification('Export failed', 'error', 3000);
3690
4270
  }
3691
4271
  }
4272
+ /** @deprecated Use {@link ExportMatrixToCSV}. */
4273
+ exportMatrixToCSV() {
4274
+ return this.ExportMatrixToCSV();
4275
+ }
3692
4276
  // ==========================================
3693
4277
  // Keyboard Shortcuts Settings
3694
4278
  // ==========================================
@@ -3701,11 +4285,11 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3701
4285
  const setting = engine.UserSettings.find(s => s.Setting === SHORTCUTS_SETTINGS_KEY);
3702
4286
  if (setting) {
3703
4287
  this.shortcutsSettingEntity = setting;
3704
- this.showShortcuts = setting.Value === 'true';
4288
+ this.ShowShortcuts = setting.Value === 'true';
3705
4289
  }
3706
4290
  else {
3707
4291
  // Default to hidden
3708
- this.showShortcuts = false;
4292
+ this.ShowShortcuts = false;
3709
4293
  }
3710
4294
  this.cdr.markForCheck();
3711
4295
  }
@@ -3716,24 +4300,32 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3716
4300
  /**
3717
4301
  * Toggle analytics filters visibility
3718
4302
  */
3719
- toggleFilters() {
3720
- this.filtersCollapsed = !this.filtersCollapsed;
4303
+ ToggleFilters() {
4304
+ this.FiltersCollapsed = !this.FiltersCollapsed;
3721
4305
  this.cdr.markForCheck();
3722
4306
  }
4307
+ /** @deprecated Use {@link ToggleFilters}. */
4308
+ toggleFilters() {
4309
+ return this.ToggleFilters();
4310
+ }
3723
4311
  /**
3724
4312
  * Applies the expanded/collapsed state emitted by the filters mj-accordion-panel.
3725
4313
  * The panel's Expanded is the inverse of filtersCollapsed; set (not flip) so it
3726
4314
  * stays in sync, and mark for check (OnPush change detection).
3727
4315
  */
3728
- onFiltersExpandedChange(expanded) {
3729
- this.filtersCollapsed = !expanded;
4316
+ OnFiltersExpandedChange(expanded) {
4317
+ this.FiltersCollapsed = !expanded;
3730
4318
  this.cdr.markForCheck();
3731
4319
  }
4320
+ /** @deprecated Use {@link OnFiltersExpandedChange}. */
4321
+ onFiltersExpandedChange(expanded) {
4322
+ return this.OnFiltersExpandedChange(expanded);
4323
+ }
3732
4324
  /**
3733
4325
  * Toggle keyboard shortcuts visibility and save preference
3734
4326
  */
3735
- async toggleShortcuts() {
3736
- this.showShortcuts = !this.showShortcuts;
4327
+ async ToggleShortcuts() {
4328
+ this.ShowShortcuts = !this.ShowShortcuts;
3737
4329
  this.cdr.markForCheck();
3738
4330
  try {
3739
4331
  const userId = this.metadata.CurrentUser?.ID;
@@ -3751,34 +4343,46 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3751
4343
  this.shortcutsSettingEntity.Setting = SHORTCUTS_SETTINGS_KEY;
3752
4344
  }
3753
4345
  }
3754
- this.shortcutsSettingEntity.Value = this.showShortcuts ? 'true' : 'false';
4346
+ this.shortcutsSettingEntity.Value = this.ShowShortcuts ? 'true' : 'false';
3755
4347
  await this.shortcutsSettingEntity.Save();
3756
4348
  }
3757
4349
  catch (error) {
3758
4350
  console.warn('Failed to save shortcuts setting:', error);
3759
4351
  }
3760
4352
  }
4353
+ /** @deprecated Use {@link ToggleShortcuts}. */
4354
+ async toggleShortcuts() {
4355
+ return this.ToggleShortcuts();
4356
+ }
3761
4357
  /** Case-insensitive UUID check whether a run is the currently selected Run A (baseline). */
3762
4358
  IsCompareRunA(run) {
3763
- return UUIDsEqual(this.compareRunA?.ID, run.ID);
4359
+ return UUIDsEqual(this.CompareRunA?.ID, run.ID);
3764
4360
  }
3765
4361
  /** Case-insensitive UUID check whether a run is the currently selected Run B (compare). */
3766
4362
  IsCompareRunB(run) {
3767
- return UUIDsEqual(this.compareRunB?.ID, run.ID);
4363
+ return UUIDsEqual(this.CompareRunB?.ID, run.ID);
3768
4364
  }
3769
4365
  // ==========================================
3770
4366
  // Edit / Save Methods
3771
4367
  // ==========================================
3772
4368
  /** True if the suite record has any unsaved field changes. */
3773
- get isDirty() {
4369
+ get IsDirty() {
3774
4370
  return this.record?.Dirty === true;
3775
4371
  }
4372
+ /** @deprecated Use {@link IsDirty}. */
4373
+ get isDirty() {
4374
+ return this.IsDirty;
4375
+ }
3776
4376
  /** Names of fields with pending edits, used by the save bar. */
3777
- get dirtyFieldNames() {
4377
+ get DirtyFieldNames() {
3778
4378
  if (!this.record?.Fields)
3779
4379
  return [];
3780
4380
  return this.record.Fields.filter(f => f.Dirty).map(f => f.Name);
3781
4381
  }
4382
+ /** @deprecated Use {@link DirtyFieldNames}. */
4383
+ get dirtyFieldNames() {
4384
+ return this.DirtyFieldNames;
4385
+ }
3782
4386
  /** Parsed tags as a plain array — derived from record.Tags JSON. */
3783
4387
  get tags() {
3784
4388
  return TagsHelper.parseTags(this.record?.Tags);
@@ -3797,7 +4401,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3797
4401
  ResultType: 'entity_object'
3798
4402
  });
3799
4403
  if (result.Success) {
3800
- this.parentSuiteOptions = result.Results || [];
4404
+ this.ParentSuiteOptions = result.Results || [];
3801
4405
  this.cdr.markForCheck();
3802
4406
  }
3803
4407
  }
@@ -3806,10 +4410,10 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3806
4410
  }
3807
4411
  }
3808
4412
  /** Save all pending changes on the suite record. */
3809
- async saveChanges() {
3810
- if (!this.isDirty || this.isSaving)
4413
+ async SaveChanges() {
4414
+ if (!this.IsDirty || this.IsSaving)
3811
4415
  return;
3812
- this.isSaving = true;
4416
+ this.IsSaving = true;
3813
4417
  this.cdr.markForCheck();
3814
4418
  try {
3815
4419
  const ok = await this.SaveRecord(false);
@@ -3826,23 +4430,31 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3826
4430
  SharedService.Instance.CreateSimpleNotification(msg, 'error', 4000);
3827
4431
  }
3828
4432
  finally {
3829
- this.isSaving = false;
4433
+ this.IsSaving = false;
3830
4434
  this.cdr.markForCheck();
3831
4435
  }
3832
4436
  }
4437
+ /** @deprecated Use {@link SaveChanges}. */
4438
+ async saveChanges() {
4439
+ return this.SaveChanges();
4440
+ }
3833
4441
  /** Discard all pending field changes on the suite. */
3834
- async discardChanges() {
3835
- if (!this.isDirty)
4442
+ async DiscardChanges() {
4443
+ if (!this.IsDirty)
3836
4444
  return;
3837
4445
  if (!(await this.confirmService.Confirm('Discard your unsaved changes?')))
3838
4446
  return;
3839
4447
  this.record.Revert();
3840
- this.tagDraft = '';
4448
+ this.TagDraft = '';
3841
4449
  this.cdr.markForCheck();
3842
4450
  }
4451
+ /** @deprecated Use {@link DiscardChanges}. */
4452
+ async discardChanges() {
4453
+ return this.DiscardChanges();
4454
+ }
3843
4455
  /** Add a tag from tagDraft (called on Enter / comma / blur). */
3844
- addTagFromDraft() {
3845
- const raw = this.tagDraft.trim();
4456
+ AddTagFromDraft() {
4457
+ const raw = this.TagDraft.trim();
3846
4458
  if (!raw)
3847
4459
  return;
3848
4460
  // Allow comma-separated entry: "alpha, beta" → two tags
@@ -3854,49 +4466,61 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3854
4466
  merged.push(t);
3855
4467
  }
3856
4468
  this.record.Tags = TagsHelper.toJson(merged);
3857
- this.tagDraft = '';
4469
+ this.TagDraft = '';
3858
4470
  this.cdr.markForCheck();
3859
4471
  }
4472
+ /** @deprecated Use {@link AddTagFromDraft}. */
4473
+ addTagFromDraft() {
4474
+ return this.AddTagFromDraft();
4475
+ }
3860
4476
  /** Remove a single tag. */
3861
- removeTag(tag) {
4477
+ RemoveTag(tag) {
3862
4478
  this.record.Tags = TagsHelper.removeTag(this.record.Tags, tag);
3863
4479
  this.cdr.markForCheck();
3864
4480
  }
4481
+ /** @deprecated Use {@link RemoveTag}. */
4482
+ removeTag(tag) {
4483
+ return this.RemoveTag(tag);
4484
+ }
3865
4485
  /** Handle Enter / comma in the tag input to commit the draft tag. */
3866
- onTagInputKeydown(event) {
4486
+ OnTagInputKeydown(event) {
3867
4487
  if (event.key === 'Enter' || event.key === ',') {
3868
4488
  event.preventDefault();
3869
- this.addTagFromDraft();
4489
+ this.AddTagFromDraft();
3870
4490
  return;
3871
4491
  }
3872
4492
  // Backspace on an empty draft pops the last chip
3873
- if (event.key === 'Backspace' && !this.tagDraft) {
4493
+ if (event.key === 'Backspace' && !this.TagDraft) {
3874
4494
  const all = this.tags;
3875
4495
  if (all.length > 0) {
3876
4496
  event.preventDefault();
3877
- this.removeTag(all[all.length - 1]);
4497
+ this.RemoveTag(all[all.length - 1]);
3878
4498
  }
3879
4499
  }
3880
4500
  }
4501
+ /** @deprecated Use {@link OnTagInputKeydown}. */
4502
+ onTagInputKeydown(event) {
4503
+ return this.OnTagInputKeydown(event);
4504
+ }
3881
4505
  // ==========================================
3882
4506
  // Suite Membership: Add / Remove / Reorder Tests
3883
4507
  // ==========================================
3884
4508
  /** Empty-state message shown when the add-tests search matches nothing. */
3885
4509
  get AddTestsNoMatchMessage() {
3886
- return `No tests match "${this.addTestsSearch}".`;
4510
+ return `No tests match "${this.AddTestsSearch}".`;
3887
4511
  }
3888
4512
  /** Open the picker dialog and load tests not yet in this suite. */
3889
- async openAddTestsDialog() {
3890
- this.showAddTestsDialog = true;
3891
- this.selectedTestIdsToAdd = new Set();
3892
- this.addTestsSearch = '';
3893
- this.loadingAvailableTests = true;
4513
+ async OpenAddTestsDialog() {
4514
+ this.ShowAddTestsDialog = true;
4515
+ this.SelectedTestIdsToAdd = new Set();
4516
+ this.AddTestsSearch = '';
4517
+ this.LoadingAvailableTests = true;
3894
4518
  this.cdr.markForCheck();
3895
4519
  try {
3896
- if (!this.testsLoaded) {
4520
+ if (!this.TestsLoaded) {
3897
4521
  await this.loadTests();
3898
4522
  }
3899
- const existingTestIds = new Set(this.suiteTests.map(st => st.TestID));
4523
+ const existingTestIds = new Set(this.SuiteTests.map(st => st.TestID));
3900
4524
  const rv = RunView.FromMetadataProvider(this.ProviderToUse);
3901
4525
  const result = await rv.RunView({
3902
4526
  EntityName: 'MJ: Tests',
@@ -3904,7 +4528,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3904
4528
  ResultType: 'entity_object'
3905
4529
  });
3906
4530
  if (result.Success) {
3907
- this.availableTests = (result.Results || []).filter(t => !existingTestIds.has(t.ID));
4531
+ this.AvailableTests = (result.Results || []).filter(t => !existingTestIds.has(t.ID));
3908
4532
  }
3909
4533
  else {
3910
4534
  SharedService.Instance.CreateSimpleNotification('Failed to load tests', 'error', 3000);
@@ -3915,65 +4539,93 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
3915
4539
  SharedService.Instance.CreateSimpleNotification('Failed to load tests', 'error', 3000);
3916
4540
  }
3917
4541
  finally {
3918
- this.loadingAvailableTests = false;
4542
+ this.LoadingAvailableTests = false;
3919
4543
  this.cdr.markForCheck();
3920
4544
  }
3921
4545
  }
3922
- closeAddTestsDialog() {
3923
- if (this.isAddingTests)
4546
+ /** @deprecated Use {@link OpenAddTestsDialog}. */
4547
+ async openAddTestsDialog() {
4548
+ return this.OpenAddTestsDialog();
4549
+ }
4550
+ CloseAddTestsDialog() {
4551
+ if (this.IsAddingTests)
3924
4552
  return;
3925
- this.showAddTestsDialog = false;
3926
- this.selectedTestIdsToAdd = new Set();
3927
- this.addTestsSearch = '';
4553
+ this.ShowAddTestsDialog = false;
4554
+ this.SelectedTestIdsToAdd = new Set();
4555
+ this.AddTestsSearch = '';
3928
4556
  this.cdr.markForCheck();
3929
4557
  }
3930
- toggleAddSelection(testId) {
3931
- if (this.selectedTestIdsToAdd.has(testId)) {
3932
- this.selectedTestIdsToAdd.delete(testId);
4558
+ /** @deprecated Use {@link CloseAddTestsDialog}. */
4559
+ closeAddTestsDialog() {
4560
+ return this.CloseAddTestsDialog();
4561
+ }
4562
+ ToggleAddSelection(testId) {
4563
+ if (this.SelectedTestIdsToAdd.has(testId)) {
4564
+ this.SelectedTestIdsToAdd.delete(testId);
3933
4565
  }
3934
4566
  else {
3935
- this.selectedTestIdsToAdd.add(testId);
4567
+ this.SelectedTestIdsToAdd.add(testId);
3936
4568
  }
3937
4569
  // Set mutation alone doesn't trigger OnPush — assign a new reference
3938
- this.selectedTestIdsToAdd = new Set(this.selectedTestIdsToAdd);
4570
+ this.SelectedTestIdsToAdd = new Set(this.SelectedTestIdsToAdd);
3939
4571
  this.cdr.markForCheck();
3940
4572
  }
4573
+ /** @deprecated Use {@link ToggleAddSelection}. */
4574
+ toggleAddSelection(testId) {
4575
+ return this.ToggleAddSelection(testId);
4576
+ }
4577
+ IsAddSelected(testId) {
4578
+ return this.SelectedTestIdsToAdd.has(testId);
4579
+ }
4580
+ /** @deprecated Use {@link IsAddSelected}. */
3941
4581
  isAddSelected(testId) {
3942
- return this.selectedTestIdsToAdd.has(testId);
4582
+ return this.IsAddSelected(testId);
3943
4583
  }
3944
4584
  /** Tests that match the picker search filter. */
3945
- get filteredAvailableTests() {
3946
- const q = this.addTestsSearch.trim().toLowerCase();
4585
+ get FilteredAvailableTests() {
4586
+ const q = this.AddTestsSearch.trim().toLowerCase();
3947
4587
  if (!q)
3948
- return this.availableTests;
3949
- return this.availableTests.filter(t => (t.Name || '').toLowerCase().includes(q) ||
4588
+ return this.AvailableTests;
4589
+ return this.AvailableTests.filter(t => (t.Name || '').toLowerCase().includes(q) ||
3950
4590
  (t.Description || '').toLowerCase().includes(q) ||
3951
4591
  (t.Type || '').toLowerCase().includes(q));
3952
4592
  }
3953
- selectAllFiltered() {
3954
- for (const t of this.filteredAvailableTests) {
3955
- this.selectedTestIdsToAdd.add(t.ID);
4593
+ /** @deprecated Use {@link FilteredAvailableTests}. */
4594
+ get filteredAvailableTests() {
4595
+ return this.FilteredAvailableTests;
4596
+ }
4597
+ SelectAllFiltered() {
4598
+ for (const t of this.FilteredAvailableTests) {
4599
+ this.SelectedTestIdsToAdd.add(t.ID);
3956
4600
  }
3957
- this.selectedTestIdsToAdd = new Set(this.selectedTestIdsToAdd);
4601
+ this.SelectedTestIdsToAdd = new Set(this.SelectedTestIdsToAdd);
3958
4602
  this.cdr.markForCheck();
3959
4603
  }
3960
- clearAddSelection() {
3961
- this.selectedTestIdsToAdd = new Set();
4604
+ /** @deprecated Use {@link SelectAllFiltered}. */
4605
+ selectAllFiltered() {
4606
+ return this.SelectAllFiltered();
4607
+ }
4608
+ ClearAddSelection() {
4609
+ this.SelectedTestIdsToAdd = new Set();
3962
4610
  this.cdr.markForCheck();
3963
4611
  }
4612
+ /** @deprecated Use {@link ClearAddSelection}. */
4613
+ clearAddSelection() {
4614
+ return this.ClearAddSelection();
4615
+ }
3964
4616
  /** Create suite-test rows for every selected test, appended after existing ones. */
3965
- async confirmAddTests() {
3966
- if (this.selectedTestIdsToAdd.size === 0 || this.isAddingTests)
4617
+ async ConfirmAddTests() {
4618
+ if (this.SelectedTestIdsToAdd.size === 0 || this.IsAddingTests)
3967
4619
  return;
3968
- this.isAddingTests = true;
4620
+ this.IsAddingTests = true;
3969
4621
  this.cdr.markForCheck();
3970
4622
  const provider = this.ProviderToUse;
3971
- const startSequence = this.suiteTests.reduce((max, st) => Math.max(max, st.Sequence ?? 0), 0);
3972
- const idsToAdd = Array.from(this.selectedTestIdsToAdd);
4623
+ const startSequence = this.SuiteTests.reduce((max, st) => Math.max(max, st.Sequence ?? 0), 0);
4624
+ const idsToAdd = Array.from(this.SelectedTestIdsToAdd);
3973
4625
  // Snapshot test names from the picker so we can hydrate the joined "Test"
3974
4626
  // column on the optimistic rows (BaseEntity's joined view fields aren't
3975
4627
  // always populated through a TransactionGroup save).
3976
- const testNameById = new Map(this.availableTests.map(t => [t.ID, t.Name]));
4628
+ const testNameById = new Map(this.AvailableTests.map(t => [t.ID, t.Name]));
3977
4629
  try {
3978
4630
  const tg = await provider.CreateTransactionGroup();
3979
4631
  const newRows = [];
@@ -4002,12 +4654,12 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
4002
4654
  }
4003
4655
  }
4004
4656
  }
4005
- this.suiteTests = [...this.suiteTests, ...newRows];
4657
+ this.SuiteTests = [...this.SuiteTests, ...newRows];
4006
4658
  this.cdr.markForCheck();
4007
4659
  SharedService.Instance.CreateSimpleNotification(idsToAdd.length === 1 ? 'Test added to suite' : `${idsToAdd.length} tests added to suite`, 'success', 2000);
4008
- this.showAddTestsDialog = false;
4009
- this.selectedTestIdsToAdd = new Set();
4010
- this.addTestsSearch = '';
4660
+ this.ShowAddTestsDialog = false;
4661
+ this.SelectedTestIdsToAdd = new Set();
4662
+ this.AddTestsSearch = '';
4011
4663
  }
4012
4664
  else {
4013
4665
  const failed = newRows.find(r => r.LatestResult && !r.LatestResult.Success);
@@ -4020,35 +4672,47 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
4020
4672
  SharedService.Instance.CreateSimpleNotification(msg, 'error', 4000);
4021
4673
  }
4022
4674
  finally {
4023
- this.isAddingTests = false;
4675
+ this.IsAddingTests = false;
4024
4676
  this.cdr.markForCheck();
4025
4677
  }
4026
4678
  }
4679
+ /** @deprecated Use {@link ConfirmAddTests}. */
4680
+ async confirmAddTests() {
4681
+ return this.ConfirmAddTests();
4682
+ }
4027
4683
  /** Show the inline remove-confirm for one suite-test row. */
4028
- requestRemoveTest(suiteTest, event) {
4684
+ RequestRemoveTest(suiteTest, event) {
4029
4685
  if (event)
4030
4686
  event.stopPropagation();
4031
- this.confirmingRemoveSuiteTestId = suiteTest.ID;
4687
+ this.ConfirmingRemoveSuiteTestId = suiteTest.ID;
4032
4688
  this.cdr.markForCheck();
4033
4689
  }
4034
- cancelRemoveTest(event) {
4690
+ /** @deprecated Use {@link RequestRemoveTest}. */
4691
+ requestRemoveTest(suiteTest, event) {
4692
+ return this.RequestRemoveTest(suiteTest, event);
4693
+ }
4694
+ CancelRemoveTest(event) {
4035
4695
  if (event)
4036
4696
  event.stopPropagation();
4037
- this.confirmingRemoveSuiteTestId = null;
4697
+ this.ConfirmingRemoveSuiteTestId = null;
4038
4698
  this.cdr.markForCheck();
4039
4699
  }
4700
+ /** @deprecated Use {@link CancelRemoveTest}. */
4701
+ cancelRemoveTest(event) {
4702
+ return this.CancelRemoveTest(event);
4703
+ }
4040
4704
  /** Delete the suite-test join row and refresh the list. */
4041
- async confirmRemoveTest(suiteTest, event) {
4705
+ async ConfirmRemoveTest(suiteTest, event) {
4042
4706
  if (event)
4043
4707
  event.stopPropagation();
4044
- if (this.isRemovingTest)
4708
+ if (this.IsRemovingTest)
4045
4709
  return;
4046
- this.isRemovingTest = true;
4710
+ this.IsRemovingTest = true;
4047
4711
  this.cdr.markForCheck();
4048
4712
  try {
4049
4713
  const ok = await suiteTest.Delete();
4050
4714
  if (ok) {
4051
- this.suiteTests = this.suiteTests.filter(t => !UUIDsEqual(t.ID, suiteTest.ID));
4715
+ this.SuiteTests = this.SuiteTests.filter(t => !UUIDsEqual(t.ID, suiteTest.ID));
4052
4716
  SharedService.Instance.CreateSimpleNotification('Test removed from suite', 'success', 2000);
4053
4717
  }
4054
4718
  else {
@@ -4061,19 +4725,23 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
4061
4725
  SharedService.Instance.CreateSimpleNotification(msg, 'error', 4000);
4062
4726
  }
4063
4727
  finally {
4064
- this.isRemovingTest = false;
4065
- this.confirmingRemoveSuiteTestId = null;
4728
+ this.IsRemovingTest = false;
4729
+ this.ConfirmingRemoveSuiteTestId = null;
4066
4730
  this.cdr.markForCheck();
4067
4731
  }
4068
4732
  }
4733
+ /** @deprecated Use {@link ConfirmRemoveTest}. */
4734
+ async confirmRemoveTest(suiteTest, event) {
4735
+ return this.ConfirmRemoveTest(suiteTest, event);
4736
+ }
4069
4737
  /** Handle CDK drag-drop reordering — persists new Sequence values. */
4070
- async onTestDrop(event) {
4738
+ async OnTestDrop(event) {
4071
4739
  if (event.previousIndex === event.currentIndex)
4072
4740
  return;
4073
4741
  // Optimistic reorder
4074
- const reordered = [...this.suiteTests];
4742
+ const reordered = [...this.SuiteTests];
4075
4743
  moveItemInArray(reordered, event.previousIndex, event.currentIndex);
4076
- this.suiteTests = reordered;
4744
+ this.SuiteTests = reordered;
4077
4745
  this.cdr.markForCheck();
4078
4746
  // Compute which rows actually changed sequence (1-based contiguous numbering)
4079
4747
  const dirty = [];
@@ -4086,7 +4754,7 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
4086
4754
  });
4087
4755
  if (dirty.length === 0)
4088
4756
  return;
4089
- this.isReorderingTests = true;
4757
+ this.IsReorderingTests = true;
4090
4758
  this.cdr.markForCheck();
4091
4759
  const provider = this.ProviderToUse;
4092
4760
  try {
@@ -4100,27 +4768,31 @@ let MJTestSuiteFormComponentExtended = class MJTestSuiteFormComponentExtended ex
4100
4768
  const failed = dirty.find(r => r.LatestResult && !r.LatestResult.Success);
4101
4769
  const detail = failed?.LatestResult?.CompleteMessage || 'Failed to save new order';
4102
4770
  SharedService.Instance.CreateSimpleNotification(detail, 'error', 4000);
4103
- this.testsLoaded = false;
4771
+ this.TestsLoaded = false;
4104
4772
  await this.loadTests();
4105
4773
  }
4106
4774
  }
4107
4775
  catch (err) {
4108
4776
  const msg = err instanceof Error ? err.message : 'Failed to save new order';
4109
4777
  SharedService.Instance.CreateSimpleNotification(msg, 'error', 4000);
4110
- this.testsLoaded = false;
4778
+ this.TestsLoaded = false;
4111
4779
  await this.loadTests();
4112
4780
  }
4113
4781
  finally {
4114
- this.isReorderingTests = false;
4782
+ this.IsReorderingTests = false;
4115
4783
  this.cdr.markForCheck();
4116
4784
  }
4117
4785
  }
4786
+ /** @deprecated Use {@link OnTestDrop}. */
4787
+ async onTestDrop(event) {
4788
+ return this.OnTestDrop(event);
4789
+ }
4118
4790
  static { this.ɵfac = /*@__PURE__*/ (() => { let ɵMJTestSuiteFormComponentExtended_BaseFactory; return function MJTestSuiteFormComponentExtended_Factory(__ngFactoryType__) { return (ɵMJTestSuiteFormComponentExtended_BaseFactory || (ɵMJTestSuiteFormComponentExtended_BaseFactory = i0.ɵɵgetInheritedFactory(MJTestSuiteFormComponentExtended)))(__ngFactoryType__ || MJTestSuiteFormComponentExtended); }; })(); }
4119
4791
  static { this.ɵcmp = /*@__PURE__*/ i0.ɵɵdefineComponent({ type: MJTestSuiteFormComponentExtended, selectors: [["mj-test-suite-form"]], viewQuery: function MJTestSuiteFormComponentExtended_Query(rf, ctx) { if (rf & 1) {
4120
4792
  i0.ɵɵviewQuery(_c0, 5);
4121
4793
  } if (rf & 2) {
4122
4794
  let _t;
4123
- i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.chartContainer = _t.first);
4795
+ i0.ɵɵqueryRefresh(_t = i0.ɵɵloadQuery()) && (ctx.ChartContainer = _t.first);
4124
4796
  } }, hostBindings: function MJTestSuiteFormComponentExtended_HostBindings(rf, ctx) { if (rf & 1) {
4125
4797
  i0.ɵɵlistener("keydown", function MJTestSuiteFormComponentExtended_keydown_HostBindingHandler($event) { return ctx.handleKeyboardShortcut($event); }, i0.ɵɵresolveDocument)("beforeunload", function MJTestSuiteFormComponentExtended_beforeunload_HostBindingHandler($event) { return ctx.handleBeforeUnload($event); }, i0.ɵɵresolveWindow);
4126
4798
  } }, standalone: false, features: [i0.ɵɵInheritDefinitionFeature], decls: 87, vars: 51, consts: [["tagInput", ""], ["chartContainer", ""], ["pickerSearch", ""], [1, "test-suite-form"], [1, "suite-header"], ["aria-label", "Breadcrumb", 1, "breadcrumb"], ["href", "javascript:void(0)", 3, "click"], [1, "fas", "fa-vial"], [1, "breadcrumb-text"], [1, "current"], [1, "fas", "fa-chevron-right", "separator"], [1, "fas", "fa-layer-group"], [1, "header-content"], [1, "header-left"], [1, "suite-icon"], [1, "suite-info"], ["title", "You have unsaved changes", "aria-label", "Unsaved changes", 1, "dirty-indicator"], [1, "suite-meta"], [1, "status-badge", 3, "ngClass"], [1, "fas", 3, "ngClass"], [1, "test-count"], ["title", "You have unsaved changes", 1, "unsaved-pill"], [1, "header-actions"], ["mjButton", "", "title", "Export to CSV", 3, "click"], [1, "fas", "fa-file-excel"], ["mjButton", "", "variant", "primary", 3, "click"], [1, "fas", "fa-play"], ["mjButton", "", 3, "click", "disabled"], [1, "suite-description"], [1, "tabs-container"], ["role", "tablist", 1, "tabs"], ["role", "tab", 1, "tab", 3, "click"], [1, "fas", "fa-th-large"], [1, "fas", "fa-flask"], [1, "tab-badge"], [1, "fas", "fa-history"], [1, "fas", "fa-chart-line"], [1, "fas", "fa-balance-scale"], [1, "tab-content"], [1, "overview-tab"], [1, "tests-tab"], [1, "runs-tab"], [1, "analytics-tab"], [1, "compare-tab"], [1, "shortcuts-toggle", 3, "click", "title"], [1, "fas", "fa-keyboard"], [1, "keyboard-shortcuts"], ["Mode", "slide", 3, "Title", "Resizable"], ["role", "region", "aria-label", "Unsaved changes", 1, "save-bar"], [1, "save-bar-message"], ["aria-hidden", "true", 1, "save-bar-dot"], [1, "save-bar-text"], [1, "save-bar-fields"], [1, "save-bar-actions"], ["type", "button", "title", "Save changes (\u2318S)", 1, "save-bar-btn", "save-bar-btn--primary", 3, "click", "disabled"], ["type", "button", "title", "Discard changes", 1, "save-bar-btn", "save-bar-btn--ghost", 3, "click", "disabled"], [1, "fas", "fa-undo"], [1, "fas", "fa-circle"], [1, "edit-section"], [1, "edit-section-header"], [1, "fas", "fa-pen-to-square"], [1, "edit-section-sub"], [1, "edit-grid"], [1, "edit-field", "edit-field--full"], ["for", "suite-name"], [1, "required"], ["id", "suite-name", "type", "text", "placeholder", "e.g., Core Agent Test Suite", 1, "config-input", 3, "ngModelChange", "ngModel"], ["for", "suite-description"], ["id", "suite-description", "rows", "3", "placeholder", "What does this suite cover?", 1, "config-input", "config-textarea", 3, "ngModelChange", "ngModel"], [1, "edit-field"], ["for", "suite-status"], [1, "select-wrapper"], ["id", "suite-status", 1, "config-input", 3, "ngModelChange", "ngModel"], [3, "ngValue"], ["for", "suite-parent"], ["id", "suite-parent", 1, "config-input", 3, "ngModelChange", "ngModel"], ["for", "suite-tags"], [1, "tag-editor", 3, "click"], [1, "tag-chip-editable"], ["id", "suite-tags", "type", "text", 1, "tag-input-inline", 3, "ngModelChange", "keydown", "blur", "placeholder", "ngModel"], [1, "config-hint"], [1, "fas", "fa-cogs"], ["for", "suite-timeout"], ["id", "suite-timeout", "type", "number", "placeholder", "Default: 300000 (5 min)", 1, "config-input", 3, "ngModelChange", "ngModel"], [1, "meta-section"], [1, "meta-grid"], [1, "meta-item"], [1, "meta-label"], [1, "meta-value"], [1, "meta-value", "meta-mono"], ["type", "button", 1, "tag-remove", 3, "click"], [1, "fas", "fa-times"], [1, "tests-actionbar"], [1, "tests-actionbar-text"], [1, "tests-actionbar-count"], [1, "tests-actionbar-hint"], [1, "tests-actionbar-actions"], ["mjButton", "", "variant", "primary", 3, "click", "disabled"], [1, "fas", "fa-plus"], [1, "loading-state"], ["cdkDropList", "", 1, "tests-list"], ["Icon", "fa-solid fa-flask", "Title", "No Tests in Suite", "Message", "Add tests to this suite to start running them together.", "ActionText", "Add tests", "ActionIcon", "fa-solid fa-plus"], [1, "skeleton-list"], [1, "skeleton-card"], [1, "skeleton-sequence"], [1, "skeleton-icon"], [1, "skeleton-content"], [1, "skeleton-line", "wide"], [1, "skeleton-line", "narrow"], ["cdkDropList", "", 1, "tests-list", 3, "cdkDropListDropped"], ["cdkDrag", "", 1, "test-item", 3, "cdkDragDisabled"], ["cdkDragHandle", "", "title", "Drag to reorder", "aria-label", "Drag to reorder", 1, "test-drag-handle"], [1, "fas", "fa-grip-vertical"], [1, "test-sequence"], [1, "test-icon"], [1, "test-content", 3, "click"], [1, "test-name"], [1, "test-status"], [1, "test-remove-confirm"], [1, "fas", "fa-info-circle"], [1, "test-remove-confirm", 3, "click"], [1, "test-remove-confirm-text"], ["type", "button", 1, "test-remove-confirm-btn", "test-remove-confirm-btn--danger", 3, "click", "disabled"], [1, "fas", "fa-spinner", "fa-spin"], ["type", "button", 1, "test-remove-confirm-btn", "test-remove-confirm-btn--ghost", 3, "click", "disabled"], ["type", "button", "title", "Remove from suite", "aria-label", "Remove from suite", 1, "test-remove-btn", 3, "click"], [1, "fas", "fa-chevron-right", "test-chevron", 3, "click"], ["Icon", "fa-solid fa-flask", "Title", "No Tests in Suite", "Message", "Add tests to this suite to start running them together.", "ActionText", "Add tests", "ActionIcon", "fa-solid fa-plus", 3, "Action"], [1, "runs-list"], ["Icon", "fa-solid fa-circle-play", "Title", "No Suite Runs Yet", "Message", "Run this suite to see execution history and results here.", "ActionText", "Run Suite Now", "ActionIcon", "fa-solid fa-play"], [1, "run-item"], [1, "run-item", 3, "click"], [1, "run-icon"], [1, "fas"], [1, "run-content"], [1, "run-header"], [1, "run-id"], [1, "run-status"], [1, "run-meta"], [1, "fas", "fa-calendar"], [1, "run-eval-metrics"], [1, "eval-metric", "status", 3, "class"], ["title", "Human evaluation", 1, "eval-metric", "human"], ["title", "Auto score (pass rate)", 1, "eval-metric", "auto", 3, "high", "medium", "low"], [1, "run-tags"], [1, "fas", "fa-chevron-right"], [1, "fas", "fa-check-circle"], [1, "eval-metric", "status"], [1, "fas", "fa-user"], [1, "eval-pending"], [1, "fas", "fa-clock"], ["title", "Auto score (pass rate)", 1, "eval-metric", "auto"], [1, "fas", "fa-robot"], [1, "tag-chip"], ["Icon", "fa-solid fa-circle-play", "Title", "No Suite Runs Yet", "Message", "Run this suite to see execution history and results here.", "ActionText", "Run Suite Now", "ActionIcon", "fa-solid fa-play", 3, "Action"], ["Icon", "fa-solid fa-chart-line", "Title", "No Analytics Data", "Message", "Run this suite to start collecting analytics data.", "ActionText", "Run Suite Now", "ActionIcon", "fa-solid fa-play"], ["text", "Loading analytics data..."], [1, "analytics-subnav"], [1, "subnav-tabs"], [1, "subnav-tab", 3, "click"], [1, "fas", "fa-chart-bar"], [1, "fas", "fa-th"], [1, "fas", "fa-project-diagram"], ["Size", "sm", 3, "ExpandedChange", "FlushBody", "Expanded"], ["mjAccordionTitle", ""], ["mjAccordionBody", ""], [1, "fas", "fa-filter", 2, "color", "var(--test-primary)"], [1, "mj-accordion-badge", "mj-accordion-badge--right", "mj-accordion-badge--muted"], [1, "filters-content"], [1, "filter-group"], [1, "filter-buttons"], [1, "filter-btn", 3, "click"], [1, "filter-hint"], [1, "filter-buttons", "tag-filters"], [1, "filter-btn", "tag-btn", "all-tags-btn", 3, "click"], [1, "filter-btn", "tag-btn", 3, "active"], [1, "filter-btn", "tag-btn", 3, "click"], [1, "fas", "fa-check"], [1, "analytics-kpis"], [1, "kpi-card"], [1, "kpi-icon"], [1, "fas", "fa-play-circle"], [1, "kpi-content"], [1, "kpi-value"], [1, "kpi-label"], [1, "kpi-icon", "success"], [1, "kpi-trend", 3, "ngClass"], [1, "kpi-icon", "info"], [1, "kpi-icon", "warning"], [1, "fas", "fa-dollar-sign"], [1, "analytics-table-section"], [1, "fas", "fa-table"], [1, "analytics-table-wrapper"], [1, "analytics-table"], [1, "clickable-row"], ["Variant", "no-results", "Size", "compact", "Title", "No runs match the current filters."], [1, "clickable-row", 3, "click"], [1, "status-chip", 3, "ngClass"], [1, "pass-rate-cell"], [1, "pass-rate-bar", 3, "ngClass"], [1, "tag-cell"], [1, "tag-chip-table"], [1, "tag-more"], [1, "matrix-section"], ["Variant", "no-results", "Icon", "fa-solid fa-table-cells", "Title", "No Matrix Data", "Message", "No suite runs match the current filters."], ["text", "Loading test matrix..."], [1, "matrix-header"], [1, "matrix-header-right"], [1, "matrix-filter-input"], [1, "fas", "fa-search"], ["type", "text", "placeholder", "Filter tests...", 1, "filter-input", 3, "input", "value"], ["title", "Clear filter", 1, "clear-filter-btn"], [1, "matrix-run-count"], ["mjButton", "", "title", "Export matrix to CSV", 3, "click", "disabled"], [1, "fas", "fa-download"], [1, "matrix-scroll-container"], [1, "test-matrix"], ["title", "Sort by sequence", 1, "seq-header", 3, "click"], ["title", "Sort by name", 1, "test-name-header", 3, "click"], [1, "run-header", 3, "title"], [1, "spacer-header"], [3, "row-selected"], [1, "totals-row"], [1, "seq-cell", "totals-label"], [1, "test-name-cell", "totals-label"], [1, "result-cell", "totals-cell"], [1, "spacer-cell"], ["title", "Clear filter", 1, "clear-filter-btn", 3, "click"], [1, "run-header", 3, "click", "title"], [1, "run-header-content"], [1, "run-tags-header"], [1, "run-date"], [1, "run-pass-rate", 3, "ngClass"], [1, "tag-chip-header"], [1, "tag-more-header"], [3, "click"], [1, "seq-cell"], [1, "test-name-cell"], [1, "test-name", 3, "title"], [1, "result-cell", 3, "ngClass", "clickable", "cell-not-run", "title"], [1, "result-cell", 3, "click", "ngClass", "title"], [1, "cell-eval-stack"], [1, "cell-not-run-indicator"], [1, "cell-status", 3, "ngClass", "cell-skipped-status", "title"], ["title", "Human Review: No rating submitted yet", 1, "cell-human", "no-feedback"], [1, "cell-human", "has-feedback", 3, "rating-low", "rating-medium", "rating-good", "rating-excellent", "title"], [1, "cell-auto", "has-score", 3, "score-low", "score-medium", "score-good", "score-excellent", "title"], ["title", "Auto Score: No automated score available", 1, "cell-auto", "no-score"], [1, "cell-status", 3, "ngClass", "title"], [1, "fas", "fa-user-slash"], [1, "cell-human", "has-feedback", 3, "title"], [1, "rating-value"], [1, "cell-auto", "has-score", 3, "title"], [1, "score-value"], [1, "fas", "fa-minus"], [1, "cell-eval-stack", "totals-stack"], [1, "totals-status"], [1, "totals-human"], [1, "totals-auto"], [1, "pass-count"], [1, "avg-label"], [1, "count-label"], [1, "chart-section"], ["Variant", "no-results", "Icon", "fa-solid fa-diagram-project", "Title", "No Chart Data", "Message", "No suite runs match the current filters."], ["text", "Loading chart data..."], [1, "chart-header"], [1, "chart-legend"], [1, "legend-item", "chart-passed"], [1, "legend-item", "chart-failed"], [1, "legend-item", "chart-error"], [1, "fas", "fa-exclamation"], [1, "legend-item", "chart-skipped"], [1, "fas", "fa-forward"], [1, "chart-container"], [1, "d3-chart"], [1, "chart-info"], ["Icon", "fa-solid fa-chart-line", "Title", "No Analytics Data", "Message", "Run this suite to start collecting analytics data.", "ActionText", "Run Suite Now", "ActionIcon", "fa-solid fa-play", 3, "Action"], [1, "compare-selection"], [1, "compare-run-selector"], [1, "run-selector-list"], [1, "selected-run-preview"], [1, "compare-vs"], [1, "fas", "fa-exchange-alt"], [1, "compare-results"], ["Icon", "fa-solid fa-scale-balanced", "Title", "Select Two Runs to Compare", "Message", "Choose a baseline run (A) and a comparison run (B) from the lists above to see a detailed side-by-side comparison."], ["Icon", "fa-solid fa-scale-balanced", "Title", "Not Enough Runs to Compare", "Message", "You need at least 2 suite runs to use the comparison feature.", "ActionText", "Run Suite Now", "ActionIcon", "fa-solid fa-play"], [1, "run-selector-item", 3, "selected"], [1, "run-selector-item", 3, "click"], [1, "selector-status"], [1, "selector-content"], [1, "selector-date"], [1, "selector-rate"], [1, "selector-tags"], [1, "tag-mini"], [1, "preview-header"], [1, "preview-label"], [1, "clear-btn", 3, "click"], [1, "preview-details"], [1, "preview-rate"], [1, "run-selector-item", 3, "selected", "disabled"], [1, "compare-summary"], [1, "compare-summary-card"], [1, "summary-label"], [1, "summary-value", 3, "ngClass"], [1, "compare-summary-card", "improved"], [1, "summary-value"], [1, "compare-summary-card", "regressed"], [1, "compare-table-section"], [1, "fas", "fa-list"], [1, "compare-table-wrapper"], [1, "compare-table"], [3, "ngClass"], [1, "status-chip", "status-missing"], [1, "muted"], [1, "change-indicator", "improved"], [1, "change-indicator", "regressed"], [1, "change-indicator", "unchanged"], [1, "fas", "fa-arrow-up"], [1, "fas", "fa-arrow-down"], ["text", "Loading comparison data..."], ["Icon", "fa-solid fa-scale-balanced", "Title", "Not Enough Runs to Compare", "Message", "You need at least 2 suite runs to use the comparison feature.", "ActionText", "Run Suite Now", "ActionIcon", "fa-solid fa-play", 3, "Action"], [1, "shortcuts-header"], ["title", "Hide shortcuts", 1, "shortcuts-close", 3, "click"], [1, "shortcut-list"], [1, "shortcut-item"], [1, "shortcut-keys"], ["Mode", "slide", 3, "Closed", "Title", "Resizable"], [3, "PanelClose", "PanelMode", "selectedTestId", "selectedSuiteId", "runMode"], [1, "picker-backdrop", 3, "click"], ["role", "dialog", "aria-modal", "true", "aria-labelledby", "add-tests-dialog-title", 1, "picker-dialog", 3, "click"], [1, "picker-header"], ["id", "add-tests-dialog-title", 1, "picker-title"], [1, "fas", "fa-plus-circle"], ["type", "button", "aria-label", "Close", 1, "picker-close", 3, "click", "disabled"], [1, "picker-search"], [1, "fas", "fa-search", "picker-search-icon"], ["type", "text", "placeholder", "Search tests by name, description, or type...", "autofocus", "", 1, "picker-search-input", 3, "ngModelChange", "ngModel"], ["type", "button", "aria-label", "Clear search", 1, "picker-search-clear"], [1, "picker-toolbar"], [1, "picker-toolbar-status"], [1, "picker-toolbar-actions"], ["type", "button", 1, "picker-link-btn", 3, "click", "disabled"], [1, "picker-body"], [1, "picker-loading"], [1, "picker-list"], [1, "picker-footer"], ["type", "button", 1, "picker-btn", "picker-btn--primary", 3, "click", "disabled"], ["type", "button", 1, "picker-btn", "picker-btn--ghost", 3, "click", "disabled"], ["type", "button", "aria-label", "Clear search", 1, "picker-search-clear", 3, "click"], ["text", "Loading tests..."], ["Variant", "success", "Size", "compact", "Title", "Everything's already in the suite", "Message", "This suite contains every available test. Create a new test first to add more."], ["Variant", "no-results", "Size", "compact", "Title", "No matches", 3, "Message"], ["role", "checkbox", "tabindex", "0", 1, "picker-row", 3, "picker-row--selected"], ["role", "checkbox", "tabindex", "0", 1, "picker-row", 3, "click"], [1, "picker-check"], [1, "picker-row-main"], [1, "picker-row-name"], [1, "picker-row-desc"], [1, "picker-row-meta"], [1, "picker-row-pill"], [1, "picker-row-pill", "picker-row-pill--status"], [1, "save-bar-kbd"]], template: function MJTestSuiteFormComponentExtended_Template(rf, ctx) { if (rf & 1) {
@@ -4315,7 +4987,7 @@ export { MJTestSuiteFormComponentExtended };
4315
4987
  (() => { (typeof ngDevMode === "undefined" || ngDevMode) && i0.ɵsetClassMetadata(MJTestSuiteFormComponentExtended, [{
4316
4988
  type: Component,
4317
4989
  args: [{ standalone: false, selector: 'mj-test-suite-form', changeDetection: ChangeDetectionStrategy.OnPush, template: "<div class=\"test-suite-form\">\n <!-- Header Section -->\n <div class=\"suite-header\">\n <!-- Breadcrumb Navigation -->\n <nav class=\"breadcrumb\" aria-label=\"Breadcrumb\">\n <ol>\n <li>\n <a href=\"javascript:void(0)\" (click)=\"navigateToTestingDashboard()\">\n <i class=\"fas fa-vial\"></i>\n <span class=\"breadcrumb-text\">Testing</span>\n </a>\n </li>\n <li class=\"current\">\n <i class=\"fas fa-chevron-right separator\"></i>\n <i class=\"fas fa-layer-group\"></i>\n <span>{{ record.Name }}</span>\n </li>\n </ol>\n </nav>\n\n <div class=\"header-content\">\n <div class=\"header-left\">\n <div class=\"suite-icon\" [style.background-color]=\"getStatusColor()\">\n <i class=\"fas fa-layer-group\"></i>\n </div>\n <div class=\"suite-info\">\n <h1>\n {{ record.Name }}\n @if (isDirty) {\n <span class=\"dirty-indicator\" title=\"You have unsaved changes\" aria-label=\"Unsaved changes\">\u25CF</span>\n }\n </h1>\n <div class=\"suite-meta\">\n <span class=\"status-badge\" [ngClass]=\"getStatusClass()\">\n <i class=\"fas\" [ngClass]=\"record.Status === 'Active' ? 'fa-circle-check' : 'fa-circle-pause'\"></i>\n {{ record.Status }}\n </span>\n @if (testsLoaded) {\n <span class=\"test-count\">\n <i class=\"fas fa-flask\"></i>\n {{ suiteTests.length }} tests\n </span>\n }\n @if (isDirty) {\n <span class=\"unsaved-pill\" title=\"You have unsaved changes\">\n <i class=\"fas fa-circle\"></i> Unsaved\n </span>\n }\n </div>\n </div>\n </div>\n <div class=\"header-actions\">\n <app-evaluation-mode-toggle></app-evaluation-mode-toggle>\n <button mjButton (click)=\"exportToExcel()\" title=\"Export to CSV\">\n <i class=\"fas fa-file-excel\"></i> Export\n </button>\n <button mjButton (click)=\"runSuite()\" variant=\"primary\">\n <i class=\"fas fa-play\"></i> Run Suite\n </button>\n <button mjButton (click)=\"refresh()\" [disabled]=\"isRefreshing\">\n <i class=\"fas\" [ngClass]=\"isRefreshing ? 'fa-sync fa-spin' : 'fa-sync'\"></i>\n {{ isRefreshing ? 'Refreshing...' : 'Refresh' }}\n </button>\n </div>\n </div>\n @if (record.Description) {\n <div class=\"suite-description\">\n <p>{{ record.Description }}</p>\n </div>\n }\n </div>\n\n <!-- Tabs -->\n <div class=\"tabs-container\">\n <div class=\"tabs\" role=\"tablist\">\n <button class=\"tab\"\n [class.active]=\"activeTab === 'overview'\"\n (click)=\"changeTab('overview')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'overview'\">\n <i class=\"fas fa-th-large\"></i> Overview\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'tests'\"\n (click)=\"changeTab('tests')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'tests'\">\n <i class=\"fas fa-flask\"></i> Tests\n @if (testsLoaded) {\n <span class=\"tab-badge\">{{ suiteTests.length }}</span>\n }\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'runs'\"\n (click)=\"changeTab('runs')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'runs'\">\n <i class=\"fas fa-history\"></i> Runs\n @if (runsLoaded) {\n <span class=\"tab-badge\">{{ suiteRuns.length }}</span>\n }\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'analytics'\"\n (click)=\"changeTab('analytics')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'analytics'\">\n <i class=\"fas fa-chart-line\"></i> Analytics\n </button>\n <button class=\"tab\"\n [class.active]=\"activeTab === 'compare'\"\n (click)=\"changeTab('compare')\"\n role=\"tab\"\n [attr.aria-selected]=\"activeTab === 'compare'\">\n <i class=\"fas fa-balance-scale\"></i> Compare\n </button>\n </div>\n </div>\n\n <!-- Tab Content -->\n <div class=\"tab-content\" [class.has-savebar]=\"isDirty\">\n <!-- Overview Tab -->\n @if (activeTab === 'overview') {\n <div class=\"overview-tab\">\n <!-- Editable Details -->\n <section class=\"edit-section\">\n <header class=\"edit-section-header\">\n <h3><i class=\"fas fa-pen-to-square\"></i> Details</h3>\n <p class=\"edit-section-sub\">Edit any field \u2014 changes are saved with the bar at the bottom.</p>\n </header>\n <div class=\"edit-grid\">\n <div class=\"edit-field edit-field--full\">\n <label for=\"suite-name\">Name <span class=\"required\">*</span></label>\n <input\n id=\"suite-name\"\n type=\"text\"\n class=\"config-input\"\n placeholder=\"e.g., Core Agent Test Suite\"\n [(ngModel)]=\"record.Name\"\n />\n </div>\n\n <div class=\"edit-field edit-field--full\">\n <label for=\"suite-description\">Description</label>\n <textarea\n id=\"suite-description\"\n class=\"config-input config-textarea\"\n rows=\"3\"\n placeholder=\"What does this suite cover?\"\n [ngModel]=\"record.Description\"\n (ngModelChange)=\"record.Description = $event || null\"\n ></textarea>\n </div>\n\n <div class=\"edit-field\">\n <label for=\"suite-status\">Status</label>\n <div class=\"select-wrapper\">\n <select\n id=\"suite-status\"\n class=\"config-input\"\n [(ngModel)]=\"record.Status\"\n >\n @for (opt of statusOptions; track opt) {\n <option [ngValue]=\"opt\">{{ opt }}</option>\n }\n </select>\n </div>\n </div>\n\n <div class=\"edit-field\">\n <label for=\"suite-parent\">Parent Suite</label>\n <select\n id=\"suite-parent\"\n class=\"config-input\"\n [ngModel]=\"record.ParentID\"\n (ngModelChange)=\"record.ParentID = $event || null\"\n >\n <option [ngValue]=\"null\">\u2014 None (top-level) \u2014</option>\n @for (s of parentSuiteOptions; track s.ID) {\n <option [ngValue]=\"s.ID\">{{ s.Name }}</option>\n }\n </select>\n </div>\n\n <div class=\"edit-field edit-field--full\">\n <label for=\"suite-tags\">Tags</label>\n <div class=\"tag-editor\" (click)=\"tagInput.focus()\">\n @for (tag of tags; track tag) {\n <span class=\"tag-chip-editable\">\n {{ tag }}\n <button type=\"button\" class=\"tag-remove\" (click)=\"removeTag(tag); $event.stopPropagation()\" [attr.aria-label]=\"'Remove tag ' + tag\">\n <i class=\"fas fa-times\"></i>\n </button>\n </span>\n }\n <input\n #tagInput\n id=\"suite-tags\"\n type=\"text\"\n class=\"tag-input-inline\"\n [placeholder]=\"tags.length === 0 ? 'Add tags, press Enter or comma...' : ''\"\n [(ngModel)]=\"tagDraft\"\n (keydown)=\"onTagInputKeydown($event)\"\n (blur)=\"addTagFromDraft()\"\n />\n </div>\n <span class=\"config-hint\">Press Enter or comma to add a tag. Backspace on empty input removes the last tag.</span>\n </div>\n </div>\n </section>\n\n <!-- Execution Settings -->\n <section class=\"edit-section\">\n <header class=\"edit-section-header\">\n <h3><i class=\"fas fa-cogs\"></i> Execution Settings</h3>\n </header>\n <div class=\"edit-grid\">\n <div class=\"edit-field\">\n <label for=\"suite-timeout\">Max Execution Time (ms)</label>\n <input\n id=\"suite-timeout\"\n type=\"number\"\n class=\"config-input\"\n placeholder=\"Default: 300000 (5 min)\"\n [ngModel]=\"record.MaxExecutionTimeMS\"\n (ngModelChange)=\"record.MaxExecutionTimeMS = $event === '' || $event === null ? null : +$event\"\n />\n <span class=\"config-hint\">Default timeout for tests in this suite. Currently: {{ formatTimeout(record.MaxExecutionTimeMS) }}</span>\n </div>\n </div>\n </section>\n\n <!-- Metadata (read-only) -->\n <section class=\"meta-section\">\n <div class=\"meta-grid\">\n <div class=\"meta-item\">\n <div class=\"meta-label\">Created</div>\n <div class=\"meta-value\">{{ record.__mj_CreatedAt | date:'medium' }}</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">Last updated</div>\n <div class=\"meta-value\">{{ record.__mj_UpdatedAt | date:'medium' }}</div>\n </div>\n <div class=\"meta-item\">\n <div class=\"meta-label\">Suite ID</div>\n <div class=\"meta-value meta-mono\">{{ record.ID }}</div>\n </div>\n </div>\n </section>\n </div>\n }\n\n <!-- Tests Tab -->\n @if (activeTab === 'tests') {\n <div class=\"tests-tab\">\n <!-- Header: action bar -->\n <div class=\"tests-actionbar\">\n <div class=\"tests-actionbar-text\">\n <strong>Suite tests</strong>\n @if (testsLoaded) {\n <span class=\"tests-actionbar-count\">{{ suiteTests.length }}</span>\n }\n <span class=\"tests-actionbar-hint\">Drag to reorder \u00B7 Hover a row to remove</span>\n </div>\n <div class=\"tests-actionbar-actions\">\n <button mjButton variant=\"primary\" (click)=\"openAddTestsDialog()\" [disabled]=\"isReorderingTests\">\n <i class=\"fas fa-plus\"></i> Add tests\n </button>\n </div>\n </div>\n\n <!-- Loading State -->\n @if (loadingTests) {\n <div class=\"loading-state\">\n <div class=\"skeleton-list\">\n @for (i of [1,2,3,4,5]; track i) {\n <div class=\"skeleton-card\">\n <div class=\"skeleton-sequence\"></div>\n <div class=\"skeleton-icon\"></div>\n <div class=\"skeleton-content\">\n <div class=\"skeleton-line wide\"></div>\n <div class=\"skeleton-line narrow\"></div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n <!-- Tests List -->\n @if (!loadingTests && suiteTests.length > 0) {\n <div class=\"tests-list\" cdkDropList (cdkDropListDropped)=\"onTestDrop($event)\">\n @for (test of suiteTests; track test.ID) {\n <div class=\"test-item\" cdkDrag [cdkDragDisabled]=\"isReorderingTests || isRemovingTest\">\n <span class=\"test-drag-handle\" cdkDragHandle title=\"Drag to reorder\" aria-label=\"Drag to reorder\">\n <i class=\"fas fa-grip-vertical\"></i>\n </span>\n <div class=\"test-sequence\">{{ test.Sequence }}</div>\n <div class=\"test-icon\"><i class=\"fas fa-flask\"></i></div>\n <div class=\"test-content\" (click)=\"openTest(test.TestID)\">\n <div class=\"test-name\">{{ test.Test }}</div>\n <div class=\"test-status\">\n @if (test.Status) {\n <span><i class=\"fas fa-info-circle\"></i> {{ test.Status }}</span>\n }\n </div>\n </div>\n <!-- Inline remove confirm OR hover-reveal X -->\n @if (confirmingRemoveSuiteTestId === test.ID) {\n <div class=\"test-remove-confirm\" (click)=\"$event.stopPropagation()\">\n <span class=\"test-remove-confirm-text\">Remove from suite?</span>\n <button\n type=\"button\"\n class=\"test-remove-confirm-btn test-remove-confirm-btn--danger\"\n (click)=\"confirmRemoveTest(test, $event)\"\n [disabled]=\"isRemovingTest\"\n >\n @if (isRemovingTest) {\n <i class=\"fas fa-spinner fa-spin\"></i>\n } @else {\n Remove\n }\n </button>\n <button\n type=\"button\"\n class=\"test-remove-confirm-btn test-remove-confirm-btn--ghost\"\n (click)=\"cancelRemoveTest($event)\"\n [disabled]=\"isRemovingTest\"\n >\n Cancel\n </button>\n </div>\n } @else {\n <button\n type=\"button\"\n class=\"test-remove-btn\"\n title=\"Remove from suite\"\n aria-label=\"Remove from suite\"\n (click)=\"requestRemoveTest(test, $event)\"\n >\n <i class=\"fas fa-times\"></i>\n </button>\n <i class=\"fas fa-chevron-right test-chevron\" (click)=\"openTest(test.TestID)\"></i>\n }\n </div>\n }\n </div>\n }\n <!-- Empty State -->\n @if (testsLoaded && !loadingTests && suiteTests.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-flask\"\n Title=\"No Tests in Suite\"\n Message=\"Add tests to this suite to start running them together.\"\n ActionText=\"Add tests\"\n ActionIcon=\"fa-solid fa-plus\"\n (Action)=\"openAddTestsDialog()\" />\n }\n </div>\n }\n\n <!-- Runs Tab -->\n @if (activeTab === 'runs') {\n <div class=\"runs-tab\">\n <!-- Loading State -->\n @if (loadingRuns) {\n <div class=\"loading-state\">\n <div class=\"skeleton-list\">\n @for (i of [1,2,3]; track i) {\n <div class=\"skeleton-card\">\n <div class=\"skeleton-icon\"></div>\n <div class=\"skeleton-content\">\n <div class=\"skeleton-line wide\"></div>\n <div class=\"skeleton-line narrow\"></div>\n </div>\n </div>\n }\n </div>\n </div>\n }\n <!-- Runs List -->\n @if (!loadingRuns && suiteRuns.length > 0) {\n <div class=\"runs-list\">\n @for (run of suiteRuns; track run) {\n <div class=\"run-item\" (click)=\"openSuiteRun(run.ID)\">\n <div class=\"run-icon\" [style.background-color]=\"getRunStatusColor(run.Status)\">\n <i class=\"fas\"\n [class.fa-check]=\"run.Status === 'Completed'\"\n [class.fa-times]=\"run.Status === 'Failed'\"\n [class.fa-spinner]=\"run.Status === 'Running'\"\n [class.fa-clock]=\"run.Status === 'Pending'\"\n [class.fa-ban]=\"run.Status === 'Cancelled'\"></i>\n </div>\n <div class=\"run-content\">\n <div class=\"run-header\">\n <span class=\"run-id\">Run #{{ run.ID.substring(0, 8) }}</span>\n <span class=\"run-status\" [style.color]=\"getRunStatusColor(run.Status)\">{{ run.Status }}</span>\n </div>\n <div class=\"run-meta\">\n <span><i class=\"fas fa-calendar\"></i> {{ getRelativeTime(run.StartedAt) }}</span>\n @if (run.TotalTests) {\n <span>\n <i class=\"fas fa-check-circle\"></i> {{ run.PassedTests }}/{{ run.TotalTests }}\n ({{ getPassRate(run).toFixed(0) }}%)\n </span>\n }\n </div>\n <!-- Evaluation metrics row -->\n <div class=\"run-eval-metrics\">\n <!-- Status badge -->\n @if (evalPreferences.showExecution) {\n <span class=\"eval-metric status\" [class]=\"'status-' + run.Status.toLowerCase()\">\n <i class=\"fas\"\n [class.fa-circle-check]=\"run.Status === 'Completed'\"\n [class.fa-circle-xmark]=\"run.Status === 'Failed'\"\n [class.fa-spinner]=\"run.Status === 'Running'\"\n [class.fa-clock]=\"run.Status === 'Pending'\"\n [class.fa-ban]=\"run.Status === 'Cancelled'\"></i>\n </span>\n }\n <!-- Human score (placeholder - need avg from suite run) -->\n @if (evalPreferences.showHuman) {\n <span class=\"eval-metric human\" title=\"Human evaluation\">\n <i class=\"fas fa-user\"></i>\n <span class=\"eval-pending\"><i class=\"fas fa-clock\"></i></span>\n </span>\n }\n <!-- Auto score (pass rate as proxy) -->\n @if (evalPreferences.showAuto && run.TotalTests) {\n <span class=\"eval-metric auto\"\n [class.high]=\"getPassRate(run) >= 80\"\n [class.medium]=\"getPassRate(run) >= 50 && getPassRate(run) < 80\"\n [class.low]=\"getPassRate(run) < 50\"\n title=\"Auto score (pass rate)\">\n <i class=\"fas fa-robot\"></i>\n <span>{{ getPassRate(run).toFixed(0) }}%</span>\n </span>\n }\n </div>\n <!-- Tags display -->\n @if (getRunTags(run).length > 0) {\n <div class=\"run-tags\">\n @for (tag of getRunTags(run); track tag) {\n <span class=\"tag-chip\">{{ tag }}</span>\n }\n </div>\n }\n </div>\n <i class=\"fas fa-chevron-right\"></i>\n </div>\n }\n </div>\n }\n <!-- Empty State -->\n @if (runsLoaded && !loadingRuns && suiteRuns.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-circle-play\"\n Title=\"No Suite Runs Yet\"\n Message=\"Run this suite to see execution history and results here.\"\n ActionText=\"Run Suite Now\"\n ActionIcon=\"fa-solid fa-play\"\n (Action)=\"runSuite()\" />\n }\n </div>\n }\n\n <!-- Analytics Tab -->\n @if (activeTab === 'analytics') {\n <div class=\"analytics-tab\">\n <!-- Loading State -->\n @if (loadingAnalytics) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading analytics data...\"></mj-loading>\n </div>\n }\n @if (!loadingAnalytics && analyticsLoaded) {\n <!-- View Toggle Sub-nav -->\n <div class=\"analytics-subnav\">\n <div class=\"subnav-tabs\">\n <button class=\"subnav-tab\"\n [class.active]=\"analyticsView === 'summary'\"\n (click)=\"setAnalyticsView('summary')\">\n <i class=\"fas fa-chart-bar\"></i>\n <span>Summary</span>\n </button>\n <button class=\"subnav-tab\"\n [class.active]=\"analyticsView === 'matrix'\"\n (click)=\"setAnalyticsView('matrix')\">\n <i class=\"fas fa-th\"></i>\n <span>Matrix</span>\n </button>\n <button class=\"subnav-tab\"\n [class.active]=\"analyticsView === 'chart'\"\n (click)=\"setAnalyticsView('chart')\">\n <i class=\"fas fa-project-diagram\"></i>\n <span>Chart</span>\n </button>\n </div>\n </div>\n <!-- Collapsible Filters (shared by both views) -->\n <mj-accordion-panel Size=\"sm\" [FlushBody]=\"true\"\n [Expanded]=\"!filtersCollapsed\" (ExpandedChange)=\"onFiltersExpandedChange($event)\">\n <ng-template mjAccordionTitle>\n <i class=\"fas fa-filter\" style=\"color: var(--test-primary);\"></i>\n Filters\n @if (filtersCollapsed) {\n <span class=\"mj-accordion-badge mj-accordion-badge--right mj-accordion-badge--muted\">\n {{ analyticsTimeRange === 'all' ? 'All Time' : analyticsTimeRange }}\n @if (selectedTags.length > 0) {\n <span> \u00B7 {{ selectedTags.length }} tags</span>\n }\n </span>\n }\n </ng-template>\n <ng-template mjAccordionBody>\n <div class=\"filters-content\">\n <div class=\"filter-group\">\n <label>Time Range</label>\n <div class=\"filter-buttons\">\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === '7d'\" (click)=\"setTimeRange('7d')\">7 Days</button>\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === '30d'\" (click)=\"setTimeRange('30d')\">30 Days</button>\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === '90d'\" (click)=\"setTimeRange('90d')\">90 Days</button>\n <button class=\"filter-btn\" [class.active]=\"analyticsTimeRange === 'all'\" (click)=\"setTimeRange('all')\">All Time</button>\n </div>\n </div>\n @if (uniqueTags.length > 0) {\n <div class=\"filter-group\">\n <label>Filter by Tag @if (selectedTags.length > 0) {\n <span class=\"filter-hint\">({{ selectedTags.length }} selected)</span>\n }</label>\n <div class=\"filter-buttons tag-filters\">\n <button class=\"filter-btn tag-btn all-tags-btn\"\n [class.active]=\"selectedTags.length === 0\"\n (click)=\"toggleTagFilter(null)\">\n <i class=\"fas fa-layer-group\"></i> All Tags\n </button>\n @for (tag of uniqueTags; track tag) {\n <button class=\"filter-btn tag-btn\"\n [class.active]=\"isTagSelected(tag)\"\n (click)=\"toggleTagFilter(tag)\">\n @if (isTagSelected(tag)) {\n <i class=\"fas fa-check\"></i>\n }\n {{ tag }}\n </button>\n }\n </div>\n </div>\n }\n </div>\n </ng-template>\n </mj-accordion-panel>\n <!-- Summary View -->\n @if (analyticsView === 'summary') {\n <!-- KPI Cards -->\n <div class=\"analytics-kpis\">\n <div class=\"kpi-card\">\n <div class=\"kpi-icon\"><i class=\"fas fa-play-circle\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ getTotalRuns() }}</div>\n <div class=\"kpi-label\">Total Runs</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon success\"><i class=\"fas fa-check-circle\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ getAveragePassRate().toFixed(1) }}%</div>\n <div class=\"kpi-label\">Avg Pass Rate</div>\n <div class=\"kpi-trend\" [ngClass]=\"{'trend-up': getPassRateTrend().direction === 'up', 'trend-down': getPassRateTrend().direction === 'down'}\">\n <i class=\"fas\" [ngClass]=\"{'fa-arrow-up': getPassRateTrend().direction === 'up', 'fa-arrow-down': getPassRateTrend().direction === 'down', 'fa-minus': getPassRateTrend().direction === 'stable'}\"></i>\n {{ getPassRateTrend().value.toFixed(1) }}%\n </div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon info\"><i class=\"fas fa-clock\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ formatDuration(getAverageDuration()) }}</div>\n <div class=\"kpi-label\">Avg Duration</div>\n </div>\n </div>\n <div class=\"kpi-card\">\n <div class=\"kpi-icon warning\"><i class=\"fas fa-dollar-sign\"></i></div>\n <div class=\"kpi-content\">\n <div class=\"kpi-value\">{{ formatCost(getTotalCost()) }}</div>\n <div class=\"kpi-label\">Total Cost</div>\n </div>\n </div>\n </div>\n <!-- Runs Table -->\n <div class=\"analytics-table-section\">\n <h3><i class=\"fas fa-table\"></i> Run History</h3>\n <div class=\"analytics-table-wrapper\">\n <table class=\"analytics-table\">\n <thead>\n <tr>\n <th>Date</th>\n <th>Status</th>\n <th>Pass Rate</th>\n <th>Tests</th>\n <th>Duration</th>\n <th>Cost</th>\n <th>Tags</th>\n </tr>\n </thead>\n <tbody>\n @for (dp of getFilteredAnalyticsData(); track dp) {\n <tr (click)=\"openSuiteRun(dp.runId)\" class=\"clickable-row\">\n <td>{{ dp.date | date:'short' }}</td>\n <td>\n <span class=\"status-chip\" [ngClass]=\"'status-' + dp.status.toLowerCase()\">{{ dp.status }}</span>\n </td>\n <td>\n <div class=\"pass-rate-cell\">\n <div class=\"pass-rate-bar\" [style.width.%]=\"dp.passRate\" [ngClass]=\"{'high': dp.passRate >= 80, 'medium': dp.passRate >= 50 && dp.passRate < 80, 'low': dp.passRate < 50}\"></div>\n <span>{{ dp.passRate.toFixed(0) }}%</span>\n </div>\n </td>\n <td>{{ dp.passedTests }}/{{ dp.totalTests }}</td>\n <td>{{ formatDuration(dp.duration) }}</td>\n <td>{{ formatCost(dp.cost) }}</td>\n <td>\n <div class=\"tag-cell\">\n @for (tag of dp.tags.slice(0, 2); track tag) {\n <span class=\"tag-chip-table\">{{ tag }}</span>\n }\n @if (dp.tags.length > 2) {\n <span class=\"tag-more\">+{{ dp.tags.length - 2 }}</span>\n }\n </div>\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n @if (getFilteredAnalyticsData().length === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Size=\"compact\"\n Title=\"No runs match the current filters.\" />\n }\n </div>\n }\n <!-- Matrix View -->\n @if (analyticsView === 'matrix') {\n <!-- Loading Matrix -->\n @if (loadingMatrix) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading test matrix...\"></mj-loading>\n </div>\n }\n <!-- Matrix Content -->\n @if (!loadingMatrix && matrixLoaded && matrixData.length > 0) {\n <div class=\"matrix-section\">\n <div class=\"matrix-header\">\n <h3><i class=\"fas fa-th\"></i> Test Results Matrix</h3>\n <div class=\"matrix-header-right\">\n <div class=\"matrix-filter-input\">\n <i class=\"fas fa-search\"></i>\n <input type=\"text\"\n placeholder=\"Filter tests...\"\n [value]=\"matrixTestFilter\"\n (input)=\"onMatrixFilterInput($event)\"\n class=\"filter-input\">\n @if (matrixTestFilter) {\n <button class=\"clear-filter-btn\" (click)=\"clearMatrixFilter()\" title=\"Clear filter\">\n <i class=\"fas fa-times\"></i>\n </button>\n }\n </div>\n <span class=\"matrix-run-count\">{{ matrixData.length }} runs \u00B7 {{ getUniqueTestsFromMatrix().length }} tests</span>\n <button mjButton (click)=\"exportMatrixToCSV()\" [disabled]=\"matrixData.length === 0\" title=\"Export matrix to CSV\">\n <i class=\"fas fa-download\"></i> Export\n </button>\n </div>\n </div>\n <div class=\"matrix-scroll-container\">\n <table class=\"test-matrix\">\n <thead>\n <tr>\n <th class=\"seq-header\" (click)=\"toggleMatrixSort('sequence')\" title=\"Sort by sequence\">\n #\n <i class=\"fas\" [ngClass]=\"matrixSortBy === 'sequence' ? (matrixSortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort'\"></i>\n </th>\n <th class=\"test-name-header\" (click)=\"toggleMatrixSort('name')\" title=\"Sort by name\">\n Test\n <i class=\"fas\" [ngClass]=\"matrixSortBy === 'name' ? (matrixSortAsc ? 'fa-sort-up' : 'fa-sort-down') : 'fa-sort'\"></i>\n </th>\n @for (run of matrixData; track run) {\n <th class=\"run-header\" (click)=\"openSuiteRun(run.runId)\" [title]=\"'Click to view suite run - ' + (run.date | date:'medium')\">\n <div class=\"run-header-content\">\n @if (run.tags.length > 0) {\n <div class=\"run-tags-header\">\n @for (tag of run.tags.slice(0, 2); track tag) {\n <span class=\"tag-chip-header\">{{ tag }}</span>\n }\n @if (run.tags.length > 2) {\n <span class=\"tag-more-header\">+{{ run.tags.length - 2 }}</span>\n }\n </div>\n }\n <div class=\"run-date\">{{ getRelativeTime(run.date) }}</div>\n <div class=\"run-pass-rate\" [ngClass]=\"{'high': run.passRate >= 80, 'medium': run.passRate >= 50 && run.passRate < 80, 'low': run.passRate < 50}\">\n {{ run.passRate.toFixed(0) }}%\n </div>\n </div>\n </th>\n }\n <th class=\"spacer-header\"></th>\n </tr>\n </thead>\n <tbody>\n @for (test of getUniqueTestsFromMatrix(); track test) {\n <tr\n [class.row-selected]=\"selectedMatrixTestId === test.testId\"\n (click)=\"selectMatrixRow(test.testId)\">\n <td class=\"seq-cell\">{{ test.sequence }}</td>\n <td class=\"test-name-cell\">\n <span class=\"test-name\" [title]=\"test.testName\">{{ test.testName }}</span>\n </td>\n @for (run of matrixData; track run) {\n <td class=\"result-cell\"\n [ngClass]=\"getMatrixCellClass(getTestResultForRun(run.runId, test.testId))\"\n [class.clickable]=\"getTestResultForRun(run.runId, test.testId)\"\n [class.cell-not-run]=\"!getTestResultForRun(run.runId, test.testId)\"\n [title]=\"getTestResultForRun(run.runId, test.testId)?.status + ' - Click to view test run' || 'Not Run'\"\n (click)=\"onMatrixCellClick(getTestResultForRun(run.runId, test.testId), $event)\">\n @if (getTestResultForRun(run.runId, test.testId); as result) {\n <div class=\"cell-eval-stack\">\n <!-- Status indicator -->\n @if (evalPreferences.showExecution) {\n <span class=\"cell-status\"\n [ngClass]=\"'status-' + result.status.toLowerCase()\"\n [class.cell-skipped-status]=\"result.status === 'Skipped' || result.status === 'Pending'\"\n [title]=\"getStatusTooltip(result.status)\">\n <i class=\"fas\"\n [class.fa-check]=\"result.status === 'Passed'\"\n [class.fa-times]=\"result.status === 'Failed'\"\n [class.fa-exclamation]=\"result.status === 'Error'\"\n [class.fa-hourglass-end]=\"result.status === 'Timeout'\"\n [class.fa-forward]=\"result.status === 'Skipped'\"\n [class.fa-spinner]=\"result.status === 'Running'\"\n [class.fa-clock]=\"result.status === 'Pending'\"></i>\n </span>\n }\n <!-- Human score - slashed icon if no feedback, colored by rating if has feedback -->\n @if (evalPreferences.showHuman && !result.humanRating) {\n <span class=\"cell-human no-feedback\"\n title=\"Human Review: No rating submitted yet\">\n <i class=\"fas fa-user-slash\"></i>\n </span>\n }\n @if (evalPreferences.showHuman && result.humanRating) {\n <span class=\"cell-human has-feedback\"\n [class.rating-low]=\"result.humanRating <= 4\"\n [class.rating-medium]=\"result.humanRating >= 5 && result.humanRating <= 6\"\n [class.rating-good]=\"result.humanRating >= 7 && result.humanRating <= 8\"\n [class.rating-excellent]=\"result.humanRating >= 9\"\n [title]=\"getHumanTooltip(result.humanRating, result.humanComments)\">\n <i class=\"fas fa-user\"></i>\n <span class=\"rating-value\">{{ result.humanRating }}</span>\n </span>\n }\n <!-- Auto score - colored by percentage -->\n @if (evalPreferences.showAuto && result.score != null) {\n <span class=\"cell-auto has-score\"\n [class.score-low]=\"result.score < 0.5\"\n [class.score-medium]=\"result.score >= 0.5 && result.score < 0.7\"\n [class.score-good]=\"result.score >= 0.7 && result.score < 0.85\"\n [class.score-excellent]=\"result.score >= 0.85\"\n [title]=\"'Auto Score: ' + (result.score * 100).toFixed(0) + '% automated evaluation'\">\n <i class=\"fas fa-robot\"></i>\n <span class=\"score-value\">{{ (result.score * 100).toFixed(0) }}</span>\n </span>\n }\n @if (evalPreferences.showAuto && result.score == null) {\n <span class=\"cell-auto no-score\"\n title=\"Auto Score: No automated score available\">\n <i class=\"fas fa-robot\"></i>\n </span>\n }\n </div>\n }\n @if (!getTestResultForRun(run.runId, test.testId)) {\n <span class=\"cell-not-run-indicator\">\n <i class=\"fas fa-minus\"></i>\n </span>\n }\n </td>\n }\n <td class=\"spacer-cell\"></td>\n </tr>\n }\n </tbody>\n <!-- Footer row with totals -->\n <tfoot>\n <tr class=\"totals-row\">\n <td class=\"seq-cell totals-label\"></td>\n <td class=\"test-name-cell totals-label\">\n <strong>Totals</strong>\n </td>\n @for (run of matrixData; track run) {\n <td class=\"result-cell totals-cell\">\n <div class=\"cell-eval-stack totals-stack\">\n <!-- Status totals -->\n @if (evalPreferences.showExecution) {\n <span class=\"totals-status\">\n <span class=\"pass-count\">{{ getRunPassedCount(run) }}/{{ getRunTotalCount(run) }}</span>\n </span>\n }\n <!-- Human totals -->\n @if (evalPreferences.showHuman) {\n <span class=\"totals-human\">\n @if (getRunHumanAvg(run) != null) {\n <span class=\"avg-label\">{{ getRunHumanAvg(run)?.toFixed(1) }}</span>\n }\n <span class=\"count-label\">({{ getRunHumanCount(run) }})</span>\n </span>\n }\n <!-- Auto totals -->\n @if (evalPreferences.showAuto) {\n <span class=\"totals-auto\">\n @if (getRunAutoAvg(run) != null) {\n <span class=\"avg-label\">{{ (getRunAutoAvg(run)! * 100).toFixed(0) }}%</span>\n }\n <span class=\"count-label\">({{ getRunAutoCount(run) }})</span>\n </span>\n }\n </div>\n </td>\n }\n <td class=\"spacer-cell\"></td>\n </tr>\n </tfoot>\n </table>\n </div>\n </div>\n }\n <!-- Empty Matrix State -->\n @if (!loadingMatrix && matrixLoaded && matrixData.length === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Icon=\"fa-solid fa-table-cells\"\n Title=\"No Matrix Data\"\n Message=\"No suite runs match the current filters.\" />\n }\n }\n <!-- Chart View -->\n @if (analyticsView === 'chart') {\n <!-- Loading Chart -->\n @if (loadingMatrix) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading chart data...\"></mj-loading>\n </div>\n }\n <!-- Chart Content -->\n @if (!loadingMatrix && matrixLoaded && matrixData.length > 0) {\n <div class=\"chart-section\">\n <div class=\"chart-header\">\n <h3><i class=\"fas fa-project-diagram\"></i> Test Results Flow</h3>\n <div class=\"chart-legend\">\n <span class=\"legend-item chart-passed\"><i class=\"fas fa-check\"></i> Passed</span>\n <span class=\"legend-item chart-failed\"><i class=\"fas fa-times\"></i> Failed</span>\n <span class=\"legend-item chart-error\"><i class=\"fas fa-exclamation\"></i> Error</span>\n <span class=\"legend-item chart-skipped\"><i class=\"fas fa-forward\"></i> Skipped</span>\n </div>\n </div>\n <div class=\"chart-container\">\n <div #chartContainer class=\"d3-chart\"></div>\n </div>\n <div class=\"chart-info\">\n <i class=\"fas fa-info-circle\"></i>\n Interactive visualization showing test results across {{ matrixData.length }} runs.\n Hover over elements for details, click nodes to navigate.\n </div>\n </div>\n }\n <!-- Empty Chart State -->\n @if (!loadingMatrix && matrixLoaded && matrixData.length === 0) {\n <mj-empty-state\n Variant=\"no-results\"\n Icon=\"fa-solid fa-diagram-project\"\n Title=\"No Chart Data\"\n Message=\"No suite runs match the current filters.\" />\n }\n }\n }\n <!-- Empty State -->\n @if (!loadingAnalytics && analyticsLoaded && analyticsData.length === 0) {\n <mj-empty-state\n Icon=\"fa-solid fa-chart-line\"\n Title=\"No Analytics Data\"\n Message=\"Run this suite to start collecting analytics data.\"\n ActionText=\"Run Suite Now\"\n ActionIcon=\"fa-solid fa-play\"\n (Action)=\"runSuite()\" />\n }\n </div>\n }\n\n <!-- Compare Tab -->\n @if (activeTab === 'compare') {\n <div class=\"compare-tab\">\n <!-- Run Selection -->\n <div class=\"compare-selection\">\n <div class=\"compare-run-selector\">\n <h4>Run A (Baseline)</h4>\n @if (!loadingRuns && suiteRuns.length > 0) {\n <div class=\"run-selector-list\">\n @for (run of suiteRuns; track run) {\n <div class=\"run-selector-item\"\n [class.selected]=\"IsCompareRunA(run)\"\n (click)=\"selectCompareRunA(run)\">\n <div class=\"selector-status\" [style.background-color]=\"getRunStatusColor(run.Status)\"></div>\n <div class=\"selector-content\">\n <div class=\"selector-date\">{{ run.StartedAt | date:'short' }}</div>\n <div class=\"selector-rate\">{{ getPassRate(run).toFixed(0) }}% pass</div>\n </div>\n @if (getRunTags(run).length > 0) {\n <div class=\"selector-tags\">\n @for (tag of getRunTags(run).slice(0, 2); track tag) {\n <span class=\"tag-mini\">{{ tag }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (compareRunA) {\n <div class=\"selected-run-preview\">\n <div class=\"preview-header\">\n <span class=\"preview-label\">Selected:</span>\n <button class=\"clear-btn\" (click)=\"compareRunA = null; compareResults = []\">Clear</button>\n </div>\n <div class=\"preview-details\">\n <span>{{ compareRunA.StartedAt | date:'medium' }}</span>\n <span class=\"preview-rate\">{{ getPassRate(compareRunA).toFixed(1) }}%</span>\n </div>\n </div>\n }\n </div>\n <div class=\"compare-vs\"><i class=\"fas fa-exchange-alt\"></i></div>\n <div class=\"compare-run-selector\">\n <h4>Run B (Compare)</h4>\n @if (!loadingRuns && suiteRuns.length > 0) {\n <div class=\"run-selector-list\">\n @for (run of suiteRuns; track run) {\n <div class=\"run-selector-item\"\n [class.selected]=\"IsCompareRunB(run)\"\n [class.disabled]=\"IsCompareRunA(run)\"\n (click)=\"!IsCompareRunA(run) && selectCompareRunB(run)\">\n <div class=\"selector-status\" [style.background-color]=\"getRunStatusColor(run.Status)\"></div>\n <div class=\"selector-content\">\n <div class=\"selector-date\">{{ run.StartedAt | date:'short' }}</div>\n <div class=\"selector-rate\">{{ getPassRate(run).toFixed(0) }}% pass</div>\n </div>\n @if (getRunTags(run).length > 0) {\n <div class=\"selector-tags\">\n @for (tag of getRunTags(run).slice(0, 2); track tag) {\n <span class=\"tag-mini\">{{ tag }}</span>\n }\n </div>\n }\n </div>\n }\n </div>\n }\n @if (compareRunB) {\n <div class=\"selected-run-preview\">\n <div class=\"preview-header\">\n <span class=\"preview-label\">Selected:</span>\n <button class=\"clear-btn\" (click)=\"compareRunB = null; compareResults = []\">Clear</button>\n </div>\n <div class=\"preview-details\">\n <span>{{ compareRunB.StartedAt | date:'medium' }}</span>\n <span class=\"preview-rate\">{{ getPassRate(compareRunB).toFixed(1) }}%</span>\n </div>\n </div>\n }\n </div>\n </div>\n <!-- Comparison Results -->\n @if (compareRunA && compareRunB && !loadingCompare) {\n <div class=\"compare-results\">\n <!-- Summary Cards -->\n <div class=\"compare-summary\">\n <div class=\"compare-summary-card\">\n <div class=\"summary-label\">Pass Rate Change</div>\n <div class=\"summary-value\" [ngClass]=\"{'positive': getComparePassRateDiff()! > 0, 'negative': getComparePassRateDiff()! < 0}\">\n <i class=\"fas\" [ngClass]=\"{'fa-arrow-up': getComparePassRateDiff()! > 0, 'fa-arrow-down': getComparePassRateDiff()! < 0, 'fa-minus': getComparePassRateDiff() === 0}\"></i>\n {{ getComparePassRateDiff()! > 0 ? '+' : '' }}{{ getComparePassRateDiff()?.toFixed(1) }}%\n </div>\n </div>\n <div class=\"compare-summary-card\">\n <div class=\"summary-label\">Duration Change</div>\n <div class=\"summary-value\" [ngClass]=\"{'positive': getCompareDurationDiff()! < 0, 'negative': getCompareDurationDiff()! > 0}\">\n <i class=\"fas\" [ngClass]=\"{'fa-arrow-down': getCompareDurationDiff()! < 0, 'fa-arrow-up': getCompareDurationDiff()! > 0, 'fa-minus': getCompareDurationDiff() === 0}\"></i>\n {{ formatDuration(getAbsCompareDurationDiff()) }}\n </div>\n </div>\n <div class=\"compare-summary-card improved\">\n <div class=\"summary-label\">Improved</div>\n <div class=\"summary-value\">{{ getCompareImprovedCount() }}</div>\n </div>\n <div class=\"compare-summary-card regressed\">\n <div class=\"summary-label\">Regressed</div>\n <div class=\"summary-value\">{{ getCompareRegressedCount() }}</div>\n </div>\n </div>\n <!-- Detailed Comparison Table -->\n <div class=\"compare-table-section\">\n <h3><i class=\"fas fa-list\"></i> Test-by-Test Comparison</h3>\n <div class=\"compare-table-wrapper\">\n <table class=\"compare-table\">\n <thead>\n <tr>\n <th>Test</th>\n <th>Run A Status</th>\n <th>Run B Status</th>\n <th>Score Diff</th>\n <th>Duration Diff</th>\n <th>Change</th>\n </tr>\n </thead>\n <tbody>\n @for (result of compareResults; track result) {\n <tr [ngClass]=\"{'improved': result.runA && result.runB && result.runA.status !== 'Passed' && result.runB.status === 'Passed', 'regressed': result.runA && result.runB && result.runA.status === 'Passed' && result.runB.status !== 'Passed'}\">\n <td class=\"test-name-cell\">{{ result.testName }}</td>\n <td>\n @if (result.runA) {\n <span class=\"status-chip\" [ngClass]=\"'status-' + result.runA.status.toLowerCase()\">{{ result.runA.status }}</span>\n }\n @if (!result.runA) {\n <span class=\"status-chip status-missing\">N/A</span>\n }\n </td>\n <td>\n @if (result.runB) {\n <span class=\"status-chip\" [ngClass]=\"'status-' + result.runB.status.toLowerCase()\">{{ result.runB.status }}</span>\n }\n @if (!result.runB) {\n <span class=\"status-chip status-missing\">N/A</span>\n }\n </td>\n <td>\n @if (result.scoreDiff != null) {\n <span [ngClass]=\"{'positive': result.scoreDiff > 0, 'negative': result.scoreDiff < 0}\">\n {{ result.scoreDiff > 0 ? '+' : '' }}{{ (result.scoreDiff * 100).toFixed(1) }}%\n </span>\n }\n @if (result.scoreDiff == null) {\n <span class=\"muted\">-</span>\n }\n </td>\n <td>\n @if (result.durationDiff != null) {\n <span [ngClass]=\"{'positive': result.durationDiff < 0, 'negative': result.durationDiff > 0}\">\n {{ result.durationDiff > 0 ? '+' : '' }}{{ result.durationDiff.toFixed(1) }}s\n </span>\n }\n @if (result.durationDiff == null) {\n <span class=\"muted\">-</span>\n }\n </td>\n <td>\n @if (result.runA && result.runB && result.runA.status !== 'Passed' && result.runB.status === 'Passed') {\n <span class=\"change-indicator improved\">\n <i class=\"fas fa-arrow-up\"></i> Fixed\n </span>\n }\n @if (result.runA && result.runB && result.runA.status === 'Passed' && result.runB.status !== 'Passed') {\n <span class=\"change-indicator regressed\">\n <i class=\"fas fa-arrow-down\"></i> Broke\n </span>\n }\n @if (!result.statusChanged) {\n <span class=\"change-indicator unchanged\">\n <i class=\"fas fa-minus\"></i>\n </span>\n }\n </td>\n </tr>\n }\n </tbody>\n </table>\n </div>\n </div>\n </div>\n }\n <!-- Loading State for Compare -->\n @if (loadingCompare) {\n <div class=\"loading-state\">\n <mj-loading text=\"Loading comparison data...\"></mj-loading>\n </div>\n }\n <!-- Empty State -->\n @if (!compareRunA || !compareRunB) {\n <mj-empty-state\n Icon=\"fa-solid fa-scale-balanced\"\n Title=\"Select Two Runs to Compare\"\n Message=\"Choose a baseline run (A) and a comparison run (B) from the lists above to see a detailed side-by-side comparison.\" />\n }\n <!-- No Runs State -->\n @if (runsLoaded && suiteRuns.length < 2) {\n <mj-empty-state\n Icon=\"fa-solid fa-scale-balanced\"\n Title=\"Not Enough Runs to Compare\"\n Message=\"You need at least 2 suite runs to use the comparison feature.\"\n ActionText=\"Run Suite Now\"\n ActionIcon=\"fa-solid fa-play\"\n (Action)=\"runSuite()\" />\n }\n </div>\n }\n </div>\n\n <!-- Keyboard Shortcuts Toggle Button -->\n <button class=\"shortcuts-toggle\" (click)=\"toggleShortcuts()\" [title]=\"showShortcuts ? 'Hide keyboard shortcuts' : 'Show keyboard shortcuts'\">\n <i class=\"fas fa-keyboard\"></i>\n </button>\n\n <!-- Keyboard Shortcuts Hint (Desktop Only) -->\n @if (showShortcuts) {\n <div class=\"keyboard-shortcuts\">\n <div class=\"shortcuts-header\">\n <i class=\"fas fa-keyboard\"></i>\n Shortcuts\n <button class=\"shortcuts-close\" (click)=\"toggleShortcuts()\" title=\"Hide shortcuts\">\n <i class=\"fas fa-times\"></i>\n </button>\n </div>\n <div class=\"shortcut-list\">\n <div class=\"shortcut-item\">\n <span>Save changes</span>\n <span class=\"shortcut-keys\"><kbd>Cmd</kbd><kbd>S</kbd></span>\n </div>\n <div class=\"shortcut-item\">\n <span>Refresh</span>\n <span class=\"shortcut-keys\"><kbd>Cmd</kbd><kbd>R</kbd></span>\n </div>\n <div class=\"shortcut-item\">\n <span>Run Suite</span>\n <span class=\"shortcut-keys\"><kbd>Cmd</kbd><kbd>Enter</kbd></span>\n </div>\n <div class=\"shortcut-item\">\n <span>Switch Tabs</span>\n <span class=\"shortcut-keys\"><kbd>1</kbd>-<kbd>5</kbd></span>\n </div>\n </div>\n </div>\n }\n\n <!-- Slide Panel for Suite Execution -->\n @if (testingDialogService.IsPanelOpen) {\n <mj-slide-panel\n Mode=\"slide\"\n [Title]=\"testingDialogService.PanelOptions?.suiteId ? 'Suite Execution' : 'Run Suite'\"\n [Resizable]=\"true\"\n (Closed)=\"OnPanelClosed()\">\n <app-test-run-dialog\n [PanelMode]=\"true\"\n [selectedTestId]=\"testingDialogService.PanelOptions?.testId ?? null\"\n [selectedSuiteId]=\"testingDialogService.PanelOptions?.suiteId ?? null\"\n [runMode]=\"testingDialogService.PanelOptions?.mode ?? 'suite'\"\n (PanelClose)=\"OnPanelClosed()\">\n </app-test-run-dialog>\n </mj-slide-panel>\n }\n\n <!-- Add Tests Picker Dialog -->\n @if (showAddTestsDialog) {\n <div class=\"picker-backdrop\" (click)=\"closeAddTestsDialog()\"></div>\n <div class=\"picker-dialog\" role=\"dialog\" aria-modal=\"true\" aria-labelledby=\"add-tests-dialog-title\" (click)=\"$event.stopPropagation()\">\n <div class=\"picker-header\">\n <h2 id=\"add-tests-dialog-title\" class=\"picker-title\">\n <i class=\"fas fa-plus-circle\"></i> Add tests to suite\n </h2>\n <button type=\"button\" class=\"picker-close\" (click)=\"closeAddTestsDialog()\" [disabled]=\"isAddingTests\" aria-label=\"Close\">\n <i class=\"fas fa-times\"></i>\n </button>\n </div>\n\n <div class=\"picker-search\">\n <i class=\"fas fa-search picker-search-icon\"></i>\n <input\n #pickerSearch\n type=\"text\"\n class=\"picker-search-input\"\n placeholder=\"Search tests by name, description, or type...\"\n [(ngModel)]=\"addTestsSearch\"\n autofocus\n />\n @if (addTestsSearch) {\n <button type=\"button\" class=\"picker-search-clear\" (click)=\"addTestsSearch = ''\" aria-label=\"Clear search\">\n <i class=\"fas fa-times\"></i>\n </button>\n }\n </div>\n\n <div class=\"picker-toolbar\">\n <span class=\"picker-toolbar-status\">\n @if (selectedTestIdsToAdd.size > 0) {\n <strong>{{ selectedTestIdsToAdd.size }}</strong> selected\n } @else if (!loadingAvailableTests) {\n {{ filteredAvailableTests.length }} test{{ filteredAvailableTests.length === 1 ? '' : 's' }} available\n }\n </span>\n <div class=\"picker-toolbar-actions\">\n <button type=\"button\" class=\"picker-link-btn\" (click)=\"selectAllFiltered()\" [disabled]=\"loadingAvailableTests || filteredAvailableTests.length === 0\">\n Select all\n </button>\n <button type=\"button\" class=\"picker-link-btn\" (click)=\"clearAddSelection()\" [disabled]=\"selectedTestIdsToAdd.size === 0\">\n Clear\n </button>\n </div>\n </div>\n\n <div class=\"picker-body\">\n @if (loadingAvailableTests) {\n <div class=\"picker-loading\">\n <mj-loading text=\"Loading tests...\"></mj-loading>\n </div>\n } @else if (filteredAvailableTests.length === 0) {\n @if (availableTests.length === 0) {\n <mj-empty-state\n Variant=\"success\"\n Size=\"compact\"\n Title=\"Everything's already in the suite\"\n Message=\"This suite contains every available test. Create a new test first to add more.\" />\n } @else {\n <mj-empty-state\n Variant=\"no-results\"\n Size=\"compact\"\n Title=\"No matches\"\n [Message]=\"AddTestsNoMatchMessage\" />\n }\n } @else {\n <ul class=\"picker-list\">\n @for (t of filteredAvailableTests; track t.ID) {\n <li\n class=\"picker-row\"\n [class.picker-row--selected]=\"isAddSelected(t.ID)\"\n (click)=\"toggleAddSelection(t.ID)\"\n role=\"checkbox\"\n [attr.aria-checked]=\"isAddSelected(t.ID)\"\n tabindex=\"0\"\n >\n <span class=\"picker-check\">\n @if (isAddSelected(t.ID)) {\n <i class=\"fas fa-check\"></i>\n }\n </span>\n <div class=\"picker-row-main\">\n <div class=\"picker-row-name\">{{ t.Name }}</div>\n @if (t.Description) {\n <div class=\"picker-row-desc\">{{ t.Description }}</div>\n }\n </div>\n <div class=\"picker-row-meta\">\n @if (t.Type) {\n <span class=\"picker-row-pill\">{{ t.Type }}</span>\n }\n <span class=\"picker-row-pill picker-row-pill--status\" [attr.data-status]=\"t.Status?.toLowerCase()\">{{ t.Status }}</span>\n </div>\n </li>\n }\n </ul>\n }\n </div>\n\n <div class=\"picker-footer\">\n <button\n type=\"button\"\n class=\"picker-btn picker-btn--primary\"\n (click)=\"confirmAddTests()\"\n [disabled]=\"selectedTestIdsToAdd.size === 0 || isAddingTests\"\n >\n @if (isAddingTests) {\n <i class=\"fas fa-spinner fa-spin\"></i> Adding\u2026\n } @else {\n <i class=\"fas fa-check\"></i>\n @if (selectedTestIdsToAdd.size === 0) {\n Add tests\n } @else if (selectedTestIdsToAdd.size === 1) {\n Add 1 test\n } @else {\n Add {{ selectedTestIdsToAdd.size }} tests\n }\n }\n </button>\n <button\n type=\"button\"\n class=\"picker-btn picker-btn--ghost\"\n (click)=\"closeAddTestsDialog()\"\n [disabled]=\"isAddingTests\"\n >\n Cancel\n </button>\n </div>\n </div>\n }\n\n <!-- Sticky Save Bar \u2014 always in the DOM so CSS transitions can fire reliably.\n Visibility is controlled by the .save-bar--visible class, driven by isDirty. -->\n <div\n class=\"save-bar\"\n [class.save-bar--visible]=\"isDirty\"\n [attr.aria-hidden]=\"!isDirty\"\n role=\"region\"\n aria-label=\"Unsaved changes\"\n >\n <div class=\"save-bar-message\">\n <span class=\"save-bar-dot\" aria-hidden=\"true\">\u25CF</span>\n <span class=\"save-bar-text\">\n <strong>Unsaved changes</strong>\n <span class=\"save-bar-fields\">\n @if (dirtyFieldNames.length === 1) {\n {{ dirtyFieldNames[0] }} edited\n } @else if (dirtyFieldNames.length > 1) {\n {{ dirtyFieldNames.length }} fields edited\n }\n </span>\n </span>\n </div>\n <div class=\"save-bar-actions\">\n <button\n type=\"button\"\n class=\"save-bar-btn save-bar-btn--primary\"\n (click)=\"saveChanges()\"\n [disabled]=\"isSaving || !isDirty\"\n [attr.tabindex]=\"isDirty ? 0 : -1\"\n title=\"Save changes (\u2318S)\"\n >\n @if (isSaving) {\n <i class=\"fas fa-spinner fa-spin\"></i> Saving\u2026\n } @else {\n <i class=\"fas fa-check\"></i> Save changes\n <span class=\"save-bar-kbd\"><kbd>\u2318</kbd><kbd>S</kbd></span>\n }\n </button>\n <button\n type=\"button\"\n class=\"save-bar-btn save-bar-btn--ghost\"\n (click)=\"discardChanges()\"\n [disabled]=\"isSaving || !isDirty\"\n [attr.tabindex]=\"isDirty ? 0 : -1\"\n title=\"Discard changes\"\n >\n <i class=\"fas fa-undo\"></i> Discard\n </button>\n </div>\n </div>\n</div>\n", styles: ["/* ===========================\n Test Suite Form - World-Class UX\n =========================== */\n\n/* CSS Custom Properties for Theming - using :host for Angular encapsulation */\n:host {\n --test-primary: var(--mj-brand-primary);\n --test-primary-light: var(--mj-brand-primary);\n --test-success: var(--mj-status-success);\n --test-error: var(--mj-status-error);\n --test-warning: var(--mj-status-warning);\n --test-disabled: var(--mj-text-muted);\n --test-bg: var(--mj-bg-surface-card);\n --test-surface: var(--mj-bg-surface);\n --test-border: var(--mj-border-default);\n --test-text: var(--mj-text-primary);\n --test-text-secondary: var(--mj-text-secondary);\n --test-text-muted: var(--mj-text-disabled);\n --test-radius-sm: 6px;\n --test-radius-md: 10px;\n --test-radius-lg: 16px;\n --test-shadow-sm: var(--mj-shadow-sm);\n --test-shadow-md: var(--mj-shadow-md);\n --test-shadow-lg: var(--mj-shadow-lg);\n --test-transition: all 0.2s cubic-bezier(0.4, 0, 0.2, 1);\n display: block;\n height: 100%;\n}\n\n.test-suite-form {\n position: relative;\n display: flex;\n flex-direction: column;\n height: 100%;\n background: var(--test-bg);\n font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;\n}\n\n/* Breadcrumb */\n.breadcrumb {\n margin-bottom: 16px;\n}\n\n.breadcrumb ol {\n display: flex;\n align-items: center;\n gap: 4px;\n list-style: none;\n margin: 0;\n padding: 0;\n font-size: 13px;\n}\n\n.breadcrumb li {\n display: flex;\n align-items: center;\n gap: 4px;\n}\n\n.breadcrumb a {\n color: var(--test-primary);\n text-decoration: none;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 8px;\n border-radius: 6px;\n transition: background 0.15s;\n}\n\n.breadcrumb a:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n text-decoration: none;\n}\n\n.breadcrumb .separator {\n font-size: 10px;\n color: var(--test-text-muted);\n margin: 0 4px;\n}\n\n.breadcrumb .current {\n color: var(--test-text-secondary);\n font-weight: 500;\n}\n\n.breadcrumb-text {\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n/* Header */\n.suite-header {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n padding: 20px;\n}\n\n.header-content {\n display: flex;\n justify-content: space-between;\n align-items: flex-start;\n margin-bottom: 16px;\n gap: 16px;\n}\n\n.header-left {\n display: flex;\n gap: 16px;\n flex: 1;\n min-width: 0;\n}\n\n.suite-icon {\n width: 56px;\n height: 56px;\n border-radius: var(--test-radius-md);\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--mj-text-inverse);\n font-size: 24px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-md);\n transition: var(--test-transition);\n}\n\n.suite-icon:hover { transform: scale(1.05); }\n\n.suite-info { flex: 1; min-width: 0; }\n\n.suite-info h1 {\n margin: 0 0 8px 0;\n font-size: clamp(18px, 4vw, 24px);\n font-weight: 700;\n color: var(--test-text);\n word-wrap: break-word;\n}\n\n.suite-meta {\n display: flex;\n align-items: center;\n gap: 12px;\n flex-wrap: wrap;\n}\n\n.status-badge {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 6px 14px;\n border-radius: 20px;\n color: var(--mj-text-inverse);\n font-size: 12px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-badge.status-active { background: var(--test-success); }\n.status-badge.status-disabled { background: var(--test-disabled); }\n.status-badge.status-pending { background: var(--test-warning); }\n\n.status-badge-inline {\n display: inline-flex;\n padding: 2px 10px;\n border-radius: 10px;\n color: var(--mj-text-inverse);\n font-size: 11px;\n font-weight: 600;\n}\n\n.status-badge-inline.status-active { background: var(--test-success); }\n.status-badge-inline.status-disabled { background: var(--test-disabled); }\n.status-badge-inline.status-pending { background: var(--test-warning); }\n\n.test-count {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n font-size: 14px;\n color: var(--test-text-secondary);\n padding: 4px 10px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.header-actions {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n}\n\n.suite-description {\n padding: 16px;\n background: var(--mj-bg-surface-card);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.suite-description p {\n margin: 0;\n color: var(--test-text-secondary);\n line-height: 1.6;\n font-size: 14px;\n}\n\n/* Tabs */\n.tabs-container {\n background: var(--test-surface);\n border-bottom: 1px solid var(--test-border);\n position: sticky;\n top: 0;\n z-index: 10;\n}\n\n.tabs {\n display: flex;\n padding: 0 20px;\n overflow-x: auto;\n scrollbar-width: none;\n}\n\n.tabs::-webkit-scrollbar { display: none; }\n\n.tab {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 14px 18px;\n border: none;\n background: transparent;\n border-bottom: 3px solid transparent;\n color: var(--test-text-secondary);\n font-size: 14px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n white-space: nowrap;\n}\n\n.tab:hover {\n color: var(--test-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n}\n\n.tab.active {\n color: var(--test-primary);\n border-bottom-color: var(--test-primary);\n font-weight: 600;\n}\n\n.tab-badge {\n background: var(--test-border);\n color: var(--test-text-secondary);\n padding: 2px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.tab.active .tab-badge {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n color: var(--test-primary);\n}\n\n.tab-shortcut {\n font-size: 10px;\n color: var(--test-text-muted);\n background: var(--test-bg);\n padding: 2px 6px;\n border-radius: 4px;\n font-weight: 600;\n}\n\n/* Tab Content */\n.tab-content {\n flex: 1;\n overflow-y: auto;\n padding: 20px;\n}\n\n/* Overview Tab */\n.overview-tab {\n display: flex;\n flex-direction: column;\n gap: 20px;\n animation: fadeIn 0.3s ease-out;\n}\n\n@keyframes fadeIn {\n from { opacity: 0; transform: translateY(10px); }\n to { opacity: 1; transform: translateY(0); }\n}\n\n.info-section, .config-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.info-section h3, .config-section h3 {\n margin: 0 0 20px 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.info-section h3 i, .config-section h3 i {\n color: var(--test-primary);\n}\n\n.info-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n}\n\n.info-item {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.info-label {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.info-value {\n font-size: 14px;\n color: var(--test-text);\n font-weight: 500;\n}\n\n.config-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(250px, 1fr));\n gap: 20px;\n}\n\n.config-item {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.config-item label {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.config-input {\n padding: 10px 14px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.config-input:focus {\n outline: none;\n border-color: var(--test-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.config-hint {\n font-size: 11px;\n color: var(--test-text-muted);\n}\n\n/* Tests Tab */\n.tests-tab, .runs-tab {\n animation: fadeIn 0.3s ease-out;\n}\n\n.loading-state { padding: 0; }\n\n.skeleton-list {\n display: flex;\n flex-direction: column;\n gap: 12px;\n}\n\n.skeleton-card {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.skeleton-sequence {\n width: 32px;\n height: 32px;\n border-radius: 50%;\n background: linear-gradient(90deg, var(--mj-border-default) 25%, var(--mj-bg-surface-sunken) 50%, var(--mj-border-default) 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skeleton-icon {\n width: 40px;\n height: 40px;\n border-radius: var(--test-radius-md);\n background: linear-gradient(90deg, var(--mj-border-default) 25%, var(--mj-bg-surface-sunken) 50%, var(--mj-border-default) 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skeleton-content {\n flex: 1;\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.skeleton-line {\n height: 14px;\n border-radius: 4px;\n background: linear-gradient(90deg, var(--mj-border-default) 25%, var(--mj-bg-surface-sunken) 50%, var(--mj-border-default) 75%);\n background-size: 200% 100%;\n animation: shimmer 1.5s infinite;\n}\n\n.skeleton-line.wide { width: 70%; }\n.skeleton-line.narrow { width: 40%; }\n\n@keyframes shimmer {\n 0% { background-position: 200% 0; }\n 100% { background-position: -200% 0; }\n}\n\n.tests-list, .runs-list {\n display: flex;\n flex-direction: column;\n gap: 10px;\n}\n\n.test-item, .run-item {\n display: flex;\n align-items: center;\n gap: 14px;\n padding: 16px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.test-item:hover, .run-item:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n border-color: var(--test-primary-light);\n transform: translateX(4px);\n}\n\n.test-sequence {\n width: 32px;\n height: 32px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: var(--test-bg);\n border-radius: 50%;\n font-size: 14px;\n font-weight: 700;\n color: var(--test-text-secondary);\n flex-shrink: 0;\n}\n\n.test-icon, .run-icon {\n width: 40px;\n height: 40px;\n display: flex;\n align-items: center;\n justify-content: center;\n border-radius: var(--test-radius-md);\n color: var(--mj-text-inverse);\n font-size: 18px;\n flex-shrink: 0;\n box-shadow: var(--test-shadow-sm);\n}\n\n.test-icon {\n background: var(--test-primary);\n}\n\n.test-content, .run-content { flex: 1; min-width: 0; }\n\n.test-name, .run-header {\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n margin-bottom: 4px;\n}\n\n.run-header {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.run-id { font-weight: 600; }\n\n.run-status {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n}\n\n.test-status, .run-meta {\n display: flex;\n gap: 12px;\n font-size: 12px;\n color: var(--test-text-secondary);\n}\n\n.test-status span, .run-meta span {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n}\n\n.test-item > i, .run-item > i {\n color: var(--test-text-muted);\n font-size: 14px;\n transition: var(--test-transition);\n}\n\n.test-item:hover > i, .run-item:hover > i {\n color: var(--test-primary);\n transform: translateX(2px);\n}\n\n/* Keyboard Shortcuts */\n/* Keyboard shortcuts toggle button - visible when shortcuts are hidden */\n.shortcuts-toggle {\n position: fixed;\n bottom: 20px;\n right: 20px;\n width: 36px;\n height: 36px;\n border-radius: 50%;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n box-shadow: var(--test-shadow-md);\n cursor: pointer;\n display: flex;\n align-items: center;\n justify-content: center;\n color: var(--test-text-secondary);\n font-size: 14px;\n z-index: 99;\n transition: var(--test-transition);\n opacity: 0.7;\n}\n\n.shortcuts-toggle:hover {\n opacity: 1;\n transform: scale(1.1);\n color: var(--test-primary);\n border-color: var(--test-primary);\n}\n\n.keyboard-shortcuts {\n position: fixed;\n bottom: 20px;\n right: 20px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n padding: 12px 16px;\n box-shadow: var(--test-shadow-lg);\n font-size: 12px;\n z-index: 100;\n max-width: 260px;\n}\n\n.shortcuts-header {\n display: flex;\n align-items: center;\n gap: 6px;\n margin-bottom: 10px;\n padding-bottom: 8px;\n border-bottom: 1px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text);\n}\n\n.shortcuts-close {\n margin-left: auto;\n background: none;\n border: none;\n cursor: pointer;\n color: var(--test-text-muted);\n font-size: 12px;\n padding: 2px 4px;\n border-radius: 4px;\n transition: var(--test-transition);\n}\n\n.shortcuts-close:hover {\n color: var(--test-text);\n background: var(--test-border);\n}\n\n.shortcut-list {\n display: flex;\n flex-direction: column;\n gap: 6px;\n}\n\n.shortcut-item {\n display: flex;\n justify-content: space-between;\n align-items: center;\n color: var(--test-text-secondary);\n}\n\n.shortcut-keys {\n display: flex;\n gap: 4px;\n}\n\n.shortcut-keys kbd {\n background: var(--test-bg);\n border: 1px solid var(--test-border);\n border-radius: 4px;\n padding: 2px 6px;\n font-size: 11px;\n color: var(--test-text);\n}\n\n/* Responsive */\n@media (max-width: 1024px) {\n .keyboard-shortcuts, .shortcuts-toggle { display: none; }\n}\n\n@media (max-width: 768px) {\n .suite-header { padding: 16px; }\n\n .header-content {\n flex-direction: column;\n gap: 16px;\n }\n\n .header-actions {\n width: 100%;\n justify-content: stretch;\n }\n\n .header-actions button { flex: 1; }\n\n .tab-shortcut { display: none; }\n\n .info-grid { grid-template-columns: 1fr; }\n\n .test-item, .run-item { padding: 14px; }\n}\n\n@media (max-width: 480px) {\n .suite-icon {\n width: 40px;\n height: 40px;\n font-size: 18px;\n }\n\n .suite-info h1 { font-size: 16px; }\n\n .tab-badge { display: none; }\n\n .test-sequence { display: none; }\n}\n\n@media (hover: none) and (pointer: coarse) {\n .test-item:active, .run-item:active {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, var(--mj-bg-surface));\n transform: scale(0.98);\n }\n\n .tab { min-height: 48px; }\n .test-item, .run-item { min-height: 64px; }\n}\n\n@media (prefers-reduced-motion: reduce) {\n *, *::before, *::after {\n animation-duration: 0.01ms !important;\n transition-duration: 0.01ms !important;\n }\n}\n\n@media print {\n .header-actions, .tabs-container, .keyboard-shortcuts {\n display: none !important;\n }\n}\n\n/* ===========================\n Tags UI\n =========================== */\n.run-tags {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n margin-top: 8px;\n}\n\n.tag-chip {\n display: inline-flex;\n align-items: center;\n padding: 3px 10px;\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n border-radius: 12px;\n font-size: 11px;\n font-weight: 500;\n color: var(--mj-brand-primary-hover);\n}\n\n.tag-mini {\n display: inline-flex;\n align-items: center;\n padding: 2px 6px;\n background: var(--mj-bg-surface-sunken);\n border-radius: 4px;\n font-size: 10px;\n font-weight: 500;\n color: var(--mj-text-muted);\n}\n\n.tag-more {\n font-size: 10px;\n color: var(--mj-text-disabled);\n font-weight: 500;\n}\n\n/* Evaluation metrics row for Runs list */\n.run-eval-metrics {\n display: flex;\n align-items: center;\n gap: 8px;\n margin-top: 8px;\n}\n\n.eval-metric {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 3px 8px;\n border-radius: 6px;\n font-size: 11px;\n font-weight: 500;\n}\n\n.eval-metric.status {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.eval-metric.status.status-completed {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n}\n\n.eval-metric.status.status-failed {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n}\n\n.eval-metric.status.status-running {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary-hover);\n}\n\n.eval-metric.status.status-pending {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.eval-metric.human {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n border: 1px solid var(--mj-status-warning);\n color: var(--mj-status-warning);\n}\n\n.eval-metric.human .eval-pending {\n font-size: 9px;\n color: var(--mj-status-warning);\n}\n\n.eval-metric.auto {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n color: var(--mj-brand-primary-hover);\n}\n\n.eval-metric.auto.high {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n color: var(--mj-status-success);\n}\n\n.eval-metric.auto.medium {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-status-warning) 30%, transparent);\n color: var(--mj-status-warning);\n}\n\n.eval-metric.auto.low {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n color: var(--mj-status-error);\n}\n\n.tag-cell {\n display: flex;\n align-items: center;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.tag-chip-table {\n display: inline-block;\n padding: 3px 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n color: var(--mj-brand-primary-hover);\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 80px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* ===========================\n Analytics Tab\n =========================== */\n.analytics-tab {\n animation: fadeIn 0.3s ease-out;\n}\n\n/* Collapsible filters */\n/* Filters disclosure migrated to <mj-accordion-panel>. The bespoke\n .analytics-filters wrapper card chrome, the .filters-header bar (+ hover),\n and the .filters-header > i chevron are now owned by the accordion and were\n removed. The title-slot layout (.filters-title flex row) and font are now\n provided by the accordion title + Size=\"sm\"; the collapsed summary uses the\n shared .mj-accordion-badge (--muted --right). Only the body content\n (.filters-content) styling is kept below. */\n\n.filters-content {\n display: flex;\n flex-direction: column;\n gap: 16px;\n padding: 12px 16px 16px 16px;\n border-top: 1px solid var(--test-border);\n}\n\n.filter-group {\n display: flex;\n flex-direction: column;\n gap: 8px;\n}\n\n.filter-group label {\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.filter-buttons {\n display: flex;\n flex-wrap: wrap;\n gap: 8px;\n}\n\n.filter-btn {\n padding: 8px 16px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n background: var(--test-surface);\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.filter-btn:hover {\n border-color: var(--test-primary);\n color: var(--test-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n}\n\n.filter-btn.active {\n background: var(--test-primary);\n border-color: var(--test-primary);\n color: var(--mj-text-inverse);\n}\n\n.filter-btn.tag-btn {\n padding: 6px 12px;\n font-size: 12px;\n display: inline-flex;\n align-items: center;\n gap: 6px;\n}\n\n.filter-btn.tag-btn i.fa-check {\n font-size: 10px;\n}\n\n.filter-btn.tag-btn.all-tags-btn {\n font-weight: 600;\n}\n\n.filter-btn.tag-btn.all-tags-btn i {\n font-size: 11px;\n}\n\n.filter-hint {\n font-weight: 400;\n font-size: 10px;\n color: var(--test-primary);\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-filters {\n max-height: 120px;\n overflow-y: auto;\n}\n\n/* KPI Cards */\n.analytics-kpis {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.kpi-card {\n display: flex;\n align-items: center;\n gap: 16px;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n transition: var(--test-transition);\n}\n\n.kpi-card:hover {\n transform: translateY(-2px);\n box-shadow: var(--test-shadow-md);\n}\n\n.kpi-icon {\n width: 48px;\n height: 48px;\n display: flex;\n align-items: center;\n justify-content: center;\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n border-radius: var(--test-radius-md);\n color: var(--test-primary);\n font-size: 20px;\n}\n\n.kpi-icon.success {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--test-success);\n}\n\n.kpi-icon.info {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.kpi-icon.warning {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--test-warning);\n}\n\n.kpi-content {\n flex: 1;\n min-width: 0;\n}\n\n.kpi-value {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n line-height: 1.2;\n}\n\n.kpi-label {\n font-size: 12px;\n color: var(--test-text-secondary);\n margin-top: 2px;\n}\n\n.kpi-trend {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n font-size: 12px;\n font-weight: 600;\n margin-top: 4px;\n padding: 2px 8px;\n border-radius: 10px;\n background: var(--mj-bg-surface-sunken);\n color: var(--test-text-secondary);\n}\n\n.kpi-trend.trend-up {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--test-success);\n}\n\n.kpi-trend.trend-down {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--test-error);\n}\n\n/* Analytics Table */\n.analytics-table-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.analytics-table-section h3 {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.analytics-table-section h3 i {\n color: var(--test-primary);\n}\n\n.analytics-table-wrapper {\n overflow-x: auto;\n margin: 0 -24px;\n padding: 0 24px;\n}\n\n.analytics-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.analytics-table th {\n text-align: left;\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.analytics-table td {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.analytics-table tbody tr.clickable-row {\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.analytics-table tbody tr.clickable-row:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n}\n\n.status-chip {\n display: inline-flex;\n align-items: center;\n padding: 4px 10px;\n border-radius: 12px;\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n}\n\n.status-chip.status-completed { background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface)); color: var(--test-success); }\n.status-chip.status-passed { background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface)); color: var(--test-success); }\n.status-chip.status-failed { background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface)); color: var(--test-error); }\n.status-chip.status-error { background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface)); color: var(--test-warning); }\n.status-chip.status-running { background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface)); color: var(--test-primary); }\n.status-chip.status-pending { background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface)); color: var(--mj-brand-primary); }\n.status-chip.status-cancelled { background: var(--mj-bg-surface-sunken); color: var(--test-disabled); }\n.status-chip.status-missing { background: var(--mj-bg-surface-sunken); color: var(--test-text-muted); }\n.status-chip.status-timeout { background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface)); color: var(--test-warning); }\n.status-chip.status-skipped { background: var(--mj-bg-surface-sunken); color: var(--test-disabled); }\n\n.pass-rate-cell {\n display: flex;\n align-items: center;\n gap: 8px;\n min-width: 100px;\n}\n\n.pass-rate-bar {\n height: 6px;\n border-radius: 3px;\n background: var(--test-success);\n transition: width 0.3s ease;\n max-width: 60px;\n}\n\n.pass-rate-bar.medium { background: var(--test-warning); }\n.pass-rate-bar.low { background: var(--test-error); }\n\n/* Analytics Sub-navigation */\n.analytics-subnav {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 8px;\n margin-bottom: 20px;\n box-shadow: var(--test-shadow-sm);\n display: inline-flex;\n}\n\n.subnav-tabs {\n display: flex;\n gap: 4px;\n background: var(--test-bg);\n border-radius: var(--test-radius-md);\n padding: 4px;\n}\n\n.subnav-tab {\n display: flex;\n align-items: center;\n gap: 8px;\n padding: 10px 20px;\n border: none;\n border-radius: var(--test-radius-sm);\n background: transparent;\n color: var(--test-text-secondary);\n font-size: 13px;\n font-weight: 500;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.subnav-tab:hover {\n color: var(--test-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n}\n\n.subnav-tab.active {\n background: var(--test-surface);\n color: var(--test-primary);\n box-shadow: var(--test-shadow-sm);\n}\n\n.subnav-tab i {\n font-size: 14px;\n}\n\n/* Matrix View */\n.matrix-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 16px;\n box-shadow: var(--test-shadow-sm);\n display: flex;\n flex-direction: column;\n max-height: calc(100vh - 280px);\n min-height: 300px;\n}\n\n.matrix-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 12px;\n margin-bottom: 12px;\n padding-bottom: 12px;\n border-bottom: 1px solid var(--test-border);\n flex-shrink: 0;\n}\n\n.matrix-header-right {\n display: flex;\n align-items: center;\n gap: 12px;\n}\n\n.matrix-header h3 {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 15px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.matrix-header h3 i {\n color: var(--test-primary);\n}\n\n.matrix-run-count {\n font-size: 12px;\n color: var(--test-text-muted);\n font-weight: 500;\n}\n\n/* Matrix filter input */\n.matrix-filter-input {\n display: flex;\n align-items: center;\n gap: 8px;\n background: var(--mj-bg-surface-card);\n border: 1px solid var(--test-border);\n border-radius: 6px;\n padding: 4px 10px;\n min-width: 180px;\n}\n\n.matrix-filter-input i.fa-search {\n color: var(--mj-text-disabled);\n font-size: 12px;\n}\n\n.matrix-filter-input .filter-input {\n border: none;\n background: transparent;\n outline: none;\n font-size: 12px;\n color: var(--test-text);\n width: 100%;\n}\n\n.matrix-filter-input .filter-input::placeholder {\n color: var(--mj-text-disabled);\n}\n\n.clear-filter-btn {\n border: none;\n background: none;\n padding: 2px 4px;\n cursor: pointer;\n color: var(--mj-text-disabled);\n font-size: 10px;\n border-radius: 3px;\n transition: all 0.15s ease;\n}\n\n.clear-filter-btn:hover {\n background: var(--mj-border-default);\n color: var(--mj-text-muted);\n}\n\n/* Scrollable matrix container with fixed height */\n.matrix-scroll-container {\n flex: 1;\n overflow: auto;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n}\n\n.test-matrix {\n display: table;\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.test-matrix thead {\n display: table-header-group;\n}\n\n.test-matrix thead tr {\n display: table-row;\n}\n\n.test-matrix tbody {\n display: table-row-group;\n}\n\n.test-matrix tbody tr {\n display: table-row;\n cursor: pointer;\n transition: background-color 0.15s ease;\n}\n\n.test-matrix tbody tr:hover {\n background-color: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n}\n\n.test-matrix tbody tr.row-selected {\n background-color: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent) !important;\n}\n\n.test-matrix tbody tr.row-selected td {\n border-top: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n border-bottom: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.test-matrix tbody tr.row-selected .seq-cell,\n.test-matrix tbody tr.row-selected .test-name-cell {\n background-color: color-mix(in srgb, var(--mj-brand-primary) 12%, transparent) !important;\n}\n\n.test-matrix tfoot {\n display: table-footer-group;\n}\n\n.test-matrix th,\n.test-matrix td {\n display: table-cell;\n border: 1px solid var(--test-border);\n padding: 8px 12px;\n text-align: center;\n vertical-align: middle;\n}\n\n.test-matrix th {\n background: var(--test-bg);\n font-weight: 600;\n font-size: 11px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n position: sticky;\n top: 0;\n z-index: 10;\n border-bottom: 3px solid var(--mj-text-secondary) !important;\n}\n\n/* Sequence column - shared styles */\n.test-matrix .seq-header,\n.test-matrix .seq-cell {\n width: 36px;\n min-width: 36px;\n max-width: 36px;\n text-align: center;\n position: sticky;\n left: 0;\n font-size: 11px;\n color: var(--mj-text-muted);\n border-right: 1px solid var(--test-border);\n padding: 6px 4px !important;\n}\n\n/* Seq header - sticky top AND left, highest z-index */\n.test-matrix .seq-header {\n cursor: pointer;\n font-weight: 600;\n background: var(--test-bg);\n z-index: 12; /* Higher than other headers */\n top: 0;\n}\n\n/* Seq body cells - sticky left only, lower z-index */\n.test-matrix .seq-cell {\n background: var(--test-surface);\n z-index: 2;\n}\n\n.test-matrix .seq-header i {\n font-size: 9px;\n margin-left: 2px;\n opacity: 0.6;\n}\n\n.test-matrix .seq-header:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.test-matrix .test-name-header {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-bg);\n z-index: 11;\n border-right: 2px solid var(--test-border);\n cursor: pointer;\n}\n\n.test-matrix .test-name-header i {\n font-size: 9px;\n margin-left: 4px;\n opacity: 0.6;\n}\n\n.test-matrix .test-name-header:hover {\n background: var(--mj-bg-surface-sunken);\n}\n\n.test-matrix .run-header {\n min-width: 120px;\n width: 120px;\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n/* Spacer column absorbs extra width */\n.test-matrix .spacer-header,\n.test-matrix .spacer-cell {\n width: 100%;\n min-width: 20px;\n background: var(--test-bg);\n border: none;\n}\n\n.test-matrix .spacer-cell {\n background: var(--test-surface);\n}\n\n.test-matrix .run-header:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n}\n\n.run-header-content {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.run-date {\n font-size: 12px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.run-pass-rate {\n font-size: 11px;\n font-weight: 700;\n padding: 2px 8px;\n border-radius: 10px;\n}\n\n.run-pass-rate.high {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--test-success);\n}\n\n.run-pass-rate.medium {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--test-warning);\n}\n\n.run-pass-rate.low {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--test-error);\n}\n\n.run-tags {\n display: flex;\n gap: 4px;\n}\n\n.tag-tiny {\n font-size: 9px;\n padding: 2px 6px;\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--test-primary);\n border-radius: 8px;\n white-space: nowrap;\n max-width: 60px;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n/* Matrix column header tags - emphasized */\n.run-tags-header {\n display: flex;\n flex-wrap: wrap;\n gap: 3px;\n justify-content: center;\n margin-bottom: 4px;\n}\n\n.tag-chip-header {\n display: inline-block;\n padding: 3px 8px;\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n color: var(--mj-brand-primary-hover);\n border-radius: 10px;\n font-size: 10px;\n font-weight: 600;\n white-space: nowrap;\n max-width: 70px;\n overflow: hidden;\n text-overflow: ellipsis;\n text-transform: none;\n letter-spacing: normal;\n}\n\n.tag-more-header {\n font-size: 9px;\n color: var(--test-text-secondary);\n padding: 2px 4px;\n}\n\n.test-matrix .test-name-cell {\n text-align: left;\n min-width: 150px;\n max-width: 500px;\n width: auto;\n position: sticky;\n left: 36px;\n background: var(--test-surface);\n z-index: 2;\n border-right: 2px solid var(--test-border);\n padding: 6px 10px !important;\n}\n\n.test-name {\n display: block;\n white-space: nowrap;\n font-weight: 500;\n color: var(--test-text);\n font-size: 12px;\n}\n\n.result-cell {\n position: relative;\n min-width: 120px;\n width: 120px;\n transition: var(--test-transition);\n}\n\n.result-cell.clickable {\n cursor: pointer;\n}\n\n.result-cell.clickable:hover {\n transform: scale(1.1);\n box-shadow: var(--mj-shadow-md);\n z-index: 5;\n}\n\n.result-cell i {\n font-size: 14px;\n}\n\n/* Completed cells have neutral white background - pills tell the story */\n.result-cell.cell-passed,\n.result-cell.cell-failed,\n.result-cell.cell-error,\n.result-cell.cell-timeout,\n.result-cell.cell-running,\n.result-cell.cell-pending {\n background: var(--mj-bg-surface);\n color: var(--test-text);\n}\n\n/* Skipped/not-run cells get hatched background */\n.result-cell.cell-skipped {\n background: repeating-linear-gradient(\n 45deg,\n var(--mj-bg-surface-card),\n var(--mj-bg-surface-card) 4px,\n var(--mj-border-default) 4px,\n var(--mj-border-default) 8px\n );\n color: var(--test-disabled);\n}\n\n.result-cell.cell-none {\n background: var(--mj-bg-surface-card);\n color: var(--test-text-muted);\n}\n\n.cell-score {\n display: block;\n font-size: 10px;\n font-weight: 600;\n margin-top: 2px;\n}\n\n/* Matrix cell evaluation stack - shows multiple eval types horizontally */\n.cell-eval-stack {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n flex-wrap: nowrap;\n}\n\n.result-cell.multi-eval {\n min-width: 120px;\n width: auto;\n}\n\n/* Add timeout status icon */\n.cell-status.status-timeout {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.cell-status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n}\n\n.cell-status.status-failed {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n}\n\n.cell-status.status-error {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.cell-status.status-skipped {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.cell-status.status-running {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary-hover);\n}\n\n.cell-status.status-pending {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.cell-human {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n font-size: 10px;\n}\n\n.cell-auto {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 2px 6px;\n border-radius: 4px;\n font-size: 10px;\n font-weight: 600;\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary-hover);\n}\n\n.cell-auto.high {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n}\n\n.cell-auto.medium {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.cell-auto.low {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n}\n\n.cell-none-indicator {\n color: var(--test-text-muted);\n opacity: 0.5;\n}\n\n.matrix-info {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n}\n\n.matrix-info i {\n color: var(--test-primary);\n}\n\n/* ===========================\n Chart View\n =========================== */\n.chart-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.chart-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n flex-wrap: wrap;\n gap: 16px;\n margin-bottom: 20px;\n padding-bottom: 16px;\n border-bottom: 1px solid var(--test-border);\n}\n\n.chart-header h3 {\n display: flex;\n align-items: center;\n gap: 10px;\n margin: 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n}\n\n.chart-header h3 i {\n color: var(--test-primary);\n}\n\n.chart-legend {\n display: flex;\n gap: 16px;\n flex-wrap: wrap;\n}\n\n.chart-legend .legend-item {\n display: flex;\n align-items: center;\n gap: 6px;\n font-size: 12px;\n font-weight: 500;\n padding: 4px 10px;\n border-radius: 12px;\n}\n\n.chart-legend .legend-item.chart-passed {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--test-success);\n}\n\n.chart-legend .legend-item.chart-failed {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--test-error);\n}\n\n.chart-legend .legend-item.chart-error {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--test-warning);\n}\n\n.chart-legend .legend-item.chart-skipped {\n background: var(--mj-bg-surface-sunken);\n color: var(--test-disabled);\n}\n\n.chart-container {\n min-height: 500px;\n position: relative;\n overflow: hidden;\n background: var(--mj-bg-surface-card);\n border-radius: var(--test-radius-md);\n border: 1px solid var(--test-border);\n}\n\n.d3-chart {\n width: 100%;\n height: 500px;\n}\n\n.d3-chart svg {\n width: 100%;\n height: 100%;\n}\n\n/* D3 Chart Node Styles */\n.d3-chart .node {\n cursor: pointer;\n transition: transform 0.2s ease;\n}\n\n.d3-chart .node:hover {\n transform: scale(1.05);\n}\n\n.d3-chart .node-label {\n font-size: 11px;\n font-weight: 500;\n fill: var(--test-text);\n pointer-events: none;\n}\n\n.d3-chart .link {\n fill: none;\n stroke-opacity: 0.4;\n transition: stroke-opacity 0.2s ease;\n}\n\n.d3-chart .link:hover {\n stroke-opacity: 0.8;\n}\n\n.d3-chart .tooltip {\n position: absolute;\n padding: 10px 14px;\n background: var(--mj-bg-overlay);\n color: var(--mj-text-inverse);\n border-radius: 8px;\n font-size: 12px;\n pointer-events: none;\n z-index: 100;\n box-shadow: var(--mj-shadow-lg);\n max-width: 250px;\n}\n\n.d3-chart .tooltip-title {\n font-weight: 600;\n margin-bottom: 4px;\n}\n\n.d3-chart .tooltip-value {\n opacity: 0.8;\n}\n\n.chart-info {\n display: flex;\n align-items: center;\n gap: 8px;\n font-size: 12px;\n color: var(--test-text-muted);\n padding: 12px;\n background: var(--test-bg);\n border-radius: var(--test-radius-sm);\n margin-top: 16px;\n}\n\n.chart-info i {\n color: var(--test-primary);\n}\n\n/* ===========================\n Compare Tab\n =========================== */\n.compare-tab {\n animation: fadeIn 0.3s ease-out;\n}\n\n.compare-selection {\n display: grid;\n grid-template-columns: 1fr auto 1fr;\n gap: 24px;\n align-items: flex-start;\n margin-bottom: 24px;\n}\n\n.compare-run-selector {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-run-selector h4 {\n margin: 0 0 16px 0;\n font-size: 14px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.run-selector-list {\n max-height: 200px;\n overflow-y: auto;\n display: flex;\n flex-direction: column;\n gap: 8px;\n margin-bottom: 12px;\n}\n\n.run-selector-item {\n display: flex;\n align-items: center;\n gap: 10px;\n padding: 10px 12px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n cursor: pointer;\n transition: var(--test-transition);\n}\n\n.run-selector-item:hover {\n border-color: var(--test-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 5%, transparent);\n}\n\n.run-selector-item.selected {\n border-color: var(--test-primary);\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n}\n\n.run-selector-item.disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.selector-status {\n width: 8px;\n height: 8px;\n border-radius: 50%;\n flex-shrink: 0;\n}\n\n.selector-content {\n flex: 1;\n min-width: 0;\n}\n\n.selector-date {\n font-size: 12px;\n font-weight: 500;\n color: var(--test-text);\n}\n\n.selector-rate {\n font-size: 11px;\n color: var(--test-text-secondary);\n}\n\n.selector-tags {\n display: flex;\n gap: 4px;\n flex-wrap: wrap;\n}\n\n.selected-run-preview {\n padding: 12px;\n background: var(--mj-bg-surface-card);\n border-radius: var(--test-radius-sm);\n border: 1px solid var(--test-border);\n}\n\n.preview-header {\n display: flex;\n justify-content: space-between;\n align-items: center;\n margin-bottom: 8px;\n}\n\n.preview-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n}\n\n.clear-btn {\n padding: 4px 8px;\n border: none;\n background: transparent;\n color: var(--test-error);\n font-size: 11px;\n font-weight: 500;\n cursor: pointer;\n}\n\n.clear-btn:hover { text-decoration: underline; }\n\n.preview-details {\n display: flex;\n justify-content: space-between;\n font-size: 12px;\n color: var(--test-text);\n}\n\n.preview-rate {\n font-weight: 600;\n color: var(--test-success);\n}\n\n.compare-vs {\n display: flex;\n align-items: center;\n justify-content: center;\n width: 40px;\n height: 40px;\n background: var(--test-bg);\n border-radius: 50%;\n color: var(--test-text-muted);\n font-size: 16px;\n align-self: center;\n margin-top: 60px;\n}\n\n/* Compare Results */\n.compare-results {\n animation: fadeIn 0.3s ease-out;\n}\n\n.compare-summary {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 16px;\n margin-bottom: 24px;\n}\n\n.compare-summary-card {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 20px;\n box-shadow: var(--test-shadow-sm);\n text-align: center;\n}\n\n.compare-summary-card .summary-label {\n font-size: 11px;\n font-weight: 600;\n text-transform: uppercase;\n color: var(--test-text-muted);\n margin-bottom: 8px;\n}\n\n.compare-summary-card .summary-value {\n font-size: 24px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n justify-content: center;\n gap: 6px;\n}\n\n.compare-summary-card .summary-value.positive { color: var(--test-success); }\n.compare-summary-card .summary-value.negative { color: var(--test-error); }\n\n.compare-summary-card.improved {\n border-left: 4px solid var(--test-success);\n}\n\n.compare-summary-card.improved .summary-value { color: var(--test-success); }\n\n.compare-summary-card.regressed {\n border-left: 4px solid var(--test-error);\n}\n\n.compare-summary-card.regressed .summary-value { color: var(--test-error); }\n\n/* Compare Table */\n.compare-table-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.compare-table-section h3 {\n margin: 0 0 16px 0;\n font-size: 16px;\n font-weight: 600;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.compare-table-section h3 i {\n color: var(--test-primary);\n}\n\n.compare-table-wrapper {\n overflow-x: auto;\n}\n\n.compare-table {\n width: 100%;\n border-collapse: collapse;\n font-size: 13px;\n}\n\n.compare-table th {\n text-align: left;\n padding: 12px 16px;\n background: var(--mj-bg-surface-card);\n border-bottom: 2px solid var(--test-border);\n font-weight: 600;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n font-size: 11px;\n letter-spacing: 0.5px;\n white-space: nowrap;\n}\n\n.compare-table td {\n padding: 14px 16px;\n border-bottom: 1px solid var(--test-border);\n color: var(--test-text);\n}\n\n.compare-table tbody tr.improved {\n background: color-mix(in srgb, var(--mj-status-success) 5%, transparent);\n}\n\n.compare-table tbody tr.regressed {\n background: color-mix(in srgb, var(--mj-status-error) 5%, transparent);\n}\n\n.test-name-cell {\n font-weight: 500;\n max-width: 200px;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.positive { color: var(--test-success); }\n.negative { color: var(--test-error); }\n.muted { color: var(--test-text-muted); }\n\n.change-indicator {\n display: inline-flex;\n align-items: center;\n gap: 4px;\n padding: 4px 8px;\n border-radius: 10px;\n font-size: 11px;\n font-weight: 600;\n}\n\n.change-indicator.improved {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--test-success);\n}\n\n.change-indicator.regressed {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--test-error);\n}\n\n.change-indicator.unchanged {\n background: var(--mj-bg-surface-sunken);\n color: var(--test-text-muted);\n}\n\n/* ===========================\n Responsive Analytics/Compare\n =========================== */\n@media (max-width: 1024px) {\n .compare-selection {\n grid-template-columns: 1fr;\n gap: 16px;\n }\n\n .compare-vs {\n margin: 0;\n align-self: center;\n justify-self: center;\n }\n\n .analytics-kpis {\n grid-template-columns: repeat(2, 1fr);\n }\n}\n\n@media (max-width: 768px) {\n .filter-buttons {\n flex-direction: column;\n }\n\n .filter-btn {\n width: 100%;\n text-align: center;\n }\n\n .analytics-kpis {\n grid-template-columns: 1fr;\n }\n\n .kpi-card {\n padding: 16px;\n }\n\n .kpi-value {\n font-size: 20px;\n }\n\n .compare-summary {\n grid-template-columns: repeat(2, 1fr);\n }\n\n .analytics-table-wrapper,\n .compare-table-wrapper {\n margin: 0 -20px;\n padding: 0 20px;\n }\n\n .run-selector-list {\n max-height: 150px;\n }\n}\n\n@media (max-width: 480px) {\n .compare-summary {\n grid-template-columns: 1fr;\n }\n\n .compare-summary-card .summary-value {\n font-size: 20px;\n }\n\n .analytics-table th,\n .analytics-table td,\n .compare-table th,\n .compare-table td {\n padding: 10px 12px;\n font-size: 12px;\n }\n}\n\n/* ===========================\n Matrix Evaluation Indicators\n =========================== */\n\n/* Human Feedback Indicators */\n.cell-human {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-human.no-feedback {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-disabled);\n}\n\n.cell-human.no-feedback i {\n font-size: 11px;\n}\n\n.cell-human.has-feedback {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-human.has-feedback i {\n font-size: 9px;\n}\n\n.cell-human.has-feedback .rating-value {\n font-weight: 700;\n font-size: 11px;\n}\n\n/* Human rating color coding: red \u22644, yellow 5-6, light-green 7-8, green 9-10 */\n.cell-human.rating-low {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n}\n\n.cell-human.rating-medium {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.cell-human.rating-good {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n}\n\n.cell-human.rating-excellent {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n}\n\n/* Auto Score Indicators */\n.cell-auto {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n gap: 3px;\n min-width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 10px;\n}\n\n.cell-auto.no-score {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-disabled);\n}\n\n.cell-auto.no-score i {\n font-size: 11px;\n}\n\n.cell-auto.has-score {\n padding: 0 6px;\n border-radius: 12px;\n min-width: 36px;\n}\n\n.cell-auto.has-score i {\n font-size: 9px;\n}\n\n.cell-auto.has-score .score-value {\n font-weight: 700;\n font-size: 10px;\n}\n\n/* Auto score color coding (0-100%): red <50, yellow 50-69, light-green 70-84, green 85+ */\n.cell-auto.score-low {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n}\n\n.cell-auto.score-medium {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.cell-auto.score-good {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n}\n\n.cell-auto.score-excellent {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n}\n\n/* Status indicators in matrix cells */\n.cell-status {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n border-radius: 50%;\n font-size: 11px;\n}\n\n.cell-status.status-passed {\n background: color-mix(in srgb, var(--mj-status-success) 15%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n}\n\n.cell-status.status-failed {\n background: color-mix(in srgb, var(--mj-status-error) 15%, var(--mj-bg-surface));\n color: var(--mj-status-error);\n}\n\n.cell-status.status-error {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.cell-status.status-timeout {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.cell-status.status-skipped,\n.cell-status.status-pending {\n background: var(--mj-bg-surface-sunken);\n color: var(--mj-text-muted);\n}\n\n.cell-status.status-running {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n/* Not-run / Skipped cells with hatch pattern */\n.result-cell.cell-not-run {\n background: repeating-linear-gradient(\n 45deg,\n var(--mj-bg-surface-card),\n var(--mj-bg-surface-card) 4px,\n var(--mj-border-default) 4px,\n var(--mj-border-default) 8px\n );\n color: var(--mj-text-disabled);\n}\n\n.result-cell.cell-not-run .cell-eval-stack {\n opacity: 0.6;\n}\n\n.cell-not-run-indicator {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 22px;\n color: var(--mj-text-disabled);\n font-size: 11px;\n}\n\n/* ===========================\n Matrix Totals Footer Row\n =========================== */\n.test-matrix tfoot {\n position: sticky;\n bottom: 0;\n z-index: 2;\n}\n\n.totals-row {\n background: var(--mj-bg-surface-sunken);\n border-top: 2px solid var(--test-border);\n}\n\n.totals-row td {\n padding: 10px 12px;\n font-weight: 600;\n}\n\n.totals-row .totals-label {\n background: var(--mj-bg-surface-sunken);\n font-size: 12px;\n color: var(--test-text-secondary);\n text-transform: uppercase;\n letter-spacing: 0.5px;\n}\n\n.totals-row .totals-cell {\n background: var(--mj-bg-surface-sunken);\n}\n\n.totals-stack {\n display: flex;\n flex-direction: column;\n align-items: center;\n gap: 4px;\n}\n\n.totals-status,\n.totals-human,\n.totals-auto {\n display: flex;\n align-items: center;\n gap: 4px;\n font-size: 11px;\n padding: 2px 8px;\n border-radius: 10px;\n white-space: nowrap;\n}\n\n.totals-status {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary);\n}\n\n.totals-status .pass-count {\n font-weight: 700;\n}\n\n.totals-human {\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning);\n}\n\n.totals-human .avg-label {\n font-weight: 700;\n}\n\n.totals-human .count-label {\n font-size: 10px;\n opacity: 0.8;\n}\n\n.totals-auto {\n background: color-mix(in srgb, var(--mj-brand-primary) 15%, var(--mj-bg-surface));\n color: var(--mj-brand-primary-hover);\n}\n\n.totals-auto .avg-label {\n font-weight: 700;\n}\n\n.totals-auto .count-label {\n font-size: 10px;\n opacity: 0.8;\n}\n\n/* ===========================\n Editable Overview Form\n =========================== */\n\n.edit-section {\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n padding: 24px;\n box-shadow: var(--test-shadow-sm);\n}\n\n.edit-section-header {\n margin: 0 0 18px 0;\n}\n\n.edit-section-header h3 {\n margin: 0 0 4px 0;\n font-size: 18px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.edit-section-header h3 i {\n color: var(--test-primary);\n}\n\n.edit-section-sub {\n margin: 0;\n font-size: 12px;\n color: var(--test-text-muted);\n}\n\n.edit-grid {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: 18px;\n}\n\n@media (max-width: 720px) {\n .edit-grid {\n grid-template-columns: 1fr;\n }\n}\n\n.edit-field {\n display: flex;\n flex-direction: column;\n gap: 6px;\n min-width: 0;\n}\n\n.edit-field--full {\n grid-column: 1 / -1;\n}\n\n.edit-field label {\n font-size: 12px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.edit-field .required {\n color: var(--mj-status-error);\n margin-left: 2px;\n}\n\n.config-textarea {\n resize: vertical;\n min-height: 72px;\n font-family: inherit;\n line-height: 1.5;\n}\n\n.select-wrapper {\n position: relative;\n}\n\n.select-wrapper select.config-input {\n padding-right: 36px;\n appearance: none;\n background-color: var(--test-surface);\n background-image: url(\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' width='12' height='8' viewBox='0 0 12 8'%3E%3Cpath fill='%2364748b' d='M1.41 0L6 4.59 10.59 0 12 1.41l-6 6-6-6z'/%3E%3C/svg%3E\");\n background-repeat: no-repeat;\n background-position: right 14px center;\n background-size: 10px 7px;\n}\n\n/* Tag editor (chips + inline input) */\n.tag-editor {\n display: flex;\n flex-wrap: wrap;\n gap: 6px;\n padding: 8px 10px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n background: var(--test-surface);\n transition: var(--test-transition);\n cursor: text;\n min-height: 42px;\n align-items: center;\n}\n\n.tag-editor:focus-within {\n border-color: var(--test-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.tag-chip-editable {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 3px 4px 3px 10px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary-hover);\n border-radius: 999px;\n font-size: 12px;\n font-weight: 600;\n line-height: 1;\n border: 1px solid color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n}\n\n.tag-chip-editable .tag-remove {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n border: none;\n background: transparent;\n color: var(--mj-brand-primary);\n cursor: pointer;\n font-size: 10px;\n transition: background 0.15s;\n}\n\n.tag-chip-editable .tag-remove:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 25%, transparent);\n color: var(--mj-brand-primary-hover);\n}\n\n.tag-input-inline {\n flex: 1;\n min-width: 140px;\n border: none;\n background: transparent;\n outline: none;\n font-size: 13px;\n color: var(--test-text);\n padding: 4px 2px;\n}\n\n.tag-input-inline::placeholder {\n color: var(--test-text-muted);\n}\n\n/* Read-only metadata section */\n.meta-section {\n background: var(--mj-bg-surface-sunken);\n border-radius: var(--test-radius-md);\n padding: 14px 18px;\n border: 1px solid var(--test-border);\n}\n\n.meta-grid {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(180px, 1fr));\n gap: 14px;\n}\n\n.meta-item {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.meta-label {\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.5px;\n color: var(--test-text-muted);\n}\n\n.meta-value {\n font-size: 12px;\n color: var(--test-text-secondary);\n font-weight: 500;\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.meta-mono {\n font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;\n font-size: 11px;\n}\n\n/* Dirty indicator next to the title */\n.dirty-indicator {\n display: inline-block;\n margin-left: 8px;\n font-size: 14px;\n color: var(--mj-status-warning);\n line-height: 1;\n animation: pulseDot 1.8s ease-in-out infinite;\n vertical-align: middle;\n}\n\n@keyframes pulseDot {\n 0%, 100% { opacity: 1; transform: scale(1); }\n 50% { opacity: 0.5; transform: scale(0.85); }\n}\n\n.unsaved-pill {\n display: inline-flex;\n align-items: center;\n gap: 6px;\n padding: 4px 10px;\n background: color-mix(in srgb, var(--mj-status-warning) 15%, var(--mj-bg-surface));\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n border: 1px solid color-mix(in srgb, var(--mj-status-warning) 30%, transparent);\n border-radius: 999px;\n font-size: 11px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.4px;\n}\n\n.unsaved-pill i {\n font-size: 6px;\n animation: pulseDot 1.8s ease-in-out infinite;\n}\n\n/* Pad the bottom of the tab content when the save bar is visible\n so the bar doesn't cover the last form field. */\n.tab-content.has-savebar {\n padding-bottom: 84px;\n}\n\n/* ===========================\n Sticky Save Bar\n =========================== */\n\n.save-bar {\n position: absolute;\n left: 16px;\n right: 96px; /* leaves room for the bottom-right chat launcher */\n bottom: 16px;\n z-index: 50;\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 16px;\n padding: 12px 16px 12px 20px;\n background: var(--mj-bg-surface-elevated, var(--test-surface));\n border: 1px solid color-mix(in srgb, var(--mj-status-warning) 25%, var(--test-border));\n border-radius: 14px;\n box-shadow: 0 12px 32px color-mix(in srgb, var(--mj-status-warning) 10%, rgba(0, 0, 0, 0.18));\n backdrop-filter: blur(8px);\n /* Hidden / off-screen state by default. The .save-bar--visible class\n animates it up into view. Using `transition` (not `animation`)\n guarantees the change runs every time isDirty toggles. */\n opacity: 0;\n transform: translateY(calc(100% + 32px));\n pointer-events: none;\n transition:\n transform 420ms cubic-bezier(0.16, 1, 0.3, 1),\n opacity 260ms cubic-bezier(0.16, 1, 0.3, 1);\n will-change: transform, opacity;\n}\n\n.save-bar--visible {\n opacity: 1;\n transform: translateY(0);\n pointer-events: auto;\n}\n\n@media (prefers-reduced-motion: reduce) {\n .save-bar {\n transition: opacity 180ms ease-out;\n transform: none;\n }\n .save-bar--visible {\n transform: none;\n }\n}\n\n.save-bar-message {\n display: flex;\n align-items: center;\n gap: 12px;\n min-width: 0;\n}\n\n.save-bar-dot {\n color: var(--mj-status-warning);\n font-size: 12px;\n line-height: 1;\n animation: pulseDot 1.8s ease-in-out infinite;\n}\n\n.save-bar-text {\n display: flex;\n flex-direction: column;\n gap: 2px;\n min-width: 0;\n}\n\n.save-bar-text strong {\n font-size: 14px;\n color: var(--test-text);\n font-weight: 700;\n}\n\n.save-bar-fields {\n font-size: 12px;\n color: var(--test-text-muted);\n}\n\n.save-bar-actions {\n display: flex;\n align-items: center;\n gap: 8px;\n flex-shrink: 0;\n}\n\n.save-bar-btn {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 9px 16px;\n font-size: 13px;\n font-weight: 600;\n border-radius: 8px;\n border: 1px solid transparent;\n cursor: pointer;\n transition: var(--test-transition);\n font-family: inherit;\n}\n\n.save-bar-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.save-bar-btn--primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse, white);\n border-color: var(--mj-brand-primary);\n box-shadow: 0 2px 8px color-mix(in srgb, var(--mj-brand-primary) 35%, transparent);\n}\n\n.save-bar-btn--primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover, var(--mj-brand-primary));\n transform: translateY(-1px);\n box-shadow: 0 4px 12px color-mix(in srgb, var(--mj-brand-primary) 45%, transparent);\n}\n\n.save-bar-btn--primary:active:not(:disabled) {\n transform: translateY(0);\n}\n\n.save-bar-btn--ghost {\n background: transparent;\n color: var(--test-text-secondary);\n border-color: var(--test-border);\n}\n\n.save-bar-btn--ghost:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover, var(--mj-bg-surface-card));\n color: var(--test-text);\n border-color: var(--test-text-muted);\n}\n\n.save-bar-kbd {\n display: inline-flex;\n gap: 3px;\n margin-left: 6px;\n opacity: 0.85;\n}\n\n.save-bar-kbd kbd {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 18px;\n height: 18px;\n padding: 0 4px;\n font-size: 10px;\n font-weight: 600;\n font-family: ui-monospace, SFMono-Regular, 'SF Mono', Menlo, monospace;\n background: color-mix(in srgb, white 18%, transparent);\n border: 1px solid color-mix(in srgb, white 35%, transparent);\n border-radius: 4px;\n color: inherit;\n}\n\n@media (max-width: 640px) {\n .save-bar {\n left: 8px;\n right: 8px;\n bottom: 88px; /* push above the chat launcher on small screens */\n flex-direction: column;\n align-items: stretch;\n padding: 12px;\n }\n .save-bar-actions {\n justify-content: space-between;\n }\n .save-bar-kbd {\n display: none;\n }\n}\n\n/* ===========================\n Tests Tab \u2014 Action Bar + Drag/Remove\n =========================== */\n\n.tests-actionbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: 12px;\n padding: 12px 16px;\n margin-bottom: 16px;\n background: var(--test-surface);\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-md);\n}\n\n.tests-actionbar-text {\n display: flex;\n align-items: center;\n gap: 10px;\n flex-wrap: wrap;\n min-width: 0;\n}\n\n.tests-actionbar-text strong {\n font-size: 14px;\n color: var(--test-text);\n font-weight: 700;\n}\n\n.tests-actionbar-count {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 22px;\n height: 22px;\n padding: 0 8px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--mj-bg-surface));\n color: var(--mj-brand-primary-hover);\n font-size: 12px;\n font-weight: 700;\n}\n\n.tests-actionbar-hint {\n font-size: 12px;\n color: var(--test-text-muted);\n}\n\n.tests-actionbar-actions {\n display: flex;\n gap: 8px;\n flex-shrink: 0;\n}\n\n/* Override the default test-item hover translate so drag transforms don't fight it. */\n.tests-list[cdk-drop-list] .test-item:hover,\n.tests-list .test-item.cdk-drag:hover {\n transform: none;\n}\n\n/* The whole row is draggable. .test-content keeps the click-to-open behavior. */\n.test-item.cdk-drag {\n position: relative;\n}\n\n.test-item .test-content {\n cursor: pointer;\n}\n\n/* Drag handle */\n.test-drag-handle {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 22px;\n height: 28px;\n border-radius: 4px;\n color: var(--test-text-muted);\n cursor: grab;\n flex-shrink: 0;\n transition: background 0.15s, color 0.15s;\n user-select: none;\n}\n\n.test-drag-handle:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 10%, transparent);\n color: var(--test-primary);\n}\n\n.test-drag-handle:active,\n.cdk-drag-preview .test-drag-handle {\n cursor: grabbing;\n}\n\n/* Remove (X) button \u2014 hover-revealed */\n.test-remove-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 28px;\n height: 28px;\n border-radius: 50%;\n border: none;\n background: transparent;\n color: var(--test-text-muted);\n cursor: pointer;\n flex-shrink: 0;\n opacity: 0;\n transition: opacity 0.15s, background 0.15s, color 0.15s;\n}\n\n.test-item:hover .test-remove-btn,\n.test-remove-btn:focus-visible {\n opacity: 1;\n}\n\n.test-remove-btn:hover {\n background: color-mix(in srgb, var(--mj-status-error) 12%, transparent);\n color: var(--mj-status-error);\n}\n\n.test-chevron {\n cursor: pointer;\n}\n\n/* Inline remove-confirm */\n.test-remove-confirm {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 4px 8px 4px 12px;\n border-radius: 999px;\n background: color-mix(in srgb, var(--mj-status-error) 8%, var(--test-surface));\n border: 1px solid color-mix(in srgb, var(--mj-status-error) 30%, transparent);\n flex-shrink: 0;\n}\n\n.test-remove-confirm-text {\n font-size: 12px;\n font-weight: 600;\n color: var(--mj-status-error-text, var(--mj-status-error));\n}\n\n.test-remove-confirm-btn {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 4px 10px;\n border-radius: 6px;\n border: 1px solid transparent;\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n font-family: inherit;\n transition: var(--test-transition);\n}\n\n.test-remove-confirm-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.test-remove-confirm-btn--danger {\n background: var(--mj-status-error);\n color: var(--mj-text-inverse, white);\n}\n\n.test-remove-confirm-btn--danger:hover:not(:disabled) {\n background: color-mix(in srgb, var(--mj-status-error) 88%, black);\n}\n\n.test-remove-confirm-btn--ghost {\n background: transparent;\n color: var(--test-text-secondary);\n border-color: var(--test-border);\n}\n\n.test-remove-confirm-btn--ghost:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover, var(--mj-bg-surface-card));\n color: var(--test-text);\n}\n\n/* CDK drag-drop visuals */\n.cdk-drag-preview {\n box-shadow: 0 12px 32px rgba(0, 0, 0, 0.25);\n border-radius: var(--test-radius-md);\n background: var(--test-surface);\n opacity: 0.95;\n}\n\n.cdk-drag-placeholder {\n opacity: 0;\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, transparent);\n border: 2px dashed var(--test-primary);\n border-radius: var(--test-radius-md);\n}\n\n.cdk-drag-placeholder * {\n visibility: hidden;\n}\n\n.cdk-drag-animating {\n transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n.tests-list.cdk-drop-list-dragging .test-item:not(.cdk-drag-placeholder) {\n transition: transform 250ms cubic-bezier(0, 0, 0.2, 1);\n}\n\n/* ===========================\n Add Tests Picker Dialog\n =========================== */\n\n.picker-backdrop {\n position: fixed;\n inset: 0;\n background: rgba(0, 0, 0, 0.45);\n z-index: 200;\n animation: pickerFadeIn 180ms ease-out;\n backdrop-filter: blur(2px);\n}\n\n@keyframes pickerFadeIn {\n from { opacity: 0; }\n to { opacity: 1; }\n}\n\n.picker-dialog {\n position: fixed;\n top: 50%;\n left: 50%;\n transform: translate(-50%, -50%);\n width: min(720px, calc(100vw - 32px));\n max-height: min(80vh, 720px);\n z-index: 201;\n display: flex;\n flex-direction: column;\n background: var(--test-surface);\n border-radius: var(--test-radius-lg);\n box-shadow: 0 24px 64px rgba(0, 0, 0, 0.28);\n animation: pickerSlideUp 220ms cubic-bezier(0.16, 1, 0.3, 1);\n overflow: hidden;\n}\n\n@keyframes pickerSlideUp {\n from {\n opacity: 0;\n transform: translate(-50%, calc(-50% + 16px));\n }\n to {\n opacity: 1;\n transform: translate(-50%, -50%);\n }\n}\n\n.picker-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 18px 20px;\n border-bottom: 1px solid var(--test-border);\n}\n\n.picker-title {\n margin: 0;\n font-size: 16px;\n font-weight: 700;\n color: var(--test-text);\n display: flex;\n align-items: center;\n gap: 8px;\n}\n\n.picker-title i {\n color: var(--test-primary);\n}\n\n.picker-close {\n width: 32px;\n height: 32px;\n border-radius: 8px;\n border: none;\n background: transparent;\n color: var(--test-text-muted);\n cursor: pointer;\n font-size: 14px;\n transition: background 0.15s, color 0.15s;\n}\n\n.picker-close:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover, var(--mj-bg-surface-card));\n color: var(--test-text);\n}\n\n.picker-close:disabled {\n opacity: 0.5;\n cursor: not-allowed;\n}\n\n.picker-search {\n position: relative;\n padding: 14px 20px 0 20px;\n}\n\n.picker-search-icon {\n position: absolute;\n top: 50%;\n left: 32px;\n transform: translateY(-30%);\n color: var(--test-text-muted);\n font-size: 13px;\n pointer-events: none;\n}\n\n.picker-search-input {\n width: 100%;\n padding: 10px 36px 10px 36px;\n border: 1px solid var(--test-border);\n border-radius: var(--test-radius-sm);\n background: var(--test-surface);\n font-size: 14px;\n color: var(--test-text);\n font-family: inherit;\n transition: var(--test-transition);\n}\n\n.picker-search-input:focus {\n outline: none;\n border-color: var(--test-primary);\n box-shadow: 0 0 0 3px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.picker-search-clear {\n position: absolute;\n top: 50%;\n right: 32px;\n transform: translateY(-30%);\n width: 22px;\n height: 22px;\n border-radius: 50%;\n border: none;\n background: transparent;\n color: var(--test-text-muted);\n cursor: pointer;\n font-size: 11px;\n display: inline-flex;\n align-items: center;\n justify-content: center;\n}\n\n.picker-search-clear:hover {\n background: var(--mj-bg-surface-hover, var(--mj-bg-surface-card));\n color: var(--test-text);\n}\n\n.picker-toolbar {\n display: flex;\n align-items: center;\n justify-content: space-between;\n padding: 10px 20px;\n border-bottom: 1px solid var(--test-border);\n}\n\n.picker-toolbar-status {\n font-size: 12px;\n color: var(--test-text-secondary);\n}\n\n.picker-toolbar-status strong {\n color: var(--mj-brand-primary-hover);\n font-weight: 700;\n}\n\n.picker-toolbar-actions {\n display: flex;\n gap: 14px;\n}\n\n.picker-link-btn {\n border: none;\n background: transparent;\n color: var(--test-primary);\n font-size: 12px;\n font-weight: 600;\n cursor: pointer;\n padding: 0;\n font-family: inherit;\n}\n\n.picker-link-btn:hover:not(:disabled) {\n text-decoration: underline;\n}\n\n.picker-link-btn:disabled {\n color: var(--test-text-muted);\n cursor: not-allowed;\n}\n\n.picker-body {\n flex: 1;\n overflow-y: auto;\n padding: 8px 12px;\n min-height: 200px;\n}\n\n.picker-loading {\n display: flex;\n align-items: center;\n justify-content: center;\n padding: 40px;\n}\n\n.picker-list {\n list-style: none;\n padding: 0;\n margin: 0;\n display: flex;\n flex-direction: column;\n gap: 4px;\n}\n\n.picker-row {\n display: flex;\n align-items: center;\n gap: 12px;\n padding: 10px 12px;\n border-radius: 8px;\n cursor: pointer;\n transition: background 0.12s, border-color 0.12s;\n border: 1px solid transparent;\n}\n\n.picker-row:hover {\n background: var(--mj-bg-surface-hover, var(--mj-bg-surface-card));\n}\n\n.picker-row:focus-visible {\n outline: none;\n border-color: var(--test-primary);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--mj-brand-primary) 15%, transparent);\n}\n\n.picker-row--selected {\n background: color-mix(in srgb, var(--mj-brand-primary) 8%, var(--test-surface));\n border-color: color-mix(in srgb, var(--mj-brand-primary) 30%, transparent);\n}\n\n.picker-row--selected:hover {\n background: color-mix(in srgb, var(--mj-brand-primary) 12%, var(--test-surface));\n}\n\n.picker-check {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 20px;\n height: 20px;\n border-radius: 5px;\n border: 1.5px solid var(--test-border);\n background: var(--test-surface);\n color: var(--mj-text-inverse, white);\n font-size: 11px;\n flex-shrink: 0;\n transition: background 0.12s, border-color 0.12s;\n}\n\n.picker-row--selected .picker-check {\n background: var(--mj-brand-primary);\n border-color: var(--mj-brand-primary);\n}\n\n.picker-row-main {\n flex: 1;\n min-width: 0;\n}\n\n.picker-row-name {\n font-size: 13px;\n font-weight: 600;\n color: var(--test-text);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n}\n\n.picker-row-desc {\n font-size: 12px;\n color: var(--test-text-muted);\n white-space: nowrap;\n overflow: hidden;\n text-overflow: ellipsis;\n margin-top: 2px;\n}\n\n.picker-row-meta {\n display: flex;\n align-items: center;\n gap: 6px;\n flex-shrink: 0;\n}\n\n.picker-row-pill {\n display: inline-flex;\n align-items: center;\n padding: 2px 8px;\n border-radius: 999px;\n font-size: 10px;\n font-weight: 700;\n text-transform: uppercase;\n letter-spacing: 0.3px;\n background: var(--mj-bg-surface-sunken, var(--test-bg));\n color: var(--test-text-secondary);\n border: 1px solid var(--test-border);\n}\n\n.picker-row-pill--status[data-status=\"active\"] {\n background: color-mix(in srgb, var(--mj-status-success) 12%, var(--mj-bg-surface));\n color: var(--mj-status-success);\n border-color: color-mix(in srgb, var(--mj-status-success) 30%, transparent);\n}\n\n.picker-row-pill--status[data-status=\"disabled\"] {\n background: var(--mj-bg-surface-sunken, var(--test-bg));\n color: var(--test-text-muted);\n}\n\n.picker-row-pill--status[data-status=\"pending\"] {\n background: color-mix(in srgb, var(--mj-status-warning) 12%, var(--mj-bg-surface));\n color: var(--mj-status-warning-text, var(--mj-status-warning));\n border-color: color-mix(in srgb, var(--mj-status-warning) 30%, transparent);\n}\n\n.picker-footer {\n display: flex;\n gap: 8px;\n padding: 14px 20px;\n border-top: 1px solid var(--test-border);\n background: var(--mj-bg-surface-sunken, var(--test-bg));\n}\n\n.picker-btn {\n display: inline-flex;\n align-items: center;\n gap: 8px;\n padding: 9px 16px;\n font-size: 13px;\n font-weight: 600;\n border-radius: 8px;\n border: 1px solid transparent;\n cursor: pointer;\n font-family: inherit;\n transition: var(--test-transition);\n}\n\n.picker-btn:disabled {\n opacity: 0.6;\n cursor: not-allowed;\n}\n\n.picker-btn--primary {\n background: var(--mj-brand-primary);\n color: var(--mj-text-inverse, white);\n border-color: var(--mj-brand-primary);\n}\n\n.picker-btn--primary:hover:not(:disabled) {\n background: var(--mj-brand-primary-hover, var(--mj-brand-primary));\n}\n\n.picker-btn--ghost {\n background: transparent;\n color: var(--test-text-secondary);\n border-color: var(--test-border);\n}\n\n.picker-btn--ghost:hover:not(:disabled) {\n background: var(--mj-bg-surface-hover, var(--mj-bg-surface-card));\n color: var(--test-text);\n}\n\n@media (max-width: 640px) {\n .picker-dialog {\n width: calc(100vw - 16px);\n max-height: calc(100vh - 32px);\n }\n .tests-actionbar {\n flex-direction: column;\n align-items: stretch;\n }\n .tests-actionbar-actions {\n justify-content: flex-end;\n }\n .test-remove-btn {\n opacity: 1; /* always visible on touch */\n }\n}\n\n"] }]
4318
- }], null, { chartContainer: [{
4990
+ }], null, { ChartContainer: [{
4319
4991
  type: ViewChild,
4320
4992
  args: ['chartContainer']
4321
4993
  }], handleKeyboardShortcut: [{