adaptar-vite-plugin 1.0.8 → 1.0.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1 +1 @@
1
- export declare const ERROR_BRIDGE_SCRIPT = "(function(){\n if (window.__ADAPTAR_ERROR_BRIDGE__) return;\n window.__ADAPTAR_ERROR_BRIDGE__ = true;\n\n var operationId = new URL(window.location.href).searchParams.get('__adaptarOperation') || undefined;\n var activeBlockingSources = Object.create(null);\n var blankCheckStartedAt = 0;\n var blankCheckTimer;\n var blankFailureReported = false;\n var BLANK_CONFIRMATION_MS = 8000;\n var ROOT_CHECK_INTERVAL_MS = 500;\n\n function readRootHealth() {\n var root = document.getElementById('root');\n var hasRootContent = Boolean(\n root && (root.children.length || (root.textContent || '').trim())\n );\n\n return {\n state: !root ? 'missing' : (hasRootContent ? 'healthy' : 'empty'),\n hasRootContent: hasRootContent,\n readyState: document.readyState\n };\n }\n\n function sendRendered(heartbeat) {\n try {\n var rootHealth = readRootHealth();\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: heartbeat ? 'render-heartbeat' : 'preview-rendered',\n operationId: operationId,\n renderedAt: Date.now(),\n hasRootContent: rootHealth.hasRootContent,\n rootHealth: rootHealth\n }, '*');\n } catch (_) {}\n }\n\n function classifyResource(target, url) {\n var tag = String((target && target.tagName) || '').toLowerCase();\n var rel = String((target && target.rel) || '').toLowerCase();\n var as = String((target && target.as) || '').toLowerCase();\n var type = String((target && target.type) || '').toLowerCase();\n var value = String(url || '').split(/[?#]/)[0].toLowerCase();\n\n if (\n as === 'font' ||\n type.indexOf('font/') === 0 ||\n /\\.(woff2?|ttf|otf|eot)$/.test(value)\n ) return 'font';\n if (\n tag === 'img' ||\n as === 'image' ||\n /\\.(avif|bmp|gif|ico|jpe?g|png|svg|webp)$/.test(value)\n ) return 'image';\n if (\n tag === 'audio' || tag === 'video' || tag === 'source' || tag === 'track' ||\n as === 'audio' || as === 'video' ||\n /\\.(aac|flac|m4a|m4v|mov|mp3|mp4|ogg|ogv|wav|webm|vtt)$/.test(value)\n ) return 'media';\n if (\n tag === 'script' ||\n as === 'script' ||\n rel === 'modulepreload' ||\n type === 'module' ||\n /\\.(c|m)?(j|t)sx?$/.test(value)\n ) return 'module';\n if (\n (tag === 'link' && rel === 'stylesheet') ||\n as === 'style' ||\n type === 'text/css' ||\n /\\.(css|less|sass|scss|styl)$/.test(value)\n ) return 'stylesheet';\n return 'unknown';\n }\n\n function resourceFailureMessage(resourceKind, url) {\n var labels = {\n font: 'Failed to load font: ',\n image: 'Failed to load image: ',\n media: 'Failed to load media: ',\n module: 'Failed to load module: ',\n stylesheet: 'Failed to load stylesheet: ',\n unknown: 'Failed to load resource: '\n };\n return (labels[resourceKind] || labels.unknown) + url;\n }\n\n function send(message, stack, filename, lineno, colno, source, plugin, metadata) {\n try {\n var details = metadata || {};\n var rootHealth = readRootHealth();\n var renderBlocking = details.renderBlocking === true || (\n details.blockWhenRootEmpty === true && !rootHealth.hasRootContent\n );\n var errorSource = source || 'unknown';\n\n if (renderBlocking) {\n activeBlockingSources[errorSource] = true;\n }\n\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'error',\n operationId: operationId,\n error: {\n message: message || 'Unknown error',\n stack: stack || '',\n filename: filename,\n lineno: lineno,\n colno: colno,\n source: source,\n plugin: plugin,\n classification: renderBlocking ? 'render-blocking' : 'diagnostic',\n category: details.category || 'runtime',\n severity: renderBlocking ? 'error' : 'advisory',\n resourceKind: details.resourceKind || 'unknown',\n renderBlocking: renderBlocking,\n rootHealth: rootHealth\n }\n }, '*');\n } catch (_) {}\n }\n\n function recoverBlockingErrorsIfRendered(reason) {\n try {\n var rootHealth = readRootHealth();\n var resolvedSources = Object.keys(activeBlockingSources);\n if (!rootHealth.hasRootContent || resolvedSources.length === 0) return;\n\n activeBlockingSources = Object.create(null);\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'preview-recovered',\n operationId: operationId,\n recoveredAt: Date.now(),\n reason: reason || 'root-healthy',\n resolvedSources: resolvedSources,\n hasRootContent: true,\n rootHealth: rootHealth\n }, '*');\n } catch (_) {}\n }\n\n // Resource failures remain observable, but only a module required before\n // the application mounts can be classified as render-blocking. Images,\n // fonts, media, and stylesheets are quality diagnostics.\n window.addEventListener('error', function(e) {\n var target = e.target || e.srcElement;\n if (target && target !== window && target.tagName) {\n var url = target.src || target.href || target.currentSrc;\n var resourceKind = classifyResource(target, url);\n if (url || resourceKind !== 'unknown') {\n send(\n resourceFailureMessage(resourceKind, url || '(unknown resource URL)'),\n '', url, undefined, undefined, 'resource-load', undefined,\n {\n category: 'resource',\n resourceKind: resourceKind,\n blockWhenRootEmpty: resourceKind === 'module'\n }\n );\n return;\n }\n }\n\n send(\n e.message || (e.error && e.error.message) || 'Runtime error occurred',\n e.error ? e.error.stack : '',\n e.filename, e.lineno, e.colno, 'runtime', undefined,\n { category: 'runtime', resourceKind: 'module', blockWhenRootEmpty: true }\n );\n }, true);\n\n // An async failure is blocking only when the application has no rendered\n // root. Interaction failures in an otherwise-rendered page remain visible\n // diagnostics and never invalidate the candidate by themselves.\n window.addEventListener('unhandledrejection', function(e) {\n var r = e.reason;\n send(\n r && r.message ? r.message : String(r || 'Unhandled Promise Rejection'),\n r && r.stack,\n r && r.fileName,\n r && r.lineNumber,\n r && r.columnNumber,\n 'unhandledrejection', undefined,\n { category: 'runtime', resourceKind: 'module', blockWhenRootEmpty: true }\n );\n });\n\n // Intercept only errors that indicate a Vite/module compilation failure.\n // Their render-blocking status is still tied to an empty application root;\n // Vite HMR compile failures are classified separately by the server bridge.\n var nativeConsoleError = console.error;\n console.error = function() {\n var args = Array.prototype.slice.call(arguments);\n var msg = args.map(function(a) {\n return typeof a === 'string' ? a : (a && a.message ? a.message : String(a));\n }).join(' ');\n if (/Failed to load module|Internal Server Error|Syntax Error/i.test(msg)) {\n send(\n msg, '', undefined, undefined, undefined, 'console.error', undefined,\n { category: 'compile', resourceKind: 'module', blockWhenRootEmpty: true }\n );\n }\n return nativeConsoleError.apply(console, args);\n };\n\n function sendStable(rootHealth) {\n var images = Array.prototype.slice.call(document.querySelectorAll('img'));\n var controls = Array.prototype.slice.call(\n document.querySelectorAll('button, a[href], input, select, textarea')\n );\n var checks = {\n viewportWidth: document.documentElement.clientWidth || window.innerWidth || 0,\n horizontalOverflow:\n document.documentElement.scrollWidth >\n (document.documentElement.clientWidth || window.innerWidth || 0) + 2,\n brokenImages: images.filter(function(img) {\n return img.complete && img.naturalWidth === 0;\n }).length,\n missingImageAlt: images.filter(function(img) {\n return !img.hasAttribute('alt');\n }).length,\n unlabeledControls: controls.filter(function(control) {\n var label = (\n control.getAttribute('aria-label') ||\n control.getAttribute('title') ||\n control.textContent ||\n control.value ||\n ''\n ).trim();\n return !label;\n }).length\n };\n var diagnostics = [];\n\n if (checks.horizontalOverflow) {\n diagnostics.push({\n code: 'horizontal-overflow',\n classification: 'diagnostic',\n category: 'layout',\n severity: 'advisory',\n resourceKind: 'quality',\n renderBlocking: false,\n message: 'The preview has horizontal overflow.'\n });\n }\n if (checks.brokenImages > 0) {\n diagnostics.push({\n code: 'broken-images',\n classification: 'diagnostic',\n category: 'asset',\n severity: 'advisory',\n resourceKind: 'image',\n renderBlocking: false,\n message: 'One or more images could not be displayed.',\n count: checks.brokenImages\n });\n }\n if (checks.missingImageAlt > 0) {\n diagnostics.push({\n code: 'missing-image-alt',\n classification: 'diagnostic',\n category: 'accessibility',\n severity: 'advisory',\n resourceKind: 'image',\n renderBlocking: false,\n message: 'One or more images are missing alternative text.',\n count: checks.missingImageAlt\n });\n }\n if (checks.unlabeledControls > 0) {\n diagnostics.push({\n code: 'unlabeled-controls',\n classification: 'diagnostic',\n category: 'accessibility',\n severity: 'advisory',\n resourceKind: 'control',\n renderBlocking: false,\n message: 'One or more controls do not have an accessible label.',\n count: checks.unlabeledControls\n });\n }\n\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'preview-stable',\n operationId: operationId,\n stableAt: Date.now(),\n hasRootContent: rootHealth.hasRootContent,\n rootHealth: rootHealth,\n checks: checks,\n diagnostics: diagnostics\n }, '*');\n }\n\n function confirmPreviewHealth() {\n var rootHealth = readRootHealth();\n if (rootHealth.hasRootContent) {\n if (blankCheckTimer) {\n clearTimeout(blankCheckTimer);\n blankCheckTimer = undefined;\n }\n blankCheckStartedAt = 0;\n blankFailureReported = false;\n recoverBlockingErrorsIfRendered('root-recovered');\n sendStable(rootHealth);\n return;\n }\n\n if (!blankCheckStartedAt) blankCheckStartedAt = Date.now();\n if (Date.now() - blankCheckStartedAt >= BLANK_CONFIRMATION_MS) {\n if (!blankFailureReported) {\n blankFailureReported = true;\n send(\n 'Runtime error: Preview failed to render; a component or import may have failed silently.',\n '', location.href, undefined, undefined, 'blank-screen', undefined,\n { category: 'render', resourceKind: 'document', renderBlocking: true }\n );\n }\n blankCheckTimer = setTimeout(confirmPreviewHealth, ROOT_CHECK_INTERVAL_MS);\n return;\n }\n\n blankCheckTimer = setTimeout(confirmPreviewHealth, ROOT_CHECK_INTERVAL_MS);\n }\n\n // A blank root is blocking only when it remains blank across a sustained\n // observation window. Normal React mounting and Vite reloads are transient.\n window.addEventListener('load', function() {\n blankCheckStartedAt = Date.now();\n requestAnimationFrame(function() {\n requestAnimationFrame(function() {\n sendRendered(false);\n });\n });\n\n setTimeout(confirmPreviewHealth, 3000);\n });\n\n window.setInterval(function() {\n sendRendered(true);\n }, 20000);\n window.setInterval(function() {\n recoverBlockingErrorsIfRendered('root-health-monitor');\n }, 1000);\n})();";
1
+ export declare const ERROR_BRIDGE_SCRIPT = "(function(){\n if (window.__ADAPTAR_ERROR_BRIDGE__) return;\n window.__ADAPTAR_ERROR_BRIDGE__ = true;\n\n var operationId = new URL(window.location.href).searchParams.get('__adaptarOperation') || undefined;\n var activeBlockingSources = Object.create(null);\n var blankCheckStartedAt = 0;\n var blankCheckTimer;\n var blankFailureReported = false;\n var BLANK_CONFIRMATION_MS = 8000;\n var ROOT_CHECK_INTERVAL_MS = 500;\n\n function readRootHealth() {\n var root = document.getElementById('root');\n var hasRootContent = Boolean(\n root && (root.children.length || (root.textContent || '').trim())\n );\n\n return {\n state: !root ? 'missing' : (hasRootContent ? 'healthy' : 'empty'),\n hasRootContent: hasRootContent,\n readyState: document.readyState\n };\n }\n\n function sendRendered(heartbeat) {\n try {\n var rootHealth = readRootHealth();\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: heartbeat ? 'render-heartbeat' : 'preview-rendered',\n operationId: operationId,\n renderedAt: Date.now(),\n hasRootContent: rootHealth.hasRootContent,\n rootHealth: rootHealth\n }, '*');\n } catch (_) {}\n }\n\n function classifyResource(target, url) {\n var tag = String((target && target.tagName) || '').toLowerCase();\n var rel = String((target && target.rel) || '').toLowerCase();\n var as = String((target && target.as) || '').toLowerCase();\n var type = String((target && target.type) || '').toLowerCase();\n var value = String(url || '').split(/[?#]/)[0].toLowerCase();\n\n if (\n as === 'font' ||\n type.indexOf('font/') === 0 ||\n /\\.(woff2?|ttf|otf|eot)$/.test(value)\n ) return 'font';\n if (\n tag === 'img' ||\n as === 'image' ||\n /\\.(avif|bmp|gif|ico|jpe?g|png|svg|webp)$/.test(value)\n ) return 'image';\n if (\n tag === 'audio' || tag === 'video' || tag === 'source' || tag === 'track' ||\n as === 'audio' || as === 'video' ||\n /\\.(aac|flac|m4a|m4v|mov|mp3|mp4|ogg|ogv|wav|webm|vtt)$/.test(value)\n ) return 'media';\n if (\n tag === 'script' ||\n as === 'script' ||\n rel === 'modulepreload' ||\n type === 'module' ||\n /\\.(c|m)?(j|t)sx?$/.test(value)\n ) return 'module';\n if (\n (tag === 'link' && rel === 'stylesheet') ||\n as === 'style' ||\n type === 'text/css' ||\n /\\.(css|less|sass|scss|styl)$/.test(value)\n ) return 'stylesheet';\n return 'unknown';\n }\n\n function resourceFailureMessage(resourceKind, url) {\n var labels = {\n font: 'Failed to load font: ',\n image: 'Failed to load image: ',\n media: 'Failed to load media: ',\n module: 'Failed to load module: ',\n stylesheet: 'Failed to load stylesheet: ',\n unknown: 'Failed to load resource: '\n };\n return (labels[resourceKind] || labels.unknown) + url;\n }\n\n function send(message, stack, filename, lineno, colno, source, plugin, metadata) {\n try {\n var details = metadata || {};\n var rootHealth = readRootHealth();\n var renderBlocking = details.renderBlocking === true || (\n details.blockWhenRootEmpty === true && !rootHealth.hasRootContent\n );\n var errorSource = source || 'unknown';\n\n if (renderBlocking) {\n activeBlockingSources[errorSource] = true;\n }\n\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'error',\n operationId: operationId,\n error: {\n message: message || 'Unknown error',\n stack: stack || '',\n filename: filename,\n lineno: lineno,\n colno: colno,\n source: source,\n plugin: plugin,\n classification: renderBlocking ? 'render-blocking' : 'diagnostic',\n category: details.category || 'runtime',\n severity: renderBlocking ? 'error' : 'advisory',\n resourceKind: details.resourceKind || 'unknown',\n renderBlocking: renderBlocking,\n rootHealth: rootHealth\n }\n }, '*');\n } catch (_) {}\n }\n\n function recoverBlockingErrorsIfRendered(reason) {\n try {\n var rootHealth = readRootHealth();\n var resolvedSources = Object.keys(activeBlockingSources);\n if (!rootHealth.hasRootContent || resolvedSources.length === 0) return;\n\n activeBlockingSources = Object.create(null);\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'preview-recovered',\n operationId: operationId,\n recoveredAt: Date.now(),\n reason: reason || 'root-healthy',\n resolvedSources: resolvedSources,\n hasRootContent: true,\n rootHealth: rootHealth\n }, '*');\n } catch (_) {}\n }\n\n // Resource failures remain observable, but only a module required before\n // the application mounts can be classified as render-blocking. Images,\n // fonts, media, and stylesheets are quality diagnostics.\n window.addEventListener('error', function(e) {\n var target = e.target || e.srcElement;\n if (target && target !== window && target.tagName) {\n var url = target.src || target.href || target.currentSrc;\n var resourceKind = classifyResource(target, url);\n // Inline/module wrapper elements have no actionable resource URL. Vite's\n // HMR bridge and the sustained blank-root monitor report real compile or\n // render failures with useful evidence, so do not manufacture an\n // \"unknown resource URL\" module error here.\n if (!url) return;\n if (url || resourceKind !== 'unknown') {\n send(\n resourceFailureMessage(resourceKind, url),\n '', url, undefined, undefined, 'resource-load', undefined,\n {\n category: 'resource',\n resourceKind: resourceKind,\n blockWhenRootEmpty: resourceKind === 'module'\n }\n );\n return;\n }\n }\n\n send(\n e.message || (e.error && e.error.message) || 'Runtime error occurred',\n e.error ? e.error.stack : '',\n e.filename, e.lineno, e.colno, 'runtime', undefined,\n { category: 'runtime', resourceKind: 'module', blockWhenRootEmpty: true }\n );\n }, true);\n\n // An async failure is blocking only when the application has no rendered\n // root. Interaction failures in an otherwise-rendered page remain visible\n // diagnostics and never invalidate the candidate by themselves.\n window.addEventListener('unhandledrejection', function(e) {\n var r = e.reason;\n send(\n r && r.message ? r.message : String(r || 'Unhandled Promise Rejection'),\n r && r.stack,\n r && r.fileName,\n r && r.lineNumber,\n r && r.columnNumber,\n 'unhandledrejection', undefined,\n { category: 'runtime', resourceKind: 'module', blockWhenRootEmpty: true }\n );\n });\n\n // Intercept only errors that indicate a Vite/module compilation failure.\n // Their render-blocking status is still tied to an empty application root;\n // Vite HMR compile failures are classified separately by the server bridge.\n var nativeConsoleError = console.error;\n console.error = function() {\n var args = Array.prototype.slice.call(arguments);\n var msg = args.map(function(a) {\n return typeof a === 'string' ? a : (a && a.message ? a.message : String(a));\n }).join(' ');\n if (/Failed to load module|Internal Server Error|Syntax Error/i.test(msg)) {\n send(\n msg, '', undefined, undefined, undefined, 'console.error', undefined,\n { category: 'compile', resourceKind: 'module', blockWhenRootEmpty: true }\n );\n }\n return nativeConsoleError.apply(console, args);\n };\n\n function sendStable(rootHealth) {\n var images = Array.prototype.slice.call(document.querySelectorAll('img'));\n var controls = Array.prototype.slice.call(\n document.querySelectorAll('button, a[href], input, select, textarea')\n );\n var checks = {\n viewportWidth: document.documentElement.clientWidth || window.innerWidth || 0,\n horizontalOverflow:\n document.documentElement.scrollWidth >\n (document.documentElement.clientWidth || window.innerWidth || 0) + 2,\n brokenImages: images.filter(function(img) {\n return img.complete && img.naturalWidth === 0;\n }).length,\n missingImageAlt: images.filter(function(img) {\n return !img.hasAttribute('alt');\n }).length,\n unlabeledControls: controls.filter(function(control) {\n var label = (\n control.getAttribute('aria-label') ||\n control.getAttribute('title') ||\n control.textContent ||\n control.value ||\n ''\n ).trim();\n return !label;\n }).length\n };\n var diagnostics = [];\n\n if (checks.horizontalOverflow) {\n diagnostics.push({\n code: 'horizontal-overflow',\n classification: 'diagnostic',\n category: 'layout',\n severity: 'advisory',\n resourceKind: 'quality',\n renderBlocking: false,\n message: 'The preview has horizontal overflow.'\n });\n }\n if (checks.brokenImages > 0) {\n diagnostics.push({\n code: 'broken-images',\n classification: 'diagnostic',\n category: 'asset',\n severity: 'advisory',\n resourceKind: 'image',\n renderBlocking: false,\n message: 'One or more images could not be displayed.',\n count: checks.brokenImages\n });\n }\n if (checks.missingImageAlt > 0) {\n diagnostics.push({\n code: 'missing-image-alt',\n classification: 'diagnostic',\n category: 'accessibility',\n severity: 'advisory',\n resourceKind: 'image',\n renderBlocking: false,\n message: 'One or more images are missing alternative text.',\n count: checks.missingImageAlt\n });\n }\n if (checks.unlabeledControls > 0) {\n diagnostics.push({\n code: 'unlabeled-controls',\n classification: 'diagnostic',\n category: 'accessibility',\n severity: 'advisory',\n resourceKind: 'control',\n renderBlocking: false,\n message: 'One or more controls do not have an accessible label.',\n count: checks.unlabeledControls\n });\n }\n\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'preview-stable',\n operationId: operationId,\n stableAt: Date.now(),\n hasRootContent: rootHealth.hasRootContent,\n rootHealth: rootHealth,\n checks: checks,\n diagnostics: diagnostics\n }, '*');\n }\n\n function confirmPreviewHealth() {\n var rootHealth = readRootHealth();\n if (rootHealth.hasRootContent) {\n if (blankCheckTimer) {\n clearTimeout(blankCheckTimer);\n blankCheckTimer = undefined;\n }\n blankCheckStartedAt = 0;\n blankFailureReported = false;\n recoverBlockingErrorsIfRendered('root-recovered');\n sendStable(rootHealth);\n return;\n }\n\n if (!blankCheckStartedAt) blankCheckStartedAt = Date.now();\n if (Date.now() - blankCheckStartedAt >= BLANK_CONFIRMATION_MS) {\n if (!blankFailureReported) {\n blankFailureReported = true;\n send(\n 'Runtime error: Preview failed to render; a component or import may have failed silently.',\n '', location.href, undefined, undefined, 'blank-screen', undefined,\n { category: 'render', resourceKind: 'document', renderBlocking: true }\n );\n }\n blankCheckTimer = setTimeout(confirmPreviewHealth, ROOT_CHECK_INTERVAL_MS);\n return;\n }\n\n blankCheckTimer = setTimeout(confirmPreviewHealth, ROOT_CHECK_INTERVAL_MS);\n }\n\n // A blank root is blocking only when it remains blank across a sustained\n // observation window. Normal React mounting and Vite reloads are transient.\n window.addEventListener('load', function() {\n blankCheckStartedAt = Date.now();\n requestAnimationFrame(function() {\n requestAnimationFrame(function() {\n sendRendered(false);\n });\n });\n\n setTimeout(confirmPreviewHealth, 3000);\n });\n\n window.setInterval(function() {\n sendRendered(true);\n }, 20000);\n window.setInterval(function() {\n recoverBlockingErrorsIfRendered('root-health-monitor');\n }, 1000);\n})();";
@@ -151,9 +151,14 @@ export const ERROR_BRIDGE_SCRIPT = /* js */ `(function(){
151
151
  if (target && target !== window && target.tagName) {
152
152
  var url = target.src || target.href || target.currentSrc;
153
153
  var resourceKind = classifyResource(target, url);
154
+ // Inline/module wrapper elements have no actionable resource URL. Vite's
155
+ // HMR bridge and the sustained blank-root monitor report real compile or
156
+ // render failures with useful evidence, so do not manufacture an
157
+ // "unknown resource URL" module error here.
158
+ if (!url) return;
154
159
  if (url || resourceKind !== 'unknown') {
155
160
  send(
156
- resourceFailureMessage(resourceKind, url || '(unknown resource URL)'),
161
+ resourceFailureMessage(resourceKind, url),
157
162
  '', url, undefined, undefined, 'resource-load', undefined,
158
163
  {
159
164
  category: 'resource',
@@ -5,4 +5,4 @@
5
5
  * `iframe.contentWindow.location`. Intercepting the History API also covers
6
6
  * client-side routers, whose navigation does not trigger an iframe load.
7
7
  */
8
- export declare const NAVIGATION_BRIDGE_SCRIPT = "(function() {\n if (window.__ADAPTAR_NAVIGATION_BRIDGE__) return;\n window.__ADAPTAR_NAVIGATION_BRIDGE__ = true;\n\n var INTERNAL_OPERATION_PARAM = '__adaptarOperation';\n var operationId = new URL(window.location.href).searchParams.get(INTERNAL_OPERATION_PARAM) || undefined;\n var lastLocation = '';\n\n function readLocation() {\n var url = new URL(window.location.href);\n url.searchParams.delete(INTERNAL_OPERATION_PARAM);\n\n return {\n pathname: url.pathname || '/',\n search: url.search,\n hash: url.hash\n };\n }\n\n function reportLocation(reason) {\n try {\n var location = readLocation();\n var signature = location.pathname + location.search + location.hash;\n if (signature === lastLocation) return;\n lastLocation = signature;\n\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'preview-navigation',\n operationId: operationId,\n reason: reason,\n location: location\n }, '*');\n } catch (_) {}\n }\n\n function scheduleReport(reason) {\n if (typeof queueMicrotask === 'function') {\n queueMicrotask(function() {\n reportLocation(reason);\n });\n return;\n }\n\n Promise.resolve().then(function() {\n reportLocation(reason);\n });\n }\n\n function instrumentHistory(method) {\n try {\n var original = window.history[method];\n if (typeof original !== 'function') return;\n\n window.history[method] = function() {\n var result = original.apply(this, arguments);\n scheduleReport(method);\n return result;\n };\n } catch (_) {}\n }\n\n instrumentHistory('pushState');\n instrumentHistory('replaceState');\n\n window.addEventListener('popstate', function() {\n scheduleReport('popstate');\n });\n window.addEventListener('hashchange', function() {\n scheduleReport('hashchange');\n });\n window.addEventListener('pageshow', function() {\n scheduleReport('pageshow');\n });\n\n reportLocation('initial');\n})();";
8
+ export declare const NAVIGATION_BRIDGE_SCRIPT = "(function() {\n if (window.__ADAPTAR_NAVIGATION_BRIDGE__) return;\n window.__ADAPTAR_NAVIGATION_BRIDGE__ = true;\n\n var INTERNAL_OPERATION_PARAM = '__adaptarOperation';\n var operationId = new URL(window.location.href).searchParams.get(INTERNAL_OPERATION_PARAM) || undefined;\n var lastLocation = '';\n\n function readLocation() {\n var url = new URL(window.location.href);\n url.searchParams.delete(INTERNAL_OPERATION_PARAM);\n\n return {\n pathname: url.pathname || '/',\n search: url.search,\n hash: url.hash\n };\n }\n\n function reportLocation(reason) {\n try {\n var location = readLocation();\n var signature = location.pathname + location.search + location.hash;\n if (signature === lastLocation) return;\n lastLocation = signature;\n\n window.parent.postMessage({\n source: 'adaptar-preview',\n type: 'preview-navigation',\n operationId: operationId,\n reason: reason,\n location: location\n }, '*');\n } catch (_) {}\n }\n\n function scheduleReport(reason) {\n if (typeof queueMicrotask === 'function') {\n queueMicrotask(function() {\n reportLocation(reason);\n });\n return;\n }\n\n Promise.resolve().then(function() {\n reportLocation(reason);\n });\n }\n\n function instrumentHistory(method) {\n try {\n var original = window.history[method];\n if (typeof original !== 'function') return;\n\n window.history[method] = function() {\n var result = original.apply(this, arguments);\n scheduleReport(method);\n return result;\n };\n } catch (_) {}\n }\n\n // Older generations occasionally interpreted \"return to the top\" as a\n // route and emitted href=\"/top\". Treat that legacy value as a scroll\n // action so it cannot replace the working preview document with a route\n // that does not exist. New generations should use href=\"#top\" instead.\n function normalizeLegacyTopLink(event) {\n if (!event || event.defaultPrevented) return;\n if (event.button != null && event.button !== 0) return;\n if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;\n\n var target = event.target;\n var anchor = target && typeof target.closest === 'function'\n ? target.closest('a[href]')\n : null;\n if (!anchor || typeof anchor.getAttribute !== 'function') return;\n\n var href = anchor.getAttribute('href');\n if (href !== '/top' && href !== '/top/') return;\n\n event.preventDefault();\n try {\n window.history.replaceState({}, '', '/');\n } catch (_) {}\n\n try {\n var root = window.document && window.document.getElementById\n ? window.document.getElementById('top')\n : null;\n if (root && typeof root.scrollIntoView === 'function') {\n root.scrollIntoView({ behavior: 'smooth', block: 'start' });\n } else if (typeof window.scrollTo === 'function') {\n window.scrollTo({ top: 0, behavior: 'smooth' });\n }\n } catch (_) {}\n\n scheduleReport('legacy-top-link');\n }\n\n instrumentHistory('pushState');\n instrumentHistory('replaceState');\n\n window.addEventListener('popstate', function() {\n scheduleReport('popstate');\n });\n window.addEventListener('hashchange', function() {\n scheduleReport('hashchange');\n });\n window.addEventListener('pageshow', function() {\n scheduleReport('pageshow');\n });\n\n if (window.document && typeof window.document.addEventListener === 'function') {\n window.document.addEventListener('click', normalizeLegacyTopLink);\n }\n\n reportLocation('initial');\n})();";
@@ -67,6 +67,43 @@ export const NAVIGATION_BRIDGE_SCRIPT = /* js */ `(function() {
67
67
  } catch (_) {}
68
68
  }
