UI modules map#
This page is a map of the UI layer: which modules exist, what DOM they own, and how they communicate with state/rendering without creating tight coupling.
It is especially useful when:
you want to add a new sidebar module,
you want to find where a UI behavior lives,
you are debugging “why did this update twice?” issues.
At a glance#
Audience
Computational users: skim “UI coordinator” and “Which module owns what?”
Developers: read fully; use the module table as a navigation map.
Time
20–40 minutes
Prerequisites
UI coordinator (the orchestrator)#
The UI layer is orchestrated by:
cellucid/assets/js/app/ui/core/ui-coordinator.js(initUI)
initUI is intentionally thin:
collects DOM references once (
collectDOMReferences)initializes feature modules (
initXfunctions)wires cross-module callbacks (e.g. “visibility changed → refresh legend + badges”)
subscribes to a small set of
DataStateevents
This separation exists to prevent:
a single monolithic UI file that becomes unmaintainable
accidental performance regressions from repeated DOM queries
DOM cache (single source of truth for selectors)#
Instead of every module calling document.getElementById(...) repeatedly, Cellucid uses:
cellucid/assets/js/app/ui/core/dom-cache.js
collectDOMReferences(document) returns a nested object keyed by UI domains:
dom.datasetdom.fieldSelectordom.filterdom.highlightdom.renderdom.cameradom.viewdom.sessiondom.communityAnnotation…
Design rule:
When adding new UI elements, prefer adding them to the DOM cache rather than scattering selectors across modules.
Module index (what exists today)#
Most UI modules live under:
cellucid/assets/js/app/ui/modules/
They are imported/initialized in:
cellucid/assets/js/app/ui/core/ui-coordinator.js
Core “shell” modules#
Module |
File |
Owns |
|---|---|---|
Sidebar toggle/resize |
|
sidebar visibility + |
Stats display |
|
top stats line (dataset/field/cell counts) |
Dataset + session modules#
Module |
File |
Owns |
|---|---|---|
Dataset controls |
|
dataset dropdown + dataset info block |
Dataset connections |
|
local-user picker, remote server connect, GitHub connect |
Session controls |
|
save/load |
Field/filter/highlight modules#
Module |
File |
Owns |
|---|---|---|
Field selector |
|
categorical/continuous/gene selectors + copy/rename/delete |
Gene expression helper |
|
gene dropdown/search behavior |
Deleted fields panel |
|
restore/purge UI |
Filter controls |
|
filter stack UI + count |
Legend renderer |
|
legend DOM + outlier UI container |
Highlight controls |
|
highlight pages + groups + selection mode tools |
View/render/camera modules#
Module |
File |
Owns |
|---|---|---|
View controls |
|
multiview (“Keep view”), layout mode, camera lock, badges |
Dimension controls |
|
dimension dropdown + per-view dim cycling |
Render controls |
|
points vs smoke, smoke sliders, connectivity toggles |
Velocity overlay controls |
|
vector field overlay settings + field selection |
Camera controls |
|
navigation mode + camera settings panels |
“Large feature” modules#
These are bigger subsystems but still treated as modules:
Module |
File |
Owns |
|---|---|---|
Figure export |
|
export UI + engine + SVG/PNG renderers |
Community annotation |
|
auth + pull/publish + voting/moderation UI |
Visualization reset |
|
reset actions (clear filters/highlights/etc.) |
Note
Page Analysis (plots / DE / correlation / etc.) is initialized directly in cellucid/assets/js/app/main.js (not via ui-coordinator.js).
Its UI lives in cellucid/assets/js/app/analysis/ui/.
See Analysis architecture.
How modules communicate (state events + callbacks)#
Most modules follow this pattern:
Inputs (DOM events) call
DataStatemethods (or viewer methods when appropriate).DataStateemits events (field:changed,visibility:changed, etc.).The UI coordinator reacts to those events and triggers targeted UI updates (legend/stats/badges).
Why this matters:
It prevents “UI module A reaches into UI module B’s internal state”.
It makes it easier to reason about what needs to update when.
Example: visibility changes
Filtering module updates filters → state recomputes visibility → state emits
visibility:changed.UI coordinator debounces a visibility UI update and triggers:
filterControls render
highlight summary render
view badge render
Session serialization interaction (what UI state is persisted)#
The session system can capture/restore generic UI control values, but some UI subtrees are intentionally excluded. Exclusion uses a DOM attribute:
data-state-serializer-skip="true"
Examples:
Figure export section is excluded (
cellucid/index.htmland the figure-export UI code).Benchmark section is excluded.
Dataset connection controls are excluded (sessions assume the dataset is already loaded).
Community annotation UI state is excluded (auth/network-driven).
Code pointers:
cellucid/assets/js/app/state-serializer/ui-controls.jscellucid/assets/js/app/state-serializer/README.md
Troubleshooting (UI module boundary issues)#
Symptom: “One click triggers multiple UI updates”#
Likely causes:
Two modules subscribed to the same
DataStateevent and both re-render the same DOM region.A module is initialized twice (usually due to calling
initUItwice on reload paths).
How to confirm:
Set a breakpoint in the module init function and reload.
Log subscriptions by temporarily instrumenting
EventEmitter.on.
Fix:
Keep DOM ownership crisp: one module “owns” a DOM subtree.
Prefer coordinator wiring over cross-module subscriptions.
Next: Data loading pipeline and caching (data sources) or Extension points overview (how to add modules safely).