@zaip-ai/cli 1.0.8 → 1.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (212) hide show
  1. package/README.md +1 -1
  2. package/dist/services/adapters.d.ts +1 -1
  3. package/dist/services/adapters.js +1 -1
  4. package/dist/services/agent-adapters.js +2 -2
  5. package/dist/services/agent-adapters.js.map +1 -1
  6. package/dist/services/agent-adapters.test.js +5 -0
  7. package/dist/services/agent-adapters.test.js.map +1 -1
  8. package/dist/services/logger.test.js +2 -2
  9. package/dist/services/runtime-template.test.js +6 -6
  10. package/dist/services/template-manifest.d.ts.map +1 -1
  11. package/dist/services/template-manifest.js +5 -0
  12. package/dist/services/template-manifest.js.map +1 -1
  13. package/package.json +1 -1
  14. package/templates/.agents/architecture/software-architecture.md +131 -0
  15. package/templates/.agents/branding/brandbook.docx +0 -0
  16. package/templates/.agents/branding/brandbook.md +60 -0
  17. package/templates/.agents/config/clients/codex/agents/zaip-agent-finalizer.toml +1 -1
  18. package/templates/.agents/config/clients/codex/agents/zaip-agent-orchestrator.toml +1 -1
  19. package/templates/.agents/dashboard/.tmp-run-list.json +254 -0
  20. package/templates/.agents/dashboard/.zaip-managed.json +252 -136
  21. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-Z60df2r-.js → CostCalculationGuidePage-DxR_pgSV.js} +1 -1
  22. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-4jZLTWE_.js +2156 -0
  23. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Coc6pA-1.js → PricingSettingsDialog-CKcsqb-r.js} +1 -1
  24. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-BomxqCBU.css +1 -0
  25. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-DmmJD2Th.js +3 -0
  26. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BwtitWa_.js +1 -0
  27. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-N5Csr0RP.js +68 -0
  28. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-DFBuc_qp.js → icons-vendor-C0fj2uU9.js} +1 -1
  29. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-C4aIB1PV.js +2 -0
  30. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-CCQOHdVI.js → react-vendor-BEsJ-FYH.js} +1 -1
  31. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-pC2_0J8x.js → traceability-B6y6Gw12.js} +1 -1
  32. package/templates/.agents/dashboard/apps/frontend/dist/index.html +5 -5
  33. package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
  34. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +204 -32
  35. package/templates/.agents/dashboard/apps/frontend/src/DashboardAlternateContent.tsx +11 -11
  36. package/templates/.agents/dashboard/apps/frontend/src/ProjectSettingsPage.tsx +14 -7
  37. package/templates/.agents/dashboard/apps/frontend/src/ProjectStackEditor.tsx +4 -2
  38. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.test.ts +3 -1
  39. package/templates/.agents/dashboard/apps/frontend/src/TraceabilityWorkspace.tsx +14 -11
  40. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +19 -4
  41. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +24 -2
  42. package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +22 -1
  43. package/templates/.agents/dashboard/apps/frontend/src/overview-status-page.test.ts +3 -1
  44. package/templates/.agents/dashboard/apps/frontend/src/project-settings-page.css +36 -26
  45. package/templates/.agents/dashboard/apps/frontend/src/project-settings.test.ts +2 -2
  46. package/templates/.agents/dashboard/apps/frontend/src/snapshot-banner-semantics.test.ts +40 -6
  47. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +39 -1
  48. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.test.ts +89 -23
  49. package/templates/.agents/dashboard/apps/frontend/src/use-app-cost-loaders.ts +219 -29
  50. package/templates/.agents/dashboard/apps/frontend/src/use-app-effects.ts +61 -7
  51. package/templates/.agents/dashboard/apps/frontend/src/use-app-operations-pages.ts +3 -6
  52. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.test.ts +2 -2
  53. package/templates/.agents/dashboard/apps/frontend/src/work-list-pagination.ts +3 -4
  54. package/templates/.agents/dashboard/apps/frontend/vite.config.ts +4 -1
  55. package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
  56. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.test.ts +89 -19
  57. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CompactLifecycleCostTable.tsx +31 -16
  58. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.test.ts +43 -14
  59. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostCharts.tsx +5 -5
  60. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostModelsAuditTable.tsx +1 -1
  61. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorContent.tsx +67 -42
  62. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +115 -39
  63. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +188 -38
  64. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.test.ts +75 -17
  65. package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostTables.tsx +147 -58
  66. package/templates/.agents/dashboard/modules/cost-monitor/src/react/cost-monitor.css +5 -2
  67. package/templates/.agents/dashboard/modules/cost-monitor/src/react/index.ts +2 -2
  68. package/templates/.agents/dashboard/modules/cost-monitor/src/react/period-cost-groups.ts +2 -1
  69. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-compact-cost-page.ts +18 -7
  70. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-group-page.ts +95 -9
  71. package/templates/.agents/dashboard/modules/cost-monitor/src/react/use-cost-run-page.ts +29 -17
  72. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-monitor-projection.test.ts +0 -24
  73. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.test.ts +225 -424
  74. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-projection.ts +24 -15
  75. package/templates/.agents/dashboard/modules/cost-monitor/src/server/build-cost-summary-terminal-day.test.ts +6 -1
  76. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-monitor-projection-agents.test.ts +31 -0
  77. package/templates/.agents/dashboard/modules/cost-monitor/src/server/source-size-cost-server/build-cost-summary-projection-duration.test.ts +414 -0
  78. package/templates/.agents/dashboard/modules/operations/package.json +1 -1
  79. package/templates/.agents/dashboard/modules/operations/src/contracts.ts +26 -23
  80. package/templates/.agents/dashboard/modules/operations/src/react/EntityTables.tsx +31 -11
  81. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +3 -1
  82. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +51 -21
  83. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +47 -5
  84. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPageNavigation.tsx +1 -4
  85. package/templates/.agents/dashboard/modules/operations/src/react/PeriodSummaryTable.tsx +32 -10
  86. package/templates/.agents/dashboard/modules/operations/src/react/TaskDetailsDialog.tsx +9 -3
  87. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +13 -5
  88. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.test.ts +12 -4
  89. package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkList.tsx +1 -4
  90. package/templates/.agents/dashboard/modules/operations/src/react/conversation-rows.ts +1 -2
  91. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.test.ts +385 -4
  92. package/templates/.agents/dashboard/modules/operations/src/react/export/ExcelExportAction.tsx +481 -61
  93. package/templates/.agents/dashboard/modules/operations/src/react/export/export-layout.test.ts +14 -1
  94. package/templates/.agents/dashboard/modules/operations/src/react/export/xlsx-runtime-e2e.test.ts +248 -0
  95. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.test.ts +4 -4
  96. package/templates/.agents/dashboard/modules/operations/src/react/operation-duration.ts +6 -4
  97. package/templates/.agents/dashboard/modules/operations/src/react/operations-global-filters.test.ts +4 -1
  98. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +1 -1
  99. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +1 -1
  100. package/templates/.agents/dashboard/modules/operations/src/react/operations-page-contract.ts +46 -6
  101. package/templates/.agents/dashboard/modules/operations/src/react/operations-view-order.test.ts +1 -4
  102. package/templates/.agents/dashboard/modules/operations/src/react/period-summary.test.ts +82 -6
  103. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.test.ts +2 -1
  104. package/templates/.agents/dashboard/modules/operations/src/react/work-board-model.ts +1 -1
  105. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.test.ts +19 -7
  106. package/templates/.agents/dashboard/modules/operations/src/server/execution-relationship-counts.ts +4 -1
  107. package/templates/.agents/dashboard/modules/operations/src/server/quick-daily-operations-projection.ts +11 -5
  108. package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
  109. package/templates/.agents/dashboard/packages/branding/package.json +1 -1
  110. package/templates/.agents/dashboard/packages/branding/src/index.ts +1 -1
  111. package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
  112. package/templates/.agents/dashboard/packages/contracts/src/index.ts +1 -0
  113. package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
  114. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +152 -149
  115. package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs.metafile.json +7333 -0
  116. package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +149 -146
  117. package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
  118. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-dashboard.mjs +15 -7
  119. package/templates/.agents/dashboard/packages/local-runtime/scripts/build-sqlite-runtime.mjs +44 -13
  120. package/templates/.agents/dashboard/packages/local-runtime/scripts/rebuild-sqlite-read-model.mjs +5 -2
  121. package/templates/.agents/dashboard/packages/local-runtime/scripts/source-size-runtime-scripts/sqlite-read-model-fixture.mjs +397 -0
  122. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-read-model.mjs +472 -145
  123. package/templates/.agents/dashboard/packages/local-runtime/scripts/test-unix-launchers.mjs +15 -3
  124. package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-sqlite-read-model.mjs +129 -31
  125. package/templates/.agents/dashboard/packages/local-runtime/server.mjs +153 -150
  126. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.test.ts +6 -2
  127. package/templates/.agents/dashboard/packages/local-runtime/src/project-settings.ts +3 -3
  128. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.test.ts +55 -16
  129. package/templates/.agents/dashboard/packages/local-runtime/src/server-board.ts +23 -13
  130. package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot.test.ts +19 -4
  131. package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +7 -3
  132. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +132 -9
  133. package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -60
  134. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-metric-contract.ts +16 -2
  135. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +1861 -350
  136. package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +1218 -352
  137. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +119 -42
  138. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +45 -20
  139. package/templates/.agents/dashboard/packages/ui/package.json +1 -1
  140. package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
  141. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +7 -5
  142. package/templates/.agents/design/.zaip-managed.json +3 -3
  143. package/templates/.agents/design/apps/catalog/package.json +1 -1
  144. package/templates/.agents/design/packages/local-runtime/package.json +1 -1
  145. package/templates/.agents/growth/estrategia-bootstrap-90-dias.md +169 -0
  146. package/templates/.agents/growth/strategy.md +27 -0
  147. package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +197 -0
  148. package/templates/.agents/marketing/strategy.md +29 -0
  149. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  150. package/templates/.agents/requirements/comparative-audit.md +1 -1
  151. package/templates/.agents/requirements/context-engine.md +1 -1
  152. package/templates/.agents/requirements/cost-observability.md +1 -1
  153. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  154. package/templates/.agents/requirements/data-export.md +1 -1
  155. package/templates/.agents/requirements/design-system.md +1 -1
  156. package/templates/.agents/requirements/execution-engine.md +1 -1
  157. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  158. package/templates/.agents/requirements/model-orchestration.md +1 -1
  159. package/templates/.agents/requirements/public-distribution.md +1 -1
  160. package/templates/.agents/requirements/software-architecture.md +1 -1
  161. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  162. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  163. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  164. package/templates/.agents/runtime/VERSION +1 -1
  165. package/templates/.agents/runtime/bin/zaip.mjs +30 -29
  166. package/templates/.agents/runtime/checksums.sha256 +6 -6
  167. package/templates/.agents/runtime/manifest.json +1 -1
  168. package/templates/.agents/runtime/pnpm-lock.sha256 +1 -1
  169. package/templates/.agents/runtime/provenance.json +5 -5
  170. package/templates/.agents/runtime/sbom.cdx.json +3 -3
  171. package/templates/.agents/sales/playbook.md +32 -0
  172. package/templates/.agents/sales/roteiro-comercial-zaip.md +252 -0
  173. package/templates/.agents/skills/zaip-aios/SKILL.md +1 -1
  174. package/templates/.agents/skills/zaip-skill-brand-governance/SKILL.md +9 -2
  175. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-framework.md +39 -0
  176. package/templates/.agents/skills/zaip-skill-brand-governance/references/brandbook-standards.md +1 -1
  177. package/templates/.agents/skills/zaip-skill-enforce-architecture/SKILL.md +2 -0
  178. package/templates/.agents/skills/zaip-skill-growth-strategy-revops/SKILL.md +6 -0
  179. package/templates/.agents/skills/zaip-skill-marketing/SKILL.md +6 -0
  180. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-local-telemetry.mjs +39 -82
  181. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.mjs +13 -8
  182. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-lib.test.mjs +44 -14
  183. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.mjs +19 -9
  184. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/reconcile-telemetry-remeasurement.test.mjs +29 -5
  185. package/templates/.agents/skills/zaip-skill-reconcile-telemetry/scripts/source-size-telemetry/model-identity-reconciliation.mjs +77 -0
  186. package/templates/.agents/skills/zaip-skill-sales-methodology/SKILL.md +6 -0
  187. package/templates/.agents/skills/zaip-skill-sqlite-read-model/scripts/ensure-node.mjs +28 -5
  188. package/templates/.agents/zaip-aios/INDEX.md +10 -0
  189. package/templates/.agents/zaip-aios/README.md +2 -1
  190. package/templates/.agents/zaip-aios/VERSION +1 -1
  191. package/templates/.agents/zaip-aios/guides/18-catalogo-de-agentes.md +76 -0
  192. package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +72 -0
  193. package/templates/.agents/zaip-aios/guides/20-instalacao-detalhada.md +330 -0
  194. package/templates/.agents/zaip-aios/guides/21-telemetria-monitor-custos.md +52 -0
  195. package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +69 -0
  196. package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +483 -0
  197. package/templates/.agents/zaip-aios/guides/24-calculo-de-custos-openai.md +85 -0
  198. package/templates/.agents/zaip-aios/guides/25-fontes-de-precos-openai.md +18 -0
  199. package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +44 -0
  200. package/templates/.agents/zaip-aios/guides/27-telemetria-git-native.md +167 -0
  201. package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +144 -0
  202. package/templates/.agents/zaip-aios/guides/assets/diagrams/lifecycles-task-spec-prd.png +0 -0
  203. package/templates/.agents/zaip-aios/guides/assets/diagrams/prd-lifecycle.png +0 -0
  204. package/templates/.agents/zaip-aios/guides/assets/diagrams/spec-lifecycle.png +0 -0
  205. package/templates/.agents/zaip-aios/guides/assets/diagrams/task-lifecycle.png +0 -0
  206. package/templates/template-manifest.json +313 -177
  207. package/templates/.agents/dashboard/apps/frontend/dist/assets/DashboardHelpDialog-Cs6dzDmW.js +0 -1505
  208. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-QE1k9yZB.css +0 -1
  209. package/templates/.agents/dashboard/apps/frontend/dist/assets/ProjectSettingsPage-_Pf5DA_A.js +0 -3
  210. package/templates/.agents/dashboard/apps/frontend/dist/assets/TraceabilityWorkspace-BVlz3DLZ.js +0 -1
  211. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cf9ctPVt.js +0 -68
  212. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-8A4DcNXM.js +0 -2
