From 1158f2c7fdeeff5b0b2cdee91e3139dfc2dec2c2 Mon Sep 17 00:00:00 2001 From: wonkwonlee Date: Fri, 7 Aug 2026 16:47:40 -0400 Subject: [PATCH 1/8] fix(app): make BLOCK findings visually distinct and keep the verdict on screen An /impeccable critique of the four public workbenches found the same root cause repeated everywhere: a BLOCK finding rendered identically to a PASS finding (same card, differentiated only by an 11px status word), and the risk/decision rail scrolled out of view while reading the evidence it summarized. - Add a shared FindingsList component (severity-sorted, BLOCK gets distinct red chrome, a blocking/warning/passing summary line, and remediation text rendered for every domain instead of only self-hosted) and use it in all four workbenches, replacing four near-duplicate implementations. - Make the airlock status rail sticky on Network/Terraform/Kubernetes so the verdict stays visible while scrolling the evidence below it. - Stop silently substituting a different scenario when `?scenario=` names an unknown id; show a dismissible notice and normalize the URL instead. - Relabel the primary button "Replay evaluated" instead of leaving a disabled control still reading "Run replay". - self-hosted workbench: add self-hosting guide and repo links to the disconnected banner (previously zero outbound links on the page), address the message to the visitor rather than only the operator, wire aria-describedby from the disabled controls to the explanation, and group the intake picker's 15 flat options into per-domain optgroups. --- components/KubernetesWorkbenchShell.tsx | 39 ++++++---- components/ReviewWorkbenchShell.tsx | 51 +++++++------ components/SelfHostedReviewDetail.tsx | 21 +----- components/SelfHostedReviewWorkbench.tsx | 74 ++++++++++++++++--- components/StatusTone.tsx | 54 +++++++++++++- components/TerraformWorkbenchShell.tsx | 47 ++++++------ components/UnknownScenarioNotice.tsx | 33 +++++++++ components/hooks/useScenarioDeepLink.ts | 24 ++++++ tests/unit/findings-list.test.ts | 56 ++++++++++++++ tests/unit/scenario-deep-link.test.ts | 38 +++++++++- .../unit/self-hosted-workbench-route.test.ts | 6 +- 11 files changed, 350 insertions(+), 93 deletions(-) create mode 100644 components/UnknownScenarioNotice.tsx create mode 100644 tests/unit/findings-list.test.ts diff --git a/components/KubernetesWorkbenchShell.tsx b/components/KubernetesWorkbenchShell.tsx index e2ce7f3..c9975c4 100644 --- a/components/KubernetesWorkbenchShell.tsx +++ b/components/KubernetesWorkbenchShell.tsx @@ -13,8 +13,9 @@ import { EvidencePager, } from "@/components/BoundedEvidence"; import { DomainCoverageCatalog } from "@/components/DomainCoverageCatalog"; -import { readInitialScenarioId, useScenarioDeepLink } from "@/components/hooks/useScenarioDeepLink"; -import { PhasePill, RiskValue, StatusBadge } from "@/components/StatusTone"; +import { readScenarioLookup, useScenarioDeepLink } from "@/components/hooks/useScenarioDeepLink"; +import { FindingsList, PhasePill, RiskValue } from "@/components/StatusTone"; +import { UnknownScenarioNotice } from "@/components/UnknownScenarioNotice"; import { WorkbenchNav } from "@/components/WorkbenchNav"; import { MAX_VISIBLE_NESTED_ITEMS, @@ -221,13 +222,7 @@ function ProposalPanel({ state }: { state: WorkflowState }) function FindingsPanel({ state }: { state: WorkflowState }) { if (!hasFindings(state)) return

Policy evidence appears only after replay evaluation.

; - return
    {state.findings.map((finding) => ( -
  • -
    {finding.policyId}
    -

    {finding.title}

    {finding.explanation}

    - {finding.affectedResources.length > 0 ?

    Affected: {finding.affectedResources.join(", ")}

    : null} -
  • - ))}
