Skip to content
Merged
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
60 changes: 60 additions & 0 deletions docs/adr/0001-derive-lesson-order-from-position.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
# 1. Derive lesson order from position

Status: Accepted

Date: 2026-07-20

## Context

A lesson's **absolute position** used to be stored as *content* in three places,
which made inserting or reordering a lesson a ~250-edit chore:

- `order:` frontmatter was a dense consecutive integer sequence (1, 2, 3, …), so
inserting a lesson shifted the `order` of every later lesson.
- `title:` embedded the ordinal (`"Lesson 20: State machines as diagrams"`).
- Cross-references named the ordinal in visible link text and prose
(`[lesson 12](#/lesson/12-arrays)`, and bare "in lesson 20" mentions) — 103
hash-links plus dozens of prose mentions across 34 files.

Sorting is driven **only** by `order` (`site/src/lib/lessons.ts`); the numeric
prefix on a folder/slug name is cosmetic and never read. So the displayed number
can be computed from a lesson's position in the sorted list instead of stored.

## Decision

Derive the displayed lesson number from position; get ordinals out of stored
content. Specifically (Option B — we did **not** rename folders or change any
`#/lesson/<slug>` href, which was the rejected Option C):

1. **Gapped `order`.** Each lesson's `order` is `position * 10` (1st → 10, 2nd →
20, … 34th → 340). Same sequence as before, but with room to insert a lesson
between neighbors (e.g. a future maze module at 191–195) without touching any
other lesson's `order`.
2. **Concept-only titles.** The `Lesson N: ` prefix is stripped from all `title:`
values (`title: "State machines as diagrams"`).
3. **Number rendered from position.** The `lessons` array is sorted by `order`;
the displayed number is its 1-based index. `lessonNumber(slug)` in
`site/src/lib/lessons.ts` is the single source of truth; the index cards,
sidebar, and lesson-view header all read it.
4. **Slug-keyed cross-references.** Cross-reference links carry no stored number.
Their visible text uses `{n}` / `{title}` tokens (e.g.
`[lesson {n}](#/lesson/09-if-statements)`) that a custom react-markdown link
renderer in `LessonView.tsx` fills in from the target's **current**
`lessonNumber` / title at render time. Bare-prose "lesson NN" mentions were
converted to the same token-link form (or rephrased to a concept reference,
e.g. "the Objects lessons").

## Consequences

- Inserting or reordering a lesson touches **only** `order:` values. No title,
prose, or cross-reference edits are needed; every displayed number and
reference re-labels itself automatically from the new positions.
- Slugs keep their numeric prefix (`12-arrays`). It is now an opaque, harmless id
that users never see — it is not the lesson's displayed number and need not
match it. A lesson can sit at any position regardless of its slug prefix.
- Lesson READMEs remain valid Markdown, but the `{n}` / `{title}` tokens and
`#/lesson/<slug>` hrefs only resolve inside the site renderer; on GitHub the
tokens render literally and the hash links do not navigate. This was already
true of the hash links and is an accepted cost of Option B.
- Anything that reads a lesson's number must derive it (`lessonNumber`), never
parse it from a title or slug.
68 changes: 68 additions & 0 deletions docs/handoffs/ordering-refactor.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Handoff: make lesson ordering cheap to change (Option B)

## Why
Today, a lesson's **absolute position** is stored as *content* in three places, so
inserting or moving a lesson is a ~250-edit chore:

- `order:` frontmatter is dense consecutive integers → inserting shifts every later lesson.
- `title:` embeds the ordinal (`"Lesson 20: State machines as diagrams"`).
- Cross-references name the ordinal in visible prose and link text
(`[lesson 12](#/lesson/12-arrays)`, and bare "in lesson 20" mentions).

Sorting is driven **only** by `order` (`site/src/lib/lessons.ts:92`); the numeric
prefix on folder names is cosmetic. The fix: **derive the displayed number from a
lesson's position in the sorted list**, and get ordinals out of stored content.

Measured footprint (lessons 1–34): 34 titles, 103 hash-links, 128 prose "lesson NN"
mentions, 34 numbered folders.

