Firstly, please see and follow our Code of Conduct here.
- Contributing Content to the HNN Textbook
- Building the website locally
- (Advanced) Maintainer workflow description (v3.1)
This guide explains how to add or edit content in the HNN Textbook. It covers three things: Markdown files, Jupyter notebooks, and local images.
First, Go to https://github.com/jonescompneurolab/textbook , click "Fork" in the upper right, then click "Create fork". (You only need to do this once.)
Next, before you do anything else, you need to set up your fork of textbook to
publish to Github Pages. You only need to do this once, and it's easy.
-
Go to the page for your fork of the
textbookrepository. -
Click the dropdown that says
mainon the left. -
If you don't see an entry for a branch named
gh-pages, then you need to create one. You can do this easily by typing ingh-pagesin the textbox, then clicking where it says "Create branch gh-pages from main". The branch should now be created. -
Go to the "Settings" tab of your fork's repository.
-
On the left-hand side, in the "Code and automation" section, click the section named "Pages".
-
If you see something at the top like "Your site is live at (URL)", then you're done, and you don't need to do step 7.
-
If you don't see that at the top, then under the "Build and deployment" section, do the following:
a. Make sure the "Source" option says "Deploy from a branch" (it probably will by default). b. Under "Branch", click the dropdown and change it to
gh-pages. c. Hit the "Save" button. d. You should done now.
The rest of Section 1 here is guidance for how to edit and organize the specific pages you want to change or add.
All content lives inside the content/ directory. It is organized into numbered
sections (directories) and numbered pages (files):
content/
├── 00_preface.md ← a top-level page (no section)
├── 01_getting_started/ ← a section
│ ├── README.md ← section title lives here
│ ├── 01_challenge.md ← first page in this section
│ ├── 02_template_model.md ← second page
│ └── 03_installation.md ← third page
├── 05_erps/
│ ├── README.md
│ ├── 01_erps_in_gui.md
│ ├── 02_hnn_core.md
│ ├── images/ ← images used by pages in this section
│ │ ├── erp_fig_01.png
│ │ └── ...
│ └── plot_simulate_evoked.ipynb ← a Jupyter notebook
└── ...
Key rules:
- The numeric prefix on directories and files (e.g.
01_,02_) controls the order pages appear in the sidebar navigation. Lower numbers come first. - The prefix is stripped when the site is built, so
02_template_model.mdbecomestemplate_model.html. - Each section directory must have a
README.mdthat defines the section's title (see below).
Markdown (.md) files are the main content format. Each file becomes one page
on the website.
- Pick the section directory where your page belongs (e.g.
content/01_getting_started/). - Choose a numeric prefix that places it in the right order. For example, to
add a page between
02_template_model.mdand03_installation.md, you would name your file03_new_page.mdand rename the old03_installation.mdto04_installation.md. - Create the file with the required metadata header and your content (see template below).
Every Markdown page must start with a metadata block inside an HTML comment. Here is the minimum required template:
<!--
# Title: 1.4 My New Page Title
# Updated: 2025-06-15
#
# Contributors:
# Your Name <your.email@example.com>
-->
## 1.4 My New Page Title
Your content goes here.What each metadata field means:
| Field | Purpose |
|---|---|
# Title: |
The title shown in the sidebar navigation. Required. |
# Updated: |
The date you last edited this page (YYYY-MM-DD). |
# Contributors: |
A list of people who wrote or edited this page. |
To create an entirely new section (a new "chapter" in the sidebar):
- Create a new directory under
content/with a numeric prefix, e.g.content/10_my_new_section/. - Inside it, create a
README.mdwith only the metadata block:
<!--
# Title: 10. My New Section
# Updated: 2025-06-15
#
# Contributors:
# Your Name
-->- Add your page
.mdfiles inside this new directory.
The textbook uses standard Markdown processed by Pandoc. If you are new to using Markdown, you can view Github's intro to Markdown here to get started. Here are the most common things you will use:
Headings:
## Section Heading
### Sub-headingBold and italic:
**bold text** and *italic text*Links to other textbook pages:
<!-- Link to a page in the same section -->
[Template Model](template_model.html)
<!-- Link to a page in a different section -->
[Installation](../01_getting_started/installation.html)Note: When linking to other pages inside your Markdown document, use the .html
extension (not .md) and drop the numeric prefix from the filename.
02_template_model.md is linked as template_model.html.
Pro-tip: You can insert HTML itself inside Markdown documents and it will be rendered correctly in the final output.
Citations (BibTeX):
The textbook has a bibliography file (textbook-bibliography.bib) in the
project root. To cite a reference:
This was first shown in a prior study [@jones_neural_2007].The [@key] syntax will be automatically replaced with a formatted citation
during the build. Feel free to add new references to that bibliography file.
Embedding a Jupyter notebook's output:
To embed the rendered output of a notebook into a Markdown page, use this special syntax on its own line:
[[my_notebook.ipynb]]The notebook file must be in the same section directory as the Markdown file. See the Jupyter Notebooks section below for more details.
Jupyter notebooks (.ipynb files) contain executable Python code. They are
not displayed on the website by themselves; instead, their rendered output is
embedded inside a Markdown page using the [[notebook.ipynb]] syntax.
- Create your
.ipynbfile in the appropriate section directory (the same directory as the Markdown page that will embed it). - Write your notebook with a mix of Markdown cells (for explanations) and code cells (for executable code).
- Create a companion Markdown page (or edit an existing one) that embeds the notebook. A minimal companion page looks like this:
<!--
# Title: 4.2 API Tutorial of ERPs Simulation
# Updated: 2025-01-29
#
# Contributors:
# Your Name
-->
[[plot_simulate_evoked.ipynb]]The [[name.ipynb]] line is all that is needed — the build system will execute the
notebook and insert its full rendered output into the page.
- Notebook filenames do not need a numeric prefix.
- Give notebooks descriptive names, e.g.
plot_simulate_evoked.ipynborbatch_simulation_notebook.ipynb. - The build system tracks whether notebooks have changed and only re-executes them when needed.
- If your notebook should be skipped during certain builds (e.g. it requires special dependencies or depends on code that is still in development), then let the Maintainers know. when you make your Pull Request.
Images are stored in an images/ subdirectory within the section that uses
them.
- If the section doesn't already have an
images/directory, create one:
content/05_erps/images/
- Place your image file (PNG, GIF, JPG, etc.) inside it. Use a descriptive name with a consistent prefix matching the section:
erp_fig_01.png
erp_fig_02.gif
gamma_fig_01.png
Use standard Markdown image syntax with a relative path:
To center an image and control its layout, wrap it in a <div>:
<div style="display:block; width:100%; margin: 0 auto;">

