From 137a176a28f719ff5767dcc2370bfaca5c44adef Mon Sep 17 00:00:00 2001 From: subzero Date: Wed, 20 May 2026 21:23:34 +0200 Subject: [PATCH 1/2] =?UTF-8?q?docs:=20site=20remediation=20=E2=80=94=20ho?= =?UTF-8?q?mepage=20Rules=20link,=20auto-counted=20demos,=20working=20Lear?= =?UTF-8?q?ning=20Path=20URLs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Implements .plans/site-remediation-spec.md (now removed — it was a working-doc scratchpad). Seven findings on the published site, all documentation-side; no framework code changes. **Homepage (`docs/index.md`):** - F1: add *The rules* link to *Where to go* between Design Principles and Components — the most distinctive Phase 2b artefact is now reachable from the landing page (previously invisible). - F2: drop the hard-coded *"twelve demos"* phrasing for *"every wirebench demo"* — delegates the count to the Learning Path table (auto-authoritative; survives demo additions). - F5: add an *Errors that teach, not just refuse* callout above *Where to go*. Real four-paragraph error message (what / why / where / try) in a fenced code block — the Phase 2b user-facing win, previewed in the first 200 words a visitor reads. - F7: replace the generic *See also* paragraph with a one-click link to `hello_led`'s *what this design is protected from* sidebar plus the demos folder root. Homepage → discipline-in-action click distance drops from 3-4 to 1. **Learning Path (`docs/learning-path.md`):** - F3: substitute all demo links from broken `../demos//` (resolves to empty body on the published site — demos aren't republished as MkDocs pages) to working `https://github.com/raeq/wirebench/tree/main/demos//`. Single regex pass; table realigned with consistent column widths. - F4: update *What each demo gives you* code block to list the current 14 per-demo artefacts (Python source, README, eleven exports, breadboard SVG, assembly guide) — previously listed only seven, missing five exports shipped across Phase 2. Point at `hello_led/docs/` on GitHub as the authoritative live index since the project README has no *What comes out* section to anchor. **Demos data gap caught:** the new `test_learning_path_table_covers_every_demo` flagged a pre-existing omission — `5v_rail_power` exists in `demos/` with a README and near-miss sidebar but wasn't on Learning Path. Added as row 2 (*"First regulator — LM7805 + Cell + bypass caps"*) so the table now covers all 19 demos in the repo, not 18. **Nav (`mkdocs.yml`):** - F6: rename the two component-page nav entries — `Components → Components — auto index` (the auto-generated parts index) and `Component catalogue (narrative) → Component notes — curated` (the hand-curated narrative). Reorder so the two pages are adjacent in the nav, communicating *one cluster about parts* rather than two pages scattered through the list. **Test coverage (`tests/docs/test_rules_doc.py` extended):** - `test_learning_path_demo_links_use_github_urls` — pins the github.com URL convention so future edits can't silently re-introduce the broken-relative-path or `raeq.github.io/wirebench/demos/...` failure modes. - `test_every_learning_path_demo_link_resolves_to_a_real_demo` — every URL on the page must correspond to a real `demos//` directory in the repo. - `test_learning_path_table_covers_every_demo` — every demo directory must have a Learning Path row. Caught the `5v_rail_power` omission this PR fixes. - `test_index_links_to_the_rules` / `test_index_does_not_hardcode_demo_count` / `test_index_shows_a_concrete_error_message` / `test_index_see_also_links_to_a_concrete_demo_readme` — pin the homepage findings so a future edit can't undo them. - `test_mkdocs_keeps_the_two_component_pages_adjacent` — pins the Finding 6 nav convention. Suite: 4829 passed (8 new), mypy clean, `mkdocs build --strict` clean. --- docs/index.md | 47 +++++++-- docs/learning-path.md | 66 +++++++----- mkdocs.yml | 4 +- tests/docs/test_rules_doc.py | 196 ++++++++++++++++++++++++++++++++++- 4 files changed, 272 insertions(+), 41 deletions(-) diff --git a/docs/index.md b/docs/index.md index 7357371..9c885d6 100644 --- a/docs/index.md +++ b/docs/index.md @@ -9,21 +9,52 @@ wirebench prevents you from constructing the wrong design in the first place.** Install with `pip install wirebench`; the source is at [github.com/raeq/wirebench](https://github.com/raeq/wirebench). +## Errors that teach, not just refuse + +Every refusal includes four things: *what failed* (the existing +per-defect message), *why the rule exists* (one-line physical +justification), *where you wired the offence* (source-line traceback +to the `wire()` call), and — when one fix is overwhelmingly the right +answer — *what to try*. The rules stay strict; the experience of +meeting them is on your side. + +```text +wire() has multiple drivers ('out', 'out') — short circuit + Why: Two OUT-direction ports on one net fight each other on the + copper — current sinks through the losing output stage until the + FETs overheat; one driver per shared conductor. + Wired at: hello_led.py:14 + Try: Remove one of the two wire() calls connecting out and out, OR + insert a series element (resistor, diode) between them to break the + direct conflict. +``` + +See [The rules](the-rules.md) for every rule and the demo where each +one is first caught. + ## Where to go -- [Learning path](learning-path.md) — suggested order through the - twelve demos. +- [Learning path](learning-path.md) — suggested order through every + wirebench demo. - [Prevention benchmark](prevention-benchmark.md) — what wirebench catches that KiCad ERC and SKiDL ERC don't, with reproducible test cases. - [Design principles](design-principles.md) — why the framework is shaped the way it is. -- [Component catalogue](component-library-data.md) — every modelled - chip, connector, and passive, with datasheet links. +- [The rules](the-rules.md) — every rule the framework enforces, with + the physical justification and the demo where each one is first + caught. +- [Components — auto index](parts.md) — auto-generated index of every + modelled chip, connector, passive, and transducer. +- [Component notes — curated](component-library-data.md) — hand-curated + narrative with datasheet links and per-part gotchas. ## See also -The demos at -[github.com/raeq/wirebench/tree/main/demos](https://github.com/raeq/wirebench/tree/main/demos) -each have their own `README.md` showing what defects the framework -would have refused in that design. +- [The `hello_led` README's *what this design is protected from* + sidebar](https://github.com/raeq/wirebench/blob/main/demos/hello_led/README.md#what-this-design-is-protected-from) + — a concrete example of how the framework refuses defective designs, + with verbatim error-class output for two near-miss snippets. +- [All wirebench demos on + GitHub](https://github.com/raeq/wirebench/tree/main/demos) — + each carries the same *what this design is protected from* sidebar. diff --git a/docs/learning-path.md b/docs/learning-path.md index aa0392f..f5c5e14 100644 --- a/docs/learning-path.md +++ b/docs/learning-path.md @@ -18,26 +18,27 @@ If you've never lit an LED with a battery, start with [the project's Hello World The *first catches* column names rules from [`the-rules.md`](the-rules.md) that this demo is the first place to surface — i.e. work the demos top-to-bottom and you'll see the framework refuse each rule in a real circuit by the time you reach the demo it's listed against. -| # | Demo | What it teaches | First catches | -|----|------------------------------------------------------------------------|------------------------------------------------------------------------------|-------------------------------| -| 1 | [`hello_led/`](../demos/hello_led/) | The minimal viable circuit — one LED, one resistor, two rails. Reading point.| [Rules 1, 2](the-rules.md) | -| 2 | [`penfold_light_switch/`](../demos/penfold_light_switch/) | First sensor circuit. LDR + comparator + transistor switch. Penfold BP107 P3.| [Rules 3, 4](the-rules.md) | -| 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. | — | -| 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. | [Rule 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. | [Rule 6](the-rules.md) | -| 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. | — | -| 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. | — | +| # | Demo | What it teaches | First catches | +|----|------------------------------------------------------------------------------------------------------------------|-------------------------------------------------------------------------------|----------------------------| +| 1 | [`hello_led/`](https://github.com/raeq/wirebench/tree/main/demos/hello_led/) | The minimal viable circuit — one LED, one resistor, two rails. Reading point. | [Rules 1, 2](the-rules.md) | +| 2 | [`5v_rail_power/`](https://github.com/raeq/wirebench/tree/main/demos/5v_rail_power/) | First regulator — LM7805 + Cell + bypass caps. Linear-supply basics. | — | +| 3 | [`penfold_light_switch/`](https://github.com/raeq/wirebench/tree/main/demos/penfold_light_switch/) | First sensor circuit. LDR + comparator + transistor switch. Penfold BP107 P3. | [Rules 3, 4](the-rules.md) | +| 4 | [`water_alarm/`](https://github.com/raeq/wirebench/tree/main/demos/water_alarm/) | Composing chips, wiring rails, latching logic. Four chips, two LEDs. | [Rule 9](the-rules.md) | +| 5 | [`penfold_reaction_game/`](https://github.com/raeq/wirebench/tree/main/demos/penfold_reaction_game/) | Sequential digital — counter ring + button-stops-clock topology. Penfold P22. | — | +| 6 | [`dice/`](https://github.com/raeq/wirebench/tree/main/demos/dice/) | Classic 555 + 4017 + diode-OR matrix. Recognisable hobbyist staple. | — | +| 7 | [`digital_thermometer/`](https://github.com/raeq/wirebench/tree/main/demos/digital_thermometer/) | First MCU project. ATmega328P + DHT11 + 7-seg display. Firmware-as-cell. | — | +| 8 | [`penfold_one_second_timer/`](https://github.com/raeq/wirebench/tree/main/demos/penfold_one_second_timer/) | Op-amp relaxation oscillator with hysteresis. Penfold BP107 P8. | — | +| 9 | [`penfold_metronome/`](https://github.com/raeq/wirebench/tree/main/demos/penfold_metronome/) | NE555 astable + speaker — the other classical astable. Penfold BP107 P9. | — | +| 10 | [`penfold_warbling_doorbuzzer/`](https://github.com/raeq/wirebench/tree/main/demos/penfold_warbling_doorbuzzer/) | Oscillator composition — slow gates fast. Penfold BP107 P16. | — | +| 11 | [`doorbell_protector/`](https://github.com/raeq/wirebench/tree/main/demos/doorbell_protector/) | Two-555 monostable with transistor switching and a relay. | — | +| 12 | [`fan_cooling/`](https://github.com/raeq/wirebench/tree/main/demos/fan_cooling/) | First `Board` demo. TMP302 + MOSFET-switched fan. Connectors that mate. | [Rule 7](the-rules.md) | +| 13 | [`backup_power/`](https://github.com/raeq/wirebench/tree/main/demos/backup_power/) | TI Designs TIDA-03031. Three-stage power architecture (eFuse + boost + buck). | — | +| 14 | [`water_alarm_split/`](https://github.com/raeq/wirebench/tree/main/demos/water_alarm_split/) | Same circuit as #3 but split across two boards via `mate()`. HAT pattern. | [Rule 6](the-rules.md) | +| 15 | [`bldc_motor/`](https://github.com/raeq/wirebench/tree/main/demos/bldc_motor/) | ATmega328P + DRV8313 + Hall sensors. Three-phase commutation. | — | +| 16 | [`isolated_rs232/`](https://github.com/raeq/wirebench/tree/main/demos/isolated_rs232/) | TIDA-01230. Cross-domain isolation — first demo to exercise `GroundDomain`. | [Rule 5](the-rules.md) | +| 17 | [`li_ion_fuel_gauge/`](https://github.com/raeq/wirebench/tree/main/demos/li_ion_fuel_gauge/) | TIDA-00594. BQ27546-G1 fuel gauge with sense resistor + thermistor. | — | +| 18 | [`penfold_fuzz_unit/`](https://github.com/raeq/wirebench/tree/main/demos/penfold_fuzz_unit/) | Audio domain — op-amp + clipping diodes. Guitar fuzz pedal. Penfold P30. | — | +| 19 | [`penfold_crystal_set/`](https://github.com/raeq/wirebench/tree/main/demos/penfold_crystal_set/) | Passive-only RF — no Rail, no battery. Boundary case. Penfold BP107 P27. | — | Rules 8, 10, 11, and 12 are framework-internal refusals — they fire during refactors and new-design construction rather than in any specific demo's near-miss snippet. The full table of all twelve rules is on [`the-rules.md`](the-rules.md). @@ -49,17 +50,26 @@ Every demo folder has the same shape: ```text demos// - .py # the source — read this first + .py # the source — read this first + README.md # what this design is protected from + recipe docs/ - .bom.csv # BOM ready to paste into a parts cart - .net # KiCad netlist for PCB layout - .cir # SPICE deck for simulation - .mmd # Mermaid render for documentation - .dot # Graphviz DOT - .yosys.json # Yosys JSON for netlistsvg - .svg # rendered schematic — open this in a browser + .bom.csv # BOM ready to paste into a parts cart + .net # KiCad netlist for PCB layout + .kicad_sch # KiCad schematic for Eeschema review + .cir # SPICE deck for simulation + .mmd # Mermaid render for documentation + .dot # Graphviz DOT + .yosys.json # Yosys JSON for netlistsvg + .svg # rendered schematic — open this in a browser + .breadboard.svg # half-size breadboard layout (single-Board demos) + .md # recipe-style breadboard assembly guide + .net-report.md # every logical net + drivers + readers + domain + .domain-report.md # GroundDomain memberships and isolation + .interface-report.md # public Board connector pins ``` +A live example of the full set sits in the [hello_led/docs/ folder on GitHub](https://github.com/raeq/wirebench/tree/main/demos/hello_led/docs) — that's the authoritative live index, regenerated whenever an exporter changes, so the listing above can't drift from what actually ships. + For composite assemblies (the `water_alarm_split`, `fan_cooling`, `bldc_motor`, `isolated_rs232`, and `li_ion_fuel_gauge` demos), there's one set of exports per board *plus* one set for the parent assembly — so you can see how the same model exports differently depending on whether you're producing a per-board netlist or an assembly-level overview. ## How to use this path diff --git a/mkdocs.yml b/mkdocs.yml index 525246f..6ee4445 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -17,9 +17,9 @@ nav: - Design principles: design-principles.md - The rules: the-rules.md - Learning path: learning-path.md - - Components: parts.md + - 'Components — auto index': parts.md + - 'Component notes — curated': component-library-data.md - Prevention benchmark: prevention-benchmark.md - - Component catalogue (narrative): component-library-data.md markdown_extensions: - admonition diff --git a/tests/docs/test_rules_doc.py b/tests/docs/test_rules_doc.py index e932a06..003f758 100644 --- a/tests/docs/test_rules_doc.py +++ b/tests/docs/test_rules_doc.py @@ -190,17 +190,180 @@ def test_doc_cross_link_to_errors_source_resolves( # --------------------------------------------------------- learning-path -def test_learning_path_cross_references_the_rules_doc() -> None: +INDEX_DOC = REPO_ROOT / 'docs' / 'index.md' +DEMOS_DIR = REPO_ROOT / 'demos' + + +@pytest.fixture(scope='module') +def learning_path_text() -> str: + return LEARNING_PATH_DOC.read_text() + + +@pytest.fixture(scope='module') +def index_text() -> str: + return INDEX_DOC.read_text() + + +def _demos_in_repo() -> set[str]: + """Every directory under `demos/` that holds an actual demo — + identified by the presence of at least one `*.py` source file + (skip strays like a docs-only or assets-only directory). We don't + require `README.md` because not every demo has one yet.""" + return { + d.name for d in DEMOS_DIR.iterdir() + if d.is_dir() and any(d.glob('*.py')) + } + + +def test_learning_path_cross_references_the_rules_doc( + learning_path_text: str, +) -> None: """`docs/learning-path.md` must name `the-rules.md` so the two pages cross-reference and the cumulative-rules property is discoverable from either direction.""" - text = LEARNING_PATH_DOC.read_text() - assert 'the-rules.md' in text, ( + assert 'the-rules.md' in learning_path_text, ( "learning-path.md must link to the-rules.md so the rule " "narrative is reachable from the demo path." ) +def test_learning_path_demo_links_use_github_urls( + learning_path_text: str, +) -> None: + """The demo links must point at GitHub folders, not + `https://raeq.github.io/wirebench/demos/...` or relative + `../demos/...` — both shapes produce broken pages on the published + docs site (demos aren't republished as MkDocs pages). + + Pin the GitHub-URL convention so the failure mode the original + Phase 2b.2 deploy hit (silent blank pages) can't return. + """ + # No relative `../demos/...` links anywhere on the page. + relative = re.findall( + r'\]\(\.\./demos/[^)]*\)', learning_path_text, + ) + assert not relative, ( + f"Relative `../demos/...` links resolve to nothing on the " + f"published site. Offenders: {relative}" + ) + # No raeq.github.io/wirebench/demos/... links either. + site_demos = re.findall( + r'https://raeq\.github\.io/wirebench/demos/[^)\s]+', + learning_path_text, + ) + assert not site_demos, ( + f"Links to `raeq.github.io/wirebench/demos/...` produce empty " + f"body content — demos aren't published as MkDocs pages. " + f"Use GitHub tree URLs instead. Offenders: {site_demos}" + ) + + +def test_every_learning_path_demo_link_resolves_to_a_real_demo( + learning_path_text: str, +) -> None: + """Every `github.com/raeq/wirebench/tree/main/demos//` link + on Learning Path must correspond to a real demo directory in the + repo. Catches typos in the URL and demos that have been removed + without the table being updated.""" + demo_links = re.findall( + r'https://github\.com/raeq/wirebench/tree/main/demos/([a-z0-9_]+)/?', + learning_path_text, + ) + assert demo_links, ( + "No github.com demo links found on Learning Path — the demo " + "table must use the canonical GitHub-tree URLs." + ) + existing = _demos_in_repo() + for slug in demo_links: + assert slug in existing, ( + f"Learning Path links to demos/{slug}/ but that directory " + f"doesn't exist (or has no README.md). Known demos: " + f"{sorted(existing)}" + ) + + +def test_learning_path_table_covers_every_demo( + learning_path_text: str, +) -> None: + """The Learning Path table must have one row per demo directory — + a new demo added to `demos/` without a table row would silently + fall off the published learning order.""" + listed = set(re.findall( + r'https://github\.com/raeq/wirebench/tree/main/demos/([a-z0-9_]+)/?', + learning_path_text, + )) + expected = _demos_in_repo() + missing = expected - listed + assert not missing, ( + f"Demos exist in the repo but aren't listed on Learning Path: " + f"{sorted(missing)}. Add a table row for each." + ) + + +# --------------------------------------------------------------- index + +def test_index_links_to_the_rules(index_text: str) -> None: + """Findings 1 + 5 of the site remediation: the homepage's *Where + to go* surface must include the rules narrative and link to it.""" + assert 'the-rules.md' in index_text, ( + "docs/index.md must link to the-rules.md — it's the most " + "distinctive Phase 2b.2 artefact and must be reachable from " + "the landing page." + ) + + +def test_index_does_not_hardcode_demo_count(index_text: str) -> None: + """The homepage must not embed a literal demo count — the count + will stale every time a demo is added. Phrasings like *every + wirebench demo* delegate to the Learning Path table (which is + authoritative and auto-survives demo additions).""" + # Reject "twelve demos", "12 demos", "18 demos", etc. Allow the + # word `demo` / `demos` as a noun without a counting modifier. + bad = re.findall( + r'(?i)\b(?:' + r'twelve|thirteen|fourteen|fifteen|sixteen|seventeen|eighteen|' + r'nineteen|twenty|\d+' + r')\s+demos\b', + index_text, + ) + assert not bad, ( + f"docs/index.md hardcodes a demo count ({bad}) — every demo " + f"addition would re-stale this line. Use *every wirebench " + f"demo* (or equivalent) and let Learning Path own the count." + ) + + +def test_index_shows_a_concrete_error_message(index_text: str) -> None: + """Finding 5: the homepage must preview the four-paragraph + error-message shape — *what / why / where / try* — concretely. + Pinned by checking for the four labels in a fenced text block.""" + # All four labels must appear in the doc. We don't assert order + # or formatting since the rendered output is what visitors see. + for label in ('Why:', 'Wired at:', 'Try:'): + assert label in index_text, ( + f"docs/index.md should preview the error-message shape " + f"with a `{label}` line — that's the Phase 2b user-facing " + f"win this homepage callout exists to surface." + ) + + +def test_index_see_also_links_to_a_concrete_demo_readme( + index_text: str, +) -> None: + """Finding 7: the *See also* surface should be one click from the + landing page to a real demo's *what this design is protected from* + sidebar — not just the demos folder root.""" + assert re.search( + r'https://github\.com/raeq/wirebench/blob/main/demos/' + r'[a-z0-9_]+/README\.md', + index_text, + ), ( + "docs/index.md *See also* should link to a specific demo's " + "README so the discipline-in-action is one click away — not " + "just the demos folder root." + ) + + # --------------------------------------------------------------- mkdocs def test_the_rules_appears_in_mkdocs_navigation() -> None: @@ -226,3 +389,30 @@ def test_mkdocs_orders_design_principles_before_rules_before_learning() -> None: f"the-rules ({tr_pos}), learning-path ({lp_pos}); expected " f"design-principles → the-rules → learning-path." ) + + +def test_mkdocs_keeps_the_two_component_pages_adjacent() -> None: + """Finding 6: the auto-generated `parts.md` index and the + hand-curated `component-library-data.md` narrative serve distinct + purposes; the navigation orders them adjacent so the auto/curated + distinction reads as one cluster rather than two scattered pages.""" + text = MKDOCS_YML.read_text() + parts_pos = text.find('parts.md') + notes_pos = text.find('component-library-data.md') + assert parts_pos != -1 and notes_pos != -1 + # Adjacent in nav order — find the lines containing each entry and + # confirm there's nothing else between them. + lines = [l.strip() for l in text.splitlines() if l.strip()] + parts_line = next( + (i for i, l in enumerate(lines) if 'parts.md' in l), None, + ) + notes_line = next( + (i for i, l in enumerate(lines) if 'component-library-data.md' in l), + None, + ) + assert parts_line is not None and notes_line is not None + assert abs(parts_line - notes_line) == 1, ( + f"mkdocs.yml: the two component pages should be adjacent so " + f"the auto/curated distinction reads as one cluster. Currently " + f"separated by {abs(parts_line - notes_line) - 1} nav entries." + ) From 281d5c98f8f00d15ef68f0b7be239ec10e11baf6 Mon Sep 17 00:00:00 2001 From: subzero Date: Wed, 20 May 2026 21:29:54 +0200 Subject: [PATCH 2/2] review: address four inline review points (Copilot) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 1. tests/docs/test_rules_doc.py: assertion message mentioned README.md but the criterion is now `*.py` files (the harness was loosened to accommodate `penfold_one_second_timer`, which has no README). Update the failure message to match. 2. tests/docs/test_rules_doc.py: docstring claimed "four labels in a fenced text block" but the test only checked three labels and didn't verify the fenced block. Realigned: the *what* paragraph is the unlabelled error message line; the other three (Why:, Wired at:, Try:) are explicit labels. Test now also verifies all three labels appear inside a fenced code block, so the rendered output is monospace and visually mirrors what a user sees in their terminal. 3. docs/learning-path.md: the "every demo folder has the same shape" block overstated invariants — `5v_rail_power/` uses `five_volt_rail_power.py` (not `.py`), and `penfold_one_second_timer/` has no `README.md`. Reworded the intro paragraph to acknowledge the real variation, switched `.py` → `*.py` and `README.md` → `README.md (when present)`, and added a sentence naming `` as the design's top-level class name for the export filenames. 4. docs/index.md: the "All wirebench demos on GitHub" bullet claimed "each carries the same *what this design is protected from* sidebar" — false (penfold_one_second_timer has no README). Softened to "most carry the same sidebar (a small number are source-only)". Suite: 4829 passed (1 added invariant: fenced-block render), mypy clean, `mkdocs build --strict` clean. --- docs/index.md | 3 ++- docs/learning-path.md | 8 ++++---- tests/docs/test_rules_doc.py | 30 ++++++++++++++++++++++++------ 3 files changed, 30 insertions(+), 11 deletions(-) diff --git a/docs/index.md b/docs/index.md index 9c885d6..599df54 100644 --- a/docs/index.md +++ b/docs/index.md @@ -57,4 +57,5 @@ one is first caught. with verbatim error-class output for two near-miss snippets. - [All wirebench demos on GitHub](https://github.com/raeq/wirebench/tree/main/demos) — - each carries the same *what this design is protected from* sidebar. + most carry the same *what this design is protected from* sidebar + (a small number are source-only). diff --git a/docs/learning-path.md b/docs/learning-path.md index f5c5e14..5c7d6a1 100644 --- a/docs/learning-path.md +++ b/docs/learning-path.md @@ -46,12 +46,12 @@ Cross-referencing the other way: [`the-rules.md`](the-rules.md) lists, for each ## What each demo gives you -Every demo folder has the same shape: +Every demo folder follows the same broad shape — one or more Python source files plus a `docs/` subfolder of generated artefacts. The Python file usually matches the folder name (`hello_led.py`, `dice.py`), occasionally not (`5v_rail_power/` uses `five_volt_rail_power.py`). Most demos carry a `README.md` with the *what this design is protected from* sidebar; a small number are source-only. ```text demos// - .py # the source — read this first - README.md # what this design is protected from + recipe + *.py # the source — read this first + README.md # what this design is protected from + recipe (when present) docs/ .bom.csv # BOM ready to paste into a parts cart .net # KiCad netlist for PCB layout @@ -68,7 +68,7 @@ demos// .interface-report.md # public Board connector pins ``` -A live example of the full set sits in the [hello_led/docs/ folder on GitHub](https://github.com/raeq/wirebench/tree/main/demos/hello_led/docs) — that's the authoritative live index, regenerated whenever an exporter changes, so the listing above can't drift from what actually ships. +`` is the design's top-level class name (e.g. `HelloLED`, `Dice`). A live example of the full set sits in the [hello_led/docs/ folder on GitHub](https://github.com/raeq/wirebench/tree/main/demos/hello_led/docs) — that's the authoritative live index, regenerated whenever an exporter changes, so the listing above can't drift from what actually ships. For composite assemblies (the `water_alarm_split`, `fan_cooling`, `bldc_motor`, `isolated_rs232`, and `li_ion_fuel_gauge` demos), there's one set of exports per board *plus* one set for the parent assembly — so you can see how the same model exports differently depending on whether you're producing a per-board netlist or an assembly-level overview. diff --git a/tests/docs/test_rules_doc.py b/tests/docs/test_rules_doc.py index 003f758..66c4e6b 100644 --- a/tests/docs/test_rules_doc.py +++ b/tests/docs/test_rules_doc.py @@ -277,8 +277,8 @@ def test_every_learning_path_demo_link_resolves_to_a_real_demo( for slug in demo_links: assert slug in existing, ( f"Learning Path links to demos/{slug}/ but that directory " - f"doesn't exist (or has no README.md). Known demos: " - f"{sorted(existing)}" + f"doesn't exist (or has no *.py source file). Known " + f"demos: {sorted(existing)}" ) @@ -335,16 +335,34 @@ def test_index_does_not_hardcode_demo_count(index_text: str) -> None: def test_index_shows_a_concrete_error_message(index_text: str) -> None: """Finding 5: the homepage must preview the four-paragraph - error-message shape — *what / why / where / try* — concretely. - Pinned by checking for the four labels in a fenced text block.""" - # All four labels must appear in the doc. We don't assert order - # or formatting since the rendered output is what visitors see. + error-message shape — *what / why / where / try* — concretely + inside a fenced code block. + + The *what* paragraph is the error message itself (no label — + starts with the exception class). The remaining three paragraphs + each carry an explicit label. Both the labels and the surrounding + fenced block are required, so the rendered output appears in + monospace and visually mirrors what a wirebench user actually + sees in their terminal. + """ + # Each of the three labelled paragraphs must appear in the doc. for label in ('Why:', 'Wired at:', 'Try:'): assert label in index_text, ( f"docs/index.md should preview the error-message shape " f"with a `{label}` line — that's the Phase 2b user-facing " f"win this homepage callout exists to surface." ) + # And those labels must live inside a fenced code block so the + # rendered output is monospace — not just inline prose. + fenced_blocks = re.findall(r'```[^`]+```', index_text, re.DOTALL) + assert any( + all(label in block for label in ('Why:', 'Wired at:', 'Try:')) + for block in fenced_blocks + ), ( + "docs/index.md's error-message preview should sit inside a " + "fenced code block so mkdocs renders it as monospace — " + "matches what wirebench users see in their terminal." + ) def test_index_see_also_links_to_a_concrete_demo_readme(