@zaip-ai/cli 4.5.0 → 4.6.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.
- package/README.md +2 -0
- package/dist/services/adapters.d.ts +1 -1
- package/dist/services/adapters.js +4 -4
- package/dist/services/adapters.js.map +1 -1
- package/dist/services/codex-native-tool-authority-template.d.ts.map +1 -1
- package/dist/services/codex-native-tool-authority-template.js +188 -9
- package/dist/services/codex-native-tool-authority-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.js +50 -128
- package/dist/services/codex-pre-tool-hook-template.js.map +1 -1
- package/dist/services/codex-pre-tool-hook-template.test.js +4 -0
- package/dist/services/codex-pre-tool-hook-template.test.js.map +1 -1
- package/dist/services/codex-pre-tool-support-template.d.ts.map +1 -1
- package/dist/services/codex-pre-tool-support-template.js +1 -4
- package/dist/services/codex-pre-tool-support-template.js.map +1 -1
- package/dist/services/codex-stale-git-lock-template.d.ts +2 -0
- package/dist/services/codex-stale-git-lock-template.d.ts.map +1 -0
- package/dist/services/codex-stale-git-lock-template.js +64 -0
- package/dist/services/codex-stale-git-lock-template.js.map +1 -0
- package/dist/services/global-codex-config-migration.test.js +1 -4
- package/dist/services/global-codex-config-migration.test.js.map +1 -1
- package/dist/services/install-template-policy.d.ts.map +1 -1
- package/dist/services/install-template-policy.js +2 -0
- package/dist/services/install-template-policy.js.map +1 -1
- package/dist/services/logger.js +2 -1
- package/dist/services/logger.js.map +1 -1
- package/dist/services/logger.test.js +71 -0
- package/dist/services/logger.test.js.map +1 -1
- package/dist/services/runtime-template-test-support.d.ts +3 -0
- package/dist/services/runtime-template-test-support.d.ts.map +1 -0
- package/dist/services/runtime-template-test-support.js +32 -0
- package/dist/services/runtime-template-test-support.js.map +1 -0
- package/dist/services/runtime-template.test.js +23 -31
- package/dist/services/runtime-template.test.js.map +1 -1
- package/package.json +1 -1
- package/templates/.agents/README.md +8 -0
- package/templates/.agents/config/agents/discovery.json +1 -1
- package/templates/.agents/config/deploy-policy.json +5 -0
- package/templates/.agents/dashboard/.zaip-managed.json +211 -75
- package/templates/.agents/dashboard/README.md +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{CostCalculationGuidePage-f4ZGJRg3.js → CostCalculationGuidePage-BRtiPijr.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{DashboardHelpDialog-CkQ_y8kg.js → DashboardHelpDialog-BR4ZeYpE.js} +99 -10
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{PricingSettingsDialog-v9zGRTlk.js → PricingSettingsDialog-Dr4QDFmn.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{ProjectSettingsPage-doIMhei7.js → ProjectSettingsPage-BG0N5dw_.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{TraceabilityWorkspace-C_4ftjyB.js → TraceabilityWorkspace-BvoM-YQ0.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-fTQhSL85.js +68 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{icons-vendor-Cv3WBFIF.js → icons-vendor-Bc6vux5t.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-BMfzKaAR.js +2 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-DZhppOIk.css +1 -0
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{react-vendor-mHgsmxb6.js → react-vendor-DNJQFov3.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/{traceability-B0CPyEVq.js → traceability-B7tjIB7W.js} +1 -1
- package/templates/.agents/dashboard/apps/frontend/dist/index.html +6 -6
- package/templates/.agents/dashboard/apps/frontend/package.json +1 -1
- package/templates/.agents/dashboard/apps/frontend/src/AppOverlays.tsx +6 -34
- package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.dom.test.ts +130 -0
- package/templates/.agents/dashboard/apps/frontend/src/ReconciliationFeedback.tsx +226 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api-sqlite-view.ts +45 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.test.ts +15 -0
- package/templates/.agents/dashboard/apps/frontend/src/dashboard-api.ts +17 -46
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-fixtures.ts +23 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.test.ts +69 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback-model.ts +59 -0
- package/templates/.agents/dashboard/apps/frontend/src/reconciliation-feedback.css +131 -0
- package/templates/.agents/dashboard/apps/frontend/src/styles.css +0 -32
- package/templates/.agents/dashboard/apps/frontend/src/use-dashboard-app-model.tsx +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/package.json +1 -1
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.test.ts +2 -2
- package/templates/.agents/dashboard/modules/cost-monitor/src/react/CostMonitorPage.tsx +1 -1
- package/templates/.agents/dashboard/modules/operations/package.json +1 -1
- package/templates/.agents/dashboard/modules/operations/src/react/OperationsPage.tsx +1 -4
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.navigation.test.ts +236 -0
- package/templates/.agents/dashboard/modules/operations/src/react/UnifiedWorkBoard.tsx +1 -24
- package/templates/.agents/dashboard/modules/operations/src/react/operations-overview-period-filter.test.ts +3 -2
- package/templates/.agents/dashboard/modules/traceability/package.json +1 -1
- package/templates/.agents/dashboard/packages/branding/package.json +1 -1
- package/templates/.agents/dashboard/packages/contracts/package.json +1 -1
- package/templates/.agents/dashboard/packages/data-export/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/DIAGNOSTICS.md +42 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.d.mts +10 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.mjs +101 -0
- package/templates/.agents/dashboard/packages/local-runtime/dashboard-diagnostics.test.mjs +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/dist/server.cjs +232 -217
- package/templates/.agents/dashboard/packages/local-runtime/dist/sqlite-read-model.cjs +201 -188
- package/templates/.agents/dashboard/packages/local-runtime/package.json +1 -1
- package/templates/.agents/dashboard/packages/local-runtime/scripts/test-sqlite-dashboard-startup.mjs +233 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/validate-provider-nonzero.mjs +45 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-executable-startup.mjs +107 -0
- package/templates/.agents/dashboard/packages/local-runtime/scripts/verify-dashboard-reconciliation-executable.mjs +271 -0
- package/templates/.agents/dashboard/packages/local-runtime/server.mjs +232 -217
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.test.ts +78 -12
- package/templates/.agents/dashboard/packages/local-runtime/src/active-codex-telemetry.ts +36 -56
- package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.test.ts +125 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/canonical-source-revision-cache.ts +168 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/dashboard-diagnostics.test.ts +57 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-agent-catalog-routes.ts +76 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-dual-reads-manifest-summaries-without-losing.test.ts +5 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-diagnostics.test.ts +118 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-result.test.ts +35 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/server-reconciliation-route.ts +105 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.test.ts +64 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/server-snapshot-synchronization.ts +19 -27
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-command.test.ts +111 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.test.ts +59 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-telemetry-refresh.ts +44 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.test.ts +20 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/server-work-reconciliation.ts +12 -7
- package/templates/.agents/dashboard/packages/local-runtime/src/server-workspace.ts +48 -6
- package/templates/.agents/dashboard/packages/local-runtime/src/server.test.ts +2 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/server.ts +126 -134
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-cost-agent-page.ts +14 -2
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.test.ts +163 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-dashboard-startup.ts +78 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-header-timestamp.test.ts +31 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-nonzero.test.ts +271 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-provider-visibility.ts +32 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-operations-duration.ts +58 -0
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-provider-dimension.ts +12 -3
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-query-view-page.ts +9 -58
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-rebuild.ts +9 -35
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-schema.ts +10 -8
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model-source-fingerprint.ts +43 -45
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.test.ts +5 -9
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-read-model.ts +5 -1
- package/templates/.agents/dashboard/packages/local-runtime/src/sqlite-startup-seed.test.ts +87 -0
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-logging.mjs +6 -2
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard-server.mjs +7 -1
- package/templates/.agents/dashboard/packages/local-runtime/start-dashboard.mjs +4 -5
- package/templates/.agents/dashboard/packages/ui/package.json +1 -1
- package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.test.mjs +33 -4
- package/templates/.agents/dashboard/packages/ui/src/DashboardHeader.tsx +8 -7
- package/templates/.agents/dashboard/packages/windows-launcher/Program.cs +46 -3
- package/templates/.agents/dashboard/tests/e2e/package.json +1 -1
- package/templates/.agents/dashboard/tests/e2e/qualify-kanban-popup.mjs +56 -0
- package/templates/.agents/dashboard/tests/e2e/specs/dashboard-update-label.spec.ts +49 -0
- package/templates/.agents/design/.zaip-managed.json +17 -5
- package/templates/.agents/design/apps/catalog/dist/assets/index-D3uhpycC.js +72 -0
- package/templates/.agents/design/apps/catalog/dist/index.html +1 -1
- package/templates/.agents/design/apps/catalog/package.json +1 -1
- package/templates/.agents/design/apps/catalog/src/pages/ComponentsPage.tsx +13 -1
- package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.stories.tsx +6 -0
- package/templates/.agents/design/apps/catalog/src/specimens/ReconciliationSpecimen.tsx +101 -0
- package/templates/.agents/design/packages/local-runtime/package.json +1 -1
- package/templates/.agents/requirements/autonomous-assurance.md +1 -1
- package/templates/.agents/requirements/cloud-hostinger-autonomy.md +1 -1
- package/templates/.agents/requirements/comparative-audit.md +1 -1
- package/templates/.agents/requirements/context-engine.md +1 -1
- package/templates/.agents/requirements/cost-observability.md +1 -1
- package/templates/.agents/requirements/dashboard-launch.md +1 -1
- package/templates/.agents/requirements/data-export.md +1 -1
- package/templates/.agents/requirements/design-system.md +1 -1
- package/templates/.agents/requirements/execution-engine.md +1 -1
- package/templates/.agents/requirements/kanban-dashboard.md +1 -1
- package/templates/.agents/requirements/model-orchestration.md +21 -6
- package/templates/.agents/requirements/public-distribution.md +4 -4
- package/templates/.agents/requirements/software-architecture.md +1 -1
- package/templates/.agents/requirements/strategic-improvements.md +1 -1
- package/templates/.agents/requirements/work-lifecycle.md +1 -1
- package/templates/.agents/requirements/zaip-strategic-95.md +1 -1
- package/templates/.agents/rules/core.md +3 -1
- package/templates/.agents/rules/workflow.md +3 -2
- package/templates/.agents/runtime/VERSION +1 -1
- package/templates/.agents/runtime/bin/zaip.mjs +366 -196
- package/templates/.agents/runtime/checksums.sha256 +5 -5
- package/templates/.agents/runtime/manifest.json +1 -1
- package/templates/.agents/runtime/provenance.json +4 -4
- package/templates/.agents/runtime/sbom.cdx.json +1 -1
- package/templates/.agents/skills/zaip-skill-config/SKILL.md +25 -3
- package/templates/.agents/skills/zaip-skill-execution/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-execution/references/autonomous-continuation.md +4 -0
- package/templates/.agents/skills/zaip-skill-execution/references/candidate-workflow.md +1 -1
- package/templates/.agents/skills/zaip-skill-execution/references/runtime-governance.md +2 -2
- package/templates/.agents/skills/zaip-skill-general-operations/SKILL.md +1 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/SKILL.md +36 -48
- package/templates/.agents/skills/zaip-skill-github-deploy/agents/openai.yaml +2 -2
- package/templates/.agents/skills/zaip-skill-github-deploy/references/deployment-workflow.md +67 -99
- package/templates/.agents/skills/zaip-skill-github-deploy/references/release-identity.md +33 -30
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.mjs +303 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/deploy-contract.test.mjs +334 -0
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/github-deploy-contract.test.mjs +41 -28
- package/templates/.agents/skills/zaip-skill-github-deploy/scripts/guarded-commits.mjs +212 -0
- package/templates/.agents/skills/zaip-skill-lifecycle-closure/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-prd/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-release-qualification/SKILL.md +44 -68
- package/templates/.agents/skills/zaip-skill-release-qualification/references/local-preparation.md +91 -44
- package/templates/.agents/skills/zaip-skill-send-email/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-send-email/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-setup/SKILL.md +5 -2
- package/templates/.agents/skills/zaip-skill-spec/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-task/SKILL.md +4 -0
- package/templates/.agents/skills/zaip-skill-testing-strategy/SKILL.md +19 -0
- package/templates/.agents/skills/zaip-skill-tool-setup/SKILL.md +2 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/SKILL.md +15 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/agents/openai.yaml +4 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/scripts/connector-guidance.test.mjs +29 -0
- package/templates/.agents/skills/zaip-skill-use-connectors/scripts/verify-connector-lock.mjs +30 -0
- package/templates/.agents/tools/cloud-runner.integration.test.mjs +3 -0
- package/templates/.agents/zaip-aios/VERSION +1 -1
- package/templates/.agents/zaip-aios/guides/03-tasks.md +6 -0
- package/templates/.agents/zaip-aios/guides/04-specs.md +6 -0
- package/templates/.agents/zaip-aios/guides/05-prds.md +4 -0
- package/templates/.agents/zaip-aios/guides/06-execucao.md +6 -0
- package/templates/.agents/zaip-aios/guides/16-lifecycle-e-reconciliacao.md +6 -0
- package/templates/.agents/zaip-aios/guides/19-assurance-e-autonomia.md +4 -0
- package/templates/.agents/zaip-aios/guides/22-fluxos-e-roteamento.md +6 -0
- package/templates/.agents/zaip-aios/guides/23-orquestracao-de-modelos.md +36 -4
- package/templates/.agents/zaip-aios/guides/26-qualidade-e-release.md +15 -4
- package/templates/.agents/zaip-aios/guides/28-lifecycle-completo.md +8 -0
- package/templates/template-manifest.json +330 -142
- package/templates/.agents/dashboard/apps/frontend/dist/assets/cost-monitor-D7PrDZy9.js +0 -68
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-OBJXZOwe.css +0 -1
- package/templates/.agents/dashboard/apps/frontend/dist/assets/index-Rbmpz4UO.js +0 -2
|
@@ -42,16 +42,10 @@ export async function rebuildSqliteReadModel(
|
|
|
42
42
|
options: SqliteRebuildOptions = {},
|
|
43
43
|
): Promise<SqliteRebuildResult> {
|
|
44
44
|
const path = sqliteReadModelPath(workspaceRoot);
|
|
45
|
-
//
|
|
45
|
+
// Startup serves a validated generation; the worker verifies canonical freshness.
|
|
46
46
|
if (options.preferExisting && !options.forceCanonical) {
|
|
47
47
|
try {
|
|
48
|
-
validateSqliteReadModelSchema(path);
|
|
49
|
-
const fingerprintStartedAt = performance.now();
|
|
50
|
-
const sourceSnapshot = await sourceFingerprint(
|
|
51
|
-
workspaceRoot,
|
|
52
|
-
rebuildFingerprintOptions(path, options),
|
|
53
|
-
);
|
|
54
|
-
const fingerprint = `${sourceSnapshot.fingerprint}\ncurrent_day=${saoPauloDay(new Date().toISOString())}`;
|
|
48
|
+
validateSqliteReadModelSchema(path, { integrity: "quick" });
|
|
55
49
|
const database = new DatabaseSync(path, { readOnly: true });
|
|
56
50
|
try {
|
|
57
51
|
if (hasReusableSnapshotPayloads(database)) {
|
|
@@ -60,38 +54,18 @@ export async function rebuildSqliteReadModel(
|
|
|
60
54
|
"SELECT value FROM zaip_vw_read_model_metadata WHERE key = 'source_file_count'",
|
|
61
55
|
)
|
|
62
56
|
.get() as { value: string };
|
|
63
|
-
const files =
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
readModelSchemaVersion,
|
|
67
|
-
hasReusableSnapshotPayloads,
|
|
68
|
-
);
|
|
69
|
-
if (files !== null) {
|
|
70
|
-
return {
|
|
71
|
-
files,
|
|
72
|
-
path,
|
|
73
|
-
cache: "generation",
|
|
74
|
-
fingerprint_ms: Math.round(performance.now() - fingerprintStartedAt),
|
|
75
|
-
materialize_ms: 0,
|
|
76
|
-
refresh: {
|
|
77
|
-
reused_sources: sourceSnapshot.reusedFiles,
|
|
78
|
-
processed_sources: sourceSnapshot.hashedFiles,
|
|
79
|
-
added_sources: 0,
|
|
80
|
-
changed_sources: 0,
|
|
81
|
-
removed_sources: 0,
|
|
82
|
-
canonical_runs_refolded: 0,
|
|
83
|
-
},
|
|
84
|
-
};
|
|
85
|
-
}
|
|
57
|
+
const files = Number(row?.value);
|
|
58
|
+
if (!Number.isSafeInteger(files) || files < 0)
|
|
59
|
+
throw new Error("Invalid snapshot file count");
|
|
86
60
|
return {
|
|
87
|
-
files
|
|
61
|
+
files,
|
|
88
62
|
path,
|
|
89
63
|
cache: "stale_seed",
|
|
90
|
-
fingerprint_ms:
|
|
64
|
+
fingerprint_ms: 0,
|
|
91
65
|
materialize_ms: 0,
|
|
92
66
|
refresh: {
|
|
93
|
-
reused_sources:
|
|
94
|
-
processed_sources:
|
|
67
|
+
reused_sources: 0,
|
|
68
|
+
processed_sources: 0,
|
|
95
69
|
added_sources: 0,
|
|
96
70
|
changed_sources: 0,
|
|
97
71
|
removed_sources: 0,
|
|
@@ -122,7 +122,10 @@ export function persistedSourceCheckpoint(
|
|
|
122
122
|
* is intentionally independent of source fingerprints: a matching cache key
|
|
123
123
|
* is not evidence that a partially copied or old-schema file is safe.
|
|
124
124
|
*/
|
|
125
|
-
export function validateSqliteReadModelSchema(
|
|
125
|
+
export function validateSqliteReadModelSchema(
|
|
126
|
+
path: string,
|
|
127
|
+
options: { integrity?: "full" | "quick" } = {},
|
|
128
|
+
): {
|
|
126
129
|
schemaVersion: string;
|
|
127
130
|
tableCount: number;
|
|
128
131
|
viewCount: number;
|
|
@@ -130,13 +133,12 @@ export function validateSqliteReadModelSchema(path: string): {
|
|
|
130
133
|
} {
|
|
131
134
|
const database = new DatabaseSync(path, { readOnly: true });
|
|
132
135
|
try {
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
);
|
|
136
|
+
// Only startup opts into quick_check. Workers and qualification retain the
|
|
137
|
+
// full cross-index integrity check, independently of source cache reuse.
|
|
138
|
+
const pragma = options.integrity === "quick" ? "quick_check" : "integrity_check";
|
|
139
|
+
const integrity = database.prepare(`PRAGMA ${pragma}`).get() as Record<string, unknown>;
|
|
140
|
+
if (integrity[pragma] !== "ok")
|
|
141
|
+
throw new Error(`SQLite ${pragma} failed for ${path}: ${integrity[pragma] ?? "unknown"}`);
|
|
140
142
|
const objects = database
|
|
141
143
|
.prepare("SELECT name, type FROM sqlite_master WHERE type IN ('table', 'view', 'index')")
|
|
142
144
|
.all() as Array<{ name: string; type: "table" | "view" | "index" }>;
|
|
@@ -3,6 +3,7 @@ import { createHash } from "node:crypto";
|
|
|
3
3
|
import { readdir, readFile, stat } from "node:fs/promises";
|
|
4
4
|
import { join, relative, resolve } from "node:path";
|
|
5
5
|
import { promisify } from "node:util";
|
|
6
|
+
import { CanonicalSourceRevisionCache } from "./canonical-source-revision-cache.js";
|
|
6
7
|
|
|
7
8
|
const execFileAsync = promisify(execFile);
|
|
8
9
|
|
|
@@ -87,23 +88,9 @@ export function isCodexManagedWorktreeRoot(root: string): boolean {
|
|
|
87
88
|
return /[\\/]\.codex[\\/]worktrees(?:[\\/][^\\/]+){1,2}$/iu.test(root);
|
|
88
89
|
}
|
|
89
90
|
|
|
90
|
-
function canonicalRevision(content: string, source: string): [string, number, number] {
|
|
91
|
-
if (source.endsWith(".jsonl")) {
|
|
92
|
-
const dates = [...content.matchAll(/"occurred_at"\s*:\s*"([^"]+)"/gu)];
|
|
93
|
-
return [
|
|
94
|
-
dates.reduce((latest, match) => ((match[1] ?? "") > latest ? (match[1] ?? "") : latest), ""),
|
|
95
|
-
dates.length,
|
|
96
|
-
content.length,
|
|
97
|
-
];
|
|
98
|
-
}
|
|
99
|
-
const updated = /^updated_at:\s*([^\r\n]+)/mu.exec(content)?.[1]?.trim() ?? "";
|
|
100
|
-
const created = /^created_at:\s*([^\r\n]+)/mu.exec(content)?.[1]?.trim() ?? "";
|
|
101
|
-
const revision = Number(/^revision:\s*(\d+)/mu.exec(content)?.[1] ?? 0);
|
|
102
|
-
return [updated || created, revision, content.length];
|
|
103
|
-
}
|
|
104
|
-
|
|
105
91
|
async function selectCanonicalSources(
|
|
106
92
|
roots: readonly string[],
|
|
93
|
+
cache: CanonicalSourceRevisionCache,
|
|
107
94
|
): Promise<Array<{ path: string; source: string }>> {
|
|
108
95
|
const locations = new Map<string, string[]>();
|
|
109
96
|
for (const [index, root] of roots.entries()) {
|
|
@@ -149,36 +136,37 @@ async function selectCanonicalSources(
|
|
|
149
136
|
const entries = [...locations.entries()];
|
|
150
137
|
for (let offset = 0; offset < entries.length; offset += 32) {
|
|
151
138
|
selected.push(
|
|
152
|
-
...(
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
paths.map(
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
throw new Error(`Incomplete canonical revisions for ${source}`);
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
139
|
+
...(
|
|
140
|
+
await Promise.all(
|
|
141
|
+
entries.slice(offset, offset + 32).map(async ([source, paths]) => {
|
|
142
|
+
const firstPath = paths[0];
|
|
143
|
+
if (firstPath === undefined) throw new Error(`No canonical source for ${source}`);
|
|
144
|
+
if (paths.length === 1) return { source, path: firstPath };
|
|
145
|
+
// Worktree checkout mtimes do not represent canonical record recency.
|
|
146
|
+
// Compare embedded revision timestamps and prefer the first registered
|
|
147
|
+
// source on an exact tie, so repeated scans remain deterministic.
|
|
148
|
+
const revisions = await Promise.all(paths.map((path) => cache.read(path, source)));
|
|
149
|
+
let winner = revisions.findIndex((item) => item !== null);
|
|
150
|
+
if (winner < 0) return null;
|
|
151
|
+
for (let index = 1; index < paths.length; index += 1) {
|
|
152
|
+
const left = revisions[index]?.entry.revision;
|
|
153
|
+
const right = revisions[winner]?.entry.revision;
|
|
154
|
+
if (!left) continue;
|
|
155
|
+
if (!right) throw new Error(`Incomplete canonical revisions for ${source}`);
|
|
156
|
+
if (
|
|
157
|
+
left[0] > right[0] ||
|
|
158
|
+
(left[0] === right[0] &&
|
|
159
|
+
(left[1] > right[1] || (left[1] === right[1] && left[2] > right[2])))
|
|
160
|
+
)
|
|
161
|
+
winner = index;
|
|
162
|
+
}
|
|
163
|
+
const selectedPath = paths[winner];
|
|
164
|
+
if (selectedPath === undefined)
|
|
165
|
+
throw new Error(`No selected canonical source for ${source}`);
|
|
166
|
+
return { source, path: selectedPath };
|
|
167
|
+
}),
|
|
168
|
+
)
|
|
169
|
+
).filter((item): item is { source: string; path: string } => item !== null),
|
|
182
170
|
);
|
|
183
171
|
}
|
|
184
172
|
return selected.sort((left, right) => left.source.localeCompare(right.source));
|
|
@@ -192,9 +180,16 @@ export async function sourceFingerprint(
|
|
|
192
180
|
candidates: SqliteReadModelSourceCandidate[];
|
|
193
181
|
hashedFiles: number;
|
|
194
182
|
reusedFiles: number;
|
|
183
|
+
duplicateReads: number;
|
|
184
|
+
duplicateReused: number;
|
|
195
185
|
}> {
|
|
186
|
+
const revisionCache = await CanonicalSourceRevisionCache.open(
|
|
187
|
+
workspaceRoot,
|
|
188
|
+
options.verifyContent,
|
|
189
|
+
);
|
|
196
190
|
const candidates = await selectCanonicalSources(
|
|
197
191
|
options.sourceRoots ?? (await dashboardCanonicalSourceRoots(workspaceRoot)),
|
|
192
|
+
revisionCache,
|
|
198
193
|
);
|
|
199
194
|
const candidatesWithMetadata: SqliteReadModelSourceCandidate[] = [];
|
|
200
195
|
let hashedFiles = 0;
|
|
@@ -261,6 +256,7 @@ export async function sourceFingerprint(
|
|
|
261
256
|
).filter((candidate): candidate is SqliteReadModelSourceCandidate => candidate !== null),
|
|
262
257
|
);
|
|
263
258
|
}
|
|
259
|
+
await revisionCache.persist();
|
|
264
260
|
return {
|
|
265
261
|
fingerprint: candidatesWithMetadata
|
|
266
262
|
.map(
|
|
@@ -271,6 +267,8 @@ export async function sourceFingerprint(
|
|
|
271
267
|
candidates: candidatesWithMetadata,
|
|
272
268
|
hashedFiles,
|
|
273
269
|
reusedFiles,
|
|
270
|
+
duplicateReads: revisionCache.reads,
|
|
271
|
+
duplicateReused: revisionCache.reused,
|
|
274
272
|
};
|
|
275
273
|
}
|
|
276
274
|
|
|
@@ -23,13 +23,7 @@ async function createSqliteTestRoot(): Promise<string> {
|
|
|
23
23
|
afterEach(async () => {
|
|
24
24
|
vi.useRealTimers();
|
|
25
25
|
vi.restoreAllMocks();
|
|
26
|
-
await Promise.all(
|
|
27
|
-
roots
|
|
28
|
-
.splice(0)
|
|
29
|
-
.map((root) =>
|
|
30
|
-
import("node:fs/promises").then(({ rm }) => rm(root, { recursive: true, force: true })),
|
|
31
|
-
),
|
|
32
|
-
);
|
|
26
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
33
27
|
});
|
|
34
28
|
describe("SQLite dashboard read model", () => {
|
|
35
29
|
it("materializes a fresh workspace without opening a nonexistent checkpoint database", async () => {
|
|
@@ -87,7 +81,7 @@ describe("SQLite dashboard read model", () => {
|
|
|
87
81
|
filesystem.close();
|
|
88
82
|
}
|
|
89
83
|
});
|
|
90
|
-
it("
|
|
84
|
+
it("serves an unchanged validated snapshot as a stale seed pending canonical verification", async () => {
|
|
91
85
|
const root = await createSqliteTestRoot();
|
|
92
86
|
roots.push(root);
|
|
93
87
|
await mkdir(join(root, ".agents/config"), { recursive: true });
|
|
@@ -95,7 +89,9 @@ describe("SQLite dashboard read model", () => {
|
|
|
95
89
|
vi.spyOn(Date, "now").mockReturnValue(Date.now() + 5_000);
|
|
96
90
|
await rebuildSqliteReadModel(root);
|
|
97
91
|
await expect(rebuildSqliteReadModel(root, { preferExisting: true })).resolves.toMatchObject({
|
|
98
|
-
cache: "
|
|
92
|
+
cache: "stale_seed",
|
|
93
|
+
fingerprint_ms: 0,
|
|
94
|
+
materialize_ms: 0,
|
|
99
95
|
});
|
|
100
96
|
});
|
|
101
97
|
it("rolls back a failed refresh including first-fold payloads while a reader remains open", async () => {
|
|
@@ -9,7 +9,11 @@ export type {
|
|
|
9
9
|
SqliteFirstFoldMonthlyQuick,
|
|
10
10
|
} from "./sqlite-read-model-first-fold-types.js";
|
|
11
11
|
export { rebuildSqliteReadModel } from "./sqlite-read-model-rebuild.js";
|
|
12
|
-
export {
|
|
12
|
+
export {
|
|
13
|
+
hasReusableSnapshotPayloads,
|
|
14
|
+
sqliteReadModelPath,
|
|
15
|
+
validateSqliteReadModelSchema,
|
|
16
|
+
} from "./sqlite-read-model-schema.js";
|
|
13
17
|
export {
|
|
14
18
|
type SqliteReadModelSourceCandidate,
|
|
15
19
|
sourceFingerprint,
|
|
@@ -0,0 +1,87 @@
|
|
|
1
|
+
import { mkdir, mkdtemp, rm, writeFile } from "node:fs/promises";
|
|
2
|
+
import { join, resolve } from "node:path";
|
|
3
|
+
import { DatabaseSync } from "node:sqlite";
|
|
4
|
+
import { afterEach, describe, expect, it, vi } from "vitest";
|
|
5
|
+
import { SqliteReadModelFileSystem } from "./sqlite-read-model-filesystem.js";
|
|
6
|
+
import { rebuildSqliteReadModel } from "./sqlite-read-model-rebuild.js";
|
|
7
|
+
import { validateSqliteReadModelSchema } from "./sqlite-read-model-schema.js";
|
|
8
|
+
import * as fingerprints from "./sqlite-read-model-source-fingerprint.js";
|
|
9
|
+
|
|
10
|
+
const roots: string[] = [];
|
|
11
|
+
const source = ".agents/records/tasks/TASK-STARTUP.md";
|
|
12
|
+
const record = (title: string) =>
|
|
13
|
+
`---\nid: TASK-STARTUP\ntitle: ${title}\nstatus: executing\ncreated_at: 2026-09-29T12:00:00Z\n---\n`;
|
|
14
|
+
async function fixture() {
|
|
15
|
+
const parent = resolve(import.meta.dirname, "../../../../..", ".local/sqlite/tests");
|
|
16
|
+
await mkdir(parent, { recursive: true });
|
|
17
|
+
const root = await mkdtemp(join(parent, "startup-seed-"));
|
|
18
|
+
roots.push(root);
|
|
19
|
+
await mkdir(join(root, ".agents/records/tasks"), { recursive: true });
|
|
20
|
+
await writeFile(join(root, source), record("Before"));
|
|
21
|
+
return root;
|
|
22
|
+
}
|
|
23
|
+
afterEach(async () => {
|
|
24
|
+
vi.restoreAllMocks();
|
|
25
|
+
await Promise.all(roots.splice(0).map((root) => rm(root, { recursive: true, force: true })));
|
|
26
|
+
});
|
|
27
|
+
describe("validated startup generation", () => {
|
|
28
|
+
it("serves a seed without traversing sources, then canonical refresh observes the changed record", async () => {
|
|
29
|
+
const root = await fixture();
|
|
30
|
+
await rebuildSqliteReadModel(root);
|
|
31
|
+
await writeFile(join(root, source), record("After"));
|
|
32
|
+
const fingerprint = vi.spyOn(fingerprints, "sourceFingerprint");
|
|
33
|
+
const sql = vi.spyOn(DatabaseSync.prototype, "prepare");
|
|
34
|
+
const result = await rebuildSqliteReadModel(root, {
|
|
35
|
+
preferExisting: true,
|
|
36
|
+
sourceRoots: ["invalid-startup-root"],
|
|
37
|
+
});
|
|
38
|
+
expect(result).toMatchObject({ cache: "stale_seed", fingerprint_ms: 0, materialize_ms: 0 });
|
|
39
|
+
expect(fingerprint).not.toHaveBeenCalled();
|
|
40
|
+
expect(sql).toHaveBeenCalledWith("PRAGMA quick_check");
|
|
41
|
+
expect(sql).not.toHaveBeenCalledWith("PRAGMA integrity_check");
|
|
42
|
+
const reader = new SqliteReadModelFileSystem(root);
|
|
43
|
+
try {
|
|
44
|
+
expect(await reader.readText(source)).toContain("title: Before");
|
|
45
|
+
await rebuildSqliteReadModel(root);
|
|
46
|
+
expect(sql).toHaveBeenCalledWith("PRAGMA integrity_check");
|
|
47
|
+
reader.refresh();
|
|
48
|
+
expect(await reader.readText(source)).toContain("title: After");
|
|
49
|
+
} finally {
|
|
50
|
+
reader.close();
|
|
51
|
+
}
|
|
52
|
+
});
|
|
53
|
+
|
|
54
|
+
it.each([
|
|
55
|
+
"old-schema",
|
|
56
|
+
"missing-index",
|
|
57
|
+
"missing-payload",
|
|
58
|
+
])("rebuilds %s rather than serving incomplete seed", async (damage) => {
|
|
59
|
+
const root = await fixture();
|
|
60
|
+
const first = await rebuildSqliteReadModel(root);
|
|
61
|
+
const database = new DatabaseSync(first.path);
|
|
62
|
+
try {
|
|
63
|
+
if (damage === "old-schema")
|
|
64
|
+
database.exec(
|
|
65
|
+
"UPDATE zaip_aios_activities SET payload_json=json_set(payload_json,'$.value','0') WHERE payload_type='metadata' AND json_extract(payload_json,'$.key')='schema_version'",
|
|
66
|
+
);
|
|
67
|
+
else if (damage === "missing-index")
|
|
68
|
+
database.exec("DROP INDEX zaip_activities_work_item_identity");
|
|
69
|
+
else
|
|
70
|
+
database.exec(
|
|
71
|
+
"DELETE FROM zaip_aios_activities WHERE payload_type='first_fold_all_time_board_cache'",
|
|
72
|
+
);
|
|
73
|
+
} finally {
|
|
74
|
+
database.close();
|
|
75
|
+
}
|
|
76
|
+
expect((await rebuildSqliteReadModel(root, { preferExisting: true })).cache).toBe("rebuilt");
|
|
77
|
+
expect(() => validateSqliteReadModelSchema(first.path)).not.toThrow();
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
it("rejects corrupt bytes and never serves them as a validated seed", async () => {
|
|
81
|
+
const root = await fixture();
|
|
82
|
+
const first = await rebuildSqliteReadModel(root);
|
|
83
|
+
await writeFile(first.path, "corrupt SQLite bytes");
|
|
84
|
+
expect(() => validateSqliteReadModelSchema(first.path, { integrity: "quick" })).toThrow();
|
|
85
|
+
await expect(rebuildSqliteReadModel(root, { preferExisting: true })).rejects.toThrow();
|
|
86
|
+
});
|
|
87
|
+
});
|
|
@@ -1,11 +1,15 @@
|
|
|
1
1
|
import { appendFileSync, mkdirSync } from "node:fs";
|
|
2
|
-
import {
|
|
2
|
+
import { createDashboardDiagnostics, sanitizeDiagnostic } from "./dashboard-diagnostics.mjs";
|
|
3
|
+
import { launcherLogPath, sessionDirectory, workspaceRoot } from "./start-dashboard-paths.mjs";
|
|
4
|
+
export const launcherDiagnostics = createDashboardDiagnostics(workspaceRoot, "launcher");
|
|
5
|
+
process.env.ZAIP_DASHBOARD_LAUNCH_ID = launcherDiagnostics.correlationId;
|
|
3
6
|
export function appendLauncherLog(message) {
|
|
7
|
+
launcherDiagnostics.log("launcher_message", { message });
|
|
4
8
|
try {
|
|
5
9
|
mkdirSync(sessionDirectory, { recursive: true });
|
|
6
10
|
appendFileSync(
|
|
7
11
|
launcherLogPath,
|
|
8
|
-
`[${new Date().toISOString()}] ${stripTerminalControlCodes(message)}\n`,
|
|
12
|
+
`[${new Date().toISOString()}] ${stripTerminalControlCodes(sanitizeDiagnostic(message))}\n`,
|
|
9
13
|
"utf8",
|
|
10
14
|
);
|
|
11
15
|
} catch {
|
|
@@ -55,12 +55,18 @@ export async function startDashboardServer({
|
|
|
55
55
|
ZAIP_SQLITE_PREFER_LAST_VALID_SEED: "0",
|
|
56
56
|
ZAIP_SQLITE_DB_PATH: dbPath,
|
|
57
57
|
},
|
|
58
|
-
stdio: ["ignore", "
|
|
58
|
+
stdio: ["ignore", "pipe", "pipe"],
|
|
59
59
|
windowsHide: true,
|
|
60
60
|
});
|
|
61
61
|
onChildStarted(child);
|
|
62
|
+
appendLog(`Runtime spawned: pid=${child.pid} port=${port} attempt=${attempt}`);
|
|
63
|
+
child.stdout?.setEncoding("utf8");
|
|
64
|
+
child.stdout?.on("data", (chunk) =>
|
|
65
|
+
appendLog(`runtime stdout: ${String(chunk).slice(0, 16000)}`),
|
|
66
|
+
);
|
|
62
67
|
child.stderr?.setEncoding("utf8");
|
|
63
68
|
child.stderr?.on("data", (chunk) => {
|
|
69
|
+
appendLog(`runtime stderr: ${String(chunk).slice(0, 16000)}`);
|
|
64
70
|
stderr = `${stderr}${chunk}`.slice(-4_000);
|
|
65
71
|
startupTelemetryBuffer = `${startupTelemetryBuffer}${String(chunk)}`;
|
|
66
72
|
const lines = startupTelemetryBuffer.split("\n");
|
|
@@ -3,7 +3,6 @@ import {
|
|
|
3
3
|
dashboardUrl,
|
|
4
4
|
findAvailablePortWithFallback,
|
|
5
5
|
openBrowser,
|
|
6
|
-
preferredPort,
|
|
7
6
|
} from "./start-dashboard-navigation.mjs";
|
|
8
7
|
|
|
9
8
|
export {
|
|
@@ -23,6 +22,7 @@ import {
|
|
|
23
22
|
import {
|
|
24
23
|
appendLauncherLog,
|
|
25
24
|
formatLauncherError,
|
|
25
|
+
launcherDiagnostics,
|
|
26
26
|
stripTerminalControlCodes,
|
|
27
27
|
} from "./start-dashboard-logging.mjs";
|
|
28
28
|
import {
|
|
@@ -36,7 +36,6 @@ import {
|
|
|
36
36
|
lockHeartbeatFloorMs,
|
|
37
37
|
lockPath,
|
|
38
38
|
lockTimeoutMs,
|
|
39
|
-
maxPortAttempts,
|
|
40
39
|
maxPortHistory,
|
|
41
40
|
maxServerStartAttempts,
|
|
42
41
|
nodeCompileCacheDirectory,
|
|
@@ -494,6 +493,7 @@ async function startDashboardServer(
|
|
|
494
493
|
});
|
|
495
494
|
}
|
|
496
495
|
async function main() {
|
|
496
|
+
launcherDiagnostics.log("launcher_entered");
|
|
497
497
|
let ownedChild = null;
|
|
498
498
|
let currentSession = null;
|
|
499
499
|
let launchPage = null;
|
|
@@ -741,9 +741,8 @@ async function waitForDashboard(port, child) {
|
|
|
741
741
|
await delay(100);
|
|
742
742
|
}
|
|
743
743
|
}
|
|
744
|
-
|
|
745
|
-
|
|
746
|
-
}
|
|
744
|
+
const delay = (milliseconds) =>
|
|
745
|
+
new Promise((resolveDelay) => setTimeout(resolveDelay, milliseconds));
|
|
747
746
|
const isMain = process.argv[1]
|
|
748
747
|
? resolve(process.argv[1]) === fileURLToPath(import.meta.url)
|
|
749
748
|
: false;
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import { readFileSync } from "node:fs";
|
|
2
|
+
import { createElement } from "react";
|
|
3
|
+
import { renderToStaticMarkup } from "react-dom/server";
|
|
2
4
|
import { describe, expect, it } from "vitest";
|
|
5
|
+
import { DashboardHeader } from "./DashboardHeader.tsx";
|
|
3
6
|
|
|
4
7
|
const styles = readFileSync(new URL("ui.css", import.meta.url), "utf8");
|
|
5
|
-
const headerSource = readFileSync(new URL("DashboardHeader.tsx", import.meta.url), "utf8");
|
|
6
8
|
|
|
7
9
|
describe("DashboardHeader", () => {
|
|
8
10
|
it("optically balances the repository badge horizontal spacing", () => {
|
|
@@ -10,12 +12,39 @@ describe("DashboardHeader", () => {
|
|
|
10
12
|
expect(rule).toContain("padding-block: 0.3rem");
|
|
11
13
|
expect(rule).toContain("padding-inline: 0.6rem 0.72rem");
|
|
12
14
|
});
|
|
13
|
-
it("
|
|
14
|
-
|
|
15
|
-
|
|
15
|
+
it.each(["pt-BR", "en-US"])("shows only snapshot freshness in %s", (locale) => {
|
|
16
|
+
const telemetry = "2026-09-29T11:32:27Z";
|
|
17
|
+
const snapshot = "2026-09-29T12:59:35Z";
|
|
18
|
+
const markup = renderHeader({ locale, lastUpdatedAt: telemetry, snapshotUpdatedAt: snapshot });
|
|
19
|
+
const formatted = new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "medium" });
|
|
20
|
+
expect(markup.match(/<small>/g)).toHaveLength(1);
|
|
21
|
+
expect(markup).toContain(locale === "pt-BR" ? "Última Atualização" : "Last Updated");
|
|
22
|
+
expect(markup).toContain(formatted.format(new Date(snapshot)));
|
|
23
|
+
expect(markup).not.toContain(formatted.format(new Date(telemetry)));
|
|
24
|
+
});
|
|
25
|
+
it("keeps an unavailable snapshot explicit even when telemetry exists", () => {
|
|
26
|
+
const markup = renderHeader({ lastUpdatedAt: "2026-09-29T11:32:27Z" });
|
|
27
|
+
expect(markup.match(/<small>/g)).toHaveLength(1);
|
|
28
|
+
expect(markup).toContain("Atualização indisponível");
|
|
29
|
+
expect(markup).not.toContain("11:32:27");
|
|
16
30
|
});
|
|
17
31
|
});
|
|
18
32
|
|
|
33
|
+
function renderHeader(props) {
|
|
34
|
+
return renderToStaticMarkup(
|
|
35
|
+
createElement(DashboardHeader, {
|
|
36
|
+
filters: null,
|
|
37
|
+
lastUpdatedAt: null,
|
|
38
|
+
loading: false,
|
|
39
|
+
locale: "pt-BR",
|
|
40
|
+
onLocaleChange() {},
|
|
41
|
+
onRefresh() {},
|
|
42
|
+
title: "Dashboard ZAIP AIOS",
|
|
43
|
+
...props,
|
|
44
|
+
}),
|
|
45
|
+
);
|
|
46
|
+
}
|
|
47
|
+
|
|
19
48
|
function cssRule(source, selector) {
|
|
20
49
|
const match = [...source.matchAll(/([^{}]+)\{([^}]*)\}/g)].find((candidate) =>
|
|
21
50
|
candidate[1]?.split(",").some((item) => item.trim() === selector),
|
|
@@ -7,7 +7,7 @@ const zaipLogoUrl = new URL("../../../../config/src/logo_novo_azul.png", import.
|
|
|
7
7
|
export function DashboardHeader({
|
|
8
8
|
filters,
|
|
9
9
|
filtersActive = false,
|
|
10
|
-
|
|
10
|
+
snapshotUpdatedAt = null,
|
|
11
11
|
loading,
|
|
12
12
|
locale,
|
|
13
13
|
onLocaleChange,
|
|
@@ -19,6 +19,7 @@ export function DashboardHeader({
|
|
|
19
19
|
filters: ReactNode;
|
|
20
20
|
filtersActive?: boolean;
|
|
21
21
|
lastUpdatedAt: string | null;
|
|
22
|
+
snapshotUpdatedAt?: string | null;
|
|
22
23
|
loading: boolean;
|
|
23
24
|
locale: DashboardHeaderLocale;
|
|
24
25
|
onLocaleChange: (locale: DashboardHeaderLocale) => void;
|
|
@@ -38,8 +39,8 @@ export function DashboardHeader({
|
|
|
38
39
|
refresh: "Atualizar",
|
|
39
40
|
refreshing: "Atualizando…",
|
|
40
41
|
settings: "Configurações",
|
|
41
|
-
updated: "Última
|
|
42
|
-
waiting: "
|
|
42
|
+
updated: "Última Atualização",
|
|
43
|
+
waiting: "Atualização indisponível",
|
|
43
44
|
}
|
|
44
45
|
: {
|
|
45
46
|
filters: "Filters",
|
|
@@ -47,13 +48,13 @@ export function DashboardHeader({
|
|
|
47
48
|
refresh: "Refresh",
|
|
48
49
|
refreshing: "Refreshing…",
|
|
49
50
|
settings: "Settings",
|
|
50
|
-
updated: "Last
|
|
51
|
-
waiting: "
|
|
51
|
+
updated: "Last Updated",
|
|
52
|
+
waiting: "Update unavailable",
|
|
52
53
|
};
|
|
53
54
|
const updatedLabel =
|
|
54
|
-
|
|
55
|
+
snapshotUpdatedAt === null
|
|
55
56
|
? copy.waiting
|
|
56
|
-
: `${copy.updated} ${new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "medium" }).format(new Date(
|
|
57
|
+
: `${copy.updated} ${new Intl.DateTimeFormat(locale, { dateStyle: "short", timeStyle: "medium" }).format(new Date(snapshotUpdatedAt))}`;
|
|
57
58
|
const changeLocale = (next: DashboardHeaderLocale) => {
|
|
58
59
|
onLocaleChange(next);
|
|
59
60
|
setLocaleOpen(false);
|