Skip to main content

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 HTML onclick attributes can only call functions on window. Each module exposes its handler functions at the bottom using Object.assign:
The HTML then just uses the function name directly:
Rule: Every function referenced in an 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:
The key rule: 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:
Then anywhere in data.js or modules that data.js calls:
This is the only mechanism for lower-layer modules to trigger a view update.

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:
Safe values (hardcoded strings, enum results, numbers from your own code) do not need escaping, but when in doubt, escape it. Never use innerHTML with concatenated user input without escapeHTML. The shared renderMarkdown() function in markdown.js validates URLs against an allowlist (http, https, mailto) before rendering link tags; chat renderers call that shared boundary rather than implementing their own Markdown path.

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:
Toggle functions always compute data once and pass it through to all sub-renderers:
This prevents redundant pipeline runs when multiple views refresh simultaneously.

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:
Toggle in Settings → Display → “Verbose console logging” (moved from Privacy in v1.7.x — debug mode also reveals diagnostic UI in the sync popover + Push-efficiency / Lean-sync-mode panels in the Diagnose modal, not a privacy posture).