@zaip-ai/cli 1.3.4 → 1.3.6

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 (81) hide show
  1. package/dist/services/install-agents-template.d.ts.map +1 -1
  2. package/dist/services/install-agents-template.js +18 -2
  3. package/dist/services/install-agents-template.js.map +1 -1
  4. package/dist/services/template-manifest.d.ts.map +1 -1
  5. package/dist/services/template-manifest.js +2 -0
  6. package/dist/services/template-manifest.js.map +1 -1
  7. package/package.json +1 -1
  8. package/templates/.agents/architecture/.gitkeep +0 -0
  9. package/templates/.agents/branding/.gitkeep +0 -0
  10. package/templates/.agents/dashboard/.zaip-managed.json +63 -51
  11. package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-BZWjXQB-.js → CostCalculationGuidePage-DwVZOmMV.js} +1 -1
  12. package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-D8qqeIPE.js → DashboardHelpDialog-3Lrfq645.js} +1 -1
  13. package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-Cm12_A0v.js → PricingSettingsDialog-CyjMXkm8.js} +1 -1
  14. package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-DxO9F8B4.js → ProjectSettingsPage-B_Oj6Dlf.js} +1 -1
  15. package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-_x0twOcS.js → TraceabilityWorkspace-diq9g1L8.js} +1 -1
  16. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-C09KmlSw.js +68 -0
  17. package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-CR4Fbf3g.js → icons-vendor-DXBOtWNI.js} +1 -1
  18. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Deo_W81f.js +2 -0
  19. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-hPPfVGQv.css +1 -0
  20. package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-DRJQbKnp.js → react-vendor-DaNXQPyk.js} +1 -1
  21. package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-CMrDYqjR.js → traceability-I4AKqVDX.js} +1 -1
  22. package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
  23. package/templates/.agents/dashboard/apps/frontend/src/App.tsx +43 -15
  24. package/templates/.agents/dashboard/apps/frontend/src/cost-period-refresh.test.ts +18 -3
  25. package/templates/.agents/dashboard/apps/frontend/src/dashboard-capability-nav.css +11 -0
  26. package/templates/.agents/dashboard/apps/frontend/src/operations-period-contract.test.ts +14 -0
  27. package/templates/.agents/dashboard/apps/frontend/src/operations-period.test.ts +22 -7
  28. package/templates/.agents/dashboard/apps/frontend/src/operations-period.ts +11 -2
  29. package/templates/.agents/dashboard/apps/frontend/src/snapshot-coherence.test.ts +7 -4
  30. package/templates/.agents/dashboard/modules/operations/src/react/OperationsFilteredStatusDialog.tsx +131 -0
  31. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.helpers.tsx +2 -1
  32. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.test.ts +12 -4
  33. package/templates/.agents/dashboard/modules/operations/src/react/OperationsOverview.tsx +69 -157
  34. package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +19 -4
  35. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.test.ts +25 -0
  36. package/templates/.agents/dashboard/modules/operations/src/react/operation-date-range.ts +29 -0
  37. package/templates/.agents/dashboard/modules/operations/src/react/operations-coverage-toast.test.ts +3 -2
  38. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +12 -2
  39. package/templates/.agents/dashboard/modules/operations/src/react/operations-overview.status-cards.test.ts +13 -7
  40. package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +1 -1
  41. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.mjs +40 -0
  42. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-prerequisites.test.mjs +38 -0
  43. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +33 -26
  44. package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.test.mjs +5 -8
  45. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-loading.spec.ts +4 -6
  46. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-pagination.spec.ts +4 -0
  47. package/templates/.agents/dashboard/tests/e2e/specs/cost-monitor-period-transition.spec.ts +43 -17
  48. package/templates/.agents/dashboard/tests/e2e/specs/dashboard-performance-matrix.spec.ts +9 -3
  49. package/templates/.agents/dashboard/tests/e2e/specs/dashboard.spec.ts +51 -22
  50. package/templates/.agents/growth/.gitkeep +0 -0
  51. package/templates/.agents/marketing/.gitkeep +0 -0
  52. package/templates/.agents/requirements/autonomous-assurance.md +1 -1
  53. package/templates/.agents/requirements/comparative-audit.md +1 -1
  54. package/templates/.agents/requirements/context-engine.md +1 -1
  55. package/templates/.agents/requirements/cost-observability.md +1 -1
  56. package/templates/.agents/requirements/dashboard-launch.md +1 -1
  57. package/templates/.agents/requirements/data-export.md +1 -1
  58. package/templates/.agents/requirements/design-system.md +1 -1
  59. package/templates/.agents/requirements/execution-engine.md +1 -1
  60. package/templates/.agents/requirements/kanban-dashboard.md +1 -1
  61. package/templates/.agents/requirements/model-orchestration.md +1 -1
  62. package/templates/.agents/requirements/public-distribution.md +1 -1
  63. package/templates/.agents/requirements/software-architecture.md +1 -1
  64. package/templates/.agents/requirements/strategic-improvements.md +1 -1
  65. package/templates/.agents/requirements/work-lifecycle.md +1 -1
  66. package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
  67. package/templates/.agents/sales/.gitkeep +0 -0
  68. package/templates/README.md +722 -0
  69. package/templates/template-manifest.json +96 -97
  70. package/templates/.agents/architecture/software-architecture.md +0 -131
  71. package/templates/.agents/branding/brandbook.docx +0 -0
  72. package/templates/.agents/branding/brandbook.md +0 -60
  73. package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-Cpp8xFij.js +0 -68
  74. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-B31GMG5O.css +0 -1
  75. package/templates/.agents/dashboard/apps/frontend/dist/assets/index-ncC1rxap.js +0 -2
  76. package/templates/.agents/growth/estrategia-bootstrap-90-dias.md +0 -169
  77. package/templates/.agents/growth/strategy.md +0 -27
  78. package/templates/.agents/marketing/estrategia-marketing-zaip-90-dias.md +0 -197
  79. package/templates/.agents/marketing/strategy.md +0 -29
  80. package/templates/.agents/sales/playbook.md +0 -32
  81. package/templates/.agents/sales/roteiro-comercial-zaip.md +0 -252
@@ -1,6 +1,7 @@
1
1
  import { describe, expect, it } from "vitest";
2
2
  import {
3
3
  dateRangeForPreset,
4
+ factualCostDateRange,
4
5
  initialOperationsDateRange,
5
6
  initialTaskDateRange,
6
7
  isDateInOperationPreset,
@@ -34,6 +35,30 @@ describe("operation date ranges", () => {
34
35
  });
35
36
  });
36
37
 
38
+ it("uses the first factual Cost Monitor day for All Time while ending today", () => {
39
+ expect(
40
+ factualCostDateRange(
41
+ [
42
+ { key: "2026-06-01", estimated_brl: null, estimated_usd: null },
43
+ { key: "2026-06-02", estimated_brl: 0, estimated_usd: 0 },
44
+ { key: "2026-06-03", estimated_brl: 1.25, estimated_usd: 0.24 },
45
+ ],
46
+ { start: "2026-07-10", end: "2026-07-14" },
47
+ today,
48
+ ),
49
+ ).toEqual({ start: "2026-06-02", end: "2026-07-14" });
50
+ });
51
+
52
+ it("keeps the Operations range when no factual Cost Monitor day is available", () => {
53
+ expect(
54
+ factualCostDateRange(
55
+ [{ key: "2026-06-01", estimated_brl: null, estimated_usd: null }],
56
+ { start: "2026-07-10", end: "2026-07-14" },
57
+ today,
58
+ ),
59
+ ).toEqual({ start: "2026-07-10", end: "2026-07-14" });
60
+ });
61
+
37
62
  it.each([
38
63
  ["today", "2026-07-14", "2026-07-14"],
39
64
  ["yesterday", "2026-07-13", "2026-07-13"],
@@ -17,6 +17,35 @@ export interface OperationDateRange {
17
17
  end: string;
18
18
  }
19
19
 
20
+ /** A daily Cost Monitor group with a factual monetary measurement. */
21
+ export interface FactualCostDay {
22
+ key: string;
23
+ estimated_brl: number | null;
24
+ estimated_usd: number | null;
25
+ }
26
+
27
+ /**
28
+ * All Time is unbounded for requests, but its disabled date controls should
29
+ * describe the actual Cost Monitor coverage rather than an Operations record.
30
+ */
31
+ export function factualCostDateRange(
32
+ days: readonly FactualCostDay[] | undefined,
33
+ fallback: OperationDateRange,
34
+ today = new Date(),
35
+ ): OperationDateRange {
36
+ const firstMeasuredDay = days
37
+ ?.map((day) => day.key)
38
+ .filter(
39
+ (key, index, keys) =>
40
+ /^\d{4}-\d{2}-\d{2}$/.test(key) &&
41
+ (days[index]!.estimated_brl !== null || days[index]!.estimated_usd !== null),
42
+ )
43
+ .sort()[0];
44
+ return firstMeasuredDay === undefined
45
+ ? fallback
46
+ : { start: firstMeasuredDay, end: toDateInputValue(today) };
47
+ }
48
+
20
49
  export function initialTaskDateRange(
21
50
  tasks: readonly { created_at: string }[],
22
51
  today = new Date(),
@@ -36,7 +36,7 @@ describe("Operations persisted aggregate display", () => {
36
36
  expect(totals).toMatchObject({ duration_ms: null, tokens: 0, cost_brl: 0 });
37
37
  });
38
38
 
39
- it("preserves unavailable aggregate totals instead of rendering coerced zeroes", () => {
39
+ it("preserves unavailable aggregate totals and renders zero only for an empty population", () => {
40
40
  const overview = readFileSync(new URL("OperationsOverview.tsx", import.meta.url), "utf8");
41
41
 
42
42
  expect(overview).toContain("overviewMetrics?.duration_ms ??");
@@ -48,7 +48,8 @@ describe("Operations persisted aggregate display", () => {
48
48
  expect(overview).toContain(
49
49
  "durationMs: totalDurationMetric, tokens: totalTokensMetric, costBrl: totalCostMetric",
50
50
  );
51
- expect(overview).toContain("formatEntityDuration(averageDuration, locale)");
51
+ expect(overview).toContain("formatSummaryMetricValue(\n averageDuration,");
52
+ expect(overview).toContain('populationCount === 0 ? "0"');
52
53
  expect(overview).not.toContain("allPhaseMetrics.tokens ?? 0");
53
54
  expect(overview).not.toContain("executionMetrics.durationMs ?? 0");
54
55
  });
@@ -50,6 +50,15 @@ describe("overview period filter", () => {
50
50
  expect(shouldRenderOperationsContent(true)).toBe(false);
51
51
  });
52
52
 
53
+ it("synchronizes the controlled All Time display after factual cost coverage arrives", () => {
54
+ const source = readFileSync(new URL("OperationsPage.tsx", import.meta.url), "utf8");
55
+
56
+ expect(source).toContain('datePreset !== "all"');
57
+ expect(source).toContain("dateRange.start === defaultDateRange.start");
58
+ expect(source).toContain('updatePeriod("all", defaultDateRange);');
59
+ expect(source).toContain("requestPeriodForOperations still omits both bounds");
60
+ });
61
+
53
62
  it("derives all big-number KPIs from the period-filtered global aggregate", () => {
54
63
  const source = readFileSync(new URL("OperationsOverview.tsx", import.meta.url), "utf8");
55
64
  const helpers = readFileSync(
@@ -81,8 +90,9 @@ describe("overview period filter", () => {
81
90
 
82
91
  expect(source).toContain('currency: "BRL" | "USD"');
83
92
  expect(source).toContain("const selectedCurrencyCost =");
84
- expect(source).toContain("formatCurrency(averageCost, currency, locale)");
85
- expect(source).toContain("formatRoundedCurrency(totalCostMetric, currency, locale)");
93
+ expect(source).toContain("formatCurrency(value, currency, locale)");
94
+ expect(source).toContain("formatSummaryMetricValue(\n totalCostMetric,");
95
+ expect(source).toContain("formatRoundedCurrency(value, currency, locale)");
86
96
  expect(source).toContain("const topEntitiesCost =");
87
97
  expect(topPanel).toContain("formatCurrency(topCost, currency, locale)");
88
98
  expect(periods).toContain("resolveRunCost?: ((runId: string) => number | null)");
@@ -2,6 +2,10 @@ import { readFileSync } from "node:fs";
2
2
  import { describe, expect, it } from "vitest";
3
3
 
4
4
  const source = readFileSync(new URL("./OperationsOverview.tsx", import.meta.url), "utf8");
5
+ const dialogSource = readFileSync(
6
+ new URL("./OperationsFilteredStatusDialog.tsx", import.meta.url),
7
+ "utf8",
8
+ );
5
9
 
6
10
  describe("OperationsOverview status cards", () => {
7
11
  it("opens a filtered dialog for Tasks, SPECs, and PRDs", () => {
@@ -12,16 +16,18 @@ describe("OperationsOverview status cards", () => {
12
16
  });
13
17
 
14
18
  it("uses selected-type labels, closes before document navigation, and paginates", () => {
15
- expect(source).toContain("onOpenDocument(item.kind, item.id)");
19
+ expect(dialogSource).toContain("onOpenDocument(item.kind, item.id)");
16
20
  expect(source).toContain("setSelectedStatus(null);");
17
21
  expect(source).toContain('"Status das tarefas"');
18
22
  expect(source).toContain('"Status das SPECs"');
19
23
  expect(source).toContain('"Status dos PRDs"');
20
- expect(source).toContain("const total = page?.total ?? items.length;");
21
- expect(source).toContain("aria-label={`${label} — ${total} ${taskLabel}`}");
22
- expect(source).toContain("onLoadMore(items.length)");
23
- expect(source).toContain('aria-busy="true"');
24
- expect(source).toContain('role="alert"');
25
- expect(source).toContain('Nenhum${kind === "task" ? "a" : ""}');
24
+ expect(dialogSource).toContain("const total = page?.total ?? items.length;");
25
+ expect(dialogSource).toContain(
26
+ "aria-label={`" + "$" + "{label} — $" + "{total} $" + "{taskLabel}`}",
27
+ );
28
+ expect(dialogSource).toContain("onLoadMore(items.length)");
29
+ expect(dialogSource).toContain('aria-busy="true"');
30
+ expect(dialogSource).toContain('role="alert"');
31
+ expect(dialogSource).toContain("Nenhum$" + '{kind === "task" ? "a" : ""}');
26
32
  });
27
33
  });
@@ -582,7 +582,7 @@ describe("dashboard backend", () => {
582
582
  tasks: expect.any(Array),
583
583
  runs: expect.any(Array),
584
584
  });
585
- });
585
+ }, 60_000);
586
586
  it("serves only allowlisted canonical agent portraits", async () => {
587
587
  const app = createDashboardApp(workspaceRoot, "test-session");
588
588
  const agentIds = [
@@ -0,0 +1,40 @@
1
+ import { access, mkdir } from "node:fs/promises";
2
+ import { relative, resolve } from "node:path";
3
+ import process from "node:process";
4
+
5
+ async function exists(path) {
6
+ return access(path).then(
7
+ () => true,
8
+ () => false,
9
+ );
10
+ }
11
+
12
+ /**
13
+ * A fresh npm installation has no lifecycle history yet. The SQLite read
14
+ * model supports that empty state, so its canonical source root is created
15
+ * instead of requiring the development workspace to be installed again.
16
+ */
17
+ export async function ensureDashboardRecordsDirectory(root) {
18
+ await mkdir(resolve(root, ".agents/records"), { recursive: true });
19
+ }
20
+
21
+ export async function assertDashboardPrerequisites(workspaceRoot) {
22
+ const major = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
23
+ if (!Number.isInteger(major) || major < 22)
24
+ throw new Error(
25
+ `O Dashboard SQLite requer Node.js 22 ou superior; a versão encontrada foi ${process.versions.node}. Instale o Node.js 22 e tente novamente.`,
26
+ );
27
+ try {
28
+ await import("node:sqlite");
29
+ } catch {
30
+ throw new Error(
31
+ "O Node.js instalado não disponibiliza node:sqlite. Reinstale uma versão suportada do Node.js 22 ou superior.",
32
+ );
33
+ }
34
+ await ensureDashboardRecordsDirectory(workspaceRoot);
35
+ const required = resolve(workspaceRoot, ".agents/config");
36
+ if (!(await exists(required)))
37
+ throw new Error(
38
+ `Pré-requisito do Dashboard SQLite ausente: ${relative(workspaceRoot, required) || required}. Execute corepack pnpm install na raiz do ZAIP-AIOS e tente novamente.`,
39
+ );
40
+ }
@@ -0,0 +1,38 @@
1
+ import { access, mkdir, mkdtemp, rm } from "node:fs/promises";
2
+ import { tmpdir } from "node:os";
3
+ import { join } from "node:path";
4
+ import { afterEach, describe, expect, it } from "vitest";
5
+ import {
6
+ assertDashboardPrerequisites,
7
+ ensureDashboardRecordsDirectory,
8
+ } from "./start-dashboard-prerequisites.mjs";
9
+
10
+ const temporaryDirectories = [];
11
+
12
+ afterEach(async () => {
13
+ await Promise.all(
14
+ temporaryDirectories.splice(0).map((path) => rm(path, { recursive: true, force: true })),
15
+ );
16
+ });
17
+
18
+ describe("dashboard SQLite prerequisites", () => {
19
+ it("creates the empty records root required by a fresh npm installation", async () => {
20
+ const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-empty-records-"));
21
+ temporaryDirectories.push(root);
22
+
23
+ await expect(access(join(root, ".agents", "records"))).rejects.toMatchObject({
24
+ code: "ENOENT",
25
+ });
26
+ await ensureDashboardRecordsDirectory(root);
27
+ await expect(access(join(root, ".agents", "records"))).resolves.toBeUndefined();
28
+ });
29
+
30
+ it("accepts a zeroed workspace after creating its records root", async () => {
31
+ const root = await mkdtemp(join(tmpdir(), "zaip-dashboard-prerequisites-"));
32
+ temporaryDirectories.push(root);
33
+ await mkdir(join(root, ".agents", "config"), { recursive: true });
34
+
35
+ await expect(assertDashboardPrerequisites(root)).resolves.toBeUndefined();
36
+ await expect(access(join(root, ".agents", "records"))).resolves.toBeUndefined();
37
+ });
38
+ });
@@ -25,6 +25,7 @@ import {
25
25
  presentLaunchFailure,
26
26
  writeLauncherStatus,
27
27
  } from "./start-dashboard-lifecycle.mjs";
28
+ import { assertDashboardPrerequisites } from "./start-dashboard-prerequisites.mjs";
28
29
  import {
29
30
  isProcessAlive,
30
31
  stopOwnedDashboardProcess,
@@ -171,30 +172,7 @@ export function formatLauncherError(value, maximumLength = 3_600) {
171
172
  const lastHalf = maximumLength - firstHalf;
172
173
  return `${message.slice(0, firstHalf)}\n\n… detalhes reduzidos; consulte o log temporário …\n\n${message.slice(-lastHalf)}`;
173
174
  }
174
- export async function assertDashboardPrerequisites() {
175
- const major = Number.parseInt(process.versions.node.split(".")[0] ?? "", 10);
176
- if (!Number.isInteger(major) || major < 22)
177
- throw new Error(
178
- `O Dashboard SQLite requer Node.js 22 ou superior; a versão encontrada foi ${process.versions.node}. Instale o Node.js 22 e tente novamente.`,
179
- );
180
- try {
181
- await import("node:sqlite");
182
- } catch {
183
- throw new Error(
184
- "O Node.js instalado não disponibiliza node:sqlite. Reinstale uma versão suportada do Node.js 22 ou superior.",
185
- );
186
- }
187
- for (const required of [
188
- resolve(workspaceRoot, ".agents/records"),
189
- resolve(workspaceRoot, ".agents/config"),
190
- resolve(workspaceRoot, "node_modules/esbuild"),
191
- ]) {
192
- if (!(await exists(required)))
193
- throw new Error(
194
- `Pré-requisito do Dashboard SQLite ausente: ${relative(workspaceRoot, required) || required}. Execute corepack pnpm install na raiz do ZAIP-AIOS e tente novamente.`,
195
- );
196
- }
197
- }
175
+
198
176
  export async function readDashboardSession(path = sessionPath) {
199
177
  try {
200
178
  return JSON.parse(await readFile(path, "utf8"));
@@ -457,6 +435,32 @@ export async function refreshDashboardBuildTargets({
457
435
  return { artifacts: Object.values(targets).flatMap((target) => target.artifacts), builtTargets };
458
436
  }
459
437
  async function ensureFreshDashboardBuild(onProgress = () => {}) {
438
+ // npm consumers receive a ready-to-run server and frontend bundle. They do
439
+ // not receive development-only esbuild, so never turn the first dashboard
440
+ // click into a pnpm-install requirement when that packaged runtime exists.
441
+ const buildTool = resolve(workspaceRoot, "node_modules/esbuild");
442
+ if (!(await exists(buildTool))) {
443
+ const packagedArtifacts = [installedServer, resolve(frontendRoot, "index.html")];
444
+ if (
445
+ await Promise.all(packagedArtifacts.map((path) => exists(path))).then((entries) =>
446
+ entries.every(Boolean),
447
+ )
448
+ ) {
449
+ buildProgress(
450
+ onProgress,
451
+ 39,
452
+ "Usando runtime empacotado",
453
+ "O pacote npm já inclui o dashboard pronto; nenhuma instalação de desenvolvimento é necessária.",
454
+ );
455
+ const signature = await Promise.all(
456
+ [...packagedArtifacts, resolve(packageRoot, "start-dashboard.mjs")].map(async (path) => {
457
+ const details = await stat(path);
458
+ return `${path}:${details.size}:${details.mtimeMs}`;
459
+ }),
460
+ );
461
+ return createHash("sha256").update(signature.join("|")).digest("hex");
462
+ }
463
+ }
460
464
  const { artifacts } = await refreshDashboardBuildTargets({ onProgress });
461
465
  const artifactFingerprint = await Promise.all(
462
466
  [...artifacts, resolve(packageRoot, "start-dashboard.mjs")].map(async (path) => {
@@ -626,7 +630,10 @@ async function redirectLaunchPage(launchPage, target) {
626
630
  launchPage.closeAfterHandoffGrace();
627
631
  }
628
632
  async function assertRuntimeReady() {
629
- const serverPath = (await exists(sourceServer)) ? sourceServer : installedServer;
633
+ const serverPath =
634
+ (await exists(resolve(workspaceRoot, "node_modules/esbuild"))) && (await exists(sourceServer))
635
+ ? sourceServer
636
+ : installedServer;
630
637
  if (!(await exists(serverPath))) {
631
638
  throw new Error(
632
639
  "Dashboard server bundle is missing; run the ZAIP release build or reinstall ZAIP-AIOS",
@@ -854,7 +861,7 @@ async function main() {
854
861
  detail:
855
862
  "Verificando Node.js 22 e o suporte a node:sqlite; isto não instala um banco de dados.",
856
863
  });
857
- await assertDashboardPrerequisites();
864
+ await assertDashboardPrerequisites(workspaceRoot);
858
865
  launchPage.setProgress({
859
866
  percent: 18,
860
867
  phase: "Preparando a sessão do dashboard",
@@ -5,7 +5,6 @@ import { tmpdir } from "node:os";
5
5
  import { join } from "node:path";
6
6
  import { afterEach, describe, expect, it } from "vitest";
7
7
  import {
8
- assertDashboardPrerequisites,
9
8
  browserLaunchCommands,
10
9
  findAvailablePort,
11
10
  findAvailablePortWithFallback,
@@ -56,17 +55,15 @@ describe("dashboard executable session", () => {
56
55
  expect(stripTerminalControlCodes(colored)).toBe("Missing export");
57
56
  });
58
57
 
59
- it("shows preparation before validating a Node runtime that can import node:sqlite", async () => {
58
+ it("shows preparation before validating dashboard prerequisites", async () => {
60
59
  const source = await readFile(new URL("./start-dashboard.mjs", import.meta.url), "utf8");
61
60
 
62
- expect(source).toContain("export async function assertDashboardPrerequisites()");
63
- expect(source).toContain('await import("node:sqlite")');
64
- expect(source).not.toContain('builtinModules.includes("node:sqlite")');
65
- expect(source).toContain("Node.js 22 ou superior");
61
+ expect(source).toContain('from "./start-dashboard-prerequisites.mjs";');
62
+ expect(source).toContain("await assertDashboardPrerequisites(workspaceRoot);");
63
+ expect(source).toContain("Usando runtime empacotado");
66
64
  expect(source.indexOf("await openBrowser(dashboardUrl(launchPort, token))")).toBeLessThan(
67
- source.indexOf("await assertDashboardPrerequisites();"),
65
+ source.indexOf("await assertDashboardPrerequisites(workspaceRoot);"),
68
66
  );
69
- await expect(assertDashboardPrerequisites()).resolves.toBeUndefined();
70
67
  });
71
68
 
72
69
  it("keeps the beginning and end of an oversized launcher error", () => {
@@ -50,7 +50,7 @@ test("releases an aborted Cost group load and keeps Operation reachable", async
50
50
  });
51
51
 
52
52
  await openDashboard(page);
53
- await expectGlobalMonthPreset(page);
53
+ await expectGlobalAllTimePreset(page);
54
54
  const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
55
55
  await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
56
56
  const costTabs = page.locator(".cost-tabs");
@@ -87,7 +87,7 @@ test("loads every Cost Monitor tab before returning to Operation", async ({ page
87
87
  });
88
88
 
89
89
  await openDashboard(page);
90
- await expectGlobalMonthPreset(page);
90
+ await expectGlobalAllTimePreset(page);
91
91
  const capabilityNav = page.getByRole("navigation", { name: "Áreas do dashboard" });
92
92
  await capabilityNav.getByRole("button", { name: /Monitor de Custos/ }).click();
93
93
  const costTabs = page.locator(".cost-tabs");
@@ -124,10 +124,8 @@ test("loads every Cost Monitor tab before returning to Operation", async ({ page
124
124
  expect(failedApiResponses).toEqual([]);
125
125
  });
126
126
 
127
- async function expectGlobalMonthPreset(page: import("@playwright/test").Page) {
128
- await expect(
129
- page.locator(".dashboard-global-controls").getByRole("combobox", { name: "Período" }),
130
- ).toHaveValue("this-month");
127
+ async function expectGlobalAllTimePreset(page: import("@playwright/test").Page) {
128
+ await expect(page.locator(".dashboard-global-controls").getByLabel("Período")).toHaveValue("all");
131
129
  }
132
130
 
133
131
  async function openDashboard(page: import("@playwright/test").Page) {
@@ -156,6 +156,10 @@ async function openAllTimeCostMonitor(page: Page) {
156
156
  );
157
157
  await page.goto("/?token=e2e-token");
158
158
  await operations;
159
+ await page
160
+ .getByRole("navigation", { name: "Áreas do dashboard" })
161
+ .getByRole("button", { name: /Operação/ })
162
+ .click();
159
163
  const period = page
160
164
  .locator(".dashboard-global-controls")
161
165
  .getByRole("combobox", { name: "Período" });
@@ -8,12 +8,13 @@ test("refreshes Cost Monitor within five seconds when Todo o Período is selecte
8
8
  page,
9
9
  }) => {
10
10
  await openDashboard(page);
11
+ await selectThisMonth(page);
11
12
  await openCostMonitor(page);
12
13
 
13
- const summary = waitForCostSummary(page, "all");
14
+ const summaryRequest = waitForCostSummaryRequest(page, "all");
14
15
  const startedAt = Date.now();
15
16
  await periodControl(page).selectOption("all");
16
- await summary;
17
+ await summaryRequest;
17
18
  await expectCostMonitorReady(page);
18
19
 
19
20
  expect(Date.now() - startedAt).toBeLessThan(fiveSeconds);
@@ -25,22 +26,29 @@ test("loads Cost Monitor within five seconds when the user enters after choosing
25
26
  }) => {
26
27
  await openDashboard(page);
27
28
 
29
+ await page
30
+ .getByRole("navigation", { name: "Áreas do dashboard" })
31
+ .getByRole("button", { name: /Operação/ })
32
+ .click();
33
+ await expect(periodControl(page)).toBeVisible();
34
+
35
+ await selectThisMonth(page);
36
+ const summary = waitForCostSummary(page, "all");
28
37
  await periodControl(page).selectOption("all");
38
+ await summary;
29
39
  await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 60_000 });
30
40
 
31
- const summary = waitForCostSummary(page, "all");
32
41
  const startedAt = Date.now();
33
42
  await page
34
43
  .getByRole("navigation", { name: "Áreas do dashboard" })
35
44
  .getByRole("button", { name: /Monitor de Custos/ })
36
45
  .click();
37
- await summary;
38
46
  await expectCostMonitorReady(page);
39
47
 
40
48
  expect(Date.now() - startedAt).toBeLessThan(fiveSeconds);
41
49
  });
42
50
 
43
- test("shows one retry after a summary 503 and does not retry in a loop", async ({ page }) => {
51
+ test("retries a summary 503 once when Cost Monitor opens and does not loop", async ({ page }) => {
44
52
  let summaries = 0;
45
53
  await page.route("**/api/v2/cost-monitor**", async (route) => {
46
54
  const url = new URL(route.request().url());
@@ -56,16 +64,10 @@ test("shows one retry after a summary 503 and does not retry in a loop", async (
56
64
  });
57
65
 
58
66
  await openDashboard(page);
59
- await openCostMonitor(page, false);
60
- const retry = page.locator(".app-loading-panel-error").getByRole("button", {
61
- name: "Tentar novamente",
62
- });
63
- await expect(retry).toBeVisible();
64
-
65
- await retry.click();
66
- await expectCostMonitorReady(page);
67
+ await openCostMonitor(page);
67
68
  await page.waitForTimeout(750);
68
69
  expect(summaries).toBe(2);
70
+ await expect(page.locator(".app-error")).toHaveCount(0);
69
71
  });
70
72
 
71
73
  test("does not let a late all-time summary overwrite the newer period", async ({ page }) => {
@@ -130,6 +132,7 @@ test("does not let a late all-time summary overwrite the newer period", async ({
130
132
  });
131
133
 
132
134
  await openDashboard(page);
135
+ await selectThisMonth(page);
133
136
  await openCostMonitor(page);
134
137
  await expect.poll(() => priorPeriodBody).not.toBeNull();
135
138
  const staleBody = withStaleSummarySentinel(priorPeriodBody!);
@@ -176,17 +179,26 @@ async function openDashboard(page: Page) {
176
179
  await operations;
177
180
  }
178
181
 
179
- async function openCostMonitor(page: Page, expectReady = true) {
180
- const summary = expectReady ? waitForCostSummary(page, "this-month") : null;
182
+ async function openCostMonitor(page: Page) {
181
183
  await page
182
184
  .getByRole("navigation", { name: "Áreas do dashboard" })
183
185
  .getByRole("button", { name: /Monitor de Custos/ })
184
186
  .click();
185
- if (!expectReady) return;
186
- await summary;
187
187
  await expectCostMonitorReady(page);
188
188
  }
189
189
 
190
+ async function selectThisMonth(page: Page) {
191
+ await page
192
+ .getByRole("navigation", { name: "Áreas do dashboard" })
193
+ .getByRole("button", { name: /Operação/ })
194
+ .click();
195
+ const summary =
196
+ (await page.locator(".cost-tabs").count()) > 0 ? waitForCostSummary(page, "this-month") : null;
197
+ await periodControl(page).selectOption("this-month");
198
+ await summary;
199
+ await expect(page.locator(".period-transition-overlay")).toHaveCount(0, { timeout: 60_000 });
200
+ }
201
+
190
202
  function periodControl(page: Page) {
191
203
  return page.locator(".dashboard-global-controls").getByRole("combobox", { name: "Período" });
192
204
  }
@@ -203,6 +215,20 @@ function waitForCostSummary(page: Page, preset: "all" | "this-month") {
203
215
  });
204
216
  }
205
217
 
218
+ function waitForCostSummaryRequest(page: Page, preset: "all" | "this-month") {
219
+ return page.waitForRequest(
220
+ (request) => {
221
+ const url = new URL(request.url());
222
+ if (url.pathname !== COST_MONITOR_PATH || url.searchParams.get("scope") !== "summary")
223
+ return false;
224
+ return preset === "all"
225
+ ? !url.searchParams.has("from") && !url.searchParams.has("to")
226
+ : url.searchParams.has("from") && url.searchParams.has("to");
227
+ },
228
+ { timeout: 10_000 },
229
+ );
230
+ }
231
+
206
232
  async function expectCostMonitorReady(page: Page) {
207
233
  await expect(page.locator(".cost-tabs")).toBeVisible({ timeout: fiveSeconds });
208
234
  const dailyTable = page.locator(".cost-content-daily table");
@@ -174,6 +174,10 @@ async function openDashboard(page: Page) {
174
174
  }
175
175
 
176
176
  async function selectPeriod(page: Page, period: "this-month" | "all") {
177
+ await page
178
+ .getByRole("navigation", { name: "Áreas do dashboard" })
179
+ .getByRole("button", { name: /Operação/ })
180
+ .click();
177
181
  const control = page
178
182
  .locator(".dashboard-global-controls")
179
183
  .getByRole("combobox", { name: "Período" });
@@ -290,9 +294,11 @@ async function exerciseOperations(
290
294
  },
291
295
  successfulResponses,
292
296
  content === ".operations-work-board"
293
- ? boardStages.map((stage) =>
294
- operationSelectionResponseMatcher(content, kind, expectedPeriod, stage),
295
- )
297
+ ? kind === "Tudo"
298
+ ? undefined
299
+ : boardStages.map((stage) =>
300
+ operationSelectionResponseMatcher(content, kind, expectedPeriod, stage),
301
+ )
296
302
  : [selectionResponse],
297
303
  );
298
304
  }