Changelog

All notable changes to this repository. The format follows Keep a Changelog; phases refer to spec §12 (sitemap-and-content-directory.md).

[Unreleased] — All 161 pages published (2026-09-19)

The author reviewed every page and promoted the lot. status: review no longer appears anywhere in the content; 161 pages are published and the status badge is gone from the site.

Changed

  • TODO(confirm) no longer blocks publication, by the author’s ruling. §5.3 made a marker a publication blocker, which assumed the only way to avoid shipping an unverified claim is to withhold the page carrying it — the wrong trade when the page is otherwise reviewed, since it hides a whole lesson to avoid admitting a footnote. Two neighbouring rules were the same shape (an exercise with no answer key, a notebook a page names before it exists) and now share one disclosed() policy in check-content. What unites them is that the page already tells the reader: the marker is literal in the prose, the exercise says “No answer key yet” and declines to grade, and the notebook card carries a “notebook not yet indexed” badge with its buttons disabled. They remain reported as warnings. --strict-todo restores the original gate.
  • A checker bug, found by promoting. The rule meant to keep a published lesson’s notebook runnable read the lesson’s datasets: list — what the lesson discusses — rather than what the notebook downloads. It fired on L5.4, L5.5 and L5.6 for naming ds-fsaverage, which they name precisely because its licence blocks it, while their notebooks download ds-lemon or nothing. It would equally have missed a notebook fetching a closed dataset its lesson never listed. It now reads notebooks/index.json.
  • /start/ and /about/ were out of date and are corrected. /start/ still told readers that only three lessons existed and the rest were hidden stubs. /about/ still called the site name, domain and repository undecided.

Added (analytics)

  • Plausible analytics, at the author’s request, in the <head> of every page. It is the site’s only third-party request — no cookies, no cross-site identifier, nothing to consent to. /about/’s privacy section names it rather than claiming the site talks to nobody, and says that a content blocker stops it with no loss of function. Note that this makes “no analytics”, stated in the Phase 0 decisions in README.md, historical rather than current.

Added

  • TodoNote. Publishing removed the badge that used to explain the markers, which would have left TODO(confirm) sitting in prose marked finished with nothing saying what it meant. Each page now counts its own markers and says. On lessons, capstones, labs, pitfalls and paths — 103 pages; dataset pages already had their own note.

Fixed

  • The pipelines CI job, which had never passed. 17 failures and 52 errors all reduced to one undeclared package: pipelines/pyproject.toml never listed scikit-learn, and MNE implements method='fastica' through it. It passed locally throughout, because this machine has scikit-learn installed for the notebooks — which is how a missing declaration hides, being present for an unrelated reason everywhere except the one environment that installs only what is declared.

Known gaps

  • A second cache that invents faults. After 161 content files changed at once, the build reported Duplicate id "…" found for collections containing no duplicate; clearing .astro produced a clean build from identical sources. That is the second cache here to do this (the first was node_modules/.vite, twice). npm run dev:clean now clears both and build:clean joins it. Reproduce against a clean build before believing a strange symptom.
  • The two item banks are still algorithmic. The page review does not cover them: 67 drill items and 189 scroller annotations remain unreviewed, and scripts/apply-label-review.py can never write expert without a verdict.

[Unreleased] — Deployed to eeg.neurokinetikz.com (2026-09-19)

§13 items 1 and 3 are answered. The site is live at https://eeg.neurokinetikz.com/, published from this repository by .github/workflows/deploy.yml on every push to main.

Added

  • Deployment. deploy.yml stopped being a placeholder: it renders the notebooks, builds, runs Pagefind, asserts every notebook fragment exists, and publishes to GitHub Pages. HTTPS is enforced.
  • A sitemap and robots.txt. Neither could exist while site was a placeholder, because a sitemap needs an absolute origin to write into <loc>. 233 URLs. It lists what the build emits, which includes the 161 pages still at status: review — they are reachable, so they are listed. That is a different question from the feed’s, which carries published items only; the sitemap answers “what is reachable” and the feed answers “what is finished”.

Changed

  • A custom domain serves at its own root, so there is no base path. This is the decision with the longest reach in the repository: 466 internal links are root-absolute, and they are correct only because the site is served at /. Publishing at a bare …github.io/eeg/ URL instead would require a base and a rewrite of every one of them. A base-path implementation was written and then reverted when the domain was chosen.
  • The domain is configured in the repository’s Pages settings, not by site/public/CNAME. actions/deploy-pages does not read a CNAME file — a fact worth recording because the file’s presence suggests otherwise. It is kept so the setting survives redeploys.
  • Commit authorship rewritten to GitHub’s users.noreply alias, which GitHub’s email-privacy setting requires (it rejects the push otherwise, GH007). refs/original/ is retained locally as a backup.

