@zaip-ai/cli 1.0.7 → 1.0.8

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