Build a class with several walks
This page builds a class kit with several walks, from the history of the project to just present. A walk
is one path through the history, with its own notes and slides. Several walks describes each
part in detail. Here the parts come together, in the order that you make them.
The repository timewalk-test, at github.com/rahuldave/timewalk-test, is a complete example. It has nine
walks. Three are narratives, on every step, on some steps and on tags of its own. Two are tutorials, and
four are small walks taken out of the big tutorial. Two small walks, testing and finish, have notes of their own. Clone it and run just present to see each feature.
Its README is a tour. What a narrative and a tutorial need gives the same demands as checklists.

The parts of a class
A class with several walks has these parts:
| Part | Where | What it holds |
|---|---|---|
| The project | Its own repository | The history: tagged steps, and for a tutorial the small commits between them |
| The table of contents | toc.toml in the kit |
The list of walks, the default first |
| The default walk | notes.md and slides/ in the kit |
The notes and the slides of the first walk |
| Every other walk | walks/NAME/ in the kit |
Its notes.md, and slides/slides.toml with its slide files |
| The recipes | justfile in the kit |
just present, just check and just pdf |
The kit is the folder of Making the steps, with a table of contents and a folder for each walk:
my-class/
├── toc.toml
├── notes.md the default walk
├── slides/slides.toml its slides
├── walks/
│ ├── tests/
│ │ ├── notes.md
│ │ └── slides/slides.toml
│ └── tutorial/
│ ├── notes.md
│ └── slides/slides.toml
├── justfile
├── repo/ ignored: a clone of the project
└── worktree/ ignored: the replay copyMake the project a uv project
Make the project a uv project from its first commit. The first commit holds pyproject.toml, with
[project] and a build system, .python-version and uv.lock. Then every step has its own locked
environment. Run every command of the notes and the recipes through uv run or a just recipe. Never use the
Python of the system.
The recipe just setup
The justfile of the project has a recipe setup. The section of every step in the notes starts with
$ just setup. A learner runs it first, at every step, in the replay copy.
just setup makes two things, and nothing more:
- The environment, for example with
uv sync. - The artifacts. An artifact is a file that a step needs and that git does not hold, for example a
corpus or a report.
just setupmakes each artifact if it is missing, in a folder that git ignores.
just setup never touches git. It makes no commit, branch or tag, and it changes no config or hook. Run
it twice, and the second run changes nothing. A learner can jump straight to a later step, so just setup
at that step must make every artifact that the step needs.
# Make the environment of this step: the first command of every step
setup:
uv sync
[ -f data/corpus.txt ] || uv run python scripts/corpus.py data/corpus.txtMake the history
Make the steps as for one walk, with one annotated tag for each step, on one branch. See Making the steps. Each kind of walk then needs a little more:
- A walk on some of the steps needs nothing more. Name its steps in
toc.toml. - A walk on one part, with steps of its own, needs a branch with tags of its own, for example
hooks-00tohooks-02. Start the branch at the step where the part begins. - A small walk out of a tutorial needs nothing more. Name its steps and moves with
select. See Take a small walk out of a big one. - A tutorial needs small commits between the tags. Each small commit is a move.
Commits for a tutorial
Make each move a commit of its own. timewalk names a move by its place between the tags, so the third
commit after step-01 is step-02.3. Its subject needs no name, and can be empty. The title of the move
comes from its ### heading in the notes. With jj, jj new starts the next move. The last commit of the step carries the tag:
git commit -m "step-02.1: a test of counting"
git commit -m "step-02.2: a test of an empty text"
git commit -m "step-02: case does not matter, and the tests pass"
git tag -a step-02 -m "Tests
Two tests, one failing, and the fix."A step of one commit has no moves, and works as in a narrative.
Moves with jj
jj, also called Jujutsu, is a version control tool that can share one repository with git. jj calls such a repository colocated. timewalk reads a colocated repository as it reads any git repository.
In jj, give each move a change of its own, with its message, before you write it. jj new -m makes a
new empty change with a message, on top of the change before. The files that you then edit go into that
change. The first move of a step uses jj describe -m, because the working copy is already an empty change:
jj describe -m "step-02.1: a test of counting"
# write tests/test_count.py
jj new -m "step-02.2: a test of an empty text"
# write tests/test_empty.py
jj new -m "step-02: case does not matter, and the tests pass"
# change src/tally/__init__.py
jj newThe last change of the step keeps the subject of the step. The last jj new closes that change, so that
its commit does not change again, and starts the empty change for the next step.
jj does not make annotated tags. So tag the step with git, on the commit of the change before the working
copy, @-:
git tag -a step-02 "$(jj log --no-graph -r @- -T commit_id)" -m "Tests
Two tests, one failing, and the fix."Do not tag the working copy itself, @. jj then puts an empty change after it, and that change becomes
a move of the next step.
If you change an earlier move with jj, jj rewrites the later changes, and they get new commits. Then point the tags at the new commits again.
Split a step that is one commit
If the history has one commit for a step, split it into moves. First take each group of files from the tag, one commit at a time:
git switch --detach step-01
git checkout step-02 -- tests/test_count.py
git commit -m "step-02.1: a test of counting"
git checkout step-02 -- tests/test_empty.py
git commit -m "step-02.2: a test of an empty text"
git restore --source step-02 --staged --worktree .
git commit -m "step-02: case does not matter, and the tests pass"The last command takes the rest of the step, and removes the files that the step removed. So the last commit has the same files as the old step-02. Then do these steps:
- Rebase the later steps on the new commit.
- Point the tags at the new commits again, as in How to change an earlier step.
- Restart timewalk, which reads the tags when it starts.
Write the table of contents
List the walks in toc.toml. The first walk is the default:
[[walk]]
id = "narrative"
title = "The project, step by step"
description = "How the project was built, one tagged step at a time."
notes = "notes.md"
slides = "slides/slides.toml"
[[walk]]
id = "tests"
title = "Only the tests"
description = "Three steps of the project: the code, its tests, and a fix."
folder = "walks/tests"
steps = ["step-01", "step-02", "step-05"]
[[walk]]
id = "hooks"
title = "A git hook, one move at a time"
description = "A check that git runs before each commit, shown one commit at a time."
kind = "tutorial"
folder = "walks/hooks"
tags = "hooks-*"Give each walk a description of a sentence or two. The status band shows it at the first step of the
walk. The table of contents lists every key.
Write the notes and the slides
Write the notes of each walk as for one walk, with one ## step-name section for each step. Start each
section with $ just setup. See Notes.
In a tutorial, give each move a ### step-NN.k section, in the order of the commits. Let
timewalk-notes draft the sections that are missing, and then finish each one by hand:
timewalk-notes repo --toc toc.toml --walk tutorial # print the drafts
timewalk-notes repo --toc toc.toml --walk tutorial --write # write them into the notesA draft has the title of the move, and comments that ask for each part in the order that a reader needs
them. First comes the problem and the change, for both modes. A block ::: do follows, for each change
by hand and where it goes, and a block ::: watch, for what the change does. A comment for the check comes next. Last
comes a line files:, with one item for each file of the move, uv.lock too, with no words.
Replace each comment. Write for a reader who knows the program, and not timewalk. See
Write for a reader who has never heard of timewalk.
Give each item its words, and quote the main line of the change with show:.
files:
- diff `src/tally/__init__.py`: one word, `lower()`, answers the failing test.
show: `for word in text.lower().split():`An item is one file to look at, with words. See The items under files and Tutorial walks.
Write a manifest for each walk, with an entry for each step, even of one slide. A manifest can use the slide files of another walk, with a path from its own folder:
[slides]
step-01 = ["../../../slides/talk.md#2", "tests.md#1"]
"step-02.1" = ["moves.md#1"]Keep every slide file in a slides folder of a walk. The page serves slides from those folders only. See Slides for moves.
Add the recipes
These recipes run timewalk from GitHub on the clone of the project. just present checks the walks first,
and stops if a check finds an error:
timewalk := "uvx --from git+https://github.com/rahuldave/timewalk@v1.4.6"
# Open the walks: just present, or just present tutorial for another walk
present walk="" *args:
{{ timewalk }} timewalk-check repo --toc toc.toml
{{ timewalk }} timewalk repo --toc toc.toml {{ if walk == "" { "" } else { "--walk " + walk } }} {{ args }}
# Check the notes and slides of every walk against the steps
check:
{{ timewalk }} timewalk-check repo --toc toc.toml
# Make the PDF of one walk's slides: just pdf tests
pdf walk="":
{{ timewalk }} timewalk-pdf --toc toc.toml {{ if walk == "" { "" } else { "--walk " + walk } }} -o build/slides.pdfTo try a change to timewalk before it is released, set timewalk to
uvx --from ~/Projects/timewalk, a clone of timewalk on your machine. uv builds timewalk again when the clone has a new commit, or when a file in its
src folder changes. You do not need to clear uv’s cache.
Check and present
- Run
just check. It names each problem, for example a step with no slides, a move with no notes, or a slide that does not exist. The checks lists them. - For a tutorial, run
timewalk-noteswithout--write. It says if a move has no section. - Run the commands of each step and each move, at the commit where a learner meets them. See Run the commands of a walk.
- Run
just present. Open the Room window for the class. Change the walk with the menu in the step bar. - Before the class, go through every walk, step and move in two windows, in do mode and in watch mode. See A check the evening before.