Known gaps

  • “Open in Colab” stays hidden. The repository is private and the published site is public, which GitHub Pro permits, but Colab cannot read a private repository. REPO_IS_PUBLIC in site/src/lib/site/config.ts is the single switch that turns the buttons on for all 59 notebook cards if the repository is ever made public.

[Unreleased] — Phase 4, Level 7 and polish (2026-09-18)

Built by eight tracks in two waves. This completes §12: every phase of the build plan is done.

Added

  • Lessons all eight Level 7 electives and C7, the final capstone. Every level of the curriculum now has a body: 56 lessons, 8 capstones, 167 checkable exercises.
  • Widgets w-latency-budget and the sleep mode of w-raw-scroller26 widgets, the complete §8 catalog. The latency widget decomposes a real-time budget into filter group delay, buffer and processing rather than printing a total, and makes the zero-phase point concrete: asking for zero phase in a live loop costs a look-ahead of N−1 samples, and run per block it returns a negative delay, an output ahead of its input.
  • Notebooks 7 more, 59 in all, each executed with its outputs stored.
  • Site a /cite page assembling what the catalog already records; verified “In other tools” sidebars for every level; and the first accessibility audit, run against the built output in both themes.

Changed

  • Two dataset licences verified from primary sources, and a third found wrong in the catalog. ds-sleep-edf qualifies (ODC-By 1.0, derivative databases expressly covered). ds-bci-iv-2a does not: it is CC BY-ND, so its lesson may download, compute and publish benchmark tables but may ship nothing derived from the signal — the community summary of “open with attribution” is wrong on the decisive point. And ds-mpeng was recorded under its descriptor paper’s licence rather than the data’s: the paper is CC BY, the data are CC BY-NC, downstream of consent given for non-commercial research only. Nothing had ever derived from it. The registry fix is §10.11 item 0.
  • derive_snippets refuses no-derivatives licences explicitly. It returned no for CC-BY-ND-4.0 only because that spelling is absent from the permissive list — the right answer for the wrong reason, and correcting an apparent typo would have opened it silently.
  • “Expert” meant two different things. A sleep hypnogram scored by the recording’s own technologist is genuinely expert, but §4.5’s promotion rule — an annotated view leaves draft when every item is expert — means the author reviewed it. A third party’s scoring would have satisfied a gate that exists for the author’s judgement. An expert label must now carry reviewed_at or source.
  • Accessibility. Nine classes of failure fixed, the largest being 1,868 scrollable regions unreachable by keyboard. That remedy had to be remedied: the first version used a landmark role, so a notebook page acquired dozens of identically-named landmarks and a previously-silent rule began failing. Caught by re-measuring after the change. Two rounds of target-size failures, the second only visible once the first was fixed.
  • Exercise states read as what they are. An unfilled exercise advertised “Accepted within ±0”, which it could not honour since it declines to grade until its notebook runs. Three states now: a tolerance, an exact answer required, or no key yet.
  • The two group-contrast capstones contradicted each other on subset size, thirty per group against ten to twenty. Both now use the tighter figure and both name the ambiguity: §11’s number is written for a single-cohort capstone and the binding constraint is the ten-minute runtime.

Known gaps

  • Author review of every draft, label and answer key; all 161 content pages remain status: review, and the two Phase 0 item banks (67 drill items, 195 scroller annotations) are still unreviewed. The review tool is built: cd site && REVIEW_TOOL=1 npm run dev.
  • §13 items 1 and 3 (site name and domain; repository name) still block deploy, canonical URLs and the Colab buttons — the only disabled control left on 59 notebook cards. ds-mne-sample’s licence is a third open question; answering it unblocks a real head model, L5.4’s original exercise, L5.5, L5.6 and part of C5.
  • ds-srm’s EDF headers declare no unit, so every amplitude reads 10⁶ too large, and the dataset is self-consistent about it. mne-bids-pipeline runs on it, succeeds, and writes a report in which every microvolt is wrong. Recorded as a caveat; only one notebook loads it and no asset derives from it.
  • moabb’s default download channel for ds-bci-iv-2a is not the one whose licence was verified. Both notebooks pin the verified provider; whether the default republishes under the same terms is TODO(confirm).
  • Two figures skipped deliberately — no licence-clear dataset holds a marked epileptiform discharge or a scanner artifact, and simulating either would fabricate what those lessons teach people to recognise. L7.7 ships with no widget, no notebook and no figure.
  • No compact CNN in L7.1 (braindecode/torch deliberately absent); a filter-bank CSP stands in and says what it does not answer.
  • TFCE ships in neither cluster widget.

