@zaip-ai/cli 1.0.7 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (444) hide show
  1. package/README.md +6 -4
  2. package/dist/services/adapters.d.ts +1 -1
  3. package/dist/services/adapters.d.ts.map +1 -1
  4. package/dist/services/adapters.js +15 -5
  5. package/dist/services/adapters.js.map +1 -1
  6. package/dist/services/adapters.test.js +1 -1
  7. package/dist/services/agent-adapters.d.ts +9 -0
  8. package/dist/services/agent-adapters.d.ts.map +1 -1
  9. package/dist/services/agent-adapters.js +39 -4
  10. package/dist/services/agent-adapters.js.map +1 -1
  11. package/dist/services/agent-adapters.test.js +13 -1
  12. package/dist/services/agent-adapters.test.js.map +1 -1
  13. package/dist/services/codex-config-template.d.ts +2 -1
  14. package/dist/services/codex-config-template.d.ts.map +1 -1
  15. package/dist/services/codex-config-template.js +13 -2
  16. package/dist/services/codex-config-template.js.map +1 -1
  17. package/dist/services/codex-rules.d.ts +1 -1
  18. package/dist/services/codex-rules.d.ts.map +1 -1
  19. package/dist/services/codex-rules.js +31 -7
  20. package/dist/services/codex-rules.js.map +1 -1
  21. package/dist/services/codex-subagent-hook-template.d.ts +1 -1
  22. package/dist/services/codex-subagent-hook-template.d.ts.map +1 -1
  23. package/dist/services/codex-subagent-hook-template.js +4 -1
  24. package/dist/services/codex-subagent-hook-template.js.map +1 -1
  25. package/dist/services/doctor.d.ts.map +1 -1
  26. package/dist/services/doctor.js +3 -1
  27. package/dist/services/doctor.js.map +1 -1
  28. package/dist/services/global-codex-config.d.ts +6 -3
  29. package/dist/services/global-codex-config.d.ts.map +1 -1
  30. package/dist/services/global-codex-config.js +82 -13
  31. package/dist/services/global-codex-config.js.map +1 -1
  32. package/dist/services/global-codex-config.test.js +57 -10
  33. package/dist/services/global-codex-config.test.js.map +1 -1
  34. package/dist/services/install-agents-template.d.ts.map +1 -1
  35. package/dist/services/install-agents-template.js +15 -2
  36. package/dist/services/install-agents-template.js.map +1 -1
  37. package/dist/services/logger.js +1 -1
  38. package/dist/services/logger.js.map +1 -1
  39. package/dist/services/logger.test.js +3 -3
  40. package/dist/services/logger.test.js.map +1 -1
  41. package/dist/services/runtime-template.test.js +21 -5
  42. package/dist/services/runtime-template.test.js.map +1 -1
  43. package/dist/services/template-manifest.d.ts.map +1 -1
  44. package/dist/services/template-manifest.js +7 -0
  45. package/dist/services/template-manifest.js.map +1 -1
  46. package/package.json +1 -1
  47. package/templates/.agents/architecture/software-architecture.md +131 -0
  48. package/templates/.agents/branding/brandbook.docx +0 -0
  49. package/templates/.agents/branding/brandbook.md +60 -0
  50. package/templates/.agents/config/agents/catalog.yaml +310 -10
  51. package/templates/.agents/config/clients/codex/agents/zaip-agent-brand-strategist.toml +30 -0
  52. package/templates/.agents/config/clients/codex/agents/zaip-agent-designer.toml +5 -5
  53. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +2 -2
  54. package/templates/.agents/config/clients/codex/agents/zaip-agent-general-operations.toml +30 -0
  55. package/templates/.agents/config/clients/codex/agents/zaip-agent-governance-compliance.toml +30 -0
  56. package/templates/.agents/config/clients/codex/agents/zaip-agent-growth-strategist.toml +30 -0
  57. package/templates/.agents/config/clients/codex/agents/zaip-agent-marketing-analyst.toml +8 -8
  58. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +5 -3
  59. package/templates/.agents/config/clients/codex/agents/zaip-agent-sales-specialist.toml +30 -0
  60. package/templates/.agents/config/project.yaml +1 -1
  61. package/templates/.agents/dashboard/.tmp-run-list.json +254 -0
  62. package/templates/.agents/dashboard/.zaip-managed.json +775 -163
  63. package/templates/.agents/dashboard/DATA-DICTIONARY.md +2227 -0
  64. package/templates/.agents/dashboard/README.md +48 -97
  65. package/templates/.agents/dashboard/apps/frontend/dist/assets/CostCalculationGuidePage-DxR_pgSV.js +1 -0
  66. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-4jZLTWE_.js +2156 -0
  67. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CKcsqb-r.js +1 -0
  68. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BomxqCBU.css +1 -0
  69. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-DmmJD2Th.js +3 -0
  70. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BwtitWa_.js +1 -0
  71. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-CaKwQP43.css +1 -0
  72. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-BZ5WdaMN.css +1 -0
  73. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-N5Csr0RP.js +68 -0
  74. package/templates/.agents/dashboard/apps/frontend/dist/assets/{dashboard-localization-C98LGC-Q.js → dashboard-localization-CLJ0lT9v.js} +1 -1
  75. package/templates/.agents/dashboard/apps/frontend/dist/assets/icons-vendor-C0fj2uU9.js +1 -0
  76. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-C4aIB1PV.js +2 -0
  77. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DK_enB5Z.css +1 -0
  78. package/templates/.agents/dashboard/apps/frontend/dist/assets/react-vendor-BEsJ-FYH.js +9 -0
  79. package/templates/.agents/dashboard/apps/frontend/dist/assets/rolldown-runtime-D9-fqq9M.js +1 -0
  80. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-B6y6Gw12.js +1 -0
  81. package/templates/.agents/dashboard/apps/frontend/dist/assets/traceability-DFY7ZDlu.css +1 -0
  82. package/templates/.agents/dashboard/apps/frontend/dist/index.html +9 -2
  83. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  84. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +1129 -600
  85. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingBoundary.tsx +48 -0
  86. package/templates/.agents/dashboard/apps/frontend/src/AppLoadingPanels.tsx +2 -0
  87. package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +247 -0
  88. package/templates/.agents/dashboard/apps/frontend/src/AppRequestGuards.tsx +95 -0
  89. package/templates/.agents/dashboard/apps/frontend/src/AppSetupRoutes.tsx +87 -0
  90. package/templates/.agents/dashboard/apps/frontend/src/CostCalculationGuidePage.tsx +641 -0
  91. package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +159 -0
  92. package/templates/.agents/dashboard/apps/frontend/src/DashboardCapabilityNav.tsx +13 -13
  93. package/templates/.agents/dashboard/apps/frontend/src/DashboardGlobalSettings.tsx +56 -0
  94. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +266 -315
  95. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +151 -0
  96. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +37 -0
  97. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +114 -12
  98. package/templates/.agents/dashboard/apps/frontend/src/app-preferences.ts +15 -0
  99. package/templates/.agents/dashboard/apps/frontend/src/app-session-token.ts +20 -0
  100. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.css +267 -0
  101. package/templates/.agents/dashboard/apps/frontend/src/cost-calculation-guide.test.ts +69 -0
  102. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +85 -0
  103. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +133 -0
  104. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +453 -13
  105. package/templates/.agents/dashboard/apps/frontend/src/dashboard-localization.ts +1 -1
  106. package/templates/.agents/dashboard/apps/frontend/src/main.tsx +1 -0
  107. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.test.ts +55 -0
  108. package/templates/.agents/dashboard/apps/frontend/src/operations-detail-hydration.ts +36 -0
  109. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.test.ts +126 -0
  110. package/templates/.agents/dashboard/apps/frontend/src/operations-entity-page-projection.ts +181 -0
  111. package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +138 -0
  112. package/templates/.agents/dashboard/apps/frontend/src/operations-period-render.test.ts +105 -0
  113. package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +6 -3
  114. package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +9 -5
  115. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +26 -0
  116. package/templates/.agents/dashboard/apps/frontend/src/period-transition-overlay.css +44 -0
  117. package/templates/.agents/dashboard/apps/frontend/src/project-settings-model.ts +121 -62
  118. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +482 -3
  119. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +84 -17
  120. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +78 -0
  121. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +234 -39
  122. package/templates/.agents/dashboard/apps/frontend/src/snapshot-notice.ts +15 -0
  123. package/templates/.agents/dashboard/apps/frontend/src/sqlite-cost-adapters.ts +26 -0
  124. package/templates/.agents/dashboard/apps/frontend/src/styles.css +14 -2
  125. package/templates/.agents/dashboard/apps/frontend/src/traceability-workspace.css +38 -2
  126. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +253 -0
  127. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +380 -0
  128. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +175 -0
  129. package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +395 -0
  130. package/templates/.agents/dashboard/apps/frontend/src/use-app-trace-documents.ts +141 -0
  131. package/templates/.agents/dashboard/apps/frontend/src/use-idle-cost-prefetch.ts +45 -0
  132. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +46 -0
  133. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +36 -0
  134. package/templates/.agents/dashboard/apps/frontend/vite.config.ts +58 -5
  135. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  136. package/templates/.agents/dashboard/modules/cost-monitor/src/contracts.ts +47 -6
  137. package/templates/.agents/dashboard/modules/cost-monitor/src/duration-estimate.ts +13 -0
  138. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +134 -0
  139. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +290 -0
  140. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +133 -19
  141. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +134 -43
  142. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostFilters.tsx +137 -19
  143. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.test.ts +2 -0
  144. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +13 -10
  145. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +328 -0
  146. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +302 -6
  147. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +627 -400
  148. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostSummary.tsx +5 -13
  149. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +370 -72
  150. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +467 -326
  151. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.test.ts +17 -0
  152. package/templates/.agents/dashboard/modules/cost-monitor/src/react/MacroCostTable.tsx +25 -6
  153. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-copy.ts +0 -2
  154. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-page-helpers.ts +17 -0
  155. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-responsive.css +6 -3
  156. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-row-filters.ts +123 -0
  157. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-summary.ts +8 -3
  158. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor-views.ts +32 -0
  159. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +55 -14
  160. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-table-presentation.ts +121 -0
  161. package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +3 -0
  162. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.test.ts +68 -0
  163. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +33 -5
  164. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +119 -0
  165. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.test.ts +12 -0
  166. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +146 -0
  167. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +118 -0
  168. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.test.ts +2 -2
  169. package/templates/.agents/dashboard/modules/cost-monitor/src/server/aggregate.ts +5 -3
  170. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-pricing-fallback.test.ts +53 -0
  171. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.basics.test.ts +34 -0
  172. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +168 -161
  173. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.ts +188 -29
  174. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +757 -0
  175. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +784 -0
  176. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +100 -0
  177. package/templates/.agents/dashboard/modules/cost-monitor/src/server/index.ts +1 -0
  178. package/templates/.agents/dashboard/modules/cost-monitor/src/server/refactor-map-xr9l.json +61 -0
  179. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-monitor-projection-agents.test.ts +31 -0
  180. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-summary-projection-duration.test.ts +414 -0
  181. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.test.ts +41 -0
  182. package/templates/.agents/dashboard/modules/cost-monitor/src/task-cost-groups.ts +55 -11
  183. package/templates/.agents/dashboard/modules/operations/package.json +2 -1
  184. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +185 -0
  185. package/templates/.agents/dashboard/modules/operations/src/duration-population.test.ts +63 -1
  186. package/templates/.agents/dashboard/modules/operations/src/duration-population.ts +60 -5
  187. package/templates/.agents/dashboard/modules/operations/src/react/EntityDetailsDialog.tsx +14 -3
  188. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.test.ts +44 -1
  189. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +345 -18
  190. package/templates/.agents/dashboard/modules/operations/src/react/MacroPlanningDialog.tsx +1 -0
  191. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.test.ts +9 -5
  192. package/templates/.agents/dashboard/modules/operations/src/react/OperationDateFilters.tsx +2 -2
  193. package/templates/.agents/dashboard/modules/operations/src/react/OperationsMainContent.tsx +168 -0
  194. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +95 -6
  195. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +367 -14
  196. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +562 -131
  197. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +353 -355
  198. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +65 -0
  199. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageOverview.tsx +169 -0
  200. package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +73 -8
  201. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.test.tsx +32 -0
  202. package/templates/.agents/dashboard/modules/operations/src/react/PrdDocumentDialog.tsx +62 -6
  203. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.test.ts +55 -0
  204. package/templates/.agents/dashboard/modules/operations/src/react/TaskBoard.tsx +27 -19
  205. package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +105 -42
  206. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.priority.test.ts +19 -0
  207. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +265 -36
  208. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +171 -8
  209. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +98 -68
  210. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.test.ts +72 -1
  211. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +78 -17
  212. package/templates/.agents/dashboard/modules/operations/src/react/execution-metrics.ts +6 -4
  213. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +431 -1
  214. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +574 -20
  215. package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +42 -4
  216. package/templates/.agents/dashboard/modules/operations/src/react/export/xlsx-runtime-e2e.test.ts +248 -0
  217. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog-content.css +49 -0
  218. package/templates/.agents/dashboard/modules/operations/src/react/macro-planning-dialog.css +93 -50
  219. package/templates/.agents/dashboard/modules/operations/src/react/manual-work-priority.test.ts +82 -0
  220. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +29 -2
  221. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +22 -1
  222. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +77 -4
  223. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +67 -5
  224. package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +54 -0
  225. package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +22 -0
  226. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +149 -12
  227. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +27 -0
  228. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +176 -0
  229. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.test.ts +160 -0
  230. package/templates/.agents/dashboard/modules/operations/src/react/operations-period-scope.ts +47 -0
  231. package/templates/.agents/dashboard/modules/operations/src/react/operations-query.ts +10 -0
  232. package/templates/.agents/dashboard/modules/operations/src/react/operations-responsive.css +3 -0
  233. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-labels.ts +28 -0
  234. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +21 -7
  235. package/templates/.agents/dashboard/modules/operations/src/react/operations.css +42 -0
  236. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.test.ts +54 -0
  237. package/templates/.agents/dashboard/modules/operations/src/react/period-summary-population.ts +32 -0
  238. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +241 -6
  239. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.ts +148 -6
  240. package/templates/.agents/dashboard/modules/operations/src/react/popup-localization.test.ts +1 -1
  241. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css +132 -0
  242. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.css.test.ts +13 -0
  243. package/templates/.agents/dashboard/modules/operations/src/react/unified-work-board.header.test.ts +16 -0
  244. package/templates/.agents/dashboard/modules/operations/src/react/use-infinite-column.ts +11 -5
  245. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +7 -4
  246. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +5 -84
  247. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.test.ts +291 -15
  248. package/templates/.agents/dashboard/modules/operations/src/server/build-operations-projection.ts +182 -472
  249. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.test.ts +40 -0
  250. package/templates/.agents/dashboard/modules/operations/src/server/canonical-compat.ts +19 -6
  251. package/templates/.agents/dashboard/modules/operations/src/server/canonical-run-projection.ts +31 -6
  252. package/templates/.agents/dashboard/modules/operations/src/server/daily-duration-projection.ts +220 -0
  253. package/templates/.agents/dashboard/modules/operations/src/server/execution-duration-projection.ts +28 -0
  254. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +39 -2
  255. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +25 -13
  256. package/templates/.agents/dashboard/modules/operations/src/server/filter.test.ts +3 -3
  257. package/templates/.agents/dashboard/modules/operations/src/server/filter.ts +7 -2
  258. package/templates/.agents/dashboard/modules/operations/src/server/index.ts +3 -0
  259. package/templates/.agents/dashboard/modules/operations/src/server/macro.ts +3 -12
  260. package/templates/.agents/dashboard/modules/operations/src/server/manifest-usage.ts +65 -0
  261. package/templates/.agents/dashboard/modules/operations/src/server/operations-projection-aggregates.ts +297 -0
  262. package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +330 -0
  263. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.test.ts +505 -0
  264. package/templates/.agents/dashboard/modules/operations/src/server/quick-operations-projection.ts +609 -0
  265. package/templates/.agents/dashboard/modules/operations/src/server/runs.ts +18 -19
  266. package/templates/.agents/dashboard/modules/operations/src/work-board-model.ts +76 -0
  267. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  268. package/templates/.agents/dashboard/modules/traceability/src/server/build-task-trace.ts +3 -1
  269. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  270. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  271. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  272. package/templates/.agents/dashboard/packages/contracts/src/index.ts +2 -0
  273. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  274. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +1759 -0
  275. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs.metafile.json +7333 -0
  276. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +1611 -0
  277. package/templates/.agents/dashboard/packages/local-runtime/package.json +8 -3
  278. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +20 -0
  279. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +114 -0
  280. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +31 -0
  281. package/templates/.agents/dashboard/packages/local-runtime/scripts/source-size-runtime-scripts/sqlite-read-model-fixture.mjs +397 -0
  282. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +589 -0
  283. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +53 -0
  284. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +203 -0
  285. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +1646 -107
  286. package/templates/.agents/dashboard/packages/local-runtime/src/agent-adapter-sync.ts +30 -0
  287. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.test.ts +3 -2
  288. package/templates/.agents/dashboard/packages/local-runtime/src/agent-catalog-settings.ts +5 -0
  289. package/templates/.agents/dashboard/packages/local-runtime/src/manifest-runs.ts +72 -33
  290. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +339 -4
  291. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +392 -1
  292. package/templates/.agents/dashboard/packages/local-runtime/src/runtime.mjs +5 -5
  293. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +110 -0
  294. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +189 -0
  295. package/templates/.agents/dashboard/packages/local-runtime/src/server-cross-layer-parity.test.ts +245 -0
  296. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models-route.ts +64 -0
  297. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.test.ts +204 -0
  298. package/templates/.agents/dashboard/packages/local-runtime/src/server-daily-read-models.ts +194 -0
  299. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.test.ts +163 -0
  300. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-pages.ts +354 -0
  301. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.test.ts +74 -0
  302. package/templates/.agents/dashboard/packages/local-runtime/src/server-operations-runs-route.ts +113 -0
  303. package/templates/.agents/dashboard/packages/local-runtime/src/server-overview.test.ts +68 -0
  304. package/templates/.agents/dashboard/packages/local-runtime/src/server-period.ts +47 -0
  305. package/templates/.agents/dashboard/packages/local-runtime/src/server-projections.ts +483 -0
  306. package/templates/.agents/dashboard/packages/local-runtime/src/server-settings.test.ts +5 -2
  307. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +178 -84
  308. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +149 -0
  309. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +675 -267
  310. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +1212 -309
  311. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.test.ts +53 -0
  312. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +722 -0
  313. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +3743 -0
  314. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +7017 -0
  315. package/templates/.agents/dashboard/packages/local-runtime/src/work-priority-contract.test.ts +33 -0
  316. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-history.mjs +41 -0
  317. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-processes.mjs +210 -0
  318. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +514 -190
  319. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +324 -24
  320. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  321. package/templates/.agents/dashboard/packages/windows-launcher/LauncherVerificationBehaviorTests.cs +206 -0
  322. package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +95 -8
  323. package/templates/.agents/dashboard/packages/windows-launcher/build-dashboard-launcher.ps1 +2 -2
  324. package/templates/.agents/dashboard/packages/windows-launcher/run-launcher-verification-tests.ps1 +22 -0
  325. package/templates/.agents/dashboard/read-model.dbml +2201 -0
  326. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  327. package/templates/.agents/dashboard/tests/e2e/playwright.config.ts +1 -1
  328. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +216 -41
  329. package/templates/.agents/dashboard/work-priority.json +34 -0
  330. package/templates/.agents/dashboard/zaip-dashboard-linux.sh +45 -0
  331. package/templates/.agents/dashboard/zaip-dashboard-mac.command +3 -0
  332. package/templates/.agents/dashboard/zaip-dashboard-windows.exe +0 -0
  333. package/templates/.agents/design/.zaip-managed.json +3 -3
  334. package/templates/.agents/design/apps/catalog/package.json +1 -1
  335. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  336. package/templates/.agents/growth/estrategia-bootstrap-90-dias.md +169 -0
  337. package/templates/.agents/growth/strategy.md +27 -0
  338. package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +197 -0
  339. package/templates/.agents/marketing/strategy.md +29 -0
  340. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  341. package/templates/.agents/requirements/comparative-audit.md +1 -1
  342. package/templates/.agents/requirements/context-engine.md +1 -1
  343. package/templates/.agents/requirements/cost-observability.md +1 -1
  344. package/templates/.agents/requirements/dashboard-launch.md +24 -0
  345. package/templates/.agents/requirements/data-export.md +1 -1
  346. package/templates/.agents/requirements/design-system.md +1 -1
  347. package/templates/.agents/requirements/execution-engine.md +1 -1
  348. package/templates/.agents/requirements/kanban-dashboard.md +4 -3
  349. package/templates/.agents/requirements/model-orchestration.md +1 -1
  350. package/templates/.agents/requirements/public-distribution.md +9 -6
  351. package/templates/.agents/requirements/software-architecture.md +1 -1
  352. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  353. package/templates/.agents/requirements/work-lifecycle.md +9 -3
  354. package/templates/.agents/requirements/zaip-strategic-95.md +4 -4
  355. package/templates/.agents/rules/autonomy-decision.md +15 -0
  356. package/templates/.agents/rules/core.md +1 -1
  357. package/templates/.agents/rules/run-lifecycle-closure.md +1 -1
  358. package/templates/.agents/rules/specialist-routing.md +19 -0
  359. package/templates/.agents/rules/workflow.md +1 -1
  360. package/templates/.agents/runtime/VERSION +1 -1
  361. package/templates/.agents/runtime/bin/zaip.mjs +118 -67
  362. package/templates/.agents/runtime/checksums.sha256 +6 -4
  363. package/templates/.agents/runtime/manifest.json +3 -1
  364. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -0
  365. package/templates/.agents/runtime/provenance.json +11 -0
  366. package/templates/.agents/runtime/sbom.cdx.json +1929 -39
  367. package/templates/.agents/sales/playbook.md +32 -0
  368. package/templates/.agents/sales/roteiro-comercial-zaip.md +252 -0
  369. package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
  370. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +22 -0
  371. package/templates/.agents/skills/zaip-skill-brand-governance/agents/openai.yaml +4 -0
  372. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +39 -0
  373. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +30 -0
  374. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/SKILL.md +25 -0
  375. package/templates/.agents/skills/zaip-skill-dashboard-sqlite/agents/openai.yaml +4 -0
  376. package/templates/.agents/skills/zaip-skill-enforce-architecture/SKILL.md +2 -0
  377. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/SKILL.md +18 -0
  378. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/agents/openai.yaml +6 -0
  379. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.mjs +71 -0
  380. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/ready-queue-fence.test.mjs +60 -0
  381. package/templates/.agents/skills/zaip-skill-executar-fila-pronta/scripts/scheduled-ready-queue-contract.test.mjs +111 -0
  382. package/templates/.agents/skills/zaip-skill-execution/SKILL.md +2 -2
  383. package/templates/.agents/skills/zaip-skill-frontend-launcher/references/launcher-contract.md +3 -1
  384. package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +12 -0
  385. package/templates/.agents/skills/zaip-skill-general-operations/agents/openai.yaml +4 -0
  386. package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +9 -7
  387. package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
  388. package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +11 -38
  389. package/templates/.agents/skills/zaip-skill-governance-compliance/SKILL.md +12 -0
  390. package/templates/.agents/skills/zaip-skill-governance-compliance/agents/openai.yaml +4 -0
  391. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +21 -0
  392. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/agents/openai.yaml +4 -0
  393. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/references/growth-revops-framework.md +25 -0
  394. package/templates/.agents/skills/zaip-skill-instalar-agendamento/SKILL.md +17 -0
  395. package/templates/.agents/skills/zaip-skill-instalar-agendamento/agents/openai.yaml +6 -0
  396. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +9 -3
  397. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +99 -93
  398. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +132 -23
  399. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +149 -1
  400. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +76 -19
  401. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +217 -7
  402. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +77 -0
  403. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +20 -0
  404. package/templates/.agents/skills/zaip-skill-sales-methodology/agents/openai.yaml +4 -0
  405. package/templates/.agents/skills/zaip-skill-sales-methodology/references/sales-process-framework.md +77 -0
  406. package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -0
  407. package/templates/.agents/skills/zaip-skill-sqlite-read-model/SKILL.md +96 -0
  408. package/templates/.agents/skills/zaip-skill-sqlite-read-model/agents/openai.yaml +4 -0
  409. package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +41 -0
  410. package/templates/.agents/zaip-aios/INDEX.md +11 -0
  411. package/templates/.agents/zaip-aios/README.md +38 -8
  412. package/templates/.agents/zaip-aios/VERSION +1 -1
  413. package/templates/.agents/zaip-aios/guides/09-agentes-e-squads.md +43 -6
  414. package/templates/.agents/zaip-aios/guides/10-seguranca.md +21 -7
  415. package/templates/.agents/zaip-aios/guides/17-como-o-custo-e-calculado.md +32 -0
  416. package/templates/.agents/zaip-aios/guides/18-catalogo-de-agentes.md +76 -0
  417. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +72 -0
  418. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +330 -0
  419. package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +52 -0
  420. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +69 -0
  421. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +483 -0
  422. package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +85 -0
  423. package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +18 -0
  424. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +44 -0
  425. package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +167 -0
  426. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +144 -0
  427. package/templates/.agents/zaip-aios/guides/assets/diagrams/lifecycles-task-spec-prd.png +0 -0
  428. package/templates/.agents/zaip-aios/guides/assets/diagrams/prd-lifecycle.png +0 -0
  429. package/templates/.agents/zaip-aios/guides/assets/diagrams/spec-lifecycle.png +0 -0
  430. package/templates/.agents/zaip-aios/guides/assets/diagrams/task-lifecycle.png +0 -0
  431. package/templates/template-manifest.json +1082 -215
  432. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-CLYIi7Vo.js +0 -1
  433. package/templates/.agents/dashboard/apps/frontend/dist/assets/AgentCatalogSettingsPage-DTDNVQGX.css +0 -1
  434. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-Cv-JcoxP.js +0 -1320
  435. package/templates/.agents/dashboard/apps/frontend/dist/assets/PricingSettingsDialog-CzEggcRb.js +0 -1
  436. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-05YKn6Z4.js +0 -3
  437. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-YO3yqtgV.css +0 -1
  438. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8AzCnl3a.js +0 -77
  439. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DdBYQ_8D.css +0 -1
  440. package/templates/.agents/dashboard/apps/frontend/src/AgentCatalogSettingsPage.tsx +0 -617
  441. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings-page.css +0 -660
  442. package/templates/.agents/dashboard/apps/frontend/src/agent-catalog-settings.test.ts +0 -249
  443. package/templates/.agents/dashboard/dashboard.config.json +0 -4
  444. package/templates/.agents/dashboard/zaip-dashboard.exe +0 -0