69
69
 
70
+ // Older generations occasionally interpreted "return to the top" as a
71
+ // route and emitted href="/top". Treat that legacy value as a scroll
72
+ // action so it cannot replace the working preview document with a route
73
+ // that does not exist. New generations should use href="#top" instead.
74
+ function normalizeLegacyTopLink(event) {
75
+ if (!event || event.defaultPrevented) return;
76
+ if (event.button != null && event.button !== 0) return;
77
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
78
+
79
+ var target = event.target;
80
+ var anchor = target && typeof target.closest === 'function'
81
+ ? target.closest('a[href]')
82
+ : null;
83
+ if (!anchor || typeof anchor.getAttribute !== 'function') return;
84
+
85
+ var href = anchor.getAttribute('href');
86
+ if (href !== '/top' && href !== '/top/') return;
87
+
88
+ event.preventDefault();
89
+ try {
90
+ window.history.replaceState({}, '', '/');
91
+ } catch (_) {}
92
+
93
+ try {
94
+ var root = window.document && window.document.getElementById
95
+ ? window.document.getElementById('top')
96
+ : null;
97
+ if (root && typeof root.scrollIntoView === 'function') {
98
+ root.scrollIntoView({ behavior: 'smooth', block: 'start' });
99
+ } else if (typeof window.scrollTo === 'function') {
100
+ window.scrollTo({ top: 0, behavior: 'smooth' });
101
+ }
102
+ } catch (_) {}
103
+
104
+ scheduleReport('legacy-top-link');
105
+ }
106
+
70
107
  instrumentHistory('pushState');