## Scope: Option B (do), Option C (do NOT)
**In scope (B):**
1. **Gapped `order`.** Rewrite each lesson's `order` to `position * 10` (current
1st → 10, 2nd → 20, … 34th → 340), preserving today's exact sequence but
leaving room to insert. (A future maze module will slot in at, e.g., 191–195
between Objects and State Machines — nothing else will need to move.)
2. **De-numbered titles.** Strip the `Lesson N: ` prefix from all 34 `title:`
values → concept only (`title: "State machines as diagrams"`).
3. **Site renders the number from position.** The `lessons` array is already
sorted by `order`; display a 1-based index as the lesson number everywhere a
number should appear — index cards (`LessonCard.tsx`), the lesson header
(`LessonView`), and the sidebar (`AppSidebar.tsx`, whose `shortTitle` regex on
line 18 becomes obsolete — titles no longer carry the prefix; prepend the
derived number instead if a number is wanted there). Numbers must stay 1..34
in the same order after the refactor — this is a no-visible-reorder change.
4. **Reorder-proof cross-references.** This is the point of the whole exercise:
after this, moving/inserting a lesson must NOT require editing any other
lesson's prose. Recommended mechanism: a custom link renderer (the site
renders lesson markdown — find the react-markdown/MD renderer in
`site/src/`) that, for any `#/lesson/<slug>` link, resolves the target
lesson's **current** derived number (and/or title) at render time via
`getLesson(slug)` + its index. Convert the 103 existing
`[lesson N](#/lesson/<slug>)` links to a canonical slug-keyed form the
renderer fills in, so the visible "lesson N" is always computed, never stored.
For the bare-prose "lesson NN" mentions that are not links, prefer turning them
into such links, or rephrase to a concept reference ("the arrays lesson"). No
stored ordinal may remain in prose.

**Out of scope (Option C — do NOT do):**
- Do **not** rename lesson folders or strip numeric prefixes from slugs.
- Do **not** change any `#/lesson/<slug>` href target. Slugs stay exactly as they
are (their numeric prefix is now a harmless opaque id users never see).

## Deliverables
- All 34 lessons updated (order + title) and cross-references converted.
- Site code renders derived numbers and resolves cross-ref numbers at render time.
- `docs/adr/0001-derive-lesson-order-from-position.md` recording the decision
(context: ordinal-as-content churn; decision: derive from position, concept-only
titles, slug-keyed cross-refs; consequence: inserts/reorders touch only `order`).
- `cd site && npm run build && npm run lint` both pass.

## Acceptance check
- Index and sidebar show lessons 1..34 in the identical order and with the same
visible numbers as before this change.
- Every cross-reference link still resolves, and its visible number matches the
target's current position (test by temporarily bumping one lesson's `order`
past a neighbor — the reference text should follow automatically, with no
content edit — then revert).
- No `title:` contains `Lesson \d+:`; no lesson prose contains a hard-coded
"lesson NN" ordinal.
4 changes: 2 additions & 2 deletions lessons/01-flowcharts/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Lesson 1: Programs are step-by-step"
title: "Programs are step-by-step"
goal: "See a program as a flowchart of blocks and watch the computer evaluate it one step at a time."
order: 1
order: 10
section: "Programming with Blocks"
---

Expand Down
6 changes: 3 additions & 3 deletions lessons/02-conditionals/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Lesson 2: Making decisions"
title: "Making decisions"
goal: "Use a condition to make a program choose between two paths."
order: 2
order: 20
section: "Programming with Blocks"
---

Expand Down Expand Up @@ -30,7 +30,7 @@ asks `a > b?` — that's `true` here (`7 > 4`) — so the program follows the
Press **▶ Run step by step**. Watch the diamond answer `true` and the program
head down the `true` arrow — while the other branch **grays out and gets
skipped**. Those blocks never run. This fork is exactly what Java writes as
`if / else` — you'll type it yourself in lesson 9.
`if / else` — you'll type it yourself in [lesson {n}](#/lesson/09-if-statements).

