-
Notifications
You must be signed in to change notification settings - Fork 2
Description
Summary
This issue documents the discovery and fix of a race condition bug when loading Mashlib (SolidOS data browser) from CDN, and introduces dual-mode support (local + CDN).
Problem Description
When serving RDF resources with Accept: text/html, JSS returns an HTML wrapper that loads Mashlib to render the data. Two critical bugs were discovered:
Bug 1: Recursive Nesting (CDN mode)
Symptoms:
- On first page load, the Mashlib UI renders recursively nested inside itself infinitely
- Hard refresh (Ctrl+Shift+R) fixes the issue temporarily
- Consistent reproduction in incognito/fresh browser sessions
Root Cause:
The original implementation used defer + DOMContentLoaded:
<script>document.addEventListener('DOMContentLoaded', function() {
panes.runDataBrowser()
})</script>
<script defer="defer" src="https://unpkg.com/mashlib@2.0.0/dist/mashlib.min.js"></script>The defer attribute should make scripts execute after parsing but before DOMContentLoaded. However, with slow CDN loading, there's a race condition where:
- HTML parsing completes
DOMContentLoadedfires before the CDN script finishes downloadingpanesis undefined or partially initialized- Mashlib enters an error state causing recursive rendering
Bug 2: First-Load Blank Content (after partial fix)
Symptoms:
- After adding guards like
if (typeof panes !== 'undefined'), first load showed blank content - Data was fetched (304 in network tab) but UI didn't render
- Hard refresh worked
Root Cause:
Mashlib internally creates document contexts/clones. Our guard scripts ran in these cloned contexts where panes wasn't defined, preventing initialization even when it should run.
Solution
1. CDN Mode: Use script.onload Pattern
Instead of relying on defer + DOMContentLoaded, dynamically create the script element and use onload:
if (cdnVersion) {
const cdnBase = `https://unpkg.com/mashlib@${cdnVersion}/dist`;
return `<!doctype html><html><head><meta charset="utf-8"/><title>SolidOS Web App</title>
<link href="${cdnBase}/mash.css" rel="stylesheet"></head>
<body id="PageBody"><header id="PageHeader"></header>
<div class="TabulatorOutline" id="DummyUUID" role="main"><table id="outline"></table><div id="GlobalDashboard"></div></div>
<footer id="PageFooter"></footer>
<script>
(function() {
var s = document.createElement('script');
s.src = '${cdnBase}/mashlib.min.js';
s.onload = function() { panes.runDataBrowser(); };
s.onerror = function() { document.body.innerHTML = '<p>Failed to load Mashlib from CDN</p>'; };
document.head.appendChild(s);
})();
</script></body></html>`;
}This guarantees panes.runDataBrowser() only executes after mashlib.min.js is fully loaded and executed.
2. Local Mode: Match Official databrowser.html
For local hosting, use the exact HTML from mashlib's official databrowser.html:
return `<!doctype html><html><head><meta charset="utf-8"/><title>SolidOS Web App</title><script>document.addEventListener('DOMContentLoaded', function() {
panes.runDataBrowser()
})</script><script defer="defer" src="/mashlib.min.js"></script><link href="/mash.css" rel="stylesheet"></head><body id="PageBody"><header id="PageHeader"></header><div class="TabulatorOutline" id="DummyUUID" role="main"><table id="outline"></table><div id="GlobalDashboard"></div></div><footer id="PageFooter"></footer></body></html>`;Local hosting eliminates the CDN latency that causes the race condition.
3. Cache Control Headers
Added Cache-Control: no-store to the HTML wrapper response to prevent browsers from caching the HTML and confusing it with the actual RDF content:
headers['Vary'] = 'Accept';
headers['Cache-Control'] = 'no-store';CLI Options
Two modes are now supported:
| Flag | Description | Requires |
|---|---|---|
--mashlib |
Local mode - serve from src/mashlib-local/dist/ |
Local mashlib build |
--mashlib-cdn |
CDN mode - load from unpkg.com | Nothing (zero footprint) |
--mashlib-version <ver> |
Specify CDN version (default: 2.0.0) | --mashlib-cdn |
Files Changed
src/mashlib/index.js-generateDatabrowserHtml()now supports CDN mode withscript.onloadsrc/server.js- AddedmashlibCdnoption and request decorationsrc/handlers/resource.js- Pass CDN version to HTML generator, added cache headerssrc/config.js- AddedmashlibCdndefault and env var mappingbin/jss.js- Added--mashlib-cdnCLI option
Testing
CDN Mode Test
# Start server
jss start --port 4000 --mashlib-cdn --mashlib-version 2.0.0
# Verify HTML output
curl -H "Accept: text/html" http://localhost:4000/path/to/resource.ttl
# Should show script.onload pattern with unpkg.com URLsLocal Mode Test
# Start server (requires mashlib built locally)
jss start --port 4000 --mashlib
# Verify HTML output
curl -H "Accept: text/html" http://localhost:4000/path/to/resource.ttl
# Should show defer pattern with /mashlib.min.jsBrowser Test
- Open incognito window
- Navigate to an RDF resource URL
- First load should render correctly (no recursive nesting, no blank content)
- Console should only show expected "Unable to fetch your preferences" warning (normal for anonymous users)
Why This Matters
- CDN mode keeps JSS lightweight (no bundled mashlib ~2MB+)
- Local mode provides reliability for production deployments
- Both modes now work correctly on first load without race conditions
Related
- Mashlib: https://github.com/SolidOS/mashlib
- SolidOS: https://github.com/SolidOS/solidos
- Similar pattern used by: Google Analytics, Facebook SDK, and other CDN-loaded libraries
This issue was created with assistance from Claude Code during a debugging session that identified and fixed the race condition.