71
108
  instrumentHistory('replaceState');
72
109
 
@@ -80,5 +117,9 @@ export const NAVIGATION_BRIDGE_SCRIPT = /* js */ `(function() {
80
117
  scheduleReport('pageshow');
81
118
  });
82
119
 
120
+ if (window.document && typeof window.document.addEventListener === 'function') {
121
+ window.document.addEventListener('click', normalizeLegacyTopLink);
122
+ }
123
+
83
124
  reportLocation('initial');
84
125
  })();`;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "adaptar-vite-plugin",
3
- "version": "1.0.8",
3
+ "version": "1.0.10",
4
4
  "description": "Vite plugin for Adaptar preview error, navigation, and selection bridging",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -151,9 +151,14 @@ export const ERROR_BRIDGE_SCRIPT = /* js */ `(function(){
151
151
  if (target && target !== window && target.tagName) {
152
152
  var url = target.src || target.href || target.currentSrc;
153
153
  var resourceKind = classifyResource(target, url);
154
+ // Inline/module wrapper elements have no actionable resource URL. Vite's
155
+ // HMR bridge and the sustained blank-root monitor report real compile or
156
+ // render failures with useful evidence, so do not manufacture an
157
+ // "unknown resource URL" module error here.
158
+ if (!url) return;
154
159
  if (url || resourceKind !== 'unknown') {
155
160
  send(
156
- resourceFailureMessage(resourceKind, url || '(unknown resource URL)'),
161
+ resourceFailureMessage(resourceKind, url),
157
162
  '', url, undefined, undefined, 'resource-load', undefined,
158
163
  {
159
164
  category: 'resource',
@@ -67,6 +67,43 @@ export const NAVIGATION_BRIDGE_SCRIPT = /* js */ `(function() {
67
67
  } catch (_) {}
68
68
  }
