diff --git a/components/RdpHybridPresentation.js b/components/RdpHybridPresentation.js index 577fa23..06118ee 100644 --- a/components/RdpHybridPresentation.js +++ b/components/RdpHybridPresentation.js @@ -1,43 +1,62 @@ import { useEffect, useMemo, useRef, useState } from 'react' +import JsonArtifactLink from './JsonArtifactLink' import styles from './RdpHybridPresentation.module.css' const MISSING_TIMELINE = 'The media timeline is unavailable. OpenAdapt shows the authenticated video and artifacts, but it does not infer a phase from playback time.' -// These labels only describe renderer phases. The runtime facts stay in the -// exporter payload and are rendered below without a frontend interpretation. +// These labels explain exact renderer phases. Runtime facts stay in the +// exported timeline and are never reconstructed from playback time. const PHASE_PRESENTATION = { execute_request: { - label: 'Authorized request', - detail: 'A qualified request enters the execution boundary.', + stage: 'request', + label: 'The request arrives', + detail: + 'The authorized appointment data enters the customer-controlled runner.', }, demonstration: { - label: 'Demonstrate through RDP', - detail: 'The presentation replays retained operator input.', + stage: 'build', + label: 'A person shows the task once', + detail: 'The demonstration includes the real mouse, keyboard, and RDP timing.', }, compiled_workflow: { - label: 'Compiled workflow', - detail: 'OpenAdapt exposes the exact exported workflow node.', + stage: 'build', + label: 'OpenAdapt builds a reusable workflow', + detail: 'The video shows the exact exported program, not a drawn example.', }, governed_replay: { - label: 'Governed replay', - detail: 'The runner replays the qualified workflow with fresh observations.', + stage: 'run', + label: 'OpenAdapt completes the task', + detail: 'The runner checks the live record before it enters the new request.', }, independent_effect_check: { - label: 'Independent effect proof', - detail: 'The configured verifier determines the result.', + stage: 'verify', + label: 'The saved result is checked', + detail: 'A separate read-only database check confirms the appointment.', + showOutcome: true, }, wrong_record_refusal: { - label: 'Wrong-record refusal', - detail: 'The qualified workflow stops before the consequential action.', + stage: 'verify', + label: 'Wrong record. Save blocked.', + detail: 'The record changed, so OpenAdapt stopped before the write.', + showOutcome: true, }, terminal_summary: { - label: 'Execution result', - detail: 'The presentation shows the retained terminal result.', + stage: 'verify', + label: 'Verified result or safe stop', + detail: + 'The correct run is verified. The wrong-record run stops before Save.', }, } +const STORY_STAGES = [ + { id: 'request', label: 'Request' }, + { id: 'build', label: 'Build once' }, + { id: 'run', label: 'Run' }, + { id: 'verify', label: 'Verify or stop' }, +] + const clamp = (value, minimum, maximum) => Math.min(Math.max(value, minimum), maximum) @@ -110,59 +129,55 @@ function activeEntry(timeline, currentMs) { ) } -function phaseChapters(timeline) { +function storyChapters(timeline) { if (!timeline) return [] - const chapters = [] - for (const entry of timeline.timeline) { - const previous = chapters.at(-1) - if (previous?.phase === entry.phase) { - previous.end_pts_s = entry.end_pts_s - previous.end_frame_exclusive = entry.end_frame_exclusive - } else { - chapters.push({ - phase: entry.phase, - start_pts_s: entry.start_pts_s, - end_pts_s: entry.end_pts_s, - start_frame: entry.start_frame, - end_frame_exclusive: entry.end_frame_exclusive, - }) - } - } - return chapters + return STORY_STAGES.flatMap((stage) => { + const entries = timeline.timeline.filter( + (entry) => PHASE_PRESENTATION[entry.phase]?.stage === stage.id + ) + if (!entries.length) return [] + return [ + { + ...stage, + start_pts_s: entries[0].start_pts_s, + end_pts_s: entries.at(-1).end_pts_s, + start_frame: entries[0].start_frame, + end_frame_exclusive: entries.at(-1).end_frame_exclusive, + }, + ] + }) } -function evidencePath(entry, activeNode) { - const facts = entry?.facts ?? {} - return [ - { - label: 'Request', - value: - typeof facts.authorization === 'string' - ? facts.authorization - : null, - }, - { - label: 'Identity', - value: typeof facts.identity === 'string' ? facts.identity : null, - }, - { - label: 'Compiled action', - value: activeNode?.title ?? null, - }, - { - label: 'Effect proof', - value: - typeof facts.effect === 'string' - ? [facts.effect, facts.effect_verifier_kind] - .filter((value) => typeof value === 'string') - .join(' · ') - : null, - }, - { - label: 'Outcome', - value: typeof facts.outcome === 'string' ? facts.outcome : null, - }, - ] +function nodeFacts(node) { + const facts = [] + if (node.param) facts.push(`Input: $${node.param}`) + const resolutionRungs = Array.isArray(node.resolution?.rungs) + ? node.resolution.rungs + .filter((rung) => rung?.present && typeof rung.label === 'string') + .map((rung) => rung.label) + : [] + if (resolutionRungs.length) { + facts.push(`Resolve: ${resolutionRungs.join(' + ')}`) + } + if (Array.isArray(node.postconditions) && node.postconditions.length) { + facts.push( + `Check: ${node.postconditions + .map((condition) => condition.replaceAll('_', ' ')) + .join(' + ')}` + ) + } + if (Array.isArray(node.badges) && node.badges.length) { + facts.push(`Policy: ${node.badges.join(' + ')}`) + } + if (Array.isArray(node.effects) && node.effects.length) { + facts.push( + `${node.effects.length} required effect ${ + node.effects.length === 1 ? 'check' : 'checks' + }` + ) + } + if (!facts.length && node.kind) facts.push(node.kind) + return facts } export default function RdpHybridPresentation({ @@ -171,6 +186,7 @@ export default function RdpHybridPresentation({ manifestSrc = '/demos/rdp/presentation.manifest.json', graphSrc = '/demos/rdp/program-graph.json', timelineSrc = '/demos/rdp/presentation.timeline.json', + qualificationSrc = '/demos/rdp/qualification.json', }) { const shellRef = useRef(null) const videoRef = useRef(null) @@ -212,8 +228,7 @@ export default function RdpHybridPresentation({ const update = () => { setReducedMotion(media.matches) const video = videoRef.current - if (!video) return - if (media.matches) video.pause() + if (media.matches && video) video.pause() } update() media.addEventListener?.('change', update) @@ -228,7 +243,10 @@ export default function RdpHybridPresentation({ const observer = new window.IntersectionObserver( ([intersection]) => { - if (!intersection?.isIntersecting || intersection.intersectionRatio < 0.4) { + if ( + !intersection?.isIntersecting || + intersection.intersectionRatio < 0.4 + ) { return } startedRef.current = true @@ -269,26 +287,29 @@ export default function RdpHybridPresentation({ : null, [frameBindingAvailable, manifest, timelinePayload] ) + const effectiveDurationMs = + durationMs || + (timeline + ? Math.round( + (timeline.derivative.frame_count / timeline.derivative.fps) * 1000 + ) + : 0) const entry = activeEntry(timeline, currentMs) - const chapters = useMemo(() => phaseChapters(timeline), [timeline]) - const chapter = chapters.find( - (item) => - entry && - entry.start_frame >= item.start_frame && - entry.start_frame < item.end_frame_exclusive + const chapters = useMemo(() => storyChapters(timeline), [timeline]) + const phaseContent = entry ? PHASE_PRESENTATION[entry.phase] : null + const activeChapter = chapters.find((item) => item.id === phaseContent?.stage) + const activeStageIndex = STORY_STAGES.findIndex( + (item) => item.id === phaseContent?.stage ) const activeNodeIds = new Set( entry?.compiled_graph?.node_id ? [entry.compiled_graph.node_id] : [] ) const nodes = Array.isArray(graph?.nodes) ? graph.nodes : [] - const activeNode = nodes.find((node) => node.id === entry?.compiled_graph?.node_id) - const evidence = evidencePath(entry, activeNode) const parameterNames = Array.isArray(graph?.bundle?.params) ? graph.bundle.params .map((parameter) => parameter?.name) .filter((name) => typeof name === 'string') : [] - const phaseContent = entry ? PHASE_PRESENTATION[entry.phase] : null const toggle = () => { const video = videoRef.current @@ -299,8 +320,9 @@ export default function RdpHybridPresentation({ video.currentTime = 0 } void video.play() + } else { + video.pause() } - else video.pause() } const seek = (milliseconds) => { @@ -318,8 +340,7 @@ export default function RdpHybridPresentation({ const expand = () => { const stage = stageRef.current - if (!stage?.requestFullscreen) return - void stage.requestFullscreen() + if (stage?.requestFullscreen) void stage.requestFullscreen() } return ( @@ -329,16 +350,8 @@ export default function RdpHybridPresentation({ aria-label="RDP execution presentation" >
- Recorded reference execution - Evidence-bound view - - {manifest?.workflow_digest - ? `bundle ${manifest.workflow_digest.slice(0, 12)}` - : 'loading bundle binding'} - - - Open MP4 - + Real RDP demo + Customer-controlled runner
@@ -352,7 +365,9 @@ export default function RdpHybridPresentation({ poster={poster} preload="metadata" onLoadedMetadata={(event) => { - setDurationMs(Math.round(event.currentTarget.duration * 1000)) + setDurationMs( + Math.round(event.currentTarget.duration * 1000) + ) }} onPlay={() => setPlaying(true)} onPause={() => setPlaying(false)} @@ -360,33 +375,34 @@ export default function RdpHybridPresentation({ Your browser does not support this presentation. -
- {phaseContent?.label ?? 'Authenticated media'} - {entry?.facts?.outcome ?? 'Evidence view'} -
-
seek(Number(event.target.value))} @@ -394,7 +410,7 @@ export default function RdpHybridPresentation({ />
{timeLabel(currentMs)} - {timeLabel(durationMs)} + {timeLabel(effectiveDurationMs)}
@@ -404,43 +420,26 @@ export default function RdpHybridPresentation({ {phaseContent?.label ?? 'Authenticated media'}

-

Verified execution

- {timeline ? 'exact frame link' : 'artifact-bound'} +

What is happening

+ + {activeStageIndex >= 0 + ? `Stage ${activeStageIndex + 1} of 4` + : 'Exact media'} +
{entry && phaseContent ? ( <>

{phaseContent.label}

{phaseContent.detail}

-
    - {evidence.map((stage, index) => ( -
  1. - {String(index + 1).padStart(2, '0')} -
    - {stage.label} - {stage.value && {stage.value}} -
    -
  2. - ))} -
-
- {entry.source_frame && ( -

- Retained frame - - {entry.source_frame.presentation_phase} · {entry.source_frame.file} - -

- )} - {Object.hasOwn(entry.facts ?? {}, 'model_calls') && ( -

- Model calls - {entry.facts.model_calls} + {entry.facts.outcome}

)} -
) : ( <> @@ -448,65 +447,155 @@ export default function RdpHybridPresentation({

{MISSING_TIMELINE}

)} -
- Media - {manifest?.video_sha256?.slice(0, 20) ?? 'loading'}… - Graph - {manifest?.program_graph_sha256?.slice(0, 20) ?? 'loading'}… -
-
+
{chapters.map((item, index) => { - const selected = item.phase === chapter?.phase - const presentation = PHASE_PRESENTATION[item.phase] + const selected = item.id === activeChapter?.id return ( ) })} - {!timeline &&

{MISSING_TIMELINE}

} + {!timeline && ( +

{MISSING_TIMELINE}

+ )}
-
-
-
-

Compiled workflow

+
+
+ - {parameterNames.length - ? `Parameters: ${parameterNames.map((name) => `$${name}`).join(' · ')}` - : 'Only exact exported graph nodes appear here.'} + The workflow OpenAdapt built + The exact nine-step program and its inputs + +
+
+
+

Compiled workflow

+ + {parameterNames.length + ? `Inputs: ${parameterNames + .map((name) => `$${name}`) + .join(' · ')}` + : 'Only exact exported graph nodes appear here.'} + +
+ + {reducedMotion ? 'Reduced motion' : 'Media-synced'} + +
+
    + {nodes.map((node) => ( +
  1. + + {String(node.index + 1).padStart(2, '0')} + + {node.title} +
      + {nodeFacts(node).map((fact) => ( +
    • {fact}
    • + ))} +
    +
  2. + ))} +
- {reducedMotion ? 'Reduced motion' : 'Media-synced'} -
-
    - {nodes.map((node) => ( -
  1. - {String(node.index + 1).padStart(2, '0')} - {node.title} + + +
    + + + Technical evidence - {node.param - ? `input: $${node.param}` - : node.badges?.join(' · ') || node.kind} + Open the reports, exact timeline, graph, and raw media -
  2. - ))} -
+ + +
+
+ + Qualification evidence + + + Exact media timeline + + + Program graph + + + Presentation manifest + + Raw MP4 + + RDP architecture + +
+
+
+
Media hash
+
+ + {manifest?.video_sha256 ?? 'loading'} + +
+
+
+
Graph hash
+
+ + {manifest?.program_graph_sha256 ?? 'loading'} + +
+
+ {entry?.source_frame && ( +
+
Current retained frame
+
+ + {entry.source_frame.presentation_phase} ·{' '} + {entry.source_frame.file} + +
+
+ )} + {Object.hasOwn(entry?.facts ?? {}, 'model_calls') && ( +
+
Model calls
+
{entry.facts.model_calls}
+
+ )} +
+
+
- {loadError &&

Evidence load error: {loadError}

} + {loadError && ( +

Evidence load error: {loadError}

+ )} ) } diff --git a/components/RdpHybridPresentation.module.css b/components/RdpHybridPresentation.module.css index c465c41..f04ef10 100644 --- a/components/RdpHybridPresentation.module.css +++ b/components/RdpHybridPresentation.module.css @@ -13,7 +13,6 @@ .consoleHeader, .graphHeader, .clock, -.provenance, .chapter, .playButton { display: flex; @@ -32,139 +31,441 @@ text-transform: uppercase; } -.digest { margin-left: auto; color: #6f9088; } .boundLabel { padding: 4px 7px; - border: 1px solid rgba(140, 211, 198, .18); + border: 1px solid rgba(140, 211, 198, 0.18); border-radius: 999px; color: #7fb4a8; } -.mediaLink { - color: #a8c4be; - text-underline-offset: 3px; -} -.mediaLink:hover { color: #edf8f5; } -.grid { display: grid; gap: 1px; background: rgba(140, 211, 198, 0.14); } -.stage { position: relative; min-height: 300px; overflow: hidden; background: #071011; } -.video { display: block; width: 100%; height: 100%; min-height: 300px; object-fit: contain; background: #050a0b; } -.phaseBadge { position: absolute; top: 16px; left: 16px; display: grid; gap: 3px; padding: 8px 10px; border: 1px solid rgba(127, 247, 220, 0.32); border-radius: 10px; background: rgba(4, 19, 19, 0.74); backdrop-filter: blur(12px); font-size: 11px; } -.phaseBadge span { color: #95cac0; } -.phaseBadge strong { color: #e7fffa; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; letter-spacing: .07em; text-transform: uppercase; } -.phaseBadge[data-known='false'] strong { color: #b8cfca; } -.expandButton { + +.grid { + display: grid; + gap: 1px; + background: rgba(140, 211, 198, 0.14); +} + +.stage { + position: relative; + display: flex; + flex-direction: column; + min-height: 300px; + overflow: hidden; + background: #071011; +} + +.video { + display: block; + flex: 1 1 auto; + width: 100%; + height: 100%; + min-height: 300px; + object-fit: contain; + background: #050a0b; +} + +.expandButton, +.playButton { position: absolute; + border: 1px solid rgba(159, 245, 223, 0.25); + border-radius: 999px; + background: rgba(4, 18, 18, 0.8); + color: #e5faf5; + font-size: 11px; + font-weight: 700; + backdrop-filter: blur(12px); +} + +.expandButton { top: 16px; right: 16px; padding: 7px 10px; - border: 1px solid rgba(159, 245, 223, .25); +} + +.playButton { + right: 16px; + bottom: 48px; + gap: 8px; + padding: 9px 12px; +} + +.expandButton:hover, +.playButton:hover { + border-color: #79e4c5; + background: #102728; +} + +.scrubWrap { + position: static; + flex: 0 0 auto; + padding: 3px 16px 10px; + background: #050a0b; +} + +.scrubWrap input { + width: 100%; + accent-color: #68e3bd; +} + +.scrubWrap input:disabled { + cursor: not-allowed; + opacity: 0.45; +} + +.clock { + justify-content: space-between; + color: #b2d0c9; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 10px; +} + +.console { + min-height: 300px; + padding: 24px; + background: linear-gradient(180deg, #0b1e20, #081516); +} + +.consoleHeader { + justify-content: space-between; + padding-bottom: 14px; + border-bottom: 1px solid rgba(149, 222, 205, 0.15); + color: #80b6aa; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 10px; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.consoleHeader p { + margin: 0; +} + +.consoleHeader span { + color: #68e3bd; +} + +.console h3 { + max-width: 18ch; + margin: 42px 0 12px; + color: #f0fffb; + font-family: var(--font-display, ui-sans-serif); + font-size: clamp(25px, 3vw, 34px); + line-height: 1.04; +} + +.console > p { + max-width: 36ch; + margin: 0; + color: #abc4bf; + font-size: 15px; + line-height: 1.6; +} + +.console .outcome { + display: inline-flex; + width: auto; + margin-top: 26px; + padding: 8px 12px; + border: 1px solid rgba(104, 224, 198, 0.35); border-radius: 999px; - background: rgba(4, 18, 18, .8); - color: #d8f4ee; + background: rgba(77, 215, 171, 0.12); + color: #8af2d2; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 11px; font-weight: 700; - backdrop-filter: blur(12px); + letter-spacing: 0.08em; +} + +.console .outcome[data-outcome='halted'] { + border-color: rgba(255, 143, 129, 0.36); + background: rgba(166, 54, 43, 0.16); + color: #ffb0a7; } -.expandButton:hover { border-color: #79e4c5; background: #102728; } -.playButton { position: absolute; right: 16px; bottom: 48px; gap: 8px; padding: 9px 12px; border: 1px solid rgba(159, 245, 223, .25); border-radius: 999px; background: rgba(4, 18, 18, .8); color: #e5faf5; font-size: 12px; font-weight: 700; backdrop-filter: blur(12px); } -.playButton:hover { border-color: #79e4c5; background: #102728; } -.scrubWrap { position: absolute; right: 16px; bottom: 12px; left: 16px; } -.scrubWrap input { width: 100%; accent-color: #68e3bd; } -.scrubWrap input:disabled { opacity: .45; cursor: not-allowed; } -.clock { justify-content: space-between; color: #b2d0c9; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; } -.console { min-height: 300px; padding: 20px; background: linear-gradient(180deg, #0b1e20, #081516); } -.consoleHeader { justify-content: space-between; padding-bottom: 14px; border-bottom: 1px solid rgba(149, 222, 205, .15); color: #80b6aa; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; letter-spacing: .08em; text-transform: uppercase; } -.consoleHeader p { margin: 0; } -.consoleHeader span { color: #68e3bd; } -.console h3 { margin: 22px 0 8px; color: #f0fffb; font-family: var(--font-display, ui-sans-serif); font-size: 26px; line-height: 1.05; } -.console > p { margin: 0; color: #abc4bf; font-size: 14px; line-height: 1.55; } -.evidencePath { + +.rail { display: grid; - gap: 0; - padding: 0; - margin: 20px 0 0; - list-style: none; + grid-template-columns: repeat(4, minmax(0, 1fr)); + gap: 1px; + border-top: 1px solid rgba(140, 211, 198, 0.14); + background: rgba(140, 211, 198, 0.14); } -.evidencePath li { - position: relative; + +.chapter { + min-height: 76px; + flex-direction: column; + align-items: flex-start; + justify-content: center; + gap: 4px; + padding: 12px 16px; + border: 0; + background: #0a1719; + color: #d7f0ea; + text-align: left; +} + +.chapter:hover, +.chapter[data-active='true'] { + background: #103034; +} + +.chapter[data-active='true'] { + box-shadow: inset 0 2px 0 #69e2bd; +} + +.chapter > span { + color: #67dfbc; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 10px; +} + +.chapter strong { + font-size: 12px; +} + +.chapter small { + color: #87aaa2; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; + font-size: 9px; +} + +.contractNote { + grid-column: 1 / -1; + margin: 0; + padding: 14px 18px; + background: #0a1719; + color: #8eaaa4; + font-size: 12px; + line-height: 1.45; +} + +.detailsStack { display: grid; - grid-template-columns: 30px minmax(0, 1fr); - gap: 9px; - min-height: 48px; - color: #6f8d86; + gap: 1px; + border-top: 1px solid rgba(140, 211, 198, 0.14); + background: rgba(140, 211, 198, 0.14); +} + +.details { + background: #071213; } -.evidencePath li:not(:last-child)::after { + +.details summary { + position: relative; + display: flex; + align-items: center; + min-height: 76px; + padding: 14px 54px 14px 18px; + cursor: pointer; + list-style: none; +} + +.details summary::-webkit-details-marker { + display: none; +} + +.details summary::after { position: absolute; - top: 25px; - bottom: 3px; - left: 10px; - width: 1px; - background: rgba(130, 196, 182, .18); - content: ''; + top: 50%; + right: 20px; + color: #68e3bd; + content: '+'; + font-size: 24px; + line-height: 1; + transform: translateY(-50%); +} + +.details[open] summary::after { + content: '−'; +} + +.details summary:hover { + background: #0d2426; } -.evidencePath li > span { + +.details summary span { display: grid; - place-items: center; - width: 21px; - height: 21px; - border: 1px solid rgba(130, 196, 182, .22); + gap: 4px; +} + +.details summary strong { + color: #e8fbf6; + font-size: 13px; +} + +.details summary small { + color: #88a8a1; + font-size: 11px; +} + +.graph, +.evidencePanel { + padding: 0 18px 20px; +} + +.graphHeader { + justify-content: space-between; + gap: 18px; + padding-top: 16px; + margin-bottom: 14px; + border-top: 1px solid rgba(149, 222, 205, 0.14); +} + +.graphHeader p { + margin: 0; + color: #daf8f1; + font-size: 13px; + font-weight: 700; +} + +.graphHeader span { + display: block; + margin-top: 3px; + color: #829e98; + font-size: 11px; +} + +.motion { + padding: 5px 7px; + border: 1px solid rgba(128, 220, 196, 0.2); border-radius: 999px; + color: #89bbb0 !important; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; - font-size: 8px; + font-size: 9px !important; + letter-spacing: 0.06em; + text-transform: uppercase; + white-space: nowrap; } -.evidencePath li div { min-width: 0; display: grid; gap: 2px; } -.evidencePath strong { color: #91aaa4; font-size: 11px; } -.evidencePath small { - overflow: hidden; + +.graph ol { + display: grid; + grid-template-columns: repeat(3, minmax(0, 1fr)); + gap: 8px; + padding: 0; + margin: 0; + list-style: none; +} + +.graph > ol > li { + min-height: 126px; + padding: 12px; + border: 1px solid rgba(142, 207, 193, 0.14); + border-radius: 12px; + background: #0b1c1d; + opacity: 0.72; + transition: + border-color 0.25s ease, + background 0.25s ease, + opacity 0.25s ease, + transform 0.25s ease; +} + +.graph > ol > li[data-active='true'] { + border-color: rgba(99, 230, 191, 0.72); + background: linear-gradient(145deg, #10443e, #0d2728); + box-shadow: 0 0 28px -13px #60e6bf; + opacity: 1; + transform: translateY(-2px); +} + +.graph > ol > li > span { + display: block; + margin-bottom: 8px; + color: #5de0b9; + font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; - text-overflow: ellipsis; - white-space: nowrap; } -.evidencePath li[data-supported='true'] { color: #83eccb; } -.evidencePath li[data-supported='true'] > span { - border-color: rgba(101, 230, 189, .7); - background: rgba(75, 210, 171, .14); - box-shadow: 0 0 18px -8px #65e6bd; + +.graph > ol > li > strong { + display: block; + color: #ebfff9; + font-size: 12px; + line-height: 1.3; +} + +.nodeFacts { + display: grid !important; + gap: 4px !important; + padding: 0 !important; + margin: 9px 0 0 !important; + color: #8caea6; + font-size: 10px; + line-height: 1.35; + list-style: none !important; +} + +.nodeFacts li { + min-height: 0; + padding: 0; + border: 0; + background: transparent; + opacity: 1; + transform: none; } -.evidencePath li[data-supported='true'] strong { color: #e7fff9; } -.exactDetails { + +.evidencePanel { display: grid; - gap: 7px; - margin-top: 4px; - padding-top: 13px; - border-top: 1px solid rgba(149, 222, 205, .15); + gap: 20px; + padding-top: 16px; + border-top: 1px solid rgba(149, 222, 205, 0.14); } -.exactDetails p { + +.evidenceLinks { display: grid; - grid-template-columns: auto minmax(0, 1fr); - gap: 10px; + grid-template-columns: repeat(2, minmax(0, 1fr)); + gap: 8px; +} + +.evidenceLinks a { + padding: 10px 12px; + border: 1px solid rgba(137, 213, 197, 0.16); + border-radius: 10px; + background: #0b1c1d; + color: #b7ddd5; + font-size: 12px; + text-decoration: none; +} + +.evidenceLinks a:hover { + border-color: rgba(104, 227, 189, 0.5); + color: #effffb; +} + +.provenance { + display: grid; + gap: 8px; margin: 0; - color: #75978f; +} + +.provenance div { + display: grid; + grid-template-columns: 150px minmax(0, 1fr); + gap: 12px; + color: #78958e; font-size: 10px; } -.exactDetails strong { color: #a5c3bc; } -.exactDetails span { overflow: hidden; text-align: right; text-overflow: ellipsis; white-space: nowrap; } -.provenance { display: grid; grid-template-columns: auto 1fr; gap: 5px 10px; margin-top: 20px; padding-top: 14px; border-top: 1px solid rgba(149, 222, 205, .15); color: #78958e; font-size: 10px; } -.provenance code { overflow: hidden; color: #a9c9c1; text-overflow: ellipsis; white-space: nowrap; } -.rail { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 1px; border-top: 1px solid rgba(140, 211, 198, .14); background: rgba(140, 211, 198, .14); } -.chapter { min-height: 84px; flex-direction: column; align-items: flex-start; justify-content: center; gap: 4px; padding: 12px 14px; border: 0; background: #0a1719; color: #d7f0ea; text-align: left; } -.chapter:hover, .chapter[data-active='true'] { background: #103034; } -.chapter[data-active='true'] { box-shadow: inset 0 2px 0 #69e2bd; } -.chapter > span { color: #67dfbc; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; } -.chapter strong { font-size: 12px; } -.chapter small { color: #87aaa2; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9px; letter-spacing: .06em; text-transform: uppercase; } -.contractNote { grid-column: 1 / -1; margin: 0; padding: 14px 18px; background: #0a1719; color: #8eaaa4; font-size: 12px; line-height: 1.45; } -.graph { padding: 18px; border-top: 1px solid rgba(140, 211, 198, .14); background: #071213; } -.graphHeader { justify-content: space-between; gap: 18px; margin-bottom: 14px; } -.graphHeader p { margin: 0; color: #daf8f1; font-size: 13px; font-weight: 700; } -.graphHeader span { display: block; margin-top: 3px; color: #829e98; font-size: 11px; } -.motion { padding: 5px 7px; border: 1px solid rgba(128, 220, 196, .2); border-radius: 999px; color: #89bbb0 !important; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 9px !important; letter-spacing: .06em; text-transform: uppercase; white-space: nowrap; } -.graph ol { display: grid; grid-template-columns: repeat(4, minmax(0, 1fr)); gap: 8px; padding: 0; margin: 0; list-style: none; } -.graph li { position: relative; min-height: 88px; padding: 12px; border: 1px solid rgba(142, 207, 193, .14); border-radius: 12px; background: #0b1c1d; opacity: .58; transition: border-color .25s ease, background .25s ease, opacity .25s ease, transform .25s ease; } -.graph li[data-active='true'] { border-color: rgba(99, 230, 191, .72); background: linear-gradient(145deg, #10443e, #0d2728); box-shadow: 0 0 28px -13px #60e6bf; opacity: 1; transform: translateY(-2px); } -.graph li > span { display: block; margin-bottom: 8px; color: #5de0b9; font-family: ui-monospace, SFMono-Regular, Menlo, monospace; font-size: 10px; } -.graph li strong { display: block; color: #ebfff9; font-size: 12px; line-height: 1.3; } -.graph li small { display: block; margin-top: 7px; color: #8caea6; font-size: 10px; line-height: 1.3; } -.error { margin: 0; padding: 12px 18px; border-top: 1px solid rgba(245, 148, 131, .24); background: rgba(107, 31, 24, .28); color: #ffb3a4; font-size: 12px; } + +.provenance dt { + color: #a5c3bc; +} + +.provenance dd { + min-width: 0; + margin: 0; + overflow: hidden; + color: #a9c9c1; + text-align: right; + text-overflow: ellipsis; + white-space: nowrap; +} + +.error { + margin: 0; + padding: 12px 18px; + border-top: 1px solid rgba(245, 148, 131, 0.24); + background: rgba(107, 31, 24, 0.28); + color: #ffb3a4; + font-size: 12px; +} + .srOnly { position: absolute; width: 1px; @@ -176,36 +477,111 @@ white-space: nowrap; border: 0; } -@media (min-width: 900px) { .grid { grid-template-columns: minmax(0, 1.45fr) minmax(300px, .55fr); } .stage, .console { min-height: 470px; } .video { min-height: 470px; } } + +@media (min-width: 900px) { + .grid { + grid-template-columns: minmax(0, 1.5fr) minmax(300px, 0.5fr); + } + + .stage, + .console, + .video { + min-height: 470px; + } +} + @media (max-width: 699px) { - .topline { padding: 0 12px; } - .boundLabel, - .digest { display: none; } - .mediaLink { margin-left: auto; } - .stage { min-height: 0; aspect-ratio: 16 / 9; } - .video { min-height: 0; aspect-ratio: 16 / 9; } - .expandButton { top: 12px; right: 12px; } - .phaseBadge { top: 12px; left: 12px; } + .topline { + padding: 0 12px; + } + + .boundLabel { + margin-left: auto; + } + + .stage, + .video { + min-height: 0; + } + + .video { + aspect-ratio: 16 / 9; + } + + .expandButton { + top: 12px; + right: 12px; + } + + .playButton { + right: 12px; + bottom: 45px; + } + + .scrubWrap { + padding-right: 12px; + padding-left: 12px; + } + + .console { + min-height: 230px; + padding: 20px; + } + + .console h3 { + margin-top: 28px; + } + .rail { - display: flex; - overflow-x: auto; - scroll-snap-type: x mandatory; - scrollbar-width: thin; + grid-template-columns: repeat(4, minmax(0, 1fr)); } + .chapter { - flex: 0 0 min(74vw, 250px); - scroll-snap-align: start; + min-width: 0; + min-height: 72px; + padding: 10px 8px; + } + + .chapter strong { + font-size: 10px; + } + + .chapter small { + display: none; } - .contractNote { min-width: 100%; } + + .details summary { + min-height: 70px; + padding-left: 14px; + } + .graph ol { display: flex; overflow-x: auto; scroll-snap-type: x mandatory; } - .graph li { + + .graph > ol > li { flex: 0 0 min(70vw, 240px); scroll-snap-align: start; } - .playButton { bottom: 52px; } + + .evidenceLinks { + grid-template-columns: 1fr; + } + + .provenance div { + grid-template-columns: 1fr; + gap: 3px; + } + + .provenance dd { + text-align: left; + } +} + +@media (prefers-reduced-motion: reduce) { + .graph > ol > li { + transition: none; + } } -@media (prefers-reduced-motion: reduce) { .graph li { transition: none; } } diff --git a/lib/publicJsonArtifacts.mjs b/lib/publicJsonArtifacts.mjs index c95fb34..376ce76 100644 --- a/lib/publicJsonArtifacts.mjs +++ b/lib/publicJsonArtifacts.mjs @@ -49,7 +49,7 @@ export const PUBLIC_JSON_ARTIFACTS = Object.freeze({ title: 'RDP presentation manifest', description: 'Content hashes and exact retained-frame provenance for the paced RDP presentation video.', - sha256: '4ab70f350be7894c1a3d4556afafd7b20558f9b3e807713518fc3b359d6095c3', + sha256: 'bb38218d7cb4cbcb31b6e188737071380f373fd8449f6ed42521f930bb32ec95', }), '/demos/rdp/presentation.timeline.json': Object.freeze({ source: '/demos/rdp/presentation.timeline.json', @@ -58,7 +58,7 @@ export const PUBLIC_JSON_ARTIFACTS = Object.freeze({ title: 'RDP hybrid presentation timeline', description: 'Exact decoded-frame intervals, retained source-frame bindings, compiled graph nodes, and public runtime facts for the RDP presentation.', - sha256: 'bb90f660da908d2ae857b5949b77088fae3f977efcb9dcddb1570d68b1d85567', + sha256: 'b668275ad7308f359380aa1bcb89ce98b7ae49673b36c4491330c399b2472403', }), '/desktop-preview/MANIFEST.json': Object.freeze({ source: '/desktop-preview/MANIFEST.json', diff --git a/pages/execute.js b/pages/execute.js index c37ec94..55d4435 100644 --- a/pages/execute.js +++ b/pages/execute.js @@ -5,7 +5,6 @@ import ExecuteApiExchange from '@components/ExecuteApiExchange' import ExecuteBoundaryFlow from '@components/ExecuteBoundaryFlow' import ExecuteOutcomeCards from '@components/ExecuteOutcomeCards' import Footer from '@components/Footer' -import JsonArtifactLink from '@components/JsonArtifactLink' import RdpHybridPresentation from '@components/RdpHybridPresentation' const description = @@ -131,30 +130,9 @@ export default function ExecutePage() {

Model calls, wrong-record writes, and silent incorrect successes.

-
-

- Reference qualification with synthetic data. The presentation uses exact retained RDP frames and the exported compiled graph. The slower timing affects the video only. -

-

- - Inspect the qualification evidence → - - - Inspect the compiled workflow → - - - Inspect the media timeline → - - - Read the RDP architecture → - -

-
+

+ Reference qualification with synthetic data. The presentation uses exact retained RDP frames and the exported compiled graph. The workflow is shown above. Open Technical evidence for the reports, hashes, and raw artifacts. +

diff --git a/public/demos/rdp/openadapt-rdp-demo.mp4 b/public/demos/rdp/openadapt-rdp-demo.mp4 index 2a71e11..7903801 100644 Binary files a/public/demos/rdp/openadapt-rdp-demo.mp4 and b/public/demos/rdp/openadapt-rdp-demo.mp4 differ diff --git a/public/demos/rdp/poster.jpg b/public/demos/rdp/poster.jpg index e234eba..760653e 100644 Binary files a/public/demos/rdp/poster.jpg and b/public/demos/rdp/poster.jpg differ diff --git a/public/demos/rdp/presentation.manifest.json b/public/demos/rdp/presentation.manifest.json index 4323b8b..fbe25d3 100644 --- a/public/demos/rdp/presentation.manifest.json +++ b/public/demos/rdp/presentation.manifest.json @@ -5,7 +5,7 @@ 800 ], "hybrid_timeline": "openadapt-rdp-demo.timeline.json", - "hybrid_timeline_sha256": "bb90f660da908d2ae857b5949b77088fae3f977efcb9dcddb1570d68b1d85567", + "hybrid_timeline_sha256": "b668275ad7308f359380aa1bcb89ce98b7ae49673b36c4491330c399b2472403", "phases": [ { "outcome": "RECORDED", @@ -88,6 +88,6 @@ "schema_version": "openadapt.rdp-presentation-render.v2", "timing": "paced derivative; source frames and input events remain exact", "video": "openadapt-rdp-demo.mp4", - "video_sha256": "f98655660d56eb8c96fc50c7d87a6759a895a108ea2fc46616208f7a27537af9", + "video_sha256": "c8df573bd0edbf139d4b5718ee14f2bf35b7e69331bfd9c5bc592dbf2e351504", "workflow_digest": "da52230dbde8d8ae6bd8060f4d2dc3f8c503487bbb3a214159af9550317670b9" } diff --git a/public/demos/rdp/presentation.timeline.json b/public/demos/rdp/presentation.timeline.json index a7f6df4..30065d5 100644 --- a/public/demos/rdp/presentation.timeline.json +++ b/public/demos/rdp/presentation.timeline.json @@ -7,7 +7,7 @@ 800 ], "video": "openadapt-rdp-demo.mp4", - "video_sha256": "f98655660d56eb8c96fc50c7d87a6759a895a108ea2fc46616208f7a27537af9" + "video_sha256": "c8df573bd0edbf139d4b5718ee14f2bf35b7e69331bfd9c5bc592dbf2e351504" }, "program_graph_sha256": "7cb346cabe49b1077b9eaa960257d32f6a9262401fcf7443218e7726a7203570", "schema_version": "openadapt.rdp-hybrid-presentation.v1",