-
Notifications
You must be signed in to change notification settings - Fork 0
Architecture
DecoJS is a vanilla-JavaScript Progressive Web App with no build pipeline. The browser loads ES modules directly from source, and every .js file in the repo is exactly the file the browser parses. This page covers the high-level shape; per-file details live in Module-Reference.
There is no bundler, no transpiler, no TypeScript compile step, no dist/ directory. HTML files load entry modules with <script type="module" src="..."> and the rest resolves through native import statements. Two consequences worth naming:
-
Imports use explicit relative paths with
.jsextensions (e.g.import { COMPARTMENTS } from './tissueCompartments.js'). No bare-module resolution, no import maps, no path aliases. -
Third-party libraries come from CDN
<script>tags, not npm. Chart.js, chartjs-plugin-annotation, Hammer.js, and chartjs-plugin-zoom are loaded fromcdn.jsdelivr.netin the entry HTML;node_modules/exists only for Jest (used by an alternate test runner; the primary runner is dependency-free).
The rationale is educational transparency: a reader with a browser and a text editor can trace any value on screen back to the exact line of source that produced it, without sourcemaps, bundling artefacts, or a toolchain to stand up.
The app is split into three user-facing sections, defined by NAV_ITEMS in js/nav.js:
| Section | Count | Entry HTML files | Primary modules |
|---|---|---|---|
| Sandbox | 5 simulators |
sandbox/index.html, sandbox/tissue-saturation.html, sandbox/transfilling.html, sandbox/cascade-filling.html, sandbox/gas-law.html
|
DiveSetupEditor + DiveProfileChart + MValueChart + GFChart (main sandbox); specialised simulators for the rest |
| Theory | 4 lessons |
pressure.html, tissue-loading.html, m-values.html, gradient-factors.html
|
Embed chart components with canned setups; TissueSaturationSim, StickyTOC, HeroMotion for page chrome |
| Tests | 7 quizzes |
quiz-physics.html, quiz-anatomy.html, quiz-accidents.html, quiz-safety.html, quiz-training.html, quiz-equipment.html, quiz-vessel.html
|
Generic quiz.js engine + per-topic JSON in data/quiz-*.json
|
The landing (index.html) and about.html sit outside these groups.
All imports are static (no dynamic import() calls) and the graph is acyclic. The core has four shared modules used across sections; the Sandbox-only chart and editor components layer on top.
graph TD
subgraph Entry["Entry HTMLs"]
SandboxHTML[sandbox/index.html]
TheoryHTML[theory pages<br/>pressure / tissue-loading / m-values / gradient-factors]
QuizHTML[quiz-*.html]
end
subgraph Components["Components & Charts"]
DSE[components/DiveSetupEditor.js]
DPC[charts/DiveProfileChart.js]
MVC[charts/MValueChart.js]
GFC[charts/GFChart.js]
TSS[components/TissueSaturationSim.js]
Quiz[quiz.js]
end
subgraph Core["Core algorithm modules"]
Deco[decoModel.js]
TC[tissueCompartments.js]
DS[diveSetup.js]
DP[diveProfile.js]
TE[tissueEducation.js]
end
subgraph Shared["App-wide shared"]
Nav[nav.js]
I18n[i18n.js]
CT[charts/chartTheme.js]
CTypes[charts/chartTypes.js]
end
SandboxHTML --> DSE
SandboxHTML --> DPC
SandboxHTML --> MVC
SandboxHTML --> GFC
SandboxHTML --> Nav
SandboxHTML --> I18n
TheoryHTML --> DPC
TheoryHTML --> MVC
TheoryHTML --> GFC
TheoryHTML --> TSS
TheoryHTML --> Nav
TheoryHTML --> I18n
QuizHTML --> Quiz
QuizHTML --> Nav
QuizHTML --> I18n
DSE --> DS
DSE --> CTypes
DSE --> I18n
DPC --> Deco
DPC --> DS
DPC --> TC
DPC --> CT
DPC --> CTypes
DPC --> I18n
MVC --> Deco
MVC --> TC
MVC --> CT
MVC --> CTypes
MVC --> I18n
GFC --> Deco
GFC --> TC
GFC --> CT
GFC --> I18n
TSS --> TE
TSS --> Deco
TSS --> TC
Deco --> TC
DS --> Deco
DS --> TC
Nav --> I18n
Observations:
-
decoModel.jsandtissueCompartments.jsare the two universal-core modules — every chart pulls from them. -
diveSetup.jsdepends ondecoModel.js(it callscalculateNDL,generateDecoSchedule,simulateDepthTime, etc.), so importingdiveSetuppulls in the whole algorithm. -
diveProfile.jsis standalone — parsing and validation only, no algorithm imports. -
Nothing imports
quiz.jsexcept quiz pages — the quiz subsystem is cleanly isolated from the deco algorithm.
All three algorithm charts — DiveProfileChart, MValueChart, GFChart — use the same rendering stack:
-
Chart.js v3+ on HTML5 Canvas (not SVG). One
<canvas>per chart instance; Chart.js owns the pixel buffer and redraws on everychart.update(). - chartjs-plugin-annotation for M-value lines, pAnchor markers, deco stop annotations, and gas switch markers.
-
Hammer.js + chartjs-plugin-zoom for pinch/pan/wheel interaction. Double-click resets zoom; a lock button (
js/charts/interactionLock.js) toggles interaction on for mobile. -
chartTheme.jsreads CSS custom properties from:rootand applies them as Chart.js defaults — typography, grid colors, legend styling. Idempotent; called before every render and on theme toggle. -
chartTypes.jscentralises dive-setup validation and normalisation (validateDiveSetup,normalizeDiveSetup,mergeOptions). No runtime types — just schema-style validation.
All CDN scripts are loaded in the entry HTML, not imported by modules, so the algorithm core remains Chart.js-free and is directly unit-testable under Node.
Custom JSON-based loader — no i18next, no framework. Three languages ship (locales/en.json, locales/cs.json, locales/es.json): theory pages default to English, quizzes default to Czech (SPČR source material).
-
js/i18n.jsfetches the current locale bundle, exposes atranslate(key, params)function, and wires a language switcher into the nav. - On user language change it dispatches a global
languagechangeevent. - Every component that renders text listens for
languagechangeand re-renders — chart axis labels, legend, tooltips, DiveSetupEditor form labels, nav links, quiz question bodies.
Components extend the native EventTarget and communicate via DOM-style custom events — no pub/sub library, no store, no framework state.
The canonical flow:
- User edits a field in
DiveSetupEditor. - The editor serialises its form to a
diveSetupobject and emitschangewithdetail.diveSetup(ifemitOnInput === true, every keystroke; otherwise on blur). - Each chart listens on the editor instance:
editor.addEventListener('change', e => chart.update(e.detail.diveSetup)). -
update()re-validates throughchartTypes.js, recomputes tissue loading and ceiling viadecoModel.js, rebuilds Chart.js datasets, and callschart.update().
The same pattern carries languagechange (global on window) and theme toggles. No component reaches into another's internals; all cross-component state moves through events or through the diveSetup object itself.
Per-file details live in Module-Reference.