28 entries across 24 modules.
HistoryPanel component that displays past search queries,
when they were executed, and how many results were returned.
History is persisted in chrome.storage.local (same mechanism as user settings / cache).
Clicking a query re-triggers the search via the app context's pendingSearchQuery.
Where a MoleculeSpinner is in its resolve-and-render lifecycle.
What a MoleculeSpinner resolved, reported through
MoleculeSpinnerProps.onStatusChange. Everything but state is present only once a
structure has been drawn.
Props for MoleculeSpinner.
query - The search query to depict. Changing it resolves a new structure.size - Canvas edge length in CSS pixels. Defaults to 128, matching the cube it replaces.spinSeconds - Seconds per revolution. Changing it retimes without rebuilding.atomScale - Multiplier on the uniform atom radius; lower is more stick, less ball.showHydrogens - Draw explicit hydrogens. On by default; the full structure reads
better than a bare skeleton.pending - Rendered while the structure is still being resolved, before anything is
known. Kept separate from fallback so callers never flash one graphic and then
replace it with another.fallback - Rendered only once it is settled that there is nothing to draw: no query,
no PubChem match, or no usable WebGL context.onStatusChange - Notified on every lifecycle transition.Renders the molecule a search query refers to as a slowly rotating ball-and-stick model, as a drop-in replacement for a generic loading spinner.
The structure comes from PubChem: the query is resolved to a CID, and its 3D conformer
fetched — falling back to the flat 2D record for the ionic and inorganic compounds
PubChem computes no conformer for, which a planar wobble keeps legible. When nothing
resolves, fallback is rendered instead, so the caller always has something on screen.
three.js is loaded through a dynamic import, so the WebGL renderer stays out of the initial bundle and only downloads once a structure is ready to draw. The component is memoized and holds its scene in a ref: the surrounding loading UI re-renders constantly as results arrive, and rebuilding the scene on each of those would restart the animation and leak WebGL contexts.
Full-panel, sortable log of every price change ChemPal has ever recorded
across every supplier and product — one row per change, newest first by
default. Only available in advanced mode; wired up the same way as
StatsPanel: a PANEL.PRICE_HISTORY entry in the app's panel switch,
reached from the speed-dial menu, hidden the moment advanced mode turns off.
Reads the full price_history IndexedDB store via getAllPriceSeries
and flattens it with buildPriceChangeRows — series with only one
recorded price (never changed) contribute no rows, so this view answers
"which products actually do have price changes logged".
StatsPanel displays per-supplier search statistics as a full panel view. Three tabs: By Supplier (pie), Daily (bar), Totals (text). The pie chart has two views toggled like the Titanic example:
Corner status badges for the bottom-left of the app: "DEV BUILD" in
non-production builds, "ADVANCED MODE" while the Konami-unlocked advanced mode
is active. Both live in a single BadgeTray, so whichever combination
applies lays out side by side rather than overlapping — and the row reflows on
its own when advanced mode is toggled.
Rendered once at the app level so the badges follow the user across panels, rather than being duplicated per page.
ExportsPanel lists the
.xlsxresult exports cached in IndexedDB, newest first, and lets the user re-download or delete each one (or clear them all). The list live-refreshes on theIDB_EXPORTS_UPDATEDevent, so exports created from the results context menu appear without reopening the drawer.