diff --git a/apps/dashboard/smoke/frontstage-route-smoke.ts b/apps/dashboard/smoke/frontstage-route-smoke.ts index d3bd4bac..ca0b7c60 100644 --- a/apps/dashboard/smoke/frontstage-route-smoke.ts +++ b/apps/dashboard/smoke/frontstage-route-smoke.ts @@ -148,6 +148,14 @@ includes(frontstageSource, "showcaseCaseHref", "central showcase case page link includes(frontstageSource, "estimated_developer_days", "efficiency baseline range"); includes(frontstageSource, "single_engineer_calendar_compression", "efficiency compression range"); includes(frontstageSource, "maturity-adjusted", "maturity adjusted copy"); +includes(frontstageSource, 'data-testid="frontstage-trajectory-analysis"', "trajectory analysis panel"); +includes(frontstageSource, 'data-testid="frontstage-trajectory-stage-curve"', "trajectory stage progress curve"); +includes(frontstageSource, 'data-testid="frontstage-trajectory-current-scene"', "trajectory current-stage scene"); +includes(frontstageSource, 'data-testid="frontstage-trajectory-verdict-card"', "trajectory verdict card"); +includes(frontstageSource, 'data-testid="frontstage-trajectory-evidence-drawer"', "trajectory evidence drawer"); +includes(frontstageSource, "buildTrajectoryAnalysis", "projection-backed trajectory analysis helper"); +includes(frontstageSource, "not a raw trajectory replay", "trajectory avoids raw replay copy"); +includes(frontstageSource, "raw trajectory logs out of the browser surface", "trajectory public boundary copy"); includes(frontstageSource, 'data-testid="frontstage-state-flow-hero"', "showcase state-flow hero"); includes(frontstageSource, "State flow control plane", "state-flow hero label"); includes(frontstageSource, "Work keeps moving. Judgment stays in charge.", "state-flow hero punchline"); @@ -211,6 +219,17 @@ includes(stylesSource, ".frontstage-showcase-motion-rail", "motion board rail CS includes(stylesSource, ".frontstage-showcase-motion-beam", "motion board beam CSS"); includes(stylesSource, "@keyframes frontstage-case-traffic", "motion board case traffic keyframes"); +const trajectorySource = sourceBetween(frontstageSource, "function TrajectoryAnalysisPanel", "const showcaseMotionTones", "trajectory analysis panel"); +includes(trajectorySource, "buildTrajectoryAnalysis(selfIterationRollout)", "trajectory uses rollout fixture projection"); +includes(trajectorySource, "frontstage-trajectory-stage", "trajectory stage render loop"); +includes(trajectorySource, "Stage progress curve", "trajectory curve label"); +includes(trajectorySource, "Evidence drawer", "trajectory evidence drawer label"); +includes(trajectorySource, "read-only projection", "trajectory read-only projection label"); +includes(trajectorySource, "inferenceReason", "trajectory surfaces bridge inference reason"); +includes(trajectorySource, "local prose, private docs, and raw trajectory logs", "trajectory private-source exclusion copy"); +excludes(trajectorySource, "fetchFrontstageStatusPayload", "trajectory live status dependency"); +excludes(trajectorySource, "statusUrl", "trajectory status URL dependency"); + const kineticStripSource = sourceBetween(frontstageSource, "function ShowcaseKineticCaseStrip", "function FrontstageRoute", "showcase kinetic strip"); includes(kineticStripSource, "frontstageShowcases", "kinetic strip catalog source"); includes(kineticStripSource, "showcaseCaseHref", "kinetic strip case links"); diff --git a/apps/dashboard/src/views/frontstage-page.tsx b/apps/dashboard/src/views/frontstage-page.tsx index 433a728e..f13c45a3 100644 --- a/apps/dashboard/src/views/frontstage-page.tsx +++ b/apps/dashboard/src/views/frontstage-page.tsx @@ -291,6 +291,118 @@ function formatRange(range: NumberRange | undefined, suffix = "") { return `${formatNumber(range.low)}-${formatNumber(range.high)}${suffix}`; } +type TrajectoryStage = { + animationEventId: string; + confidence: string; + evidenceRefs: string[]; + inferenceReason?: string; + isCurrent: boolean; + isSynthetic: boolean; + kind: string; + laneLabel: string; + laneRole: string; + progress: number; + sourceEventIds: string[]; + stageLabel: string; + title: string; + transitionLabel: string; +}; + +type TrajectoryEvidenceItem = { + eventTitle: string; + kind: "evidence_ref" | "source_event"; + ref: string; +}; + +function rolloutStageLabel(event: RolloutAnimationEvent) { + if (event.kind === "deliverable") { + return "Protocol"; + } + if (event.kind === "human_gate") { + return "Gate"; + } + if (event.kind === "validation") { + return "Validation"; + } + if (event.kind === "synthetic_bridge") { + return "UI bridge"; + } + if (event.title.toLowerCase().includes("sufficiency")) { + return "Sufficiency"; + } + if (event.kind === "handoff") { + return "Handoff"; + } + return "State"; +} + +function trajectoryConfidenceTone(confidence: string): BadgeTone { + if (confidence === "observed") { + return "success"; + } + if (confidence === "synthetic_bridge") { + return "warning"; + } + if (confidence.startsWith("inferred")) { + return "info"; + } + return "neutral"; +} + +function buildTrajectoryAnalysis(fixture: LongHorizonRolloutFixture) { + const laneById = new Map(fixture.lanes.map((lane) => [lane.lane_id, lane])); + const lastIndex = Math.max(1, fixture.animation_events.length - 1); + const stages: TrajectoryStage[] = fixture.animation_events.map((event, index) => { + const lane = laneById.get(event.lane_id); + const transitionLabel = event.state_transition + ? `${event.state_transition.from_state ?? "n/a"} -> ${event.state_transition.to_state ?? "n/a"}` + : "state retained"; + return { + animationEventId: event.animation_event_id, + confidence: event.confidence, + evidenceRefs: event.evidence_refs ?? [], + inferenceReason: event.inference_reason, + isCurrent: index === fixture.animation_events.length - 1, + isSynthetic: event.confidence === "synthetic_bridge" || event.display_hint === "dashed_edge", + kind: event.kind, + laneLabel: lane?.display_name ?? event.lane_id, + laneRole: lane?.role ?? "agent", + progress: Math.round((index / lastIndex) * 100), + sourceEventIds: event.source_event_ids, + stageLabel: rolloutStageLabel(event), + title: event.title, + transitionLabel, + }; + }); + const currentStage = stages[stages.length - 1]; + const observedCount = stages.filter((stage) => stage.confidence === "observed").length; + const syntheticCount = stages.filter((stage) => stage.isSynthetic).length; + const evidenceItems = stages.flatMap((stage) => { + const evidenceRefs = stage.evidenceRefs.map((ref) => ({ + eventTitle: stage.title, + kind: "evidence_ref" as const, + ref, + })); + const sourceEventIds = stage.sourceEventIds.map((ref) => ({ + eventTitle: stage.title, + kind: "source_event" as const, + ref, + })); + return [...evidenceRefs, ...sourceEventIds]; + }); + + return { + currentStage, + evidenceItems, + observedCount, + stages, + syntheticCount, + verdict: syntheticCount + ? "Projected handoff is visible; the next frontend patch should replace the bridge with commit-backed evidence." + : "All rendered stages are observed and ready for the next projection-backed iteration.", + }; +} + function TodoRow({ todo }: { todo: GoalChannelTodo }) { return (
@@ -442,6 +554,154 @@ function EfficiencyEvidencePanel() { ); } +function TrajectoryAnalysisPanel() { + const analysis = useMemo(() => buildTrajectoryAnalysis(selfIterationRollout), []); + const currentStage = analysis.currentStage; + + return ( + +
+
+
+
+
+ Stage progress curve +
+

+ A projection fixture turns state transitions into a visible trajectory, not a raw trajectory replay. +

+
+
+ read-only projection + {analysis.observedCount} observed + + {analysis.syntheticCount} bridge + +
+
+
+ {analysis.stages.map((stage, index) => ( +
+
+ {stage.stageLabel} + {String(index + 1).padStart(2, "0")} +
+
+
+
+ ))} +
+
+ +
+
+
+ {currentStage?.laneRole ?? "agent"} + + {currentStage?.isSynthetic ? "needs evidence" : "observed"} + +
+

+ {currentStage?.stageLabel ?? "Current stage"} +

+

+ {currentStage?.title ?? "No stage projected"} +

+

+ {currentStage?.laneLabel ?? "No lane"} / {currentStage?.transitionLabel ?? "state retained"} +

+ {currentStage?.inferenceReason ? ( +

+ {currentStage.inferenceReason} +

+ ) : null} +
+ +
+
+ Verdict +
+

{analysis.verdict}

+

+ The panel is derived from public rollout projections and keeps local prose, private docs, and raw trajectory logs out of the browser surface. +

+
+
+ +
+
+
+
+ Evidence drawer +
+

+ Source events and public evidence references explain every plotted stage. +

+
+ {analysis.evidenceItems.length} refs +
+
+ {analysis.evidenceItems.map((item) => ( +
+
+ {item.kind} + {item.eventTitle} +
+
{item.ref}
+
+ ))} +
+
+
+
+ ); +} + const showcaseMotionTones = [ { card: "border-emerald-200 bg-emerald-50", @@ -1575,6 +1835,8 @@ function FrontstageRoute({ {!isDeveloperMode ? : null} + {isShowcaseMode ? : null} + {isShowcaseMode ? : null} {!isDeveloperMode ? : null}