Skip to content

Technical documentationInternal

For the team and anyone taking the product over. Not linked from the site and not indexed.

Technical documentation pages

Cell

Cell (registry slug inside-a-cell) is an eight-step guided tour of gene expression, told through eight experimental structures from the Protein Data Bank, each shown as a pre-rendered Blender still with an optional live Mol* 3D view. This page describes how the tour is built, where its content and media come from, how the numbers in its text are checked against the structures, and what is known to be incomplete.

Purpose and users

Cell is the suite's one app of kind educational (content/apps/identity.ts, APP_ORIGINS["inside-a-cell"].kind); every other registry app is a tool. Its workspace group is learn. The product copy (content/apps/inside-a-cell.ts, page.faq) names the audience as students, people new to biotech and pharma, and colleagues outside the lab. It states that the tour is an educational illustration, not a research or clinical resource, and that the eight steps are separate experiments on different molecules rather than one gene followed through a cell.

The registry entry records:

Field Value
status preview
version 0.1.0
updated 2026-10-05
landing false (D-0021: Cell keeps its product page, workspace and docs but is not on the landing page's research grid; D-0034 keeps that exclusion and makes Cell discoverable from the landing page's separate Education section)
themes ["light", "dark"]
shell { rail: "steps", inspector: true, stage: true, statusChips: ["freshness"], orientation: "Guided tour" }
dataPolicy.compute / storage browser / browser-only
dataPolicy.endpoints []: the tour contacts no host but its own
dataPolicy.cspAllowances unsafeEval and dataConnect, both for the Mol* 5.11.0 viewer's bundled WebAssembly module
flags webgl: true, workers: false, shareAlikeData: false
objects { reads: [], writes: ["target"] }

The registry and its fields are described in Registry and content; the security allowances in Security and network.

Addresses

The route segment for the slug is cell (APP_SEGMENTS in content/apps/routes.ts).

Address What it serves Where it comes from
/cell Product page, including a live preview of the tour at the registry example step (example.params.step = "translation") Shared marketing routes; copy in content/apps/inside-a-cell.ts (entry, page)
/docs/cell Docs page: methods, exports, verification, security overview, limits content/apps/inside-a-cell.docs.ts
/cell/app The workspace, at step 1 Shared (workspace)/[app]/app route, which loads src/apps/inside-a-cell/entry.tsx through src/apps/definitions.ts
/cell/app?step=<id or n> Inbound deep link to a step Read by the tour, then rewritten to the canonical fragment form
/cell/app#s=step%3D<id> Canonical address of steps 2 to 8 Written by the tour through the host's URL writer (host.url.write)
/tools/inside-a-cell Permanent redirect to the workspace legacyPath in content/apps/identity.ts
/tools/inside-a-cell/s/:step Permanent redirect to /cell/app?step=:step legacyRedirects in content/apps/inside-a-cell.ts
/changelog#inside-a-cell Release notes content/apps/changelog/inside-a-cell.ts (entries dated 2026-10-01 and 2026-10-05)

src/apps/inside-a-cell/page-ids.ts exports an empty PAGE_IDS, so the app has no static pages under /cell/app/<page> besides those the shell provides. The redirects are covered in Deployment and environment.

Step addressing

The tour has one URL key, step. src/apps/inside-a-cell/url-schema.ts declares it with checks.oneOf(STEP_IDS, { indexAliases: true }), so a step can be named by its id (splicing) or by its 1-based number (3). The pure helpers in src/components/tools/inside-a-cell/nav.ts do the reading:

  • stepIndexOf(value, ids) trims and lower-cases the value, matches an id, then a number from 1 to ids.length; anything else gives index 0.
  • isUnknownStep(value, ids) is true for a non-empty value that names no step. The tour then starts at step 1 and shows workspaceCopy.strip.unknown(value), echoing at most MAX_ECHO = 40 characters of the value.

On arrival in the workspace (host.kind === "suite", not the product preview), tour.tsx calls acceptInbound(search, hash, urlSchema, pathname) and replaces the history entry with the canonical address. Step 1 has no step key; steps 2 to 8 are written as #s=step%3D<id>. Every step change writes the address with history: "replace", so Back does not move between steps. A popstate listener re-reads the step when the address is edited or the person navigates with Back or Forward.

The text version's per-step links and the exports' canonical links use the ?step=<id> form (stepHref in tour.tsx, stepLink in export.ts), which the workspace accepts and rewrites. Shared-link mechanics are in Storage and state.

The eight step ids are fixed in content/tools/inside-a-cell.index.ts (STEP_INDEX):

n id Short name PDB Method, resolution Released
1 chromatin Chromatin 3AFA X-ray, 2.5 Å 2010
2 transcription Transcription 8XSO Cryo-EM, 2.7 Å 2025
3 splicing Splicing 6QDV Cryo-EM, 3.3 Å 2019
4 export Export 7ZNK Cryo-EM, 3.9 Å 2023
5 translation Translation 6Y0G Cryo-EM, 3.2 Å 2020
6 exit-tunnel Exit tunnel 6OLE Cryo-EM, 3.1 Å 2019
7 folding Folding 7TUB Cryo-EM, 3.6 Å 2022
8 at-work At work 2DN1 X-ray, 1.25 Å 2006

Other code links to these ids: the plasmid explainers (content/apps/plasmid-map.explainers.ts, type CellStop) and the Education tools (content/education.ts, appHref("inside-a-cell", { step })). Renaming or removing a step id breaks those links and every saved ?step= address.

File map