</div>To add a caption below the image:
<div style="display:block; width:100%; margin: 0 auto;">

</div>
<p style="text-align:justify; display: block; margin: 0 auto; width: 90%; font-size: 1em;">
Figure 1: Description of what the figure shows.
</p>- Always use relative paths from the Markdown file to the image (e.g.
images/my_figure.png), not absolute paths. - Prefer PNG for static images and GIF for animations.
- Keep image file sizes reasonable for web loading.
When adding a new page, make sure you have:
- A metadata comment block at the top of your
.mdfile with# Title:,# Updated:, and# Contributors: - A numeric prefix on the filename that places it in the correct order
- If you created a new section directory, a
README.mdwith the section title metadata - Images placed in the section's
images/subdirectory and referenced with relative paths - Notebooks placed in the same section directory as their companion
Markdown page, embedded with
[[notebook.ipynb]] - Internal links using
.htmlextensions and filenames without numeric prefixes
This is optional.
If you want to build the final HTML files yourself so that you can inspect the output bor investigate errors, we've made this easy to do. Note that Windows is not supported. You can do this via the following:
- First, you need to install Anaconda if you haven't already. Restart any Terminal windows that you may have open after this.
- Next, ensure that
makeis installed. You can test ifmakeis installed by running the commandwhich makewhich should then return a filename. If it returns nothing, then you need to install it.- (MacOS) If you don't have
make, then you can install it via installing "Xcode Command-Line Tools", which is needed for HNN. To do this, simply run the commandxcode-select --installand select all the default options. Make sure you restart your computer after installing this! - (Linux) If you don't have
makeon Linux, then you should install whatever the "basic software building tools" package your distribution uses. As an example, on Ubuntu, this isbuild-essential. So, in the case of Ubuntu, you could install this using the following command:sudo apt install build-essential.
- (MacOS) If you don't have
- We provide two different Anaconda environments, and you can install one, the other, or both:
- You can install an environment that uses the latest "stable" (i.e. officially released) version of HNN-Core by running
make create-textbook-stable-env. This will create a new Anaconda environment namedtextbook-stable-env. - You can install an environment that uses the latest "master" branch (i.e. development) version of HNN-Core by running
make create-textbook-dev-env. This will create a new Anaconda environment namedtextbook-dev-env.
- You can install an environment that uses the latest "stable" (i.e. officially released) version of HNN-Core by running
- Once you have created and activated whichever Anaconda environment you want to use, then the way you are expected to run a build of the website is by using the command
python build.py, along with any optional arguments you may want.- (Strongly recommended) To view what the optional arguments do, run
python build.py -hto display the built-in help. Note that there are many arguments with many different values that you can pass, and the documentation is extensive. - A default run of
python build.py(by itself) will NOT re-execute any Jupyter notebooks, and is equivalent to doing a run with the following arguments:
python build.py \ --build-directory=auto \ --code-version=stable \ --execution-type=no-execution - (Strongly recommended) To view what the optional arguments do, run
This is only for active developers or maintainers managing the repository as a whole, and useless to anyone else. This was built from the discussion of proposal v3.0 here https://www.notion.so/jonescompneurolab/Austin-Proposal-v3-0-2a244dbbcce680d3892aed671bf55132 .
- Filetypes:
MD: Markdown files (.md)NB: Jupyter notebook files (.ipynb)
- People:
Author: Someone "authoring" any MD or NB files, including either creation of new ones or updating existing ones. This is expected to be both the Maintainers and also other members of the Lab, including summer students and GSoC students, on occasion.Maintainer: Someone who understands the website-building and notebook-execution code, maintains that code, and also actively maintains the content that the website is intended to publish. Maintenance includes adding files to and from the different "notebook skip" categories based on upstream changes tohnn-coreitself. They will also probably be "authoring" content too. Probably only Dylan or Austin (and maybe someone from CCV).
- Stable build terms:
stable: The current stable "release" of thehnn-corepackage, as used bypipfrom https://pypi.org/project/hnn-core/ .textbook-stable-env: A Conda environment used by the Textbook repo that includes a full installation ofhnn-core'sstableversion as defined above. (This formerly went through several different name changes).content-build: A "build" of the website using MD and NB files under thecontentdirectory to make output files (HTML pages and "notebook-output-JSON-files") that go in thecontentdirectory. Builds usingtextbook-stable-envoutput their website build to this directory by default (unless overridden by the user's arguments tobuild.py).
- Dev build terms:
master: Themaster(aka development) branch of thehnn-coresource code at https://github.com/jonescompneurolab/hnn-core as of its latest commit.custom: A custom commit of hnn-core for use in adev-build, usually from someone's fork.textbook-dev-env: A Conda environment used by the Textbook repo that includes a full installation of eitherhnn-core'smasterbranch or acustombranch. (This formerly went through several different name changes).dev-build: A "build" of the website using MD and NB files under thecontentdirectory to make output files (HTML pages and "notebook-output-JSON-files") that go in thedevdirectory. Builds usingtextbook-dev-env(including--code-versionbothmasterandcustom) output their website build to this directory by default (unless overridden by the user's arguments tobuild.py).- On Pull Requests, the website build in
devwill be generated automatically into thedevdirectory. The build itself may (dependency on recency) be directly viewable by all in the browser by accessing thedevvariant of the HTML files hosted by Github Pages for the corresponding fork. - When a Pull Request is merged into
main, nodevfolder will be included in the merge commit or rebase, and nodevbuild will be built in the deployment action.
- On Pull Requests, the website build in
- Github Actions:
deploy.yml: This is the single Action used for all deployment. It runs on every push to any branch in both the upstream repository and forks. It uses conditional steps (based ongithub.repository) to handle the two scenarios:- For both upstream and forks: Creates
textbook-stable-envand performs astablebuild (intocontent) using--execution-type updated-unskipped-notebooks. For the upstreammainbranch this build fails on error; for forks it continues on error. - Upstream-only (
jonescompneurolab/textbookmainbranch): Deploys the built content to the official GitHub Pages website. - Fork-only: Additionally creates
textbook-dev-envand performs amasterbuild intodev/, then deploys both the stablecontentanddevoutput to the fork's GitHub Pages websites.
- For both upstream and forks: Creates
If an Author wants to add new or change existing MD or NB content, then they make their changes and push a Pull Request to textbook from their fork. Simple.
- The ONLY exception is if the Author wants to use a NB that requires very new code changes to
hnn-corethat are not yet merged intomaster(for example, if their NB relies on code that is currently only present in a Pull Request tohnn-core). In this case, the Author should explicitly tell the Maintainers that this is the case.
Due to the way we use Github Actions, for individual PRs, both a stable and master
build should be automatically built for each fork-branch that makes a PR. However, these
builds will NOT appear on the actual page for the Pull Request. Instead, you must go to
the actual fork's "Actions" tab to view the build progress (example:
https://github.com/asoplata/textbook/actions ). This is currently required in order to
run the builds "from" the fork directly, so that they can be published on that same
fork's Github Pages.
Maintainers are dealing with up to four possible situations here:
Ideally, the only way that main branch is changed (except in cases of emergency) is via the the "official deployment", which is conducted by deploy.yml after a Pull Request is merged. The below describes effects of the deploy.yml Action.
- How it runs:
- Execution: always set to
--execution-type updated-unskipped-notebooks. - Version:
--code-versionis always using the default, which isstable. - Env:
textbook-stable-env
- Execution: always set to
- How it affects files/folders:
- This produces a deployment commit onto the
gh-pagesbranch which is what actually gets published to the final website. - This only needs the
contentand various asset files, builds only tocontent, and should not produce anydevfiles/folders. - This should have Github Action step to specifically add any existing
devcontent to the.gitignoresuch that any leftoverdevcontent from a Pull Request is excluded.
- This produces a deployment commit onto the
- Failure modes:
- Ideally, this should not have any, and hopefully never fail.
-
B.1. For Fork-PRs, the
deploy.ymlAction is run. The first phase of this is the Action performing astable-style build, identical to whatdeploy.ymldoes on themainbranch.-
How it runs:
- Execution:
--execution-type updated-unskipped-notebooks, same as before - Version:
--code-version stable, same as before - Env:
textbook-stable-env, same as before
- Execution:
-
How it affects files/folders:
- This only needs the
contentand various asset files. It will change.gitignoreso that it can track HTML files, enabling future Action-produced HTML output to be added to a future commit in thegh-pagesbranch. It will then produce output HTML pages and notebook-JSON-output files incontent. It will then create a commit with all the output on thegh-pagesbranch. From the Fork-PR, this will be deployed to<username>.github.io/textbook/content/preface.html(during the final "Deploy to GitHub Pages (Fork)" step ofdeploy.yml).- Note: Only the most recent
stablebuild of the fork will be deployed to<username>.github.io/textbook/content/preface.html, such as https://asoplata.github.io/textbook/content/preface.html . In other words, if a user has two PRs open, then their fork-specific deployment will only reflect the PR that had the most recent successful build (this will need to be communicated to Authors if they're doing multiple PRs).
- Note: Only the most recent
- This only needs the
-
Failure modes:
- B.1.1: If a new or updated notebook from the PR breaks this
stablebuild, then the Maintainers respond accordingly:- B.1.1.1: We first assume it's a bug in the notebook itself. Either we or the Author fix the bug in the PR, and the
stablebuild is re-run automatically (thus using the build as a "test"). - B.1.1.2: If it's a new notebook and relies on code that is only available in
masterbut notstable, then we add the notebook to theskip_if_stablecategory. - B.1.1.3: If it's an existing notebook and the code changes rely on code that is only available in
masterbut notstable, then we do the following:- Retain the prior (
stable-compatible version) version of the notebook (e.g.<name>.ipynb) from before the PR. - Rename the version of the changed notebook to
master_only_<name>.ipynb. It will be stored and accessible online, but not "published" and displayed in a webpage. - Add
master_only_<name>.ipynbtoskip_if_stable.
- Retain the prior (
- B.1.1.1: We first assume it's a bug in the notebook itself. Either we or the Author fix the bug in the PR, and the
- B.1.2: If the problem comes from a notebook that is not changed in the PR, then the Maintainers know that something has broken upstream. This means there is a new bug between an existing notebook and
stable. (Ideally this should never happen, but it should be fixed in a new, different PR.)
- B.1.1: If a new or updated notebook from the PR breaks this
-
-
B.2. For Fork-PRs, next is the second phase of
deploy.yml. This is when the Action performs amasterbuild intodev, independent of the output of thestablebuild in the first phase.- How it runs:
- Execution:
--execution-type updated-unskipped-notebooks, same as before - Version: UNLIKE before,
--code-versionis set tomaster - Env: UNLIKE before,
textbook-dev-env
- Execution:
- How it affects files/folders:
- This only needs the
contentand various asset files. It will change.gitignoreso that it can track HTML files and all files underdev/, enabling future Action-produced HTML output (bothcontentanddev) to be added to a future commit in thegh-pagesbranch. It will produce output HTML pages and notebook-JSON-output files indev. It will then create a commit with all the output on thegh-pagesbranch. From the Fork-PR, this will be deployed to<username>.github.io/textbook/dev/preface.html(during the final "Deploy to GitHub Pages (Fork)" step ofdeploy.yml).- Note: Only the most recent
masterbuild of the fork will be deployed to<username>.github.io/textbook/dev/preface.html, such as https://asoplata.github.io/textbook/dev/preface.html . In other words, if a user has two PRs open, then their fork-specific deployment will only reflect the PR that had the most recent successful build (this will need to be communicated to Authors if they're doing multiple PRs).
- Note: Only the most recent
- This only needs the
- Failure modes:
- B.2.1: If a new or updated notebook from the PR breaks this
masterbuild, then the Maintainers respond accordingly:- B.2.1.1: We first assume it's a bug in the notebook itself. Either we or the Author fix the bug in the PR, and the
masterbuild is re-run automatically (thus using the build as a "test"). - B.2.1.2: If it's a new or existing notebook that is compatible with only
stablebut notmaster, then the notebook is breaking due to a non-backwards-compatible change inmaster. The notebook code needs to be changed so that there is a version compatible withmaster(and therefore the upcoming release).- If there's a way to change the notebook to be compatible with both
stableandmaster, then great, do that. (I mean cleanly, NOT usingif hnn_core.__version__ < XXX). - If not, then we need to do the following:
- For the PR version of the notebook (compatible with
stablebut notmaster), add it toskip_if_dev. - Make a second version of the notebook that is compatible with
master(and does not need to be compatible withstable). - Name this second, new notebook to
master_only_<name>.ipynb. It will be stored and accessible online, but not "published" and displayed in a webpage. - Add
master_only_<name>.ipynbtoskip_if_stable.
- For the PR version of the notebook (compatible with
- If there's a way to change the notebook to be compatible with both
- B.2.1.1: We first assume it's a bug in the notebook itself. Either we or the Author fix the bug in the PR, and the
- B.2.2: If the problem comes from a notebook that is not changed in the PR, then the Maintainers know that something has broken upstream. In this case, we follow the same procedure as B.2.1.2.
- B.2.1: If a new or updated notebook from the PR breaks this
- How it runs:
-
B.3. If a Fork-PR's notebook fails BOTH
stableandmasterbuilds, then the following Failure Modes apply:- B.3.1. It's probably a bug. Fix it! (Hopefully it's a bug with the notebook, and not our Python build code…)
- B.3.2. If it's not a bug, then it's probably because the notebook depends on a "
custom" code change that is so new that it's not yet merged into master. In this case, the Maintainers should do the following:- Include
[no ci]in all future commit messages in order both not waste computation and to prevent overwriting of existingdevbuild output. - Manually remove
dev/from.gitignorein order to be able to push its output files to the PR branch. This will probably cause all CI runs to fail (which we want, in this case). - Run a local
--code-version=custom --custom-owner-commit=<username>:<abc123>build that builds intodevand uses the corresponding owner username/commit. - Locally inspect the contents of the
devfolder (if the Author rancustom) to inspect the output.- Note that because
[no ci]will exclude the publishing of the fork'sdevcontent, this PR'sdevfolder will not be viewable directly via Github pages.
- Note that because
- Rename the problem notebook to
custom_only_<name>.ipynb - Add
custom_only_<name>.ipynbto bothskip_if_stableandskip_if_dev. - DON'T FORGET to re-add
dev/to.gitignore.
- Include
Finally, whenever there's a new HNN-core release, the Maintainers need to open a PR that does the following:
- Possibly rename (aka "promote") any
custom_only_<name>.ipynbnotebooks based on:- No rename if the PR they need is still open.
- Rename to
master_only_<name>.ipynbif the commit they need is inmasterbut was NOT included in the lateststablerelease. Then, remove the notebook fromskip_if_devbut notskip_if_stable. (This probably will never happen). - Overwrite their originals at
<name>.ipynbif the commit they need was merged intomasterand is included in the lateststablerelease. Remove the notebook from bothskip_if_devandskip_if_stable.
- Rename (aka "promote") all
master_only_<name>.ipynbnotebooks to overwrite their originals at<name>.ipynb. Remove them fromskip_if_stable. - Then, do a comprehensive run of the latest
stableandmaster. This includes the important step of generating output for the CPU-heavy notebooks we usually skip. Do the following:- Manually remove any Optimization (and other CPU-heavy notebooks) from the appropriate
skipcategories. - Then run
--execution-type=all-unskipped-notebooksexecution, for bothstableandmaster. Check to see if everything executes and builds correctly. - Delete the
devoutput (we don't need it). - Retain and push your new version of the
contentdirectory. This is needed in order to properly populate the published webpages for CPU-heavy notebooks with output content. - Re-add CPU-heavy notebooks back to the appropriate
skipcategories.
- Manually remove any Optimization (and other CPU-heavy notebooks) from the appropriate