import { cn } from '@gentleduck/libs/cn' import React from 'react' import type { IamIFlowRecorder } from './lib/flow' import { isDevtoolsBlocked } from './lib/guard' import { ensureStylesInjected } from './lib/styles' import type { IamIDecisionInput, IamIDevtoolsEngine, IamIDevtoolsMetrics, IamPanelKey } from './lib/types' import { IamDecisionInspector } from './panels/decision' import { IamFlowPanel } from './panels/flow' import { IamMetricsPanel } from './panels/metrics' import { IamPoliciesPanel } from './panels/policies' import { IamRolesPanel } from './panels/roles' import { IamSubjectsPanel } from './panels/subjects' export interface IIamDevtoolsInnerProps { engine: IamIDevtoolsEngine metrics?: IamIDevtoolsMetrics flow?: IamIFlowRecorder initialPanel?: IamPanelKey defaultRequest?: Partial pollMs?: number embedded?: boolean } const TABS: { key: IamPanelKey; label: string; dot: string }[] = [ { key: 'flow', label: 'Flow', dot: '#84cc16' }, { key: 'decision', label: 'Decision', dot: '#60a5fa' }, { key: 'policies', label: 'Policies', dot: '#a78bfa' }, { key: 'roles', label: 'Roles', dot: '#34d399' }, { key: 'subjects', label: 'Subjects', dot: '#fbbf24' }, { key: 'metrics', label: 'Metrics', dot: '#ec4899' }, ] // Hard-no in production: admin reads here would leak the full auth model. // No prop escape hatch by design - see lib/guard.ts. The guard sits in a thin // wrapper so the inner component's hook order stays unconditional. export function IamDevtoolsInner(props: IIamDevtoolsInnerProps) { if (isDevtoolsBlocked(props.engine)) return null return } function IamDevtoolsInnerImpl({ engine, metrics, flow, initialPanel = 'flow', defaultRequest, pollMs, embedded = false, }: IIamDevtoolsInnerProps) { React.useEffect(() => { ensureStylesInjected() }, []) const [active, setActive] = React.useState(initialPanel) const content = ( <>
{active === 'flow' && flow && } {active === 'flow' && !flow && (
No flow recorder wired. Pass flow={recorder} to the devtool and bind it to your engine's afterEvaluate hook.
)} {active === 'decision' && } {active === 'policies' && } {active === 'roles' && } {active === 'subjects' && } {active === 'metrics' && }
) if (embedded) { return
{content}
} return (
{content}
) }