> ## Documentation Index
> Fetch the complete documentation index at: https://docs.getbased.health/llms.txt
> Use this file to discover all available pages before exploring further.

# Cross-module patterns

> Window exports, circular dependency avoidance, and shared state patterns in getbased.

# Cross-Module Patterns

getbased is authored in TypeScript and compiled to JavaScript ES modules. Local development loads those emitted modules directly; production uses Rolldown bundles. Keep runtime imports ending in `.js` and use explicit dependencies and configured callbacks to avoid cycles. The architecture checker rejects new dependency cycles.

## Compatibility handlers and delegated actions

Existing inline HTML handlers call functions exposed on `window`. Some modules retain `Object.assign(window, {...})` as a compatibility surface. Static shell controls also use delegated actions installed by `shell-actions.ts`, and lazy features expose loader or runtime entry points.

When changing a handler, preserve its public name and availability at the time the control becomes usable. Do not install competing owners for the same compatibility export. Prefer feature-owned delegates and narrow typed callbacks for new wiring; do not expand global exports simply to avoid designing a dependency boundary.

## Cross-module calls and runtime seams

Use a normal ES-module import for an allowed dependency:

```ts theme={null}
import { showNotification } from './utils.js';
import { state } from './state.js';
```

When a lower-level service needs a higher-level UI action, inject a callback through a small configuration or runtime seam. Existing `window` calls remain compatibility paths, but the target direction is composition → UI/workflow → domain service → foundation/storage. See the [architecture contract](https://github.com/elkimek/get-based/blob/main/ARCHITECTURE.md#target-dependency-direction-inside-js).

## Decoupled refresh triggering

`data.ts` exposes `registerRefreshCallback()` so data code can request a refresh without importing the view/router graph. `startup-orchestrator.ts` calls `registerAppRefreshCallback()` from `app-event-listeners.ts`; that callback rebuilds the sidebar, navigates to `state.currentView` through its configured dependency, and updates the chat nudge.

This is one specific refresh seam. Other features have their own configured runtime callbacks; do not route every UI operation through a global service locator.

***

## 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:

```js theme={null}
import { escapeHTML } from './utils.js';

// Always escape — even values that "look safe"
el.innerHTML = `
  <div class="marker-name">${escapeHTML(marker.name)}</div>
  <div class="marker-value">${escapeHTML(String(value))}</div>
`;
```

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.ts` 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.ts`

`state.ts` exports a single shared mutable object. Any module that needs to read or write application state imports it directly:

```js theme={null}
import { state } from './state.js';

// Read
const activeProfile = state.currentProfile;
const entries = state.importedData.entries;

// Write
state.unitSystem = 'US';
state.importedData.notes.push({ date: today, text: 'New note' });
```

`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:

```js theme={null}
// category-page-view.js
export function showCategory(catKey, data) {
  // Reuse passed data if available, otherwise compute once
  const d = data || getActiveData();
  filterDatesByRange(d);
  // ... render with d
}
```

Toggle functions always compute `data` once and pass it through to all sub-renderers:

```js theme={null}
function toggleNoteOverlay() {
  state.noteOverlayMode = state.noteOverlayMode === 'on' ? 'off' : 'on';
  const data = getActiveData();
  showDashboard(data);
  showCategory(state.currentCategory, data); // same data object
}
```

This prevents redundant pipeline runs when multiple views refresh simultaneously.

***

## Debug mode

`isDebugMode()` in `utils.ts` reads the `labcharts-debug` localStorage flag. All `console.warn` and `console.error` calls in production are gated behind this:

```js theme={null}
if (isDebugMode()) console.warn('PII diff:', replacements);
```

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).


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.