Overview & Prerequisites

This page is the setup gate. It tells you what you need on your machine, what you should already know, how the workspace is bootstrapped, and how to read the rest of the curriculum. Do not start Section 1 until every command on this page runs on your machine.


Platform Scope

You will develop on Linux or macOS, using Unix PTYs. That is a hard requirement for Sections 1–4.

PlatformStatusNotes
Linux (glibc or musl)Primary/dev/ptmx + devpts; epoll; signalfd; strace; /proc. Everything in this book works.
macOS (Apple Silicon or Intel)Primary/dev/ptmx + /dev/ttysNNN; kqueue; no /proc; dtruss is hobbled by SIP. Every platform difference is called out where it occurs.
FreeBSD / OpenBSDWorks, untested hereClose enough to macOS that the differences are mechanical.
WindowsNot requiredConPTY is architecturally different and is covered as a comparison in Linux, macOS & ConPTY. Do not attempt Windows support before Milestone 13.

Note: If you are on Windows, use WSL2 for the implementation work. WSL2 is a real Linux kernel with real devpts, so every lab works. Native Windows work belongs after the core is stable.

Operating-system-specific behavior is marked throughout with an explicit Linux: / macOS: prefix. When you hit one, run the command on your own machine rather than trusting the text — behavior drifts between kernel versions.


What You Should Already Know

You do not need terminal knowledge. You do need the following, and the curriculum will not re-teach them:

Rust. Ownership and borrowing, enum with payloads and match, traits and generics, Result and error handling, modules and Cargo workspaces, iterators, Vec/slices/indexing, and enough unsafe literacy to read an FFI call and know why it needs a safety comment.

Unix systems fundamentals. Processes and PIDs, fork/exec/wait, file descriptors and what dup2 does, blocking vs. non-blocking I/O, signals as an asynchronous delivery mechanism, and the idea that the kernel owns objects your process refers to by integer.

Tooling. git, a debugger you can actually drive (lldb or gdb), and comfort at a shell.

Run this self-check. If you cannot explain each line's output in one sentence, spend an evening on that topic first:

# 1. What are these numbers, and why do three of them usually match?
ps -o pid,ppid,pgid,sid,tpgid,stat,tty,comm -p $$

# 2. Why does the second command print nothing, and what is fd 1 connected to in each case?
ls -l /proc/self/fd 2>/dev/null || ls -l /dev/fd     # Linux || macOS
sh -c 'ls -l /dev/fd' | cat

# 3. Why does this print a different number of lines than plain `ls`?
ls | cat

# 4. What is the terminal doing differently in these two cases?
stty -a | head -5
stty raw -echo; stty -a | head -5; stty sane

Required Tools

Rust toolchain      rustup, stable ≥ 1.75   (edition 2021)
A C compiler        for crates that build native code (cc / clang)
git                 2.x
An editor with rust-analyzer

Inspection tools you will use constantly:
  stty              read/write termios from the shell
  ps                process, session, pgroup, and foreground-pgroup inspection
  tty               print the terminal device of the current shell
  lsof              which process holds which fd  (macOS: install via brew)
  hexdump / xxd     look at bytes
  script            record a terminal session (the reference implementation of what you will build)
  Linux only:  strace, /proc, ltrace
  macOS only:  dtruss (needs sudo + SIP considerations), fs_usage, sample

Programs used as test subjects (install what is missing):
  bash, zsh, /bin/sh
  vim, less, top, htop
  python3
  tmux            (as a reference multiplexer to compare against — NOT as a dependency)

Verify:

rustc --version && cargo --version
stty -a | head -3
tty                       # e.g. /dev/pts/3   (Linux)  or  /dev/ttys004  (macOS)
ps -o pid,pgid,sid,tpgid,tty,comm -p $$
command -v vim less top python3 tmux script
# Linux:
ls -l /dev/ptmx && ls /dev/pts | head
# macOS:
ls -l /dev/ptmx && ls /dev/ttys* | head

Warning: On macOS, lsof and dtruss are more restricted than their Linux counterparts. Several experiments in Lab 4 have a macOS variant. Where a Linux-only tool appears with no alternative, the lab says so explicitly rather than pretending otherwise.


Bootstrapping the Workspace

There is a starter workspace in this repository at book/projects/mini-terminal/. Copy it out and work in your own repo:

# from the root of this repository:
cp -r book/projects/mini-terminal ~/mini-terminal
cd ~/mini-terminal && git init && git add -A && git commit -m "starter workspace"

cargo build                  # everything COMPILES from the start
cargo run -p raw-inspector   # the one COMPLETE example — press keys, Ctrl+Q quits
cargo test                   # MOST OF THESE FAIL. That is the design.
./scripts/boundary-audit.sh  # the eight Section 5 checks, from day one