[Unreleased] — Phase 3, Levels 4–6 complete (2026-09-18)

Built by sixteen tracks in two waves against the Phase 3 addendum of site/CONTRACTS.md.

Added

  • Lessons all seven Level 4 (time-frequency and oscillations), all seven Level 5 (connectivity and spatial analysis) and all six Level 6 (inference and rigor) as full drafts, with C4, C5 and C6. Checkable exercises rise from 65 to 136; every one in Levels 0–6 has a filled key whose comment states the subject, the parameters, the tolerance and the reason the tolerance is what it is.
  • Widgets w-evoked-vs-induced, w-phase-clock, w-wavelet-explorer, w-tf-baseline-explorer, w-burst-detector, w-volume-conduction-sim, w-csp-explorer, w-garden-of-forking-paths, plus the forward mode on w-dipole-to-scalp and the tf mode on w-cluster-permutation-viz — 25 widgets in all. registry.ts is now generated rather than transcribed.
  • Notebooks 22 more (nb-4-1nb-6-5 and four capstones), 52 in total, each executed with its outputs stored, plus helpers_l4/l5/l6.py. statsmodels, scikit-learn and mne-connectivity pinned; bycycle optional with a documented fallback.
  • Site progress export and import (plan-then-apply, seven typed refusal codes, a refusal leaves storage byte-identical); “In other tools” sidebars for Levels 0–3 with every claim verified against EEGLAB 2026.0.0 and FieldTrip 20251218; the statistics thread wired across nine lessons; a dev-only review surface for the two Phase 0 item banks with an applier that can never mark an item expert without a verdict.
  • Tooling scripts/merge-manifest.py (parallel data tracks write manifest parts), scripts/gen-widget-registry.py, scripts/apply-label-review.py.

Changed

  • Two dataset licences failed the §10.7 gate and no boundary-element head model ships. ds-fsaverage is settled and does not qualify: its licence covers data as well as software and permits derivatives only by propagating the whole agreement onto every copy. Every convenient source is silent — the copy MNE ships contains no licence file, the hosting node returns no record, and MNE’s dataset documentation names none — so a check of those three would have concluded there was nothing to worry about. ds-mne-sample is genuinely contested and is a new §13 decision: its own documentation restricts use to familiarisation, its download channel carries nothing, a public mirror declares CC0 while dropping the restriction sentence, and the archive bundles fsaverage. Unlike ERP CORE there is no strictest-reading compromise, because “familiarisation only” cannot be complied with by being stricter. Consequently §8’s “precomputed BEM maps” and §6 L5.4’s “sphere vs BEM” exercise are not possible. What ships is two concentric sphere models, one shell against four, flagged is_bem: false with a disclosure the widget prints verbatim, and L5.4 now asks “one shell versus four — what does the skull do?”. Recorded as a deviation from the spec.
  • §6’s phase-locking positive control does not work, and the lesson said a failing pipeline is broken. Over 30 recordings the paired test — stimulus against silence within each recording, each normalised by its own neighbourhood — gives +0.259 dB, t(29) = 0.33, p = .745; power at the nominal frequency is 0.586× its own neighbourhood and none of the 30 reaches 2×. The archive names the phase “Fixed Auditory Stimulus” and states no rate; the rate was this site’s inference. L4.5 now reports the measurement as its lesson and uses two controls that do work. data/directory.yaml carries the finding and a question for the author.
  • Share-alike now reaches the reader. Seventeen ERP CORE sidecars named the licence without marking themselves share-alike and three carried no attribution; two different attribution strings were in use. Every asset now carries the complete one, sixteen pages render the obligation and the credit, and check-content rejects a sidecar that names a share-alike licence without both.
  • An unfilled exercise was grading answers. A null key coerced to zero against a default tolerance of zero, so a learner typing 0 was told they were correct. All four checkable types now decline to grade and say why; the rule lives in a tested module.
  • The content checker gained three rules: every array whose entries claim a label_source is validated (a sidecar was shipping a value the spec does not allow), share-alike assets must carry their attribution, and scripts/scrub-notebooks.py --check is a CI step alongside a new job that runs the pipelines tests.

