{
  "format": "odb-inspection/1",
  "executed": false,
  "source": {
    "path": "components/video/frame-viewer/1.0.0/viewer.js",
    "sha256": "78a13574f0771a01d12d4d2bc8ca109f0f446a2f63f81e32c23b29277e78abbf",
    "build": "No compilation; isolated web component JavaScript.",
    "text": "// SPDX-License-Identifier: AGPL-3.0-or-later\n// Source-independent PNG frame viewer. Runs without camera, USB or network permission.\nclass OdbVideoViewer extends HTMLElement {\n  connectedCallback() {\n    this.innerHTML = '<style>odb-video-viewer{display:flex;flex-direction:column;box-sizing:border-box;height:100%;padding:12px;font:15px system-ui;color:#eee;background:#090b13}canvas{width:100%;height:100%;min-height:0;object-fit:contain;background:#000;flex:1}p{margin:8px 0 0}small{color:#bec5d6}</style><canvas aria-label=\"Video frame\"></canvas><p role=\"status\">Waiting for an image</p><small>Uncalibrated preview</small>';\n  }\n  async showFrame(frame) {\n    if (frame?.format !== 'odb.video.frame/1' || frame.encoding !== 'image/png' || !(frame.data instanceof ArrayBuffer) || frame.data.byteLength > 8388608 || !Number.isInteger(frame.width) || !Number.isInteger(frame.height) || frame.width < 1 || frame.height < 1 || frame.width > 2048 || frame.height > 2048) throw Error('Invalid video frame');\n    const image = await createImageBitmap(new Blob([frame.data], {type:'image/png'}));\n    try {\n      if (image.width !== frame.width || image.height !== frame.height) throw Error('Frame dimensions differ from metadata');\n      const canvas = this.querySelector('canvas'); canvas.width = image.width; canvas.height = image.height;\n      canvas.getContext('2d').drawImage(image, 0, 0);\n      this.querySelector('p').textContent = `${frame.mode === 'replay' ? 'Recorded replay' : frame.mode === 'live' ? 'Live preview' : 'Last acquired image'} · frame ${frame.sequence}`;\n      this.querySelector('small').textContent = `${image.width} × ${image.height} · ${frame.profile === 'unknown' ? 'Probe not identified · ' : ''}Uncalibrated preview`;\n    } finally { image.close(); }\n  }\n}\ncustomElements.define('odb-video-viewer', OdbVideoViewer);\nconst viewer = document.createElement('odb-video-viewer'); document.body.style.cssText = 'margin:0;height:100vh'; document.body.append(viewer);\naddEventListener('message', event => {\n  if (event.source !== parent || event.data !== 'odb-ui-connect/1' || event.ports.length !== 1) return;\n  const port = event.ports[0];\n  port.onmessage = async ({data}) => {\n    try { await viewer.showFrame(data); port.postMessage({type:'odb-video-rendered/1', session:data.session, sequence:data.sequence}); }\n    catch { port.postMessage({type:'odb-video-error/1'}); }\n  }; port.start();\n}, {once:true});\n"
  },
  "behavior": "Receives PNG ArrayBuffers through a private MessagePort. Decodes one frame, draws it on canvas, releases the bitmap, then acknowledges its session and sequence. No network or device access. Shows replay/live provenance, dimensions and uncalibrated status. No distance or diagnostic interpretation.",
  "ports": {
    "frames": {
      "direction": "in",
      "kind": "stream",
      "contract": "odb.video.frame/1"
    }
  },
  "parameters": {},
  "fixtures": [],
  "evidence": []
}