It is scaffolding, not a solution. Function bodies are todo!("Lab N: …"), and the tests that exercise them are written and failing on purpose — the tests are the specification. Read the failing test, read the lab, implement, re-run. A green suite means that milestone is done.

CrateState
raw-inspectorComplete, a reference to read (Lab 1)
terminal-ptySkeleton + tests (Labs 2–3)
terminal-protocolSkeleton + tests (Lab 6)
terminal-coreSkeleton + tests (Lab 7 onward)
terminal-debuggerHex dump complete; record/replay skeleton (Lab 5)
scripts/, CI, golden-test layoutComplete

Crates for Milestones 7–13 (terminal-input, terminal-render-model, terminal-gui, terminal-mux, terminal-cli) are deliberately absent — the [workspace] members list has them commented out with the milestone that introduces each. Create them when a lab tells you to, because an empty crate is a lie about what you understand.

Prefer to start from nothing? Do that instead; the labs give every line. The workspace exists so you are not retyping boilerplate, not so you can skip the thinking.

The full design — every crate's responsibility, what it must not know about, its public API, its internal state, its dependencies, its test strategy, and whether it is platform-independent — is in Workspace Design. Read that before you create the second crate.

Commit after every milestone. The git history is the record of what you understood and when; the capstone asks you to walk it.


The Reading Order

flowchart TD
    A[Introduction] --> HG[Hitchhiker's Guide]
    HG --> B[Overview & Prerequisites]
    B --> W[The Warm-Up]
    W --> C[Mental Model — Milestone 0]
    C --> D[Workspace Design]
    C --> E[Roadmap: 15 milestones]
    C --> F[Teaching Method]
    F --> G[Section 1: PTY Laboratory<br/>M1–M3]
    G --> H[Section 2: Terminal Core<br/>M4–M6]
    H --> I[Section 3: Graphical Frontend<br/>M7–M8]
    H --> J[Section 4: Multiplexer<br/>M9–M12]
    I --> J
    J --> K[Section 5: Reusable Architecture<br/>M13]
    K --> L[Advanced Compatibility<br/>M14]
    L --> M[Capstone]
    G -.reference.-> N[Observability: terminal-debugger]
    H -.reference.-> N
    H -.reference.-> O[Testing Strategy]
    J -.reference.-> O

Sections 3 and 4 are genuinely independent of each other — both depend only on Section 2. If you care more about multiplexers than about fonts, do Section 4 first. Everything else is strictly sequential.


The Learning Priorities, Restated as a Contract

The introduction lists ten learning priorities in order. Here is what each one means operationally — the artifact that proves you have it:

#PriorityProof artifact
1What happens when a terminal starts a shellYour pty-runner from Lab 2, plus your annotated syscall trace
2Emulator vs. shell vs. TTY vs. PTY vs. driver vs. muxYour written answers in answers-m0.md, re-checked at the end
3Data flow keyboard → emulator → PTY → shell → childThe full trace in Trace a Keystroke, reproduced with your own debugger output
4How escape sequences modify terminal stateYour parser's unit tests, one per sequence, showing screen-before/screen-after
5How a terminal maintains its logical screenYour Screen/Grid and its golden tests
6How output becomes rendered cells or pixelsYour CPU renderer and its damage-tracking output
7Process groups, sessions, controlling terminals, signals, job controlLab 4 experiment log, with ps output before and after each step
8How a mux hosts sessions independently of a UIA session that survives kill -9 of your client
9How a reusable library separates concernsYour cargo tree output plus the boundary defense in Section 5
10Explain every part without unexplained abstractionsThe capstone write-up

How Long This Takes

Honest estimates, assuming evenings and weekends and that you do the experiments rather than skimming them.

PartCalendar timeThe part that actually takes the time
Section 12–3 weeksJob control. Everyone underestimates job control.
Section 23–5 weeksNot the parser — the screen model. Pending wrap, scroll regions, and resize semantics.
Section 32–4 weeksFont metrics and input encoding, roughly evenly.
Section 43–4 weeksThe protocol and resize negotiation with multiple clients.
Section 51–2 weeksMostly reading and refactoring; the thinking is the work.
Capstone1–2 weeksIntegration, and the write-up.

If you finish Section 2 in a weekend you have almost certainly used a crate you were told not to use. Check your Cargo.lock.


A Note on the Five Sections

The brief for this curriculum describes "four major sections" and then lists five headings — the fifth being the libghostty-style architectural study. That is not a contradiction to paper over: Sections 1–4 are the build; Section 5 is an architectural study you perform on what you already built. It is explicitly framed as advanced material, and it is placed last because a boundary argument you have not earned is just an opinion. If you read Section 5 first, you will produce tasteful-looking crate names around code you do not understand.


Next: The Hitchhiker's Guide for why terminals are like this, then The Warm-Up to see one with your own eyes, then The Terminal Mental Model — Milestone 0, and it contains no code.