@@ -1,120 +1,271 @@
1
1
  import type { CostMonitorProjection } from "@zaip-ai/dashboard-cost-monitor/contracts";
2
2
  import type { CostCurrency, CostLocale } from "@zaip-ai/dashboard-cost-monitor/react";
3
- import { CostMonitorPage } from "@zaip-ai/dashboard-cost-monitor/react";
4
- import type {
5
- ChangeProjection,
6
- MacroProjection,
7
- OperationsProjection,
8
- PrdDetailProjection,
9
- SpecDetailProjection,
10
- } from "@zaip-ai/dashboard-operations/contracts";
3
+ import type { OperationsProjection } from "@zaip-ai/dashboard-operations/contracts";
4
+ import { OperationsPage } from "@zaip-ai/dashboard-operations/react";
5
+ import { useCallback, useEffect, useMemo, useRef, useState } from "react";
6
+ import { AppLoadingBoundary } from "./AppLoadingBoundary.js";
7
+ import type { LoadingArea } from "./AppLoadingPanels.js";
8
+ import { AppOverlays } from "./AppOverlays.js";
11
9
  import {
12
- EntityDetailsDialog,
13
- MacroDocumentDialog,
14
- MacroPlanningDialog,
15
- OperationsPage,
16
- } from "@zaip-ai/dashboard-operations/react";
17
- import type { TaskTrace } from "@zaip-ai/dashboard-traceability/contracts";
18
- import { TaskTraceLoadingPanel, TaskTracePanel } from "@zaip-ai/dashboard-traceability/react";
19
- import { lazy, Suspense, useCallback, useEffect, useMemo, useRef, useState } from "react";
10
+ isAbortError,
11
+ isCurrentDashboardRequest,
12
+ isCurrentOperationsPeriodRequest,
13
+ isCurrentPeriodTransition,
14
+ mergeBoardPages,
15
+ PeriodTransitionOverlay,
16
+ PeriodTransitionShell,
17
+ } from "./AppRequestGuards.js";
20
18
  import {
21
- DashboardLoadingNotice,
22
- DashboardSkeleton,
23
- LoadFailedPanel,
24
- type LoadingArea,
25
- LoadingPanel,
26
- } from "./AppLoadingPanels.js";
19
+ costCalculationGuidePath,
20
+ pricingSetupPath,
21
+ projectSetupPath,
22
+ renderAppSetupRoute,
23
+ } from "./AppSetupRoutes.js";
24
+ import { currencyKey, initialDashboardView, savedLocale } from "./app-preferences.js";
25
+ import { readDashboardSessionToken } from "./app-session-token.js";
26
+ import { renderDashboardAlternateContent } from "./DashboardAlternateContent.js";
27
27
  import { DashboardCapabilityNav, type DashboardView } from "./DashboardCapabilityNav.js";
