Skip to content

Mashlib Data Browser: CDN race condition fix and dual-mode support #8

@melvincarvalho

Description

@melvincarvalho

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:

  1. HTML parsing completes
  2. DOMContentLoaded fires before the CDN script finishes downloading
  3. panes is undefined or partially initialized
  4. 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

  1. src/mashlib/index.js - generateDatabrowserHtml() now supports CDN mode with script.onload
  2. src/server.js - Added mashlibCdn option and request decoration
  3. src/handlers/resource.js - Pass CDN version to HTML generator, added cache headers
  4. src/config.js - Added mashlibCdn default and env var mapping
  5. bin/jss.js - Added --mashlib-cdn CLI 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 URLs

Local 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.js

Browser Test

  1. Open incognito window
  2. Navigate to an RDF resource URL
  3. First load should render correctly (no recursive nesting, no blank content)
  4. 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


This issue was created with assistance from Claude Code during a debugging session that identified and fixed the race condition.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions