diff --git a/components/AttendedDecisionPreview.js b/components/AttendedDecisionPreview.js new file mode 100644 index 0000000..7d6cc97 --- /dev/null +++ b/components/AttendedDecisionPreview.js @@ -0,0 +1,81 @@ +import { useState } from 'react' + +import styles from './AttendedDecisionPreview.module.css' + +const CLOUD_ATTENTION_DEMO = 'https://app.openadapt.ai/demo/attention' + +const states = { + request: { + image: '/attended-decision/identity-request.png', + alt: 'OpenAdapt mobile portal requesting an identity check before Save', + }, + result: { + image: '/attended-decision/identity-verified.png', + alt: 'OpenAdapt mobile portal showing the runner-verified identity result', + }, +} + +// The two images are exact headless captures of the public Cloud identity +// case. Their source, hashes, and capture sequence are in public/attended-decision. +export default function AttendedDecisionPreview() { + const [state, setState] = useState('request') + const current = states[state] + + return ( +
+
+
+ + +
+ {current.alt} +
+ Exact public Cloud capture · synthetic OpenEMR data +
+
+
+

Attended decisions · Cloud demo

+

+ Keep a person in the loop. Keep the runner in control. +

+

+ When identity conflicts, OpenAdapt pauses before Save. The + phone shows only permitted answers. The customer runner + reads the live record again and returns the result. +

