mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-06 15:22:12 +03:00
* feat(compression): dependência omniglyph (file:) + smoke de import * feat(compression): engine omniglyph — contexto-como-imagem com gates fail-closed * fix(compression): omniglyph adapter fail-open no transform (try/catch) * feat(compression): registra omniglyph no registry e catálogo (single mode, stackPriority 90) * feat(compression): modo único omniglyph (async), selecionar o modo é o enable * feat(compression): plumbing supportsVision + providerTransport até os engines * feat(compression): estimador de tokens image-aware — modo stacked mantém a saída do omniglyph * docs(compression): corrige comentário do prefixo base64 no decode PNG (64 chars) * feat(compression): registra omniglyph nas listas de modo/engine (db, combo, deriveDefaultPlan, mcp) * feat(dashboard): dedicated OmniGlyph engine screen (context-as-image) Adds a per-engine detail page at /dashboard/context/omniglyph, alongside the other compression engines in the sidebar. Four sections: the economics (measured savings), a REAL before→after (dense text vs the rendered PNG page, not a mockup), the fail-closed gate flow, and the enable control wired to /api/settings/compression (preview engine, off by default). Sidebar entry + i18n label across all locales. * chore(compression): consume published omniglyph@^1.0.0 from the npm registry Replaces the local file: dependency used during the preview phase — npm ci now resolves omniglyph from the registry with integrity, unblocking CI. * fix(compression): satisfy v3.8.47 quality gates for the omniglyph engine - dependency-allowlist: approve omniglyph (own package, published from diegosouzapw/OmniGlyph; supply-chain review done by the maintainer) - ladder maps (#6533 guard): rank omniglyph 80 (stackPriority 90, runs after every text engine) with expectedReductionFactor 0.35 (measured 0.23-0.33) - drop the two explicit any casts in omniglyph tests (no-explicit-any is error-level in tests since #6218) * chore(compression): rebaseline strategySelector for the omniglyph mode dispatch +18 lines of cohesive dispatch/type wiring at the existing mode chokepoints (sync no-op + async single-mode branch + providerTransport on the options types) — not extractable without hiding the dispatch boundary, mirroring the prior compression rebaselines. Also drops an unused eslint-disable directive in image-aware-tokens.test.ts (warning-level red under --max-warnings 0). * chore(quality): register inherited base tests in stryker tap.testFiles masked-200-exhaustion-fallback-6427 and headroom-codex-quota-snapshot-6379 arrived via the base merge without their stryker registration — check:mutation-test-coverage --strict requires covering tests to be listed. * refactor(compression): keep omniglyph wiring under the complexity gate - extract the async single-mode resolution to engines/omniglyphSingleMode.ts (runCompressionAsync was at complexity 17 after the mode branch; back <=15) - split OmniglyphContextPageClient into section components (was 161 lines in one function; every function now under the 80-line cap) - complexity baseline 2052->2053: the +1 is inherited base drift (the ratchet does not run on fast-path merges — same pattern as the v3.8.44/46 rebaselines); this PR's own code is measured complexity-net-zero * chore(quality): register 3 more inherited base tests in stryker tap.testFiles route-guard-middleware-local-only, combo-diagnostics-trace and idempotency-fusion-collision arrived via the latest base merge without their stryker registration (fast-path merges skip check:mutation-test-coverage). * chore(quality): cognitive-complexity baseline 883->884 (inherited base drift) check:cognitive-complexity measures 884 identically on the pristine origin/release/v3.8.47 tip and on this HEAD — the PR itself is cognitive-net-zero (single-mode resolution extracted to its own module, page client split into section components). Same inherited-drift pattern as the v3.8.4x release rebaselines. --------- Co-authored-by: diegosouzapw <diegosouzapw@devbox.local>
142 lines
5.2 KiB
TypeScript
142 lines
5.2 KiB
TypeScript
// @vitest-environment jsdom
|
||
import React, { act } from "react";
|
||
import { createRoot } from "react-dom/client";
|
||
import { describe, it, expect, beforeEach, afterEach, vi } from "vitest";
|
||
import { SAMPLE_METRICS } from "../../../src/app/(dashboard)/dashboard/context/omniglyph/sampleData.ts";
|
||
|
||
// i18n is not resolved in vitest/jsdom; the page hardcodes its engine copy in English
|
||
// anyway (catalog convention), so no next-intl mock is needed. Asserts are on
|
||
// i18n-independent content: headings, measured numbers, gate labels, data-testid hooks.
|
||
|
||
// ── Harness (mirrors tests/unit/ui/compressionPanel.test.tsx) ──────────────────
|
||
const containers: HTMLElement[] = [];
|
||
const roots: Array<{ unmount: () => void }> = [];
|
||
|
||
function mount(ui: React.ReactElement): HTMLElement {
|
||
const container = document.createElement("div");
|
||
document.body.appendChild(container);
|
||
containers.push(container);
|
||
const root = createRoot(container);
|
||
roots.push(root);
|
||
act(() => {
|
||
root.render(ui);
|
||
});
|
||
return container;
|
||
}
|
||
|
||
beforeEach(() => {
|
||
(
|
||
globalThis as typeof globalThis & { IS_REACT_ACT_ENVIRONMENT?: boolean }
|
||
).IS_REACT_ACT_ENVIRONMENT = true;
|
||
});
|
||
|
||
afterEach(async () => {
|
||
vi.restoreAllMocks();
|
||
await act(async () => {
|
||
while (roots.length > 0) roots.pop()?.unmount();
|
||
});
|
||
for (let i = 0; i < 10; i++) await Promise.resolve();
|
||
while (containers.length > 0) containers.pop()?.remove();
|
||
document.body.innerHTML = "";
|
||
});
|
||
|
||
async function flush() {
|
||
await act(async () => {
|
||
for (let i = 0; i < 10; i++) await Promise.resolve();
|
||
});
|
||
}
|
||
|
||
interface CapturedPut {
|
||
url: string;
|
||
body: Record<string, unknown>;
|
||
}
|
||
|
||
function setupFetchMock(): { puts: CapturedPut[] } {
|
||
const puts: CapturedPut[] = [];
|
||
const json = (body: unknown, status = 200) =>
|
||
new Response(JSON.stringify(body), { status, headers: { "Content-Type": "application/json" } });
|
||
|
||
// omniglyph absent from `engines` initially → disabled; rtk/caveman present so we can
|
||
// assert they SURVIVE the PUT (the store persists the whole map as one row).
|
||
const initialConfig = {
|
||
enabled: true,
|
||
engines: { rtk: { enabled: true, level: "standard" }, caveman: { enabled: false } },
|
||
};
|
||
|
||
vi.spyOn(globalThis, "fetch").mockImplementation(
|
||
async (input: RequestInfo | URL, init?: RequestInit) => {
|
||
const url = input.toString();
|
||
const method = (init?.method ?? "GET").toUpperCase();
|
||
if (url.includes("/api/settings/compression")) {
|
||
if (method === "PUT") {
|
||
const body = JSON.parse(String(init?.body ?? "{}"));
|
||
puts.push({ url, body });
|
||
return json({ ...initialConfig, ...body });
|
||
}
|
||
return json(initialConfig);
|
||
}
|
||
return json({}, 404);
|
||
}
|
||
);
|
||
return { puts };
|
||
}
|
||
|
||
// ── Tests ──────────────────────────────────────────────────────────────────────
|
||
describe("OmniglyphContextPage", () => {
|
||
it("renders the four sections with the measured numbers and the real render", async () => {
|
||
setupFetchMock();
|
||
const { default: Page } = await import(
|
||
"../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient"
|
||
);
|
||
let container!: HTMLElement;
|
||
await act(async () => {
|
||
container = mount(<Page />);
|
||
});
|
||
await flush();
|
||
|
||
const text = container.textContent ?? "";
|
||
// Header + preview status
|
||
expect(text).toContain("OmniGlyph");
|
||
expect(text).toContain("Preview");
|
||
// Economics
|
||
expect(text).toContain("~10×");
|
||
expect(text).toContain("59–70%");
|
||
// Before → after: measured savings + the real rendered image
|
||
expect(text).toContain(`−${SAMPLE_METRICS.savingsPct}% tokens on this block`);
|
||
const img = container.querySelector("img");
|
||
expect(img, "the rendered sample page <img> must be present").toBeTruthy();
|
||
expect(img!.getAttribute("src")).toMatch(/^data:image\/png;base64,/);
|
||
// Gates
|
||
expect(text).toContain("claude-fable-5");
|
||
expect(text).toContain("direct Anthropic");
|
||
// Config control
|
||
expect(container.querySelector('[data-testid="omniglyph-enable-toggle"]')).toBeTruthy();
|
||
});
|
||
|
||
it("enabling the engine PUTs the full engines map with omniglyph on, preserving the others", async () => {
|
||
const { puts } = setupFetchMock();
|
||
const { default: Page } = await import(
|
||
"../../../src/app/(dashboard)/dashboard/context/omniglyph/OmniglyphContextPageClient"
|
||
);
|
||
let container!: HTMLElement;
|
||
await act(async () => {
|
||
container = mount(<Page />);
|
||
});
|
||
await flush();
|
||
|
||
const toggle = container.querySelector('[data-testid="omniglyph-enable-toggle"] button') as HTMLButtonElement | null;
|
||
expect(toggle, "enable toggle button must exist").toBeTruthy();
|
||
await act(async () => {
|
||
toggle!.click();
|
||
});
|
||
await flush();
|
||
|
||
expect(puts.length).toBe(1);
|
||
const engines = puts[0]!.body.engines as Record<string, { enabled: boolean }>;
|
||
expect(engines.omniglyph).toEqual({ enabled: true });
|
||
// The other engines must survive the whole-map PUT.
|
||
expect(engines.rtk?.enabled).toBe(true);
|
||
expect(engines.caveman?.enabled).toBe(false);
|
||
});
|
||
});
|