28
- import type { FxRate, PricingCatalog, ReconciliationResult } from "./dashboard-api.js";
28
+ import { DashboardGlobalSettings } from "./DashboardGlobalSettings.js";
29
+ import type {
30
+ FxRate,
31
+ OperationsBoardPage,
32
+ OperationsEntityPageKind,
33
+ OperationsLifecyclePageKind,
34
+ OperationsPageFilters,
35
+ PricingCatalog,
36
+ ReconciliationResult,
37
+ SqliteViewName,
38
+ WorkBoardStage,
39
+ } from "./dashboard-api.js";
29
40
  import { DashboardApiClient } from "./dashboard-api.js";
30
41
  import { defaultOperationsPeriod, requestPeriodForOperations } from "./operations-period.js";
31
- import { loadAtomicCostSnapshot, loadCostViewSnapshot } from "./snapshot-coherence.js";
32
- import { TraceabilityWorkspace } from "./TraceabilityWorkspace.js";
42
+ import { shouldShowSnapshotNotice, snapshotNoticeDelayMs } from "./snapshot-notice.js";
43
+ import { useAppCostLoaders } from "./use-app-cost-loaders.js";
44
+ import { useAppEffects } from "./use-app-effects.js";
45
+ import { useAppOperationsPages } from "./use-app-operations-pages.js";
46
+ import { useAppTraceDocuments } from "./use-app-trace-documents.js";
47
+ import {
48
+ collectWorkListPages,
49
+ normalizeWorkListFilters,
50
+ selectedWorkListKinds,
51
+ } from "./work-list-pagination.js";
33
52
  import "./loading-feedback.css";
53
+ import "./period-transition-overlay.css";
34
54
 
35
- const legacyCostMonitorPath = "/zaip-cost-monitor";
36
- const DashboardHelpDialog = lazy(async () => {
37
- const module = await import("./DashboardHelpDialog.js");
38
- return { default: module.DashboardHelpDialog };
39
- });
40
- const AgentCatalogSettingsPage = lazy(async () => ({
41
- default: (await import("./AgentCatalogSettingsPage.js")).AgentCatalogSettingsPage,
42
- }));
43
- const PricingSettingsPage = lazy(async () => ({
44
- default: (await import("./PricingSettingsDialog.js")).PricingSettingsPage,
45
- }));
46
- const ProjectSettingsPage = lazy(async () => ({
47
- default: (await import("./ProjectSettingsPage.js")).ProjectSettingsPage,
48
- }));
49
- const pricingSetupPath = "/setup-pricing";
50
- const agentCatalogSetupPath = "/setup-agents";
51
- const projectSetupPath = "/setup-project";
52
- const sessionTokenKey = "zaip-dashboard-session-token";
53
- const localeKey = "zaip-dashboard-locale";
54
- const currencyKey = "zaip-dashboard-currency";
55
+ type BoardStageState = { loading: boolean; error: string | null };
56
+ type OperationsExportView =
57
+ | "work-kanban"
58
+ | "work-list"
59
+ | "changes"
60
+ | "decisions"
61
+ | "executions"
62
+ | "runs"
63
+ | "conversations"
64
+ | "daily"
65
+ | "monthly"
66
+ | "yearly";
67
+ type OverviewStatusKind = "task" | "spec" | "prd";
68
+ type OverviewClassification = "lite" | "standard" | "critical" | "empty" | "structured" | "rework";
69
+ type OverviewStatusPage = {
70
+ items:
71
+ | OperationsProjection["tasks"]
72
+ | OperationsProjection["specs"]
73
+ | OperationsProjection["prds"];
74
+ total: number;
75
+ loading: boolean;
76
+ error: string | null;
77
+ };
78
+ type OverviewStatusPages = Partial<
79
+ Record<`${WorkBoardStage}:${OverviewStatusKind}`, OverviewStatusPage>
80
+ >;
81
+ const overviewStatusPageKey = (stage: WorkBoardStage, kind: OverviewStatusKind) =>
82
+ `${stage}:${kind}` as const;
83
+ type OverviewClassificationPages = Partial<
84
+ Record<`${OverviewStatusKind}:${OverviewClassification}`, OverviewStatusPage>
85
+ >;
86
+ const overviewClassificationPageKey = (
87
+ kind: OverviewStatusKind,
88
+ classification: OverviewClassification,
89
+ ) => `${kind}:${classification}` as const;
90
+ const initialWorkBoardStages: readonly WorkBoardStage[] = [
91
+ "definition",
92
+ "planning",
93
+ "ready",
94
+ "executing",
95
+ "verifying",
96
+ "completed",
97
+ "blocked",
98
+ "cancelled",
99
+ "failed",
100
+ ];
101
+ const traceTaskSearchStages: readonly WorkBoardStage[] = [
102
+ "completed",
103
+ "executing",
104
+ "ready",
105
+ "blocked",
106
+ "verifying",
107
+ "failed",
108
+ "cancelled",
109
+ "planning",
110
+ "definition",
111
+ ];
55
112
 
