diff --git a/packages/app/cypress/support/e2e.ts b/packages/app/cypress/support/e2e.ts index 0edb08c0..8d03599e 100644 --- a/packages/app/cypress/support/e2e.ts +++ b/packages/app/cypress/support/e2e.ts @@ -16,11 +16,14 @@ Cypress.on('window:before:load', (win) => { }); /** - * Unlock the shared feature gate for specs that exercise agentic surfaces - * (the "Agentic Traces" scenario, /datasets, /inference/agentic/[id], and the - * Datasets nav link). The gate is OFF by default so the PR can ship without - * publicly exposing agentic features; agentic specs opt in by seeding the same - * localStorage flag the ↑↑↓↓ konami unlock writes (see use-feature-gate.ts). + * Seed the shared feature-gate flag (the same localStorage key the ↑↑↓↓ konami + * unlock writes — see use-feature-gate.ts). + * + * The agentic surfaces (the "Agentic Traces" scenario, /datasets, + * /inference/agentic/[id], and the Datasets nav link) are now PUBLIC by default + * — they no longer sit behind this gate — so agentic specs no longer need it. + * The helper is retained as a harmless no-op for those specs (and still unlocks + * the remaining hidden features: the "Hidden" tab dropdown and Measured Energy). * * Call from a spec's `cy.visit(..., { onBeforeLoad })`: * cy.visit('/datasets/x', { onBeforeLoad: unlockAgenticGate }); @@ -30,6 +33,7 @@ export function unlockAgenticGate(win: Window): void { try { win.localStorage.setItem('inferencex-feature-gate', '1'); } catch { - // localStorage unavailable — spec will see the gate locked and likely 404. + // localStorage unavailable — only the remaining hidden features stay locked; + // agentic surfaces are public regardless. } } diff --git a/packages/app/src/app/(dashboard)/inference/agentic/[id]/page.tsx b/packages/app/src/app/(dashboard)/inference/agentic/[id]/page.tsx index 91b769bd..f2612c84 100644 --- a/packages/app/src/app/(dashboard)/inference/agentic/[id]/page.tsx +++ b/packages/app/src/app/(dashboard)/inference/agentic/[id]/page.tsx @@ -1,7 +1,6 @@ import type { Metadata } from 'next'; import { notFound } from 'next/navigation'; -import { AgenticGate } from '@/components/agentic-gate'; import { AgenticPointDetail } from '@/components/inference/agentic-point/agentic-point-detail'; import { isPersistedBenchmarkId } from '@/lib/benchmark-id'; @@ -21,9 +20,5 @@ export default async function AgenticPointDetailPage({ // `/agentic/0`, `/agentic/-1`) can never resolve, so 404 instead of rendering a // blank detail shell that fires doomed id-keyed fetches. if (!isPersistedBenchmarkId(numericId)) notFound(); - return ( - - - - ); + return ; } diff --git a/packages/app/src/app/datasets/[slug]/conversations/[convId]/page.tsx b/packages/app/src/app/datasets/[slug]/conversations/[convId]/page.tsx index 5bc8fea9..8957c8b4 100644 --- a/packages/app/src/app/datasets/[slug]/conversations/[convId]/page.tsx +++ b/packages/app/src/app/datasets/[slug]/conversations/[convId]/page.tsx @@ -1,7 +1,6 @@ import { Suspense } from 'react'; import type { Metadata } from 'next'; -import { AgenticGate } from '@/components/agentic-gate'; import { ConversationView } from '@/components/datasets/conversation-view'; import { SITE_URL } from '@semianalysisai/inferencex-constants'; @@ -32,14 +31,12 @@ export default async function ConversationPage({ params }: Props) { // A second decodeURIComponent here would over-decode (and throw for ids that // contain a literal '%'). ConversationView re-encodes when it builds the API URL. return ( - -
-
- - - -
-
-
+
+
+ + + +
+
); } diff --git a/packages/app/src/app/datasets/[slug]/page.tsx b/packages/app/src/app/datasets/[slug]/page.tsx index c853a695..f32e3fa6 100644 --- a/packages/app/src/app/datasets/[slug]/page.tsx +++ b/packages/app/src/app/datasets/[slug]/page.tsx @@ -1,6 +1,5 @@ import type { Metadata } from 'next'; -import { AgenticGate } from '@/components/agentic-gate'; import { DatasetDetail } from '@/components/datasets/dataset-detail'; import { SITE_URL } from '@semianalysisai/inferencex-constants'; @@ -24,12 +23,10 @@ export async function generateMetadata({ params }: Props): Promise { export default async function DatasetDetailPage({ params }: Props) { const { slug } = await params; return ( - -
-
- -
-
-
+
+
+ +
+
); } diff --git a/packages/app/src/app/datasets/page.tsx b/packages/app/src/app/datasets/page.tsx index 711e0dbc..7fe46b93 100644 --- a/packages/app/src/app/datasets/page.tsx +++ b/packages/app/src/app/datasets/page.tsx @@ -1,6 +1,5 @@ import type { Metadata } from 'next'; -import { AgenticGate } from '@/components/agentic-gate'; import { Card } from '@/components/ui/card'; import { JsonLd } from '@/components/json-ld'; import { DatasetList } from '@/components/datasets/dataset-list'; @@ -30,14 +29,6 @@ const jsonLd = { }; export default function DatasetsPage() { - return ( - - - - ); -} - -function DatasetsPageContent() { return (
diff --git a/packages/app/src/components/GlobalFilterContext.tsx b/packages/app/src/components/GlobalFilterContext.tsx index 8bd10c71..11a75324 100644 --- a/packages/app/src/components/GlobalFilterContext.tsx +++ b/packages/app/src/components/GlobalFilterContext.tsx @@ -36,7 +36,6 @@ import { import { computeAutoSwitchDecision } from '@/lib/unofficial-run-auto-switch'; import { countCurvesByPrecision, resolveEffectivePrecisions } from '@/lib/default-precisions'; import { resolveEffectiveSequence } from '@/lib/default-sequence'; -import { useFeatureGate } from '@/lib/use-feature-gate'; import type { AvailabilityRow, WorkflowInfoResponse } from '@/lib/api'; const RUNDATE_RE = /^\d{4}-\d{2}-\d{2}$/u; @@ -162,14 +161,6 @@ export function GlobalFilterProvider({ }) { const { hasUrlParam, getUrlParam, setUrlParams } = useUrlState(); - // Agentic surfaces are hidden behind the shared konami-code feature gate - // (default OFF until agentic launches). When locked, agentic sequences are - // filtered out of `availableSequences` below — the single chokepoint that - // cascades: no agentic default (resolveEffectiveSequence falls to 8k/1k), no - // "Agentic Traces" scenario-selector entry, and no agentic x-axis mode / - // percentile selector (those key off effectiveSequence === AgenticTraces). - const agenticGateUnlocked = useFeatureGate(); - // ── Core filter state ───────────────────────────────────────────────────── const [selectedModel, setSelectedModel] = useState( () => initialModel ?? Model.DeepSeek_V4_Pro, @@ -304,26 +295,17 @@ export function GlobalFilterProvider({ }, [unofficialAvailable, selectedModel]); // Sequences available for the selected model (DB ∪ unofficial run for this model). - // - // When the agentic feature gate is locked (default), agentic sequences are - // dropped from every branch — including the static SEQUENCE_OPTIONS fallback — - // so no agentic scenario is ever selectable or defaulted. This is the single - // gate chokepoint for the main inference chart's agentic surfaces. const availableSequences = useMemo(() => { - const dropAgentic = (seqs: Sequence[]) => - agenticGateUnlocked ? seqs : seqs.filter((s) => s !== Sequence.AgenticTraces); const unofficialSeqs = unofficialAvailable .filter((a) => a.model === selectedModel) .map((a) => a.sequence as Sequence); if (!availabilityRows) { - return unofficialSeqs.length > 0 - ? dropAgentic([...new Set(unofficialSeqs)]) - : dropAgentic(SEQUENCE_OPTIONS); + return unofficialSeqs.length > 0 ? [...new Set(unofficialSeqs)] : [...SEQUENCE_OPTIONS]; } const dbSeqs = modelRows.map((r) => rowToSequence(r)).filter((s): s is Sequence => s !== null); - const merged = dropAgentic([...new Set([...dbSeqs, ...unofficialSeqs])]); - return merged.length > 0 ? merged : dropAgentic(SEQUENCE_OPTIONS); - }, [availabilityRows, modelRows, unofficialAvailable, selectedModel, agenticGateUnlocked]); + const merged = [...new Set([...dbSeqs, ...unofficialSeqs])]; + return merged.length > 0 ? merged : [...SEQUENCE_OPTIONS]; + }, [availabilityRows, modelRows, unofficialAvailable, selectedModel]); // Whether we actually know the selected model's sequences yet. Availability // may arrive from the DB (`availabilityRows`) OR from a loaded unofficial run diff --git a/packages/app/src/components/agentic-gate.tsx b/packages/app/src/components/agentic-gate.tsx deleted file mode 100644 index 9fa0aa37..00000000 --- a/packages/app/src/components/agentic-gate.tsx +++ /dev/null @@ -1,41 +0,0 @@ -'use client'; - -import { notFound } from 'next/navigation'; -import { useEffect, useState } from 'react'; - -import { FEATURE_GATE_KEY, useFeatureGate } from '@/lib/use-feature-gate'; - -/** - * Client gate for the standalone agentic product pages (`/datasets/*`, - * `/inference/agentic/[id]`). These are server-rendered routes with no nav - * entry once the header link is hidden, so a direct URL visit is the only way - * in. When the shared konami-code feature gate (see {@link useFeatureGate}) is - * locked — the default until agentic launches — we `notFound()` so the route - * behaves like a clean 404 instead of publicly exposing agentic surfaces. - * - * The gate lives in localStorage, which the server can't read, so we resolve it - * on the client: read the flag synchronously on mount, and until then render - * nothing (no content flash before a potential 404). QA can unlock at runtime - * with ↑↑↓↓ (the same mechanism as the Hidden tab dropdown) or by seeding - * `localStorage['inferencex-feature-gate'] = '1'`, after which these pages - * render in full. - */ -export function AgenticGate({ children }: { children: React.ReactNode }) { - const unlocked = useFeatureGate(); - // Distinguish "haven't read localStorage yet" from "read it, gate is locked": - // useFeatureGate() returns false on the server and on the very first client - // render before its mount effect runs, so we must not 404 during that window. - const [resolved, setResolved] = useState(false); - useEffect(() => setResolved(true), []); - - if (!resolved) return null; - if (!unlocked) { - // Belt-and-suspenders: re-read the flag directly in case an unlock event - // hasn't propagated yet on this first resolved render. - if (typeof window !== 'undefined' && localStorage.getItem(FEATURE_GATE_KEY) === '1') { - return <>{children}; - } - notFound(); - } - return <>{children}; -} diff --git a/packages/app/src/components/header/header.tsx b/packages/app/src/components/header/header.tsx index 1a12057e..eebef27d 100644 --- a/packages/app/src/components/header/header.tsx +++ b/packages/app/src/components/header/header.tsx @@ -9,7 +9,6 @@ import { track } from '@/lib/analytics'; import { ModeToggle } from '@/components/ui/mode-toggle'; import { MinecraftToggles } from '@/components/minecraft/minecraft-toggles'; import { navigateInApp } from '@/lib/client-navigation'; -import { useFeatureGate } from '@/lib/use-feature-gate'; import { cn } from '@/lib/utils'; import { GitHubStars } from './GithubStars'; @@ -52,9 +51,6 @@ const NAV_LINKS = [ label: 'Datasets', testId: 'nav-link-datasets', event: 'header_datasets_clicked', - // Agentic surface — hidden behind the konami-code feature gate (default off) - // until agentic launches. Same gate as the Hidden tab dropdown. - gated: true, }, { href: '/blog', label: 'Articles', testId: 'nav-link-blog', event: 'header_blog_clicked' }, { href: '/about', label: 'About', testId: 'nav-link-about', event: 'header_about_clicked' }, @@ -72,13 +68,10 @@ function isActive(pathname: string, href: string): boolean { export const Header = ({ starCount }: { starCount?: number | null }) => { const pathname = usePathname() ?? '/'; const router = useRouter(); - const featureGateUnlocked = useFeatureGate(); const [mobileMenuOpen, setMobileMenuOpen] = useState(false); const menuRef = useRef(null); - // Hide gated nav links (e.g. Datasets — an agentic surface) unless the shared - // feature gate is unlocked. Mirrors the tab-nav "Hidden" dropdown gating. - const navLinks = NAV_LINKS.filter((l) => !('gated' in l && l.gated) || featureGateUnlocked); + const navLinks = NAV_LINKS; // Close menu on route change useEffect(() => {