69
69
 
70
+ // Older generations occasionally interpreted "return to the top" as a
71
+ // route and emitted href="/top". Treat that legacy value as a scroll
72
+ // action so it cannot replace the working preview document with a route
73
+ // that does not exist. New generations should use href="#top" instead.
74
+ function normalizeLegacyTopLink(event) {
75
+ if (!event || event.defaultPrevented) return;
76
+ if (event.button != null && event.button !== 0) return;
77
+ if (event.metaKey || event.ctrlKey || event.shiftKey || event.altKey) return;
78
+
79
+ var target = event.target;
80
+ var anchor = target && typeof target.closest === 'function'
81
+ ? target.closest('a[href]')
82
+ : null;
83
+ if (!anchor || typeof anchor.getAttribute !== 'function') return;
84
+
85
+ var href = anchor.getAttribute('href');
86
+ if (href !== '/top' && href !== '/top/') return;
87
+
88
+ event.preventDefault();
89
+ try {
90
+ window.history.replaceState({}, '', '/');
91
+ } catch (_) {}
92
+
93
+ try {
94
+ var root = window.document && window.document.getElementById
95
+ ? window.document.getElementById('top')
96
+ : null;
97
+ if (root && typeof root.scrollIntoView === 'function') {
98
+ root.scrollIntoView({ behavior: 'smooth', block: 'start' });
99
+ } else if (typeof window.scrollTo === 'function') {
100
+ window.scrollTo({ top: 0, behavior: 'smooth' });
101
+ }
102
+ } catch (_) {}
103
+
104
+ scheduleReport('legacy-top-link');
105
+ }
106
+
70
107
  instrumentHistory('pushState');