; + return ; } function DecisionPanel({ state }: { state: WorkflowState }) { @@ -389,14 +384,21 @@ export function KubernetesWorkbenchShell({ setScenarioInUrl(sourceId); }, [controller, setScenarioInUrl]); + const [unknownScenarioId, setUnknownScenarioId] = useState(null); + useEffect(() => { - const initialId = readInitialScenarioId(KUBERNETES_REVIEW_EXAMPLES.map((example) => example.sourceId)); - if (initialId && initialId !== INITIAL_EXAMPLE.sourceId) { + const { requestedId, resolvedId } = readScenarioLookup( + KUBERNETES_REVIEW_EXAMPLES.map((example) => example.sourceId), + ); + if (resolvedId && resolvedId !== INITIAL_EXAMPLE.sourceId) { // Deep-link resolution: sync initial selection from the URL, once on // mount only. window.location is unavailable during SSR, so this can't // move into the useState initializer without a hydration mismatch. // eslint-disable-next-line react-hooks/set-state-in-effect - selectExample(initialId); + selectExample(resolvedId); + } else if (requestedId && !resolvedId) { + setUnknownScenarioId(requestedId); + setScenarioInUrl(INITIAL_EXAMPLE.sourceId); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -410,6 +412,15 @@ export function KubernetesWorkbenchShell({ return

Public replay · Kubernetes offline sandbox

Inspect a schema-validated bundled Kubernetes snapshot and proposed manifest through the deterministic gate. No cluster is contacted, no manifest is applied, and this ephemeral surface cannot make or store a decision.

Runtime variant

Examples / public replay · available

Offline snapshot · no cluster contact · no durable review record

+ {unknownScenarioId ? ( +
+ setUnknownScenarioId(null)} + requestedId={unknownScenarioId} + /> +
+ ) : null}
@@ -420,7 +431,7 @@ export function KubernetesWorkbenchShell({

- +

Image, security, selector, and protected-resource evidence

@@ -475,7 +486,7 @@ export function KubernetesWorkbenchShell({
- + ; diff --git a/components/ReviewWorkbenchShell.tsx b/components/ReviewWorkbenchShell.tsx index e0b7627..3838ec6 100644 --- a/components/ReviewWorkbenchShell.tsx +++ b/components/ReviewWorkbenchShell.tsx @@ -5,9 +5,10 @@ import { useCallback, useEffect, useMemo, useRef, useState } from "react"; import { BoundedJsonBlock } from "@/components/BoundedEvidence"; import { CaseStudyBadge } from "@/components/CaseStudyBadge"; import { DomainCoverageCatalog } from "@/components/DomainCoverageCatalog"; -import { readInitialScenarioId, useScenarioDeepLink } from "@/components/hooks/useScenarioDeepLink"; -import { PhasePill, RiskValue, StatusBadge } from "@/components/StatusTone"; +import { readScenarioLookup, useScenarioDeepLink } from "@/components/hooks/useScenarioDeepLink"; +import { FindingsList, PhasePill, RiskValue } from "@/components/StatusTone"; import { TopologyView } from "@/components/TopologyView"; +import { UnknownScenarioNotice } from "@/components/UnknownScenarioNotice"; import { WorkbenchNav } from "@/components/WorkbenchNav"; import { NETWORK_REVIEW_EXAMPLES } from "@/features/domains/network/examples"; import type { LoadedDomainCoverageCatalog } from "@/features/domains/registry"; @@ -112,25 +113,7 @@ function FindingsPanel({ state }: { state: WorkflowState }) { if (!hasFindings(state)) { return

Findings appear only after replay evaluation.

; } - return ( -
    - {state.findings.map((finding) => ( -
  • -
    - {finding.policyId} - -
    -

    {finding.title}

    -

    {finding.explanation}

    - {finding.affectedResources.length > 0 ? ( -

    - Affected: {finding.affectedResources.join(", ")} -

    - ) : null} -
  • - ))} -
- ); + return ; } function DecisionPanel({ state }: { state: WorkflowState }) { @@ -193,15 +176,21 @@ export function ReviewWorkbenchShell({ ); const canRunReplay = workflow.phase === "READY" || workflow.phase === "ERROR"; + const [unknownScenarioId, setUnknownScenarioId] = useState(null); useEffect(() => { - const initialId = readInitialScenarioId(NETWORK_REVIEW_EXAMPLES.map((example) => example.sourceId)); - if (initialId && initialId !== INITIAL_EXAMPLE.sourceId) { + const { requestedId, resolvedId } = readScenarioLookup( + NETWORK_REVIEW_EXAMPLES.map((example) => example.sourceId), + ); + if (resolvedId && resolvedId !== INITIAL_EXAMPLE.sourceId) { // Deep-link resolution: sync initial selection from the URL, once on // mount only. window.location is unavailable during SSR, so this can't // move into the useState initializer without a hydration mismatch. // eslint-disable-next-line react-hooks/set-state-in-effect - selectExample(initialId); + selectExample(resolvedId); + } else if (requestedId && !resolvedId) { + setUnknownScenarioId(requestedId); + setScenarioInUrl(INITIAL_EXAMPLE.sourceId); } // eslint-disable-next-line react-hooks/exhaustive-deps }, []); @@ -235,6 +224,16 @@ export function ReviewWorkbenchShell({ + {unknownScenarioId ? ( +
+ setUnknownScenarioId(null)} + requestedId={unknownScenarioId} + /> +
+ ) : null} +
@@ -248,7 +247,7 @@ export function ReviewWorkbenchShell({

@@ -284,7 +283,7 @@ export function ReviewWorkbenchShell({ -