+ + Try all six mobile decision cases in Cloud + + +
+
+ ) +} diff --git a/components/AttendedDecisionPreview.module.css b/components/AttendedDecisionPreview.module.css new file mode 100644 index 0000000..76e1c1f --- /dev/null +++ b/components/AttendedDecisionPreview.module.css @@ -0,0 +1,111 @@ +.section { + display: grid; + grid-template-columns: minmax(220px, 0.58fr) minmax(0, 1fr); + align-items: center; + gap: 34px; + width: 100%; + max-width: 900px; + margin: -18px auto 44px; + padding: 22px; + border: 1px solid var(--hairline); + border-radius: 14px; + background: var(--panel); + text-align: left; +} + +.device { + display: grid; + justify-items: center; + gap: 9px; + margin: 0; +} + +.toggle { + display: inline-flex; + padding: 3px; + border: 1px solid var(--hairline); + border-radius: 999px; + background: var(--ground); +} + +.toggle button { + padding: 6px 10px; + border: 0; + border-radius: 999px; + background: transparent; + color: var(--ink-3); + font-size: 0.72rem; + font-weight: 600; + cursor: pointer; +} + +.toggle button[aria-pressed='true'] { + background: var(--ink); + color: var(--panel); +} + +.device img { + display: block; + width: auto; + max-width: 100%; + height: 400px; +} + +.device figcaption { + color: var(--ink-3); + font-size: 0.68rem; + line-height: 1.4; + text-align: center; +} + +.copy h2 { + max-width: 29rem; + margin: 0 0 12px; + color: var(--ink); + font-size: clamp(1.45rem, 2.5vw, 2rem); + line-height: 1.12; +} + +.copy > p:not(.eyebrow) { + max-width: 34rem; + margin: 0; + color: var(--ink-2); + font-size: 0.98rem; + line-height: 1.6; +} + +.eyebrow { + margin: 0 0 8px; + color: var(--accent); + font-family: var(--font-mono); + font-size: 0.68rem; + font-weight: 600; + letter-spacing: 0.08em; + text-transform: uppercase; +} + +.demoLink { + display: inline-flex; + align-items: center; + gap: 0.2rem; + margin-top: 20px; + font-size: 0.9rem; + font-weight: 600; +} + +@media (max-width: 700px) { + .section { + grid-template-columns: 1fr; + gap: 22px; + margin: -12px auto 34px; + padding: 18px; + } + + .device { + grid-row: 2; + } + + .device img { + height: 390px; + } +} diff --git a/components/MastHead.js b/components/MastHead.js index c5e70df..4cd587a 100644 --- a/components/MastHead.js +++ b/components/MastHead.js @@ -2,6 +2,7 @@ import React from 'react' import Link from 'next/link' import ReplayHero from '@components/ReplayHero' +import AttendedDecisionPreview from '@components/AttendedDecisionPreview' import { track, EVENTS } from 'utils/analytics' import styles from './MastHead.module.css' @@ -114,6 +115,7 @@ export default function Home({ githubStats }) {
+
diff --git a/cypress/e2e/attended-decision-preview.cy.js b/cypress/e2e/attended-decision-preview.cy.js new file mode 100644 index 0000000..7b5486e --- /dev/null +++ b/cypress/e2e/attended-decision-preview.cy.js @@ -0,0 +1,21 @@ +describe('attended decision preview', () => { + it('switches between the real request and runner result, and links to the full demo', () => { + cy.visit('/') + + cy.get('[data-testid="attended-decision-toggle"]').within(() => { + cy.get('button').eq(1).click().should('have.attr', 'aria-pressed', 'true') + }) + cy.get('[data-testid="attended-decision-capture"]') + .should('have.attr', 'src', '/attended-decision/identity-verified.png') + + cy.get('[data-testid="attended-decision-toggle"]').within(() => { + cy.get('button').eq(0).click().should('have.attr', 'aria-pressed', 'true') + }) + cy.get('[data-testid="attended-decision-capture"]') + .should('have.attr', 'src', '/attended-decision/identity-request.png') + + cy.get('[data-testid="attended-decision-demo-link"]') + .should('have.attr', 'href', 'https://app.openadapt.ai/demo/attention') + .and('have.attr', 'target', '_blank') + }) +}) diff --git a/lib/publicJsonArtifacts.mjs b/lib/publicJsonArtifacts.mjs index 4eca10b..bbd57f9 100644 --- a/lib/publicJsonArtifacts.mjs +++ b/lib/publicJsonArtifacts.mjs @@ -33,6 +33,15 @@ export const PUBLIC_JSON_ARTIFACTS = Object.freeze({ 'Capture provenance and content hashes for the public Desktop screenshots.', sha256: '8c0c67b7af7d4cedbf93175e4a74d1a7ac7f19b799e309dbeb044cf8dced30ee', }), + '/attended-decision/provenance.json': Object.freeze({ + source: '/attended-decision/provenance.json', + fileName: 'provenance.json', + format: 'json', + title: 'Attended decision capture provenance', + description: + 'Source, action sequence, and content hashes for the public mobile decision captures.', + sha256: '1592c08273916431af74b2af509c1aa67afde21df69c0d689194da29ec03cb77', + }), '/lending-demo/provenance.json': Object.freeze({ source: '/lending-demo/provenance.json', fileName: 'provenance.json', diff --git a/public/attended-decision/identity-request.png b/public/attended-decision/identity-request.png new file mode 100644 index 0000000..9deb012 Binary files /dev/null and b/public/attended-decision/identity-request.png differ diff --git a/public/attended-decision/identity-verified.png b/public/attended-decision/identity-verified.png new file mode 100644 index 0000000..8f4516a Binary files /dev/null and b/public/attended-decision/identity-verified.png differ diff --git a/public/attended-decision/provenance.json b/public/attended-decision/provenance.json new file mode 100644 index 0000000..d81c86d --- /dev/null +++ b/public/attended-decision/provenance.json @@ -0,0 +1,41 @@ +{ + "source": { + "url": "https://app.openadapt.ai/demo/attention", + "captured_at": "2026-07-29", + "method": "Headless Playwright Chromium capture of the live public .attention-device element", + "viewport": { + "width": 768, + "height": 1100, + "device_scale_factor": 2 + }, + "data_boundary": "The public demo uses synthetic OpenEMR data. These captures do not show customer data." + }, + "captures": [ + { + "file": "identity-request.png", + "state": "Initial public identity request before an operator action; the retained OpenEMR evidence image completed with nonzero natural dimensions before capture", + "sha256": "9c9c8673b3497d61fd0aa636fb175ace4fd0f37bcf2b738149a80eefaf8f6cec", + "dimensions": { + "width": 860, + "height": 1936 + } + }, + { + "file": "identity-verified.png", + "state": "Public runner result after the permitted Check identity action", + "sha256": "5a83d5fd4b02f8c16f6ebb25f3ebb6eace839c3fede27077cde0c18945c5d728", + "dimensions": { + "width": 860, + "height": 1686 + } + } + ], + "sequence": [ + "Open the public attention demo.", + "Wait until the retained OpenEMR evidence image is visible, complete, and has nonzero natural width and height.", + "Capture the .attention-device element in its initial identity-request state.", + "Select the permitted Check identity action.", + "Wait for the Identity verified runner receipt.", + "Capture the same .attention-device element." + ] +}