Known gaps

  • Author review of every Phase 3 draft, label and answer key; all 161 content pages remain status: draft.
  • ds-mne-sample’s licence is a new §13 question. Answering it unblocks a real boundary-element model, §6’s original L5.4 exercise, L5.5’s inverse comparison, L5.6’s connectome and a quarter of C5.
  • nb-5-7 takes 19 minutes and breaks the §11 ten-minute budget; 9 seconds of that is compute and the rest is one host serving at a twentieth of the speed of the other two. Shrinking its subject survey would delete the selection-effect disclosure L5.7 turns on. The author decides which to keep.
  • §6 words L4.4’s exercise as a peak where the shipped key is a window mean; the prompt is unambiguous as written but the intent is the author’s to confirm.
  • TFCE ships in neither cluster widget: it needs its own null distribution and did not fit the budget. Both say so rather than relabelling what they do have.
  • ex-6-2 as §6 words it has no separable answer; it should ask for standard errors.
  • The Level 4 and 5 answer keys disagree with their notebooks in three places, each with the cause found and both numbers printed: two skull-effect figures differ because the widget evaluates at its nearest stored grid point and the notebook at the exact position, and one coherence noise estimate differs by a single draw.

[Unreleased] — Phase 2, Levels 2–3 complete (2026-09-18)

Built by twelve parallel tracks against the Phase 2 addendum of site/CONTRACTS.md. The tracks were interrupted twice by a session limit and finished from the state on disk rather than restarted, so the repository history for this phase is five commits rather than one.

Added

  • Lessons all eight Level 2 lessons (L2.1 loading and BIDS, L2.2 bad channels, L2.3 re-referencing, L2.4 filter choices, L2.5 artifact rejection, L2.6 ICA, L2.7 ASR/SSP and alternatives, L2.8 the pipeline) and all seven Level 3 lessons (L3.1 epoching and baseline, L3.2 the ERP and its components, L3.3 measuring ERPs, L3.4 topographies, L3.5 SNR and trial counts, L3.6 single-trial approaches, L3.7 statistics for ERPs) as full drafts; every checkable exercise in Levels 0–3 now has an answer key with its subject, channel, window and tolerance recorded beside it.
  • Widgets w-bad-channel-detective, w-threshold-tuner, w-ica-component-gallery (a seeded 20-component drill with a confusion table), w-reference-explorer (waveform and topography modes), w-epoch-builder, w-erp-averager, w-measurement-explorer, w-cluster-permutation-viz — 17 widgets in total, each with folder tests and a lab page; registry.ts regenerated.
  • pipelines/ the eegpipe package: the canonical step order as code (load → montage → bad channels → filter → interpolate → re-reference → ICA → epoch → reject), PipelineConfig from YAML, a run log that records a failure as a row rather than raising, HTML QC reports, version pinning and seeding, a CLI, three configs (erpcore-p3, eegbci, synthetic) and 123 tests. A subject that fails does not stop the cohort.
  • Notebooks nb-2-1nb-2-8, nb-3-1nb-3-7, and the capstones nb-c2-clean-pipeline and nb-c3-replicate-erp-core — 17 more, 30 in total, all executed with outputs stored; _shared/helpers_l2.py and _shared/helpers_l3.py; pyprep, mne-bids, autoreject and pandas pinned, with mne-icalabel (plus onnxruntime) and asrpy as optional packages that each degrade to a documented fallback.
  • Data extract_p2_widgets.py, make_ica_gallery.py, make_topomap_matrices.py, make_cluster_fixtures.py, make_figures_p2.py, fetch_erpcore.py; the manifest is now 157 records and 43.4 MB, with an asset kind taxonomy (timeseries, matrix, activation, spectrum, index, figure, json) so a topography matrix is no longer validated as if it were a µV time series.
  • Pitfalls 31 with full bodies, up from 15.
  • Paths /paths/erp, /paths/oscillations, /paths/bci, /paths/realtime.
  • Tooling scripts/scrub-notebooks.py (rewrites machine-specific absolute paths out of executed notebooks, in outputs as well as source; --check for CI) and scripts/merge-notebook-index.py.

