From 8919dd6118e971120ccd6fd9ffe4657dcd950e3a Mon Sep 17 00:00:00 2001 From: rcholic Date: Sat, 27 Dec 2025 01:24:54 +0000 Subject: [PATCH] chore: sync extension files from sentience-chrome v2.0.2 --- sentience/extension/content.js | 8 +- sentience/extension/injected_api.js | 499 +++++++++++++++++- sentience/extension/manifest.json | 8 +- .../extension/pkg/sentience_core_bg.wasm | Bin 101498 -> 102522 bytes sentience/extension/release.json | 93 ++-- 5 files changed, 546 insertions(+), 62 deletions(-) diff --git a/sentience/extension/content.js b/sentience/extension/content.js index e625a77..833ee05 100644 --- a/sentience/extension/content.js +++ b/sentience/extension/content.js @@ -1,6 +1,12 @@ // content.js - ISOLATED WORLD (Bridge between Main World and Background) console.log('[Sentience Bridge] Loaded.'); +// Detect if we're in a child frame (for iframe support) +const isChildFrame = window !== window.top; +if (isChildFrame) { + console.log('[Sentience Bridge] Running in child frame:', window.location.href); +} + // 1. Pass Extension ID to Main World (So API knows where to find resources) document.documentElement.dataset.sentienceExtensionId = chrome.runtime.id; @@ -129,4 +135,4 @@ function handleSnapshotRequest(data) { } } -console.log('[Sentience Bridge] Ready - Extension ID:', chrome.runtime.id); +// console.log('[Sentience Bridge] Ready - Extension ID:', chrome.runtime.id); diff --git a/sentience/extension/injected_api.js b/sentience/extension/injected_api.js index 8081667..a47ad55 100644 --- a/sentience/extension/injected_api.js +++ b/sentience/extension/injected_api.js @@ -1,7 +1,7 @@ // injected_api.js - MAIN WORLD (NO WASM! CSP-Resistant!) // This script ONLY collects raw DOM data and sends it to background for processing (async () => { - console.log('[SentienceAPI] Initializing (CSP-Resistant Mode)...'); + // console.log('[SentienceAPI] Initializing (CSP-Resistant Mode)...'); // Wait for Extension ID from content.js const getExtensionId = () => document.documentElement.dataset.sentienceExtensionId; @@ -22,7 +22,7 @@ return; } - console.log('[SentienceAPI] Extension ID:', extId); + // console.log('[SentienceAPI] Extension ID:', extId); // Registry for click actions (still needed for click() function) window.sentience_registry = []; @@ -123,6 +123,102 @@ } } + // --- HELPER: Get SVG Fill/Stroke Color --- + // For SVG elements, get the fill or stroke color (SVGs use fill/stroke, not backgroundColor) + function getSVGColor(el) { + if (!el || el.tagName !== 'SVG') return null; + + const style = window.getComputedStyle(el); + + // Try fill first (most common for SVG icons) + const fill = style.fill; + if (fill && fill !== 'none' && fill !== 'transparent' && fill !== 'rgba(0, 0, 0, 0)') { + // Convert fill to rgb() format if needed + const rgbaMatch = fill.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); + if (rgbaMatch) { + const alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1.0; + if (alpha >= 0.9) { + return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`; + } + } else if (fill.startsWith('rgb(')) { + return fill; + } + } + + // Fallback to stroke if fill is not available + const stroke = style.stroke; + if (stroke && stroke !== 'none' && stroke !== 'transparent' && stroke !== 'rgba(0, 0, 0, 0)') { + const rgbaMatch = stroke.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); + if (rgbaMatch) { + const alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1.0; + if (alpha >= 0.9) { + return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`; + } + } else if (stroke.startsWith('rgb(')) { + return stroke; + } + } + + return null; + } + + // --- HELPER: Get Effective Background Color --- + // Traverses up the DOM tree to find the nearest non-transparent background color + // For SVGs, also checks fill/stroke properties + // This handles rgba(0,0,0,0) and transparent values that browsers commonly return + function getEffectiveBackgroundColor(el) { + if (!el) return null; + + // For SVG elements, use fill/stroke instead of backgroundColor + if (el.tagName === 'SVG') { + const svgColor = getSVGColor(el); + if (svgColor) return svgColor; + } + + let current = el; + const maxDepth = 10; // Prevent infinite loops + let depth = 0; + + while (current && depth < maxDepth) { + const style = window.getComputedStyle(current); + + // For SVG elements in the tree, also check fill/stroke + if (current.tagName === 'SVG') { + const svgColor = getSVGColor(current); + if (svgColor) return svgColor; + } + + const bgColor = style.backgroundColor; + + if (bgColor && bgColor !== 'transparent' && bgColor !== 'rgba(0, 0, 0, 0)') { + // Check if it's rgba with alpha < 1 (semi-transparent) + const rgbaMatch = bgColor.match(/rgba?\((\d+),\s*(\d+),\s*(\d+)(?:,\s*([\d.]+))?\)/); + if (rgbaMatch) { + const alpha = rgbaMatch[4] ? parseFloat(rgbaMatch[4]) : 1.0; + // If alpha is high enough (>= 0.9), consider it opaque enough + if (alpha >= 0.9) { + // Convert to rgb() format for Gateway compatibility + return `rgb(${rgbaMatch[1]}, ${rgbaMatch[2]}, ${rgbaMatch[3]})`; + } + // If semi-transparent, continue up the tree + } else if (bgColor.startsWith('rgb(')) { + // Already in rgb() format, use it + return bgColor; + } else { + // Named color or other format, return as-is + return bgColor; + } + } + + // Move up the DOM tree + current = current.parentElement; + depth++; + } + + // Fallback: return null if nothing found + return null; + } + // --- HELPER: Viewport Check --- function isInViewport(rect) { return ( @@ -131,8 +227,22 @@ ); } - // --- HELPER: Occlusion Check --- - function isOccluded(el, rect) { + // --- HELPER: Occlusion Check (Optimized to avoid layout thrashing) --- + // Only checks occlusion for elements likely to be occluded (high z-index, positioned) + // This avoids forced reflow for most elements, dramatically improving performance + function isOccluded(el, rect, style) { + // Fast path: Skip occlusion check for most elements + // Only check for elements that are likely to be occluded (overlays, modals, tooltips) + const zIndex = parseInt(style.zIndex, 10); + const position = style.position; + + // Skip occlusion check for normal flow elements (vast majority) + // Only check for positioned elements or high z-index (likely overlays) + if (position === 'static' && (isNaN(zIndex) || zIndex <= 10)) { + return false; // Assume not occluded for performance + } + + // For positioned/high z-index elements, do the expensive check const cx = rect.x + rect.width / 2; const cy = rect.y + rect.height / 2; @@ -492,11 +602,279 @@ return path.join(' > ') || el.tagName.toLowerCase(); } + // --- HELPER: Wait for DOM Stability (SPA Hydration) --- + // Waits for the DOM to stabilize before taking a snapshot + // Useful for React/Vue apps that render empty skeletons before hydration + async function waitForStability(options = {}) { + const { + minNodeCount = 500, + quietPeriod = 200, // milliseconds + maxWait = 5000 // maximum wait time + } = options; + + const startTime = Date.now(); + + return new Promise((resolve) => { + // Check if DOM already has enough nodes + const nodeCount = document.querySelectorAll('*').length; + if (nodeCount >= minNodeCount) { + // DOM seems ready, but wait for quiet period to ensure stability + let lastChange = Date.now(); + const observer = new MutationObserver(() => { + lastChange = Date.now(); + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: false + }); + + const checkStable = () => { + const timeSinceLastChange = Date.now() - lastChange; + const totalWait = Date.now() - startTime; + + if (timeSinceLastChange >= quietPeriod) { + observer.disconnect(); + resolve(); + } else if (totalWait >= maxWait) { + observer.disconnect(); + console.warn('[SentienceAPI] DOM stability timeout - proceeding anyway'); + resolve(); + } else { + setTimeout(checkStable, 50); + } + }; + + checkStable(); + } else { + // DOM doesn't have enough nodes yet, wait for them + const observer = new MutationObserver(() => { + const currentCount = document.querySelectorAll('*').length; + const totalWait = Date.now() - startTime; + + if (currentCount >= minNodeCount) { + observer.disconnect(); + // Now wait for quiet period + let lastChange = Date.now(); + const quietObserver = new MutationObserver(() => { + lastChange = Date.now(); + }); + + quietObserver.observe(document.body, { + childList: true, + subtree: true, + attributes: false + }); + + const checkQuiet = () => { + const timeSinceLastChange = Date.now() - lastChange; + const totalWait = Date.now() - startTime; + + if (timeSinceLastChange >= quietPeriod) { + quietObserver.disconnect(); + resolve(); + } else if (totalWait >= maxWait) { + quietObserver.disconnect(); + console.warn('[SentienceAPI] DOM stability timeout - proceeding anyway'); + resolve(); + } else { + setTimeout(checkQuiet, 50); + } + }; + + checkQuiet(); + } else if (totalWait >= maxWait) { + observer.disconnect(); + console.warn('[SentienceAPI] DOM node count timeout - proceeding anyway'); + resolve(); + } + }); + + observer.observe(document.body, { + childList: true, + subtree: true, + attributes: false + }); + + // Timeout fallback + setTimeout(() => { + observer.disconnect(); + console.warn('[SentienceAPI] DOM stability max wait reached - proceeding'); + resolve(); + }, maxWait); + } + }); + } + + // --- HELPER: Collect Iframe Snapshots (Frame Stitching) --- + // Recursively collects snapshot data from all child iframes + // This enables detection of elements inside iframes (e.g., Stripe forms) + // + // NOTE: Cross-origin iframes cannot be accessed due to browser security (Same-Origin Policy). + // Only same-origin iframes will return snapshot data. Cross-origin iframes will be skipped + // with a warning. For cross-origin iframes, users must manually switch frames using + // Playwright's page.frame() API. + async function collectIframeSnapshots(options = {}) { + const iframeData = new Map(); // Map of iframe element -> snapshot data + + // Find all iframe elements in current document + const iframes = Array.from(document.querySelectorAll('iframe')); + + if (iframes.length === 0) { + return iframeData; + } + + console.log(`[SentienceAPI] Found ${iframes.length} iframe(s), requesting snapshots...`); + + // Request snapshot from each iframe + const iframePromises = iframes.map((iframe, idx) => { + return new Promise((resolve) => { + const requestId = `iframe-${idx}-${Date.now()}`; + + // 1. EXTENDED TIMEOUT (Handle slow children) + const timeout = setTimeout(() => { + console.warn(`[SentienceAPI] ⚠️ Iframe ${idx} snapshot TIMEOUT (id: ${requestId})`); + resolve(null); + }, 10000); // Increased to 10s to handle slow processing + + // 2. ROBUST LISTENER with debugging + const listener = (event) => { + // Debug: Log all SENTIENCE_IFRAME_SNAPSHOT_RESPONSE messages to see what's happening + if (event.data?.type === 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE') { + // Only log if it's not our request (for debugging) + if (event.data?.requestId !== requestId) { + // console.log(`[SentienceAPI] Received response for different request: ${event.data.requestId} (expected: ${requestId})`); + } + } + + // Check if this is the response we're waiting for + if (event.data?.type === 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE' && + event.data?.requestId === requestId) { + + clearTimeout(timeout); + window.removeEventListener('message', listener); + + if (event.data.error) { + console.warn(`[SentienceAPI] Iframe ${idx} returned error:`, event.data.error); + resolve(null); + } else { + const elementCount = event.data.snapshot?.raw_elements?.length || 0; + console.log(`[SentienceAPI] ✓ Received ${elementCount} elements from Iframe ${idx} (id: ${requestId})`); + resolve({ + iframe: iframe, + data: event.data.snapshot, + error: null + }); + } + } + }; + + window.addEventListener('message', listener); + + // 3. SEND REQUEST with error handling + try { + if (iframe.contentWindow) { + // console.log(`[SentienceAPI] Sending request to Iframe ${idx} (id: ${requestId})`); + iframe.contentWindow.postMessage({ + type: 'SENTIENCE_IFRAME_SNAPSHOT_REQUEST', + requestId: requestId, + options: { + ...options, + collectIframes: true // Enable recursion for nested iframes + } + }, '*'); // Use '*' for cross-origin, but browser will enforce same-origin policy + } else { + console.warn(`[SentienceAPI] Iframe ${idx} contentWindow is inaccessible (Cross-Origin?)`); + clearTimeout(timeout); + window.removeEventListener('message', listener); + resolve(null); + } + } catch (error) { + console.error(`[SentienceAPI] Failed to postMessage to Iframe ${idx}:`, error); + clearTimeout(timeout); + window.removeEventListener('message', listener); + resolve(null); + } + }); + }); + + // Wait for all iframe responses + const results = await Promise.all(iframePromises); + + // Store iframe data + results.forEach((result, idx) => { + if (result && result.data && !result.error) { + iframeData.set(iframes[idx], result.data); + console.log(`[SentienceAPI] ✓ Collected snapshot from iframe ${idx}`); + } else if (result && result.error) { + console.warn(`[SentienceAPI] Iframe ${idx} snapshot error:`, result.error); + } else if (!result) { + console.warn(`[SentienceAPI] Iframe ${idx} returned no data (timeout or error)`); + } + }); + + return iframeData; + } + + // --- HELPER: Handle Iframe Snapshot Request (for child frames) --- + // When a parent frame requests snapshot, this handler responds with local snapshot + // NOTE: Recursion is safe because querySelectorAll('iframe') only finds direct children. + // Iframe A can ask Iframe B, but won't go back up to parent (no circular dependency risk). + function setupIframeSnapshotHandler() { + window.addEventListener('message', async (event) => { + // Security: only respond to snapshot requests from parent frames + if (event.data?.type === 'SENTIENCE_IFRAME_SNAPSHOT_REQUEST') { + const { requestId, options } = event.data; + + try { + // Generate snapshot for this iframe's content + // Allow recursive collection - querySelectorAll('iframe') only finds direct children, + // so Iframe A will ask Iframe B, but won't go back up to parent (safe recursion) + // waitForStability: false makes performance better - i.e. don't wait for children frames + const snapshotOptions = { ...options, collectIframes: true, waitForStability: options.waitForStability === false ? false : false }; + const snapshot = await window.sentience.snapshot(snapshotOptions); + + // Send response back to parent + if (event.source && event.source.postMessage) { + event.source.postMessage({ + type: 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE', + requestId: requestId, + snapshot: snapshot, + error: null + }, '*'); + } + } catch (error) { + // Send error response + if (event.source && event.source.postMessage) { + event.source.postMessage({ + type: 'SENTIENCE_IFRAME_SNAPSHOT_RESPONSE', + requestId: requestId, + snapshot: null, + error: error.message + }, '*'); + } + } + } + }); + } + + // Setup iframe handler when script loads (only once) + if (!window.sentience_iframe_handler_setup) { + setupIframeSnapshotHandler(); + window.sentience_iframe_handler_setup = true; + } + // --- GLOBAL API --- window.sentience = { // 1. Geometry snapshot (NEW ARCHITECTURE - No WASM in Main World!) snapshot: async (options = {}) => { try { + // Step 0: Wait for DOM stability if requested (for SPA hydration) + if (options.waitForStability !== false) { + await waitForStability(options.waitForStability || {}); + } + // Step 1: Collect raw DOM data (Main World - CSP can't block this!) const rawData = []; window.sentience_registry = []; @@ -512,9 +890,17 @@ const textVal = getText(el); const inView = isInViewport(rect); - const occluded = inView ? isOccluded(el, rect) : false; - + + // Get computed style once (needed for both occlusion check and data collection) const style = window.getComputedStyle(el); + + // Only check occlusion for elements likely to be occluded (optimized) + // This avoids layout thrashing for the vast majority of elements + const occluded = inView ? isOccluded(el, rect, style) : false; + + // Get effective background color (traverses DOM to find non-transparent color) + const effectiveBgColor = getEffectiveBackgroundColor(el); + rawData.push({ id: idx, tag: el.tagName.toLowerCase(), @@ -524,7 +910,8 @@ visibility: toSafeString(style.visibility), opacity: toSafeString(style.opacity), z_index: toSafeString(style.zIndex || "auto"), - bg_color: toSafeString(style.backgroundColor), + position: toSafeString(style.position), + bg_color: toSafeString(effectiveBgColor || style.backgroundColor), color: toSafeString(style.color), cursor: toSafeString(style.cursor), font_weight: toSafeString(style.fontWeight), @@ -543,10 +930,87 @@ }); }); - console.log(`[SentienceAPI] Collected ${rawData.length} elements, sending to background for WASM processing...`); + console.log(`[SentienceAPI] Collected ${rawData.length} elements from main frame`); + + // Step 1.5: Collect iframe snapshots and FLATTEN immediately + // "Flatten Early" architecture: Merge iframe elements into main array before WASM + // This allows WASM to process all elements uniformly (no recursion needed) + let allRawElements = [...rawData]; // Start with main frame elements + let totalIframeElements = 0; + + if (options.collectIframes !== false) { + try { + console.log(`[SentienceAPI] Starting iframe collection...`); + const iframeSnapshots = await collectIframeSnapshots(options); + console.log(`[SentienceAPI] Iframe collection complete. Received ${iframeSnapshots.size} snapshot(s)`); + + if (iframeSnapshots.size > 0) { + // FLATTEN IMMEDIATELY: Don't nest them. Just append them with coordinate translation. + iframeSnapshots.forEach((iframeSnapshot, iframeEl) => { + // Debug: Log structure to verify data is correct + // console.log(`[SentienceAPI] Processing iframe snapshot:`, iframeSnapshot); + + if (iframeSnapshot && iframeSnapshot.raw_elements) { + const rawElementsCount = iframeSnapshot.raw_elements.length; + console.log(`[SentienceAPI] Processing ${rawElementsCount} elements from iframe (src: ${iframeEl.src || 'unknown'})`); + // Get iframe's bounding rect (offset for coordinate translation) + const iframeRect = iframeEl.getBoundingClientRect(); + const offset = { x: iframeRect.x, y: iframeRect.y }; + + // Get iframe context for frame switching (Playwright needs this) + const iframeSrc = iframeEl.src || iframeEl.getAttribute('src') || ''; + let isSameOrigin = false; + try { + // Try to access contentWindow to check if same-origin + isSameOrigin = iframeEl.contentWindow !== null; + } catch (e) { + isSameOrigin = false; + } + + // Adjust coordinates and add iframe context to each element + const adjustedElements = iframeSnapshot.raw_elements.map(el => { + const adjusted = { ...el }; + + // Adjust rect coordinates to parent viewport + if (adjusted.rect) { + adjusted.rect = { + ...adjusted.rect, + x: adjusted.rect.x + offset.x, + y: adjusted.rect.y + offset.y + }; + } + + // Add iframe context so agents can switch frames in Playwright + adjusted.iframe_context = { + src: iframeSrc, + is_same_origin: isSameOrigin + }; + + return adjusted; + }); + + // Append flattened iframe elements to main array + allRawElements.push(...adjustedElements); + totalIframeElements += adjustedElements.length; + } + }); + + // console.log(`[SentienceAPI] Merged ${iframeSnapshots.size} iframe(s). Total elements: ${allRawElements.length} (${rawData.length} main + ${totalIframeElements} iframe)`); + } + } catch (error) { + console.warn('[SentienceAPI] Iframe collection failed:', error); + } + } - // Step 2: Send to background worker for WASM processing (CSP-immune!) - const processed = await processSnapshotInBackground(rawData, options); + // Step 2: Send EVERYTHING to WASM (One giant flat list) + // Now WASM prunes iframe elements and main elements in one pass! + // No recursion needed - everything is already flat + console.log(`[SentienceAPI] Sending ${allRawElements.length} total elements to WASM (${rawData.length} main + ${totalIframeElements} iframe)`); + const processed = await processSnapshotInBackground(allRawElements, options); + + if (!processed || !processed.elements) { + throw new Error('WASM processing returned invalid result'); + } // Step 3: Capture screenshot if requested let screenshot = null; @@ -558,7 +1022,16 @@ const cleanedElements = cleanElement(processed.elements); const cleanedRawElements = cleanElement(processed.raw_elements); - console.log(`[SentienceAPI] ✓ Complete: ${cleanedElements.length} elements, ${cleanedRawElements.length} raw (WASM took ${processed.duration?.toFixed(1)}ms)`); + // FIXED: Removed undefined 'totalIframeRawElements' + // FIXED: Logic updated for "Flatten Early" architecture. + // processed.elements ALREADY contains the merged iframe elements, + // so we simply use .length. No addition needed. + + const totalCount = cleanedElements.length; + const totalRaw = cleanedRawElements.length; + const iframeCount = totalIframeElements || 0; + + console.log(`[SentienceAPI] ✓ Complete: ${totalCount} Smart Elements, ${totalRaw} Raw Elements (includes ${iframeCount} from iframes) (WASM took ${processed.duration?.toFixed(1)}ms)`); return { status: "success", @@ -569,9 +1042,11 @@ }; } catch (error) { console.error('[SentienceAPI] snapshot() failed:', error); + console.error('[SentienceAPI] Error stack:', error.stack); return { status: "error", - error: error.message || 'Unknown error' + error: error.message || 'Unknown error', + stack: error.stack }; } }, diff --git a/sentience/extension/manifest.json b/sentience/extension/manifest.json index 170c6c2..7e44a1c 100644 --- a/sentience/extension/manifest.json +++ b/sentience/extension/manifest.json @@ -1,7 +1,7 @@ { "manifest_version": 3, "name": "Sentience Semantic Visual Grounding Extractor", - "version": "2.0.1", + "version": "2.0.2", "description": "Extract semantic visual grounding data from web pages", "permissions": ["activeTab", "scripting"], "host_permissions": [""], @@ -19,13 +19,15 @@ { "matches": [""], "js": ["content.js"], - "run_at": "document_start" + "run_at": "document_start", + "all_frames": true }, { "matches": [""], "js": ["injected_api.js"], "run_at": "document_idle", - "world": "MAIN" + "world": "MAIN", + "all_frames": true } ], "content_security_policy": { diff --git a/sentience/extension/pkg/sentience_core_bg.wasm b/sentience/extension/pkg/sentience_core_bg.wasm index 1b0df4b8d79a08532854a7c07bfbc352de3a6141..548f38c338524bcf98c1c2f070982b28554f5157 100644 GIT binary patch delta 25033 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 diff --git a/sentience/extension/release.json b/sentience/extension/release.json index 6b3a46f..cc1dbee 100644 --- a/sentience/extension/release.json +++ b/sentience/extension/release.json @@ -1,45 +1,45 @@ { - "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/272846250", - "assets_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/272846250/assets", - "upload_url": "https://uploads.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/272846250/assets{?name,label}", - "html_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/tag/v2.0.1", - "id": 272846250, + "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/272959448", + "assets_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/272959448/assets", + "upload_url": "https://uploads.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/272959448/assets{?name,label}", + "html_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/tag/v2.0.2", + "id": 272959448, "author": { - "login": "rcholic", - "id": 135060, - "node_id": "MDQ6VXNlcjEzNTA2MA==", - "avatar_url": "https://avatars.githubusercontent.com/u/135060?v=4", + "login": "github-actions[bot]", + "id": 41898282, + "node_id": "MDM6Qm90NDE4OTgyODI=", + "avatar_url": "https://avatars.githubusercontent.com/in/15368?v=4", "gravatar_id": "", - "url": "https://api.github.com/users/rcholic", - "html_url": "https://github.com/rcholic", - "followers_url": "https://api.github.com/users/rcholic/followers", - "following_url": "https://api.github.com/users/rcholic/following{/other_user}", - "gists_url": "https://api.github.com/users/rcholic/gists{/gist_id}", - "starred_url": "https://api.github.com/users/rcholic/starred{/owner}{/repo}", - "subscriptions_url": "https://api.github.com/users/rcholic/subscriptions", - "organizations_url": "https://api.github.com/users/rcholic/orgs", - "repos_url": "https://api.github.com/users/rcholic/repos", - "events_url": "https://api.github.com/users/rcholic/events{/privacy}", - "received_events_url": "https://api.github.com/users/rcholic/received_events", - "type": "User", + "url": "https://api.github.com/users/github-actions%5Bbot%5D", + "html_url": "https://github.com/apps/github-actions", + "followers_url": "https://api.github.com/users/github-actions%5Bbot%5D/followers", + "following_url": "https://api.github.com/users/github-actions%5Bbot%5D/following{/other_user}", + "gists_url": "https://api.github.com/users/github-actions%5Bbot%5D/gists{/gist_id}", + "starred_url": "https://api.github.com/users/github-actions%5Bbot%5D/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/github-actions%5Bbot%5D/subscriptions", + "organizations_url": "https://api.github.com/users/github-actions%5Bbot%5D/orgs", + "repos_url": "https://api.github.com/users/github-actions%5Bbot%5D/repos", + "events_url": "https://api.github.com/users/github-actions%5Bbot%5D/events{/privacy}", + "received_events_url": "https://api.github.com/users/github-actions%5Bbot%5D/received_events", + "type": "Bot", "user_view_type": "public", "site_admin": false }, - "node_id": "RE_kwDOQshiJ84QQ02q", - "tag_name": "v2.0.1", + "node_id": "RE_kwDOQshiJ84QRQfY", + "tag_name": "v2.0.2", "target_commitish": "main", - "name": "Release v2.0.1", + "name": "Release v2.0.2", "draft": false, "immutable": false, "prerelease": false, - "created_at": "2025-12-26T04:24:57Z", - "updated_at": "2025-12-26T04:32:21Z", - "published_at": "2025-12-26T04:31:58Z", + "created_at": "2025-12-27T01:12:14Z", + "updated_at": "2025-12-27T01:13:47Z", + "published_at": "2025-12-27T01:13:15Z", "assets": [ { - "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/333048763", - "id": 333048763, - "node_id": "RA_kwDOQshiJ84T2eu7", + "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/333313919", + "id": 333313919, + "node_id": "RA_kwDOQshiJ84T3fd_", "name": "extension-files.tar.gz", "label": "", "uploader": { @@ -65,17 +65,17 @@ }, "content_type": "application/gzip", "state": "uploaded", - "size": 67295, - "digest": "sha256:0ebbd7b8993470d2b9909cb39760816b0509becfd872826ff3dc0627a24d02f7", + "size": 73449, + "digest": "sha256:679e004e2be6d7ccab6030b2437353aeab74f0e5882608c3b38048ecffecff82", "download_count": 0, - "created_at": "2025-12-26T04:32:21Z", - "updated_at": "2025-12-26T04:32:21Z", - "browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.0.1/extension-files.tar.gz" + "created_at": "2025-12-27T01:13:16Z", + "updated_at": "2025-12-27T01:13:16Z", + "browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.0.2/extension-files.tar.gz" }, { - "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/333048765", - "id": 333048765, - "node_id": "RA_kwDOQshiJ84T2eu9", + "url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/assets/333313918", + "id": 333313918, + "node_id": "RA_kwDOQshiJ84T3fd-", "name": "extension-package.zip", "label": "", "uploader": { @@ -101,15 +101,16 @@ }, "content_type": "application/zip", "state": "uploaded", - "size": 70262, - "digest": "sha256:69d2acf2b358cfe35bf0697813f5e75be60ab54f88854e7e810c2d390f8f911d", + "size": 75594, + "digest": "sha256:e44d1c2d30ada8912df9b1fe3c96a69b9a74a9c6bf89f7c21397d07ec75dda9c", "download_count": 0, - "created_at": "2025-12-26T04:32:21Z", - "updated_at": "2025-12-26T04:32:21Z", - "browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.0.1/extension-package.zip" + "created_at": "2025-12-27T01:13:16Z", + "updated_at": "2025-12-27T01:13:16Z", + "browser_download_url": "https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/releases/download/v2.0.2/extension-package.zip" } ], - "tarball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/tarball/v2.0.1", - "zipball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/zipball/v2.0.1", - "body": "" + "tarball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/tarball/v2.0.2", + "zipball_url": "https://api.github.com/repos/SentienceAPI/Sentience-Geometry-Chrome-Extension/zipball/v2.0.2", + "body": "## What's Changed\r\n* Close gaps during migration from Servo + Chrome to extension migrations by @rcholic in https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/pull/17\r\n\r\n\r\n**Full Changelog**: https://github.com/SentienceAPI/Sentience-Geometry-Chrome-Extension/compare/v2.0.1...v2.0.2", + "mentions_count": 1 }