@@ -8,9 +8,12 @@ export type ExcelExportInput = {
8
8
  view: string;
9
9
  filters: Readonly<Record<string, unknown>>;
10
10
  rows: readonly Record<string, unknown>[];
11
- pageSource?: PreparedExportPageSource | {
12
- prepare: () => Promise<PreparedExportPageSource>;
13
- } | undefined;
11
+ pageSource?:
12
+ | PreparedExportPageSource
13
+ | {
14
+ prepare: () => Promise<PreparedExportPageSource>;
15
+ }
16
+ | undefined;
14
17
  };
15
18
  export type PreparedExportPageSource = {
16
19
  totalRows: number;
@@ -31,60 +34,345 @@ export const PERIOD_EXPORT_COLUMNS = [
31
34
  "duration_hours",
32
35
  ] as const;
33
36
 
34
- // Board pages contain three canonical entity shapes. Keep their full common
35
- // schema even when the visible first page happens not to contain one shape.
36
- export const KANBAN_EXPORT_COLUMNS = [
37
- "kind",
37
+ // Work views contain PRDs, SPECs, and Tasks. Export the common, user-facing
38
+ // fields instead of the union of their internal lifecycle projections. The
39
+ // latter produces columns that are empty for an entire view (or nearly every
40
+ // row), which makes the workbook much harder to use.
41
+ export const WORK_EXPORT_COLUMNS = [
42
+ "type",
38
43
  "id",
39
44
  "title",
40
- "short_name",
41
- "objective",
42
- "lifecycle_status",
43
- "work_status",
44
45
  "status",
45
46
  "risk",
46
- "priority",
47
- "owner",
47
+ "occurred_at",
48
+ "duration_hours",
49
+ "tokens",
50
+ "cost_brl",
51
+ ] as const;
52
+
53
+ // Execution rows are entities, not period summaries. Keep their schema
54
+ // aligned with the Execution table so the workbook never inherits the empty
55
+ // PRD/SPEC/period columns shown by a different tab.
56
+ export const EXECUTION_EXPORT_COLUMNS = [
57
+ "id",
58
+ "kind",
59
+ "status",
60
+ "origin",
61
+ "task_id",
62
+ "conversation_id",
48
63
  "created_at",
49
- "updated_at",
64
+ "finished_at",
65
+ "duration_hours",
66
+ "tokens",
67
+ "cost_brl",
68
+ "invocations",
69
+ ] as const;
70
+
71
+ export const AGENT_INVOCATION_EXPORT_COLUMNS = [
72
+ "run_id",
73
+ "execution_id",
74
+ "conversation_id",
75
+ "task_id",
76
+ "origin",
77
+ "run_role",
78
+ "run_purpose",
79
+ "phase",
80
+ "status",
81
+ "agent",
82
+ "provider",
83
+ "model",
84
+ "reasoning_effort",
50
85
  "started_at",
51
86
  "finished_at",
52
- "parent_id",
53
- "spec_id",
54
- "change_id",
55
- "manual_order",
56
- "attempt_count",
57
- "progress",
58
- "is_active",
59
- "artifact_path",
60
- "requirement_ids",
61
- "acceptance_ids",
62
- "depends_on",
63
- "write_scopes",
64
- "task_ids",
65
- "execution_ids",
66
- "run_ids",
67
87
  "duration_hours",
68
- "duration_provenance",
88
+ "input_tokens",
89
+ "cached_input_tokens",
90
+ "output_tokens",
91
+ "reasoning_tokens",
69
92
  "total_tokens",
93
+ "estimated_usd",
94
+ "estimated_brl",
95
+ ] as const;
96
+
97
+ export const COMPACT_LIFECYCLE_EXPORT_COLUMNS = [
98
+ "id",
99
+ "title",
100
+ "status",
101
+ "occurred_at",
102
+ "invocations",
103
+ "duration_hours",
104
+ "tokens",
105
+ "cost_brl",
106
+ ] as const;
107
+
108
+ export const COST_GROUP_EXPORT_COLUMNS = [
109
+ "key",
110
+ "label",
111
+ "invocations",
112
+ "tasks",
113
+ "tokens",
114
+ "cost_usd",
115
+ "cost_brl",
116
+ "duration_hours",
117
+ ] as const;
118
+
119
+ export const CONVERSATION_EXPORT_COLUMNS = [
120
+ "conversation_id",
121
+ "status",
122
+ "risk",
123
+ "started_at",
124
+ "finished_at",
125
+ "model",
126
+ "reasoning_effort",
127
+ "invocations",
128
+ "duration_hours",
129
+ "tokens",
70
130
  "cost_brl",
71
131
  ] as const;
72
132
 
133
+ export const CHANGE_EXPORT_COLUMNS = [
134
+ "id",
135
+ "title",
136
+ "status",
137
+ "risk",
138
+ "source_type",
139
+ "source_id",
140
+ "requirement_ids_json",
141
+ "acceptance_ids_json",
142
+ "task_ids_json",
143
+ "occurred_at",
144
+ ] as const;
145
+
146
+ export const DECISION_EXPORT_COLUMNS = [
147
+ "id",
148
+ "title",
149
+ "origin",
150
+ "scope_type",
151
+ "scope_id",
152
+ "occurred_at",
153
+ ] as const;
154
+
155
+ /** @deprecated Use WORK_EXPORT_COLUMNS for Kanban and Work List exports. */
156
+ export const KANBAN_EXPORT_COLUMNS = WORK_EXPORT_COLUMNS;
157
+
73
158
  export async function* exportPageSourceRows(
74
159
  pageSource: PreparedExportPageSource,
160
+ mapRows: (
161
+ rows: readonly Record<string, unknown>[],
162
+ ) => Record<string, unknown>[] = exportRowsWithDurationHours,
75
163
  ): AsyncGenerator<Record<string, unknown>[]> {
76
164
  const seen = new Set<string>();
77
165
  for await (const page of pageSource.pages()) {
78
166
  const unique = page.filter((row) => {
79
- const id = typeof row.id === "string" ? row.id : JSON.stringify(row);
167
+ const id =
168
+ typeof row.id === "string"
169
+ ? row.id
170
+ : typeof row.run_id === "string"
171
+ ? row.run_id
172
+ : typeof row.agent_invocation_id === "string"
173
+ ? row.agent_invocation_id
174
+ : typeof row.key === "string"
175
+ ? row.key
176
+ : JSON.stringify(row);
80
177
  if (seen.has(id)) return false;
81
178
  seen.add(id);
82
179
  return true;
83
180
  });
84
- if (unique.length > 0) yield exportRowsWithDurationHours(unique);
181
+ if (unique.length > 0) yield mapRows(unique);
85
182
  }
86
183
  }
87
184
 
185
+ export function exportWorkRows(
186
+ rows: readonly Record<string, unknown>[],
187
+ ): Record<string, unknown>[] {
188
+ return rows.map((row) => {
189
+ const telemetry = asRecord(row.telemetry);
190
+ const usage = asRecord(row.usage);
191
+ const totalUsage = asRecord(usage?.total);
192
+ const tokenUsage = asRecord(totalUsage?.tokens);
193
+ const costUsage = asRecord(totalUsage?.cost);
194
+ const metrics = asRecord(row.metrics);
195
+ const metricDuration = asRecord(metrics?.duration);
196
+ const metricTokens = asRecord(metrics?.total_tokens);
197
+ const metricCost = asRecord(metrics?.cost);
198
+ const durationMs =
199
+ numberValue(row.duration_ms) ??
200
+ numberValue(telemetry?.duration_ms) ??
201
+ numberValue(row.total_duration) ??
202
+ numberValue(metricDuration?.known_sum);
203
+ return {
204
+ type: row.kind ?? row.entity_kind,
205
+ id: row.id,
206
+ title: row.title,
207
+ // work_status is the Kanban column for macros; Task status is already
208
+ // the canonical work stage for a Task.
209
+ status: row.work_status ?? row.status ?? row.lifecycle_status,
210
+ risk: row.risk ?? null,
211
+ occurred_at: row.occurred_at ?? row.created_at ?? row.updated_at ?? null,
212
+ duration_hours: durationMs === null ? null : durationMs / 3_600_000,
213
+ tokens:
214
+ numberValue(row.total_tokens) ??
215
+ numberValue(telemetry?.tokens) ??
216
+ numberValue(tokenUsage?.total) ??
217
+ numberValue(metricTokens?.known_sum),
218
+ cost_brl:
219
+ numberValue(row.cost_brl) ??
220
+ numberValue(row.total_cost_brl) ??
221
+ numberValue(telemetry?.cost_brl) ??
222
+ numberValue(costUsage?.brl) ??
223
+ numberValue(metricCost?.known_sum),
224
+ };
225
+ });
226
+ }
227
+
228
+ /** Normalize both the Operations projection and the paginated SQLite view. */
229
+ export function exportExecutionRows(
230
+ rows: readonly Record<string, unknown>[],
231
+ ): Record<string, unknown>[] {
232
+ return rows.map((row) => {
233
+ const origin = asRecord(row.origin);
234
+ const originType =
235
+ typeof origin?.type === "string"
236
+ ? origin.type
237
+ : typeof row.origin_type === "string"
238
+ ? row.origin_type
239
+ : null;
240
+ const durationMs = executionDurationMs(row);
241
+ return {
242
+ id: row.id ?? row.execution_id ?? null,
243
+ kind: row.kind ?? row.execution_kind ?? null,
244
+ status: row.status ?? null,
245
+ origin: originType,
246
+ task_id: row.task_id ?? (originType === "task" ? (origin?.task_id ?? null) : null),
247
+ conversation_id:
248
+ row.conversation_id ??
249
+ (originType === "interaction" ? (origin?.conversation_id ?? null) : null),
250
+ created_at: row.created_at ?? row.started_at ?? null,
251
+ finished_at: row.finished_at ?? null,
252
+ duration_hours: durationMs === null ? null : durationMs / 3_600_000,
253
+ tokens: numberValue(row.total_tokens),
254
+ cost_brl: numberValue(row.cost_brl) ?? numberValue(row.total_cost_brl),
255
+ invocations:
256
+ numberValue(row.orchestrator_run_count) ?? numberValue(row.invocation_count) ?? null,
257
+ };
258
+ });
259
+ }
260
+
261
+ export function exportAgentInvocationRows(
262
+ rows: readonly Record<string, unknown>[],
263
+ ): Record<string, unknown>[] {
264
+ return rows.map((row) => {
265
+ const durationMs = executionDurationMs(row);
266
+ return {
267
+ run_id: row.run_id ?? row.agent_invocation_id ?? row.id ?? null,
268
+ execution_id: row.execution_id ?? null,
269
+ conversation_id: row.conversation_id ?? null,
270
+ task_id: row.task_id ?? null,
271
+ origin: row.origin ?? row.invocation_kind ?? null,
272
+ run_role: row.run_role ?? row.zaip_agent_role ?? null,
273
+ run_purpose: row.run_purpose ?? row.purpose ?? null,
274
+ phase: row.phase ?? null,
275
+ status: row.status ?? null,
276
+ agent: row.agent ?? row.zaip_agent ?? null,
277
+ provider: row.provider ?? null,
278
+ model: row.model ?? null,
279
+ reasoning_effort: row.reasoning_effort ?? null,
280
+ started_at: row.started_at ?? row.source_started_at ?? row.occurred_at ?? null,
281
+ finished_at: row.finished_at ?? row.source_finished_at ?? null,
282
+ duration_hours: durationMs === null ? null : durationMs / 3_600_000,
283
+ input_tokens: numberValue(row.input_tokens),
284
+ cached_input_tokens: numberValue(row.cached_input_tokens),
285
+ output_tokens: numberValue(row.output_tokens),
286
+ reasoning_tokens: numberValue(row.reasoning_tokens),
287
+ total_tokens: numberValue(row.total_tokens),
288
+ estimated_usd: numberValue(row.estimated_usd) ?? numberValue(row.cost_usd),
289
+ estimated_brl: numberValue(row.estimated_brl) ?? numberValue(row.cost_brl),
290
+ };
291
+ });
292
+ }
293
+
294
+ export function exportCompactLifecycleRows(
295
+ rows: readonly Record<string, unknown>[],
296
+ ): Record<string, unknown>[] {
297
+ return rows.map((row) => {
298
+ const metrics = asRecord(row.metrics);
299
+ const duration = asRecord(metrics?.duration);
300
+ const tokens = asRecord(metrics?.total_tokens);
301
+ const cost = asRecord(metrics?.cost);
302
+ const durationMs = numberValue(duration?.known_sum) ?? numberValue(row.total_duration);
303
+ return {
304
+ id: row.id ?? null,
305
+ title: row.title ?? null,
306
+ status: row.status ?? null,
307
+ occurred_at: row.occurred_at ?? null,
308
+ invocations: numberValue(row.run_count) ?? numberValue(row.invocation_count),
309
+ duration_hours: durationMs === null ? null : durationMs / 3_600_000,
310
+ tokens: numberValue(tokens?.known_sum) ?? numberValue(row.total_tokens),
311
+ cost_brl: numberValue(cost?.known_sum) ?? numberValue(row.total_cost_brl),
312
+ };
313
+ });
314
+ }
315
+
316
+ export function exportCostGroupRows(
317
+ rows: readonly Record<string, unknown>[],
318
+ ): Record<string, unknown>[] {
319
+ return rows.map((row) => {
320
+ const durationMs = numberValue(row.duration_ms) ?? numberValue(row.total_duration);
321
+ return {
322
+ key: row.key ?? null,
323
+ label: row.label ?? null,
324
+ invocations: numberValue(row.run_count) ?? numberValue(row.invocation_count),
325
+ tasks: numberValue(row.task_count),
326
+ tokens: numberValue(row.total_tokens),
327
+ cost_usd: numberValue(row.estimated_usd) ?? numberValue(row.total_cost_usd),
328
+ cost_brl: numberValue(row.estimated_brl) ?? numberValue(row.total_cost_brl),
329
+ duration_hours: durationMs === null ? null : durationMs / 3_600_000,
330
+ };
331
+ });
332
+ }
333
+
334
+ export function exportConversationRows(
335
+ rows: readonly Record<string, unknown>[],
336
+ ): Record<string, unknown>[] {
337
+ return rows.map((row) => ({
338
+ conversation_id: row.conversation_id ?? row.id ?? null,
339
+ status: row.status ?? null,
340
+ risk: row.risk ?? null,
341
+ started_at: row.started_at ?? null,
342
+ finished_at: row.finished_at ?? null,
343
+ model: row.model ?? null,
344
+ reasoning_effort: row.reasoning_effort ?? null,
345
+ invocations: numberValue(row.invocation_count),
346
+ duration_hours:
347
+ (numberValue(row.total_duration) ?? numberValue(row.duration_ms)) === null
348
+ ? null
349
+ : Number(numberValue(row.total_duration) ?? numberValue(row.duration_ms)) / 3_600_000,
350
+ tokens: numberValue(row.total_tokens),
351
+ cost_brl: numberValue(row.total_cost_brl) ?? numberValue(row.cost_brl),
352
+ }));
353
+ }
354
+
355
+ function exportSelectedRows(
356
+ rows: readonly Record<string, unknown>[],
357
+ keys: readonly string[],
358
+ ): Record<string, unknown>[] {
359
+ return rows.map((row) => Object.fromEntries(keys.map((key) => [key, row[key] ?? null])));
360
+ }
361
+
362
+ function executionDurationMs(row: Record<string, unknown>): number | null {
363
+ return numberValue(row.duration_ms) ?? numberValue(row.source_duration_ms);
364
+ }
365
+
366
+ function asRecord(value: unknown): Record<string, unknown> | null {
367
+ return value !== null && typeof value === "object" && !Array.isArray(value)
368
+ ? (value as Record<string, unknown>)
369
+ : null;
370
+ }
371
+
372
+ function numberValue(value: unknown): number | null {
373
+ return typeof value === "number" ? value : null;
374
+ }
375
+
88
376
  export function exportRowsWithDurationHours(
89
377
  rows: readonly Record<string, unknown>[],
90
378
  ): Record<string, unknown>[] {
@@ -112,21 +400,58 @@ function orderedExportEntries(row: Record<string, unknown>): [string, unknown][]
112
400
  }
113
401
 
114
402
  function normalizePeriodRow(row: Record<string, unknown>): Record<string, unknown> {
115
- if (Object.keys(row).some((key) => key !== "key" && PERIOD_EXPORT_COLUMNS.includes(key as never)))
403
+ const hasPeriodSourceAliases =
404
+ ("key" in row || "period_key" in row) &&
405
+ ("prd_count" in row ||
406
+ "spec_count" in row ||
407
+ "run_count" in row ||
408
+ "total_run_count" in row ||
409
+ "costBrl" in row ||
410
+ "total_cost_brl" in row ||
411
+ "durationMs" in row ||
412
+ "operations_total_duration" in row ||
413
+ "tasksCreated" in row);
414
+ if (!hasPeriodSourceAliases) {
415
+ if (
416
+ Object.keys(row).some((key) => key !== "key" && PERIOD_EXPORT_COLUMNS.includes(key as never))
417
+ )
418
+ return row;
116
419
  return row;
117
- if (!("prd_count" in row || "spec_count" in row || "run_count" in row)) return row;
420
+ }
118
421
  return {
119
- key: row.key,
120
- prds: row.prd_count,
121
- specs: row.spec_count,
122
- tasks: row.task_count,
123
- runs: row.run_count,
124
- tests: row.test_count,
125
- evidences: row.evidence_count,
422
+ key: row.key ?? row.period_key,
423
+ prds: row.prds ?? row.prd_count,
424
+ specs: row.specs ?? row.spec_count,
425
+ tasks: row.tasks ?? row.tasksCreated ?? row.task_count,
426
+ runs: row.runs ?? row.run_count ?? row.total_run_count ?? row.invocation_count,
427
+ tests: row.tests ?? row.test_count ?? row.validation_count,
428
+ evidences: row.evidences ?? row.evidence_count,
126
429
  duration_hours:
127
- typeof row.duration_ms === "number" ? row.duration_ms / 3_600_000 : row.duration_ms,
128
- tokens: row.total_tokens,
129
- cost: row.estimated_brl,
430
+ typeof (
431
+ row.durationMs ??
432
+ row.duration_ms ??
433
+ row.operations_total_duration ??
434
+ row.total_duration
435
+ ) === "number"
436
+ ? Number(
437
+ row.durationMs ??
438
+ row.duration_ms ??
439
+ row.operations_total_duration ??
440
+ row.total_duration,
441
+ ) / 3_600_000
442
+ : (row.durationMs ??
443
+ row.duration_ms ??
444
+ row.operations_total_duration ??
445
+ row.total_duration ??
446
+ null),
447
+ tokens: row.tokens ?? row.total_tokens,
448
+ cost:
449
+ row.cost ??
450
+ ("costBrl" in row
451
+ ? row.costBrl
452
+ : "total_cost_brl" in row
453
+ ? row.total_cost_brl
454
+ : row.estimated_brl),
130
455
  };
131
456
  }
132
457
 
@@ -137,6 +462,60 @@ function durationHoursKey(key: string): string | null {
137
462
  return key.endsWith("_duration_ms") ? `${key.slice(0, -2)}hours` : null;
138
463
  }
139
464
 
465
+ export function exportHeadersForView(
466
+ view: string,
467
+ rows: readonly Record<string, unknown>[],
468
+ ): string[] {
469
+ const headers: string[] = [];
470
+ const headerSet = new Set<string>();
471
+ const isPeriodView = /-(daily|monthly|annual|yearly)$/.test(view);
472
+ const isWorkView = /-work-(kanban|list)$/.test(view);
473
+ const isExecutionView = /-executions$/.test(view);
474
+ const isCostExecutionView = view === "cost-executions";
475
+ const isAgentInvocationView = /-(?:agentCalls|agent-calls|runs)$/.test(view);
476
+ const isOperationsConversationView = view === "operations-conversations";
477
+ const isCompactLifecycleView =
478
+ /-(?:tasks|specs|prds|conversations)$/.test(view) && !isOperationsConversationView;
479
+ const isCostGroupView = /-(?:agents|models|providers)$/.test(view);
480
+ const isChangeView = view === "operations-changes";
481
+ const isDecisionView = view === "operations-decisions";
482
+ if (isPeriodView) headers.push(...PERIOD_EXPORT_COLUMNS);
483
+ if (isWorkView) headers.push(...WORK_EXPORT_COLUMNS);
484
+ if (isExecutionView && !isCostExecutionView) headers.push(...EXECUTION_EXPORT_COLUMNS);
485
+ if (isCostExecutionView) headers.push(...COMPACT_LIFECYCLE_EXPORT_COLUMNS);
486
+ if (isAgentInvocationView) headers.push(...AGENT_INVOCATION_EXPORT_COLUMNS);
487
+ if (isCompactLifecycleView) headers.push(...COMPACT_LIFECYCLE_EXPORT_COLUMNS);
488
+ if (isCostGroupView) headers.push(...COST_GROUP_EXPORT_COLUMNS);
489
+ if (isOperationsConversationView) headers.push(...CONVERSATION_EXPORT_COLUMNS);
490
+ if (isChangeView) headers.push(...CHANGE_EXPORT_COLUMNS);
491
+ if (isDecisionView) headers.push(...DECISION_EXPORT_COLUMNS);
492
+ for (const key of headers) headerSet.add(key);
493
+ // `cost-executions` has a compact mapped schema. Raw lifecycle fields must
494
+ // not leak into the workbook after those fixed headers.
495
+ const hasExplicitSchema =
496
+ isWorkView ||
497
+ isCostExecutionView ||
498
+ isExecutionView ||
499
+ isAgentInvocationView ||
500
+ isCompactLifecycleView ||
501
+ isCostGroupView ||
502
+ isOperationsConversationView ||
503
+ isChangeView ||
504
+ isDecisionView;
505
+ if (!hasExplicitSchema) {
506
+ for (const row of rows)
507
+ for (const [key] of orderedExportEntries(row)) {
508
+ const header = durationHoursKey(key) ?? key;
509
+ if (!headerSet.has(header)) {
510
+ headerSet.add(header);
511
+ headers.push(header);
512
+ }
513
+ }
514
+ }
515
+ if (headers.length === 0) headers.push("result");
516
+ return headers;
517
+ }
518
+
140
519
  export function ExcelExportAction({ filters, pageSource, rows, view }: ExcelExportInput) {
141
520
  const controller = useRef<ExportController | null>(null);
142
521
  if (controller.current === null) controller.current = new ExportController();
@@ -154,21 +533,17 @@ export function ExcelExportAction({ filters, pageSource, rows, view }: ExcelExpo
154
533
  const timer = setTimeout(() => setShowSuccess(false), EXPORT_SUCCESS_TOAST_MS);
155
534
  return () => clearTimeout(timer);
156
535
  }, [state.status]);
157
- const headers: string[] = [];
158
- const headerSet = new Set<string>();
159
- const isPeriodView = /-(daily|monthly|annual|yearly)$/.test(view);
160
- if (isPeriodView) headers.push(...PERIOD_EXPORT_COLUMNS);
161
- if (pageSource !== undefined && /work-kanban$/.test(view)) headers.push(...KANBAN_EXPORT_COLUMNS);
162
- for (const key of headers) headerSet.add(key);
163
- for (const row of rows)
164
- for (const [key] of orderedExportEntries(row)) {
165
- const header = durationHoursKey(key) ?? key;
166
- if (!headerSet.has(header)) {
167
- headerSet.add(header);
168
- headers.push(header);
169
- }
170
- }
171
- if (headers.length === 0) headers.push("result");
536
+ const headers = exportHeadersForView(view, rows);
537
+ const isWorkView = /-work-(kanban|list)$/.test(view);
538
+ const isExecutionView = /-executions$/.test(view);
539
+ const isCostExecutionView = view === "cost-executions";
540
+ const isAgentInvocationView = /-(?:agentCalls|agent-calls|runs)$/.test(view);
541
+ const isOperationsConversationView = view === "operations-conversations";
542
+ const isCompactLifecycleView =
543
+ /-(?:tasks|specs|prds|conversations)$/.test(view) && !isOperationsConversationView;
544
+ const isCostGroupView = /-(?:agents|models|providers)$/.test(view);
545
+ const isChangeView = view === "operations-changes";
546
+ const isDecisionView = view === "operations-decisions";
172
547
  const createSnapshot = async () => {
173
548
  const preparedPageSource =
174
549
  pageSource === undefined
@@ -184,11 +559,56 @@ export function ExcelExportAction({ filters, pageSource, rows, view }: ExcelExpo
184
559
  totalRows: preparedPageSource?.totalRows ?? rows.length,
185
560
  pages: async function* () {
186
561
  if (preparedPageSource !== undefined) {
187
- yield* exportPageSourceRows(preparedPageSource);
562
+ yield* exportPageSourceRows(
563
+ preparedPageSource,
564
+ isWorkView
565
+ ? exportWorkRows
566
+ : isOperationsConversationView
567
+ ? exportConversationRows
568
+ : isChangeView
569
+ ? (sourceRows: readonly Record<string, unknown>[]) =>
570
+ exportSelectedRows(sourceRows, CHANGE_EXPORT_COLUMNS)
571
+ : isDecisionView
572
+ ? (sourceRows: readonly Record<string, unknown>[]) =>
573
+ exportSelectedRows(sourceRows, DECISION_EXPORT_COLUMNS)
574
+ : isCostExecutionView
575
+ ? exportCompactLifecycleRows
576
+ : isExecutionView
577
+ ? exportExecutionRows
578
+ : isAgentInvocationView
579
+ ? exportAgentInvocationRows
580
+ : isCompactLifecycleView
581
+ ? exportCompactLifecycleRows
582
+ : isCostGroupView
583
+ ? exportCostGroupRows
584
+ : exportRowsWithDurationHours,
585
+ );
188
586
  return;
189
587
  }
190
588
  for (let offset = 0; offset < rows.length; offset += 500)
191
- yield exportRowsWithDurationHours(rows.slice(offset, offset + 500));
589
+ yield (
590
+ isWorkView
591
+ ? exportWorkRows
592
+ : isOperationsConversationView
593
+ ? exportConversationRows
594
+ : isChangeView
595
+ ? (sourceRows: readonly Record<string, unknown>[]) =>
596
+ exportSelectedRows(sourceRows, CHANGE_EXPORT_COLUMNS)
597
+ : isDecisionView
598
+ ? (sourceRows: readonly Record<string, unknown>[]) =>
599
+ exportSelectedRows(sourceRows, DECISION_EXPORT_COLUMNS)
600
+ : isCostExecutionView
601
+ ? exportCompactLifecycleRows
602
+ : isExecutionView
603
+ ? exportExecutionRows
604
+ : isAgentInvocationView
605
+ ? exportAgentInvocationRows
606
+ : isCompactLifecycleView
607
+ ? exportCompactLifecycleRows
608
+ : isCostGroupView
609
+ ? exportCostGroupRows
610
+ : exportRowsWithDurationHours
611
+ )(rows.slice(offset, offset + 500));
192
612
  },
193
613
  });
194
614
  };
@@ -28,10 +28,23 @@ describe("Excel export layout", () => {
28
28
  });
29
29
 
30
30
  it("uses the paginated board source rather than just visible Kanban rows", () => {
31
- expect(operationsPage).toContain('pageSource={\n view === "work-kanban"');
31
+ expect(operationsPage).toContain(
32
+ 'pageSource={\n view === "work-kanban"',
33
+ );
32
34
  expect(operationsPage).toContain("boardExportSource?.({");
33
35
  });
34
36
 
37
+ it("uses the paginated SQLite source for every Operations table, not the visible page", () => {
38
+ expect(operationsPage).toContain("detailExportSource?.(view, {");
39
+ const app = readFileSync(
40
+ new URL("../../../../../apps/frontend/src/App.tsx", import.meta.url),
41
+ "utf8",
42
+ );
43
+ expect(app).toContain('executions: "operations_executions"');
44
+ expect(app).toContain("totalRows: firstPage.total_count");
45
+ expect(app).toContain("if (nextOffset >= page.total_count) return");
46
+ });
47
+
35
48
  it("keeps the Operations action outside the tab nav and fixed at the bar end", () => {
36
49
  const controls = operationsPage.slice(
37
50
  operationsPage.indexOf('<section className="dashboard-global-controls">'),