```blocks
preset: cond-demo
Expand Down
4 changes: 2 additions & 2 deletions lessons/03-loops/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Lesson 3: Doing things over and over"
title: "Doing things over and over"
goal: "Use a loop to repeat a step many times without copying it out by hand."
order: 3
order: 30
section: "Programming with Blocks"
---

Expand Down
4 changes: 2 additions & 2 deletions lessons/04-functions/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Lesson 4: Reusable blocks"
title: "Reusable blocks"
goal: "Package a flowchart into a named block you can reuse instead of rebuilding it."
order: 4
order: 40
section: "Programming with Blocks"
---

Expand Down
8 changes: 4 additions & 4 deletions lessons/05-what-is-java/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
title: "Lesson 5: What is Java?"
title: "What is Java?"
goal: "Recognize Java's basic punctuation and program shape before writing or running any code."
order: 5
order: 50
section: "Java Fundamentals"
---

# A language, not a magic spell

You've been building programs already — as flowcharts of blocks
([lesson 1](#/lesson/01-flowcharts) onward). From here on we write those same
([lesson {n}](#/lesson/01-flowcharts) onward). From here on we write those same
ideas as **text**, in a language called **Java**. It's the language FRC robot
code is written in, and one of the most widely used languages in the world.

Expand Down Expand Up @@ -72,7 +72,7 @@ public class Main {

Don't worry about memorizing this — the playground writes it for you for now,
and you'll learn to write it yourself in
[lesson 11](#/lesson/11-writing-methods). For today, just recognize the shapes
[lesson {n}](#/lesson/11-writing-methods). For today, just recognize the shapes
in it with the vocabulary from above:

- `public class Main { ... }` — a **class** named `Main`, with everything it
Expand Down
12 changes: 6 additions & 6 deletions lessons/06-hello-world/README.md
Original file line number Diff line number Diff line change
@@ -1,16 +1,16 @@
---
title: "Lesson 6: Hello, World!"
title: "Hello, World!"
goal: "Explain what a program actually is, then write and run your first one."
order: 6
order: 60
section: "Java Fundamentals"
---

# What is a program?

You've already been building programs — as flowcharts of blocks. Everything you
did there carries over: doing steps in order ([lesson 1](#/lesson/01-flowcharts)), making decisions
([lesson 2](#/lesson/02-conditionals)), repeating steps ([lesson 3](#/lesson/03-loops)), and packaging steps into reusable pieces
([lesson 4](#/lesson/04-functions)). From here on we write those same ideas as **text** instead of wiring
did there carries over: doing steps in order ([lesson {n}](#/lesson/01-flowcharts)), making decisions
([lesson {n}](#/lesson/02-conditionals)), repeating steps ([lesson {n}](#/lesson/03-loops)), and packaging steps into reusable pieces
([lesson {n}](#/lesson/04-functions)). From here on we write those same ideas as **text** instead of wiring
boxes together. The concepts don't change; only the notation does — and
[last lesson](#/lesson/05-what-is-java) you already met that notation: braces,
parentheses, commas, semicolons, quotes.
Expand Down Expand Up @@ -48,7 +48,7 @@ Two things to know about the playground before you press Run:
- Java instructions normally live inside a small "program shell" of wrapper
code. The playground writes that shell for you so you can focus on the
instructions themselves — you'll learn to write the shell yourself in
[lesson 11](#/lesson/11-writing-methods).
[lesson {n}](#/lesson/11-writing-methods).

Press **Run** and watch the output panel below the code.

Expand Down
8 changes: 4 additions & 4 deletions lessons/07-variables/README.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
---
title: "Lesson 7: Naming things"
title: "Naming things"
goal: "Store a value under a typed name so you can reuse it and change it in one place."
order: 7
order: 70
section: "Java Fundamentals"
---

Expand All @@ -24,7 +24,7 @@ the computer reaches in and uses that value.
The word in front of the name — `String` — is the variable's **type**: what
*kind* of value it holds. `String` means text. Java makes you say the type up
front, and in exchange it will catch you the moment you try to put the wrong
kind of value in — more on this in [lesson 8](#/lesson/08-numbers-and-text).
kind of value in — more on this in [lesson {n}](#/lesson/08-numbers-and-text).

The `+` between `"Hello, "` and `name` **joins** text together into one string,
so this program prints `Hello, Ada`.
Expand Down Expand Up @@ -88,7 +88,7 @@ and stores `10`. Then `score = score + 5` looks confusing until you remember `=`
means "gets set to," not "equals": the computer works out the right side first
(`10 + 5 = 15`), then stores that back into `score`. This "take the value, change
it, put it back" pattern is exactly the running total the **repeat** block kept
in [lesson 3](#/lesson/03-loops) — a value that updates itself, one step at a time.
in [lesson {n}](#/lesson/03-loops) — a value that updates itself, one step at a time.

Your turn: add a line that gives a `10` penalty (subtract, don't add), then
print the score again. Predict the final number before you run it.
12 changes: 6 additions & 6 deletions lessons/08-numbers-and-text/README.md
Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
---
title: "Lesson 8: Types — numbers and text"
title: "Types — numbers and text"
goal: "Tell the basic kinds of data apart, and convert between them."
order: 8
order: 80
section: "Java Fundamentals"
---

# Numbers are not text

The block editor quietly hid something from you: it only ever dealt with
numbers. Real programs juggle different **kinds** of data — [lesson 7](#/lesson/07-variables) already
numbers. Real programs juggle different **kinds** of data — [lesson {n}](#/lesson/07-variables) already
made you name the kind every time you declared a variable. That kind is the
**type**, and the two you'll meet first are:

Expand All @@ -24,7 +24,7 @@ System.out.println("3" + "4");
```