56
- function savedLocale(): CostLocale {
57
- return window.localStorage.getItem(localeKey) === "en-US" ? "en-US" : "pt-BR";
58
- }
59
-
60
- function initialDashboardView(pathname: string): DashboardView | null {
61
- return pathname.replace(/\/+$/, "") === legacyCostMonitorPath ? "cost-monitor" : null;
113
+ /** Mounts the Daily Cost view without requesting the historical summary blob. */
114
+ function dailyCostSeed(
115
+ currency: "BRL" | "USD",
116
+ filters: CostMonitorProjection["filters"],
117
+ ): CostMonitorProjection {
118
+ const metric = {
119
+ events: 0,
120
+ tokens: { input: null, cached_input: null, output: null, reasoning: null, total: null },
121
+ cost: { usd: null, brl: null },
122
+ coverage: { events: 0, tokens: 0, usd: 0, brl: 0 },
123
+ };
124
+ const usage = {
125
+ conversation: metric,
126
+ planning: metric,
127
+ execution: metric,
128
+ total: metric,
129
+ new_work: metric,
130
+ rework: metric,
131
+ };
132
+ return {
133
+ schema_version: 2,
134
+ layout_version: 2,
135
+ readonly: true,
136
+ source: ".agents/records",
137
+ aggregate_only: true,
138
+ data_source: "run_records",
139
+ legacy_effort_baseline: null,
140
+ currency,
141
+ pricing: { catalog_updated_at: null, fx_rate: null, conversion_applied: false },
142
+ filters,
143
+ overview: {
144
+ task_count: 0,
145
+ run_count: 0,
146
+ event_count: 0,
147
+ total_tokens: null,
148
+ estimated_usd: null,
149
+ estimated_brl: null,
150
+ recorded_usd: null,
151
+ recorded_brl: null,
152
+ average_run_cost_usd: null,
153
+ average_run_cost_brl: null,
154
+ average_run_duration_ms: null,
155
+ known_tokens_per_cycle: null,
156
+ top_10_token_share: null,
157
+ coverage: { events: 0, tokens: 0, usd: 0, brl: 0 },
158
+ },
159
+ filters_available: {
160
+ providers: [],
161
+ agents: [],
162
+ models: [],
163
+ statuses: [],
164
+ priorities: [],
165
+ risks: [],
166
+ phases: [],
167
+ origins: [],
168
+ work_types: [],
169
+ },
170
+ configured_agents: [],
171
+ usage,
172
+ origins: { conversation: usage, task: usage, spec: usage, prd: usage },
173
+ prds: [],
174
+ specs: [],
175
+ runs: [],
176
+ groups: {
177
+ conversations: [],
178
+ tasks: [],
179
+ providers: [],
180
+ agents: [],
181
+ models: [],
182
+ daily: [],
183
+ monthly: [],
184
+ annual: [],
185
+ },
186
+ };
62
187
  }
63
188
 
189
+ export {
190
+ isAbortError,
191
+ isCurrentDashboardRequest,
192
+ isCurrentOperationsPageRequest,
193
+ isCurrentOperationsPeriodRequest,
194
+ isCurrentPeriodTransition,
195
+ mergeBoardPages,
196
+ needsAtomicCostSnapshot,
197
+ PeriodTransitionOverlay,
198
+ PeriodTransitionShell,
199
+ } from "./AppRequestGuards.js";
64
200
  export function App() {
65
- const token = useMemo(() => {
66
- const url = new URL(window.location.href);
67
- const suppliedToken = url.searchParams.get("token");
68
- const sessionToken =
69
- suppliedToken ??
70
- window.localStorage.getItem(sessionTokenKey) ??
71
- window.sessionStorage.getItem(sessionTokenKey) ??
72
- "";
73
- if (sessionToken) {
74
- window.localStorage.setItem(sessionTokenKey, sessionToken);
75
- window.sessionStorage.setItem(sessionTokenKey, sessionToken);
76
- }
77
- if (url.searchParams.has("token")) {
78
- url.searchParams.delete("token");
79
- window.history.replaceState(null, "", `${url.pathname}${url.search}${url.hash}`);
80
- }
81
- return sessionToken;
82
- }, []);
201
+ const [snapshotUpdating, setSnapshotUpdating] = useState(false);
202
+ const [snapshotNoticeDismissed, setSnapshotNoticeDismissed] = useState(false);
203
+ const [snapshotNoticeDelayElapsed, setSnapshotNoticeDelayElapsed] = useState(false);
204
+ const token = useMemo(readDashboardSessionToken, []);
83
205
  const api = useMemo(() => new DashboardApiClient(token), [token]);
206
+ const { loadCostDetail, loadCostReadModelPage, loadCostRunPage, loadCostGroupPage } =
207
+ useAppCostLoaders(api);
84
208
  const [dashboardView, setDashboardView] = useState<DashboardView | null>(() =>
85
209
  initialDashboardView(window.location.pathname),
86
210
  );
211
+ const dashboardViewRef = useRef(dashboardView);
212
+ dashboardViewRef.current = dashboardView;
87
213
  const [operations, setOperations] = useState<OperationsProjection | null>(null);
214
+ const [entityPage, setEntityPage] = useState<{
215
+ offset: number;
216
+ total: number | null;
217
+ loading: boolean;
218
+ error: string | null;
219
+ aggregation?: "canonical_entity" | "page_partial" | undefined;
220
+ }>({ offset: 0, total: null, loading: false, error: null });
221
+ const [entityPageKind, setEntityPageKind] = useState<
222
+ OperationsEntityPageKind | OperationsLifecyclePageKind
223
+ >("runs");
224
+ const entityFiltersRef = useRef<OperationsPageFilters>({ query: "", status: "", risk: "" });
225
+ const [boardTotals, setBoardTotals] = useState<Partial<Record<WorkBoardStage, number>>>({});
226
+ const [boardKindTotals, setBoardKindTotals] = useState<
227
+ Partial<Record<WorkBoardStage, OperationsBoardPage["totals_by_kind"]>>
228
+ >({});
229
+ const [boardStates, setBoardStates] = useState<Partial<Record<WorkBoardStage, BoardStageState>>>(
230
+ {},
231
+ );
232
+ const [overviewStatusPages, setOverviewStatusPages] = useState<OverviewStatusPages>({});
233
+ const [overviewClassificationPages, setOverviewClassificationPages] =
234
+ useState<OverviewClassificationPages>({});
88
235
  const [costs, setCosts] = useState<CostMonitorProjection | null>(null);
89
236
  const [displayedCosts, setDisplayedCosts] = useState<CostMonitorProjection | null>(null);
90
- const [trace, setTrace] = useState<TaskTrace | null>(null);
91
- const [documentDetail, setDocumentDetail] = useState<
92
- | { label: "PRD"; detail: PrdDetailProjection }
93
- | { label: "SPEC"; detail: SpecDetailProjection }
94
- | null
95
- >(null);
96
- const [traceLink, setTraceLink] = useState<
97
- | { kind: "change"; row: ChangeProjection }
98
- | { kind: "macro"; label: "PRD" | "SPEC"; row: MacroProjection }
99
- | null
100
- >(null);
101
- const [traceLoadingTaskId, setTraceLoadingTaskId] = useState<string | null>(null);
102
- const [selectedTraceTaskId, setSelectedTraceTaskId] = useState("");
103
237
  const [costLocale, setCostLocale] = useState<CostLocale>(savedLocale);
104
238
  const [currency, setCurrency] = useState<CostCurrency>(() =>
105
239
  window.localStorage.getItem(currencyKey) === "USD" ? "USD" : "BRL",
106
240
  );
241
+ // Language is the global selector's source of truth for the paired display
242
+ // currency, so Operations and Cost Monitor cannot diverge.
243
+ const changeDashboardLocale = useCallback((next: CostLocale) => {
244
+ setCostLocale(next);
245
+ setCurrency(next === "en-US" ? "USD" : "BRL");
246
+ }, []);
107
247
  const [operationsPeriod, setOperationsPeriod] = useState(() => defaultOperationsPeriod);
248
+ const operationsPeriodRef = useRef(operationsPeriod);
249
+ operationsPeriodRef.current = operationsPeriod;
108
250
  const [helpOpen, setHelpOpen] = useState(false);
109
251
  const [pricing, setPricing] = useState<PricingCatalog | null>(null);
110
252
  const [fxRate, setFxRate] = useState<FxRate | null>(null);
111
253
  const [pathname, setPathname] = useState(window.location.pathname);
112
254
  const [operationsLoading, setOperationsLoading] = useState(true);
255
+ const [operationsSurfaceLoading, setOperationsSurfaceLoading] = useState(false);
256
+ const [costSurfaceLoading, setCostSurfaceLoading] = useState(false);
257
+ const [periodTransitionPending, setPeriodTransitionPending] = useState(false);
113
258
  const [loadingArea, setLoadingArea] = useState<LoadingArea | null>("operations");
114
259
  const [failedArea, setFailedArea] = useState<LoadingArea | null>(null);
115
260
  const [refreshDialog, setRefreshDialog] = useState<
116
261
  "choose" | "refreshing" | "reconciling" | null
117
262
  >(null);
263
+ useEffect(() => {
264
+ void api
265
+ .ready()
266
+ .then((ready) => setSnapshotUpdating(ready.snapshot_state === "updating"))
267
+ .catch(() => {});
268
+ }, [api]);
118
269
  const [error, setError] = useState<string | null>(null);
119
270
  const [reconciliationResult, setReconciliationResult] = useState<ReconciliationResult | null>(
120
271
  null,
@@ -124,22 +275,88 @@ export function App() {
124
275
  title: "Dashboard Zaip AIOS",
125
276
  repositoryName: "",
126
277
  });
278
+ const {
279
+ closeDocument,
280
+ documentDetail,
281
+ openDocument,
282
+ openTrace,
283
+ openTraceChange,
284
+ openTraceMacro,
285
+ selectedTraceTaskId,
286
+ setSelectedTraceTaskId,
287
+ setTrace,
288
+ setTraceLink,
289
+ setTraceLoadingTaskId,
290
+ setTraceTasks,
291
+ trace,
292
+ traceLink,
293
+ traceLoadingTaskId,
294
+ traceTasks,
295
+ } = useAppTraceDocuments({ api, operations, setError, setFailedArea, setLoadingArea });
127
296
  const refreshVersion = useRef(0);
297
+ const periodTransitionVersionRef = useRef(0);
128
298
  const detailLoadInFlight = useRef(false);
129
- const costLoadInFlight = useRef(false);
130
299
  const costLoadingCount = useRef(0);
300
+ const costSummaryRequestVersion = useRef(0);
301
+ const costSummaryAbortRef = useRef<AbortController | null>(null);
131
302
  const costsLoadedRef = useRef(false);
132
303
  const operationsDetailLoadedRef = useRef(false);
133
- const costPrefetch = useRef<{ key: string; promise: ReturnType<typeof api.snapshot> } | null>(
134
- null,
135
- );
136
- const costPeriod = requestPeriodForOperations(operationsPeriod);
137
- const costSnapshotKey = `${currency}:${costPeriod.from ?? ""}:${costPeriod.to ?? ""}`;
138
-
304
+ const operationsDetailCancelRef = useRef<(() => void) | null>(null);
305
+ const operationsDetailAbortRef = useRef<AbortController | null>(null);
306
+ const operationsPageAbortRef = useRef<AbortController | null>(null);
307
+ const operationsPageRequestVersionRef = useRef(0);
308
+ const boardLoadedRef = useRef<Partial<Record<WorkBoardStage, number>>>({});
309
+ const boardInFlightRef = useRef(new Set<WorkBoardStage>());
310
+ const overviewStatusInFlightRef = useRef(new Set<string>());
311
+ const boardInitialPageRef = useRef(new Set<WorkBoardStage>());
312
+ const boardFiltersRef = useRef<OperationsPageFilters>({
313
+ query: "",
314
+ status: "",
315
+ risk: "",
316
+ origin: "",
317
+ kind: "",
318
+ });
319
+ const workListFiltersRef = useRef<OperationsPageFilters>({
320
+ query: "",
321
+ status: "",
322
+ risk: "",
323
+ });
324
+ const boardFilterKeyRef = useRef("||||");
325
+ const boardRequestVersionRef = useRef(0);
326
+ const quickOverviewMetricsRef =
327
+ useRef<OperationsProjection["summary"]["overview_metrics"]>(undefined);
328
+ // A new local SQLite generation is immutable and verified by the launcher.
329
+ // Its server begins warming the compact first fold as soon as readiness is
330
+ // observed, so the first paint must join that bounded cache instead of
331
+ // forcing a second full aggregate calculation.
332
+ const initialAggregateFreshRef = useRef(false);
139
333
  const refresh = useCallback(
140
- async (visible = true, reconcileOpenWork = false) => {
334
+ async (visible = true, reconcileOpenWork = false, refreshProjection = false) => {
335
+ operationsDetailCancelRef.current?.();
336
+ operationsDetailCancelRef.current = null;
337
+ operationsDetailAbortRef.current?.abort();
338
+ operationsDetailAbortRef.current = null;
339
+ boardRequestVersionRef.current += 1;
340
+ boardInFlightRef.current.clear();
341
+ boardInitialPageRef.current.clear();
342
+ boardLoadedRef.current = {};
343
+ setBoardTotals({});
344
+ setBoardKindTotals({});
345
+ setBoardStates({});
346
+ setOverviewStatusPages({});
347
+ setOverviewClassificationPages({});
141
348
  const requestVersion = ++refreshVersion.current;
142
- const isCurrentRequest = () => requestVersion === refreshVersion.current;
349
+ const transitionVersion = periodTransitionVersionRef.current;
350
+ const activeDashboardView = dashboardViewRef.current;
351
+ const period = requestPeriodForOperations(operationsPeriod);
352
+ const isCurrentRequest = () =>
353
+ isCurrentDashboardRequest(requestVersion, refreshVersion.current) &&
354
+ isCurrentOperationsPeriodRequest(
355
+ requestVersion,
356
+ refreshVersion.current,
357
+ period,
358
+ requestPeriodForOperations(operationsPeriodRef.current),
359
+ );
143
360
  if (visible) {
144
361
  setOperationsLoading(true);
145
362
  setLoadingArea("operations");
@@ -147,12 +364,22 @@ export function App() {
147
364
  setError(null);
148
365
  setFailedArea(null);
149
366
  try {
150
- const period = requestPeriodForOperations(operationsPeriod);
151
- if (reconcileOpenWork) {
152
- const snapshot = await api.reconcileOpenWork(currency, period);
367
+ if (reconcileOpenWork || refreshProjection) {
368
+ let snapshot: Awaited<ReturnType<typeof api.refreshProjection>>;
369
+ let reconciliation:
370
+ | Awaited<ReturnType<typeof api.reconcileOpenWork>>["reconciliation"]
371
+ | null = null;
372
+ if (reconcileOpenWork) {
373
+ const reconciledSnapshot = await api.reconcileOpenWork(currency, period);
374
+ reconciliation = reconciledSnapshot.reconciliation;
375
+ snapshot = reconciledSnapshot;
376
+ } else {
377
+ snapshot = await api.refreshProjection(currency, period);
378
+ }
153
379
  if (!isCurrentRequest()) return;
154
- setReconciliationResult(snapshot.reconciliation);
380
+ if (reconciliation !== null) setReconciliationResult(reconciliation);
155
381
  setOperations(snapshot.operations);
382
+ quickOverviewMetricsRef.current = snapshot.operations.summary.overview_metrics;
156
383
  operationsDetailLoadedRef.current = true;
157
384
  setCosts(snapshot.costs);
158
385
  costsLoadedRef.current = true;
@@ -165,33 +392,43 @@ export function App() {
165
392
  }
166
393
  setLastUpdatedAt(snapshot.projection.source_latest_at);
167
394
  } else {
168
- // The overview endpoint carries server-side aggregates for KPI
169
- // parity while omitting expensive Run detail until a capability is selected.
170
- const requestingDetails = operationsDetailLoadedRef.current;
171
- if (costsLoadedRef.current) {
172
- const snapshot = await loadAtomicCostSnapshot(() => api.snapshot(currency, period));
173
- if (!isCurrentRequest()) return;
174
- setOperations(snapshot.operations);
175
- operationsDetailLoadedRef.current = true;
176
- setCosts(snapshot.costs);
177
- costsLoadedRef.current = true;
178
- setDisplayedCosts(null);
179
- setLastUpdatedAt(snapshot.projection.source_latest_at);
180
- } else {
181
- const projection = requestingDetails
182
- ? await api.operations(period)
183
- : await api.operations(period, true);
395
+ {
396
+ // First paint is aggregate-only; board and detail data are loaded
397
+ // on their own bounded routes rather than hydrating entity arrays.
398
+ const [projection, costSummary] = await Promise.all([
399
+ api.operations(period, "quick", undefined, initialAggregateFreshRef.current),
400
+ // An English first paint must have the factual USD summary
401
+ // before Operations formats currency. BRL retains the fast
402
+ // Operations-only path until the user opens Cost Monitor.
403
+ currency === "USD"
404
+ ? api.costMonitor(currency, period, "summary")
405
+ : Promise.resolve(null),
406
+ ]);
184
407
  if (!isCurrentRequest()) return;
408
+ initialAggregateFreshRef.current = false;
185
409
  setOperations(projection);
186
- operationsDetailLoadedRef.current = requestingDetails;
410
+ quickOverviewMetricsRef.current = projection.summary.overview_metrics;
411
+ operationsDetailLoadedRef.current = false;
412
+ if (costSummary !== null) {
413
+ setCosts(costSummary);
414
+ // This is only a first-fold placeholder. It must not suppress
415
+ // the factual, narrow Cost summary when the user opens Cost.
416
+ costsLoadedRef.current = false;
417
+ setDisplayedCosts(null);
418
+ }
187
419
  setLastUpdatedAt(projection.summary.source_latest_at);
188
420
  }
189
421
  }
190
422
  } catch (cause) {
191
423
  if (!isCurrentRequest()) return;
192
424
  setError(cause instanceof Error ? cause.message : String(cause));
193
- setFailedArea("operations");
425
+ setFailedArea(activeDashboardView === "cost-monitor" ? "costs" : "operations");
194
426
  } finally {
427
+ if (
428
+ isCurrentRequest() &&
429
+ isCurrentPeriodTransition(transitionVersion, periodTransitionVersionRef.current)
430
+ )
431
+ setPeriodTransitionPending(false);
195
432
  if (visible && isCurrentRequest()) {
196
433
  setOperationsLoading(false);
197
434
  setLoadingArea((current) => (current === "operations" ? null : current));
@@ -200,129 +437,464 @@ export function App() {
200
437
  },
201
438
  [api, currency, operationsPeriod],
202
439
  );
203
-
204
- const loadOperationsDetails = useCallback(async (): Promise<OperationsProjection | null> => {
205
- if (operationsDetailLoadedRef.current) return operations;
206
- if (detailLoadInFlight.current) return null;
207
- detailLoadInFlight.current = true;
208
- costLoadingCount.current += 1;
209
- setLoadingArea("costs");
210
- setFailedArea(null);
211
- try {
440
+ const loadOperationsDetails = useCallback(
441
+ async (signal?: AbortSignal): Promise<OperationsProjection | null> => {
442
+ if (operationsDetailLoadedRef.current) return null;
443
+ if (detailLoadInFlight.current) return null;
444
+ if (signal?.aborted) return null;
445
+ operationsDetailAbortRef.current?.abort();
446
+ const controller = signal === undefined ? new AbortController() : null;
447
+ const requestSignal = signal ?? controller?.signal;
448
+ if (requestSignal === undefined) return null;
449
+ if (controller !== null) operationsDetailAbortRef.current = controller;
450
+ detailLoadInFlight.current = true;
451
+ costLoadingCount.current += 1;
452
+ setLoadingArea("costs");
453
+ setFailedArea(null);
454
+ try {
455
+ const requestVersion = ++refreshVersion.current;
456
+ const period = requestPeriodForOperations(operationsPeriod);
457
+ const isCurrentRequest = () =>
458
+ isCurrentDashboardRequest(requestVersion, refreshVersion.current) &&
459
+ isCurrentOperationsPeriodRequest(
460
+ requestVersion,
461
+ refreshVersion.current,
462
+ period,
463
+ requestPeriodForOperations(operationsPeriodRef.current),
464
+ );
465
+ const projection = await api.operations(period, "detail", requestSignal);
466
+ if (!isCurrentRequest()) return null;
467
+ setOperations(projection);
468
+ operationsDetailLoadedRef.current = true;
469
+ return projection;
470
+ } catch (cause) {
471
+ if (requestSignal.aborted || isAbortError(cause)) return null;
472
+ setError(cause instanceof Error ? cause.message : String(cause));
473
+ setFailedArea("operations");
474
+ return null;
475
+ } finally {
476
+ if (controller !== null && operationsDetailAbortRef.current === controller) {
477
+ operationsDetailAbortRef.current = null;
478
+ }
479
+ detailLoadInFlight.current = false;
480
+ costLoadingCount.current = Math.max(0, costLoadingCount.current - 1);
481
+ if (costLoadingCount.current === 0) {
482
+ setLoadingArea((current) => (current === "costs" ? null : current));
483
+ }
484
+ }
485
+ },
486
+ [api, operationsPeriod],
487
+ );
488
+ const { loadOperationsEntityPage, loadOperationsLifecyclePage } = useAppOperationsPages({
489
+ api,
490
+ entityFiltersRef,
491
+ operationsPageAbortRef,
492
+ operationsPageRequestVersionRef,
493
+ operationsPeriod,
494
+ operationsPeriodRef,
495
+ refreshVersion,
496
+ setEntityPage,
497
+ setEntityPageKind,
498
+ setOperations,
499
+ });
500
+ const loadFilteredOperationsPage = useCallback(
501
+ (
502
+ view: "runs" | "conversations" | "executions" | "changes" | "decisions",
503
+ filters: OperationsPageFilters,
504
+ ) => {
505
+ entityFiltersRef.current = filters;
506
+ if (view === "changes" || view === "decisions")
507
+ void loadOperationsLifecyclePage(view, 0, filters);
508
+ else void loadOperationsEntityPage(view, 0, filters);
509
+ },
510
+ [loadOperationsEntityPage, loadOperationsLifecyclePage],
511
+ );
512
+ const loadMoreBoard = useCallback(
513
+ async (stage: WorkBoardStage) => {
514
+ const current = operations;
515
+ if (current === null || boardInFlightRef.current.has(stage)) return;
516
+ boardInFlightRef.current.add(stage);
517
+ const requestVersion = boardRequestVersionRef.current;
212
518
  const period = requestPeriodForOperations(operationsPeriod);
213
- const projection = await api.operations(period);
214
- setOperations(projection);
215
- operationsDetailLoadedRef.current = true;
216
- return projection;
217
- } catch (cause) {
218
- setError(cause instanceof Error ? cause.message : String(cause));
219
- setFailedArea("operations");
220
- return null;
221
- } finally {
222
- detailLoadInFlight.current = false;
223
- costLoadingCount.current = Math.max(0, costLoadingCount.current - 1);
224
- if (costLoadingCount.current === 0) {
225
- setLoadingArea((current) => (current === "costs" ? null : current));
519
+ setBoardStates((previous) => ({ ...previous, [stage]: { loading: true, error: null } }));
520
+ const loaded = boardLoadedRef.current[stage] ?? 0;
521
+ try {
522
+ const page = await api.operationsBoard(period, stage, loaded, boardFiltersRef.current);
523
+ if (
524
+ requestVersion !== boardRequestVersionRef.current ||
525
+ period.from !== requestPeriodForOperations(operationsPeriodRef.current).from ||
526
+ period.to !== requestPeriodForOperations(operationsPeriodRef.current).to
527
+ )
528
+ return;
529
+ setOperations((previous) =>
530
+ previous === null
531
+ ? previous
532
+ : {
533
+ ...mergeBoardPages(previous, [page]),
534
+ // Board pages are entity data only. The quick aggregate keeps charts coherent.
535
+ summary: previous.summary,
536
+ },
537
+ );
538
+ setBoardTotals((previous) => ({ ...previous, [stage]: page.total }));
539
+ setBoardKindTotals((previous) => ({ ...previous, [stage]: page.totals_by_kind }));
540
+ const next = loaded + page.prds.length + page.specs.length + page.tasks.length;
541
+ boardLoadedRef.current = { ...boardLoadedRef.current, [stage]: next };
542
+ } catch (cause) {
543
+ if (requestVersion !== boardRequestVersionRef.current || isAbortError(cause)) return;
544
+ setBoardStates((previous) => ({
545
+ ...previous,
546
+ [stage]: {
547
+ loading: false,
548
+ error: cause instanceof Error ? cause.message : String(cause),
549
+ },
550
+ }));
551
+ } finally {
552
+ if (requestVersion === boardRequestVersionRef.current) {
553
+ boardInFlightRef.current.delete(stage);
554
+ setBoardStates((previous) =>
555
+ previous[stage]?.error === null
556
+ ? { ...previous, [stage]: { loading: false, error: null } }
557
+ : previous,
558
+ );
559
+ }
226
560
  }
227
- }
228
- }, [api, operations, operationsPeriod]);
229
-
230
- const loadCosts = useCallback(async () => {
231
- if (costsLoadedRef.current) return;
232
- if (costLoadInFlight.current) return;
233
- costLoadInFlight.current = true;
234
- costLoadingCount.current += 1;
235
- setLoadingArea("costs");
236
- setFailedArea(null);
237
- try {
561
+ },
562
+ [api, operations, operationsPeriod],
563
+ );
564
+ const loadFilteredBoard = useCallback((filters: OperationsPageFilters) => {
565
+ const normalizedFilters = {
566
+ ...filters,
567
+ kind: filters.kind ?? boardFiltersRef.current.kind ?? "",
568
+ };
569
+ const filterKey = `${normalizedFilters.query}|${normalizedFilters.status}|${normalizedFilters.risk}|${normalizedFilters.origin ?? ""}|${normalizedFilters.kind}`;
570
+ if (filterKey === boardFilterKeyRef.current) return;
571
+ boardFilterKeyRef.current = filterKey;
572
+ boardFiltersRef.current = normalizedFilters;
573
+ boardRequestVersionRef.current += 1;
574
+ boardInFlightRef.current.clear();
575
+ boardInitialPageRef.current.clear();
576
+ boardLoadedRef.current = {};
577
+ setBoardTotals({});
578
+ setBoardKindTotals({});
579
+ setBoardStates({});
580
+ setOverviewStatusPages({});
581
+ setOverviewClassificationPages({});
582
+ setOperations((current) =>
583
+ current === null
584
+ ? current
585
+ : {
586
+ ...current,
587
+ prds: [],
588
+ specs: [],
589
+ tasks: [],
590
+ changes: [],
591
+ summary:
592
+ filterKey === "||||" && quickOverviewMetricsRef.current !== undefined
593
+ ? { ...current.summary, overview_metrics: quickOverviewMetricsRef.current }
594
+ : current.summary,
595
+ },
596
+ );
597
+ }, []);
598
+ const loadFilteredWorkList = useCallback(
599
+ async (filters: OperationsPageFilters) => {
600
+ const requestVersion = ++boardRequestVersionRef.current;
238
601
  const period = requestPeriodForOperations(operationsPeriod);
239
- const prefetched = costPrefetch.current;
240
- await loadCostViewSnapshot(
241
- () =>
242
- prefetched?.key === costSnapshotKey ? prefetched.promise : api.snapshot(currency, period),
243
- (snapshot) => {
244
- setOperations(snapshot.operations);
245
- operationsDetailLoadedRef.current = true;
246
- setCosts(snapshot.costs);
247
- setDisplayedCosts(null);
248
- setLastUpdatedAt(snapshot.projection.source_latest_at);
602
+ const normalizedFilters = normalizeWorkListFilters(filters, workListFiltersRef.current);
603
+ workListFiltersRef.current = normalizedFilters;
604
+ const requestedKind = selectedWorkListKinds(normalizedFilters.kind);
605
+ const loadKindStage = async (
606
+ stage: WorkBoardStage,
607
+ kind: "prd" | "spec" | "task" | "standalone-task",
608
+ ) => {
609
+ return collectWorkListPages(async (offset) => {
610
+ const page = await api.operationsBoard(
611
+ period,
612
+ stage,
613
+ offset,
614
+ { ...normalizedFilters, kind },
615
+ undefined,
616
+ 50,
617
+ );
618
+ return {
619
+ total: page.total,
620
+ received: page.prds.length + page.specs.length + page.tasks.length,
621
+ value: page,
622
+ };
623
+ });
624
+ };
625
+ try {
626
+ const pages = (
627
+ await Promise.all(
628
+ initialWorkBoardStages.flatMap((stage) =>
629
+ requestedKind.map((kind) => loadKindStage(stage, kind)),
630
+ ),
631
+ )
632
+ ).flat();
633
+ if (
634
+ requestVersion !== boardRequestVersionRef.current ||
635
+ period.from !== requestPeriodForOperations(operationsPeriodRef.current).from ||
636
+ period.to !== requestPeriodForOperations(operationsPeriodRef.current).to
637
+ )
638
+ return;
639
+ const unique = <T extends { id: string }>(rows: readonly T[]) => [
640
+ ...new Map(rows.map((row) => [row.id, row] as const)).values(),
641
+ ];
642
+ setOperations((current) =>
643
+ current === null
644
+ ? current
645
+ : {
646
+ ...current,
647
+ prds: unique(pages.flatMap((page) => page.prds)),
648
+ specs: unique(pages.flatMap((page) => page.specs)),
649
+ tasks: unique(pages.flatMap((page) => page.tasks)),
650
+ },
651
+ );
652
+ } catch (cause) {
653
+ if (requestVersion !== boardRequestVersionRef.current || isAbortError(cause)) return;
654
+ setError(cause instanceof Error ? cause.message : String(cause));
655
+ setFailedArea("operations");
656
+ }
657
+ },
658
+ [api, operationsPeriod],
659
+ );
660
+ const loadOverviewStatusPage = useCallback(
661
+ async (stage: WorkBoardStage, kind: OverviewStatusKind, offset: number) => {
662
+ const key = overviewStatusPageKey(stage, kind);
663
+ if (overviewStatusInFlightRef.current.has(key)) return;
664
+ overviewStatusInFlightRef.current.add(key);
665
+ const requestVersion = boardRequestVersionRef.current;
666
+ const period = requestPeriodForOperations(operationsPeriod);
667
+ setOverviewStatusPages((previous) => ({
668
+ ...previous,
669
+ [key]: {
670
+ items: offset === 0 ? [] : (previous[key]?.items ?? []),
671
+ total: previous[key]?.total ?? 0,
672
+ loading: true,
673
+ error: null,
249
674
  },
250
- );
251
- costsLoadedRef.current = true;
252
- costPrefetch.current = null;
253
- } catch (cause) {
254
- setError(cause instanceof Error ? cause.message : String(cause));
255
- setFailedArea("costs");
256
- } finally {
257
- costLoadInFlight.current = false;
258
- costLoadingCount.current = Math.max(0, costLoadingCount.current - 1);
259
- if (costLoadingCount.current === 0) {
260
- setLoadingArea((current) => (current === "costs" ? null : current));
675
+ }));
676
+ try {
677
+ const page = await api.operationsBoard(period, stage, offset, { kind });
678
+ if (
679
+ requestVersion !== boardRequestVersionRef.current ||
680
+ period.from !== requestPeriodForOperations(operationsPeriodRef.current).from ||
681
+ period.to !== requestPeriodForOperations(operationsPeriodRef.current).to
682
+ )
683
+ return;
684
+ const items = kind === "task" ? page.tasks : kind === "spec" ? page.specs : page.prds;
685
+ setOverviewStatusPages((previous) => ({
686
+ ...previous,
687
+ [key]: {
688
+ items: offset === 0 ? items : [...(previous[key]?.items ?? []), ...items],
689
+ total: page.total,
690
+ loading: false,
691
+ error: null,
692
+ },
693
+ }));
694
+ } catch (cause) {
695
+ if (requestVersion !== boardRequestVersionRef.current || isAbortError(cause)) return;
696
+ setOverviewStatusPages((previous) => ({
697
+ ...previous,
698
+ [key]: {
699
+ items: previous[key]?.items ?? [],
700
+ total: previous[key]?.total ?? 0,
701
+ loading: false,
702
+ error: cause instanceof Error ? cause.message : String(cause),
703
+ },
704
+ }));
705
+ } finally {
706
+ overviewStatusInFlightRef.current.delete(key);
261
707
  }
262
- }
263
- }, [api, costSnapshotKey, currency, operationsPeriod]);
264
-
708
+ },
709
+ [api, operationsPeriod],
710
+ );
711
+ const loadOverviewClassificationPage = useCallback(
712
+ async (kind: OverviewStatusKind, classification: OverviewClassification, offset: number) => {
713
+ const key = overviewClassificationPageKey(kind, classification);
714
+ if (overviewStatusInFlightRef.current.has(key)) return;
715
+ overviewStatusInFlightRef.current.add(key);
716
+ const requestVersion = boardRequestVersionRef.current;
717
+ const period = requestPeriodForOperations(operationsPeriod);
718
+ setOverviewClassificationPages((previous) => ({
719
+ ...previous,
720
+ [key]: {
721
+ items: offset === 0 ? [] : (previous[key]?.items ?? []),
722
+ total: previous[key]?.total ?? 0,
723
+ loading: true,
724
+ error: null,
725
+ },
726
+ }));
727
+ try {
728
+ const page = await api.operationsClassification(period, kind, classification, offset);
729
+ if (
730
+ requestVersion !== boardRequestVersionRef.current ||
731
+ period.from !== requestPeriodForOperations(operationsPeriodRef.current).from ||
732
+ period.to !== requestPeriodForOperations(operationsPeriodRef.current).to
733
+ )
734
+ return;
735
+ const items = kind === "task" ? page.tasks : kind === "spec" ? page.specs : page.prds;
736
+ setOverviewClassificationPages((previous) => ({
737
+ ...previous,
738
+ [key]: {
739
+ items: offset === 0 ? items : [...(previous[key]?.items ?? []), ...items],
740
+ total: page.total,
741
+ loading: false,
742
+ error: null,
743
+ },
744
+ }));
745
+ } catch (cause) {
746
+ if (requestVersion !== boardRequestVersionRef.current || isAbortError(cause)) return;
747
+ setOverviewClassificationPages((previous) => ({
748
+ ...previous,
749
+ [key]: {
750
+ items: previous[key]?.items ?? [],
751
+ total: previous[key]?.total ?? 0,
752
+ loading: false,
753
+ error: cause instanceof Error ? cause.message : String(cause),
754
+ },
755
+ }));
756
+ } finally {
757
+ overviewStatusInFlightRef.current.delete(key);
758
+ }
759
+ },
760
+ [api, operationsPeriod],
761
+ );
762
+ const boardExportSource = useCallback(
763
+ (selectedFilters: { query: string; status: string; risk: string; origin: string }) => {
764
+ const filters = { ...boardFiltersRef.current, ...selectedFilters };
765
+ const period = requestPeriodForOperations(operationsPeriod);
766
+ return {
767
+ prepare: async () => {
768
+ const firstPages = await Promise.all(
769
+ initialWorkBoardStages.map((stage) => api.operationsBoard(period, stage, 0, filters)),
770
+ );
771
+ const rowsFor = (page: OperationsBoardPage) => [
772
+ ...page.prds.map(({ kind: _kind, ...row }) => ({ kind: "prd", ...row })),
773
+ ...page.specs.map(({ kind: _kind, ...row }) => ({ kind: "spec", ...row })),
774
+ ...page.tasks.map((row) => ({ kind: "task", ...row })),
775
+ ];
776
+ return {
777
+ totalRows: firstPages.reduce((sum, page) => sum + page.total, 0),
778
+ pages: async function* () {
779
+ for (const firstPage of firstPages) {
780
+ let page = firstPage;
781
+ let offset = 0;
782
+ while (true) {
783
+ const rows = rowsFor(page);
784
+ if (rows.length === 0) break;
785
+ offset += rows.length;
786
+ yield rows;
787
+ if (offset >= page.total) break;
788
+ page = await api.operationsBoard(period, page.stage, offset, filters);
789
+ }
790
+ }
791
+ },
792
+ };
793
+ },
794
+ };
795
+ },
796
+ [api, operationsPeriod],
797
+ );
798
+ const detailExportSource = useCallback(
799
+ (
800
+ view: OperationsExportView,
801
+ filters: { query: string; status: string; risk: string; origin: string },
802
+ ) => {
803
+ const period = requestPeriodForOperations(operationsPeriod);
804
+ if (view === "work-kanban") return undefined;
805
+ const sqliteView = {
806
+ "work-list": "operations_work_list",
807
+ changes: "operations_changes",
808
+ decisions: "operations_decisions",
809
+ executions: "operations_executions",
810
+ runs: "operations_agent_invocations",
811
+ conversations: "operations_conversations",
812
+ daily: "operations_daily",
813
+ monthly: "operations_monthly",
814
+ yearly: "operations_annual",
815
+ } as const satisfies Partial<Record<OperationsExportView, SqliteViewName>>;
816
+ const source = sqliteView[view];
817
+ if (source === undefined) return undefined;
818
+ const options = (offset: number) => ({
819
+ offset,
820
+ limit: 100,
821
+ ...(filters.query ? { query: filters.query } : {}),
822
+ ...(filters.status ? { status: filters.status } : {}),
823
+ ...(filters.risk ? { risk: filters.risk } : {}),
824
+ ...(filters.origin ? { origin: filters.origin } : {}),
825
+ });
826
+ return {
827
+ prepare: async () => {
828
+ const firstPage = await api.sqliteViewPage(source, period, options(0));
829
+ return {
830
+ totalRows: firstPage.total_count,
831
+ pages: async function* () {
832
+ let page = firstPage;
833
+ while (true) {
834
+ if (page.rows.length === 0) return;
835
+ yield page.rows;
836
+ const nextOffset = page.offset + page.rows.length;
837
+ if (nextOffset >= page.total_count) return;
838
+ page = await api.sqliteViewPage(source, period, options(nextOffset));
839
+ }
840
+ },
841
+ };
842
+ },
843
+ };
844
+ },
845
+ [api, operationsPeriod],
846
+ );
265
847
  useEffect(() => {
266
- if (
267
- operations === null ||
268
- costsLoadedRef.current ||
269
- costPrefetch.current?.key === costSnapshotKey
270
- )
271
- return;
272
- const promise = api.snapshot(currency, requestPeriodForOperations(operationsPeriod));
273
- costPrefetch.current = { key: costSnapshotKey, promise };
274
- void promise.catch(() => {
275
- if (costPrefetch.current?.promise === promise) costPrefetch.current = null;
276
- });
277
- }, [api, costSnapshotKey, currency, operations, operationsPeriod]);
848
+ if (dashboardView !== "operations" || operations === null) return;
849
+ let cancelled = false;
850
+ const hydrateBoard = () => {
851
+ if (cancelled) return;
852
+ for (const stage of initialWorkBoardStages) {
853
+ if (!boardInitialPageRef.current.has(stage) && !boardInFlightRef.current.has(stage)) {
854
+ boardInitialPageRef.current.add(stage);
855
+ void loadMoreBoard(stage);
856
+ }
857
+ }
858
+ };
859
+ // The quick projection is the complete first-fold contract. Defer the
860
+ // bounded Kanban cards until after that first paint so they cannot hold up
861
+ // the useful overview when SQLite must materialise the board view.
862
+ const idle =
863
+ typeof window.requestIdleCallback === "function"
864
+ ? { kind: "idle" as const, id: window.requestIdleCallback(hydrateBoard) }
865
+ : { kind: "timeout" as const, id: window.setTimeout(hydrateBoard, 0) };
866
+ return () => {
867
+ cancelled = true;
868
+ if (idle.kind === "idle") window.cancelIdleCallback(idle.id);
869
+ else window.clearTimeout(idle.id);
870
+ };
871
+ }, [dashboardView, loadMoreBoard, operations]);
278
872
 
279
873
  const refreshFromDialog = async (reconcileOpenWork: boolean) => {
280
874
  if (reconcileOpenWork) setReconciliationResult(null);
281
875
  setRefreshDialog(reconcileOpenWork ? "reconciling" : "refreshing");
282
- await refresh(false, reconcileOpenWork);
876
+ await refresh(false, reconcileOpenWork, !reconcileOpenWork);
283
877
  setRefreshDialog(null);
284
878
  };
285
-
286
- useEffect(() => {
287
- void refresh();
288
- const timer = window.setInterval(() => void refresh(false), 30_000);
289
- return () => window.clearInterval(timer);
290
- }, [refresh]);
291
-
292
- useEffect(() => {
293
- window.localStorage.setItem(localeKey, costLocale);
294
- }, [costLocale]);
295
-
296
- useEffect(() => {
297
- window.localStorage.setItem(currencyKey, currency);
298
- }, [currency]);
299
-
300
- useEffect(() => {
301
- if (
302
- !["/", pricingSetupPath, agentCatalogSetupPath, projectSetupPath].includes(
303
- window.location.pathname,
304
- )
305
- ) {
306
- window.history.replaceState(null, "", `/${window.location.search}${window.location.hash}`);
307
- setPathname("/");
308
- }
309
- }, []);
310
-
311
- useEffect(() => {
312
- const syncPathname = () => setPathname(window.location.pathname);
313
- window.addEventListener("popstate", syncPathname);
314
- return () => window.removeEventListener("popstate", syncPathname);
315
- }, []);
316
-
317
- useEffect(() => {
318
- if (pathname !== pricingSetupPath || pricing !== null) return;
319
- void Promise.all([api.pricing(), api.fxRate()])
320
- .then(([catalog, rate]) => {
321
- setPricing(catalog);
322
- setFxRate(rate);
323
- })
324
- .catch((cause) => setError(String(cause)));
325
- }, [api, pathname, pricing]);
879
+ useAppEffects({
880
+ api,
881
+ costLocale,
882
+ currency,
883
+ dashboardView,
884
+ loadOperationsDetails,
885
+ operations,
886
+ operationsDetailAbortRef,
887
+ operationsDetailCancelRef,
888
+ operationsPageAbortRef,
889
+ pathname,
890
+ pricing,
891
+ refresh,
892
+ refreshVersion,
893
+ setError,
894
+ setFxRate,
895
+ setPathname,
896
+ setPricing,
897
+ });
326
898
 
327
899
  const navigate = (nextPath: string) => {
328
900
  window.history.pushState(null, "", nextPath);
@@ -330,79 +902,68 @@ export function App() {
330
902
  window.scrollTo({ left: 0, top: 0 });
331
903
  };
332
904
 
333
- async function openTrace(taskId: string) {
334
- setError(null);
905
+ const loadCostSummary = useCallback(async () => {
906
+ costSummaryAbortRef.current?.abort();
907
+ const controller = new AbortController();
908
+ costSummaryAbortRef.current = controller;
909
+ const requestVersion = ++costSummaryRequestVersion.current;
910
+ const period = requestPeriodForOperations(operationsPeriod);
911
+ costLoadingCount.current += 1;
912
+ setLoadingArea("costs");
335
913
  setFailedArea(null);
336
- setTrace(null);
337
- setTraceLoadingTaskId(taskId);
338
- setLoadingArea("traceability");
339
- await new Promise<void>((resolveFrame) => window.requestAnimationFrame(() => resolveFrame()));
340
914
  try {
341
- const loadedTrace = await api.taskTrace(taskId);
342
- const cardTask = operations?.tasks.find((task) => task.id === taskId);
343
- setTrace({
344
- ...loadedTrace,
345
- task_title: cardTask?.title || loadedTrace.task_title || taskId,
346
- task_risk: cardTask?.risk || loadedTrace.task_risk || null,
347
- task_created_at: cardTask?.created_at || loadedTrace.task_created_at || null,
348
- task_progress: cardTask?.progress ?? loadedTrace.task_progress ?? 0,
349
- });
350
- return true;
915
+ const summary = await api.costMonitor(currency, period, "summary", controller.signal);
916
+ if (controller.signal.aborted || requestVersion !== costSummaryRequestVersion.current) return;
917
+ setCosts(summary);
918
+ costsLoadedRef.current = true;
919
+ setDisplayedCosts(null);
351
920
  } catch (cause) {
921
+ if (controller.signal.aborted || requestVersion !== costSummaryRequestVersion.current) return;
352
922
  setError(cause instanceof Error ? cause.message : String(cause));
353
- setFailedArea("traceability");
354
- return false;
923
+ setFailedArea("costs");
355
924
  } finally {
356
- setTraceLoadingTaskId((current) => (current === taskId ? null : current));
357
- setLoadingArea((current) => (current === "traceability" ? null : current));
925
+ costLoadingCount.current = Math.max(0, costLoadingCount.current - 1);
926
+ if (costLoadingCount.current === 0)
927
+ setLoadingArea((current) => (current === "costs" ? null : current));
928
+ if (costSummaryAbortRef.current === controller) costSummaryAbortRef.current = null;
358
929
  }
359
- }
360
-
361
- function openTraceChange(changeId: string) {
362
- const row = operations?.changes.find((change) => change.id === changeId);
363
- if (row === undefined) return;
364
- setTrace(null);
365
- setTraceLink({ kind: "change", row });
366
- }
930
+ }, [api, currency, operationsPeriod]);
367
931
 
368
- function openTraceMacro(label: "PRD" | "SPEC", id: string) {
369
- const rows = label === "PRD" ? operations?.prds : operations?.specs;
370
- const row = rows?.find((macro) => macro.id === id);
371
- if (row === undefined) return;
372
- setTrace(null);
373
- setTraceLink({ kind: "macro", label, row });
374
- }
375
-
376
- async function openDocument(kind: "prd" | "spec", id: string) {
377
- setError(null);
378
- try {
379
- if (kind === "prd") {
380
- setDocumentDetail({ label: "PRD", detail: await api.prd(id) });
381
- } else {
382
- setDocumentDetail({ label: "SPEC", detail: await api.spec(id) });
383
- }
384
- } catch (cause) {
385
- setError(cause instanceof Error ? cause.message : String(cause));
386
- }
387
- }
932
+ // The global language selector can request USD while Operations is active.
933
+ // Acquire its coherent Cost Monitor snapshot here instead of requiring a
934
+ // navigation to the Cost Monitor tab first.
935
+ useEffect(() => {
936
+ if (currency !== "USD" || costs?.currency === currency) return;
937
+ void loadCostSummary();
938
+ }, [costs?.currency, currency, loadCostSummary]);
388
939
 
389
940
  const selectDashboardView = async (nextView: DashboardView) => {
390
941
  setDashboardView(nextView);
391
942
  window.scrollTo({ left: 0, top: 0 });
392
- const detailedOperations =
393
- nextView === "operations" || nextView === "traceability"
394
- ? await loadOperationsDetails()
395
- : operations;
396
943
  if (nextView === "cost-monitor") {
397
- void loadCosts();
944
+ if (costs?.currency !== currency) void loadCostSummary();
945
+ } else {
946
+ costSummaryAbortRef.current?.abort();
398
947
  }
399
948
  if (nextView === "traceability") {
400
- const latest = (detailedOperations ?? operations)?.tasks
401
- .filter((task) => task.status === "completed")
402
- .toSorted(
403
- (a, b) =>
404
- Date.parse(b.finished_at ?? b.updated_at) - Date.parse(a.finished_at ?? a.updated_at),
405
- )[0];
949
+ let candidates = traceTasks.length > 0 ? traceTasks : (operations?.tasks ?? []);
950
+ if (candidates.length === 0) {
951
+ const requestVersion = boardRequestVersionRef.current;
952
+ // Traceability is a global task selector, not a temporal work-board view.
953
+ for (const stage of traceTaskSearchStages) {
954
+ const page = await api.operationsBoard({ from: null, to: null }, stage, 0);
955
+ if (requestVersion !== boardRequestVersionRef.current) return;
956
+ if (page.tasks.length === 0) continue;
957
+ candidates = page.tasks;
958
+ setTraceTasks(page.tasks);
959
+ break;
960
+ }
961
+ }
962
+ const ordered = candidates.toSorted(
963
+ (a, b) =>
964
+ Date.parse(b.finished_at ?? b.updated_at) - Date.parse(a.finished_at ?? a.updated_at),
965
+ );
966
+ const latest = ordered.find((task) => task.status === "completed") ?? ordered[0];
406
967
  if (latest) {
407
968
  setSelectedTraceTaskId(latest.id);
408
969
  void openTrace(latest.id);
@@ -423,344 +984,312 @@ export function App() {
423
984
  const currentCostByRun = useMemo(
424
985
  () =>
425
986
  new Map(
426
- (displayedCosts ?? costs)?.runs.map((run) => [run.run_id, run.estimated_brl] as const) ??
427
- [],
987
+ (displayedCosts ?? costs)?.runs.map(
988
+ (run) =>
989
+ [run.run_id, currency === "USD" ? run.estimated_usd : run.estimated_brl] as const,
990
+ ) ?? [],
428
991
  ),
429
- [costs, displayedCosts],
992
+ [costs, currency, displayedCosts],
430
993
  );
994
+ const factualCostTotal = useMemo(() => {
995
+ const snapshot = displayedCosts ?? costs;
996
+ if (snapshot?.currency !== currency) return null;
997
+ return currency === "USD" ? snapshot.overview.estimated_usd : snapshot.overview.estimated_brl;
998
+ }, [costs, currency, displayedCosts]);
999
+ const factualCostPeriods = useMemo(() => {
1000
+ const snapshot = displayedCosts ?? costs;
1001
+ if (snapshot?.currency !== currency) return undefined;
1002
+ return {
1003
+ day: snapshot.groups.daily,
1004
+ month: snapshot.groups.monthly,
1005
+ year: snapshot.groups.annual,
1006
+ };
1007
+ }, [costs, currency, displayedCosts]);
431
1008
  const retryLastLoad = () => {
432
1009
  setError(null);
433
- if (failedArea === "costs") void loadCosts();
1010
+ if (failedArea === "costs") void refresh();
434
1011
  else if (failedArea === "traceability" && selectedTraceTaskId)
435
1012
  void openTrace(selectedTraceTaskId);
1013
+ else if (failedArea === "operations" && operations !== null) void loadOperationsDetails();
436
1014
  else void refresh();
437
1015
  };
438
1016
 
439
- if (pathname === pricingSetupPath) {
440
- if (pricing === null) {
441
- return (
442
- <main className="app-state">
443
- <p>{costLocale === "pt-BR" ? "Configuração do projeto" : "Project settings"}</p>
444
- <h1>
445
- {costLocale === "pt-BR" ? "Carregando catálogo de preços…" : "Loading pricing catalog…"}
446
- </h1>
447
- <button onClick={() => navigate("/")} type="button">
448
- {costLocale === "pt-BR" ? "Voltar ao dashboard" : "Back to dashboard"}
449
- </button>
450
- </main>
451
- );
1017
+ const setupRoute = renderAppSetupRoute({
1018
+ api,
1019
+ costLocale,
1020
+ fxRate,
1021
+ navigate,
1022
+ pathname,
1023
+ pricing,
1024
+ refresh,
1025
+ setFxRate,
1026
+ setPricing,
1027
+ });
1028
+ // A request is not an empty dashboard state. One accessible global gate
1029
+ // prevents every tab from briefly presenting zero/empty placeholders while
1030
+ // its coherent replacement snapshot or page is still in flight.
1031
+ const globalLoadingPending =
1032
+ periodTransitionPending ||
1033
+ (currency === "USD" && costs?.currency !== currency) ||
1034
+ (loadingArea !== null && loadingArea !== "costs") ||
1035
+ costSurfaceLoading ||
1036
+ operationsSurfaceLoading ||
1037
+ entityPage.loading ||
1038
+ Object.values(boardStates).some((state) => state.loading);
1039
+ // The stale snapshot is useful only while the initial screen still waits
1040
+ // for data. Once Operations has arrived, the normal dashboard is the
1041
+ // clearest confirmation that it opened; do not leave a duplicate toast.
1042
+ const initialDataPending = operations === null && operationsLoading;
1043
+ useEffect(() => {
1044
+ if (!snapshotUpdating || !initialDataPending || snapshotNoticeDismissed) {
1045
+ setSnapshotNoticeDelayElapsed(false);
1046
+ return undefined;
452
1047
  }
453
- return (
454
- <Suspense fallback={null}>
455
- <PricingSettingsPage
456
- catalog={pricing}
457
- fxRate={fxRate}
458
- locale={costLocale}
459
- onClose={() => navigate("/")}
460
- onSave={async (catalog) => {
461
- setPricing(await api.savePricing(catalog));
462
- await refresh();
463
- }}
464
- onSaveFxRate={async (rate) => {
465
- setFxRate(await api.saveFxRate(rate));
466
- await refresh();
467
- }}
468
- />
469
- </Suspense>
470
- );
471
- }
472
- if (pathname === agentCatalogSetupPath) {
473
- return (
474
- <Suspense fallback={null}>
475
- <AgentCatalogSettingsPage api={api} locale={costLocale} onClose={() => navigate("/")} />
476
- </Suspense>
1048
+ const timeout = window.setTimeout(
1049
+ () => setSnapshotNoticeDelayElapsed(true),
1050
+ snapshotNoticeDelayMs,
477
1051
  );
478
- }
479
- if (pathname === projectSetupPath) {
480
- return (
481
- <Suspense fallback={null}>
482
- <ProjectSettingsPage api={api} locale={costLocale} onClose={() => navigate("/")} />
483
- </Suspense>
484
- );
485
- }
486
- if (error && operations === null) {
487
- return (
488
- <main className="app-state">
489
- <p>Dashboard local indisponível</p>
490
- <h1>Não foi possível ler o workspace V2.</h1>
491
- <pre>{error}</pre>
492
- <button onClick={() => void refresh()} type="button">
493
- Tentar novamente
494
- </button>
495
- </main>
496
- );
497
- }
498
- if (operations === null) {
499
- return <DashboardSkeleton locale={costLocale} />;
500
- }
501
- return (
502
- <>
503
- {error ? (
504
- <div className="app-warning" role="status">
505
- <span>{error}</span>
506
- {failedArea ? (
507
- <button onClick={retryLastLoad} type="button">
508
- {costLocale === "pt-BR" ? "Tentar novamente" : "Try again"}
509
- </button>
510
- ) : null}
511
- <button onClick={() => setError(null)} type="button">
512
- ×
513
- </button>
514
- </div>
515
- ) : null}
516
- <OperationsPage
517
- alternateContent={
518
- dashboardView === "cost-monitor" ? (
519
- costs === null ? (
520
- () =>
521
- failedArea === "costs" ? (
522
- <LoadFailedPanel locale={costLocale} onRetry={retryLastLoad} />
523
- ) : (
524
- <LoadingPanel area="costs" locale={costLocale} />
525
- )
526
- ) : (
527
- (globalFilters) => (
528
- <CostMonitorPage
529
- currency={currency}
530
- data={costs}
531
- embedded
532
- globalFilters={globalFilters}
533
- lastUpdatedAt={lastUpdatedAt}
534
- locale={costLocale}
535
- loading={loadingArea === "costs"}
536
- onLocaleChange={setCostLocale}
537
- onCurrencyChange={setCurrency}
538
- onActiveDataChange={setDisplayedCosts}
539
- onOpenTrace={(taskId) => void openTrace(taskId)}
540
- onRefresh={() => setRefreshDialog("choose")}
541
- operations={operations}
542
- />
543
- )
544
- )
545
- ) : dashboardView === "traceability" ? (
546
- () => (
547
- <TraceabilityWorkspace
548
- loadingTaskId={traceLoadingTaskId}
549
- locale={costLocale}
550
- onSelectTask={(taskId) => {
551
- setSelectedTraceTaskId(taskId);
552
- void openTrace(taskId);
553
- }}
554
- onOpenChange={openTraceChange}
555
- onOpenPrd={(prdId) => openTraceMacro("PRD", prdId)}
556
- onOpenSpec={(specId) => openTraceMacro("SPEC", specId)}
557
- selectedTaskId={selectedTraceTaskId}
558
- tasks={operations.tasks}
559
- trace={trace}
560
- />
561
- )
562
- ) : dashboardView === null ? (
563
- <span aria-hidden="true" hidden />
564
- ) : undefined
565
- }
566
- capabilityNavigation={capabilityNavigation}
567
- data={operations}
568
- dashboardTitle={dashboardIdentity.title}
569
- repositoryName={dashboardIdentity.repositoryName}
570
- loading={operationsLoading}
571
- locale={costLocale}
572
- resolveRunCostBrl={
573
- costs === null ? undefined : (runId) => currentCostByRun.get(runId) ?? null
1052
+ return () => window.clearTimeout(timeout);
1053
+ }, [initialDataPending, snapshotNoticeDismissed, snapshotUpdating]);
1054
+ const snapshotNoticeVisible = shouldShowSnapshotNotice({
1055
+ snapshotUpdating,
1056
+ initialDataPending,
1057
+ dismissed: snapshotNoticeDismissed,
1058
+ delayElapsed: snapshotNoticeDelayElapsed,
1059
+ });
1060
+ const snapshotNotice = snapshotNoticeVisible ? (
1061
+ <div aria-live="polite" className="app-notice app-snapshot-status" role="status">
1062
+ <span>
1063
+ Exibindo o último snapshot local validado. Os dados canônicos estão sendo atualizados em
1064
+ segundo plano e estarão atuais na próxima abertura.
1065
+ </span>
1066
+ <button
1067
+ aria-label={
1068
+ costLocale === "pt-BR"
1069
+ ? "Fechar aviso de atualização do snapshot"
1070
+ : "Dismiss snapshot update notice"
574
1071
  }
575
- onLocaleChange={setCostLocale}
576
- onDetailDataRequest={() => void loadOperationsDetails()}
577
- onOpenDocument={(kind, id) => void openDocument(kind, id)}
578
- onOpenTask={openTrace}
579
- onPeriodChange={(preset, range) => setOperationsPeriod({ preset, range })}
580
- onRefresh={() => setRefreshDialog("choose")}
581
- settings={
582
- <div className="dashboard-global-settings">
583
- <label>
584
- <span>{costLocale === "pt-BR" ? "Moeda" : "Currency"}</span>
585
- <select
586
- aria-label={costLocale === "pt-BR" ? "Moeda" : "Currency"}
587
- onChange={(event) => setCurrency(event.target.value as CostCurrency)}
588
- value={currency}
589
- >
590
- <option value="BRL">BRL · Real brasileiro</option>
591
- <option value="USD">USD · US Dollar</option>
592
- </select>
593
- </label>
594
- <button className="dashboard-help-open" onClick={() => setHelpOpen(true)} type="button">
595
- {costLocale === "pt-BR" ? "Abrir ajuda e guides" : "Open help and guides"}
596
- </button>
597
- <button
598
- className="dashboard-help-open"
599
- onClick={() => navigate(pricingSetupPath)}
600
- type="button"
601
- >
602
- {costLocale === "pt-BR" ? "Configurar preços e câmbio" : "Configure pricing and FX"}
603
- </button>
604
- <button
605
- className="dashboard-help-open"
606
- onClick={() => navigate(agentCatalogSetupPath)}
607
- type="button"
608
- >
609
- {costLocale === "pt-BR" ? "Configurar agentes" : "Configure agents"}
610
- </button>
1072
+ onClick={() => setSnapshotNoticeDismissed(true)}
1073
+ type="button"
1074
+ >
1075
+ ×
1076
+ </button>
1077
+ </div>
1078
+ ) : null;
1079
+ const renderedOperations = operations as OperationsProjection;
1080
+ return (
1081
+ <AppLoadingBoundary
1082
+ error={error}
1083
+ globalLoadingPending={globalLoadingPending}
1084
+ locale={costLocale}
1085
+ onRefresh={() => void refresh()}
1086
+ operations={operations}
1087
+ snapshotNotice={snapshotNotice}
1088
+ setupRoute={setupRoute}
1089
+ >
1090
+ <PeriodTransitionShell pending={globalLoadingPending}>
1091
+ {error ? (
1092
+ <div aria-live="assertive" className="app-warning app-error" role="alert">
1093
+ <span>{error}</span>
1094
+ {failedArea ? (
1095
+ <button onClick={retryLastLoad} type="button">
1096
+ {costLocale === "pt-BR" ? "Tentar novamente" : "Try again"}
1097
+ </button>
1098
+ ) : null}
611
1099
  <button
612
- className="dashboard-help-open"
613
- onClick={() => navigate(projectSetupPath)}
1100
+ aria-label={costLocale === "pt-BR" ? "Fechar erro" : "Dismiss error"}
1101
+ onClick={() => setError(null)}
614
1102
  type="button"
615
1103
  >
616
- {costLocale === "pt-BR" ? "Configurar projeto" : "Configure project"}
1104
+ ×
617
1105
  </button>
618
1106
  </div>
619
- }
620
- />
621
- {dashboardView === "traceability" ? null : traceLoadingTaskId ? (
622
- <TaskTraceLoadingPanel locale={costLocale} taskId={traceLoadingTaskId} />
623
- ) : trace ? (
624
- <TaskTracePanel
1107
+ ) : null}
1108
+ {/* Cost Monitor boundary retains loadRunPage={loadCostRunPage}, loadReadModelPage={loadCostReadModelPage}, and loadDetail={loadCostDetail}. */}
1109
+ <OperationsPage
1110
+ alternateContent={renderDashboardAlternateContent({
1111
+ costs,
1112
+ costLocale,
1113
+ currency,
1114
+ dashboardView,
1115
+ failedArea,
1116
+ lastUpdatedAt,
1117
+ loadCostDetail,
1118
+ loadCostReadModelPage,
1119
+ loadCostRunPage,
1120
+ loadCostGroupPage,
1121
+ loadingArea,
1122
+ onActiveDataChange: setDisplayedCosts,
1123
+ onCurrencyChange: setCurrency,
1124
+ onLoadingChange: setCostSurfaceLoading,
1125
+ onLocaleChange: changeDashboardLocale,
1126
+ onOpenTrace: (taskId) => void openTrace(taskId),
1127
+ onRefresh: () => setRefreshDialog("choose"),
1128
+ onRetry: retryLastLoad,
1129
+ onSelectTraceTask: (taskId) => {
1130
+ setSelectedTraceTaskId(taskId);
1131
+ void openTrace(taskId);
1132
+ },
1133
+ onOpenTraceChange: openTraceChange,
1134
+ onOpenTraceMacro: openTraceMacro,
1135
+ operations: renderedOperations,
1136
+ selectedTraceTaskId,
1137
+ trace,
1138
+ traceLoadingTaskId,
1139
+ traceTasks,
1140
+ })}
1141
+ capabilityNavigation={capabilityNavigation}
1142
+ boardTotals={boardTotals}
1143
+ boardKindTotals={boardKindTotals}
1144
+ boardExportSource={boardExportSource}
1145
+ detailExportSource={detailExportSource}
1146
+ boardStates={boardStates}
1147
+ data={renderedOperations}
1148
+ datePreset={operationsPeriod.preset}
1149
+ dateRange={operationsPeriod.range}
1150
+ entityPage={entityPage}
1151
+ dashboardTitle={dashboardIdentity.title}
1152
+ repositoryName={dashboardIdentity.repositoryName}
1153
+ loading={operationsLoading}
625
1154
  locale={costLocale}
626
- onClose={() => setTrace(null)}
627
- onOpenChange={openTraceChange}
628
- onOpenPrd={(prdId) => openTraceMacro("PRD", prdId)}
629
- onOpenSpec={(specId) => openTraceMacro("SPEC", specId)}
630
- trace={trace}
631
- />
632
- ) : null}
633
- {traceLink?.kind === "change" ? (
634
- <EntityDetailsDialog
635
- details={{ kind: "change", row: traceLink.row }}
636
- locale={costLocale}
637
- onClose={() => setTraceLink(null)}
638
- />
639
- ) : null}
640
- {traceLink?.kind === "macro" ? (
641
- <MacroPlanningDialog
642
- label={traceLink.label}
643
- locale={costLocale}
644
- onClose={() => setTraceLink(null)}
1155
+ currency={currency}
1156
+ factualCostTotal={factualCostTotal}
1157
+ factualCostPeriods={factualCostPeriods}
1158
+ onLoadingChange={setOperationsSurfaceLoading}
1159
+ resolveRunCost={
1160
+ costs === null ? undefined : (runId) => currentCostByRun.get(runId) ?? null
1161
+ }
1162
+ onLocaleChange={changeDashboardLocale}
1163
+ onDetailDataRequest={(view) => {
1164
+ const kind =
1165
+ view === "conversations"
1166
+ ? "conversations"
1167
+ : view === "executions"
1168
+ ? "executions"
1169
+ : view === "runs"
1170
+ ? "runs"
1171
+ : null;
1172
+ if (kind !== null) void loadOperationsEntityPage(kind);
1173
+ else if (view === "changes" || view === "decisions")
1174
+ void loadOperationsLifecyclePage(view);
1175
+ else void loadOperationsDetails();
1176
+ }}
1177
+ onEntityPageChange={(offset) => {
1178
+ if (entityPageKind === "changes" || entityPageKind === "decisions")
1179
+ void loadOperationsLifecyclePage(entityPageKind, offset);
1180
+ else void loadOperationsEntityPage(entityPageKind, offset);
1181
+ }}
1182
+ onEntityFiltersChange={loadFilteredOperationsPage}
1183
+ onBoardFiltersChange={loadFilteredBoard}
1184
+ onWorkListFiltersChange={loadFilteredWorkList}
1185
+ onBoardTypeChange={(kind) =>
1186
+ loadFilteredBoard({
1187
+ ...boardFiltersRef.current,
1188
+ kind: kind === "all" ? "" : kind,
1189
+ })
1190
+ }
1191
+ onWorkListTypeChange={(kind) =>
1192
+ void loadFilteredWorkList({
1193
+ ...workListFiltersRef.current,
1194
+ kind: kind === "all" ? "" : kind,
1195
+ })
1196
+ }
1197
+ onLoadMoreBoard={(stage) => void loadMoreBoard(stage)}
1198
+ statusPage={(stage, kind) => overviewStatusPages[overviewStatusPageKey(stage, kind)]}
1199
+ onLoadStatusPage={(stage, kind, offset) =>
1200
+ void loadOverviewStatusPage(stage, kind, offset)
1201
+ }
1202
+ classificationPage={(kind, classification) =>
1203
+ overviewClassificationPages[overviewClassificationPageKey(kind, classification)]
1204
+ }
1205
+ onLoadClassificationPage={(kind, classification, offset) =>
1206
+ void loadOverviewClassificationPage(kind, classification, offset)
1207
+ }
645
1208
  onOpenDocument={(kind, id) => void openDocument(kind, id)}
646
- onOpenTrace={(taskId) => {
647
- setTraceLink(null);
648
- void openTrace(taskId);
1209
+ onOpenTask={openTrace}
1210
+ onPriorityChange={async (itemIds) => {
1211
+ try {
1212
+ await api.saveWorkPriority(itemIds);
1213
+ } catch (cause) {
1214
+ setError(cause instanceof Error ? cause.message : String(cause));
1215
+ throw cause;
1216
+ }
649
1217
  }}
650
- row={traceLink.row}
651
- runs={operations.runs}
652
- specs={operations.specs}
653
- tasks={operations.tasks}
1218
+ onDateChange={(preset, range) => {
1219
+ operationsDetailLoadedRef.current = false;
1220
+ operationsDetailCancelRef.current?.();
1221
+ operationsDetailCancelRef.current = null;
1222
+ operationsDetailAbortRef.current?.abort();
1223
+ operationsDetailAbortRef.current = null;
1224
+ operationsPageAbortRef.current?.abort();
1225
+ operationsPageAbortRef.current = null;
1226
+ ++operationsPageRequestVersionRef.current;
1227
+ boardRequestVersionRef.current += 1;
1228
+ boardInFlightRef.current.clear();
1229
+ boardInitialPageRef.current.clear();
1230
+ boardLoadedRef.current = {};
1231
+ setBoardTotals({});
1232
+ setBoardKindTotals({});
1233
+ setBoardStates({});
1234
+ setOverviewStatusPages({});
1235
+ setOverviewClassificationPages({});
1236
+ ++refreshVersion.current;
1237
+ setEntityPage({ offset: 0, total: null, loading: false, error: null });
1238
+ costsLoadedRef.current = false;
1239
+ setCosts(null);
1240
+ setDisplayedCosts(null);
1241
+ // Keep OperationsPage mounted so its controlled-looking local date
1242
+ // select retains the user's choice; loading gates its old content.
1243
+ setOperationsLoading(true);
1244
+ periodTransitionVersionRef.current += 1;
1245
+ setPeriodTransitionPending(true);
1246
+ setLoadingArea("operations");
1247
+ setOperationsPeriod({ preset, range });
1248
+ }}
1249
+ onRefresh={() => setRefreshDialog("choose")}
1250
+ settings={
1251
+ <DashboardGlobalSettings
1252
+ costCalculationGuidePath={costCalculationGuidePath}
1253
+ currency={currency}
1254
+ locale={costLocale}
1255
+ onCurrencyChange={setCurrency}
1256
+ onHelpOpen={() => setHelpOpen(true)}
1257
+ onNavigate={navigate}
1258
+ pricingSetupPath={pricingSetupPath}
1259
+ projectSetupPath={projectSetupPath}
1260
+ />
1261
+ }
654
1262
  />
655
- ) : null}
656
- {documentDetail ? (
657
- <MacroDocumentDialog
658
- detail={documentDetail.detail}
659
- label={documentDetail.label}
660
- locale={costLocale}
661
- onClose={() => setDocumentDetail(null)}
1263
+ <AppOverlays
1264
+ closeDocument={closeDocument}
1265
+ costLocale={costLocale}
1266
+ documentDetail={documentDetail}
1267
+ error={error}
1268
+ failedArea={failedArea}
1269
+ helpOpen={helpOpen}
1270
+ loadingArea={dashboardView === "cost-monitor" && costs === null ? null : loadingArea}
1271
+ operations={renderedOperations}
1272
+ reconciliationResult={reconciliationResult}
1273
+ refreshDialog={refreshDialog}
1274
+ showTrace={dashboardView === "traceability"}
1275
+ trace={trace}
1276
+ traceLink={traceLink}
1277
+ traceLoadingTaskId={traceLoadingTaskId}
1278
+ onCloseHelp={() => setHelpOpen(false)}
1279
+ onCloseTrace={() => setTrace(null)}
1280
+ onDismissError={() => setError(null)}
1281
+ onDismissReconciliation={() => setReconciliationResult(null)}
1282
+ onOpenDocument={(kind, id) => void openDocument(kind, id)}
1283
+ onOpenTrace={(taskId) => void openTrace(taskId)}
1284
+ onOpenTraceChange={openTraceChange}
1285
+ onOpenTraceMacro={openTraceMacro}
1286
+ onRefreshCancel={() => setRefreshDialog(null)}
1287
+ onRefreshFromDialog={(reconcileOpenWork) => void refreshFromDialog(reconcileOpenWork)}
1288
+ onRetry={retryLastLoad}
1289
+ setTraceLink={(value) => setTraceLink(value)}
662
1290
  />
663
- ) : null}
664
- {helpOpen ? (
665
- <Suspense fallback={null}>
666
- <DashboardHelpDialog locale={costLocale} onClose={() => setHelpOpen(false)} />
667
- </Suspense>
668
- ) : null}
669
- {refreshDialog === "choose" ? (
670
- <div className="app-refresh-dialog-backdrop">
671
- <section
672
- aria-label="Atualizar dashboard"
673
- aria-modal="true"
674
- className="app-refresh-dialog"
675
- role="dialog"
676
- >
677
- <h2>{costLocale === "pt-BR" ? "Atualizar dashboard" : "Refresh dashboard"}</h2>
678
- <p>
679
- {costLocale === "pt-BR"
680
- ? "Escolha uma atualização rápida ou reconcilie os itens que ainda estão em aberto."
681
- : "Choose a quick refresh or reconcile work items that are still open."}
682
- </p>
683
- <div>
684
- <button onClick={() => void refreshFromDialog(false)} type="button">
685
- {costLocale === "pt-BR" ? "Apenas atualizar dados" : "Refresh data only"}
686
- </button>
687
- <button onClick={() => void refreshFromDialog(true)} type="button">
688
- {costLocale === "pt-BR" ? "Reconciliar itens abertos" : "Reconcile open work"}
689
- </button>
690
- </div>
691
- <button
692
- className="app-refresh-cancel"
693
- onClick={() => setRefreshDialog(null)}
694
- type="button"
695
- >
696
- {costLocale === "pt-BR" ? "Cancelar" : "Cancel"}
697
- </button>
698
- </section>
699
- </div>
700
- ) : null}
701
- {refreshDialog === "refreshing" || refreshDialog === "reconciling" ? (
702
- <section aria-live="polite" className="app-refresh-status" role="status">
703
- <i />
704
- <div>
705
- <strong>
706
- {refreshDialog === "reconciling"
707
- ? costLocale === "pt-BR"
708
- ? "Reconciliando itens em aberto…"
709
- : "Reconciling open work…"
710
- : costLocale === "pt-BR"
711
- ? "Atualizando dados…"
712
- : "Refreshing data…"}
713
- </strong>
714
- <span>
715
- {costLocale === "pt-BR"
716
- ? "Você pode continuar usando o dashboard."
717
- : "You can keep using the dashboard."}
718
- </span>
719
- </div>
720
- </section>
721
- ) : null}
722
- {reconciliationResult !== null && refreshDialog === null ? (
723
- <section aria-live="polite" className="app-reconciliation-result" role="status">
724
- <strong>
725
- {costLocale === "pt-BR"
726
- ? "Resultado da reconciliação manual"
727
- : "Manual reconciliation result"}
728
- </strong>
729
- <span>
730
- {costLocale === "pt-BR"
731
- ? `${reconciliationResult.reconciled_ids.length} item(ns) reconciliado(s).`
732
- : `${reconciliationResult.reconciled_ids.length} item(s) reconciled.`}
733
- </span>
734
- {reconciliationResult.reconciled_ids.length > 0 ? (
735
- <p>{reconciliationResult.reconciled_ids.join(", ")}</p>
736
- ) : null}
737
- {reconciliationResult.blocked.length > 0 ? (
738
- <p>
739
- {costLocale === "pt-BR" ? "Bloqueados: " : "Blocked: "}
740
- {reconciliationResult.blocked
741
- .map((item) => `${item.id}${item.reason === null ? "" : ` (${item.reason})`}`)
742
- .join("; ")}
743
- </p>
744
- ) : null}
745
- {reconciliationResult.not_reconciled.length > 0 ? (
746
- <p>
747
- {costLocale === "pt-BR" ? "Não reconciliados: " : "Not reconciled: "}
748
- {reconciliationResult.not_reconciled
749
- .map((item) => `${item.id}${item.reason === null ? "" : ` (${item.reason})`}`)
750
- .join("; ")}
751
- </p>
752
- ) : null}
753
- <button onClick={() => setReconciliationResult(null)} type="button">
754
- {costLocale === "pt-BR" ? "Fechar" : "Dismiss"}
755
- </button>
756
- </section>
757
- ) : null}
758
- {loadingArea !== null &&
759
- loadingArea !== "operations" &&
760
- refreshDialog === null &&
761
- !(dashboardView === "cost-monitor" && costs === null) ? (
762
- <DashboardLoadingNotice area={loadingArea} locale={costLocale} />
763
- ) : null}
764
- </>
1291
+ </PeriodTransitionShell>
1292
+ {globalLoadingPending ? <PeriodTransitionOverlay /> : null}
1293
+ </AppLoadingBoundary>
765
1294
  );
766
1295
  }