Changed

  • ERP CORE’s licence is recorded as CC BY-SA 4.0 (spec §13 item 15, answered 2026-09-18). The licence is contested at source and three real statements disagree: the LICENSE file shipped with the data says CC BY-SA 4.0, dataset_description.json says CC0, and the OSF node thsqg record says CC BY 4.0. Spec §10.7 makes the most restrictive reading govern. Derived assets ship, each carrying CC BY-SA 4.0 and the attribution the LICENSE file asks for; share-alike binds anything derived from these data. data/directory.yaml gained license_status and license_decision, and both build_dataset_pages.py and check-content.ts now gate share-alike and contested licences on an explicit decision, so the ruling opened this dataset alone and left ds-hbn at snippets: no.
  • eegpipe.run.scrub_paths rewrites absolute paths out of a stored traceback, so a run log and the QC report built from it can be handed to someone else.
  • w-cluster-permutation-viz ships channel_adjacency, the 78-edge Delaunay neighbour list from the montage, so the browser re-clusters over the same neighbourhood the shipped null distribution was built on instead of falling back to MNE’s array lattice.
  • w-ica-component-gallery names, in the end-of-session summary only, the classes its drill offers that the loaded decomposition contains no example of. ICLabel found no cardiac and no single-electrode component in these three subjects, so two of the seven buttons were never correct and nothing said so.
  • Wall-clock test budgets are a regression guard, not a benchmark: bestOf(n, …) against regressionCeiling(expectedMs) with 8× headroom (EEG_PERF_FACTOR to override), after four timing assertions failed under a load average of about 50.
  • data/scripts/common.py reads the ERP CORE facts from data/directory.yaml at run time instead of holding a copy, which is what let two tracks write two different licences.

Known gaps

  • Author review of every Phase 2 draft, label and answer key; label_source is algorithmic throughout and all 161 content pages remain status: draft.
  • ex-2-7-snr-asr: asrpy 0.0.8 and the documented NumPy method disagree about what a cutoff of 20 SD means — 79 % of the signal RMS replaced against 17 % — and a reviewer must settle which behaviour the published key quotes.
  • ex-3-5-minimum-trials rests on an SME ≤ 1 µV threshold that is a course decision, not a literature value.
  • ICA does not converge for 9 of 10 subjects at max_iter: 500; the run log records the flag rather than failing, which is correct, but a cohort that flags nine in ten deserves an author’s eye.
  • steps.filter.ica_copy builds its ICA copy at 1–30 Hz while nb-2-6-ica and w-ica-component-gallery fit at 1–100 Hz. The difference is deliberate and each says so, but a component index is comparable within a source, not across.
  • FULL_COHORT = True in the capstones has not been executed (a further ~1.2 GB of download).
  • The data/downloads/erpcore cache was deleted after the tracks released it; data/scripts/fetch_erpcore.py repopulates it.

[Unreleased] — Phase 1, Levels 0–1 complete (2026-09-17)

Built by nine parallel tracks against the Phase 1 addendum of site/CONTRACTS.md and integrated the same day.

Added

  • Lessons L0.1, L0.2, L0.3, L0.6, L1.1, L1.2, L1.3, L1.4, L1.6, L1.7 as full drafts (every §4.1 section; answer keys computed or derived from the notebooks, TODO(confirm) where the author must confirm); Level 0/1 index bodies trimmed to prose.
  • Widgets w-sampling-explorer (hardware/math), w-montage-explorer, w-dipole-to-scalp (intro; analytic homogeneous-sphere potentials validated against MNE to < 1 %), w-build-a-signal, w-welch-explorer (welch/window), w-aperiodic-explorer (fit/compare), with folder tests and lab pages; registry.ts lists all nine.
  • Data data/scripts/extract_p1_widgets.py, make_montage_positions.py, make_dipole_fixtures.py, make_figures.py; assets for the six widgets (EEGMMIDB, Iowa PD; specparam fits for 12 subjects; 339-electrode positions from MNE’s standard_1005; dipole fixtures from MNE’s sphere model) and 13 figures under site/public/figures/; manifest now 109 records including JSON and figure kinds.
  • Pitfalls full bodies for pf-trigger-offsets, pf-dropped-samples, pf-bridged-electrodes, pf-site-device-confound, pf-group-demographic-confound, pf-aliasing-on-downsample, pf-zero-padding-resolution; Phase 0 pitfalls wired to real Example figures.
  • Notebooks nb-0-2-montages, nb-0-3-first-load, nb-0-6-first-look-report, nb-1-1-resampling, nb-1-2-fft, nb-1-3-psd, nb-1-6-line-noise, nb-1-7-specparam, capstone scaffolds nb-c0-recording-report, nb-c1-spectral-fingerprint; _shared/helpers_l1.py; index.json merged from index-parts/ (13 notebooks); specparam==2.0.0rc4 and edfio pinned.
  • Site Pagefind search (⌘K modal, /search, drafts excluded from the index unless SHOW_DRAFTS=1), /feed.xml (published entries only), resilient Figure with sidecar provenance, capstone-aware /notebooks, “used on this site” filter on /datasets; lab sidebars no longer duplicate body sections.
  • Validation check-content.ts: manifest path_roots, purely synthetic JSON/figure products, draft gate for notebook references, wiki-link grep limited to prose (not .py or notebook code cells).