The first line prints `7` — that's math. The second prints `34`, because with
text, `+` means **stick these together** (you used it that way in [lesson 7](#/lesson/07-variables)),
text, `+` means **stick these together** (you used it that way in [lesson {n}](#/lesson/07-variables)),
not "add." `"3"` and `"4"` aren't numbers to Java; they're just characters that
happen to look like digits, so it glues them into `"34"`.

Expand Down Expand Up @@ -58,12 +58,12 @@ System.out.println("Next year you will be " + (age + 1));
```

Notice we needed a **second variable**. `ageText` is a `String` and its type
can never change — that's the deal Java made with you in [lesson 7](#/lesson/07-variables). So the
can never change — that's the deal Java made with you in [lesson {n}](#/lesson/07-variables). So the
converted number gets its own name, `age`, with its own type, `int`. One name
per kind of thing keeps the program honest: you can always tell what's text
and what's a number just by reading the declarations. (The parentheses around
`(age + 1)` make the math happen *before* the joining — remember inner-first
from [lesson 1](#/lesson/01-flowcharts).)
from [lesson {n}](#/lesson/01-flowcharts).)

Conversion goes the other way too: `String.valueOf(42)` turns a number into
text — though as you saw, gluing a number onto a string with `+` usually does
Expand Down
16 changes: 8 additions & 8 deletions lessons/09-if-statements/README.md
Original file line number Diff line number Diff line change
@@ -1,13 +1,13 @@
---
title: "Lesson 9: Decisions in code"
title: "Decisions in code"
goal: "Use if, else if, and else to make a program choose between paths."
order: 9
order: 90
section: "Java Fundamentals"
---

# The decision diamond, in text

Remember the **decision diamond** from [lesson 2](#/lesson/02-conditionals)? It asked a true/false question and then
Remember the **decision diamond** from [lesson {n}](#/lesson/02-conditionals)? It asked a true/false question and then
took one path or the other, skipping the branch it didn't take. Java writes
that same idea with the word **`if`**:

Expand Down Expand Up @@ -35,7 +35,7 @@ but the "good day" line always prints.

# Both paths: else

[Lesson 2's](#/lesson/02-conditionals) diamond always had *two* arrows — a `true` arrow and a `false` arrow.
[Lesson {n}'s](#/lesson/02-conditionals) diamond always had *two* arrows — a `true` arrow and a `false` arrow.
Java spells the second one **`else`**:

```java
Expand All @@ -49,12 +49,12 @@ if (score >= 6) {

Exactly one of those two blocks runs, every time. If the question is true, you
get the first; otherwise you get the second. The other is skipped — "the road
not taken" from [lesson 2](#/lesson/02-conditionals), in text form.
not taken" from [lesson {n}](#/lesson/02-conditionals), in text form.

One thing to watch closely: that's `>=` ("greater than or equal to"), and the
"is it equal?" test is `==` with **two** equals signs. A single `=` means
"store a value" ([lesson 7](#/lesson/07-variables)), so Java uses `==` to *ask* about equality. Mixing
them up is the code version of the `>` vs `≥` bug [lesson 2](#/lesson/02-conditionals) warned about.
"store a value" ([lesson {n}](#/lesson/07-variables)), so Java uses `==` to *ask* about equality. Mixing
them up is the code version of the `>` vs `≥` bug [lesson {n}](#/lesson/02-conditionals) warned about.

Change `score` to `6`, then `5`, and run each time. Convince yourself `>= 6`
lets a `6` pass but a `5` doesn't.
Expand All @@ -81,7 +81,7 @@ Java checks each question **top to bottom** and takes the **first** one that's
true — then skips all the rest. That order matters: a score of `95` is also
`>= 80`, but because `>= 90` is checked first and wins, you never reach the B
line. This is the same "inputs before the things that use them, one step at a
time" discipline from [lesson 1](#/lesson/01-flowcharts).
time" discipline from [lesson {n}](#/lesson/01-flowcharts).

Run it, then change `score` to `85`, `72`, and `40` and run each time to see a
different branch win. Then your challenge: build a program that sets a number
Expand Down
Loading