20 Developer design notes
This section is for contributors who want to extend sclet without making the package harder to reason about.
20.1 Core object contract
SingleCellExperiment remains the only core container. New features should fit into the existing object rather than introducing a parallel class unless there is a very strong reason.
Internal state is collected under metadata(sce)$sclet. In practice, contributors should think in terms of a few stable buckets:
active: current assay, reduction, graph, and identity sourcelayers: logical expression layers mapped to underlying assaysstates: typed analysis-state registry with active records and provenancefeatures: feature-level records such as HVG metadatagraphs: graph registry entriesanalyses: higher-level outputs such as trajectory, milo, batch, supercell, and cellchatcommands: command log entries
20.2 How to add a new analysis function
When adding a new user-facing analysis function, the recommended pattern is:
- Use a canonical
Run*name for the public entry point. - Write structured outputs into the state layer with
sclet_set_analysis(), or the relevant state helper. - Record the call with
sclet_log_command(). - If the function consumes expression matrices, make it resolve from
layerfirst and only then to an underlying assay. - Expose the result through
get_*()andhas_*()helpers if users will need to inspect it later.
The goal is not just to make a function run once. The goal is to make the result discoverable, inspectable, and reusable across later steps.
20.3 Implementation rules for contributors
Contributors should follow a few practical rules:
- Do not access deep S4 internals when an accessor already exists.
- Do not add a second storage path if the state layer can already represent the result.
- Do not introduce a new user-facing expression argument unless you have decided whether it should be a logical
layeror a physicalassay. - When migration logic still needs to read old metadata, keep new results written to the unified state schema.
- Keep changes surgical: one new analysis function should normally come with its state write-back, accessor path, and focused tests.
- For derived-object workflows such as
RunSuperCell(), record provenance on the returned object itself instead of inventing a separate container class. - When a workflow needs both a rich analysis object and a typed state record, prefer
sclet_set_analysis()for the object payload andsclet_set_analysis_state()for the lightweight contract instead of hand-editingmetadata(sce)$sclet. - If an upstream Bioconductor dependency emits known deprecation warnings that
scletcannot eliminate immediately, wrap the narrow call site with a targeted compatibility helper instead of using broadsuppressWarnings().
20.4 What should be tested
For each new analysis function or state feature, tests should ideally cover:
- the result is stored in the unified state layer
- the corresponding accessor can read it back
has_*()returns the expected boolean- active state is updated if the function changes the current assay, reduction, graph, or identity
- command log entries use the canonical public command name
This keeps sclet ergonomic for end users while still staying grounded in the clearer SingleCellExperiment and Bioconductor model.