Path Role
content/apps/inside-a-cell.ts Registry content: tagline, capabilities, features, shortcuts, coach marks, limits, sources and sourceMeta, excluded sources, data policy, cross-links, legacy redirects, product-page FAQ
content/apps/inside-a-cell.entry.ts withIdentity("inside-a-cell", entry): the full entry imported by the app's own chunk
content/apps/inside-a-cell.workspace.ts Every workspace string: strip, stage and viewer messages, step panel, provenance chips, text version, exports, share line, shortcut announcements
content/apps/inside-a-cell.docs.ts The /docs/cell page content
content/apps/changelog/inside-a-cell.ts Changelog entries
content/tools/inside-a-cell.ts detail: the write-up sections, the ten source records and the six limits (shared by the registry entry and the docs page)
content/tools/inside-a-cell.index.ts STEP_INDEX, StepId, STEP_COUNT, dataRetrieved ("2026-09-28"), scaleNote
content/tools/inside-a-cell.steps.ts The eight steps' text, structures, citations, colour-key callouts, alt text, links and targets; targetsCheckedOn ("2026-09-27")
src/apps/inside-a-cell/definition.ts AppDefinition: lazy Main, the rail's "whole tour as text" action, the sources inspector, every registry shortcut bound to an action
src/apps/inside-a-cell/entry.tsx Client entry (displayName = "drybench-entry:inside-a-cell", the marker scripts/size-check.mjs finds the chunk by)
src/apps/inside-a-cell/store.ts Module-level store the mounted tour publishes (publishTour, readTour, subscribeTour)
src/apps/inside-a-cell/url-schema.ts The step URL key
src/apps/inside-a-cell/objects.ts targetInput, targetLabel: a step's UniProt targets as shared objects
src/apps/inside-a-cell/page-ids.ts Empty PAGE_IDS
src/components/tools/inside-a-cell/lazy.tsx InsideACellLazy: ToolBoundary around Tour, loaded with the steps' text
src/components/tools/inside-a-cell/tour.tsx Tour: step state, address handling, actions, export registration, layout
src/components/tools/inside-a-cell/stage.tsx CellStage: the Stage frame, the gate, the still, the lazily imported viewer
src/components/tools/inside-a-cell/molstar-viewer.tsx The Mol* viewer (default export MolstarStage); also used by the Education tools
src/components/tools/inside-a-cell/mvs.ts Pure MolViewSpec builder, camera, zoom and wheel maths
src/components/tools/inside-a-cell/download.ts Structure download with idle and total timeouts, cancellation and gunzip
src/components/tools/inside-a-cell/nav.ts Step parsing, clamping, the serial loader, withTimeout
src/components/tools/inside-a-cell/render-cost.ts Software-rasteriser detection and the reduced canvas props
src/components/tools/inside-a-cell/palette.ts The eight colour categories and their legend order
src/components/tools/inside-a-cell/still.ts STILLS_BASE, posterSrc(id), stillSizes
src/components/tools/inside-a-cell/step-parts.tsx LookAtList, StepFacts, StepProvenance and the two provenance builders
src/components/tools/inside-a-cell/steps-text.tsx StepsText: the whole tour as text in a <details> disclosure
src/components/tools/inside-a-cell/export.ts The PNG still export and the Markdown tour export, and the provenance block
src/components/tools/inside-a-cell/hooks.ts useReducedMotion, useCoarsePointer (media-query external stores)
src/components/tools/inside-a-cell/types.ts Category, CellStep, StepTarget, Scene, SceneFile and related types
src/components/tools/inside-a-cell/*.test.ts Unit tests (see Tests)
pipeline/inside-a-cell/ config.py, verify_sources.py, build_structures.py, render.py, verified.json, facts.json, requirements.txt, README.md
public/data/inside-a-cell/ scenes.json, eight *.bcif.gz structure files, stills/<step>.webp
public/vendor/molstar@5.11.0/ Vendored Mol* viewer (molstar.js, molstar.css, LICENSE.txt), copied from node_modules by scripts/vendor.mjs and not committed
e2e/inside-a-cell/ tour.spec.ts, keys-and-exports.spec.ts, sections.spec.ts
docs/design/prd-inside-a-cell.md, docs/design/build-spec-inside-a-cell.md Product requirements and build specification (see Known gaps)

UI structure and state

Loading chain

  1. src/apps/definitions.ts maps "inside-a-cell" to import("./inside-a-cell/entry"). The entry hands the shell definition and the full registry entry.
  2. definition.Main (CellWorkspace) registers the rail and renders Tour through next/dynamic with ssr: false, showing ToolLoading ("Loading the tour") while lazy.tsx downloads. The definition imports only STEP_INDEX (ids and short names), so the shell chunk never carries the step text.
  3. lazy.tsx imports steps, scaleNote and targetsCheckedOn and renders Tour inside a ToolBoundary.
  4. stage.tsx imports molstar-viewer.tsx through a second next/dynamic (ssr: false), rendered only after the reader presses the gate. Until then no Mol* code, scene data or structure file is requested.
  5. The viewer loads vendor.molstarCss and vendor.molstarJs (/vendor/molstar@5.11.0/..., from src/lib/vendor.ts) through loadStylesheet and loadScript (src/lib/load-script.ts), and reads window.molstar.

Layout

Tour renders, in order:

  • A nav labelled "Tour progress" (data-coach="steps") with "Step n of 8", the step title as the page heading (h1 in the workspace, h2 in the product preview; tabIndex={-1} so it can take focus), the structure name, and Previous and Next buttons (aria-disabled at the ends).
  • The unknown-step note, when the address named a step that does not exist.
  • ViewTabs (src/components/suite/layout/view-tabs.tsx): one tab per step, labelled "<n>. <short>".
  • A tabpanel grid (two columns from the lg breakpoint):
    • the stage (CellStage) with the two provenance chips under it (StepProvenance: the PDB entry chip and the paper chip, both ProvenanceChip from src/components/suite/data/provenance-chip.tsx);
    • an article with the step body, StepFacts (Structure with an RCSB link, Method and release year, Size, Paper with a DOI link), the step's links, its targets and, below lg, a "Next step: <short>" button that moves focus to the heading;
    • a "What you are looking at" Panel with LookAtList (colour swatch, the category's name in this step, the callout text);
    • an "On one scale" Panel: a bar for every step, width sizeNm / max(sizeNm), with the rounded size in nm and scaleNote.
  • StepsText: a section headed "The whole tour as text" holding a closed <details> with every step's body, colour key and facts, and a link per step back into the tour.

State

State Where it lives
Current step The address (host.url.read().step): the #s= fragment in the workspace, memory in the product preview. initialStepId from the route or the product page example applies when the address names none
Text version open useState in Tour, mirrored from the native <details> toggle
Viewer controller useState in Tour and CellStage, published by the viewer through onController
Viewer internals Refs and state in molstar-viewer.tsx (scene map, download cache, shown step, boot and failure state, touch rotate)
Shell-facing actions src/apps/inside-a-cell/store.ts: a module variable plus listeners, holding { index, count, actions: { go, showText, zoom, resetView }, viewer }

Nothing is written to localStorage, IndexedDB or a server. The registry says storage: "browser-only" and authored: "Anything you save in the app", but the tour has no save function of its own. The only shared state it touches is the object library through useLearn (see Integration with the suite).

Keyboard and announcements

The registry entry declares the shortcuts; definition.ts binds each one through runFor(key) and throws at module load if a declared key has no binding.

Key Action
ArrowLeft, ArrowRight Previous, next step
Home, End First, last step
1 to 8 Go to that step (the binding accepts ^[1-9]$; 9 clamps to step 8 but is not declared)
t Open the text version and move focus to its heading
r Reset the 3D view
+, - Zoom by 0.8 and 1.25

Viewer keys before the viewer exists announce shortcuts.noViewer; while it is loading, shortcuts.viewerLoading. Each step change announces "Step n of 8: <title>" through host.announce, the shell's one live region. The viewer's status line is a visible copy with aria-live="off"; its text also goes to host.announce.

Exports

tour.tsx registers two items with host.registerExport:

id Group Output
cell.still figure PNG of the current step's still at 800 × 450 per 1× scale, plus a 64 px caption band (drawStill in export.ts). Backgrounds dark or white. The band carries "Step n of 8 · title", "PDB id · first author et al. (year), journal" and the step's colour key. The provenance block (stepProvenance) is embedded by the shared PNG writer.
cell.text report Markdown file of the whole tour (tourMarkdown): title, intro, scaleNote, every step's body, colour key, structure link, method, size and paper, the source list, and "Exported from <name> <version> (<link>) on <ISO time>".

The provenance block names the step, its canonical link, the RCSB PDB entry source (version = PDB id, dataAsOf = release year, retrievedAt = dataRetrieved) and the Europe PMC paper source (version = PMID <n>), all six limits and the regulatory, advice and educational statements. Export mechanics shared by all apps are in Shared shell.

The 3D viewer

molstar-viewer.tsx is the only Mol* integration in the tour. It is written to serve one scene collection per mount, selected by the optional scenesUrl prop (default /data/inside-a-cell/scenes.json); the Education tools pass their own collection (see Education tools).

Start-up

  • MolstarStage checks hasWebGL() (src/lib/webgl.ts, cached; tries webgl2 then webgl and releases the context) only after the gate is pressed. Without WebGL it shows viewer.noWebgl over the still.
  • The viewer fetches the scene file (aborted after 30 s, reported as scenesTimeout), starts the current step's structure download as soon as the scenes arrive, and waits for the Mol* assets (timeout VIEWER_TIMEOUT_MS = 180_000). If the assets arrive after the timeout, the viewer restarts by itself.
  • molstar.Viewer.create(host, VIEWER_OPTIONS) creates a viewer with every Mol* panel, control and overlay off, viewportFocusBehavior: "disabled", volumeStreamingDisabled: true and illumination: false. Inline CSS also hides Mol*'s own viewport controls and logo inside .cell-molstar.
  • isSoftwareRenderer (render-cost.ts) reads the WebGL renderer name (unmasked where available) and matches swiftshader|llvmpipe|softpipe|software|basic render driver. On a match every scene is loaded with SOFTWARE_RENDER_PROPS: ambient occlusion off and multi-sampling off.
  • Context loss and restore are subscribed to; on loss the status reads viewer.contextLost and a "Reload 3D view" button restarts the viewer.

Per-step load

createSerialLoader(loadScene) (nav.ts) runs one load at a time. A request for a different step aborts the running load's signal and replaces any waiting request, so rapid clicking ends on the last step requested. loadScene:

  1. Returns at once if Mol* already shows this step (shownRef), keeping the reader's camera.
  2. Awaits the structure bytes from the download cache (download(id)). Downloads for steps the reader has left are aborted (dropOtherDownloads); finished downloads stay cached for a return visit.
  3. Wraps the bytes in a blob: URL, builds the MolViewSpec state with buildMvs, and calls viewer.loadMvsData(json, "mvsj") under SCENE_TIMEOUT_MS = 60_000. A timeout is treated as Mol* having stopped responding, and the whole viewer restarts.
  4. Throws if Mol* reports no structures.
  5. Re-applies canvas props after every load (MolViewSpec resets them): axes helper off, cameraResetDurationMs 0 under reduced motion and 250 ms otherwise, and the software props where they apply.
  6. Sets the camera instantly with camera.setState(cameraView(scene), 0).

The still stays over the canvas until the scene is ready, then fades over 300 ms (motion-safe only). Status messages, in order of precedence, are failure, context lost, "PDB <id> shown in 3D.", "Loading the 3D viewer." and "Loading PDB <id> in 3D."; a slow note is appended after SLOW_AFTER_MS = 8_000.

Downloads

fetchStructure(url, { signal, idleMs = 20_000, totalMs = 120_000 }) in download.ts reads the response body chunk by chunk, resetting the idle timer on each chunk. It raises DownloadError with kind timeout, http, network or unsupported, or an AbortError on cancellation. The files are stored gzipped because, as the code comments state, Vercel does not compress application/octet-stream; gunzip uses the browser's DecompressionStream when the first two bytes are 1f 8b, and passes plain bytes through when the server has already decompressed them.

MolViewSpec scene

buildMvs(scene, options) in mvs.ts produces the multi-snapshot form (kind: "multiple", one snapshot, linger_duration_ms: 1000, no transition_duration_ms), because a single-state file gets a fixed 250 ms camera animation in Mol*. The tree is:

root
  canvas      { background_color }
  camera      { target: centre, position: centre - direction * 2 * radius, up }
  download    { url: blob URL + "#.bcif" | absolute scene.url }
    parse     { format: "bcif" }
      structure  { type: "model" | "assembly", assembly_id }
        component per group (selector: group.select or label_entity_id list)
          representation  cartoon (size_factor from SIZE) | spacefill (1)
            color          category colour
            color          per colorRegions entry (selector, category colour)
            clip           plane, when group.category is in cutaway.categories
          tooltip          group.label or palette label
        component per ligand (label_asym_id list)
          representation  ball_and_stick, size_factor 1.2, ligand colour
          tooltip          ligand.label
        primitives per label (only when the host is at least 360 px wide)
          primitive        label at the pipeline's position, label_size = scene.labelSize

Cartoon size factors (SIZE) are dna 1.3, mrna 1.6, intron 1.4, trna 1.3, newchain 1.4, others 1. Label text is #f7faf8; its background is the obsidian stage colour so labels stay legible when the canvas follows the light theme. The clip plane's normal comes from the pipeline and points from the target towards the camera; Mol* removes the side the normal points to.

Camera maths:

cameraParams:  position = centre - direction * 2 * radius          (MolViewSpec reference)
cameraView:    distance = radius / sin(fov / 2) * pullBack,  fov = pi/4
               position = centre - direction * distance               ("Reset view")
zoomedPosition(position, target, factor, min, max):
               d' = clamp(|position - target| * factor, min, max)
               min = 0.2 * home distance, max = 3 * home distance
wheelZoomFactor(deltaY, deltaMode) = clamp(exp(pixels * 0.002), 0.8, 1.25)
               pixels = deltaY * (1 | 40 | 800) for deltaMode 0 | 1 | 2

Stage frame and input

CellStage wraps everything in the shared Stage (src/components/suite/layout/stage.tsx) with mode="theme", so the live canvas background follows the suite's light or dark theme (syncMolstarSurface and viewerBackground from src/components/suite/theme/viewer-surface.ts, re-applied on theme change and after every scene load). The stills keep their own dark ground. The Stage owns the zoom buttons, Reset, wheel rules (a plain wheel scrolls the page; Ctrl or Cmd with the wheel zooms through the controller) and full screen. On coarse pointers the viewer stops one-finger touches from reaching Mol* until the reader turns on "Rotate", which the stage shows as an extra button; pinch always reaches Mol*. The stage is 320 px tall on phones (still cropped with object-cover) and 16:9 from sm.

Authored models and media

Everything the tour shows except its code is produced by the Python pipeline in pipeline/inside-a-cell/ and committed. The pipeline is not run in CI. Its general place in the repository is described in Data pipeline.

Scene configuration (config.py)

CATEGORIES defines the eight colour categories; src/components/tools/inside-a-cell/palette.ts mirrors them and mvs.test.ts checks the two match.

Category Label Colour
dna DNA #5ccbf0
mrna Messenger RNA #79f2c0
intron Intron (cut out) #2f9e80
trna Transfer RNA #f59ac2
srna Ribosomal and small nuclear RNA #a78bfa
protein Protein machinery #b7c7c0
newchain The protein being made #ff9b73
ligand Small molecules and ions #ffd166

STEPS lists, per step: the PDB id; structure (model, or assembly 1 for 2DN1); lite and full_atom (entities kept at every atom); entities (every polymer label_entity_id mapped to a category and an expected description substring, or a default_category); ligands (by label_asym_id, or "*<entity>" for every copy); view (focus chains, a direction and up vector specification, margin or fixed radius); cutaway (chains the plane passes through, an offset in Å, and optionally only one category); labels (text and selectors); optional styles (spacefill for a category) and legend (renaming a category for one step; 2DN1 renames newchain to "The finished protein"). EXTRA_ENTRIES (7TRG, 7TTN, 7TTT) are the tubulin folding intermediates whose residue counts the text quotes. UNIPROT lists twelve accessions to check.

Source verification (verify_sources.py)

Standard library only. It queries the RCSB Data API GraphQL endpoint for all eleven entries, Europe PMC REST for each entry's primary-citation PMID, and UniProt REST for each accession, and writes verified.json. It exits non-zero if an entry is missing, if Europe PMC and the RCSB primary citation disagree on title (after normalisation), DOI or year, if a citation has no PMID, or if a UniProt accession is not a reviewed Homo sapiens entry. The committed file has retrieved: "2026-09-27".

Structures, scene data and facts (build_structures.py)

Requires numpy 2.5.3, scipy 1.18.1 and biotite 1.7.1 (requirements.txt). For each step it:

  1. Downloads https://models.rcsb.org/<id>.bcif into a cache directory (--cache, default <tmp>/inside-a-cell-cache). The current revision is fetched; no revision is pinned.
  2. Checks every configured entity description against the file (categorise) and fails if one differs, if a polymer has no category, or if a configured entity is missing.
  3. Reads model 1 with label fields (assembly 1 for 2DN1), drops hydrogen and deuterium, and computes the view in the entry's own frame: principal axes by SVD with a sign convention (canonical), direction and up from the view specification, up orthogonalised against direction, centre as the bounding-box centre of the focus atoms, radius as max distance * margin or the configured value.
  4. Computes the cutaway plane: point = centroid of the through chains + normal * offset, normal = -direction; categories ["protein", "srna"] unless only is set.
  5. Writes public/data/inside-a-cell/<id>[-lite].bcif.gz: only the listed mmCIF categories (KEEP_CATEGORIES, including pdbx_audit_revision_history), and only atom_site rows of model 1, non-hydrogen, that belong to full-atom entities or listed ligands, plus CA or P atoms of the other polymers. The file is gzipped at level 9 with mtime=0, so the output is byte-reproducible for the same input.
  6. Places each 3D label at the centroid of its atoms, slid along the line of sight towards the camera by radius * 0.7 (LABEL_LIFT), and sets attach left or right when two labels are close on screen (attach_labels). labelSize = radius * 0.13.
  7. Measures the facts (below) and the size: sizeNm is the largest atom-to-atom distance over the convex hull of the shown atoms (SciPy ConvexHull), in nm to one decimal; extentsNm are the principal-axis extents.

It writes scenes.json (minified; retrieved, source, generator, a coordinate-frame note, categories, scenes) and facts.json. The committed files have retrieved: "2026-09-28".

Measured facts per entry (step_facts): proteinChains (every copy, including assembly copies), machineryChains, ligandCompounds; 3AFA DNA nucleotides per strand and base pairs (struc.base_pairs); 8XSO RNA nucleotides and RNA–template base pairs; 6QDV exon and intron nucleotides; 7ZNK RNA fragment lengths; 6Y0G and 6OLE rRNA nucleotides; 6Y0G mRNA length, the mRNA and tRNA sequences from entity_poly, and the codon each tRNA's anticodon (positions 34 to 36) touches within 4 Å, with a hard check that chain D pairs with the more 3′ codon (the A site); 6OLE nascent-chain CA count, its reach from the 3′ end of the tRNA in nm, the drug-to-chain minimum distance, and every entity within 5 Å of the chain (struc.CellList); 7TUB tubulin residues and TRiC subunits; 2DN1 haem irons and oxygen molecules.

Shipped structure files

Step File Lite Atoms served gz size (bytes) Revision cut from sizeNm
chromatin 3afa.bcif.gz no 12,003 99,158 1.3 (2023-11-01) 11.8
transcription 8xso.bcif.gz no 32,513 237,482 1.1 (2025-06-11) 16.6
splicing 6qdv-lite.bcif.gz yes 21,974 212,203 3.1 (2024-11-13) 36.0
export 7znk-lite.bcif.gz yes 11,847 109,329 1.3 (2024-07-24) 45.1
translation 6y0g-lite.bcif.gz yes 20,568 214,906 1.2 (2024-05-22) 31.5
exit-tunnel 6ole-lite.bcif.gz yes 20,688 214,027 2.0 (2026-07-01) 31.6
folding 7tub-lite.bcif.gz yes 7,901 92,161 1.2 (2026-08-12) 17.3
at-work 2dn1.bcif.gz no 2,262 24,989 1.5 (2026-08-12) 6.8

The served files are cut down from the full entries, so the 3D view of the five lite complexes draws the machinery as a backbone trace while the subject RNAs and chains keep every atom.

Stills (render.py)

Requires bpy 5.2.2 and molecularnodes 520.2.0 in addition to the build dependencies. For each scene in scenes.json it downloads the full entry (or writes the assembly as its own mmCIF), loads it with Molecular Nodes, colours atoms by category in the baked Color attribute (per-style colours are lost on assembly instances, per the script), hides machinery residues whose trace atom lies in front of the cutaway plane, and draws nucleic acids as ribbons, proteins as cartoons and spacefill groups and ligands as spheres. The camera uses the same centre, direction, up and focus radius as the web viewer, with a 45° vertical field of view, sensor_fit = VERTICAL and the radius fitted to the narrower axis. Rendering is Cycles on the CPU, 2 threads, 32 samples, OpenImageDenoise, 1600 × 900, Standard view transform, background #07140f compensated for exposure 1.0, WebP quality 88. --preview renders 480 × 270 at 8 samples with a -preview suffix; --steps selects steps; --scenes reads an alternative scene file.

Differences between the still and the live view: the still is rendered from the full entry, not the trimmed file, so the machinery of the lite complexes appears as cartoon rather than trace; the cutaway is applied per residue in Blender and per pixel in Mol*; the stills carry no 3D text labels.

The eight stills total 542,146 bytes (48 KB to 128 KB each) and sit under public/data/inside-a-cell/stills/, inside the /data/* cache rule (ten minutes fresh, a day of revalidation; see Deployment and environment). The page displays them with next/image (stillSizes = "(min-width: 1126px) 800px, (min-width: 640px) 100vw, 570px"); the first-shown step's still is preloaded.

Licences of the build tools

Blender and Molecular Nodes are GPL-3.0-or-later and are recorded as build-time tools with displayOnly: true and licence id GPL-3.0-or-later-buildtime; no GPL code is shipped. The registry and pipeline README state that the rendered images are outputs not covered by the GPL. Whether that position holds for sale is held for the General Counsel. Mol* 5.11.0 is MIT and is shipped. See Dependencies and licences.

Science content and how its accuracy is checked

What the steps say

Each step in content/tools/inside-a-cell.steps.ts has a title, two or three paragraphs, a structure name, method, release year, sizeNm, a size sentence (scale), a citation (authors, year, title, journal, DOI, PMID), lookAt callouts keyed to colour categories, alt text, and optionally links and targets. Several facts are about the sample, not the biology: step 3's RNA (MINX) is listed in the entry as from human adenovirus 2; step 5's message is synthetic poly(U), its tRNAs carry the E. coli tRNA-Phe sequence (RNAcentral URS00005AA258) and the ribosome is held by cycloheximide; step 6's chain is E-cadherin stalled by PF-06446846; step 7 shows the last of four TRiC–β-tubulin intermediates. The text states each of these.

Background facts not measured from the structures come, per the file header, from UniProt function notes, the primary papers, four Europe PMC abstracts (PMIDs 21195232 and 24459735 for the 1.65-turn DNA wrap, 24196718 for U6 catalysis, 20118940 for cycloheximide), RNAcentral, and PDB-101 Molecule of the Month articles used as background reading only.

Primary citations

Step Citation PMID
1 Tachiwana H et al. Proc Natl Acad Sci U S A 107:10454-10459 (2010) 20498094
2 Kujirai T et al. Nat Commun 16:4724 (2025) 40436841
3 Fica SM et al. Science 363:710-714 (2019) 30705154
4 Pacheco-Fiallos B et al. Nature 616:828-835 (2023) 37020021
5 Bhaskar V et al. Cell Rep 31:107473 (2020) 32268098
6 Li W et al. Nat Struct Mol Biol 26:501-509 (2019) 31160784
7 Gestaut D et al. Cell 185:4770-4787.e20 (2022) 36493755
8 Park SY et al. J Mol Biol 360:690-701 (2006) 16765986

The checks

src/components/tools/inside-a-cell/content.test.ts reads pipeline/inside-a-cell/verified.json, pipeline/inside-a-cell/facts.json and public/data/inside-a-cell/scenes.json and fails when the copy drifts from them:

Test What it asserts
Index and retrieval date STEP_INDEX matches the steps' ids and names; dataRetrieved equals the first ten characters of scenes.retrieved
Step count and order 7 to 9 steps, unique ids, same [id, pdbId] order as the scene data
RCSB record Method wording matches the RCSB method ("X-ray crystallography, " or "Cryo-EM, "), resolution string equals resolutionA, year equals the release year, organisms include Homo sapiens
Europe PMC PMID, DOI (case-insensitive), year, normalised title, first author, and journal equal to "<journalAbbrev> <volume>:<pages>"
Sizes and colour key sizeNm equals facts.json and scenes.json; the size sentence contains the rounded nm; the colour key's categories equal the scene's categories in legend order; a step's own label for a category equals the scene group's label; pdbRevision is well-formed
Measured numbers Specific regular expressions on the text for every quoted count: 146 bp and "about 1.65 times" (and not "two loops"); 3AFA thickness; 8XSO 11 nt / 10 paired / 12 subunits; 6QDV 14 / 42 nt / 49 chains; 7ZNK 28 chains plus two ALYREF pieces; 6Y0G 17 nt, about 5,600 rRNA nt, 75 machinery chains, poly(U), the exact tRNA sequence, cycloheximide as 3HE; 6OLE 39 residues reaching about 8 nm, L4/L17/L39 and 28S rRNA contacts; tubulin 170, 269, 351, 439 of 444; 2DN1 four chains, four haems, four oxygens
Write-up figures The five lite entries are 6OLE, 6QDV, 6Y0G, 7TUB, 7ZNK; their deposited atom counts round to 36,000 and 215,000; the served file sizes and unzipped sizes match "25 to 240 KB (60 to 420 KB unzipped)" in the "How it works" paragraph
Targets and links The only hand-written cross-app link is step 5's to Sequence (?ex=insulin%401&via=inside-a-cell); step 8's targets are P68871 and P69905; each target's gene list, name, organism and length equal the reviewed human UniProt record; targetsCheckedOn equals verified.retrieved
Wording TREX is called the longest structure, never the largest; alt texts name PDB <id> and never say "Blender render"
Stills Every step has a still under /data/inside-a-cell/, alt text longer than 30 characters, all stills under 2.1 MiB in total
Copy style No em dashes or exclamation marks in the steps, write-up and limits

These tests compare the copy with stored pipeline outputs; they do not contact the RCSB, Europe PMC or UniProt. Freshness against the live sources depends on rerunning verify_sources.py and build_structures.py by hand. No scheduled job checks for new PDB revisions; the PRD's item A16 (PDB revision status from CI) is not implemented. The 10 October 2026 scientific audit (docs/science/2026-10-10-scientific-audit-of-the-7-to-10-october-changes.md) notes that the "separate experiments" caveat (its finding C11 from the earlier audit) is not at the start of the tour.

Other numbers in the copy have no automated check: the 3.1 billion base pairs of the human genome and the TRiC description, for example, are prose from the cited background reading.

Sources and licences

sourceIds and sourceMeta in the registry entry:

Source Licence id Read on Flags
RCSB PDB CC0-1.0 2026-10-05
UniProt CC-BY-4.0 2026-09-27
Europe PMC EMBL-EBI-terms 2026-10-05 displayOnly
RNAcentral CC0-1.0 2026-10-05
PDB-101 no-reuse-licence 2026-10-05 displayOnly; background reading only, no text reused
Mol* MIT 2026-10-05
Blender GPL-3.0-or-later-buildtime 2026-10-05 displayOnly
Molecular Nodes GPL-3.0-or-later-buildtime 2026-10-05 displayOnly
Biotite BSD-3-Clause 2026-10-05 displayOnly
NumPy and SciPy BSD-3-Clause 2026-10-05 displayOnly

Excluded sources: AlphaFold 3 outputs (non-commercial terms) and DrugBank (non-commercial licence). See Data sources.

Integration with the suite

  • Shell. The workspace uses the shared app shell with the Research sidebar (Cell sits under Learn). The definition's rail adds one action, "The whole tour as text". The inspector shows the registry's sources (SourcesInspector), starts hidden and docks only while the main region keeps 60 rem (inspectorLayout). Coach marks target data-coach="steps" and data-coach="gate". See Shared shell.
  • Objects. Step 8 names haemoglobin β (P68871, HBB) and α (P69905, HBA1/HBA2). While that step is shown in the workspace, useLearn refreshes them as target objects with ref uniprot:<accession>, label "<gene> · <UniProt name>" (the same label the protein app gives), gene aliases, and a uniprot facts block dated targetsCheckedOn. Under D-0045 viewing never adds them to the person's library; the ObjectActions row beside each target offers the explicit actions. objectInbound["inside-a-cell"] is {}: Cell opens nothing sent to it. See Storage and state.
  • Send to. Each target's link is a SendToLink to Protein, which objects.test.ts pins as /protein/app?id=P68871&obj=uniprot%3AP68871&via=inside-a-cell. In the product preview the link is a plain appHref("protein-explorer", { id }).
  • Sequence. Step 5 links to appHref("sequence-toolkit", { ex: "insulin@1" }, "inside-a-cell").
  • Inbound links. The plasmid explainers page (src/app/(tasks)/[app]/explainers/[explainer]/page.tsx) links to a tour step through e.cell.stop. The Education tools link to steps 3 and 5 and reuse the viewer, palette and media pipeline.
  • Security. The workspace's Content Security Policy comes from headersFor("inside-a-cell"); it allows no external host and adds 'unsafe-eval' and data: in connect-src for Mol*. The same policy is applied to every Education tool. See Security and network.
  • Landing export. Cell has no card in landingApps. It appears as a link in the landing page's Education groups (content/apps/landing-education.ts), which /landing.json exports.

Methods and checks dialog

The methods and checks dialog (D-0085; Shared shell, Registry and content) reads content/apps/inside-a-cell.methods-sheet.ts: one group, the tour, which every step opens (the steps share one way of being made and checked), quoting the docs page's verification entries and defaults, with the scale note. definition.methods.group() returns the step id at the tour's index.

Tests

Unit tests run under npm test (node --test). On 10 October 2026, node --test src/components/tools/inside-a-cell/*.test.ts src/apps/inside-a-cell/*.test.ts ran 52 tests, all passing.

File Covers
content.test.ts Copy against verified.json, facts.json and scenes.json (table above)
mvs.test.ts Palette equals config.py; residue-level select and colorRegions survive into the MVS tree; every scene builds a well-formed tree; labels off; no camera transition; reset view on the line of sight; relative and blob: URLs; tooltip uses the step's own label; zoom limits; wheel factor caps; legend order; lite files keep full-atom entities; label background contrast in the theme-following canvas
download.test.ts gunzip and pass-through; every shipped structure file is gzipped BinaryCIF; stalled download times out; slow but progressing download completes; cancel and HTTP errors; orAbort
nav.test.ts stepIndexOf, isUnknownStep, clampIndex, arrowDelta, the serial loader (latest request wins, abort on change, return to running step, continues after failure), withTimeout
export.test.ts First author and paper line; step link; provenance block contents; Markdown contents; caption band colours
render-cost.test.ts Software renderer detection, unknown renderer treated as GPU, the software props
src/apps/inside-a-cell/objects.test.ts Target refs, labels, aliases and fact dates; Send to plan to Protein

Browser tests in e2e/inside-a-cell/ run with strictOffline: true (any request off the origin fails the test) and a 90 s timeout:

File Covers
tour.spec.ts Next, Previous and tabs with one canonical address; provenance chips, colour key and facts; ?step= deep link rewritten to #s=; unknown step notice; legacy redirects; stills served under the data cache rule; tour chunk loading and failure recovery; the 3D view's loading line, a failed download with Try again, then the model and stage controls with no CSP violations (skipped on the phone project); axe in light and dark with the text version open; 375 px layout; the product page's concise policy statement (#regulatory) and its documentation link, with no limits list on the page (the limits are on /docs/cell since D-0103; REL-460), and its live preview, which never writes the page's address
keys-and-exports.spec.ts Registry keys equal bound keys; Home, End, 1 to 8 and arrows with one announcement each; t; r, +, - before and after the 3D view; Markdown export; PNG export with provenance
sections.spec.ts Step tabs keep focus, a single selection and the mounted stage while updating the canonical step

e2e/education/structure.spec.ts also checks that /cell/app still loads its own scenes.json and not the Education one. Test infrastructure is in Build, test and CI.

Known gaps, defects and divergences

Design documents against the code

The PRD (docs/design/prd-inside-a-cell.md) and build specification (docs/design/build-spec-inside-a-cell.md, revision 2 of 2026-10-05) describe a much larger app than the one built. The build specification's own 7 October update says its R1 scope "remains a historical specification" and that "this update does not mark those proposals as shipped". Not present in the code:

  • pre-rendered step pages, the beforeFiles rewrite and DeepLinkGuard (C1);
  • a "Labels" toggle, "Save view", the "Labels in the 3D view" line, idle preloading of the next still, and protein links for steps 6 and 7 (C2; only step 8 has targets);
  • the About drawer, Transcript and Facts views, fact marks and the facts ledger (C3, C4);
  • IndexedDB sessions, notes, saved views and resume (C6);
  • the citations, facts and session exports (C7; only the still PNG and the tour Markdown exist);
  • every stretch item S0 to S8 (teaching pack, jump to a part, presenter mode, playlists, quiz, compare, completion summary, service worker, file-backed sessions);
  • PRD A16 (PDB revision status from CI) and A17 to A20.

Specific divergences from items the build specification states as acceptance criteria:

  • Step changes use history: "replace" (src/components/tools/inside-a-cell/tour.tsx:123); build spec C1.6 requires a pushed history entry per step so Back returns to the previous step.
  • The 7 October update describes a native select in the progress strip; the 9 October follow-on in the same document supersedes it with the shared line tabs, which is what tour.tsx renders.
  • Stills live at public/data/inside-a-cell/stills/, not public/tools/inside-a-cell/ as C1 lists.

Stale or contradictory copy and comments

  • content/apps/inside-a-cell.docs.ts:48 says "Today the tour has no file export", but tour.tsx registers a PNG export and a Markdown export.
  • content/apps/inside-a-cell.docs.ts:79 says nav.test.ts checks that "the pre-rendered step pages swap correctly"; there are no pre-rendered step pages and no such test.
  • content/apps/inside-a-cell.ts:219-220 carries two contradictory comments above objects ("waits for the next step" and "Learns the targets...").
  • The header comment of content/apps/inside-a-cell.ts (lines 7 to 11) says lines for later features (notes and saved views, marks on measured numbers, step images, citations, the facts table, the handout and a third coach mark) "are added by those tasks". Apart from the step image export, none of those features exists, and the registry has two coach marks.
  • The registry's dataPolicy.authored is "Anything you save in the app"; the tour has nothing to save.
  • docs/NEXT.md lists Cell items to recheck: tour tokens, advertised shortcuts, product-preview URL isolation, announcements, step-page rendering and still-image cache headers; and "accession mapping for earlier Cell stops" under Objects.

Fragile areas

  • Step ids are typed again in content/apps/plasmid-map.explainers.ts:22 (CellStop). Nothing ties that union to STEP_INDEX.
  • The pipeline fetches the current revision of each entry. Rerunning build_structures.py or render.py after the RCSB remediates an entry can change atom counts, measured facts or the still. scenes.json records pdbRevision, and content.test.ts will fail on changed facts, but nothing reports a new revision on its own.
  • render.py and build_structures.py must be run in the same period so that the still and the trimmed file come from the same revision; nothing checks this.
  • molstar-viewer.tsx relies on Mol* internals beyond its documented API (plugin.canvas3d, canvas3dContext.contextLost, managers.structure.hierarchy) and on Mol*'s CSS class names for hiding its controls. A Mol* upgrade needs the viewer, the 3D e2e test and vendorVersions (src/lib/vendor.ts, checked against package.json) to move together.
  • The pipeline depends on Blender 5.2.2 as a pip module and Molecular Nodes 520.2.0; the README notes the compositor must be forced to CPU for headless runs without EGL.
  • npm test loads public/data/inside-a-cell/*.bcif.gz and the stills; deleting or renaming one fails the content and download tests.

How to add or change content safely

Change wording in a step

Edit content/tools/inside-a-cell.steps.ts. Keep every number that content.test.ts asserts (or update the test and its source in facts.json together, never the test alone). Keep alt naming PDB <id>, avoid em dashes and exclamation marks, and keep the copy free of product names (the registry supplies them). Run node --test src/components/tools/inside-a-cell/content.test.ts.

Refresh the data

  1. python3 pipeline/inside-a-cell/verify_sources.py (network; standard library). Review the printed table and any failure.
  2. In a virtual environment outside the repository, install pipeline/inside-a-cell/requirements.txt, then python build_structures.py --cache <dir>.
  3. Review git diff of facts.json and scenes.json: changed pdbRevision, atom counts, sizes or facts mean the copy may need to change.
  4. python render.py --cache <dir> (use --preview --steps <id> to check framing first). Each still took 1 to 4 minutes on 2 CPU threads per the pipeline README.
  5. Update dataRetrieved and targetsCheckedOn if the retrieval dates changed (the tests compare them), then run npm test.
  6. If a structure's shipped bytes change, public/data/education/structures/6y0g-view.bcif.gz and 6qdv-view.bcif.gz are byte-for-byte copies of Cell's 6Y0G and 6QDV files made by pipeline/education/structure-sources/build_views.py; rerun that script so the two stay identical (see Education tools).

Add, remove or replace a step

  1. Add the entry to STEPS in config.py with every polymer entity categorised and an expected description, plus UNIPROT accessions the text relies on.
  2. Rerun the three scripts above. content.test.ts requires 7 to 9 steps and the same order in the scene data and the steps.
  3. Add the id and short name to STEP_INDEX, the body to bodies in inside-a-cell.steps.ts, and the counts the text quotes to content.test.ts.
  4. Update the shortcut list in content/apps/inside-a-cell.ts (STEP_KEYS covers 1 to 8; the definition accepts a single digit 1 to 9 only), features, capabilities, the detail.sources credit line listing the PDB entries, and CellStop in content/apps/plasmid-map.explainers.ts.
  5. Never reuse a removed id for a different structure: old ?step= links and the legacy redirect would open the wrong step.
  6. A new source needs a sourceIds entry and sourceMeta with a licence id and read date; a share-alike or non-commercial source must be flagged or excluded.
  7. Run npm test, npm run lint, npx tsc --noEmit, npm run build and npm run test:e2e (the tour, keys and education structure specs).

Change the palette

Change CATEGORIES in config.py and palette in palette.ts together (mvs.test.ts checks they match), then re-render the stills for Cell and the Education posters: both read the same colours, and the Education manifest's colour legends name them by word (molecularColours in src/apps/education/workspace.tsx).