mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-15 11:22:15 +03:00
* feat(dashboard): pure model to compare two orchestration runs * feat(dashboard): compare-mode selection in the History grid * feat(dashboard): side-by-side comparison panel in the History tab (2.9) * chore(dashboard): compare-runs i18n + changelog * fix(dashboard): compare-panel loading state, height bound, delta legend, ARIA level Final-review fix wave for PR-A (Orchestration Canvas Fase 3): - Give each compare-panel side an explicit fetch status (loading/ok/error) so the Events metrics row shows "—" instead of a misleading real "0"/delta while a side is still loading or after its fetch failed. - Bound the compare panel's height (max-h-[45vh], overflow-y-auto, shrink-0) so a run with many activities can no longer collapse the History grid to zero height. - Clear the compare selection when the History preset changes, since a stale pick can fall outside the new range. - Add a delta-column legend (new compareDeltaLegend i18n key, translated into all 41 non-English locales) so operators know which side a positive delta favors. - Use role="status" (not role="alert") for the informational compareDifferentIdentity banner, reserving role="alert" for actual per-side fetch failures. - Restore ro.json's compareCost to the true cognate "Cost" (was distorted to "Cheltuieli" to dodge a byte-identical-to-English heuristic); audited the other 40 locales for the same pattern across the 9 compare* keys, no other instance found. * refactor(dashboard): split compare-runs/history-tab functions to clear complexity ratchets CompareRunsPanel (92 lines, max-lines-per-function) and HistoryTab (85 lines, same rule) exceeded the 80-line function cap; compareRuns.ts's a2aEventsFrom exceeded the cognitive-complexity cap (16 > 15). Extract pure/presentational helpers (ComparePanelHeaderBar, SideErrorRow, ComparisonMetrics, a2aEventFrom, HistoryStatusRows, refreshNowMsOnActionDone) with no behavior, DOM, i18n or aria change. --------- Co-authored-by: diegosouzapw <diegosouzapw@users.noreply.github.com>
99 lines
3.2 KiB
TypeScript
99 lines
3.2 KiB
TypeScript
/**
|
|
* tests/unit/ui/orchestrationCompare.test.ts
|
|
* Pure model tests for comparing two Orchestration Canvas history runs (Task A1, PR-A).
|
|
* Run: node --import tsx/esm --test tests/unit/ui/orchestrationCompare.test.ts
|
|
*/
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
buildComparison,
|
|
normalizeRunSide,
|
|
} from "../../../src/app/(dashboard)/dashboard/orchestration/model/compareRuns.ts";
|
|
|
|
const a2aItem = {
|
|
id: "a2a:1",
|
|
source: "a2a" as const,
|
|
identity: "smart-routing",
|
|
state: "succeeded" as const,
|
|
label: "smart-routing",
|
|
createdAt: "2026-09-01T10:00:00.000Z",
|
|
completedAt: "2026-09-01T10:00:30.000Z",
|
|
durationMs: 30000,
|
|
cost: null,
|
|
raw: {},
|
|
};
|
|
const caItem = {
|
|
id: "cloud-agent:9",
|
|
source: "cloud-agent" as const,
|
|
identity: "jules",
|
|
state: "succeeded" as const,
|
|
label: "fix login",
|
|
createdAt: "2026-09-01T10:00:00.000Z",
|
|
completedAt: "2026-09-01T10:00:10.000Z",
|
|
durationMs: 10000,
|
|
cost: 0.5,
|
|
raw: {},
|
|
};
|
|
|
|
test("normalizeRunSide reads a2a events from the historical task shape", () => {
|
|
const side = normalizeRunSide(a2aItem, {
|
|
events: [
|
|
{ timestamp: "2026-09-01T10:00:00.000Z", state: "submitted" },
|
|
{ timestamp: "2026-09-01T10:00:05.000Z", state: "working", message: "started" },
|
|
],
|
|
});
|
|
assert.deepEqual(side.events, [
|
|
{ label: "submitted", timestamp: "2026-09-01T10:00:00.000Z" },
|
|
{ label: "started", timestamp: "2026-09-01T10:00:05.000Z" },
|
|
]);
|
|
});
|
|
|
|
test("normalizeRunSide reads cloud-agent activities", () => {
|
|
const side = normalizeRunSide(caItem, {
|
|
activities: [
|
|
{ id: "a", type: "log", content: "cloning", timestamp: "2026-09-01T10:00:01.000Z" },
|
|
],
|
|
});
|
|
assert.deepEqual(side.events, [{ label: "cloning", timestamp: "2026-09-01T10:00:01.000Z" }]);
|
|
});
|
|
|
|
test("normalizeRunSide never throws on malformed detail", () => {
|
|
for (const bad of [null, undefined, "boom", 42, { events: "nope" }, { events: [null, 7, {}] }]) {
|
|
const side = normalizeRunSide(a2aItem, bad);
|
|
assert.deepEqual(side.events, []);
|
|
assert.deepEqual(side.memoryHits, []);
|
|
}
|
|
});
|
|
|
|
test("normalizeRunSide keeps only well-formed memoryHits", () => {
|
|
const side = normalizeRunSide(a2aItem, {
|
|
events: [],
|
|
metadata: {
|
|
memoryHits: [
|
|
{ id: "m1", key: "k", type: "factual", snippet: "s" },
|
|
{ id: "m2", key: { a: 1 }, type: "factual", snippet: "s" },
|
|
"nope",
|
|
],
|
|
},
|
|
});
|
|
assert.equal(side.memoryHits.length, 1);
|
|
assert.equal(side.memoryHits[0].id, "m1");
|
|
});
|
|
|
|
test("buildComparison computes signed deltas and sameIdentity", () => {
|
|
const left = normalizeRunSide(a2aItem, { events: [{ timestamp: null, state: "submitted" }] });
|
|
const right = normalizeRunSide({ ...a2aItem, id: "a2a:2", durationMs: 45000 }, { events: [] });
|
|
const cmp = buildComparison(left, right);
|
|
assert.equal(cmp.deltas.durationMs, 15000);
|
|
assert.equal(cmp.deltas.eventCount, -1);
|
|
assert.equal(cmp.deltas.sameIdentity, true);
|
|
});
|
|
|
|
test("buildComparison returns null delta when either side lacks the value", () => {
|
|
const left = normalizeRunSide(a2aItem, {}); // cost null
|
|
const right = normalizeRunSide(caItem, {}); // cost 0.5
|
|
const cmp = buildComparison(left, right);
|
|
assert.equal(cmp.deltas.cost, null);
|
|
assert.equal(cmp.deltas.sameIdentity, false);
|
|
});
|