Skip to content

Repository files navigation

OxRSE core training slides

This repo conains sli.dev slideshows for our training material. Each course is a single slideshow, presentations/<name>/slides.md, with its images alongside it. Shared pieces live at the top level: addon/ (a local Slidev addon that every slideshow loads, with shared components, layouts and styles), epilogue/ (closing slides added to a slideshow in an event build) and events/ (one YAML file per training event).

Slidev build

First, install dependencies:

npm install

To preview / live edit a presentation:

npx slidev --open --entry presentations/functional/slides.md

Build with a specific training event

A training event is a YAML file in events/ listing its sessions. It is the single source of truth for which decks the event uses and when: an event build builds only those decks (and the introduction), lists them on the landing page, and adds the event's own slides to each of them (see below).

For example, events/hilary-2026.yaml:

year: 2026
sessions:
  - date: "22 Jan"
    slot: "09:30"
    deck: object_oriented
  - date: "22 Jan"
    slot: "14:00"
    deck: functional
  - date: "29 Jan"
    slot: "09:30"
    deck: version_control
  ...
  - date: "19 Feb"
    slot: "14:00"
    topic: Drop-in session

Each session's deck is the name of its directory in presentations/. Its topic, shown on the orientation slide, defaults to the deck's title as the landing page shows it (title in presentations/index-metadata.yaml, else the deck's own title). Set topic to show something else, or on its own for a session without slides, such as a drop-in. The build fails if a session names a deck that does not exist, or has neither a deck nor a topic.

You can build with:

TRAINING_EVENT='hilary-2026' npx slidev --open --entry presentations/functional/slides.md

The deploy workflow reads TRAINING_EVENT from the repository variable of the same name (Settings > Secrets and variables > Actions > Variables). The two repositories that deploy these slides differ only in that variable:

Forks do not inherit repository variables, so each repository keeps its own setting when the fork is synced. Moving to a new event is a change to the fork's variable, not a commit.

Write each date as a day and a three-letter month ("29 Sep"), and each slot as the session's start time ("09:30"). Quote both, so YAML reads them as text. There is no rigorous error checking, but the epilogue walkthroughs fall back to showing the date as written if either is in another format.

The event YAML file may also set feedback_form to use a different feedback form for that event:

year: 2026
feedback_form: jmJq6KWfMA
sessions:
  ...

This is either a Microsoft Forms ID, for a form at https://forms.cloud.microsoft/e/<ID>, or a full URL starting with http:// or https:// for a form hosted anywhere else. The feedback slide in the epilogue draws a QR code for it using the FeedbackQr component. Without feedback_form, it shows our normal feedback form.

The event YAML file may also set the event's name and enrolment_key on train.rse.ox.ac.uk:

year: 2026
name: "[TT26] Fundamentals of Software Engineering for Research"
enrolment_key: example-key
sessions:
  ...

These appear in the epilogue's animated walkthroughs of train.rse.ox.ac.uk (LoginDemo, EnrolDemo, MaterialDemo, ExerciseDemo and CommentDemo, played by SiteDemo in addon/components/), which show students how to sign in, enrol on the event and use the course material. Without them, the walkthroughs show "Your event" and enrolment-key. The enrolment key is shown on the published slides of an event build.

Each session may also set a background colour for its row on the orientation slide, for example to shade alternate days:

  - date: "29 Sep"
    slot: "09:30"
    deck: object_oriented
    background: "#eef2f6"

Quote the colour, since # starts a YAML comment. The orientation slide uses the orientation layout from addon/layouts/, which replaces the theme's layout of the same name and also shows each session's weekday and start time.

A deck that is not a timetabled session, such as the course introduction, can set date: first-session in its headmatter, so that its cover shows the event's first date in an event build rather than the date it was built.

Slides added in an event build

Decks do not contain any event slides themselves. In an event build, each deck that is one of the event's sessions, or has date: first-session, gets these from addon/setup/preparser.ts:

  • after the cover, the orientation slide: the event's timetable, with the deck's own session outlined;
  • at the end, a questions slide (the questions layout from addon/layouts/), then the feedback slide (epilogue/feedback.md), then the animated walkthroughs of train.rse.ox.ac.uk (epilogue/train-rse.md).

A deck leaves out the last two with these in its headmatter, e.g. when it has no course material on train.rse.ox.ac.uk to work through:

feedback-slide: false
train-rse-walkthrough: false

The generic build adds none of these. Any other slide can be shown only in an event build by giving it training-event-only: true in its frontmatter.

Landing page

The landing page, built by scripts/build-index.mjs, lists every course with a deck in presentations/. In an event build it lists only the decks named by the event's sessions, in timetable order, after any deck with date: first-session (the course introduction). Each card then shows its session's date and start time, and a short script highlights the session in progress (in Oxford time, from an hour before it starts) and scrolls it into view. To preview another moment, append ?now= to the address, e.g. ?now=2026-09-29T13:30.

Fonts

The decks use Noto Sans and JetBrains Mono, bundled from the @fontsource-variable packages and imported in addon/styles/index.css. Each deck's headmatter selects them and sets provider: none, so no fonts are loaded from Google Fonts:

fonts:
  sans: Noto Sans Variable
  mono: JetBrains Mono Variable
  provider: none

Build Error

If you encounter an error similar to this:

var shouldDebug = hasLocalStorage && /*#__PURE__*/localStorage.getItem("DEBUG") || hasProcess && process.env.DEBUG;
                                                               ^

TypeError: localStorage.getItem is not a function
    at Object.<anonymous> (node_modules/@typescript/vfs/dist/vfs.cjs.development.js:25:64)
    at Module._compile (node:internal/modules/cjs/loader:1809:14)
    at Module._extensions..js (node:internal/modules/cjs/loader:1940:10)
    at Module.load (node:internal/modules/cjs/loader:1530:32)
    at Module._load (node:internal/modules/cjs/loader:1332:12)
    at wrapModuleLoad (node:internal/modules/cjs/loader:255:19)
    at Module.require (node:internal/modules/cjs/loader:1553:12)
    at require (node:internal/modules/helpers:152:16)
    at Object.<anonymous> (node_modules/@typescript/vfs/dist/index.js:7:20)
    at Module._compile (node:internal/modules/cjs/loader:1809:14)

You may need to set NODE_OPTIONS='--no-webstorage' during the build. This is because of an incompatability with Node >=25. See this Vue Devtools issue comment and the TypeScript Website pull request.

The deploy workflow uses the Node version in .nvmrc (currently 24), which avoids this. Use the same version locally, e.g. with nvm use.

Markdown style

CI lints every Markdown file, slides included, with markdownlint. The rules are in .markdownlint.json. The ones you are most likely to trip over:

  • Leave a blank line before and after every list, fenced code block and table.
  • Give every fenced code block a language, e.g. python, bash or console. Use text for output or data that should not be highlighted.
  • Write italics as _italic_ and bold as **bold**.
  • Start and end every table row with a pipe. There is no need to line the columns up.
  • Leave no trailing spaces.

Some rules are switched off because they clash with how slides are written: slides may repeat a title, skip heading levels, end a title with punctuation, show a $ prompt before a command, and use bare URLs.

Checking

Run markdownlint from the repository root before you push:

npx markdownlint-cli2 "**/*.md" "!node_modules"

Add --fix to repair most problems automatically, then review the diff before committing. Run it from the root, or it will not find .markdownlint.json.

Typography

Slide sources should be plain ASCII. Slidev enables markdown-it's typographer, so ASCII already renders as proper typography and there is no need to paste the real character:

Type this Renders as
--- em dash
-- en dash
... ellipsis
+- plus-minus sign
(c), (tm), (r) copyright, trademark, registered

Slidev also overrides markdown-it's quotes so that quotes stay straight. A pasted curly apostrophe therefore renders differently from every ASCII apostrophe around it.

These characters almost always arrive by copy-paste from a PDF, Word or a browser. Avoid the following, and type the ASCII equivalent instead.

Quotes

Avoid Codepoint Type instead
Left single quotation mark U+2018 '
Right single quotation mark U+2019 '
Left double quotation mark U+201C "
Right double quotation mark U+201D "
Prime U+2032 '
Double prime U+2033 "

Dashes and punctuation

Avoid Codepoint Type instead
En dash U+2013 --
Em dash U+2014 ---
Horizontal ellipsis U+2026 ...
Minus sign U+2212 -
Hyphen U+2010 -
Non-breaking hyphen U+2011 -
Modifier letter circumflex accent U+02C6 ^

Note that the minus sign and the two hyphens map to a single -, not to --. They are not dashes, and -- would render as an en dash.

Invisible characters

Avoid Codepoint Type instead
No-break space U+00A0 a normal space
Zero width space U+200B delete it
Zero width non-joiner U+200C delete it
Soft hyphen U+00AD delete it
Byte order mark U+FEFF delete it

What is not flagged

Characters that carry meaning are deliberately left alone: accented letters in names, Greek letters, maths symbols, box-drawing characters in tree output, and currency symbols. Only look-alike punctuation with an unambiguous ASCII equivalent is listed above.

Take particular care inside code spans and fenced blocks. The typographer skips them, so a pasted en dash in a command such as snakemake --dag stays exactly as pasted and will fail for anyone who copies it.

Checking

CI checks every pull request, but only the lines that the pull request adds, so existing text never fails the build. To check locally:

python3 scripts/check-typography.py --all          # every tracked Markdown file
python3 scripts/check-typography.py FILE [FILE...] # specific files

A repo-wide scan can also be run on demand from the Actions tab via the typography workflow.

The check only ever reports; it never rewrites files, because several of these characters are legitimate elsewhere and each replacement wants a human eye.

This section names characters rather than showing them, so that it passes its own check. Please keep it that way.

Deployment

When the repository is updated, a Github action will use ./build_all.sh to build all presentations (or, with TRAINING_EVENT set, the event's) into a dist/ folder and deploy to github pages.

The generated course landing page at dist/index.html now uses a custom template that pulls presentation titles from each slides.md, reuses the OxRSE logo, and can optionally surface the current training schedule when TRAINING_EVENT is set.

Optional analytics

The landing page includes optional Plausible boilerplate. To enable it during the build, set:

  • PLAUSIBLE_DOMAIN to your deployed site domain
  • PLAUSIBLE_SRC if you need a non-default Plausible script URL
  • PLAUSIBLE_API if you are proxying or self-hosting the events endpoint

Releases

Packages

Used by

Contributors

Languages