personal memory agent
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

Simplify transcripts viewer: mask only in modal, click to reveal

- Remove aruco masking from thumbnails (only apply in modal view)
- Remove unused boxCoords/aruco data attributes from thumbnail rendering
- Consolidate duplicate scaleX/scaleY calculations in _applyOverlays
- Add click-to-reveal on masked frames in modal with "Masked" badge
- Reset mask state when navigating between frames

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>

+51 -48
+51 -48
apps/transcripts/workspace.html
··· 449 449 animation: tr-pulse 1.5s ease-in-out infinite; 450 450 } 451 451 452 + .tr-modal-img-wrap canvas.tr-masked-canvas { 453 + cursor: pointer; 454 + } 455 + 456 + .tr-modal-badge-masked { 457 + background: rgba(239, 68, 68, 0.3); 458 + color: #fca5a5; 459 + } 460 + 452 461 .tr-modal-description { 453 462 padding: 12px 20px 20px; 454 463 color: rgba(255,255,255,0.8); ··· 973 982 this.pendingThumbs.clear(); 974 983 } 975 984 976 - // Draw thumbnail to canvas with optional annotations 985 + // Draw thumbnail to canvas (no overlays - those are only for full frame view) 977 986 async drawThumbnail(canvas, videoUrl, frameId, options = {}) { 978 - const { width, height, aruco } = options; 987 + const { width, height } = options; 979 988 980 989 try { 981 990 const thumb = await this.captureThumbnail(videoUrl, frameId, width, height); ··· 989 998 990 999 const ctx = canvas.getContext('2d'); 991 1000 ctx.drawImage(thumb, 0, 0, canvas.width, canvas.height); 992 - 993 - // Apply aruco mask overlay if present 994 - // Note: thumb is at target size, so sourceWidth/Height = canvas dimensions 995 - if (aruco) { 996 - // Get original video dimensions for proper scaling 997 - const entry = await this.loadVideo(videoUrl); 998 - this._applyOverlays(ctx, canvas, entry.width, entry.height, { aruco }); 999 - } 1000 1001 1001 1002 canvas.classList.remove('loading'); 1002 1003 return true; ··· 1065 1066 1066 1067 _applyOverlays(ctx, canvas, sourceWidth, sourceHeight, options = {}) { 1067 1068 const { boxCoords, participants, aruco } = options; 1069 + const scaleX = canvas.width / sourceWidth; 1070 + const scaleY = canvas.height / sourceHeight; 1068 1071 1069 1072 // Apply ArUco mask first (so other overlays draw on top) 1070 1073 const maskPolygon = this._computeArucoMaskPolygon(aruco); 1071 1074 if (maskPolygon) { 1072 - const scaleX = canvas.width / sourceWidth; 1073 - const scaleY = canvas.height / sourceHeight; 1074 - 1075 1075 ctx.fillStyle = '#000000'; 1076 1076 ctx.beginPath(); 1077 1077 ctx.moveTo(maskPolygon[0][0] * scaleX, maskPolygon[0][1] * scaleY); ··· 1084 1084 1085 1085 if (boxCoords && boxCoords.length === 4) { 1086 1086 const [xMin, yMin, xMax, yMax] = boxCoords; 1087 - const scaleX = canvas.width / sourceWidth; 1088 - const scaleY = canvas.height / sourceHeight; 1089 - 1090 1087 ctx.strokeStyle = '#ef4444'; 1091 1088 ctx.lineWidth = 3; 1092 1089 ctx.strokeRect( ··· 1568 1565 function loadCanvasThumbnail(canvas) { 1569 1566 const videoUrl = canvas.dataset.videoUrl; 1570 1567 const frameId = parseInt(canvas.dataset.frameId, 10); 1571 - const boxCoordsStr = canvas.dataset.boxCoords; 1572 - const boxCoords = boxCoordsStr ? JSON.parse(boxCoordsStr) : null; 1573 - const arucoStr = canvas.dataset.aruco; 1574 - const aruco = arucoStr ? JSON.parse(arucoStr) : null; 1575 1568 1576 1569 if (!videoUrl || isNaN(frameId)) { 1577 1570 canvas.classList.remove('loading'); 1578 1571 return; 1579 1572 } 1580 1573 1581 - // Draw at thumbnail size (120x68) 1574 + // Draw at thumbnail size (120x68) - no overlays on thumbnails 1582 1575 frameCapture.drawThumbnail(canvas, videoUrl, frameId, { 1583 - boxCoords, 1584 - aruco, 1585 1576 width: 120, 1586 1577 height: 68 1587 1578 }); ··· 1659 1650 for (const entry of group.entries) { 1660 1651 const videoUrl = getVideoUrlForChunk(entry); 1661 1652 const frameId = entry.source_ref?.frame_id; 1662 - const boxCoords = entry.source_ref?.box_2d; 1663 - const aruco = entry.source_ref?.aruco; 1664 1653 const analysis = entry.source_ref?.analysis || {}; 1665 1654 const category = analysis.primary || 'unknown'; 1666 1655 const description = analysis.visual_description || category; ··· 1668 1657 1669 1658 if (videoUrl && frameId) { 1670 1659 html += `<div class="tr-group-item" data-frame-idx="${frameIdx}" title="${escapeHtml(description)}">`; 1671 - html += `<canvas class="loading" data-video-url="${escapeHtml(videoUrl)}" data-frame-id="${frameId}"`; 1672 - if (boxCoords) html += ` data-box-coords='${JSON.stringify(boxCoords)}'`; 1673 - if (aruco) html += ` data-aruco='${JSON.stringify(aruco)}'`; 1674 - html += `></canvas>`; 1660 + html += `<canvas class="loading" data-video-url="${escapeHtml(videoUrl)}" data-frame-id="${frameId}"></canvas>`; 1675 1661 html += `<span class="tr-group-item-badge">${escapeHtml(category)}</span>`; 1676 1662 html += '</div>'; 1677 1663 } ··· 1687 1673 const monitor = chunk.source_ref?.monitor || ''; 1688 1674 const videoUrl = getVideoUrlForChunk(chunk); 1689 1675 const frameId = chunk.source_ref?.frame_id; 1690 - const boxCoords = chunk.source_ref?.box_2d; 1691 - const aruco = chunk.source_ref?.aruco; 1692 1676 const frameIdx = findFrameIndex(chunk); 1693 1677 1694 1678 let html = `<div class="tr-entry tr-entry-screen" data-idx="${idx}" data-frame-idx="${frameIdx}" data-type="screen">`; ··· 1696 1680 html += '<div class="tr-entry-content">'; 1697 1681 1698 1682 if (videoUrl && frameId) { 1699 - html += `<canvas class="tr-entry-thumb loading" data-video-url="${escapeHtml(videoUrl)}" data-frame-id="${frameId}"`; 1700 - if (boxCoords) html += ` data-box-coords='${JSON.stringify(boxCoords)}'`; 1701 - if (aruco) html += ` data-aruco='${JSON.stringify(aruco)}'`; 1702 - html += `></canvas>`; 1683 + html += `<canvas class="tr-entry-thumb loading" data-video-url="${escapeHtml(videoUrl)}" data-frame-id="${frameId}"></canvas>`; 1703 1684 } 1704 1685 1705 1686 html += '<div class="tr-entry-desc">'; ··· 1729 1710 if (frameIndex < 0 || frameIndex >= allScreenFrames.length) return; 1730 1711 1731 1712 currentFrameIndex = frameIndex; 1713 + let maskHidden = false; // Track if user has revealed masked content 1732 1714 1733 1715 const modal = document.createElement('div'); 1734 1716 modal.className = 'tr-screenshot-modal'; 1735 1717 modal.id = 'trImageModal'; 1736 1718 1719 + const drawFrame = (canvas, f, showMask) => { 1720 + const videoUrl = getVideoUrlForChunk(f); 1721 + const frameId = f.source_ref?.frame_id; 1722 + const boxCoords = f.source_ref?.box_2d; 1723 + const aruco = f.source_ref?.aruco; 1724 + const participants = f.source_ref?.participants || []; 1725 + 1726 + if (videoUrl && frameId) { 1727 + frameCapture.drawFull(canvas, videoUrl, frameId, { 1728 + boxCoords, 1729 + participants, 1730 + aruco: showMask ? aruco : null // Pass null to skip mask 1731 + }); 1732 + } else { 1733 + canvas.classList.remove('loading'); 1734 + } 1735 + }; 1736 + 1737 1737 const updateModalContent = () => { 1738 1738 const f = allScreenFrames[currentFrameIndex]; 1739 1739 const monitor = f.source_ref?.monitor || ''; ··· 1741 1741 const analysis = f.source_ref?.analysis || {}; 1742 1742 const category = analysis.primary || ''; 1743 1743 const description = analysis.visual_description || ''; 1744 - const participants = f.source_ref?.participants || []; 1744 + const aruco = f.source_ref?.aruco; 1745 + const isMasked = aruco?.masked && !maskHidden; 1745 1746 const hasPrev = currentFrameIndex > 0; 1746 1747 const hasNext = currentFrameIndex < allScreenFrames.length - 1; 1747 1748 ··· 1753 1754 <div class="tr-modal-header"> 1754 1755 ${monitorPos ? `<span class="tr-modal-badge tr-modal-badge-monitor">${monitorPos}</span>` : ''} 1755 1756 ${category ? `<span class="tr-modal-badge tr-modal-badge-category">${escapeHtml(category)}</span>` : ''} 1757 + ${isMasked ? '<span class="tr-modal-badge tr-modal-badge-masked" title="Click image to reveal">Masked</span>' : ''} 1756 1758 <button class="tr-modal-close" title="Close (Esc)">&times;</button> 1757 1759 </div> 1758 1760 <div class="tr-modal-img-wrap"> 1759 - <canvas id="trModalCanvas" class="loading"></canvas> 1761 + <canvas id="trModalCanvas" class="loading${isMasked ? ' tr-masked-canvas' : ''}"></canvas> 1760 1762 </div> 1761 1763 ${description ? `<div class="tr-modal-description">${escapeHtml(description)}</div>` : ''} 1762 1764 </div> ··· 1767 1769 1768 1770 // Draw frame to modal canvas 1769 1771 const canvas = modal.querySelector('#trModalCanvas'); 1770 - const videoUrl = getVideoUrlForChunk(f); 1771 - const frameId = f.source_ref?.frame_id; 1772 - const boxCoords = f.source_ref?.box_2d; 1773 - const aruco = f.source_ref?.aruco; 1772 + drawFrame(canvas, f, !maskHidden); 1774 1773 1775 - if (videoUrl && frameId) { 1776 - frameCapture.drawFull(canvas, videoUrl, frameId, { 1777 - boxCoords, 1778 - participants, 1779 - aruco 1774 + // Add click-to-reveal handler for masked frames 1775 + if (aruco?.masked) { 1776 + canvas.addEventListener('click', () => { 1777 + if (!maskHidden) { 1778 + maskHidden = true; 1779 + canvas.classList.remove('tr-masked-canvas'); 1780 + canvas.classList.add('loading'); 1781 + modal.querySelector('.tr-modal-badge-masked')?.remove(); 1782 + drawFrame(canvas, f, false); 1783 + } 1780 1784 }); 1781 - } else { 1782 - canvas.classList.remove('loading'); 1783 1785 } 1784 1786 }; 1785 1787 ··· 1787 1789 const newIndex = currentFrameIndex + delta; 1788 1790 if (newIndex >= 0 && newIndex < allScreenFrames.length) { 1789 1791 currentFrameIndex = newIndex; 1792 + maskHidden = false; // Reset mask state when navigating 1790 1793 updateModalContent(); 1791 1794 } 1792 1795 };