Cross-Module Patterns
getbased is a zero-build ES module app. Because there is no bundler, circular imports fail at runtime. These patterns are how the codebase avoids them.Window exports — for HTML onclick handlers
Inline HTMLonclick attributes can only call functions on window. Each module exposes its handler functions at the bottom using Object.assign:
onclick attribute anywhere in index.html must be in a window export in exactly one module. Never put the same function in two modules’ window exports.
Cross-module calls — window.fn() for circular dep avoidance
When module A needs to call a function from module B, but B also imports from A (creating a cycle), use window.fn() instead of a static import:
window.fn() is only for calls that would create a cycle. For everything else, use normal ES module imports.
registerRefreshCallback() — decoupled refresh triggering
data.js exports registerRefreshCallback(fn) so that modules in lower layers can trigger a full dashboard re-render without importing views.js directly.
main.js wires this up at init time, after all modules have loaded:
data.js or modules that data.js calls:
HTML interpolation — always escapeHTML()
Any user-controlled or data-derived string inserted into innerHTML must be escaped. This is the project’s primary XSS defense:
innerHTML with concatenated user input without escapeHTML. The renderMarkdown() function in chat.js validates URLs against an allowlist (http, https, mailto) before rendering link tags.
State access — import state from state.js
state.js exports a single shared mutable object. Any module that needs to read or write application state imports it directly:
state is never copied — always pass references or re-read from it. The object is also available as window._labState for debugging in the browser console.
The data parameter pattern — avoid redundant pipeline calls
getActiveData() is not cheap — it deep-clones the full marker schema and processes all entries. Rendering functions that might be called from multiple contexts accept an optional data parameter:
data once and pass it through to all sub-renderers:
Debug mode
isDebugMode() in utils.js reads the labcharts-debug localStorage flag. All console.warn and console.error calls in production are gated behind this: