docs: cumulative-rules narrative (Phase 2b.2)#37
Conversation
Add `docs/the-rules.md` — a single page indexing every user-facing rule the framework enforces. Per entry: the rule, the physical justification, the exception class that fires when violated (link to `framework/errors.py`), and the demo where users first see the rule caught. Twelve rules in five-minute reading length. Reviewer feedback addressed: "the rules feel arbitrary if you encounter them one at a time without the cumulative narrative; they feel principled if you can see the whole set on one page with the physical justification for each." The rules doc sits between `design-principles.md` (the abstract) and `learning-path.md` (the graduated exposure) in the site navigation. Cross-references: - `learning-path.md` gains a *First catches* column on the demo table naming the rule numbers each demo first surfaces; a closing paragraph notes that framework-internal refusals (Rules 8, 10, 11, 12) don't anchor to a single demo. - `the-rules.md` links each rule to its first-caught demo. `tests/docs/test_rules_doc.py` keeps the doc in sync as the exception hierarchy evolves: every curated user-facing rule exception is named in the doc, every `XxxError` reference in the doc resolves to a real class (catches rename typos), every `../demos/<slug>/` link resolves to an actual directory, and the mkdocs nav orders design-principles → the-rules → learning-path. Suite: 4795 passed (33 new), mypy clean.
There was a problem hiding this comment.
Pull request overview
Adds a new “cumulative rules” documentation page and accompanying consistency tests so the user-facing framework rules, their exception classes, and cross-links stay synchronized as the exception hierarchy and demo set evolve.
Changes:
- Add
docs/the-rules.mdas a single-page index of user-facing enforcement rules (with “why”, exception class, and first-caught demo). - Update
docs/learning-path.mdto add a “First catches” column and cross-referencethe-rules.md; fix a fenced-code language tag. - Add
tests/docs/test_rules_doc.pyplus mkdocs navigation updates to keep docs, exceptions, demo links, and nav ordering consistent.
Reviewed changes
Copilot reviewed 4 out of 5 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
tests/docs/test_rules_doc.py |
Adds tests to ensure rule exceptions and doc links are consistent and mkdocs nav includes/ आदेशers the new page. |
tests/docs/__init__.py |
Establishes tests/docs as a package (empty init). |
mkdocs.yml |
Inserts “Design principles” and the new “The rules” page into the docs nav in the intended order. |
docs/the-rules.md |
Introduces the single-page rules index with links to exception classes and demos. |
docs/learning-path.md |
Adds “First catches” column and cross-references to the new rules page; fixes code-fence language. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| - **What fires** — the exception class wirebench raises ([source](https://github.com/raeq/wirebench/blob/main/src/framework/errors.py)). | ||
| - **First caught at** — the demo where you'll meet this rule for the first time if you walk the [learning path](learning-path.md) top to bottom. | ||
|
|
||
| By the time you've worked through `hello_led/` + `water_alarm/` + `5v_rail_power/` + `digital_thermometer/` + `fan_cooling/` + `isolated_rs232/` + `li_ion_fuel_gauge/`, you've seen the framework catch every rule on this page in a real demo. The rules aren't aspirational — they're the load-bearing checks that keep "the code matches the breadboard" honest. |
|
|
||
| A `wire()` whose ports are already on two distinct nodes would silently fuse independent design intent into one net. The framework refuses and raises [`NodeMergeError`](https://github.com/raeq/wirebench/blob/main/src/framework/errors.py). | ||
|
|
||
| **Why:** Mergeing nets at construction time is a footgun for boards being composed from sub-circuits — a wire intended to extend one net could accidentally connect it to a parallel net the author hadn't realised was nearby. The framework requires the join to be explicit: refactor the code so the two nets are constructed together, or use a `Pin` / connector mate to bridge them deliberately. |
|
|
||
| **Why:** A mandatory pin left in air leaves the silicon stage tied to nothing. The part either doesn't power up at all (no current path) or behaves unpredictably (floating reference). The bench equivalent is a board that arrives, populates, and refuses to come on — every solder joint is fine, but one wire was missing in the schematic. | ||
|
|
||
| **First caught at:** [`5v_rail_power/`](../demos/5v_rail_power/) — the *floating regulator input* near-miss. |
| | 2 | [`penfold_light_switch/`](../demos/penfold_light_switch/) | First sensor circuit. LDR + comparator + transistor switch. Penfold BP107 P3.| — | | ||
| | 3 | [`water_alarm/`](../demos/water_alarm/) | Composing chips, wiring rails, latching logic. Four chips, two LEDs. | [Rule 9](the-rules.md) | | ||
| | 4 | [`penfold_reaction_game/`](../demos/penfold_reaction_game/) | Sequential digital — counter ring + button-stops-clock topology. Penfold P22.| — | | ||
| | 5 | [`dice/`](../demos/dice/) | Classic 555 + 4017 + diode-OR matrix. Recognisable hobbyist staple. | — | | ||
| | 6 | [`digital_thermometer/`](../demos/digital_thermometer/) | First MCU project. ATmega328P + DHT11 + 7-seg display. Firmware-as-cell. | [Rule 3](the-rules.md) | | ||
| | 7 | [`penfold_one_second_timer/`](../demos/penfold_one_second_timer/) | Op-amp relaxation oscillator with hysteresis. Penfold BP107 P8. | — | | ||
| | 8 | [`penfold_metronome/`](../demos/penfold_metronome/) | NE555 astable + speaker — the other classical astable. Penfold BP107 P9. | — | | ||
| | 9 | [`penfold_warbling_doorbuzzer/`](../demos/penfold_warbling_doorbuzzer/)| Oscillator composition — slow gates fast. Penfold BP107 P16. | — | | ||
| | 10 | [`doorbell_protector/`](../demos/doorbell_protector/) | Two-555 monostable with transistor switching and a relay. | — | | ||
| | 11 | [`fan_cooling/`](../demos/fan_cooling/) | First `Board` demo. TMP302 + MOSFET-switched fan. Connectors that mate. | [Rules 6, 7](the-rules.md) | | ||
| | 12 | [`backup_power/`](../demos/backup_power/) | TI Designs TIDA-03031. Three-stage power architecture (eFuse + boost + buck).| — | | ||
| | 13 | [`water_alarm_split/`](../demos/water_alarm_split/) | Same circuit as #3 but split across two boards via `mate()`. HAT pattern. | — | | ||
| | 14 | [`bldc_motor/`](../demos/bldc_motor/) | ATmega328P + DRV8313 + Hall sensors. Three-phase commutation. | — | | ||
| | 15 | [`isolated_rs232/`](../demos/isolated_rs232/) | TIDA-01230. Cross-domain isolation — first demo to exercise `GroundDomain`. | [Rule 5](the-rules.md) | | ||
| | 16 | [`li_ion_fuel_gauge/`](../demos/li_ion_fuel_gauge/) | TIDA-00594. BQ27546-G1 fuel gauge with sense resistor + thermistor. | [Rule 4](the-rules.md) | | ||
| | 17 | [`penfold_fuzz_unit/`](../demos/penfold_fuzz_unit/) | Audio domain — op-amp + clipping diodes. Guitar fuzz pedal. Penfold P30. | — | | ||
| | 18 | [`penfold_crystal_set/`](../demos/penfold_crystal_set/) | Passive-only RF — no Rail, no battery. Boundary case. Penfold BP107 P27. | — | |
…ngthen test
Reviewer feedback addressed:
**Accuracy.** Rule 3 (`UnconnectedPinError`) and Rule 6
(`IncompatibleMateError`) listed demos that don't actually catch
those exceptions in their READMEs. Surveyed every demo README for
the exceptions it raises and re-mapped each rule to the truly-first
demo in learning-path order:
- Rule 3 `UnconnectedPinError` → `penfold_light_switch/` (the
"floating LDR" near-miss raises UnconnectedPinError; the LDR's
terminals are mandatory), not `5v_rail_power/` or
`digital_thermometer/`.
- Rule 4 `SignalTypeMismatchError` → `penfold_light_switch/` (the
"mismatched comparator input domain" near-miss), not
`li_ion_fuel_gauge/`.
- Rule 6 `IncompatibleMateError` → `water_alarm_split/` (the
"wrong connector family" near-miss), not `fan_cooling/`.
Updated `learning-path.md`'s *First catches* column to match.
**Traceability.** Per the spec — "link to the demo's *what this
design is protected from* sidebar" — every `First caught at:` entry
now deep-links to the specific README#anchor of the near-miss
section, not just the demo folder. Anchors generated from the
section heading text using GitHub's slug algorithm.
**Test coverage.** Strengthened `test_every_demo_cross_link_resolves`
to compute each README's GitHub-style heading anchors and verify
every `#anchor` in `the-rules.md` points at a real heading. Catches
typos in the slug and renames of section headings on the demo side.
**Intro overclaim.** Softened the cumulative-progress claim — the
intro previously asserted you'd see "every rule on this page" caught
in real demos by working through a subset of demos, but Rules 8, 10,
11, 12 are framework-internal refusals with no demo anchor. Now
explicitly: "the demo-anchored rules (Rules 1–7, 9)".
**Typo.** "Mergeing" → "Merging" on Rule 10's *Why:* line.
Suite: 4795 passed, mypy clean.
|
Thanks — multiple substantive issues. All fixed in af8808d. Inaccurate rule→demo mappings. Surveyed every demo README for the exceptions it actually raises and re-mapped each rule to the truly-first demo in learning-path order:
Updated Demo links promised by the spec → README anchors, not folder roots. The spec asked for "link to the demo's what this design is protected from sidebar." Now every Test was permissive — now strict. Intro overclaim. Softened the cumulative-progress sentence — previously it implied you'd see every rule caught in real demos, but Rules 8, 10, 11, 12 are framework-internal refusals with no demo anchor. Now: "the demo-anchored rules (Rules 1–7, 9)". Typo. "Mergeing" → "Merging" on Rule 10's Why: line. Open questions:
Suite: 4795 passed (same count; 33 docs tests now exercise stronger invariants), mypy clean. |
Summary
Third task of .plans/phase-2b-spec.md — the rules feel arbitrary if you encounter them one at a time without the cumulative narrative; they feel principled if you can see the whole set on one page with the physical justification for each.
Adds
docs/the-rules.md— a five-minute single-page index of every user-facing rule the framework enforces. Per entry: the rule itself, the physical-world referent for why, the exception class that fires (link toframework/errors.py), and the demo where users first see the rule caught.Twelve rules covered:
ShortCircuitError(first caught:hello_led/)FloatingNetError(first caught:hello_led/)UnconnectedPinError(first caught:5v_rail_power/)SignalTypeMismatchError(first caught:li_ion_fuel_gauge/)DomainCrossingError(first caught:isolated_rs232/)IncompatibleMateError(first caught:fan_cooling/)PinCountMismatchError/PitchMismatchError(first caught:fan_cooling/)RefdesErrorForbiddenStateError(first caught:water_alarm/)wire()doesn't merge pre-existing nets →NodeMergeErrorEmptyWireErrorWiredChipCallErrorCross-references
learning-path.mdgains a First catches column on the demo table naming the rule numbers each demo first surfaces. A closing paragraph notes that framework-internal refusals (Rules 8, 10, 11, 12) don't anchor to a single demo.the-rules.mdlinks to the first-caught demo, so the cumulative property is visible: by the time you've worked throughhello_led/+water_alarm/+5v_rail_power/+digital_thermometer/+fan_cooling/+isolated_rs232/+li_ion_fuel_gauge/, you've seen the framework catch eight of the twelve rules in real demos.Files
docs/the-rules.md— new, the cumulative-rules narrative.docs/learning-path.md— adds First catches column + cross-reference paragraph; also fixes a pre-existing fenced-code-language lint warning along the way.mkdocs.yml— addsThe rules: the-rules.mdto nav betweenDesign principles(the abstract) andLearning path(the graduated exposure).tests/docs/test_rules_doc.py— 33 new tests pinning consistency: every curated user-facing rule exception is named in the doc; everyXxxErrorreference resolves to a real class (catches rename typos); every../demos/<slug>/link resolves to an actual directory; mkdocs nav orders design-principles → the-rules → learning-path.Test plan
uv run pytest— 4795 passed (33 new), 16 skippeduv run mypy src/ demos/— clean