71
108
  instrumentHistory('replaceState');
72
109
 
@@ -80,5 +117,9 @@ export const NAVIGATION_BRIDGE_SCRIPT = /* js */ `(function() {
80
117
  scheduleReport('pageshow');
81
118
  });
82
119
 
120
+ if (window.document && typeof window.document.addEventListener === 'function') {
121
+ window.document.addEventListener('click', normalizeLegacyTopLink);
122
+ }
123
+
83
124
  reportLocation('initial');
84
125
  })();`;
@@ -0,0 +1,94 @@
1
+ import assert from "node:assert/strict";
2
+ import test from "node:test";
3
+ import vm from "node:vm";
4
+ import { ERROR_BRIDGE_SCRIPT } from "../dist/scripts/error-bridge.js";
5
+
6
+ function createErrorBridgeRuntime() {
7
+ const listeners = new Map();
8
+ const messages = [];
9
+ const root = { children: [], textContent: "" };
10
+ const location = {
11
+ href: "https://workspace.preview.adaptar.dev/?__adaptarOperation=op-1",
12
+ };
13
+ const document = {
14
+ documentElement: { clientWidth: 1280, scrollWidth: 1280 },
15
+ getElementById(id) {
16
+ return id === "root" ? root : null;
17
+ },
18
+ querySelectorAll() {
19
+ return [];
20
+ },
21
+ };
22
+ const window = {
23
+ location,
24
+ innerWidth: 1280,
25
+ parent: {
26
+ postMessage(message) {
27
+ messages.push(JSON.parse(JSON.stringify(message)));
28
+ },
29
+ },
30
+ addEventListener(type, listener) {
31
+ const registered = listeners.get(type) || [];
32
+ registered.push(listener);
33
+ listeners.set(type, registered);
34
+ },
35
+ setInterval() {
36
+ return 1;
37
+ },
38
+ };
39
+ window.window = window;
40
+
41
+ vm.runInNewContext(ERROR_BRIDGE_SCRIPT, {
42
+ URL,
43
+ clearTimeout() {},
44
+ console: { error() {} },
45
+ document,
46
+ location,
47
+ requestAnimationFrame() {},
48
+ setTimeout() {
49
+ return 1;
50
+ },
51
+ window,
52
+ });
53
+
54
+ return {
55
+ messages,
56
+ dispatchResourceError(target) {
57
+ for (const listener of listeners.get("error") || []) {
58
+ listener({ target, srcElement: target });
59
+ }
60
+ },
61
+ };
62
+ }
63
+
64
+ test("ignores URL-less inline module resource events", () => {
65
+ const runtime = createErrorBridgeRuntime();
66
+
67
+ runtime.dispatchResourceError({
68
+ tagName: "SCRIPT",
69
+ type: "module",
70
+ src: "",
71
+ href: "",
72
+ currentSrc: "",
73
+ });
74
+
75
+ assert.deepEqual(runtime.messages, []);
76
+ });
77
+
78
+ test("still reports an external module resource failure with its URL", () => {
79
+ const runtime = createErrorBridgeRuntime();
80
+ const src = "https://workspace.preview.adaptar.dev/src/main.tsx";
81
+
82
+ runtime.dispatchResourceError({
83
+ tagName: "SCRIPT",
84
+ type: "module",
85
+ src,
86
+ href: "",
87
+ currentSrc: src,
88
+ });
89
+
90
+ assert.equal(runtime.messages.length, 1);
91
+ assert.equal(runtime.messages[0].type, "error");
92
+ assert.equal(runtime.messages[0].error.message, `Failed to load module: ${src}`);
93
+ assert.equal(runtime.messages[0].error.resourceKind, "module");
94
+ });
@@ -6,6 +6,7 @@ import { NAVIGATION_BRIDGE_SCRIPT } from "../dist/scripts/navigation-bridge.js";
6
6
  function createPreviewRuntime(initialUrl) {
7
7
  const messages = [];
8
8
  const listeners = new Map();
9
+ const documentListeners = new Map();
9
10
  const location = { href: initialUrl };
10
11
  const parent = {
11
12
  postMessage(message) {
@@ -20,10 +21,25 @@ function createPreviewRuntime(initialUrl) {
20
21
  location.href = new URL(String(nextUrl), location.href).toString();
21
22
  },
22
23
  };
24
+ const document = {
25
+ addEventListener(type, listener) {
26
+ const registered = documentListeners.get(type) || [];
27
+ registered.push(listener);
28
+ documentListeners.set(type, registered);
29
+ },
30
+ getElementById() {
31
+ return null;
32
+ },
33
+ };
34
+ const scrollCalls = [];
23
35
  const window = {
24
36
  location,
25
37
  parent,
26
38
  history,
39
+ document,
40
+ scrollTo(options) {
41
+ scrollCalls.push(options);
42
+ },
27
43
  addEventListener(type, listener) {
28
44
  const registered = listeners.get(type) || [];
29
45
  registered.push(listener);
@@ -50,6 +66,25 @@ function createPreviewRuntime(initialUrl) {
50
66
  listener();
51
67
  }
52
68
  },
69
+ clickLegacyTopLink() {
70
+ let prevented = false;
71
+ const anchor = {
72
+ getAttribute(name) {
73
+ return name === "href" ? "/top" : null;
74
+ },
75
+ };
76
+ anchor.closest = () => anchor;
77
+ for (const listener of documentListeners.get("click") || []) {
78
+ listener({
79
+ target: anchor,
80
+ button: 0,
81
+ preventDefault() {
82
+ prevented = true;
83
+ },
84
+ });
85
+ }
86
+ return { prevented, scrollCalls };
87
+ },
53
88
  };
54
89
  }
55
90
 
@@ -98,3 +133,15 @@ test("reports browser history and suppresses duplicate locations", () => {
98
133
  assert.equal(runtime.messages[1].location.pathname, "/contact");
99
134
  assert.equal(runtime.messages[1].reason, "popstate");
100
135
  });
136
+
137
+ test("normalizes legacy /top logo links into a scroll-to-root action", () => {
138
+ const runtime = createPreviewRuntime(
139
+ "https://workspace.preview.adaptar.dev/work?__adaptarOperation=operation-1",
140
+ );
141
+
142
+ const result = runtime.clickLegacyTopLink();
143
+
144
+ assert.equal(result.prevented, true);
145
+ assert.equal(new URL(runtime.messages.at(-1).location.pathname, "https://preview").pathname, "/");
146
+ assert.equal(runtime.messages.at(-1).reason, "replaceState");
147
+ });