From adf467211d2d9a74bd8a2f04f2d0430298685c21 Mon Sep 17 00:00:00 2001 From: huangrt01 Date: Sun, 28 Jun 2026 00:31:06 +0800 Subject: [PATCH] feat(frontstage): render generic rollout projections --- .../dashboard/smoke/frontstage-route-smoke.ts | 96 +- apps/dashboard/src/styles.css | 894 +++++++++ apps/dashboard/src/views/frontstage-page.tsx | 1711 +++++++++++++++-- .../dashboard-frontstage-browser-smoke.mjs | 195 +- ...frontstage-rollout-projections.public.json | 1527 +++++++++++++++ ...stage-rollout-projections-fixture-smoke.py | 265 +++ 6 files changed, 4528 insertions(+), 160 deletions(-) create mode 100644 examples/fixtures/frontstage-rollout-projections.public.json create mode 100644 examples/frontstage-rollout-projections-fixture-smoke.py diff --git a/apps/dashboard/smoke/frontstage-route-smoke.ts b/apps/dashboard/smoke/frontstage-route-smoke.ts index 846aeede..ca95bfd2 100644 --- a/apps/dashboard/smoke/frontstage-route-smoke.ts +++ b/apps/dashboard/smoke/frontstage-route-smoke.ts @@ -31,6 +31,7 @@ const dataSource = readFileSync("src/data/goal-channel-frontstage.ts", "utf8"); const localStatusQuerySource = readFileSync("src/data/local-status-query.ts", "utf8"); const statusSource = readFileSync("src/data/status.ts", "utf8"); const catalogSource = readFileSync("../../docs/showcases/showcase-catalog.json", "utf8"); +const rolloutProjectionFixtureSource = readFileSync("../../examples/fixtures/frontstage-rollout-projections.public.json", "utf8"); const privateTrapFixtureSource = readFileSync("../../examples/fixtures/frontstage-private-status-trap.public.json", "utf8"); const readmeSource = readFileSync("README.md", "utf8"); const selectionSource = readFileSync("../../docs/dashboard-frontend-selection.md", "utf8"); @@ -167,13 +168,54 @@ includes(frontstageSource, "estimated_developer_days", "efficiency baseline rang 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, "rolloutProjectionFixture", "rollout projection public fixture import"); +includes(frontstageSource, "RolloutProjectionBundle", "rollout projection bundle type"); +includes(frontstageSource, 'data-testid="frontstage-rollout-projection-constellation"', "rollout projection constellation"); +includes(frontstageSource, 'data-testid="frontstage-rollout-projection-model-contract"', "generic projection model contract"); +includes(frontstageSource, 'data-testid="frontstage-rollout-relationship-mesh"', "generic rollout relationship mesh"); +includes(frontstageSource, 'data-testid="frontstage-rollout-timeline"', "generic rollout timeline"); +includes(frontstageSource, 'data-testid="frontstage-rollout-timeline-scale"', "generic rollout timeline scale"); +includes(frontstageSource, 'data-testid="frontstage-rollout-timeline-tick"', "generic rollout timeline ticks"); +includes(frontstageSource, 'data-testid="frontstage-rollout-timeline-point"', "generic rollout timeline points"); +includes(frontstageSource, 'data-testid="frontstage-rollout-time-milestone"', "generic rollout time milestones"); +includes(frontstageSource, 'data-node-time={rolloutNodeTimeLabel(node)}', "generic rollout node time attribute"); +includes(frontstageSource, 'data-testid="frontstage-rollout-mesh-node"', "generic rollout mesh nodes"); +includes(frontstageSource, 'data-testid="frontstage-rollout-mesh-node-tooltip"', "generic rollout node tooltip"); +includes(frontstageSource, 'data-testid="frontstage-rollout-mesh-edge"', "generic rollout mesh edges"); +includes(frontstageSource, 'data-testid="frontstage-rollout-mesh-edge-hit"', "generic rollout mesh edge hit targets"); +includes(frontstageSource, 'data-testid="frontstage-rollout-mesh-edge-hotspot"', "generic rollout mesh edge hover hotspots"); +includes(frontstageSource, 'data-testid="frontstage-rollout-mesh-edge-hover-card"', "generic rollout edge hover card"); +includes(frontstageSource, 'data-testid="frontstage-rollout-mesh-time-tick"', "generic rollout mesh wall-clock ticks"); +includes(frontstageSource, 'data-edge-kind={edge.edge_kind}', "generic rollout edge kind attribute"); +includes(frontstageSource, 'data-edge-label={edge.label}', "generic rollout edge label attribute"); +includes(frontstageSource, 'data-testid="frontstage-rollout-mesh-lane"', "generic rollout mesh lanes"); +includes(frontstageSource, 'data-testid="frontstage-rollout-requirement-spine"', "rollout requirement spine"); +includes(frontstageSource, 'data-testid="frontstage-rollout-sequence-ribbon"', "rollout sequence ribbon"); +includes(frontstageSource, 'data-testid="frontstage-rollout-sequence-chip"', "rollout sequence chips"); +includes(frontstageSource, 'data-testid="frontstage-rollout-requirement-unit"', "rollout requirement units"); +includes(frontstageSource, 'data-testid="frontstage-rollout-requirement-step"', "rollout requirement steps"); +includes(frontstageSource, 'data-testid="frontstage-rollout-capability-map"', "rollout capability map"); +includes(frontstageSource, 'data-testid="frontstage-rollout-mapping-layer"', "rollout mapping layer cards"); +includes(frontstageSource, 'data-testid="frontstage-rollout-flow-signals"', "rollout flow signals"); +includes(frontstageSource, 'data-testid="frontstage-rollout-relationship-summaries"', "rollout relationship grammar"); +includes(frontstageSource, 'data-testid="frontstage-rollout-attention-hotspots"', "rollout attention hotspots"); +includes(frontstageSource, 'data-testid="frontstage-trajectory-stage-confidence"', "trajectory stage confidence card"); +includes(frontstageSource, 'data-testid="frontstage-rollout-stage-flow"', "rollout stage flow"); +includes(frontstageSource, 'data-testid="frontstage-rollout-lane-graph"', "rollout lane graph"); +includes(frontstageSource, 'data-testid="frontstage-rollout-edge-list"', "rollout edge list"); +includes(frontstageSource, "projection.scene.title", "rollout projection scene title render"); 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, "RolloutProjectionConstellation", "rollout projection renderer component"); +includes(frontstageSource, "RolloutRelationshipMesh", "generic rollout relationship mesh renderer component"); +includes(frontstageSource, "RolloutRequirementSpine", "rollout requirement spine renderer component"); +includes(frontstageSource, "RolloutProjectionCapabilityMap", "rollout capability map component"); +includes(frontstageSource, "RolloutProjectionStageFlow", "rollout stage renderer component"); +includes(frontstageSource, "RolloutProjectionLaneGraph", "rollout lane graph renderer component"); +includes(frontstageSource, "required_sections.join", "generic projection required sections render"); +includes(frontstageSource, "raw trajectories and local state stay outside", "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"); @@ -220,6 +262,43 @@ includes(catalogSource, '"public_safe_interactive_case"', "hardware-agent intera includes(catalogSource, '"interactive_page"', "hardware-agent interactive page field"); includes(catalogSource, '"2026-06-20-creator-operator-case-spec"', "creator operator showcase case"); +includes(rolloutProjectionFixtureSource, '"frontstage_rollout_projection_bundle_v0"', "rollout projection bundle schema"); +includes(rolloutProjectionFixtureSource, '"frontstage_rollout_projection_model_v0"', "rollout projection model schema"); +includes(rolloutProjectionFixtureSource, '"optional_rich_sections"', "rollout projection rich sections"); +includes(rolloutProjectionFixtureSource, '"timeline"', "rollout timeline fixture"); +includes(rolloutProjectionFixtureSource, '"item_node_ids"', "rollout timeline item ids"); +includes(rolloutProjectionFixtureSource, '"axis_kind": "wall_clock"', "rollout wall-clock timeline axis"); +includes(rolloutProjectionFixtureSource, '"timezone": "Asia/Shanghai"', "rollout timeline timezone"); +includes(rolloutProjectionFixtureSource, '"started_at"', "rollout node started time"); +includes(rolloutProjectionFixtureSource, '"completed_at"', "rollout node completed time"); +includes(rolloutProjectionFixtureSource, '"duration_label"', "rollout node duration label"); +includes(rolloutProjectionFixtureSource, '"wall-clock timeline ticks"', "rollout wall-clock acceptance item"); +includes(rolloutProjectionFixtureSource, '"node time and duration hover details"', "rollout time hover acceptance item"); +includes(rolloutProjectionFixtureSource, '"rollout_sequence"', "rollout sequence fixture"); +includes(rolloutProjectionFixtureSource, '"sequence_id": "overnight_requirement_rollout_spine"', "rollout sequence id"); +includes(rolloutProjectionFixtureSource, '"mapping_layers"', "rollout projection mapping layers"); +includes(rolloutProjectionFixtureSource, '"flow_signals"', "rollout projection flow signals"); +includes(rolloutProjectionFixtureSource, '"relationship_summaries"', "rollout projection relationship summaries"); +includes(rolloutProjectionFixtureSource, '"attention_hotspots"', "rollout projection attention hotspots"); +includes(rolloutProjectionFixtureSource, '"projection_id": "overnight_pr_batch_20260627"', "overnight PR projection id"); +includes(rolloutProjectionFixtureSource, '"sample_window": "#746-#775"', "overnight PR sample window"); +includes(rolloutProjectionFixtureSource, '"projection_is_writable": false', "rollout projection read-only contract"); +includes(rolloutProjectionFixtureSource, '"Review mesh over a 30-PR public batch"', "rollout projection scene title"); +includes(rolloutProjectionFixtureSource, '"generic projection model contract"', "rollout projection acceptance item"); +includes(rolloutProjectionFixtureSource, '"30 PR relationship mesh"', "rollout relationship mesh acceptance item"); +includes(rolloutProjectionFixtureSource, '"timeline axis"', "rollout timeline acceptance item"); +includes(rolloutProjectionFixtureSource, '"hoverable node and edge details"', "rollout hover acceptance item"); +includes(rolloutProjectionFixtureSource, '"sequential requirement rollout"', "rollout sequence acceptance item"); +includes(rolloutProjectionFixtureSource, '"Show frontstage trajectory as a reusable projection"', "first rollout requirement"); +includes(rolloutProjectionFixtureSource, '"Monitor tasks become due work instead of hidden polling"', "monitor rollout requirement"); +includes(rolloutProjectionFixtureSource, '"planned_projections"', "planned projection section"); +includes(rolloutProjectionFixtureSource, '"loopx_overall_iteration"', "planned LoopX overall iteration projection"); +excludes(rolloutProjectionFixtureSource, "/Users/", "rollout projection local path"); +excludes(rolloutProjectionFixtureSource, "/home/", "rollout projection home path"); +excludes(rolloutProjectionFixtureSource, "Bearer ", "rollout projection credential marker"); +excludes(rolloutProjectionFixtureSource, "GH_FAKE_PRIVATE", "rollout projection private trap marker"); +excludes(rolloutProjectionFixtureSource, '"private_material_body_recorded": true', "rollout projection private body flag"); + const motionSource = sourceBetween(frontstageSource, "function ShowcaseMotionBoard", "function rolloutKindTone", "showcase motion board"); includes(motionSource, "frontstageShowcases", "motion board catalog source"); includes(motionSource, "journeySegments", "motion board journey summary"); @@ -238,13 +317,18 @@ includes(stylesSource, ".frontstage-showcase-motion-beam", "motion board beam CS 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, "overnightPrProjection", "trajectory uses overnight rollout projection"); +includes(trajectorySource, "RolloutProjectionConstellation", "trajectory renders generic projection constellation"); +includes(trajectorySource, "RolloutProjectionCapabilityMap", "trajectory renders capability map"); +includes(trajectorySource, "RolloutProjectionStageFlow", "trajectory renders stage flow from projection"); +includes(trajectorySource, "RolloutProjectionLaneGraph", "trajectory renders lane graph from 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, "Anchor {anchorNode?.label", "trajectory anchor node explanation"); 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"); +includes(trajectorySource, "projection.source_contract.next_projection_hint", "trajectory surfaces next projection hint"); +includes(trajectorySource, "raw trajectories and local state stay outside", "trajectory private-source exclusion copy"); excludes(trajectorySource, "fetchFrontstageStatusPayload", "trajectory live status dependency"); excludes(trajectorySource, "statusUrl", "trajectory status URL dependency"); diff --git a/apps/dashboard/src/styles.css b/apps/dashboard/src/styles.css index 33fbbb81..51b2484c 100644 --- a/apps/dashboard/src/styles.css +++ b/apps/dashboard/src/styles.css @@ -201,6 +201,713 @@ select { border-color: rgb(14 165 233); } +.frontstage-rollout-projection-constellation { + isolation: isolate; +} + +.frontstage-rollout-projection-constellation::before { + position: absolute; + inset: 0; + z-index: 0; + content: ""; + background: + linear-gradient(115deg, rgb(255 255 255 / 0.07), transparent 38%), + repeating-linear-gradient( + 90deg, + rgb(103 232 249 / 0.08) 0, + rgb(103 232 249 / 0.08) 1px, + transparent 1px, + transparent 42px + ), + repeating-linear-gradient( + 0deg, + rgb(52 211 153 / 0.06) 0, + rgb(52 211 153 / 0.06) 1px, + transparent 1px, + transparent 36px + ); + opacity: 0.86; +} + +.frontstage-rollout-projection-constellation::after { + position: absolute; + inset: 0; + z-index: 0; + content: ""; + background: linear-gradient(100deg, transparent, rgb(103 232 249 / 0.14), transparent); + transform: translateX(-100%); + animation: frontstage-trajectory-scan 5.2s cubic-bezier(0.5, 0, 0.2, 1) infinite; +} + +.frontstage-rollout-requirement-spine { + isolation: isolate; +} + +.frontstage-rollout-pr-mesh { + isolation: isolate; +} + +.frontstage-rollout-pr-mesh::before { + position: absolute; + inset: 0; + z-index: 0; + content: ""; + background: + radial-gradient(circle at 18% 18%, rgb(103 232 249 / 0.2), transparent 24%), + radial-gradient(circle at 76% 22%, rgb(251 191 36 / 0.14), transparent 28%), + radial-gradient(circle at 50% 88%, rgb(52 211 153 / 0.14), transparent 30%), + repeating-linear-gradient( + 90deg, + rgb(255 255 255 / 0.07) 0, + rgb(255 255 255 / 0.07) 1px, + transparent 1px, + transparent 44px + ), + repeating-linear-gradient( + 0deg, + rgb(103 232 249 / 0.05) 0, + rgb(103 232 249 / 0.05) 1px, + transparent 1px, + transparent 44px + ); +} + +.frontstage-rollout-pr-mesh::after { + position: absolute; + inset: 0; + z-index: 0; + content: ""; + background: linear-gradient(105deg, transparent 10%, rgb(103 232 249 / 0.11), rgb(52 211 153 / 0.09), transparent 72%); + transform: translateX(-100%); + animation: frontstage-rollout-mesh-scan 5.8s cubic-bezier(0.5, 0, 0.2, 1) infinite; +} + +.frontstage-rollout-pr-mesh-stage { + min-width: 60rem; + min-height: 36rem; +} + +.frontstage-rollout-timeline-track { + position: relative; + height: 4.75rem; + min-width: 48rem; +} + +.frontstage-rollout-timeline-track::before { + position: absolute; + left: 0.5rem; + right: 0.5rem; + top: 1.625rem; + height: 0.125rem; + content: ""; + background: linear-gradient(90deg, rgb(103 232 249 / 0.22), rgb(52 211 153 / 0.62), rgb(251 191 36 / 0.38)); + box-shadow: 0 0 18px rgb(103 232 249 / 0.2); +} + +.frontstage-rollout-timeline-tick { + position: absolute; + top: 2.55rem; + transform: translateX(-50%); + color: rgb(148 163 184); + font-size: 0.58rem; + font-weight: 800; + line-height: 0.75rem; + white-space: nowrap; +} + +.frontstage-rollout-timeline-tick::before { + position: absolute; + left: 50%; + bottom: calc(100% + 0.25rem); + width: 1px; + height: 1.55rem; + content: ""; + background: linear-gradient(180deg, rgb(148 163 184 / 0.55), transparent); +} + +.frontstage-rollout-timeline-point { + z-index: 1; + position: absolute; + top: 0.625rem; + display: grid; + width: 2.35rem; + min-width: 1.25rem; + height: 3.75rem; + transform: translateX(-50%); + place-items: start center; + border-radius: 0.25rem; + color: rgb(226 232 240); + text-decoration: none; +} + +.frontstage-rollout-timeline-point::before { + position: absolute; + top: 0.95rem; + left: 50%; + width: 0.625rem; + height: 0.625rem; + transform: translate(-50%, -50%); + border-radius: 9999px; + content: ""; + box-shadow: 0 0 14px currentColor; +} + +.frontstage-rollout-timeline-point span { + position: absolute; + top: 1.45rem; + font-size: 0.5rem; + font-weight: 800; + line-height: 0.75rem; + opacity: 0.58; + transition: opacity 140ms ease; +} + +.frontstage-rollout-timeline-point small { + position: absolute; + top: 2.35rem; + transform: rotate(-32deg); + transform-origin: left center; + color: rgb(203 213 225); + font-size: 0.5rem; + font-weight: 800; + line-height: 0.7rem; + opacity: 0; + white-space: nowrap; +} + +.frontstage-rollout-timeline-point:hover span, +.frontstage-rollout-timeline-point:focus-visible span, +.frontstage-rollout-timeline-point:hover small, +.frontstage-rollout-timeline-point:focus-visible small { + opacity: 1; +} + +.frontstage-rollout-time-milestones { + position: relative; + height: 2.75rem; + min-width: 48rem; + border-top: 1px solid rgb(148 163 184 / 0.08); +} + +.frontstage-rollout-time-milestone { + position: absolute; + top: 0.5rem; + display: grid; + min-width: 4.7rem; + transform: translateX(-50%); + gap: 0.05rem; + border: 1px solid rgb(103 232 249 / 0.18); + border-radius: 0.375rem; + padding: 0.25rem 0.45rem; + background: linear-gradient(180deg, rgb(15 23 42 / 0.92), rgb(2 6 23 / 0.86)); + box-shadow: 0 0 18px rgb(103 232 249 / 0.08); + color: rgb(226 232 240); + text-align: center; +} + +.frontstage-rollout-time-milestone strong { + color: rgb(165 243 252); + font-size: 0.68rem; + font-weight: 900; + line-height: 0.85rem; +} + +.frontstage-rollout-time-milestone small { + color: rgb(148 163 184); + font-size: 0.52rem; + font-weight: 800; + line-height: 0.7rem; + white-space: nowrap; +} + +.frontstage-rollout-pr-mesh-time-grid { + position: absolute; + inset: 0; + z-index: 1; + pointer-events: none; +} + +.frontstage-rollout-pr-mesh-time-tick { + position: absolute; + top: 0.5rem; + bottom: 0.5rem; + color: rgb(148 163 184); + font-size: 0.55rem; + font-weight: 800; + line-height: 0.75rem; + text-shadow: 0 0 12px rgb(2 6 23); +} + +.frontstage-rollout-pr-mesh-time-tick::before { + position: absolute; + top: 1.1rem; + bottom: 0; + left: 50%; + width: 1px; + content: ""; + background: linear-gradient(180deg, rgb(103 232 249 / 0.18), rgb(148 163 184 / 0.08), transparent); +} + +.frontstage-rollout-timeline-point-merged::before { + background: rgb(52 211 153); +} + +.frontstage-rollout-timeline-point-open::before { + background: rgb(103 232 249); +} + +.frontstage-rollout-timeline-point-closed::before { + background: rgb(251 191 36); +} + +.frontstage-rollout-pr-mesh-svg { + position: absolute; + inset: 0; + width: 100%; + height: 100%; + overflow: visible; +} + +.frontstage-rollout-pr-mesh-edge { + vector-effect: non-scaling-stroke; + fill: none; + stroke-linecap: round; + stroke-dasharray: 7 9; + animation: frontstage-rollout-mesh-edge-flow 3.6s linear infinite; +} + +.frontstage-rollout-pr-mesh-edge-hit { + vector-effect: non-scaling-stroke; + stroke: rgb(255 255 255 / 0.01); + stroke-linecap: round; + stroke-width: 22; + pointer-events: stroke; +} + +.frontstage-rollout-pr-mesh-edge-group { + outline: none; +} + +.frontstage-rollout-pr-mesh-edge-group:hover .frontstage-rollout-pr-mesh-edge, +.frontstage-rollout-pr-mesh-edge-group:focus .frontstage-rollout-pr-mesh-edge { + stroke: rgb(251 191 36 / 0.9); + stroke-width: 3.2; + filter: drop-shadow(0 0 10px rgb(251 191 36 / 0.48)); +} + +.frontstage-rollout-mesh-edge-hotspot { + position: absolute; + z-index: 4; + width: 0.625rem; + height: 0.625rem; + transform: translate(-50%, -50%); + border: 1px solid rgb(255 255 255 / 0.18); + border-radius: 9999px; + padding: 0; + background: rgb(148 163 184 / 0.26); + opacity: 0.46; + cursor: help; + box-shadow: 0 0 10px rgb(148 163 184 / 0.18); + transition: + opacity 140ms ease, + transform 140ms ease, + box-shadow 140ms ease, + background 140ms ease; +} + +.frontstage-rollout-mesh-edge-hotspot:hover, +.frontstage-rollout-mesh-edge-hotspot:focus-visible { + transform: translate(-50%, -50%) scale(1.55); + opacity: 1; + box-shadow: 0 0 18px rgb(251 191 36 / 0.42); + outline: none; +} + +.frontstage-rollout-mesh-edge-hotspot-timeline { + background: rgb(148 163 184 / 0.45); +} + +.frontstage-rollout-mesh-edge-hotspot-lane { + background: rgb(52 211 153 / 0.58); +} + +.frontstage-rollout-mesh-edge-hotspot-explicit { + background: rgb(251 191 36 / 0.82); +} + +.frontstage-rollout-pr-mesh-edge-timeline { + stroke: rgb(148 163 184 / 0.28); + stroke-width: 1.25; +} + +.frontstage-rollout-pr-mesh-edge-lane { + stroke: rgb(52 211 153 / 0.42); + stroke-width: 1.8; +} + +.frontstage-rollout-pr-mesh-edge-explicit { + stroke: rgb(103 232 249 / 0.78); + stroke-width: 2.6; + filter: drop-shadow(0 0 8px rgb(103 232 249 / 0.42)); +} + +.frontstage-rollout-pr-mesh-lane-label { + position: absolute; + left: 0.75rem; + z-index: 2; + display: flex; + max-width: 9.5rem; + transform: translateY(-50%); + align-items: center; + justify-content: space-between; + gap: 0.5rem; + border: 1px solid rgb(255 255 255 / 0.1); + border-radius: 0.375rem; + padding: 0.375rem 0.5rem; + background: rgb(15 23 42 / 0.78); + color: rgb(226 232 240); + font-size: 0.625rem; + font-weight: 700; + line-height: 1rem; +} + +.frontstage-rollout-pr-mesh-node { + position: absolute; + z-index: 3; + display: grid; + width: 4.25rem; + min-height: 3rem; + transform: translate(-50%, -50%); + place-items: center; + border: 1px solid rgb(255 255 255 / 0.14); + border-radius: 0.375rem; + padding: 0.35rem; + color: rgb(248 250 252); + text-align: center; + text-decoration: none; + box-shadow: 0 0 0 rgb(103 232 249 / 0); + animation: frontstage-rollout-mesh-node-pulse 3.4s ease-in-out infinite; +} + +.frontstage-rollout-pr-mesh-node span { + font-size: 0.75rem; + font-weight: 800; + line-height: 1rem; +} + +.frontstage-rollout-pr-mesh-node small { + margin-top: 0.125rem; + font-size: 0.55rem; + font-weight: 700; + line-height: 0.75rem; + text-transform: uppercase; +} + +.frontstage-rollout-pr-mesh-node-merged { + background: linear-gradient(135deg, rgb(6 78 59 / 0.82), rgb(15 23 42 / 0.92)); +} + +.frontstage-rollout-pr-mesh-node-open { + border-color: rgb(103 232 249 / 0.58); + background: linear-gradient(135deg, rgb(8 47 73 / 0.9), rgb(15 23 42 / 0.94)); + box-shadow: 0 0 20px rgb(103 232 249 / 0.22); +} + +.frontstage-rollout-pr-mesh-node-closed { + background: linear-gradient(135deg, rgb(113 63 18 / 0.76), rgb(15 23 42 / 0.92)); +} + +.frontstage-rollout-pr-mesh-node:hover { + border-color: rgb(103 232 249 / 0.72); + box-shadow: 0 0 24px rgb(103 232 249 / 0.3); +} + +.frontstage-rollout-pr-mesh-node-tooltip { + position: absolute; + left: 50%; + bottom: calc(100% + 0.5rem); + z-index: 8; + display: grid; + width: min(16rem, 70vw); + transform: translate(-50%, 0.375rem); + gap: 0.25rem; + border: 1px solid rgb(103 232 249 / 0.32); + border-radius: 0.375rem; + padding: 0.625rem 0.75rem; + background: rgb(2 6 23 / 0.94); + color: rgb(226 232 240); + text-align: left; + opacity: 0; + pointer-events: none; + box-shadow: 0 18px 44px rgb(0 0 0 / 0.34), 0 0 24px rgb(103 232 249 / 0.18); + transition: + opacity 140ms ease, + transform 140ms ease; +} + +.frontstage-rollout-pr-mesh-node-tooltip strong { + color: rgb(255 255 255); + font-size: 0.75rem; + line-height: 1rem; +} + +.frontstage-rollout-pr-mesh-node-tooltip span { + font-size: 0.6875rem; + font-weight: 600; + line-height: 1rem; +} + +.frontstage-rollout-pr-mesh-node:hover .frontstage-rollout-pr-mesh-node-tooltip, +.frontstage-rollout-pr-mesh-node:focus-visible .frontstage-rollout-pr-mesh-node-tooltip { + transform: translate(-50%, 0); + opacity: 1; +} + +.frontstage-rollout-mesh-edge-hover-card { + position: absolute; + right: 0.75rem; + bottom: 0.75rem; + z-index: 7; + width: min(21rem, calc(100% - 1.5rem)); + min-height: 5.75rem; + border: 1px solid rgb(255 255 255 / 0.1); + border-radius: 0.375rem; + padding: 0.75rem; + background: rgb(2 6 23 / 0.9); + box-shadow: 0 16px 36px rgb(0 0 0 / 0.28); +} + +.frontstage-rollout-mesh-edge-hover-card-visible { + border-color: rgb(251 191 36 / 0.42); + box-shadow: 0 16px 36px rgb(0 0 0 / 0.28), 0 0 24px rgb(251 191 36 / 0.15); +} + +.frontstage-rollout-requirement-spine::before { + position: absolute; + inset: 0; + z-index: 0; + content: ""; + background: + radial-gradient(circle at 15% 8%, rgb(103 232 249 / 0.2), transparent 28%), + radial-gradient(circle at 78% 16%, rgb(251 191 36 / 0.14), transparent 30%), + linear-gradient(115deg, rgb(15 23 42 / 0.2), transparent 42%, rgb(6 78 59 / 0.18)), + repeating-linear-gradient( + 90deg, + rgb(255 255 255 / 0.07) 0, + rgb(255 255 255 / 0.07) 1px, + transparent 1px, + transparent 52px + ); +} + +.frontstage-rollout-requirement-spine::after { + position: absolute; + inset-block: 0; + left: -20%; + z-index: 0; + width: 22%; + content: ""; + background: linear-gradient(90deg, transparent, rgb(103 232 249 / 0.2), rgb(52 211 153 / 0.18), transparent); + transform: skewX(-10deg); + animation: frontstage-rollout-spine-sweep 4.8s cubic-bezier(0.5, 0, 0.2, 1) infinite; +} + +.frontstage-rollout-spine-track::before { + position: absolute; + left: 1.25rem; + right: 1.25rem; + top: 2rem; + z-index: 0; + height: 0.125rem; + content: ""; + background: linear-gradient(90deg, rgb(103 232 249 / 0.2), rgb(52 211 153 / 0.62), rgb(251 191 36 / 0.48), rgb(103 232 249 / 0.26)); + box-shadow: 0 0 24px rgb(103 232 249 / 0.22); +} + +.frontstage-rollout-spine-ribbon { + box-shadow: inset 0 0 32px rgb(103 232 249 / 0.08); +} + +.frontstage-rollout-sequence-chip { + min-width: 0; + box-shadow: 0 0 0 rgb(103 232 249 / 0); + transition: + border-color 160ms ease, + box-shadow 160ms ease, + transform 160ms ease; +} + +.frontstage-rollout-sequence-chip:hover { + border-color: rgb(103 232 249 / 0.44); + box-shadow: 0 0 20px rgb(103 232 249 / 0.16); + transform: translateY(-0.0625rem); +} + +.frontstage-rollout-spine-card { + z-index: 1; + isolation: isolate; +} + +.frontstage-rollout-spine-card::before { + position: absolute; + left: -0.625rem; + top: 1.875rem; + width: 0.625rem; + height: 0.125rem; + content: ""; + background: rgb(103 232 249 / 0.56); + box-shadow: 0 0 14px rgb(103 232 249 / 0.36); +} + +.frontstage-rollout-spine-card:first-child::before { + display: none; +} + +.frontstage-rollout-spine-card::after { + position: absolute; + inset-inline: 0.75rem; + bottom: 0.625rem; + height: 0.125rem; + border-radius: 9999px; + content: ""; + background: linear-gradient(90deg, rgb(103 232 249), rgb(52 211 153), rgb(251 191 36)); + box-shadow: 0 0 18px rgb(103 232 249 / 0.34); + animation: frontstage-rollout-spine-card-glow 2.8s ease-in-out infinite; +} + +.frontstage-rollout-spine-order { + box-shadow: 0 0 18px rgb(103 232 249 / 0.28); + animation: frontstage-rollout-spine-order-pulse 3.2s ease-in-out infinite; +} + +.frontstage-rollout-spine-step { + min-width: 0; +} + +.frontstage-rollout-capability-map { + isolation: isolate; +} + +.frontstage-rollout-capability-map::before { + position: absolute; + inset: 0; + z-index: 0; + content: ""; + background: + linear-gradient(120deg, rgb(103 232 249 / 0.1), transparent 34%, rgb(52 211 153 / 0.08)), + repeating-linear-gradient( + 90deg, + rgb(148 163 184 / 0.08) 0, + rgb(148 163 184 / 0.08) 1px, + transparent 1px, + transparent 48px + ), + repeating-linear-gradient( + 0deg, + rgb(14 165 233 / 0.06) 0, + rgb(14 165 233 / 0.06) 1px, + transparent 1px, + transparent 48px + ); +} + +.frontstage-rollout-capability-map::after { + position: absolute; + inset: 0; + z-index: 0; + content: ""; + background: linear-gradient(90deg, transparent, rgb(52 211 153 / 0.16), rgb(103 232 249 / 0.14), transparent); + transform: translateX(-100%); + animation: frontstage-rollout-capability-scan 6.4s cubic-bezier(0.5, 0, 0.2, 1) infinite; +} + +.frontstage-rollout-layer-rail::before { + position: absolute; + left: 1rem; + right: 1rem; + top: 50%; + z-index: 0; + height: 0.125rem; + content: ""; + background: linear-gradient(90deg, rgb(103 232 249 / 0.1), rgb(52 211 153 / 0.5), rgb(251 191 36 / 0.32)); +} + +.frontstage-rollout-layer-card { + isolation: isolate; +} + +.frontstage-rollout-layer-card::after { + position: absolute; + inset-inline: 0.75rem; + bottom: 0.625rem; + height: 0.125rem; + border-radius: 9999px; + content: ""; + background: linear-gradient(90deg, rgb(103 232 249), rgb(52 211 153), rgb(251 191 36)); + box-shadow: 0 0 18px rgb(103 232 249 / 0.28); + animation: frontstage-rollout-layer-glow 2.6s ease-in-out infinite; +} + +.frontstage-rollout-node-grid { + display: grid; + grid-template-columns: repeat(30, minmax(0, 1fr)); + gap: 0.375rem; +} + +.frontstage-rollout-node-dot { + min-width: 0.25rem; + box-shadow: 0 0 12px currentColor; + animation: frontstage-rollout-node-pulse 2.8s ease-in-out infinite; +} + +.frontstage-rollout-stage-flow { + isolation: isolate; +} + +.frontstage-rollout-stage-flow::before { + position: absolute; + left: 1rem; + right: 1rem; + top: 2.5rem; + z-index: 0; + height: 0.125rem; + content: ""; + background: linear-gradient(90deg, rgb(14 165 233 / 0.18), rgb(16 185 129 / 0.45), rgb(245 158 11 / 0.22)); +} + +.frontstage-rollout-stage-flow > * { + z-index: 1; +} + +.frontstage-rollout-stage-flow-beam { + position: absolute; + top: 2.375rem; + left: -16%; + z-index: 1; + width: 18%; + height: 0.375rem; + border-radius: 9999px; + background: linear-gradient(90deg, transparent, rgb(103 232 249), rgb(52 211 153), transparent); + box-shadow: 0 0 18px rgb(103 232 249 / 0.48); + animation: frontstage-rollout-stage-scan 3.4s cubic-bezier(0.45, 0, 0.2, 1) infinite; +} + +.frontstage-rollout-edge { + isolation: isolate; +} + +.frontstage-rollout-edge::before { + position: absolute; + inset-block: 0.5rem; + left: 0; + width: 0.125rem; + border-radius: 9999px; + content: ""; + background: linear-gradient(180deg, rgb(103 232 249), rgb(52 211 153), rgb(251 191 36)); + box-shadow: 0 0 14px rgb(103 232 249 / 0.36); + animation: frontstage-rollout-edge-glow 2.6s ease-in-out infinite; +} + @keyframes frontstage-kinetic-strip { from { transform: translateX(0); @@ -221,6 +928,138 @@ select { } } +@keyframes frontstage-trajectory-scan { + from { + transform: translateX(-100%); + } + + to { + transform: translateX(100%); + } +} + +@keyframes frontstage-rollout-capability-scan { + from { + transform: translateX(-100%); + } + + to { + transform: translateX(100%); + } +} + +@keyframes frontstage-rollout-mesh-scan { + from { + transform: translateX(-100%); + } + + to { + transform: translateX(100%); + } +} + +@keyframes frontstage-rollout-mesh-edge-flow { + from { + stroke-dashoffset: 0; + } + + to { + stroke-dashoffset: -32; + } +} + +@keyframes frontstage-rollout-mesh-node-pulse { + 0%, + 100% { + transform: translate(-50%, -50%) scale(1); + } + + 50% { + transform: translate(-50%, -50%) scale(1.045); + } +} + +@keyframes frontstage-rollout-spine-sweep { + from { + transform: translateX(0) skewX(-10deg); + } + + to { + transform: translateX(560%) skewX(-10deg); + } +} + +@keyframes frontstage-rollout-spine-card-glow { + 0%, + 100% { + opacity: 0.38; + transform: scaleX(0.64); + } + + 50% { + opacity: 1; + transform: scaleX(1); + } +} + +@keyframes frontstage-rollout-spine-order-pulse { + 0%, + 100% { + transform: translateY(0); + } + + 50% { + transform: translateY(-0.125rem); + } +} + +@keyframes frontstage-rollout-layer-glow { + 0%, + 100% { + opacity: 0.35; + transform: scaleX(0.76); + } + + 50% { + opacity: 1; + transform: scaleX(1); + } +} + +@keyframes frontstage-rollout-node-pulse { + 0%, + 100% { + opacity: 0.42; + transform: scaleY(0.72); + } + + 50% { + opacity: 1; + transform: scaleY(1); + } +} + +@keyframes frontstage-rollout-stage-scan { + from { + transform: translateX(0); + } + + to { + transform: translateX(650%); + } +} + +@keyframes frontstage-rollout-edge-glow { + 0%, + 100% { + opacity: 0.48; + } + + 50% { + opacity: 1; + } +} + @media (prefers-reduced-motion: reduce) { .frontstage-showcase-motion-beam { animation: none; @@ -250,6 +1089,61 @@ select { } } +@media (prefers-reduced-motion: reduce) { + .frontstage-rollout-projection-constellation::after, + .frontstage-rollout-pr-mesh::after, + .frontstage-rollout-pr-mesh-edge, + .frontstage-rollout-pr-mesh-node, + .frontstage-rollout-requirement-spine::after, + .frontstage-rollout-spine-card::after, + .frontstage-rollout-spine-order, + .frontstage-rollout-capability-map::after, + .frontstage-rollout-layer-card::after, + .frontstage-rollout-node-dot, + .frontstage-rollout-stage-flow-beam, + .frontstage-rollout-edge::before { + animation: none; + } + + .frontstage-rollout-projection-constellation::after { + opacity: 0.2; + transform: none; + } + + .frontstage-rollout-capability-map::after { + opacity: 0.2; + transform: none; + } + + .frontstage-rollout-pr-mesh::after { + opacity: 0.2; + transform: none; + } + + .frontstage-rollout-pr-mesh-node { + transform: translate(-50%, -50%); + } + + .frontstage-rollout-requirement-spine::after { + opacity: 0.2; + transform: none; + } +} + +@media (max-width: 768px) { + .frontstage-rollout-node-grid { + grid-template-columns: repeat(10, minmax(0, 1fr)); + } + + .frontstage-rollout-stage-flow::before, + .frontstage-rollout-stage-flow-beam, + .frontstage-rollout-spine-track::before, + .frontstage-rollout-spine-card::before, + .frontstage-rollout-layer-rail::before { + display: none; + } +} + @media (max-width: 640px) { .frontstage-ops-command-strip { padding-inline: 0.875rem; diff --git a/apps/dashboard/src/views/frontstage-page.tsx b/apps/dashboard/src/views/frontstage-page.tsx index bc98da84..b2be6536 100644 --- a/apps/dashboard/src/views/frontstage-page.tsx +++ b/apps/dashboard/src/views/frontstage-page.tsx @@ -17,6 +17,7 @@ import { useQuery } from "@tanstack/react-query"; import { useEffect, useMemo, useState } from "react"; import showcaseCatalog from "../../../../docs/showcases/showcase-catalog.json"; +import rolloutProjectionFixture from "../../../../examples/fixtures/frontstage-rollout-projections.public.json"; import rolloutFixture from "../../../../examples/fixtures/long-horizon-self-iteration-rollout.public.json"; import { frontstageRoute } from "../router"; import { @@ -129,6 +130,215 @@ type LongHorizonRolloutFixture = { const selfIterationRollout = rolloutFixture as LongHorizonRolloutFixture; +type RolloutProjectionNodeState = "open" | "merged" | "closed" | "done" | "active" | "blocked" | "planned"; +type RolloutProjectionTone = BadgeTone; + +type RolloutProjectionMetric = { + helper: string; + label: string; + metric_id: string; + tone: RolloutProjectionTone; + value: string; +}; + +type RolloutProjectionMappingLayer = { + description: string; + edge_ids: string[]; + input: string; + label: string; + layer_id: string; + node_ids: string[]; + output: string; + role: string; + tone: RolloutProjectionTone; +}; + +type RolloutProjectionFlowSignal = { + description: string; + label: string; + signal_id: string; + source_node_ids: string[]; + tone: RolloutProjectionTone; + value: string; +}; + +type RolloutProjectionRelationshipSummary = { + count: number; + description: string; + kind: string; + label: string; +}; + +type RolloutProjectionAttentionHotspot = { + description: string; + edge_ids: string[]; + hotspot_id: string; + label: string; + node_ids: string[]; + severity: "low" | "medium" | "high"; +}; + +type RolloutTimelineTick = { + at: string; + label: string; + tick_id: string; +}; + +type RolloutTimeline = { + axis_kind: string; + description: string; + end_at?: string; + item_node_ids: string[]; + start_at?: string; + ticks?: RolloutTimelineTick[]; + timeline_id: string; + time_basis?: string; + title: string; + timezone?: string; + unit_label: string; + window_label: string; +}; + +type RolloutSequenceStep = { + label: string; + node_ids: string[]; + status: string; + step_id: string; +}; + +type RolloutSequenceUnit = { + lane_id: string; + node_ids: string[]; + order: number; + outcome: string; + requirement: string; + stage_steps: RolloutSequenceStep[]; + state: RolloutProjectionNodeState; + triggered_by: string; + unit_id: string; + unlocks: string[]; +}; + +type RolloutSequence = { + description: string; + sequence_id: string; + title: string; + units: RolloutSequenceUnit[]; +}; + +type RolloutProjectionNode = { + confidence?: string; + kind: string; + label: string; + lane_id: string; + node_id: string; + role?: string; + state: RolloutProjectionNodeState; + title: string; + completed_at?: string; + display_time?: string; + duration_label?: string; + occurred_at?: string; + started_at?: string; + timezone?: string; + url?: string; +}; + +type RolloutTimeMilestone = { + detail: string; + id: string; + label: string; + time: number | null; + time_label: string; +}; + +type RolloutProjectionStage = { + actor_scope: string; + confidence: string; + current: boolean; + description: string; + label: string; + stage_id: string; +}; + +type RolloutProjectionLane = { + label: string; + lane_id: string; + node_ids: string[]; + role: string; + summary: string; +}; + +type RolloutProjectionEdge = { + confidence: string; + edge_id: string; + edge_kind: string; + from_node_id: string; + label: string; + to_node_id: string; +}; + +type RolloutProjection = { + attention_hotspots?: RolloutProjectionAttentionHotspot[]; + edges: RolloutProjectionEdge[]; + frontend_acceptance: { + must_render: string[]; + }; + flow_signals?: RolloutProjectionFlowSignal[]; + lanes: RolloutProjectionLane[]; + mapping_layers?: RolloutProjectionMappingLayer[]; + metrics: RolloutProjectionMetric[]; + nodes: RolloutProjectionNode[]; + projection_id: string; + projection_kind: string; + scene: { + confidence: string; + explanation: string; + scene_id: string; + stage_label: string; + title: string; + why_current: string; + }; + relationship_summaries?: RolloutProjectionRelationshipSummary[]; + rollout_sequence?: RolloutSequence; + timeline?: RolloutTimeline; + source_contract: { + anchor_node_id: string; + claim_boundary: string; + next_projection_hint: string; + sample_window: string; + }; + stages: RolloutProjectionStage[]; + title: string; +}; + +type RolloutProjectionBundle = { + planned_projections?: Array<{ + projection_id: string; + reason: string; + status: string; + }>; + projection_model: { + description: string; + edge_contract: string; + node_contract: string; + optional_rich_sections?: string[]; + required_sections: string[]; + schema_version: string; + }; + projections: RolloutProjection[]; + schema_version: string; + truth_contract: { + evidence_floor: string; + projection_is_writable: boolean; + recompute_rule: string; + write_authority: string; + }; +}; + +const rolloutProjectionBundle = rolloutProjectionFixture as RolloutProjectionBundle; +const overnightPrProjection = rolloutProjectionBundle.projections[0]; + type ProjectionOption = { goalId: string; projection: GoalChannelProjection; @@ -228,125 +438,1287 @@ function countShowcaseStoryBeats() { ); } -function uniqueShowcaseDomains() { - return Array.from( - new Set(frontstageShowcases.map((item) => item.domain).filter((value): value is string => Boolean(value))), - ).sort(); -} - -function showcaseSearchText(item: ShowcaseFrontstageCase) { - return [ - item.title, - item.headline, - item.domain, - item.status, - item.evidence_boundary, - ...(item.feature_points ?? []), - ...(item.pattern_tags ?? []), - ...(item.frontend_card?.badges ?? []), - item.frontend_card?.primary_metric_hint, - item.frontend_card?.visual_metaphor, - ...(item.frontend_card?.story_beats ?? []), - ] - .filter(Boolean) - .join(" ") - .toLowerCase(); -} - -function showcaseCaseHref(item?: ShowcaseFrontstageCase) { - if (!item) { - return undefined; - } - if (item.interactive_page) { - return `https://huangruiteng.github.io/loopx/${item.interactive_page}`; +function uniqueShowcaseDomains() { + return Array.from( + new Set(frontstageShowcases.map((item) => item.domain).filter((value): value is string => Boolean(value))), + ).sort(); +} + +function showcaseSearchText(item: ShowcaseFrontstageCase) { + return [ + item.title, + item.headline, + item.domain, + item.status, + item.evidence_boundary, + ...(item.feature_points ?? []), + ...(item.pattern_tags ?? []), + ...(item.frontend_card?.badges ?? []), + item.frontend_card?.primary_metric_hint, + item.frontend_card?.visual_metaphor, + ...(item.frontend_card?.story_beats ?? []), + ] + .filter(Boolean) + .join(" ") + .toLowerCase(); +} + +function showcaseCaseHref(item?: ShowcaseFrontstageCase) { + if (!item) { + return undefined; + } + if (item.interactive_page) { + return `https://huangruiteng.github.io/loopx/${item.interactive_page}`; + } + if (item.case_page) { + return `https://github.com/huangruiteng/loopx/blob/main/${item.case_page}`; + } + return undefined; +} + +function uniqueClaimOwners(projection: GoalChannelProjection) { + return Array.from( + new Set( + [ + ...projection.agent_todos.map((todo) => todo.claimed_by), + ...projection.active_leases.map((lease) => lease.owner_agent), + ].filter((value): value is string => Boolean(value)), + ), + ); +} + +function formatNumber(value: number | undefined, fallback = "n/a") { + if (value === undefined) { + return fallback; + } + return new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 }).format(value); +} + +function formatRange(range: NumberRange | undefined, suffix = "") { + if (!range || range.low === undefined || range.high === undefined) { + return "n/a"; + } + 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 === "observed_public_metadata") { + return "success"; + } + if (confidence === "synthetic_bridge") { + return "warning"; + } + if (confidence.startsWith("inferred")) { + return "info"; + } + return "neutral"; +} + +function nodeStateTone(state: RolloutProjectionNodeState): BadgeTone { + if (state === "merged" || state === "done") { + return "success"; + } + if (state === "open" || state === "active") { + return "info"; + } + if (state === "blocked") { + return "danger"; + } + return "warning"; +} + +function sequenceStepTone(status: string): BadgeTone { + if (["done", "merged", "validated"].includes(status)) { + return "success"; + } + if (["active", "reviewing", "review"].includes(status)) { + return "warning"; + } + if (["queued", "planned"].includes(status)) { + return "neutral"; + } + return "info"; +} + +const trajectoryLaneTones = [ + { + ring: "border-cyan-300/40 bg-cyan-300/10 text-cyan-100", + card: "border-cyan-200 bg-cyan-50", + dot: "bg-cyan-300", + line: "bg-cyan-300/40", + }, + { + ring: "border-emerald-300/40 bg-emerald-300/10 text-emerald-100", + card: "border-emerald-200 bg-emerald-50", + dot: "bg-emerald-400", + line: "bg-emerald-300/40", + }, + { + ring: "border-amber-300/40 bg-amber-300/10 text-amber-100", + card: "border-amber-200 bg-amber-50", + dot: "bg-amber-400", + line: "bg-amber-300/40", + }, + { + ring: "border-rose-300/40 bg-rose-300/10 text-rose-100", + card: "border-rose-200 bg-rose-50", + dot: "bg-rose-400", + line: "bg-rose-300/40", + }, + { + ring: "border-violet-300/40 bg-violet-300/10 text-violet-100", + card: "border-violet-200 bg-violet-50", + dot: "bg-violet-400", + line: "bg-violet-300/40", + }, +]; + +function trajectoryLaneTone(index: number) { + return trajectoryLaneTones[index % trajectoryLaneTones.length]; +} + +function nodesById(projection: RolloutProjection) { + return new Map(projection.nodes.map((item) => [item.node_id, item])); +} + +function nodesForLane(projection: RolloutProjection, lane: RolloutProjectionLane) { + const byId = nodesById(projection); + return lane.node_ids.map((nodeId) => byId.get(nodeId)).filter((item): item is RolloutProjectionNode => Boolean(item)); +} + +function prNumberFromNode(node: RolloutProjectionNode) { + const match = node.node_id.match(/^pr_(\d+)$/); + return match ? Number(match[1]) : null; +} + +function rolloutOrderedNodes(projection: RolloutProjection) { + const byId = nodesById(projection); + if (projection.timeline?.item_node_ids.length) { + return projection.timeline.item_node_ids + .map((nodeId) => byId.get(nodeId)) + .filter((node): node is RolloutProjectionNode => Boolean(node)); + } + + return projection.nodes + .filter((node) => node.role !== "anchor" && node.role !== "precursor") + .sort((left, right) => { + const leftPr = prNumberFromNode(left); + const rightPr = prNumberFromNode(right); + if (leftPr !== null && rightPr !== null) { + return leftPr - rightPr; + } + return left.label.localeCompare(right.label); + }); +} + +function parseRolloutTime(value?: string) { + if (!value) { + return null; + } + const parsed = Date.parse(value); + return Number.isFinite(parsed) ? parsed : null; +} + +function rolloutNodeTime(node: RolloutProjectionNode) { + return parseRolloutTime(node.occurred_at) ?? parseRolloutTime(node.started_at) ?? parseRolloutTime(node.completed_at); +} + +function rolloutTimelineBounds(projection: RolloutProjection, nodes: RolloutProjectionNode[]) { + const nodeTimes = nodes.map(rolloutNodeTime).filter((value): value is number => value !== null); + const completedTimes = nodes + .map((node) => parseRolloutTime(node.completed_at)) + .filter((value): value is number => value !== null); + const start = parseRolloutTime(projection.timeline?.start_at) ?? (nodeTimes.length ? Math.min(...nodeTimes) : null); + const end = + parseRolloutTime(projection.timeline?.end_at) ?? + (completedTimes.length ? Math.max(...completedTimes) : nodeTimes.length ? Math.max(...nodeTimes) : null); + if (start === null || end === null || end <= start) { + return null; + } + return { end, start }; +} + +function rolloutTimeRatio(value: number | null, bounds: { end: number; start: number } | null, fallbackRatio: number) { + if (value === null || !bounds) { + return fallbackRatio; + } + const ratio = (value - bounds.start) / (bounds.end - bounds.start); + return Math.min(1, Math.max(0, ratio)); +} + +function rolloutTimelinePercent( + node: RolloutProjectionNode, + bounds: { end: number; start: number } | null, + index: number, + total: number, +) { + const fallback = total > 1 ? index / (total - 1) : 0.5; + return rolloutTimeRatio(rolloutNodeTime(node), bounds, fallback) * 100; +} + +function rolloutTickPercent(tick: RolloutTimelineTick, bounds: { end: number; start: number } | null) { + return rolloutTimeRatio(parseRolloutTime(tick.at), bounds, 0) * 100; +} + +function rolloutNodeTimeLabel(node?: RolloutProjectionNode) { + return node?.display_time ?? node?.started_at ?? node?.occurred_at ?? "time n/a"; +} + +function rolloutClockLabel(value?: string) { + const match = value?.match(/T(\d{2}:\d{2})/); + return match?.[1] ?? value ?? "n/a"; +} + +function rolloutNodeStartClock(node?: RolloutProjectionNode) { + return node?.display_time?.split(" -> ")[0] ?? rolloutClockLabel(node?.started_at ?? node?.occurred_at); +} + +function uniqueRolloutMilestones(items: RolloutTimeMilestone[]) { + const seen = new Set(); + return items.filter((item) => { + const key = `${item.label}:${item.detail}:${item.time_label}`; + if (item.time === null || seen.has(key)) { + return false; + } + seen.add(key); + return true; + }); +} + +function rolloutTimelineMilestones(nodes: RolloutProjectionNode[]): RolloutTimeMilestone[] { + const timedNodes = nodes + .map((node) => ({ node, time: rolloutNodeTime(node) })) + .filter((item): item is { node: RolloutProjectionNode; time: number } => item.time !== null) + .sort((left, right) => left.time - right.time); + if (!timedNodes.length) { + return []; + } + const first = timedNodes[0]; + const midpoint = timedNodes[Math.floor((timedNodes.length - 1) / 2)]; + const lastStart = timedNodes[timedNodes.length - 1]; + const latestCompletion = nodes + .map((node) => ({ node, time: parseRolloutTime(node.completed_at) })) + .filter((item): item is { node: RolloutProjectionNode; time: number } => item.time !== null) + .sort((left, right) => right.time - left.time)[0]; + + return uniqueRolloutMilestones([ + { + detail: first.node.label, + id: "start", + label: "start", + time: first.time, + time_label: rolloutNodeStartClock(first.node), + }, + { + detail: midpoint.node.label, + id: "midpoint", + label: "midpoint", + time: midpoint.time, + time_label: rolloutNodeStartClock(midpoint.node), + }, + { + detail: lastStart.node.label, + id: "last_start", + label: "last start", + time: lastStart.time, + time_label: rolloutNodeStartClock(lastStart.node), + }, + { + detail: latestCompletion?.node.label ?? lastStart.node.label, + id: "tail_done", + label: "tail done", + time: latestCompletion?.time ?? null, + time_label: rolloutClockLabel(latestCompletion?.node.completed_at), + }, + ]); +} + +function RolloutProjectionConstellation({ + bundle, + projection, +}: { + bundle: RolloutProjectionBundle; + projection: RolloutProjection; +}) { + const visibleNodes = projection.nodes + .filter((node) => node.role !== "anchor" && node.role !== "precursor") + .slice(0, 30); + + return ( +
+
+
+
+ Rollout projection +
+

+ {projection.scene.title} +

+

+ {projection.scene.explanation} +

+
+
+ {projection.scene.stage_label} + + {projection.scene.confidence} + +
+
+ +
+ {projection.metrics.map((metric) => ( +
+
+ + {metric.label} + + {metric.helper} +
+
{metric.value}
+
+ ))} +
+ + + +
+
+ Projection model: + {bundle.projection_model.required_sections.join(" / ")}. +
+
+ Evidence floor: + {bundle.truth_contract.evidence_floor}. +
+
+
+ ); +} + +type PrMeshEdge = { + edge_id: string; + edge_kind: "timeline" | "lane" | "explicit"; + from_node_id: string; + label: string; + to_node_id: string; +}; + +function meshEdgeTitle(edge: PrMeshEdge, byId: Map) { + const fromNode = byId.get(edge.from_node_id); + const toNode = byId.get(edge.to_node_id); + return `${edge.edge_kind}: ${fromNode?.label ?? edge.from_node_id} -> ${toNode?.label ?? edge.to_node_id}; ${edge.label}`; +} + +function RolloutRelationshipMesh({ projection }: { projection: RolloutProjection }) { + const orderedNodes = rolloutOrderedNodes(projection); + const byId = new Map(orderedNodes.map((node) => [node.node_id, node])); + const [hoveredEdgeId, setHoveredEdgeId] = useState(null); + const timelineBounds = rolloutTimelineBounds(projection, orderedNodes); + const timeTicks = projection.timeline?.ticks ?? []; + const laneRows = new Map( + projection.lanes.map((lane, index) => [lane.lane_id, { index, y: 70 + index * 112 }]), + ); + const positions = new Map(); + + for (const lane of projection.lanes) { + const laneNodes = lane.node_ids.map((nodeId) => byId.get(nodeId)).filter((node): node is RolloutProjectionNode => Boolean(node)); + const laneY = laneRows.get(lane.lane_id)?.y ?? 70; + let lastX = 140; + laneNodes.forEach((node, index) => { + const fallback = laneNodes.length > 1 ? index / (laneNodes.length - 1) : 0.5; + const ratio = rolloutTimeRatio(rolloutNodeTime(node), timelineBounds, fallback); + const timeX = 190 + ratio * 750; + const spacedX = Math.max(timeX, lastX + 76); + const x = Math.min(940, Math.max(190, spacedX)); + lastX = x; + positions.set(node.node_id, { x, y: laneY }); + }); + } + + const edgeMap = new Map(); + const setEdge = (edge: PrMeshEdge) => { + const key = `${edge.from_node_id}->${edge.to_node_id}`; + const current = edgeMap.get(key); + if (!current || edge.edge_kind === "explicit" || (edge.edge_kind === "lane" && current.edge_kind === "timeline")) { + edgeMap.set(key, edge); + } + }; + + orderedNodes.slice(0, -1).forEach((node, index) => { + const next = orderedNodes[index + 1]; + setEdge({ + edge_id: `timeline_${node.node_id}_${next.node_id}`, + edge_kind: "timeline", + from_node_id: node.node_id, + label: "overnight sequence", + to_node_id: next.node_id, + }); + }); + + for (const lane of projection.lanes) { + const laneNodes = lane.node_ids.map((nodeId) => byId.get(nodeId)).filter((node): node is RolloutProjectionNode => Boolean(node)); + laneNodes.slice(0, -1).forEach((node, index) => { + const next = laneNodes[index + 1]; + setEdge({ + edge_id: `lane_${lane.lane_id}_${node.node_id}_${next.node_id}`, + edge_kind: "lane", + from_node_id: node.node_id, + label: lane.label, + to_node_id: next.node_id, + }); + }); + } + + for (const edge of projection.edges) { + if (positions.has(edge.from_node_id) && positions.has(edge.to_node_id)) { + setEdge({ + edge_id: edge.edge_id, + edge_kind: "explicit", + from_node_id: edge.from_node_id, + label: edge.label, + to_node_id: edge.to_node_id, + }); + } + } + + const meshEdges = Array.from(edgeMap.values()); + const explicitEdgeCount = meshEdges.filter((edge) => edge.edge_kind === "explicit").length; + const laneFlowEdgeCount = meshEdges.filter((edge) => edge.edge_kind === "lane").length; + const timelineEdgeCount = meshEdges.filter((edge) => edge.edge_kind === "timeline").length; + const hoveredEdge = meshEdges.find((edge) => edge.edge_id === hoveredEdgeId) ?? null; + const timelineTitle = projection.timeline?.title ?? "Rollout timeline"; + const timelineWindow = projection.timeline?.window_label ?? projection.source_contract.sample_window; + const timelineUnitLabel = projection.timeline?.unit_label ?? "work nodes"; + const timelineAxis = projection.timeline?.axis_kind ?? "ordered"; + const timelineTimezone = projection.timeline?.timezone ?? "local"; + const timeMilestones = rolloutTimelineMilestones(orderedNodes); + + return ( +
+
+
+
+ {projection.title} +
+

+ A wall-clock rollout map, all at once +

+

+ Every work unit in {timelineWindow} is placed on a real time axis. Thin links preserve ordering, lane links + show parallel workstreams, and bright links mark explicit review or follow-up relationships. Hover nodes or + lines to inspect time, duration, and relationship details. +

+
+
+ {orderedNodes.length} {timelineUnitLabel} + {meshEdges.length} links + {explicitEdgeCount} explicit + {timelineTimezone} +
+
+ +
+
+
+
{timelineTitle}
+
{timelineWindow}
+
+ {projection.timeline?.time_basis ?? "work-unit order"} +
+
+ {timelineAxis} +
+
+ {timeTicks.map((tick) => ( + + {tick.label} + + ))} + {orderedNodes.map((node, index) => ( + + {node.label.replace("#", "")} + {node.display_time?.split(" -> ")[0] ?? ""} + + ))} +
+ {timeMilestones.length ? ( +
+ {timeMilestones.map((milestone) => ( + + {milestone.time_label} + + {milestone.label} {milestone.detail} + + + ))} +
+ ) : null} +
+ +
+
+ + + {meshEdges.map((edge, index) => { + const from = positions.get(edge.from_node_id); + const to = positions.get(edge.to_node_id); + if (!from || !to) { + return null; + } + const edgeTitle = meshEdgeTitle(edge, byId); + return ( + setHoveredEdgeId((current) => (current === edge.edge_id ? null : current))} + onFocus={() => setHoveredEdgeId(edge.edge_id)} + onMouseEnter={() => setHoveredEdgeId(edge.edge_id)} + onMouseLeave={() => setHoveredEdgeId((current) => (current === edge.edge_id ? null : current))} + role="listitem" + style={{ animationDelay: `${index * 30}ms` }} + tabIndex={0} + > + {edgeTitle} + setHoveredEdgeId(edge.edge_id)} + onMouseLeave={() => setHoveredEdgeId((current) => (current === edge.edge_id ? null : current))} + onPointerEnter={() => setHoveredEdgeId(edge.edge_id)} + onPointerLeave={() => setHoveredEdgeId((current) => (current === edge.edge_id ? null : current))} + x1={from.x} + x2={to.x} + y1={from.y} + y2={to.y} + /> + setHoveredEdgeId(edge.edge_id)} + onMouseLeave={() => setHoveredEdgeId((current) => (current === edge.edge_id ? null : current))} + onPointerEnter={() => setHoveredEdgeId(edge.edge_id)} + onPointerLeave={() => setHoveredEdgeId((current) => (current === edge.edge_id ? null : current))} + x1={from.x} + x2={to.x} + y1={from.y} + y2={to.y} + /> + + ); + })} + + + {meshEdges.map((edge) => { + const from = positions.get(edge.from_node_id); + const to = positions.get(edge.to_node_id); + if (!from || !to) { + return null; + } + const edgeTitle = meshEdgeTitle(edge, byId); + return ( +
+
+ +
+
+
Timeline links
+
{timelineEdgeCount}
+
+
+
Lane-flow links
+
{laneFlowEdgeCount}
+
+
+
Explicit review links
+
{explicitEdgeCount}
+
+
+
Public boundary
+
metadata only
+
+
+
+ ); +} + +function RolloutRequirementSpine({ projection }: { projection: RolloutProjection }) { + const sequence = projection.rollout_sequence; + if (!sequence) { + return null; + } + + const byId = nodesById(projection); + const units = [...sequence.units].sort((left, right) => left.order - right.order); + const unitIds = new Set(units.map((unit) => unit.unit_id)); + const referencedNodeCount = new Set(units.flatMap((unit) => unit.node_ids)).size; + + return ( +
+
+
+
+ Requirement rollout spine +
+

+ One demand unlocks the next +

+

+ {sequence.description} +

+
+
+ {units.length} requirements + {referencedNodeCount} public nodes +
+
+ +
+ {units.map((unit, index) => ( +
+ + {String(unit.order).padStart(2, "0")} + + + {unit.requirement} + {index < units.length - 1 ? -> : null} + +
+ ))} +
+ +
+ {units.map((unit, index) => { + const lane = projection.lanes.find((item) => item.lane_id === unit.lane_id); + const nodes = unit.node_ids.map((nodeId) => byId.get(nodeId)).filter((node): node is RolloutProjectionNode => Boolean(node)); + const nextUnits = unit.unlocks.filter((unitId) => unitIds.has(unitId)); + return ( +
+
+ + {String(unit.order).padStart(2, "0")} + + {unit.state} +
+

{unit.requirement}

+

+ Trigger: {unit.triggered_by} +

+

+ {unit.outcome} +

+
+ {nodes.map((node) => ( + + {node.label} + + + ))} +
+
+ {unit.stage_steps.map((step) => ( +
+ {step.label} + {step.status} +
+ ))} +
+
+ + lane {lane?.label ?? unit.lane_id} + + + unlocks {nextUnits.length ? nextUnits.map((unitId) => unitId.replace(/^req_/, "")).join(", ") : "next projection"} + +
+
+ ); + })} +
+
+ ); +} + +function hotspotSeverityTone(severity: RolloutProjectionAttentionHotspot["severity"]): BadgeTone { + if (severity === "high") { + return "danger"; } - if (item.case_page) { - return `https://github.com/huangruiteng/loopx/blob/main/${item.case_page}`; + if (severity === "medium") { + return "warning"; } - return undefined; + return "info"; } -function uniqueClaimOwners(projection: GoalChannelProjection) { - return Array.from( - new Set( - [ - ...projection.agent_todos.map((todo) => todo.claimed_by), - ...projection.active_leases.map((lease) => lease.owner_agent), - ].filter((value): value is string => Boolean(value)), - ), - ); -} +function RolloutProjectionCapabilityMap({ + bundle, + projection, +}: { + bundle: RolloutProjectionBundle; + projection: RolloutProjection; +}) { + const layers = projection.mapping_layers ?? []; + const signals = projection.flow_signals ?? []; + const relationshipSummaries = projection.relationship_summaries ?? []; + const hotspots = projection.attention_hotspots ?? []; -function formatNumber(value: number | undefined, fallback = "n/a") { - if (value === undefined) { - return fallback; - } - return new Intl.NumberFormat("en-US", { maximumFractionDigits: 1 }).format(value); -} + return ( +
+
+
+
+ Projection capability map +
+

+ Evidence becomes state, lanes, edges, and operator decisions +

+

+ The renderer reads a generic projection contract, then makes the transformation visible: source facts become + work nodes, nodes gain lifecycle state, lanes show parallel flow, edges explain why follow-up work exists, and + hotspots keep review attention from getting lost. +

+
+
+ {bundle.projection_model.schema_version} + {bundle.projection_model.optional_rich_sections?.length ?? 0} rich layers +
+
-function formatRange(range: NumberRange | undefined, suffix = "") { - if (!range || range.low === undefined || range.high === undefined) { - return "n/a"; - } - return `${formatNumber(range.low)}-${formatNumber(range.high)}${suffix}`; -} +
+ {layers.map((layer, index) => ( +
+
+ {String(index + 1).padStart(2, "0")} + {layer.output} +
+

{layer.label}

+

{layer.role}

+

{layer.description}

+
+ + in {layer.input} + + + refs {layer.node_ids.length} nodes / {layer.edge_ids.length} edges + +
+
+ ))} +
-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; -}; +
+
+
+
Flow signals
+ {signals.length} signals +
+
+ {signals.map((signal) => ( +
+
+ {signal.label} + {signal.value} +
+

{signal.description}

+
+ ))} +
+
-type TrajectoryEvidenceItem = { - eventTitle: string; - kind: "evidence_ref" | "source_event"; - ref: string; -}; +
+
+
+
Relationship grammar
+ {relationshipSummaries.length} kinds +
+
+ {relationshipSummaries.map((summary) => ( +
+
+ {summary.label} + {summary.count} +
+

{summary.description}

+
+ ))} +
+
-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"; +
+
+
Attention hotspots
+ {hotspots.length} visible +
+
+ {hotspots.map((hotspot) => ( +
+
+ {hotspot.label} + {hotspot.severity} +
+

{hotspot.description}

+
+ ))} +
+
+
+
+
+ ); } -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 RolloutProjectionStageFlow({ projection }: { projection: RolloutProjection }) { + return ( +
+
+
+
+ Actor / state stages +
+

+ One node can move; many lanes can compose +

+
+ stage projection +
+
+
+
+ ); +} + +function RolloutProjectionLaneGraph({ projection }: { projection: RolloutProjection }) { + const byId = nodesById(projection); + + return ( +
+
+
+
+
+ Rollout lane graph +
+

+ Lanes keep throughput reviewable +

+
+ {projection.lanes.length} lanes +
+
+ {projection.lanes.map((lane, index) => { + const tone = trajectoryLaneTone(index); + const nodes = nodesForLane(projection, lane); + return ( +
+
+
+
+ {lane.role} +
+

{lane.label}

+
+ {nodes.length} nodes +
+

{lane.summary}

+
+ {nodes.map((node) => ( + + {node.label} + + + ))} +
+
+ ); + })} +
+
+ +
+
+
+
+ Review edge mesh +
+

+ Generic edges explain why one node points at another. +

+
+ {projection.edges.length} edges +
+
+ {projection.edges.map((edge, index) => { + const fromNode = byId.get(edge.from_node_id); + const toNode = byId.get(edge.to_node_id); + return ( +
+
+ {fromNode?.label ?? edge.from_node_id} + -> + + {toNode?.label ?? edge.to_node_id} + + {edge.edge_kind} +
+

{edge.label}

+
+ {fromNode?.title ?? "upstream node"} + {toNode?.title ?? "downstream node"} +
+
+ ); + })} +
+
+
+ ); } function buildTrajectoryAnalysis(fixture: LongHorizonRolloutFixture) { @@ -665,8 +2037,11 @@ function EfficiencyEvidencePanel() { } function TrajectoryAnalysisPanel() { - const analysis = useMemo(() => buildTrajectoryAnalysis(selfIterationRollout), []); - const currentStage = analysis.currentStage; + const projection = overnightPrProjection; + const nodeMap = nodesById(projection); + const anchorNode = nodeMap.get(projection.source_contract.anchor_node_id); + const currentProjectionStage = projection.stages.find((stage) => stage.current) ?? projection.stages.at(-1); + const plannedProjection = rolloutProjectionBundle.planned_projections?.[0]; return ( @@ -674,6 +2049,11 @@ function TrajectoryAnalysisPanel() { className="grid gap-4 p-4 xl:grid-cols-[minmax(0,1fr)_minmax(300px,420px)]" data-testid="frontstage-trajectory-analysis" > + + + + +

- A projection fixture turns state transitions into a visible trajectory, not a raw trajectory replay. + The same projection model maps actor state, lane flow, and review edges without reading raw logs.

read-only projection - {analysis.observedCount} observed - - {analysis.syntheticCount} bridge - + {projection.nodes.length} nodes + {projection.edges.length} edges
- {analysis.stages.map((stage, index) => ( + {projection.stages.map((stage, index) => (
- {stage.stageLabel} + {stage.actor_scope} {String(index + 1).padStart(2, "0")}
@@ -742,25 +2117,42 @@ function TrajectoryAnalysisPanel() { data-testid="frontstage-trajectory-current-scene" >
- {currentStage?.laneRole ?? "agent"} - - {currentStage?.isSynthetic ? "needs evidence" : "observed"} - + {projection.projection_kind} + {projection.scene.confidence}

- {currentStage?.stageLabel ?? "Current stage"} + {projection.scene.stage_label}

- {currentStage?.title ?? "No stage projected"} + {projection.scene.title}

- {currentStage?.laneLabel ?? "No lane"} / {currentStage?.transitionLabel ?? "state retained"} + {currentProjectionStage?.label ?? "No current stage"} / {projection.source_contract.sample_window} +

+

+ {projection.scene.why_current} +

+
+ +
+
+ projection model + + {projection.scene.confidence} + +
+

+ {projection.scene.stage_label} +

+

+ {projection.scene.why_current} +

+

+ Anchor {anchorNode?.label ?? projection.source_contract.anchor_node_id}: {anchorNode?.role ?? "projection anchor"}.

- {currentStage?.inferenceReason ? ( -

- {currentStage.inferenceReason} -

- ) : null}
Verdict
-

{analysis.verdict}

+

+ {projection.source_contract.next_projection_hint} +

- The panel is derived from public rollout projections and keeps local prose, private docs, and raw trajectory logs out of the browser surface. + The renderer consumes only the projection bundle. Public GitHub metadata is enough for this batch; raw trajectories and local state stay outside the browser surface.

+ + +

- Source events and public evidence references explain every plotted stage. + Edges, model sections, and planned projections keep the rollout display explainable.

- {analysis.evidenceItems.length} refs + {projection.edges.length} edges
- {analysis.evidenceItems.map((item) => ( + {projection.edges.map((edge) => { + const fromNode = nodeMap.get(edge.from_node_id); + const toNode = nodeMap.get(edge.to_node_id); + return (
- {item.kind} - {item.eventTitle} + {edge.edge_kind} + {edge.confidence} + {edge.label} +
+
+ {fromNode?.label ?? edge.from_node_id} -> {toNode?.label ?? edge.to_node_id}
-
{item.ref}
- ))} + ); + })} + {plannedProjection ? ( +
+
+ {plannedProjection.status} + {plannedProjection.projection_id} +
+
{plannedProjection.reason}
+
+ ) : null}
diff --git a/examples/dashboard-frontstage-browser-smoke.mjs b/examples/dashboard-frontstage-browser-smoke.mjs index 87b7d94e..dde0e0c6 100644 --- a/examples/dashboard-frontstage-browser-smoke.mjs +++ b/examples/dashboard-frontstage-browser-smoke.mjs @@ -3,7 +3,7 @@ import { spawn } from "node:child_process"; import { createRequire } from "node:module"; -import { existsSync } from "node:fs"; +import { existsSync, readFileSync } from "node:fs"; import { mkdir, readFile, rm, writeFile } from "node:fs/promises"; import { homedir } from "node:os"; import { dirname, resolve } from "node:path"; @@ -17,8 +17,13 @@ const fixturePath = resolve(dashboardDir, "public", fixtureName); const privateTrapFixtureName = "status.frontstage.private-trap.json"; const privateTrapFixturePath = resolve(dashboardDir, "public", privateTrapFixtureName); const privateTrapSourcePath = resolve(repoRoot, "examples/fixtures/frontstage-private-status-trap.public.json"); +const showcaseCatalogPath = resolve(repoRoot, "docs/showcases/showcase-catalog.json"); const visualOutputDir = resolve(repoRoot, "output/playwright/dashboard-frontstage-visual-acceptance"); const port = Number(process.env.LOOPX_DASHBOARD_FRONTSTAGE_SMOKE_PORT ?? "5197"); +const showcaseCatalog = JSON.parse(readFileSync(showcaseCatalogPath, "utf8")); +const publicShowcaseCaseCount = showcaseCatalog.cases.filter((item) => item.frontend_card).length; +const allShowcaseCasesText = `Showing ${publicShowcaseCaseCount} of ${publicShowcaseCaseCount} public-safe cases`; +const selfIterationFilterText = `Showing 1 of ${publicShowcaseCaseCount} public-safe cases`; const fakePrivateTrapMarkers = [ "GH_FAKE_PRIVATE_STATUS_ALPHA", "GH_FAKE_PRIVATE_PLAN_SUMMARY_ALPHA", @@ -475,10 +480,42 @@ async function main() { "human gate: decision_frontstage_to_implementation_lane", "INFERRED DISPLAY BRIDGE", "Truth source: event ledger", + "ROLLOUT PROJECTION", + "Review mesh over a 30-PR public batch", + "PUBLIC SAMPLE", + "30 PRs", + "Projection model:", + "Overnight PR batch with reviewable control", + "A wall-clock rollout map, all at once", + "OVERNIGHT WALL-CLOCK TIMELINE", + "#746 -> #775 / 13:24-21:30 Asia/Shanghai", + "wall_clock", + "node.started_at positions each work unit", + "TIMELINE LINKS", + "LANE-FLOW LINKS", + "EXPLICIT REVIEW LINKS", + "REQUIREMENT ROLLOUT SPINE", + "One demand unlocks the next", + "Show frontstage trajectory as a reusable projection", + "Monitor tasks become due work instead of hidden polling", + "PROJECTION CAPABILITY MAP", + "Evidence becomes state, lanes, edges, and operator decisions", + "Source intake", + "State projection", + "Lane routing", + "Edge reasoning", + "Operator readout", + "FLOW SIGNALS", + "RELATIONSHIP GRAMMAR", + "ATTENTION HOTSPOTS", + "ACTOR / STATE STAGES", + "ROLLOUT LANE GRAPH", + "REVIEW EDGE MESH", + "Anchor #674", "ASYNCHRONOUS AGENT RHYTHM", "Agent teams work across turns and off-hours", "SEARCH PUBLIC SHOWCASES", - "Showing 3 of 3 public-safe cases", + allShowcaseCasesText, "Public Boundary", "Ops live only", "None in browser", @@ -518,9 +555,157 @@ async function main() { if (!showcaseMotionBeamBox || showcaseMotionBeamBox.width < 20) { throw new Error("Showcase motion traffic beam did not render"); } + const trajectoryParticles = await desktopPage + .locator('[data-testid="frontstage-rollout-node-particles"] span') + .count(); + if (trajectoryParticles !== 30) { + throw new Error(`Rollout projection should render 30 public PR nodes; saw ${trajectoryParticles}`); + } + const timelinePointCount = await desktopPage + .locator('[data-testid="frontstage-rollout-timeline-point"]') + .count(); + if (timelinePointCount !== 30) { + throw new Error(`Rollout timeline should render all 30 ordered work units; saw ${timelinePointCount}`); + } + const timelineTickCount = await desktopPage + .locator('[data-testid="frontstage-rollout-timeline-tick"]') + .count(); + if (timelineTickCount < 8) { + throw new Error(`Rollout wall-clock timeline should render hour ticks; saw ${timelineTickCount}`); + } + const firstTimelinePointTime = await desktopPage + .locator('[data-testid="frontstage-rollout-timeline-point"]') + .first() + .getAttribute("data-node-time"); + if (!firstTimelinePointTime?.includes("13:24")) { + throw new Error(`Rollout first timeline point should expose a concrete start time; saw ${firstTimelinePointTime}`); + } + const meshTimeTickCount = await desktopPage + .locator('[data-testid="frontstage-rollout-mesh-time-tick"]') + .count(); + if (meshTimeTickCount < 8) { + throw new Error(`Rollout mesh should render wall-clock grid ticks; saw ${meshTimeTickCount}`); + } + const timelineMilestoneText = await desktopPage + .locator('[data-testid="frontstage-rollout-time-milestones"]') + .innerText(); + if ( + !timelineMilestoneText.includes("13:24") || + !timelineMilestoneText.includes("tail done") || + !timelineMilestoneText.includes("21:30") + ) { + throw new Error(`Rollout wall-clock milestones did not expose concrete time anchors: ${timelineMilestoneText}`); + } + const prMeshNodeCount = await desktopPage + .locator('[data-testid="frontstage-rollout-mesh-node"]') + .count(); + if (prMeshNodeCount !== 30) { + throw new Error(`Rollout relationship mesh should render all 30 PR nodes; saw ${prMeshNodeCount}`); + } + const prMeshNodeTooltipCount = await desktopPage + .locator('[data-testid="frontstage-rollout-mesh-node-tooltip"]') + .count(); + if (prMeshNodeTooltipCount !== 30) { + throw new Error(`Rollout relationship mesh should render hover content for all 30 nodes; saw ${prMeshNodeTooltipCount}`); + } + const firstMeshNodeTime = await desktopPage + .locator('[data-testid="frontstage-rollout-mesh-node"]') + .first() + .getAttribute("data-node-time"); + if (!firstMeshNodeTime?.includes("13:24")) { + throw new Error(`Rollout mesh nodes should expose concrete time attributes; saw ${firstMeshNodeTime}`); + } + const prMeshEdgeCount = await desktopPage + .locator('[data-testid="frontstage-rollout-mesh-edge"]') + .count(); + if (prMeshEdgeCount < 40) { + throw new Error(`Rollout relationship mesh should render a dense link graph; saw ${prMeshEdgeCount}`); + } + const firstMeshEdge = desktopPage.locator('[data-testid="frontstage-rollout-mesh-edge"]').first(); + const firstMeshEdgeKind = await firstMeshEdge.getAttribute("data-edge-kind"); + const firstMeshEdgeLabel = await firstMeshEdge.getAttribute("data-edge-label"); + const firstMeshEdgeTitle = await firstMeshEdge.getAttribute("data-edge-title"); + if (!firstMeshEdgeKind || !firstMeshEdgeLabel || !firstMeshEdgeTitle?.includes("->")) { + throw new Error("Rollout mesh edge did not expose hover/data relationship properties"); + } + const prMeshEdgeHotspotCount = await desktopPage + .locator('[data-testid="frontstage-rollout-mesh-edge-hotspot"]') + .count(); + if (prMeshEdgeHotspotCount !== prMeshEdgeCount) { + throw new Error(`Rollout mesh should expose one hover hotspot per edge; saw ${prMeshEdgeHotspotCount}`); + } + await desktopPage.locator('[data-testid="frontstage-rollout-mesh-edge-hotspot"]').first().hover({ force: true }); + await desktopPage.waitForFunction( + ({ kind, label }) => { + const card = document.querySelector('[data-testid="frontstage-rollout-mesh-edge-hover-card"]'); + return Boolean(card?.textContent?.includes(kind) && card.textContent.includes(label)); + }, + { kind: firstMeshEdgeKind, label: firstMeshEdgeLabel }, + ); + const edgeHoverText = await desktopPage.locator('[data-testid="frontstage-rollout-mesh-edge-hover-card"]').innerText(); + if (!edgeHoverText.toLowerCase().includes(firstMeshEdgeKind) || !edgeHoverText.includes(firstMeshEdgeLabel)) { + throw new Error("Rollout mesh edge hover card did not show relationship details"); + } + if (!/\d{2}:\d{2}/.test(edgeHoverText) || !edgeHoverText.includes("from:") || !edgeHoverText.includes("to:")) { + throw new Error(`Rollout mesh edge hover card did not show from/to time details: ${edgeHoverText}`); + } + const prMeshLaneCount = await desktopPage + .locator('[data-testid="frontstage-rollout-mesh-lane"]') + .count(); + if (prMeshLaneCount < 5) { + throw new Error(`Rollout relationship mesh should render five lane labels; saw ${prMeshLaneCount}`); + } + const rolloutSequenceChipCount = await desktopPage + .locator('[data-testid="frontstage-rollout-sequence-chip"]') + .count(); + if (rolloutSequenceChipCount < 7) { + throw new Error(`Rollout sequence ribbon should render at least seven chips; saw ${rolloutSequenceChipCount}`); + } + const rolloutRequirementCount = await desktopPage + .locator('[data-testid="frontstage-rollout-requirement-unit"]') + .count(); + if (rolloutRequirementCount < 7) { + throw new Error(`Rollout sequence should render at least seven requirement units; saw ${rolloutRequirementCount}`); + } + const rolloutRequirementStepCount = await desktopPage + .locator('[data-testid="frontstage-rollout-requirement-step"]') + .count(); + if (rolloutRequirementStepCount < rolloutRequirementCount * 3) { + throw new Error( + `Rollout sequence should render several stage steps per requirement; saw ${rolloutRequirementStepCount}`, + ); + } + const mappingLayerCount = await desktopPage + .locator('[data-testid="frontstage-rollout-mapping-layer"]') + .count(); + if (mappingLayerCount < 5) { + throw new Error(`Rollout capability map should render five mapping layers; saw ${mappingLayerCount}`); + } + const flowSignalText = await desktopPage.locator('[data-testid="frontstage-rollout-flow-signals"]').innerText(); + if (!flowSignalText.includes("Throughput") || !flowSignalText.includes("Review resolution")) { + throw new Error("Rollout capability map did not surface the expected flow signals"); + } + const singleAgentStateCount = await desktopPage + .locator('[data-testid="frontstage-rollout-stage"]') + .count(); + if (singleAgentStateCount < 6) { + throw new Error(`Rollout stage flow should render at least six stages; saw ${singleAgentStateCount}`); + } + const multiAgentLaneCount = await desktopPage + .locator('[data-testid="frontstage-rollout-lane"]') + .count(); + if (multiAgentLaneCount < 5) { + throw new Error(`Rollout lane graph should render at least five lanes; saw ${multiAgentLaneCount}`); + } + const reviewEdgeCount = await desktopPage + .locator('[data-testid="frontstage-rollout-edge"]') + .count(); + if (reviewEdgeCount < 10) { + throw new Error(`Rollout review mesh should render ten review edges; saw ${reviewEdgeCount}`); + } const spotlight = desktopPage.locator('[data-testid="frontstage-showcase-spotlight"]'); const initialSpotlightText = await spotlight.innerText(); - if (!initialSpotlightText.includes("Blocked P0 with safe P1/P2 rotation")) { + if (!initialSpotlightText.includes("Overnight PR batch with reviewable control")) { throw new Error("Showcase spotlight did not default to the first public case"); } await desktopPage @@ -543,7 +728,7 @@ async function main() { throw new Error(`Showcase spotlight case link points outside public showcases: ${spotlightCaseHref}`); } await desktopPage.locator('[data-testid="frontstage-showcase-search"]').fill("self-iteration"); - await desktopPage.waitForFunction(() => document.body.innerText.includes("Showing 1 of 3 public-safe cases")); + await desktopPage.waitForFunction((text) => document.body.innerText.includes(text), selfIterationFilterText); const filteredCaseText = await desktopPage.locator('[data-testid="frontstage-showcase-cases"]').innerText(); if (!filteredCaseText.includes("LoopX self-iteration loop")) { throw new Error("Showcase search did not keep the self-iteration case visible"); @@ -554,7 +739,7 @@ async function main() { await desktopPage.locator('[data-testid="frontstage-showcase-search"]').fill("no-matching-showcase"); await desktopPage.waitForFunction(() => document.body.innerText.includes("No public showcase matched the current filters.")); await desktopPage.locator('[data-testid="frontstage-showcase-search"]').fill(""); - await desktopPage.waitForFunction(() => document.body.innerText.includes("Showing 3 of 3 public-safe cases")); + await desktopPage.waitForFunction((text) => document.body.innerText.includes(text), allShowcaseCasesText); await captureFrontstage( desktopPage, `${baseUrl}/frontstage?statusUrl=/${fixtureName}&goalId=live-goal-a`, diff --git a/examples/fixtures/frontstage-rollout-projections.public.json b/examples/fixtures/frontstage-rollout-projections.public.json new file mode 100644 index 00000000..183194d6 --- /dev/null +++ b/examples/fixtures/frontstage-rollout-projections.public.json @@ -0,0 +1,1527 @@ +{ + "schema_version": "frontstage_rollout_projection_bundle_v0", + "bundle_id": "frontstage_public_rollout_projection_bundle_v0", + "generated_at": "2026-06-27T21:18:00+08:00", + "purpose": "Public-safe display projections for turning rollout and trajectory evidence into reusable frontstage visuals.", + "truth_contract": { + "projection_is_writable": false, + "write_authority": "none", + "evidence_floor": "public GitHub PR metadata and public Git history", + "recompute_rule": "Regenerate projections from public event sources before treating them as current." + }, + "public_boundary": { + "raw_task_text_recorded": false, + "raw_logs_recorded": false, + "raw_trajectory_recorded": false, + "raw_session_transcript_recorded": false, + "credential_values_recorded": false, + "absolute_paths_recorded": false, + "private_material_body_recorded": false + }, + "projection_model": { + "schema_version": "frontstage_rollout_projection_model_v0", + "description": "A projection maps source evidence into metrics, actor/state stages, lanes, nodes, and edges. The frontstage renderer consumes this model without knowing whether the source was a PR batch, agent rollout, issue-fix loop, benchmark route, or long-running LoopX iteration.", + "required_sections": [ + "metrics", + "stages", + "lanes", + "nodes", + "edges" + ], + "optional_rich_sections": [ + "timeline", + "rollout_sequence", + "mapping_layers", + "flow_signals", + "relationship_summaries", + "attention_hotspots" + ], + "node_contract": "Nodes are compact public-safe work units such as pull_request, event, todo, gate, validation, or artifact.", + "edge_contract": "Edges are compact public-safe relationships such as review, followup, handoff, revert, validation, dependency, or successor." + }, + "projections": [ + { + "projection_id": "overnight_pr_batch_20260627", + "projection_kind": "pr_batch_rollout", + "title": "Overnight PR batch with reviewable control", + "scene": { + "scene_id": "scene_recent_pr_batch_review_mesh", + "title": "Review mesh over a 30-PR public batch", + "stage_label": "Multi-agent flow", + "confidence": "observed_public_metadata", + "explanation": "A generic rollout projection turns public PR metadata into a visible state graph: PR-sized slices move through runtime, product, benchmark, docs, and review lanes, then converge through validation and merge gates.", + "why_current": "This is the first concrete projection instance after the PR #674 trajectory panel: it exercises the generic renderer with a larger public PR/review batch." + }, + "source_contract": { + "claim_boundary": "Display projection only. Public GitHub PR metadata and public repository history are the evidence floor; raw logs, private chats, local state, and private planning material are excluded.", + "anchor_node_id": "pr_674", + "sample_window": "#746-#775", + "next_projection_hint": "LoopX overall iteration should reuse this projection model instead of adding another bespoke panel." + }, + "timeline": { + "timeline_id": "overnight_pr_wall_clock", + "title": "Overnight wall-clock timeline", + "axis_kind": "wall_clock", + "window_label": "#746 -> #775 / 13:24-21:30 Asia/Shanghai", + "unit_label": "PR nodes", + "description": "The timeline is a wall-clock public-safe work-unit axis. This instance positions PR nodes by public GitHub createdAt and shows completedAt/duration on hover; other rollout projections can provide issue, todo, run, or trace timestamps.", + "item_node_ids": [ + "pr_746", + "pr_747", + "pr_748", + "pr_749", + "pr_750", + "pr_751", + "pr_752", + "pr_753", + "pr_754", + "pr_755", + "pr_756", + "pr_757", + "pr_758", + "pr_759", + "pr_760", + "pr_761", + "pr_762", + "pr_763", + "pr_764", + "pr_765", + "pr_766", + "pr_767", + "pr_768", + "pr_769", + "pr_770", + "pr_771", + "pr_772", + "pr_773", + "pr_774", + "pr_775" + ], + "timezone": "Asia/Shanghai", + "start_at": "2026-06-27T13:24:51+08:00", + "end_at": "2026-06-27T21:30:06+08:00", + "time_basis": "node.started_at positions each work unit; node.completed_at and duration_label explain review/merge latency", + "ticks": [ + { + "tick_id": "tick_1400", + "label": "14:00", + "at": "2026-06-27T14:00:00+08:00" + }, + { + "tick_id": "tick_1500", + "label": "15:00", + "at": "2026-06-27T15:00:00+08:00" + }, + { + "tick_id": "tick_1600", + "label": "16:00", + "at": "2026-06-27T16:00:00+08:00" + }, + { + "tick_id": "tick_1700", + "label": "17:00", + "at": "2026-06-27T17:00:00+08:00" + }, + { + "tick_id": "tick_1800", + "label": "18:00", + "at": "2026-06-27T18:00:00+08:00" + }, + { + "tick_id": "tick_1900", + "label": "19:00", + "at": "2026-06-27T19:00:00+08:00" + }, + { + "tick_id": "tick_2000", + "label": "20:00", + "at": "2026-06-27T20:00:00+08:00" + }, + { + "tick_id": "tick_2100", + "label": "21:00", + "at": "2026-06-27T21:00:00+08:00" + } + ] + }, + "mapping_layers": [ + { + "layer_id": "source_intake", + "label": "Source intake", + "role": "Evidence becomes objects", + "description": "Public PR metadata is reduced into compact work-unit facts without importing raw chats, local state, or private traces.", + "input": "public PR metadata", + "output": "32 public nodes", + "tone": "info", + "node_ids": [ + "pr_674", + "pr_745", + "pr_746", + "pr_747", + "pr_748", + "pr_749", + "pr_750", + "pr_751", + "pr_752", + "pr_753", + "pr_754", + "pr_755", + "pr_756", + "pr_757", + "pr_758", + "pr_759", + "pr_760", + "pr_761", + "pr_762", + "pr_763", + "pr_764", + "pr_765", + "pr_766", + "pr_767", + "pr_768", + "pr_769", + "pr_770", + "pr_771", + "pr_772", + "pr_773", + "pr_774", + "pr_775" + ], + "edge_ids": [] + }, + { + "layer_id": "state_projection", + "label": "State projection", + "role": "Nodes gain lifecycle meaning", + "description": "Each work unit is mapped onto claim, plan, edit, validate, review, and resume stages so a single agent's progress is visible.", + "input": "nodes + public states", + "output": "6 actor/state stages", + "tone": "success", + "node_ids": [ + "pr_747", + "pr_754", + "pr_759", + "pr_764", + "pr_774", + "pr_775" + ], + "edge_ids": [] + }, + { + "layer_id": "lane_routing", + "label": "Lane routing", + "role": "Work becomes parallel lanes", + "description": "The same nodes are grouped into runtime, product, benchmark, docs, and open-review lanes so multi-agent flow is legible.", + "input": "node scopes", + "output": "5 lanes", + "tone": "neutral", + "node_ids": [ + "pr_747", + "pr_754", + "pr_750", + "pr_749", + "pr_751" + ], + "edge_ids": [] + }, + { + "layer_id": "edge_reasoning", + "label": "Edge reasoning", + "role": "Relationships explain motion", + "description": "Review, revert, validation, CLI, automation, and runtime followups form a mesh that explains why the next slice exists.", + "input": "public PR relationships", + "output": "10 explainable edges", + "tone": "warning", + "node_ids": [ + "pr_674", + "pr_752", + "pr_753", + "pr_759", + "pr_761", + "pr_764", + "pr_765", + "pr_766", + "pr_773", + "pr_774" + ], + "edge_ids": [ + "edge_trajectory_anchor_to_v2", + "edge_gallery_revert_review", + "edge_monitor_watch_to_due", + "edge_monitor_due_to_writeback", + "edge_monitor_writeback_to_smoke", + "edge_monitor_smoke_to_metadata", + "edge_command_onboarding_to_review" + ] + }, + { + "layer_id": "operator_readout", + "label": "Operator readout", + "role": "Display supports decisions", + "description": "The renderer highlights merged throughput, open review, follow-up chains, and the next reusable projection target.", + "input": "metrics + stages + lanes + edges", + "output": "reviewable frontstage story", + "tone": "info", + "node_ids": [ + "pr_775", + "pr_766", + "pr_765", + "pr_751" + ], + "edge_ids": [ + "edge_trajectory_anchor_to_v2", + "edge_monitor_smoke_to_metadata", + "edge_command_onboarding_to_review" + ] + } + ], + "flow_signals": [ + { + "signal_id": "throughput", + "label": "Throughput", + "value": "29 merged", + "description": "Nearly the whole public batch landed during the visible wall-clock window.", + "tone": "success", + "source_node_ids": [ + "pr_747", + "pr_754", + "pr_759", + "pr_764", + "pr_774" + ] + }, + { + "signal_id": "active_review", + "label": "Review resolution", + "value": "1 unmerged", + "description": "The projection keeps the closed/non-merged branch visible as a review outcome instead of hiding it.", + "tone": "warning", + "source_node_ids": [ + "pr_751", + "pr_765", + "pr_766", + "pr_775" + ] + }, + { + "signal_id": "cross_lane_edges", + "label": "Cross-lane edges", + "value": "7 chains", + "description": "Relationships cross product, runtime, docs, and benchmark lanes.", + "tone": "info", + "source_node_ids": [ + "pr_752", + "pr_753", + "pr_759", + "pr_761", + "pr_764", + "pr_765", + "pr_766", + "pr_773", + "pr_774" + ] + }, + { + "signal_id": "reusable_model", + "label": "Reusable model", + "value": "5 sections", + "description": "The same renderer can later map issue-fix, content ops, benchmark, or LoopX iteration rollouts.", + "tone": "neutral", + "source_node_ids": [ + "pr_674", + "pr_775" + ] + } + ], + "relationship_summaries": [ + { + "kind": "runtime_followup", + "count": 2, + "label": "Monitor/runtime chain", + "description": "Watch routing moves into due selection and poll writeback." + }, + { + "kind": "validation_followup", + "count": 1, + "label": "Validation chain", + "description": "Runtime API work is followed by smoke coverage." + }, + { + "kind": "showcase_followup", + "count": 2, + "label": "Showcase chain", + "description": "Public cases become pages and then a canonical showcase path." + }, + { + "kind": "review_revert", + "count": 1, + "label": "Review correction", + "description": "A public surface review creates a fast revert slice." + }, + { + "kind": "review_command_followup", + "count": 1, + "label": "Command loop", + "description": "Newcomer command path feeds an explicit PR review command." + } + ], + "attention_hotspots": [ + { + "hotspot_id": "open_runtime_chain", + "label": "Runtime chain resolved", + "severity": "low", + "description": "Monitor scheduler smoke and CLI metadata moved through review and merge; the chain remains visible as a completed follow-up path.", + "node_ids": [ + "pr_765", + "pr_766" + ], + "edge_ids": [ + "edge_monitor_writeback_to_smoke", + "edge_monitor_smoke_to_metadata" + ] + }, + { + "hotspot_id": "adapter_review", + "label": "Adapter review resolved", + "severity": "low", + "description": "The memory adapter contract also merged later in the same wall-clock window, showing how a long review branch rejoins the batch.", + "node_ids": [ + "pr_751" + ], + "edge_ids": [] + }, + { + "hotspot_id": "projection_next", + "label": "Next projection target", + "severity": "low", + "description": "After this PR batch projection is accepted, LoopX overall iteration can reuse the same renderer.", + "node_ids": [ + "pr_674", + "pr_775" + ], + "edge_ids": [ + "edge_trajectory_anchor_to_v2" + ] + } + ], + "rollout_sequence": { + "sequence_id": "overnight_requirement_rollout_spine", + "title": "Requirement rollout spine", + "description": "One demand unlocks the next: each requirement becomes a reviewable slice, validates against the public boundary, and leaves the next requirement visible.", + "units": [ + { + "unit_id": "req_frontstage_projection", + "order": 1, + "requirement": "Show frontstage trajectory as a reusable projection", + "triggered_by": "owner presentation need", + "outcome": "PR #674 anchors the trajectory panel.", + "state": "merged", + "lane_id": "product_frontstage", + "node_ids": [ + "pr_674" + ], + "unlocks": [ + "req_public_showcase_case_set" + ], + "stage_steps": [ + { + "step_id": "intake", + "label": "Intake", + "status": "done", + "node_ids": [ + "pr_674" + ] + }, + { + "step_id": "panel", + "label": "Panel", + "status": "done", + "node_ids": [ + "pr_674" + ] + }, + { + "step_id": "review", + "label": "Review", + "status": "done", + "node_ids": [ + "pr_674" + ] + } + ] + }, + { + "unit_id": "req_public_showcase_case_set", + "order": 2, + "requirement": "Turn reusable cases into public showcase pages", + "triggered_by": "frontstage needs inspectable examples", + "outcome": "Case set, showcase pages, and canonical path landed.", + "state": "merged", + "lane_id": "public_docs_showcase", + "node_ids": [ + "pr_745", + "pr_752", + "pr_753", + "pr_756", + "pr_771" + ], + "unlocks": [ + "req_update_notes_loop" + ], + "stage_steps": [ + { + "step_id": "case_set", + "label": "Case set", + "status": "done", + "node_ids": [ + "pr_745" + ] + }, + { + "step_id": "pages", + "label": "Pages", + "status": "done", + "node_ids": [ + "pr_756" + ] + }, + { + "step_id": "surface_review", + "label": "Surface review", + "status": "done", + "node_ids": [ + "pr_752", + "pr_753" + ] + }, + { + "step_id": "canonical", + "label": "Canonical path", + "status": "done", + "node_ids": [ + "pr_771" + ] + } + ] + }, + { + "unit_id": "req_update_notes_loop", + "order": 3, + "requirement": "Publish biweekly update notes without relying on memory", + "triggered_by": "operator wants durable product rhythm", + "outcome": "Archive plus draft PR automation are now visible.", + "state": "merged", + "lane_id": "product_frontstage", + "node_ids": [ + "pr_754", + "pr_758" + ], + "unlocks": [ + "req_monitor_scheduler_due_work" + ], + "stage_steps": [ + { + "step_id": "archive", + "label": "Archive", + "status": "done", + "node_ids": [ + "pr_754" + ] + }, + { + "step_id": "automation", + "label": "Draft PR job", + "status": "done", + "node_ids": [ + "pr_758" + ] + }, + { + "step_id": "monitor", + "label": "Monitor hook", + "status": "done", + "node_ids": [ + "pr_758" + ] + } + ] + }, + { + "unit_id": "req_monitor_scheduler_due_work", + "order": 4, + "requirement": "Monitor tasks become due work instead of hidden polling", + "triggered_by": "continuous monitors need explicit cadence", + "outcome": "Watch routing, due selection, and writeback landed; smoke and CLI metadata remain open.", + "state": "open", + "lane_id": "kernel_runtime", + "node_ids": [ + "pr_759", + "pr_761", + "pr_764", + "pr_765", + "pr_766" + ], + "unlocks": [ + "req_skillsbench_goal_start_bridge" + ], + "stage_steps": [ + { + "step_id": "watch_route", + "label": "Watch route", + "status": "done", + "node_ids": [ + "pr_759" + ] + }, + { + "step_id": "due_select", + "label": "Due select", + "status": "done", + "node_ids": [ + "pr_761" + ] + }, + { + "step_id": "writeback", + "label": "Writeback", + "status": "done", + "node_ids": [ + "pr_764" + ] + }, + { + "step_id": "smoke", + "label": "Smoke", + "status": "active", + "node_ids": [ + "pr_765" + ] + }, + { + "step_id": "metadata", + "label": "CLI metadata", + "status": "active", + "node_ids": [ + "pr_766" + ] + } + ] + }, + { + "unit_id": "req_skillsbench_goal_start_bridge", + "order": 5, + "requirement": "Keep goal-start benchmark routing attributed while product work evolves", + "triggered_by": "solver lifecycle needs a stable bridge", + "outcome": "Goal-start bridge, closeout attribution, watchdog, and ledger updates landed.", + "state": "merged", + "lane_id": "benchmark_interface", + "node_ids": [ + "pr_750", + "pr_755", + "pr_757", + "pr_760", + "pr_762", + "pr_767", + "pr_770", + "pr_772" + ], + "unlocks": [ + "req_command_review_loop" + ], + "stage_steps": [ + { + "step_id": "preserve", + "label": "Preserve metadata", + "status": "done", + "node_ids": [ + "pr_750" + ] + }, + { + "step_id": "host_shape", + "label": "Host shape", + "status": "done", + "node_ids": [ + "pr_755" + ] + }, + { + "step_id": "watchdog", + "label": "Watchdog", + "status": "done", + "node_ids": [ + "pr_760" + ] + }, + { + "step_id": "ledger", + "label": "Ledger", + "status": "done", + "node_ids": [ + "pr_762", + "pr_767" + ] + }, + { + "step_id": "bootstrap", + "label": "Bootstrap", + "status": "done", + "node_ids": [ + "pr_770", + "pr_772" + ] + } + ] + }, + { + "unit_id": "req_command_review_loop", + "order": 6, + "requirement": "Let users drive review and newcomer commands explicitly", + "triggered_by": "slash commands should be product entry points", + "outcome": "Newcomer command path feeds an explicit PR review command.", + "state": "merged", + "lane_id": "product_frontstage", + "node_ids": [ + "pr_773", + "pr_774" + ], + "unlocks": [ + "req_profile_handoff_route" + ], + "stage_steps": [ + { + "step_id": "newcomer_path", + "label": "Newcomer path", + "status": "done", + "node_ids": [ + "pr_773" + ] + }, + { + "step_id": "review_command", + "label": "Review command", + "status": "done", + "node_ids": [ + "pr_774" + ] + }, + { + "step_id": "feedback_loop", + "label": "Feedback loop", + "status": "done", + "node_ids": [ + "pr_773", + "pr_774" + ] + } + ] + }, + { + "unit_id": "req_profile_handoff_route", + "order": 7, + "requirement": "Route side-agent handoffs by profile instead of prose", + "triggered_by": "multi-agent rollout needs a clean next receiver", + "outcome": "PR #775 keeps the open handoff route visible for review.", + "state": "open", + "lane_id": "kernel_runtime", + "node_ids": [ + "pr_775" + ], + "unlocks": [], + "stage_steps": [ + { + "step_id": "handoff", + "label": "Handoff", + "status": "active", + "node_ids": [ + "pr_775" + ] + }, + { + "step_id": "route", + "label": "Route", + "status": "active", + "node_ids": [ + "pr_775" + ] + }, + { + "step_id": "review", + "label": "Review", + "status": "active", + "node_ids": [ + "pr_775" + ] + } + ] + } + ] + }, + "metrics": [ + { + "metric_id": "public_sample", + "label": "Public sample", + "value": "30 PRs", + "helper": "#746-#775", + "tone": "info" + }, + { + "metric_id": "merged", + "label": "Merged", + "value": "29", + "helper": "public PR metadata", + "tone": "success" + }, + { + "metric_id": "open_review", + "label": "Unmerged", + "value": "1", + "helper": "0 open / 1 closed", + "tone": "warning" + }, + { + "metric_id": "review_edges", + "label": "Review edges", + "value": "10", + "helper": "follow-up relationships", + "tone": "neutral" + } + ], + "stages": [ + { + "stage_id": "claimed", + "label": "Claim", + "description": "Pick one scoped todo and reserve the lane.", + "confidence": "observed", + "actor_scope": "single_agent", + "current": false + }, + { + "stage_id": "planning", + "label": "Plan", + "description": "Split the target into ordered public-safe slices.", + "confidence": "observed", + "actor_scope": "single_agent", + "current": false + }, + { + "stage_id": "editing", + "label": "Edit", + "description": "Apply a narrow patch against one product or runtime surface.", + "confidence": "observed", + "actor_scope": "single_agent", + "current": false + }, + { + "stage_id": "validating", + "label": "Validate", + "description": "Run focused smokes and boundary checks before writeback.", + "confidence": "observed", + "actor_scope": "single_agent", + "current": false + }, + { + "stage_id": "reviewing", + "label": "Review", + "description": "Attach review packet, handoff, or self-merge decision.", + "confidence": "observed_public_metadata", + "actor_scope": "single_agent", + "current": true + }, + { + "stage_id": "resuming", + "label": "Resume", + "description": "Use the next public todo or monitor due signal for the following loop.", + "confidence": "inferred_high", + "actor_scope": "single_agent", + "current": false + } + ], + "lanes": [ + { + "lane_id": "kernel_runtime", + "label": "Kernel runtime", + "role": "product + runtime lane", + "summary": "Status, quota, monitor, handoff, and todo projection contracts.", + "node_ids": [ + "pr_747", + "pr_748", + "pr_759", + "pr_761", + "pr_764", + "pr_765", + "pr_766", + "pr_775" + ] + }, + { + "lane_id": "product_frontstage", + "label": "Product frontstage", + "role": "product surface lane", + "summary": "Public showcase pages, trajectory panels, command onboarding, and update notes.", + "node_ids": [ + "pr_754", + "pr_756", + "pr_758", + "pr_769", + "pr_771", + "pr_773", + "pr_774" + ] + }, + { + "lane_id": "benchmark_interface", + "label": "Benchmark interface", + "role": "benchmark contract lane", + "summary": "Goal-start bridge, runner attribution, reducer preservation, and latest ledger updates.", + "node_ids": [ + "pr_750", + "pr_755", + "pr_757", + "pr_760", + "pr_762", + "pr_767", + "pr_770", + "pr_772" + ] + }, + { + "lane_id": "public_docs_showcase", + "label": "Docs and showcase", + "role": "evidence lane", + "summary": "Public-safe case set, local agent launch protocol, release readiness, and adoption loop.", + "node_ids": [ + "pr_749", + "pr_752", + "pr_753", + "pr_768" + ] + }, + { + "lane_id": "open_review", + "label": "Open review lane", + "role": "open review lane", + "summary": "Adapter and branch work that stays visible as a review gate.", + "node_ids": [ + "pr_746", + "pr_751", + "pr_763" + ] + } + ], + "nodes": [ + { + "node_id": "pr_674", + "kind": "pull_request", + "label": "#674", + "title": "feat(dashboard): add frontstage trajectory analysis", + "state": "merged", + "lane_id": "product_frontstage", + "url": "https://github.com/huangruiteng/loopx/pull/674", + "role": "anchor", + "confidence": "observed_public_metadata" + }, + { + "node_id": "pr_745", + "kind": "pull_request", + "label": "#745", + "title": "Add public-safe showcase case set", + "state": "merged", + "lane_id": "public_docs_showcase", + "url": "https://github.com/huangruiteng/loopx/pull/745", + "role": "precursor", + "confidence": "observed_public_metadata" + }, + { + "node_id": "pr_775", + "kind": "pull_request", + "label": "#775", + "title": "Fix profile-scoped side-agent handoff routing", + "state": "merged", + "lane_id": "kernel_runtime", + "url": "https://github.com/huangruiteng/loopx/pull/775", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T20:50:03+08:00", + "completed_at": "2026-06-27T21:23:04+08:00", + "occurred_at": "2026-06-27T20:50:03+08:00", + "display_time": "20:50 -> 21:23", + "duration_label": "33m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_774", + "kind": "pull_request", + "label": "#774", + "title": "Add PR review slash command", + "state": "merged", + "lane_id": "product_frontstage", + "url": "https://github.com/huangruiteng/loopx/pull/774", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T20:47:10+08:00", + "completed_at": "2026-06-27T21:12:31+08:00", + "occurred_at": "2026-06-27T20:47:10+08:00", + "display_time": "20:47 -> 21:12", + "duration_label": "25m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_773", + "kind": "pull_request", + "label": "#773", + "title": "docs: add newcomer command path", + "state": "merged", + "lane_id": "product_frontstage", + "url": "https://github.com/huangruiteng/loopx/pull/773", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T19:58:53+08:00", + "completed_at": "2026-06-27T20:31:10+08:00", + "occurred_at": "2026-06-27T19:58:53+08:00", + "display_time": "19:58 -> 20:31", + "duration_label": "32m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_772", + "kind": "pull_request", + "label": "#772", + "title": "Bootstrap SkillsBench goal-start bridge before task", + "state": "merged", + "lane_id": "benchmark_interface", + "url": "https://github.com/huangruiteng/loopx/pull/772", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T19:48:30+08:00", + "completed_at": "2026-06-27T19:49:10+08:00", + "occurred_at": "2026-06-27T19:48:30+08:00", + "display_time": "19:48 -> 19:49", + "duration_label": "40 sec", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_771", + "kind": "pull_request", + "label": "#771", + "title": "docs: add public showcase pages and experimental value path", + "state": "merged", + "lane_id": "product_frontstage", + "url": "https://github.com/huangruiteng/loopx/pull/771", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T19:45:25+08:00", + "completed_at": "2026-06-27T21:07:26+08:00", + "occurred_at": "2026-06-27T19:45:25+08:00", + "display_time": "19:45 -> 21:07", + "duration_label": "1h 22m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_770", + "kind": "pull_request", + "label": "#770", + "title": "Fix SkillsBench goal-start host-local attribution", + "state": "merged", + "lane_id": "benchmark_interface", + "url": "https://github.com/huangruiteng/loopx/pull/770", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T19:31:35+08:00", + "completed_at": "2026-06-27T19:34:32+08:00", + "occurred_at": "2026-06-27T19:31:35+08:00", + "display_time": "19:31 -> 19:34", + "duration_label": "2m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_769", + "kind": "pull_request", + "label": "#769", + "title": "Add public adoption loop contract", + "state": "merged", + "lane_id": "product_frontstage", + "url": "https://github.com/huangruiteng/loopx/pull/769", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T19:10:17+08:00", + "completed_at": "2026-06-27T19:10:57+08:00", + "occurred_at": "2026-06-27T19:10:17+08:00", + "display_time": "19:10 -> 19:10", + "duration_label": "40 sec", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_768", + "kind": "pull_request", + "label": "#768", + "title": "Add release readiness mental model", + "state": "merged", + "lane_id": "public_docs_showcase", + "url": "https://github.com/huangruiteng/loopx/pull/768", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T18:59:20+08:00", + "completed_at": "2026-06-27T19:00:27+08:00", + "occurred_at": "2026-06-27T18:59:20+08:00", + "display_time": "18:59 -> 19:00", + "duration_label": "1m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_767", + "kind": "pull_request", + "label": "#767", + "title": "Update SkillsBench goal-start ledger status", + "state": "merged", + "lane_id": "benchmark_interface", + "url": "https://github.com/huangruiteng/loopx/pull/767", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T18:45:55+08:00", + "completed_at": "2026-06-27T18:47:26+08:00", + "occurred_at": "2026-06-27T18:45:55+08:00", + "display_time": "18:45 -> 18:47", + "duration_label": "1m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_766", + "kind": "pull_request", + "label": "#766", + "title": "Add monitor cadence todo CLI metadata", + "state": "merged", + "lane_id": "kernel_runtime", + "url": "https://github.com/huangruiteng/loopx/pull/766", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T18:36:48+08:00", + "completed_at": "2026-06-27T21:30:06+08:00", + "occurred_at": "2026-06-27T18:36:48+08:00", + "display_time": "18:36 -> 21:30", + "duration_label": "2h 53m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_765", + "kind": "pull_request", + "label": "#765", + "title": "Add monitor scheduler smoke coverage", + "state": "merged", + "lane_id": "kernel_runtime", + "url": "https://github.com/huangruiteng/loopx/pull/765", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T18:25:07+08:00", + "completed_at": "2026-06-27T21:28:57+08:00", + "occurred_at": "2026-06-27T18:25:07+08:00", + "display_time": "18:25 -> 21:28", + "duration_label": "3h 3m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_764", + "kind": "pull_request", + "label": "#764", + "title": "Add monitor-poll todo writeback API", + "state": "merged", + "lane_id": "kernel_runtime", + "url": "https://github.com/huangruiteng/loopx/pull/764", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T18:17:16+08:00", + "completed_at": "2026-06-27T18:17:56+08:00", + "occurred_at": "2026-06-27T18:17:16+08:00", + "display_time": "18:17 -> 18:17", + "duration_label": "40 sec", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_763", + "kind": "pull_request", + "label": "#763", + "title": "Add monitor-poll todo writeback API", + "state": "closed", + "lane_id": "open_review", + "url": "https://github.com/huangruiteng/loopx/pull/763", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T18:01:18+08:00", + "completed_at": "2026-06-27T18:06:42+08:00", + "occurred_at": "2026-06-27T18:01:18+08:00", + "display_time": "18:01 -> 18:06", + "duration_label": "5m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_762", + "kind": "pull_request", + "label": "#762", + "title": "Update SkillsBench latest case ledger", + "state": "merged", + "lane_id": "benchmark_interface", + "url": "https://github.com/huangruiteng/loopx/pull/762", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T17:57:39+08:00", + "completed_at": "2026-06-27T17:58:31+08:00", + "occurred_at": "2026-06-27T17:57:39+08:00", + "display_time": "17:57 -> 17:58", + "duration_label": "52 sec", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_761", + "kind": "pull_request", + "label": "#761", + "title": "Add due monitor work-lane selection", + "state": "merged", + "lane_id": "kernel_runtime", + "url": "https://github.com/huangruiteng/loopx/pull/761", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T17:45:10+08:00", + "completed_at": "2026-06-27T18:06:40+08:00", + "occurred_at": "2026-06-27T17:45:10+08:00", + "display_time": "17:45 -> 18:06", + "duration_label": "21m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_760", + "kind": "pull_request", + "label": "#760", + "title": "Fix SkillsBench goal-start first-action watchdog", + "state": "merged", + "lane_id": "benchmark_interface", + "url": "https://github.com/huangruiteng/loopx/pull/760", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T17:27:43+08:00", + "completed_at": "2026-06-27T17:28:29+08:00", + "occurred_at": "2026-06-27T17:27:43+08:00", + "display_time": "17:27 -> 17:28", + "duration_label": "46 sec", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_759", + "kind": "pull_request", + "label": "#759", + "title": "Fix status watch routing for monitor classifications", + "state": "merged", + "lane_id": "kernel_runtime", + "url": "https://github.com/huangruiteng/loopx/pull/759", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T17:02:53+08:00", + "completed_at": "2026-06-27T17:22:49+08:00", + "occurred_at": "2026-06-27T17:02:53+08:00", + "display_time": "17:02 -> 17:22", + "duration_label": "19m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_758", + "kind": "pull_request", + "label": "#758", + "title": "Add update notes draft PR automation", + "state": "merged", + "lane_id": "product_frontstage", + "url": "https://github.com/huangruiteng/loopx/pull/758", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T16:12:32+08:00", + "completed_at": "2026-06-27T16:14:49+08:00", + "occurred_at": "2026-06-27T16:12:32+08:00", + "display_time": "16:12 -> 16:14", + "duration_label": "2m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_757", + "kind": "pull_request", + "label": "#757", + "title": "Refine SkillsBench closeout attribution", + "state": "merged", + "lane_id": "benchmark_interface", + "url": "https://github.com/huangruiteng/loopx/pull/757", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T16:07:09+08:00", + "completed_at": "2026-06-27T16:07:40+08:00", + "occurred_at": "2026-06-27T16:07:09+08:00", + "display_time": "16:07 -> 16:07", + "duration_label": "31 sec", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_756", + "kind": "pull_request", + "label": "#756", + "title": "Add public showcase case pages", + "state": "merged", + "lane_id": "product_frontstage", + "url": "https://github.com/huangruiteng/loopx/pull/756", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T15:54:59+08:00", + "completed_at": "2026-06-27T15:59:37+08:00", + "occurred_at": "2026-06-27T15:54:59+08:00", + "display_time": "15:54 -> 15:59", + "duration_label": "4m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_755", + "kind": "pull_request", + "label": "#755", + "title": "Handle SkillsBench host-local ACP return shape", + "state": "merged", + "lane_id": "benchmark_interface", + "url": "https://github.com/huangruiteng/loopx/pull/755", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T15:38:48+08:00", + "completed_at": "2026-06-27T15:39:21+08:00", + "occurred_at": "2026-06-27T15:38:48+08:00", + "display_time": "15:38 -> 15:39", + "duration_label": "33 sec", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_754", + "kind": "pull_request", + "label": "#754", + "title": "Add biweekly update note archive", + "state": "merged", + "lane_id": "product_frontstage", + "url": "https://github.com/huangruiteng/loopx/pull/754", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T15:23:04+08:00", + "completed_at": "2026-06-27T16:01:58+08:00", + "occurred_at": "2026-06-27T15:23:04+08:00", + "display_time": "15:23 -> 16:01", + "duration_label": "38m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_753", + "kind": "pull_request", + "label": "#753", + "title": "Revert \"Add bilingual showcase gallery pages\"", + "state": "merged", + "lane_id": "public_docs_showcase", + "url": "https://github.com/huangruiteng/loopx/pull/753", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T15:12:29+08:00", + "completed_at": "2026-06-27T15:12:40+08:00", + "occurred_at": "2026-06-27T15:12:29+08:00", + "display_time": "15:12 -> 15:12", + "duration_label": "11 sec", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_752", + "kind": "pull_request", + "label": "#752", + "title": "Add bilingual showcase gallery pages", + "state": "merged", + "lane_id": "public_docs_showcase", + "url": "https://github.com/huangruiteng/loopx/pull/752", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T15:01:25+08:00", + "completed_at": "2026-06-27T15:03:10+08:00", + "occurred_at": "2026-06-27T15:01:25+08:00", + "display_time": "15:01 -> 15:03", + "duration_label": "1m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_751", + "kind": "pull_request", + "label": "#751", + "title": "Add OpenViking session memory adapter contract", + "state": "merged", + "lane_id": "open_review", + "url": "https://github.com/huangruiteng/loopx/pull/751", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T14:25:02+08:00", + "completed_at": "2026-06-27T21:16:15+08:00", + "occurred_at": "2026-06-27T14:25:02+08:00", + "display_time": "14:25 -> 21:16", + "duration_label": "6h 51m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_750", + "kind": "pull_request", + "label": "#750", + "title": "Preserve SkillsBench failure closeout metadata", + "state": "merged", + "lane_id": "benchmark_interface", + "url": "https://github.com/huangruiteng/loopx/pull/750", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T14:16:40+08:00", + "completed_at": "2026-06-27T14:17:11+08:00", + "occurred_at": "2026-06-27T14:16:40+08:00", + "display_time": "14:16 -> 14:17", + "duration_label": "31 sec", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_749", + "kind": "pull_request", + "label": "#749", + "title": "Add local agent launch dry-run protocol", + "state": "merged", + "lane_id": "public_docs_showcase", + "url": "https://github.com/huangruiteng/loopx/pull/749", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T14:06:17+08:00", + "completed_at": "2026-06-27T14:33:21+08:00", + "occurred_at": "2026-06-27T14:06:17+08:00", + "display_time": "14:06 -> 14:33", + "duration_label": "27m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_748", + "kind": "pull_request", + "label": "#748", + "title": "Add agent lane frontier hint contract", + "state": "merged", + "lane_id": "kernel_runtime", + "url": "https://github.com/huangruiteng/loopx/pull/748", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T14:03:52+08:00", + "completed_at": "2026-06-27T14:47:54+08:00", + "occurred_at": "2026-06-27T14:03:52+08:00", + "display_time": "14:03 -> 14:47", + "duration_label": "44m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_747", + "kind": "pull_request", + "label": "#747", + "title": "Fix refresh-state Next Action default", + "state": "merged", + "lane_id": "kernel_runtime", + "url": "https://github.com/huangruiteng/loopx/pull/747", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T13:52:32+08:00", + "completed_at": "2026-06-27T15:53:58+08:00", + "occurred_at": "2026-06-27T13:52:32+08:00", + "display_time": "13:52 -> 15:53", + "duration_label": "2h 1m", + "timezone": "Asia/Shanghai" + }, + { + "node_id": "pr_746", + "kind": "pull_request", + "label": "#746", + "title": "Accept Harbor reducer wrapper in run ledger upsert", + "state": "merged", + "lane_id": "open_review", + "url": "https://github.com/huangruiteng/loopx/pull/746", + "confidence": "observed_public_metadata", + "started_at": "2026-06-27T13:24:51+08:00", + "completed_at": "2026-06-27T13:25:38+08:00", + "occurred_at": "2026-06-27T13:24:51+08:00", + "display_time": "13:24 -> 13:25", + "duration_label": "47 sec", + "timezone": "Asia/Shanghai" + } + ], + "edges": [ + { + "edge_id": "edge_trajectory_anchor_to_v2", + "from_node_id": "pr_674", + "to_node_id": "pr_775", + "edge_kind": "frontstage_followup", + "label": "trajectory panel anchor expands into recent PR batch projection", + "confidence": "inferred_high" + }, + { + "edge_id": "edge_gallery_revert_review", + "from_node_id": "pr_752", + "to_node_id": "pr_753", + "edge_kind": "review_revert", + "label": "public surface review produced a quick revert slice", + "confidence": "observed_public_metadata" + }, + { + "edge_id": "edge_showcase_case_pack", + "from_node_id": "pr_745", + "to_node_id": "pr_756", + "edge_kind": "showcase_followup", + "label": "case set became public showcase pages", + "confidence": "observed_public_metadata" + }, + { + "edge_id": "edge_showcase_canonical", + "from_node_id": "pr_756", + "to_node_id": "pr_771", + "edge_kind": "showcase_followup", + "label": "case pages converged into canonical showcase path", + "confidence": "observed_public_metadata" + }, + { + "edge_id": "edge_update_note_loop", + "from_node_id": "pr_754", + "to_node_id": "pr_758", + "edge_kind": "automation_followup", + "label": "archive note became scheduled draft automation", + "confidence": "observed_public_metadata" + }, + { + "edge_id": "edge_monitor_watch_to_due", + "from_node_id": "pr_759", + "to_node_id": "pr_761", + "edge_kind": "runtime_followup", + "label": "watch routing made due monitor lane selection reviewable", + "confidence": "observed_public_metadata" + }, + { + "edge_id": "edge_monitor_due_to_writeback", + "from_node_id": "pr_761", + "to_node_id": "pr_764", + "edge_kind": "runtime_followup", + "label": "due monitor selection led to poll writeback API", + "confidence": "observed_public_metadata" + }, + { + "edge_id": "edge_monitor_writeback_to_smoke", + "from_node_id": "pr_764", + "to_node_id": "pr_765", + "edge_kind": "validation_followup", + "label": "writeback API is followed by smoke coverage", + "confidence": "observed_public_metadata" + }, + { + "edge_id": "edge_monitor_smoke_to_metadata", + "from_node_id": "pr_765", + "to_node_id": "pr_766", + "edge_kind": "cli_followup", + "label": "smoke contract is followed by CLI metadata affordance", + "confidence": "observed_public_metadata" + }, + { + "edge_id": "edge_command_onboarding_to_review", + "from_node_id": "pr_773", + "to_node_id": "pr_774", + "edge_kind": "review_command_followup", + "label": "newcomer command path feeds explicit PR review command", + "confidence": "observed_public_metadata" + } + ], + "frontend_acceptance": { + "must_render": [ + "generic projection model contract", + "30 PR relationship mesh", + "timeline axis", + "hoverable node and edge details", + "sequential requirement rollout", + "rollout requirement spine", + "projection capability map", + "30 public PR nodes", + "single-agent stage flow", + "multi-agent lane graph", + "review edge mesh", + "flow signals", + "attention hotspots", + "public evidence boundary", + "wall-clock timeline ticks", + "node time and duration hover details" + ] + } + } + ], + "planned_projections": [ + { + "projection_id": "loopx_overall_iteration", + "status": "planned", + "reason": "Use the same projection model after the overnight PR batch renderer is validated." + } + ] +} diff --git a/examples/frontstage-rollout-projections-fixture-smoke.py b/examples/frontstage-rollout-projections-fixture-smoke.py new file mode 100644 index 00000000..deac2273 --- /dev/null +++ b/examples/frontstage-rollout-projections-fixture-smoke.py @@ -0,0 +1,265 @@ +#!/usr/bin/env python3 +"""Validate the public rollout projection bundle used by frontstage.""" + +from __future__ import annotations + +import json +import re +from pathlib import Path + + +REPO_ROOT = Path(__file__).resolve().parents[1] +FIXTURE_PATH = ( + REPO_ROOT + / "examples" + / "fixtures" + / "frontstage-rollout-projections.public.json" +) + +PRIVATE_PATTERNS = [ + re.compile(r"/" + r"Users/[A-Za-z0-9._-]+/"), + re.compile(r"/home/[A-Za-z0-9._-]+/"), + re.compile(r"/" + "private/"), + re.compile(r"[A-Za-z]:\\\\Users\\\\"), + re.compile(r"\bBearer\s+[A-Za-z0-9._-]+"), + re.compile(r"\bAKIA[0-9A-Z]{16}\b"), + re.compile(r"\b" + "depart" + "ment" + r"\b", re.IGNORECASE), + re.compile("\u90e8\u95e8"), + re.compile("\u6c47\u62a5"), +] + +REQUIRED_BOUNDARY_FALSE = { + "raw_task_text_recorded", + "raw_logs_recorded", + "raw_trajectory_recorded", + "raw_session_transcript_recorded", + "credential_values_recorded", + "absolute_paths_recorded", + "private_material_body_recorded", +} + +REQUIRED_MODEL_SECTIONS = {"metrics", "stages", "lanes", "nodes", "edges"} +REQUIRED_RICH_SECTIONS = { + "timeline", + "rollout_sequence", + "mapping_layers", + "flow_signals", + "relationship_summaries", + "attention_hotspots", +} +ALLOWED_CONFIDENCE = { + "observed", + "observed_public_metadata", + "inferred_high", +} + + +def assert_public_safe(text: str) -> None: + for pattern in PRIVATE_PATTERNS: + if pattern.search(text): + raise AssertionError(f"fixture matched private pattern {pattern.pattern!r}") + + +def main() -> int: + fixture_text = FIXTURE_PATH.read_text(encoding="utf-8") + assert_public_safe(fixture_text) + payload = json.loads(fixture_text) + + assert payload["schema_version"] == "frontstage_rollout_projection_bundle_v0", payload + assert payload["truth_contract"]["projection_is_writable"] is False, payload + assert payload["truth_contract"]["write_authority"] == "none", payload + assert "public GitHub PR metadata" in payload["truth_contract"]["evidence_floor"], payload + for key in REQUIRED_BOUNDARY_FALSE: + assert payload["public_boundary"].get(key) is False, (key, payload["public_boundary"]) + + model = payload["projection_model"] + assert model["schema_version"] == "frontstage_rollout_projection_model_v0", model + assert set(model["required_sections"]) == REQUIRED_MODEL_SECTIONS, model + assert set(model["optional_rich_sections"]) == REQUIRED_RICH_SECTIONS, model + assert "frontstage renderer consumes this model" in model["description"], model + + projections = payload["projections"] + assert len(projections) == 1, projections + projection = projections[0] + assert projection["projection_id"] == "overnight_pr_batch_20260627", projection + assert projection["projection_kind"] == "pr_batch_rollout", projection + assert projection["source_contract"]["sample_window"] == "#746-#775", projection + assert projection["source_contract"]["anchor_node_id"] == "pr_674", projection + assert projection["scene"]["confidence"] == "observed_public_metadata", projection + + metrics = {metric["metric_id"]: metric for metric in projection["metrics"]} + assert metrics["public_sample"]["value"] == "30 PRs", metrics + assert metrics["merged"]["value"] == "29", metrics + assert metrics["open_review"]["value"] == "1", metrics + assert metrics["review_edges"]["value"] == "10", metrics + + stages = projection["stages"] + assert len(stages) >= 6, stages + assert sum(1 for stage in stages if stage["current"]) == 1, stages + for stage in stages: + assert stage["confidence"] in ALLOWED_CONFIDENCE, stage + assert stage["stage_id"], stage + assert stage["actor_scope"], stage + + nodes = projection["nodes"] + node_ids = [node["node_id"] for node in nodes] + assert len(node_ids) == len(set(node_ids)), node_ids + node_by_id = {node["node_id"]: node for node in nodes} + pr_batch_nodes = [ + node + for node in nodes + if re.fullmatch(r"pr_7(?:4[6-9]|5[0-9]|6[0-9]|7[0-5])", node["node_id"]) + ] + assert len(pr_batch_nodes) == 30, pr_batch_nodes + assert sum(1 for node in pr_batch_nodes if node["state"] == "merged") == 29, pr_batch_nodes + assert sum(1 for node in pr_batch_nodes if node["state"] == "open") == 0, pr_batch_nodes + assert sum(1 for node in pr_batch_nodes if node["state"] == "closed") == 1, pr_batch_nodes + for node in nodes: + assert node["confidence"] in ALLOWED_CONFIDENCE, node + assert node["url"].startswith("https://github.com/huangruiteng/loopx/pull/"), node + for node in pr_batch_nodes: + assert node["started_at"].startswith("2026-06-27T"), node + assert node["completed_at"].startswith("2026-06-27T"), node + assert node["occurred_at"] == node["started_at"], node + assert node["display_time"], node + assert node["duration_label"], node + assert node["timezone"] == "Asia/Shanghai", node + + lanes = projection["lanes"] + lane_ids = {lane["lane_id"] for lane in lanes} + assert { + "kernel_runtime", + "product_frontstage", + "benchmark_interface", + "public_docs_showcase", + "open_review", + } <= lane_ids, lanes + for lane in lanes: + assert lane["node_ids"], lane + for node_id in lane["node_ids"]: + assert node_id in node_by_id, (lane["lane_id"], node_id) + + edges = projection["edges"] + assert len(edges) == 10, edges + assert any(edge["from_node_id"] == "pr_674" for edge in edges), edges + assert any(edge["edge_kind"] == "review_revert" for edge in edges), edges + assert any(edge["edge_kind"] == "review_command_followup" for edge in edges), edges + for edge in edges: + assert edge["from_node_id"] in node_by_id, edge + assert edge["to_node_id"] in node_by_id, edge + assert edge["confidence"] in ALLOWED_CONFIDENCE, edge + assert edge.get("label"), edge + + edge_by_id = {edge["edge_id"]: edge for edge in edges} + mapping_layers = projection["mapping_layers"] + assert len(mapping_layers) >= 5, mapping_layers + assert {layer["layer_id"] for layer in mapping_layers} >= { + "source_intake", + "state_projection", + "lane_routing", + "edge_reasoning", + "operator_readout", + }, mapping_layers + for layer in mapping_layers: + assert layer["input"], layer + assert layer["output"], layer + for node_id in layer["node_ids"]: + assert node_id in node_by_id, (layer["layer_id"], node_id) + for edge_id in layer["edge_ids"]: + assert edge_id in edge_by_id, (layer["layer_id"], edge_id) + + flow_signals = projection["flow_signals"] + assert len(flow_signals) >= 4, flow_signals + assert {signal["signal_id"] for signal in flow_signals} >= { + "throughput", + "active_review", + "cross_lane_edges", + "reusable_model", + }, flow_signals + for signal in flow_signals: + for node_id in signal["source_node_ids"]: + assert node_id in node_by_id, (signal["signal_id"], node_id) + + relationship_summaries = projection["relationship_summaries"] + assert len(relationship_summaries) >= 5, relationship_summaries + assert any(summary["kind"] == "runtime_followup" for summary in relationship_summaries), relationship_summaries + + hotspots = projection["attention_hotspots"] + assert len(hotspots) >= 3, hotspots + for hotspot in hotspots: + assert hotspot["severity"] in {"low", "medium", "high"}, hotspot + for node_id in hotspot["node_ids"]: + assert node_id in node_by_id, (hotspot["hotspot_id"], node_id) + for edge_id in hotspot["edge_ids"]: + assert edge_id in edge_by_id, (hotspot["hotspot_id"], edge_id) + + timeline = projection["timeline"] + assert timeline["axis_kind"] == "wall_clock", timeline + assert timeline["unit_label"] == "PR nodes", timeline + assert timeline["timezone"] == "Asia/Shanghai", timeline + assert timeline["start_at"] == "2026-06-27T13:24:51+08:00", timeline + assert timeline["end_at"] == "2026-06-27T21:30:06+08:00", timeline + assert "13:24-21:30 Asia/Shanghai" in timeline["window_label"], timeline + assert "node.started_at" in timeline["time_basis"], timeline + assert len(timeline["ticks"]) >= 8, timeline + assert len(timeline["item_node_ids"]) == 30, timeline + assert timeline["item_node_ids"][0] == "pr_746", timeline + assert timeline["item_node_ids"][-1] == "pr_775", timeline + for node_id in timeline["item_node_ids"]: + assert node_id in node_by_id, node_id + + sequence = projection["rollout_sequence"] + assert sequence["sequence_id"] == "overnight_requirement_rollout_spine", sequence + assert "One demand unlocks the next" in sequence["description"], sequence + units = sequence["units"] + assert len(units) >= 7, units + assert [unit["order"] for unit in units] == list(range(1, len(units) + 1)), units + unit_ids = {unit["unit_id"] for unit in units} + assert "req_frontstage_projection" in unit_ids, unit_ids + assert "req_monitor_scheduler_due_work" in unit_ids, unit_ids + assert any(unit["state"] == "open" for unit in units), units + assert any(unit["state"] == "merged" for unit in units), units + for unit in units: + assert unit["requirement"], unit + assert unit["triggered_by"], unit + assert unit["outcome"], unit + assert unit["lane_id"] in lane_ids, unit + assert unit["stage_steps"], unit + for node_id in unit["node_ids"]: + assert node_id in node_by_id, (unit["unit_id"], node_id) + for unlock in unit["unlocks"]: + assert unlock in unit_ids, (unit["unit_id"], unlock) + for step in unit["stage_steps"]: + assert step["status"] in {"done", "active", "queued", "planned"}, step + for node_id in step["node_ids"]: + assert node_id in node_by_id, (unit["unit_id"], step["step_id"], node_id) + + acceptance = set(projection["frontend_acceptance"]["must_render"]) + assert { + "generic projection model contract", + "30 PR relationship mesh", + "timeline axis", + "wall-clock timeline ticks", + "node time and duration hover details", + "hoverable node and edge details", + "sequential requirement rollout", + "rollout requirement spine", + "projection capability map", + "30 public PR nodes", + "single-agent stage flow", + "multi-agent lane graph", + "review edge mesh", + "flow signals", + "attention hotspots", + "public evidence boundary", + } <= acceptance, acceptance + + planned = {item["projection_id"]: item for item in payload.get("planned_projections", [])} + assert planned["loopx_overall_iteration"]["status"] == "planned", planned + + print("frontstage-rollout-projections fixture smoke ok") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())