Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
16 changes: 16 additions & 0 deletions .claude/settings.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
{
"hooks": {
"PreToolUse": [
{
"matcher": "Bash",
"hooks": [
{
"type": "command",
"command": "f=\"$CLAUDE_PROJECT_DIR/scripts/workbench_guard.py\"; [ -f \"$f\" ] && python3 \"$f\" || true",
"timeout": 10
}
]
}
]
}
}
34 changes: 34 additions & 0 deletions CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Working in this repository

## The workbench

This checkout may be the workbench: the local branch `workbench`, holding every
note in progress side by side, with several sessions writing in it at once.
`git branch --show-current` says whether it is. CONTRIBUTING.md has the whole
workflow; on the workbench:

- Work in your own note's directory, `articles/<slug>/`, and its entry in
`classification.yml`. The other directories are other sessions' work.
- Commit with `pixi run note save <slug> -m "..."`, adding `--push` to publish
the branch. Anything outside a note: `pixi run note save <branch> <paths...> -m "..."`.
- Bring in a pull request with `pixi run note pull <number>`, and main with
`pixi run note sync`. `pixi run note list` says what each branch needs.
- Never `git checkout`, `switch`, `stash`, `reset` (except `-- <paths>`),
`restore`, `clean`, `rebase`, `pull` or `commit -a`. Each changes or hides
every session's files; a hook refuses them.
- Do not run `pixi run build`: it rewrites the `myst.yml` contents list that
`pixi run start` is serving.

## Anywhere else: a hub session, another machine

A checkout that is not the workbench is an ordinary one, and contributes by
branch and pull request on this repository:

- A note's branch is `note/<slug>`. If it exists on origin, work on it and push
to it; otherwise branch from `origin/main`. Its pull request is the note's.
- Work for someone else to fold into a note — figures from the runs, a draft
section — may go on a branch of its own instead, changing only
`articles/<slug>/` and the note's entry in `classification.yml`. The
workbench carries such a branch into the note with its commits intact.
- Figures ship with the script that made them, in the note's `figures/src/`.
- Push and open a pull request. Never push to `main`.
76 changes: 51 additions & 25 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,27 @@ in the repository. Keep them consistent — if the process changes, both move.

## Writing a note

Give it its own working tree. Writing and building are the same repository
doing two things at once, and they collide over generated files — `myst.yml`
holds the toc, and a production build run in another terminal takes notes at
draft and review out of the toc that `myst start` is watching. The dev server
then reports *File is not in project* for the file you have open. Nothing is
lost; it just stops updating, which is worse, because it looks like your
editor.
Write every note in one checkout, the **workbench**, and let `pixi run note`
make each note's branch from that note's own files. Notes do not collide: each
lives in `articles/<slug>/`, and the one file they share, `classification.yml`,
has an entry per note. So they can sit side by side in one editor window, with
a session or a terminal on each, and one `pixi run start` serving all of them.

```bash
pixi run worktree create particle-level-sets # branch note/particle-level-sets
cd ../underworld-technical-notes-worktrees/particle-level-sets
pixi install # its own environment, once
pixi run note init # once: this checkout becomes the workbench
pixi run new --slug particle-level-sets --title "…" --author louis
pixi run start # localhost:3000, hot reload
pixi run start # localhost:3000, hot reload, every draft
pixi run note save particle-level-sets -m "First draft" --push
gh pr create --head note/particle-level-sets
```

`note save` commits the note's directory, and its entry in `classification.yml`,
onto `note/<slug>`, on top of whatever that branch already had — and nothing
else. Other notes' unsaved edits stay where they are. Without `--push` it is a
local save point. A change outside any note — a template, a script, this file —
goes the same way with its paths named:
`pixi run note save docs/pdf-template templates/pdf -m "…"`.

Three cycles, three tools, and they do not interfere:

| | | |
Expand All @@ -31,20 +36,41 @@ Three cycles, three tools, and they do not interfere:
| **Review** | push the branch → link on the pull request | ~3 minutes |
| **Publishing** | merge to `main` | the live site |

`pixi run worktree list` shows what is in flight; `remove` takes the working
copy away and leaves the branch, since those are different decisions.

**Picking up a note that already exists** — one somebody started on the web, or
that you left a fortnight ago — is the same command. It adopts the branch
rather than creating one, and tells you which note is on it:

```bash
pixi run worktree create binder-note # adopts note/binder-note
cd ../underworld-technical-notes-worktrees/binder-note
git pull
pixi install # first time only
pixi run start
```
**A note is written in more than one place.** The machine that has the runs —
usually a hub session — makes the skeleton and the figures, and the prose is
finished somewhere else. Everything travels as a branch and a pull request on
this repository. Away from the workbench a checkout is an ordinary one: work on
`note/<slug>` and push to it, or put a contribution — figures, a section — on a
branch of its own that changes only that note, and open a pull request. Never
push to `main`.

The workbench is where the pieces meet. `pixi run note pull 58` merges pull
request 58 into it, fork or not, and `pixi run note pull note/binder-note` a
branch. Unsaved edits to the same files are committed to the workbench first so
that git merges the two; a real conflict stops before anything changes and says
how to resolve it. From then on the note saves like your own, and a save refuses
to overwrite commits somebody pushed after your last pull. A contribution whose
branch changes only one note is carried into that note's next save as a parent
commit: its author stays on its commits, and its pull request is marked merged
when the note's is.

`pixi run note sync` gathers: it merges main, merges every tracked branch that
somebody has pushed to, stops tracking branches whose pull requests have merged,
and lists open pull requests not yet on the bench. A conflict on one branch is
reported and the rest go ahead. `pixi run note list` shows what each tracked
branch needs — unsaved edits, saved but not pushed, pushed to since you pulled —
and any edited note that is on no branch yet.

**What not to do in the workbench:** `git checkout`, `switch`, `stash`, `reset`,
`restore`, `clean`, `rebase`, `pull` or `commit -a`. Each changes or hides every
session's files at once; a Claude Code hook (`scripts/workbench_guard.py`)
refuses them, and `note` does what they would have been wanted for. Nor run a
production build (`pixi run build`) there: it rewrites the contents list in
`myst.yml` that `pixi run start` is serving, and the dev server then reports
*File is not in project* for the file you have open. Nothing is lost; it just
stops updating, which is worse, because it looks like your editor. CI builds;
for a local one, use a checkout of its own that you never edit in
(`git worktree add --detach ../underworld-technical-notes-build origin/main`).

**Editing on GitHub is fine for a typo** and no worse than doing it locally.
It is a poor way to fix formatting, because you cannot see the built page —
Expand Down
5 changes: 5 additions & 0 deletions pixi.toml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,11 @@ build = { depends-on = ["build-pdf", "build-html"] }
# pixi run worktree create particle-level-sets
worktree = "python3 scripts/worktree.py"

# Or write every note in one checkout -- the workbench -- and build each note's
# branch from its own directory, without checking it out. CONTRIBUTING.md:
# pixi run note save particle-level-sets -m "First draft" --push
note = "python3 scripts/note.py"

# Push the site you already built straight to the preview host, instead of
# making a runner rebuild it. Removes everything before GitHub's own ~60s
# deployment, which is the floor and is the same either way.
Expand Down
Loading
Loading