Known gaps

  • Author review of all Phase 1 drafts, labels and answer keys; dataset pages are not promoted.
  • w-montage-explorer, w-dipole-to-scalp, w-welch-explorer, w-aperiodic-explorer ship generated placeholder screenshots until replaced by browser captures.
  • L0.1’s three-panel resolution-ladder figure (scalp vs intracranial vs rodent) is not generated: the intracranial and rodent sources were not downloaded in this phase.
  • Two psds.json fits (S004, S009 eyes-closed) re-evaluate below their stated R² (double-alpha decompositions); the aperiodic widget reads peak_params as summed heights.

[Unreleased] — Phase 0, vertical slice (2026-09-17)

Built in parallel by ten agents against site/CONTRACTS.md and integrated the same day (build, unit tests, content validation, headless-browser smoke test and Lighthouse all pass; see README.md). The author’s review is pending. Every lesson, lab and dataset page ships status: draft.

Added

  • Site scaffold — Astro 5 with content collections and MDX, Svelte 5 islands, uPlot; design tokens and global styles; layouts and routes for /, /start, /curriculum/…, /labs/…, /pitfalls/…, /datasets/…, /notebooks, /paths/…, /glossary, /about, /changelog, /404.
  • Content model — schemas for lessons, levels, capstones, labs, pitfalls, datasets, paths, glossary, reading and pages (site/src/content.config.ts, spec §5.2).
  • UI componentsWidgetFrame, ExerciseBox, PitfallBox, NotebookCard, Callout, Figure, Cite, LessonNav, OtherTools, LevelProgress, LadderGraphic, ProgressTracker; localStorage progress store.
  • DSP librarysite/src/lib/dsp/ (FFT, windowed-sinc FIR, Butterworth IIR / SOS, filtfilt, Welch) tested against SciPy fixtures generated by data/scripts/make_dsp_fixtures.py.
  • P0 widgetsw-raw-scroller, w-spot-the-artifact, w-filter-sandbox with READMEs and lab pages.
  • Data pipelinedata/scripts/ extraction scripts for the three widgets, data/manifest.json with provenance and checksums, scrubbed catalog snapshot (data/catalog/) and generated dataset pages.
  • Content — draft lessons L0.4, L0.5, L1.5; frontmatter stubs for every other lesson, widget, pitfall, dataset, path and glossary ID in the spec; reading-list seed (verified: false throughout).
  • Notebooksnb-0-4-browse, nb-0-5-artifact-atlas, nb-1-5-filters, shared helpers, pinned requirements, notebooks/index.json.
  • Validationscripts/check-content.ts implementing spec §5.3, the §10.7 snippet gate and the §10.10 private-path grep, with --warn-only, --strict-drafts and --verbose.
  • CI.github/workflows/build.yml (build, test, validate, upload dist), notebooks.yml (weekly execution with nbclient, 15-minute limit per notebook), deploy.yml (documented placeholder pending spec §13 items 1 and 3 and the host choice).
  • Docs — repository README.md, site/README.md, pipelines/README.md (Phase 2 skeleton), this file.

Known gaps (Phase 0 exit criteria not yet met)

  • Answer keys derived from data, not yet confirmed by the author: L0.4 eyes-closed onset (75 s ± 2 s, from the LEMON segment’s marker; the algorithmic detector says 74 s) and the four L1.5 P3 amplitudes (from nb-1-5-filters on ds-brain-invaders subject 1, Pz, 300–500 ms).

  • The artifact bank lacks ecg, bridging and drowsiness items (not derivable from the Phase 0 data); 10 of its 67 items are labelled synthetic derivatives (electrode pop, flat channel).

  • Only headless screenshots and SSR/unit tests exercised the widgets; a hands-on browser pass is still due.

  • Labels on every drill item and scroller annotation are algorithmic; the author’s review to expert is pending (§4.5).

  • TODO(confirm) markers remain in the three P0 lessons and in generated dataset pages where the catalog is silent.

  • Colab links and deploy are blocked on spec §13 items 1 and 3.