mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-07 23:42:10 +03:00
Compare commits
1 Commits
fix/releas
...
fix/releas
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
514d3c39d7 |
@@ -1 +0,0 @@
|
||||
- **fix(dashboard):** The Combos page usage guide now reads its dismissal through `useSyncExternalStore` instead of correcting SSR state inside an effect, removing an extra commit of the page tree on every load (and the `react-hooks/set-state-in-effect` error it raised).
|
||||
@@ -854,6 +854,9 @@
|
||||
"src/app/(dashboard)/dashboard/combos/page.tsx": {
|
||||
"@typescript-eslint/no-unused-vars": {
|
||||
"count": 6
|
||||
},
|
||||
"react-hooks/set-state-in-effect": {
|
||||
"count": 1
|
||||
}
|
||||
},
|
||||
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": {
|
||||
|
||||
@@ -434,7 +434,7 @@
|
||||
"open-sse/vendor/codex-chatgpt-web/bridge.ts": 1335,
|
||||
"src/app/(dashboard)/dashboard/HomePageClient.tsx": 1344,
|
||||
"src/app/(dashboard)/dashboard/api-manager/ApiManagerPageClient.tsx": 3186,
|
||||
"src/app/(dashboard)/dashboard/combos/page.tsx": 5066,
|
||||
"src/app/(dashboard)/dashboard/combos/page.tsx": 5018,
|
||||
"src/app/(dashboard)/dashboard/costs/CostOverviewTab.tsx": 1319,
|
||||
"src/app/(dashboard)/dashboard/endpoint/EndpointPageClient.tsx": 2491,
|
||||
"src/app/(dashboard)/dashboard/providers/[id]/components/modals/EditConnectionModal.tsx": 1631,
|
||||
@@ -643,6 +643,5 @@
|
||||
"_rebaseline_2026_09_03_error_boundary_campaign": "Campanha de error-boundary (#12431 #12438 #12444 #12454 #12455 #12456 #12457 #12458 #12459 #12465 #12466 #12467 #12469 #12435), medido no tip com os 14 mergeados. open-sse/executors/codex.ts 1499->1505: os primeiros 4 (1499->1503) sao DRIFT ANTERIOR a esta campanha, ja presente no tip antes dela; os 2 ultimos (1503->1505) sao do #12444, que fecha o boundary de falha da resposta do Codex. Absorver o drift junto foi inevitavel porque o cap e um numero so, mas fica registrado aqui que 4 das 6 linhas nao sao desta leva. open-sse/vendor/codex-chatgpt-web/bridge.ts 1322->1335 (+13): tambem do #12444, no mesmo caminho de falha. NAO cobre open-sse/utils/stream.ts, que segue violando por drift anterior e independente.",
|
||||
"_rebaseline_2026_09_03_12352_apikey_acl": "PR #12352 (fix/api-key-create-acl-12275) crescimento proprio: src/lib/db/apiKeys.ts 1610->1625 (+15). A criacao de API key descartava a ACL enviada no payload; preservar essa ACL exige carregar e persistir o conjunto no mesmo chokepoint de INSERT do modulo de dominio, sem extracao possivel sem partir a funcao de criacao ao meio. Coberto pelos testes do proprio PR (54/54 focados na leva).",
|
||||
"_rebaseline_2026_09_03_houminxi_combo_stacked": "Leva HouMinXi (#12624 #12626 #12632 #12637): open-sse/services/combo.ts 4075->4080 (+5), medido no tip com os quatro mergeados. Cada PR registrou o proprio crescimento contra o tip de onde forkou (o #12637 ja subira o cap para 4075); as 5 linhas restantes so aparecem quando eles empilham, porque mais de um toca o mesmo chokepoint de scoring reset-aware em combo.ts. Fiacao em ponto existente, sem extracao possivel sem partir a funcao de selecao de alvos. Coberto por combo-strategies e reset-aware-request-scope-12600 (119/119 focados na leva).",
|
||||
"_rebaseline_2026_09_04_12641_continuation_effective_input": "PR #12641 crescimento proprio: src/sse/handlers/chat.ts 2450->2454 (+4). A continuacao por previous_response_id encadeava a partir de clientRawRequest.body.input, que e capturado ANTES da reconstrucao do proprio chat.ts; quando o turno anterior ja era uma continuacao, esse campo guarda so o delta do cliente, e o erro se acumulava a cada salto ate a reconstrucao virar itens de tool sem prefixo. Persistir o input EFETIVO exige as linhas no ponto onde a reconstrucao termina, dentro do fluxo de despacho. Coberto por tests/unit/responses-continuation-store.test.ts (22/22 focados na leva).",
|
||||
"_rebaseline_2026_09_05_12671_combos_usage_guide_external_store": "combos/page.tsx 5018 -> 5066: #12671 replaces the effect-based localStorage read with useSyncExternalStore; the +48 lines are the store helpers (subscribe/getSnapshot/getServerSnapshot/emit) hoisted to module scope, which is the sanctioned shape and what let the react-hooks/set-state-in-effect suppression be dropped."
|
||||
"_rebaseline_2026_09_04_12641_continuation_effective_input": "PR #12641 crescimento proprio: src/sse/handlers/chat.ts 2450->2454 (+4). A continuacao por previous_response_id encadeava a partir de clientRawRequest.body.input, que e capturado ANTES da reconstrucao do proprio chat.ts; quando o turno anterior ja era uma continuacao, esse campo guarda so o delta do cliente, e o erro se acumulava a cada salto ate a reconstrucao virar itens de tool sem prefixo. Persistir o input EFETIVO exige as linhas no ponto onde a reconstrucao termina, dentro do fluxo de despacho. Coberto por tests/unit/responses-continuation-store.test.ts (22/22 focados na leva)."
|
||||
}
|
||||
|
||||
@@ -115,12 +115,14 @@ const ALWAYS_PROTECTED_PATTERNS = parsePatterns("ALWAYS_PROTECTED_API_PATTERNS")
|
||||
if (
|
||||
LOCAL_ONLY_PREFIXES.length === 0 ||
|
||||
LOCAL_ONLY_PATTERNS.length === 0 ||
|
||||
ALWAYS_PROTECTED_PATHS.length === 0
|
||||
ALWAYS_PROTECTED_PATHS.length === 0 ||
|
||||
ALWAYS_PROTECTED_PATTERNS.length === 0
|
||||
) {
|
||||
console.error(
|
||||
`[openapi-security-tiers] FAIL — could not parse routeGuard.ts constants ` +
|
||||
`(prefixes=${LOCAL_ONLY_PREFIXES.length}, patterns=${LOCAL_ONLY_PATTERNS.length}, ` +
|
||||
`alwaysProtected=${ALWAYS_PROTECTED_PATHS.length})`
|
||||
`alwaysProtected=${ALWAYS_PROTECTED_PATHS.length}, ` +
|
||||
`alwaysProtectedPatterns=${ALWAYS_PROTECTED_PATTERNS.length})`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
|
||||
@@ -1,15 +1,6 @@
|
||||
"use client";
|
||||
|
||||
import {
|
||||
useState,
|
||||
useEffect,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
useSyncExternalStore,
|
||||
memo,
|
||||
Suspense,
|
||||
} from "react";
|
||||
import { useState, useEffect, useCallback, useMemo, useRef, memo, Suspense } from "react";
|
||||
import dynamic from "next/dynamic";
|
||||
import Link from "next/link";
|
||||
import { useRouter, useSearchParams } from "next/navigation";
|
||||
@@ -397,42 +388,6 @@ const STRATEGY_RECOMMENDATIONS_FALLBACK = {
|
||||
|
||||
const COMBO_USAGE_GUIDE_STORAGE_KEY = "omniroute:combos:hide-usage-guide";
|
||||
|
||||
// The dismissal lives in localStorage, which SSR cannot read: a lazy useState
|
||||
// initializer would render "not dismissed" on the server and the real value on
|
||||
// the client, and correcting that in an effect is a synchronous setState inside
|
||||
// an effect (react-hooks/set-state-in-effect) that costs an extra commit of this
|
||||
// whole tree. useSyncExternalStore is the sanctioned shape for exactly this —
|
||||
// getServerSnapshot supplies the SSR-safe default, getSnapshot reads the store
|
||||
// after hydration, and the two handlers below notify subscribers instead of
|
||||
// setting state. The `storage` listener keeps other tabs in sync for free.
|
||||
const usageGuideListeners = new Set<() => void>();
|
||||
|
||||
function subscribeUsageGuide(onStoreChange: () => void): () => void {
|
||||
usageGuideListeners.add(onStoreChange);
|
||||
globalThis.addEventListener?.("storage", onStoreChange);
|
||||
return () => {
|
||||
usageGuideListeners.delete(onStoreChange);
|
||||
globalThis.removeEventListener?.("storage", onStoreChange);
|
||||
};
|
||||
}
|
||||
|
||||
function emitUsageGuideChange(): void {
|
||||
for (const listener of usageGuideListeners) listener();
|
||||
}
|
||||
|
||||
function getUsageGuideSnapshot(): boolean {
|
||||
try {
|
||||
return globalThis.localStorage?.getItem(COMBO_USAGE_GUIDE_STORAGE_KEY) !== "1";
|
||||
} catch {
|
||||
// Storage access errors (privacy mode / restricted environments) show the guide.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
function getUsageGuideServerSnapshot(): boolean {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Pure predicate hoisted out of the page component to keep its cyclomatic budget flat
|
||||
// (check:complexity new-code mode).
|
||||
function isStaleIntelligentSelection(
|
||||
@@ -811,18 +766,16 @@ function CombosPageContent() {
|
||||
// real stored value -- exactly the kind of source React's hydration
|
||||
// mismatch check is built to catch, and in dev mode a mismatch forces a
|
||||
// full client-only re-render of this tree, discarding whatever the fetch
|
||||
// effects below had already populated. useSyncExternalStore renders the
|
||||
// SSR-safe default on both passes and switches to the stored value at
|
||||
// hydration, without a second commit — see the store helpers above.
|
||||
const usageGuideNotDismissed = useSyncExternalStore(
|
||||
subscribeUsageGuide,
|
||||
getUsageGuideSnapshot,
|
||||
getUsageGuideServerSnapshot
|
||||
);
|
||||
// "Hide" (as opposed to "hide forever") is intentionally per-mount: it is not
|
||||
// persisted, and remounting the page brings the guide back — same as before.
|
||||
const [usageGuideHiddenForNow, setUsageGuideHiddenForNow] = useState(false);
|
||||
const showUsageGuide = usageGuideNotDismissed && !usageGuideHiddenForNow;
|
||||
// effects below had already populated. Start with the SSR-safe default on
|
||||
// both passes and correct it client-only, after hydration, in an effect.
|
||||
const [showUsageGuide, setShowUsageGuide] = useState(true);
|
||||
useEffect(() => {
|
||||
try {
|
||||
setShowUsageGuide(globalThis.localStorage?.getItem(COMBO_USAGE_GUIDE_STORAGE_KEY) !== "1");
|
||||
} catch {
|
||||
// Ignore storage access errors (privacy mode / restricted environments)
|
||||
}
|
||||
}, []);
|
||||
const [recentlyCreatedCombo, setRecentlyCreatedCombo] = useState("");
|
||||
const [creatingKimiPreset, setCreatingKimiPreset] = useState(false);
|
||||
const [comboDragIndex, setComboDragIndex] = useState(null);
|
||||
@@ -1053,18 +1006,17 @@ function CombosPageContent() {
|
||||
};
|
||||
|
||||
const handleHideUsageGuideForever = () => {
|
||||
setShowUsageGuide(false);
|
||||
try {
|
||||
globalThis.localStorage?.setItem(COMBO_USAGE_GUIDE_STORAGE_KEY, "1");
|
||||
} catch {}
|
||||
emitUsageGuideChange();
|
||||
};
|
||||
|
||||
const handleShowUsageGuide = () => {
|
||||
setShowUsageGuide(true);
|
||||
try {
|
||||
globalThis.localStorage?.removeItem(COMBO_USAGE_GUIDE_STORAGE_KEY);
|
||||
} catch {}
|
||||
setUsageGuideHiddenForNow(false);
|
||||
emitUsageGuideChange();
|
||||
};
|
||||
|
||||
const handleFilterChange = (nextFilter) => {
|
||||
@@ -1197,7 +1149,7 @@ function CombosPageContent() {
|
||||
|
||||
{showUsageGuide && (
|
||||
<ComboUsageGuide
|
||||
onHide={() => setUsageGuideHiddenForNow(true)}
|
||||
onHide={() => setShowUsageGuide(false)}
|
||||
onHideForever={handleHideUsageGuideForever}
|
||||
onCreateCombo={() => setShowCreateModal(true)}
|
||||
/>
|
||||
|
||||
38
tests/unit/openapi-security-tiers-gate.test.ts
Normal file
38
tests/unit/openapi-security-tiers-gate.test.ts
Normal file
@@ -0,0 +1,38 @@
|
||||
import test from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { execFileSync } from "node:child_process";
|
||||
import { fileURLToPath } from "node:url";
|
||||
import { dirname, join } from "node:path";
|
||||
|
||||
const ROOT = join(dirname(fileURLToPath(import.meta.url)), "..", "..");
|
||||
const GATE = join(ROOT, "scripts", "check", "check-openapi-security-tiers.mjs");
|
||||
|
||||
function runGate(): { code: number; out: string } {
|
||||
try {
|
||||
const out = execFileSync(process.execPath, [GATE], {
|
||||
cwd: ROOT,
|
||||
encoding: "utf8",
|
||||
stdio: ["ignore", "pipe", "pipe"],
|
||||
});
|
||||
return { code: 0, out };
|
||||
} catch (err) {
|
||||
const e = err as { status?: number; stdout?: string; stderr?: string };
|
||||
return { code: e.status ?? 1, out: `${e.stdout ?? ""}${e.stderr ?? ""}` };
|
||||
}
|
||||
}
|
||||
|
||||
// routeGuard protects a path when EITHER list matches — `isAlwaysProtectedPath`
|
||||
// ORs ALWAYS_PROTECTED_API_PATHS with ALWAYS_PROTECTED_API_PATTERNS. The gate
|
||||
// used to read only the prefix array, so every regex-covered route was reported
|
||||
// as an annotation mismatch: the four `{claude,codex}-auth/{export,apply-local}`
|
||||
// routes turned release/v3.8.51 red while being correctly protected at runtime.
|
||||
// Same defect class the LOCAL_ONLY arm already had (#12350).
|
||||
test("openapi-security-tiers accepts routes covered only by ALWAYS_PROTECTED_API_PATTERNS", () => {
|
||||
const { code, out } = runGate();
|
||||
|
||||
assert.ok(
|
||||
!/has x-always-protected but is NOT/.test(out),
|
||||
`gate reported an always-protected route as uncovered:\n${out}`
|
||||
);
|
||||
assert.equal(code, 0, `gate must pass on a clean tree, got exit ${code}:\n${out}`);
|
||||
});
|
||||
Reference in New Issue
Block a user