Reference (hooks, commands, schemas, endpoints)#
This page is the “source of truth” reference for the notebook hooks system.
Primary sources in this repo:
Python:
cellucid-python/src/cellucid/jupyter.pyPython:
cellucid-python/src/cellucid/_server_base.pyWeb app:
cellucid/assets/js/data/jupyter-source.jsWeb app:
cellucid/assets/js/app/main.jsDev notes:
cellucid/markdown/HOOKS_DEVELOPMENT.md
If you are new, start with:
Public Python API (what you can call)#
Creating a viewer#
from cellucid import show, show_anndata
show("./export_dir", height=600) -> CellucidViewershow_anndata(adata_or_path, height=600, **adapter_kwargs) -> AnnDataViewer
Basic properties#
viewer.server_url→ the underlying data server URL (usuallyhttp://127.0.0.1:<port>)viewer.viewer_url→ the iframe URL (includesjupyter=true,viewerId,viewerToken, andanndata=truefor AnnDataViewer)
Display and lifecycle#
viewer.display()→ (re)display iframe in a notebook output cellviewer.stop()→ freeze view (best effort) then stop server + unregister hookscellucid.jupyter.cleanup_all()→ stop all active viewers (also registered viaatexit)
Commands (Python → viewer)#
viewer.send_message(message: dict)(low-level)viewer.highlight_cells(cell_indices: list[int], color: str = "#ff0000")viewer.clear_highlights()viewer.set_color_by(field: str)viewer.set_visibility(cell_indices: list[int] | None = None, visible: bool = True)viewer.reset_view()
Hooks (viewer → Python)#
Decorators:
@viewer.on_ready@viewer.on_selection@viewer.on_hover@viewer.on_click@viewer.on_message(catches all events)
Programmatic registration:
viewer.register_hook(event: str, callback) -> callbackviewer.unregister_hook(event: str, callback) -> boolviewer.clear_hooks(event: str | None = None)
Synchronous “pull” API:
viewer.state(latest event snapshot)viewer.wait_for_event(event: str, timeout: float | None = 30.0, predicate=None) -> dictviewer.wait_for_ready(timeout: float | None = 30.0) -> dict
Session bundles (durable state)#
bundle = viewer.get_session_bundle(timeout: float | None = 60.0)bundle.save("path/to/file.cellucid-session")adata2 = viewer.apply_session_to_anndata(adata, inplace=False, **kwargs)adata2 = bundle.apply_to_anndata(adata, inplace=False, **kwargs)Function-level:
cellucid.apply_cellucid_session_to_anndata(...)
Connectivity / cache utilities#
viewer.debug_connection(...)→ structured connectivity reportviewer.ensure_web_ui_cached(force=False, show_progress=True)→ prefetch viewer UI assetsviewer.clear_web_cache()→ clear cached viewer UI assets
Event schemas (viewer → Python)#
All events are delivered via POST /_cellucid/events and routed by viewerId.
Python receives payloads without the type and viewerId keys.
ready#
{"n_cells": int, "dimensions": int}
selection#
{"cells": list[int], "source": str}
hover#
{"cell": int | None, "position": dict | None}
click#
{"cell": int, "button": int, "shift": bool, "ctrl": bool}
console (best-effort debug forwarding)#
{"level": "warn" | "error", "message": str, "ts": str, ...optional_fields...}
session_bundle#
{
"requestId": str,
"status": "ok" | "error",
"bytes": int, # on success
"path": str, # on success (temp file path on server machine)
"error": str # on error
}
Diagnostic events#
Used by viewer.debug_connection():
pongdebug_snapshot
@viewer.on_message envelope#
For any event type X, @viewer.on_message receives:
{"event": "X", **payload}
Command schemas (Python → viewer)#
All commands are sent via postMessage into the iframe.
You typically send only the message-specific fields; the embedding layer injects:
viewerIdviewerToken
highlight#
{"type": "highlight", "cells": [0, 10, 42], "color": "#ff00ff"}
clearHighlights#
{"type": "clearHighlights"}
setColorBy#
{"type": "setColorBy", "field": "cell_type"}
setVisibility#
{"type": "setVisibility", "cells": [0, 10, 42], "visible": False}
resetCamera#
{"type": "resetCamera"}
Session/diagnostics (internal)#
{"type": "requestSessionBundle", "requestId": "..."}
{"type": "ping", "requestId": "..."}
{"type": "debug_snapshot", "requestId": "..."}
{"type": "freeze"}
Server endpoints (debugging + integration)#
All of these are served by the Python data server that backs the viewer:
GET /_cellucid/health#
Used for:
connectivity probing
notebook proxy selection
GET /_cellucid/info#
Returns server metadata (version, mode, etc.).
GET /_cellucid/datasets#
Returns dataset listings (one dataset in AnnData mode; one or many in exported mode).
POST /_cellucid/events#
Hooks endpoint.
Behavior:
expects JSON
requires
viewerIdrequest size limit: 1MB (to guard against accidental giant payloads)
responds with JSON:
{"status": "ok", "delivered": true}if routed{"status": "not_found", "delivered": false}if viewerId not registered
POST /_cellucid/session_bundle?viewerId=...&requestId=...#
Session bundle upload endpoint (Jupyter no-download capture).
Behavior:
requires a pre-registered pending request
streams upload to a temp file
hard size cap: 512MB
validates a MAGIC header (
CELLUCID_SESSION\n)
Environment variables#
Variable |
Meaning |
When to use |
|---|---|---|
|
Where the hosted viewer UI assets are cached |
Offline use, persistent cache, shared environments |
|
Override the browser-facing server URL used in iframe embeds |
Remote kernels, custom proxies, unusual notebook frontends |
Known limitations (important)#
Indices are positional (row index), not stable cell IDs.
/_cellucid/eventshas a 1MB request limit; huge selections can be rejected.Hook callbacks can run on a server thread; keep them fast and avoid heavy work inline.
Session bundle application is only safe when applying to a dataset with matching row order (use mismatch policies to guard).