modulearn 0.2.0__tar.gz → 0.3.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (40) hide show
  1. modulearn-0.3.0/CHANGELOG.md +24 -0
  2. {modulearn-0.2.0 → modulearn-0.3.0}/PKG-INFO +8 -4
  3. {modulearn-0.2.0 → modulearn-0.3.0}/PUBLISHING.md +11 -5
  4. {modulearn-0.2.0 → modulearn-0.3.0}/README.md +6 -3
  5. modulearn-0.3.0/docs/changelog-tags-guide.html +218 -0
  6. modulearn-0.3.0/docs/ci-badge-guide.html +174 -0
  7. modulearn-0.3.0/docs/init-scaffold-guide.html +361 -0
  8. modulearn-0.3.0/docs/readme-install-guide.html +192 -0
  9. modulearn-0.3.0/docs/registry-api-guide.html +196 -0
  10. modulearn-0.3.0/docs/regression-guide.html +359 -0
  11. modulearn-0.3.0/docs/reload-guide.html +202 -0
  12. modulearn-0.3.0/docs/run-errors-guide.html +232 -0
  13. modulearn-0.3.0/docs/single-source-version-guide.html +198 -0
  14. modulearn-0.3.0/docs/type-rules-guide.html +439 -0
  15. modulearn-0.3.0/examples/regression.py +77 -0
  16. {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/__init__.py +1 -1
  17. modulearn-0.3.0/modulearn/cli.py +229 -0
  18. {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/demo.py +1 -0
  19. {modulearn-0.2.0 → modulearn-0.3.0}/pyproject.toml +5 -1
  20. modulearn-0.3.0/tests/test_cli_init.py +23 -0
  21. modulearn-0.3.0/tests/test_run_errors_cli.py +22 -0
  22. modulearn-0.3.0/tests/test_type_rules.py +134 -0
  23. modulearn-0.2.0/CHANGELOG.md +0 -6
  24. modulearn-0.2.0/modulearn/cli.py +0 -88
  25. {modulearn-0.2.0 → modulearn-0.3.0}/.github/workflows/ci.yml +0 -0
  26. {modulearn-0.2.0 → modulearn-0.3.0}/.gitignore +0 -0
  27. {modulearn-0.2.0 → modulearn-0.3.0}/LICENSE +0 -0
  28. {modulearn-0.2.0 → modulearn-0.3.0}/docs/implementation-guide.html +0 -0
  29. {modulearn-0.2.0 → modulearn-0.3.0}/docs/roadmap.html +0 -0
  30. {modulearn-0.2.0 → modulearn-0.3.0}/examples/quickstart.py +0 -0
  31. {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/__main__.py +0 -0
  32. {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/compiler.py +0 -0
  33. {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/registry.py +0 -0
  34. {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/server.py +0 -0
  35. {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/static/graph-engine.js +0 -0
  36. {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/static/graph.html +0 -0
  37. {modulearn-0.2.0 → modulearn-0.3.0}/modulearn/static/graph.js +0 -0
  38. {modulearn-0.2.0 → modulearn-0.3.0}/tests/test_robustness.py +0 -0
  39. {modulearn-0.2.0 → modulearn-0.3.0}/tests/test_run_errors.py +0 -0
  40. {modulearn-0.2.0 → modulearn-0.3.0}/tests/test_smoke.py +0 -0
@@ -0,0 +1,24 @@
1
+ # Changelog
2
+
3
+ All notable changes to ModuLearn are documented here.
4
+ This project follows [Keep a Changelog](https://keepachangelog.com) and
5
+ [Semantic Versioning](https://semver.org).
6
+
7
+ ## [0.3.0] - 2026-07-27
8
+ - `modulearn init [dir]` scaffolds a runnable starter project. (app.py, README, .gitignore)
9
+ - `modulearn run --reload` restarts the server on file changes for live development.
10
+ - Sharper `modulearn run` errors: names the failing line on import errors, suggests a real app attribute
11
+ when the main one is missing, and rejects non-app objects.
12
+
13
+ ## [0.2.0] — 2026-07-23
14
+ - Surface on_train crashes on the canvas (error + full traceback)
15
+ - Loss node now shapes the training curve
16
+ - CompiledGraph.check_shape() validates loaded array shape
17
+ - Enforce hyperparameter min/max at compile
18
+ - GitHub Actions CI across Python 3.10–3.13
19
+
20
+ ## [0.1.1] — 2026-07-22
21
+ - Add `python -m modulearn` entry point
22
+
23
+ ## [0.1.0] — 2026-07-22
24
+ - Initial release: node-graph editor, Registry, compiler, and `modulearn` CLI
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: modulearn
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: A visual, Unreal-Blueprints-style node-graph editor for building and launching ML training runs.
5
5
  Project-URL: Homepage, https://github.com/IsaiahKoamalu/modulearn
6
6
  Project-URL: Source, https://github.com/IsaiahKoamalu/modulearn
@@ -16,6 +16,7 @@ Classifier: Topic :: Scientific/Engineering :: Artificial Intelligence
16
16
  Requires-Python: >=3.10
17
17
  Requires-Dist: fastapi>=0.110
18
18
  Requires-Dist: pydantic>=2.0
19
+ Requires-Dist: scikit-learn>=1.7.2
19
20
  Requires-Dist: uvicorn>=0.29
20
21
  Provides-Extra: dev
21
22
  Requires-Dist: build; extra == 'dev'
@@ -25,14 +26,14 @@ Description-Content-Type: text/markdown
25
26
 
26
27
  # ModuLearn
27
28
 
29
+ [![CI](https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml/badge.svg)](https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml)
30
+
28
31
  A visual, [Unreal-Blueprints](https://dev.epicgames.com/documentation/en-us/unreal-engine/blueprints-visual-scripting-in-unreal-engine)-style
29
32
  node-graph editor for building and launching machine-learning training runs.
30
33
  Drop **Dataset → Model → Train** nodes on a canvas, wire **hyperparameters**,
31
34
  **loss** and **feature transforms** into them, hit ▶ Train, and watch the learning
32
35
  curve live on the graph. No forms, no drift between the UI and the code it runs.
33
36
 
34
- > **ModuLearn** is a working name — you assemble training runs from modular nodes and
35
- > learn from the live curve. Rename the package/repo freely; nothing depends on the name.
36
37
 
37
38
  <!-- A screenshot or GIF of the editor goes well here. -->
38
39
 
@@ -47,7 +48,7 @@ backend can't accept and it simply won't connect.
47
48
  ## Install
48
49
 
49
50
  ```bash
50
- pip install -e . # fastapi + uvicorn + pydantic
51
+ pip install modulearn # fastapi + uvicorn + pydantic, from PyPI
51
52
  modulearn demo # serve the bundled editor at http://localhost:8000
52
53
  ```
53
54
 
@@ -65,6 +66,9 @@ Swap `on_train` for your real training loop and nothing else changes. (The
65
66
  equivalent source file lives at [`examples/quickstart.py`](examples/quickstart.py)
66
67
  if you'd rather run it directly with `python`.)
67
68
 
69
+ > **Working on ModuLearn itself?** Clone the repo and install editable with
70
+ > dev extras: `pip install -e ".[dev]"` (adds pytest, build, twine).
71
+
68
72
  ## The whole integration is two things
69
73
 
70
74
  ```python
@@ -96,8 +96,14 @@ modulearn demo
96
96
 
97
97
  ## Cutting later releases
98
98
 
99
- 1. Bump `version` in `pyproject.toml` **and** `__version__` in `modulearn/__init__.py`.
100
- 2. `rm -rf dist && python -m build`
101
- 3. `twine check dist/*`
102
- 4. `git commit -am "modulearn <ver>" && git tag v<ver> && git push --tags`
103
- 5. `twine upload dist/*`
99
+ The version is single-sourced from `modulearn/__init__.py` `pyproject.toml`
100
+ reads it via `[tool.hatch.version]`, so you bump exactly one line.
101
+
102
+ 1. Bump `__version__` in `modulearn/__init__.py` (the only version to edit).
103
+ 2. In `CHANGELOG.md`, move the `Unreleased` notes under a new
104
+ `## [<ver>] — <YYYY-MM-DD>` heading.
105
+ 3. `rm -rf dist && python -m build`
106
+ 4. `twine check dist/*`
107
+ 5. `git commit -am "modulearn <ver>" && git tag -a v<ver> -m "modulearn <ver>" && git push --tags`
108
+ 6. `twine upload dist/*`
109
+ 7. Verify: `pip index versions modulearn` shows `<ver>` and tag `v<ver>` exists.
@@ -1,13 +1,13 @@
1
1
  # ModuLearn
2
2
 
3
+ [![CI](https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml/badge.svg)](https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml)
4
+
3
5
  A visual, [Unreal-Blueprints](https://dev.epicgames.com/documentation/en-us/unreal-engine/blueprints-visual-scripting-in-unreal-engine)-style
4
6
  node-graph editor for building and launching machine-learning training runs.
5
7
  Drop **Dataset → Model → Train** nodes on a canvas, wire **hyperparameters**,
6
8
  **loss** and **feature transforms** into them, hit ▶ Train, and watch the learning
7
9
  curve live on the graph. No forms, no drift between the UI and the code it runs.
8
10
 
9
- > **ModuLearn** is a working name — you assemble training runs from modular nodes and
10
- > learn from the live curve. Rename the package/repo freely; nothing depends on the name.
11
11
 
12
12
  <!-- A screenshot or GIF of the editor goes well here. -->
13
13
 
@@ -22,7 +22,7 @@ backend can't accept and it simply won't connect.
22
22
  ## Install
23
23
 
24
24
  ```bash
25
- pip install -e . # fastapi + uvicorn + pydantic
25
+ pip install modulearn # fastapi + uvicorn + pydantic, from PyPI
26
26
  modulearn demo # serve the bundled editor at http://localhost:8000
27
27
  ```
28
28
 
@@ -40,6 +40,9 @@ Swap `on_train` for your real training loop and nothing else changes. (The
40
40
  equivalent source file lives at [`examples/quickstart.py`](examples/quickstart.py)
41
41
  if you'd rather run it directly with `python`.)
42
42
 
43
+ > **Working on ModuLearn itself?** Clone the repo and install editable with
44
+ > dev extras: `pip install -e ".[dev]"` (adds pytest, build, twine).
45
+
43
46
  ## The whole integration is two things
44
47
 
45
48
  ```python
@@ -0,0 +1,218 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>ModuLearn — Implementing: CHANGELOG + tags that match PyPI</title>
7
+ <style>
8
+ :root{
9
+ --bg:#14181f; --panel:#1b212b; --panel2:#20262f; --edge:#2a323f;
10
+ --ink:#e6edf3; --muted:#8b98a9; --faint:#5f6b7a; --accent:#a8542f;
11
+ --c:#c98bdb; --err:#f85149; --ok:#3fb950;
12
+ --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
13
+ --sans:ui-sans-serif,system-ui,-apple-system,sans-serif;
14
+ }
15
+ *{box-sizing:border-box} html,body{margin:0}
16
+ body{background:var(--bg); color:var(--ink); font:14.5px/1.6 var(--sans);
17
+ background-image:linear-gradient(rgba(255,255,255,.022) 1px,transparent 1px),
18
+ linear-gradient(90deg,rgba(255,255,255,.022) 1px,transparent 1px);
19
+ background-size:50px 50px; -webkit-font-smoothing:antialiased;}
20
+ .wrap{max-width:860px; margin:0 auto; padding:0 22px}
21
+ code{font:12.5px/1.5 var(--mono); background:#0f131a; border:1px solid var(--edge);
22
+ border-radius:4px; padding:1px 5px; color:#c3ccd8}
23
+ pre{font:12.5px/1.6 var(--mono); background:#0f131a; border:1px solid var(--edge);
24
+ border-radius:8px; padding:13px 15px; overflow-x:auto; color:#c3ccd8; margin:12px 0}
25
+ pre code{background:none; border:none; padding:0}
26
+ a{color:var(--c)}
27
+ header{padding:60px 0 26px}
28
+ .kicker{font:600 11px/1 var(--mono); letter-spacing:.26em; text-transform:uppercase;
29
+ color:var(--muted); display:flex; align-items:center; gap:10px}
30
+ .kicker .dot{width:9px; height:9px; border-radius:50%; background:var(--c);
31
+ box-shadow:0 0 0 4px color-mix(in srgb,var(--c) 18%,transparent)}
32
+ h1{font:600 clamp(30px,5vw,46px)/1.06 var(--sans); letter-spacing:-.02em; margin:16px 0 0}
33
+ h1 code{font-size:.7em; background:#0f131a; padding:2px 9px; vertical-align:3px}
34
+ .lede{margin:16px 0 0; color:#c3ccd8; max-width:62ch} .lede b{color:var(--ink)}
35
+ .goal{margin:22px 0 0; padding:13px 15px; border-left:3px solid var(--c);
36
+ background:#0f131a; border-radius:0 7px 7px 0; font-size:13.5px} .goal b{color:var(--c)}
37
+ .progress{margin:26px 0 0; display:flex; align-items:center; gap:14px}
38
+ .track{flex:1; height:8px; border-radius:5px; background:#0f131a; border:1px solid var(--edge); overflow:hidden}
39
+ .fill{height:100%; width:0; background:var(--c); transition:width .45s cubic-bezier(.2,.7,.2,1)}
40
+ .cnt{font:600 12px/1 var(--mono); color:var(--muted); white-space:nowrap} .cnt b{color:var(--ink); font-size:15px}
41
+ h2{font:600 13px/1 var(--mono); letter-spacing:.13em; text-transform:uppercase; color:var(--muted);
42
+ margin:52px 0 0; padding-bottom:11px; border-bottom:1px solid var(--edge); display:flex; align-items:center; gap:9px}
43
+ h2::before{content:""; width:9px; height:9px; border-radius:50%; background:var(--c)}
44
+ ol.steps{list-style:none; counter-reset:s; padding:0; margin:20px 0 0}
45
+ ol.steps > li{counter-increment:s; position:relative; padding:0 0 22px 52px; border-left:1px dashed var(--edge); margin-left:15px}
46
+ ol.steps > li:last-child{border-left-color:transparent}
47
+ ol.steps > li::before{content:counter(s); position:absolute; left:-15px; top:0; width:30px; height:30px;
48
+ border-radius:50%; background:var(--panel2); border:1px solid var(--edge); color:var(--c);
49
+ font:600 12px/28px var(--mono); text-align:center}
50
+ ol.steps > li.done::before{content:"✓"; background:var(--c); color:#0f131a; border-color:var(--c)}
51
+ .stitle{font:600 15px/1.35 var(--sans); display:flex; align-items:flex-start; gap:10px; margin-top:3px}
52
+ .stitle .chk{flex:none; width:19px; height:19px; margin-top:1px; border-radius:5px; border:1.5px solid var(--edge);
53
+ background:#0f131a; cursor:pointer; position:relative; transition:.12s}
54
+ .stitle .chk:hover{border-color:var(--c)}
55
+ li.done .chk{background:var(--c); border-color:var(--c)}
56
+ li.done .chk::after{content:"✓"; position:absolute; inset:0; display:grid; place-items:center; font-size:11px; color:#0f131a}
57
+ li.done .stitle span{color:var(--muted); text-decoration:line-through; text-decoration-color:var(--faint)}
58
+ .sbody{margin:9px 0 0; color:#c3ccd8; font-size:13.5px} .sbody p{margin:9px 0 0}
59
+ .where{font:600 11px/1 var(--mono); color:var(--faint); margin:2px 0 0; letter-spacing:.04em}
60
+ .note{margin:14px 0 0; padding:11px 13px; border-radius:7px; font-size:13px;
61
+ background:color-mix(in srgb,var(--c) 8%,transparent); border:1px solid color-mix(in srgb,var(--c) 32%,transparent); color:#ecdff2}
62
+ .note b{color:var(--c)}
63
+ .ok{margin:16px 0 0; padding:11px 13px; border-radius:7px; font-size:13px;
64
+ background:rgba(63,185,80,.07); border:1px solid rgba(63,185,80,.3); color:#a6e0ac} .ok b{color:var(--ok)}
65
+ .nav{display:flex; gap:10px; flex-wrap:wrap; margin:22px 0 0}
66
+ .nav a{font:600 12px/1 var(--mono); color:var(--muted); text-decoration:none; border:1px solid var(--edge);
67
+ border-radius:20px; padding:7px 12px} .nav a:hover{border-color:var(--c); color:var(--ink)}
68
+ footer{margin:60px 0; padding-top:20px; border-top:1px solid var(--edge); color:var(--faint); font:12px/1.6 var(--mono)}
69
+ @media (prefers-reduced-motion:reduce){*{transition:none!important}}
70
+ @media (max-width:640px){ ol.steps > li{padding-left:38px} }
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <div class="wrap">
75
+ <header>
76
+ <div class="kicker"><span class="dot"></span>Roadmap · Project hygiene · P2 · S</div>
77
+ <h1>CHANGELOG + tags that match PyPI</h1>
78
+ <p class="lede">A build-it-yourself guide. <code>CHANGELOG.md</code> has a single loose
79
+ <code>0.2.0</code> entry and no format; the repo carries <code>v0.1.1</code> and
80
+ <code>v0.2.0</code> tags. This turns that into a <b>ritual</b>: a well-formed changelog and a
81
+ git tag for every release, so GitHub and PyPI always tell the same story.</p>
82
+ <div class="goal"><b>Done when:</b> <code>CHANGELOG.md</code> follows a consistent format with an
83
+ <code>Unreleased</code> section, every published PyPI version has a matching annotated git tag,
84
+ and a documented release checklist keeps them in lockstep going forward.</div>
85
+ <div class="progress"><div class="track"><div class="fill" id="fill"></div></div>
86
+ <div class="cnt"><b id="dn">0</b> / <span id="tn">0</span> steps</div></div>
87
+ <div class="nav">
88
+ <a href="roadmap.html">← roadmap</a>
89
+ <a href="single-source-version-guide.html">← single-source version</a>
90
+ <a href="readme-install-guide.html">← README install</a>
91
+ <a href="ci-badge-guide.html">CI badge →</a>
92
+ </div>
93
+ </header>
94
+
95
+ <h2>The principle</h2>
96
+ <p style="margin-top:16px">A user who hits a bug needs to answer two questions from the repo alone:
97
+ <em>"what changed, and in which version?"</em> That works only if three things line up — the
98
+ <b>changelog</b> heading, the <b>git tag</b>, and the <b>PyPI release</b>. Right now they mostly do,
99
+ but by luck, not by process. Adopt the <a href="https://keepachangelog.com">Keep a Changelog</a>
100
+ convention and a fixed release order so it stays that way.</p>
101
+ <div class="note"><b>Do this after <a href="single-source-version-guide.html">single-sourcing the
102
+ version</a>.</b> With one version file, the release ritual is: bump <code>__version__</code> →
103
+ move the <code>Unreleased</code> notes under a dated heading → tag. One number, three derived
104
+ artifacts.</div>
105
+
106
+ <h2>Steps</h2>
107
+ <ol class="steps" id="steps">
108
+ <li data-k="ct1">
109
+ <div class="stitle"><span class="chk"></span><span>Reformat CHANGELOG.md to a consistent standard</span></div>
110
+ <div class="where">CHANGELOG.md</div>
111
+ <div class="sbody">
112
+ <p>Add a header, an <code>Unreleased</code> section at the top, and keep the existing
113
+ <code>0.2.0</code> notes under a dated, versioned heading. Backfill the <code>0.1.x</code>
114
+ releases that already shipped to PyPI so the history is complete.</p>
115
+ <pre><code># Changelog
116
+
117
+ All notable changes to ModuLearn are documented here.
118
+ This project follows [Keep a Changelog](https://keepachangelog.com) and
119
+ [Semantic Versioning](https://semver.org).
120
+
121
+ ## [Unreleased]
122
+
123
+ ## [0.2.0] — 2026-07-23
124
+ - Surface on_train crashes on the canvas (error + full traceback)
125
+ - Loss node now shapes the training curve
126
+ - CompiledGraph.check_shape() validates loaded array shape
127
+ - Enforce hyperparameter min/max at compile
128
+ - GitHub Actions CI across Python 3.10–3.13
129
+
130
+ ## [0.1.1] — add python -m entry point
131
+
132
+ ## [0.1.0] — initial release</code></pre>
133
+ <div class="note"><b>Backfill from what you know.</b> The git log has the real dates
134
+ (<code>git log --tags --simplify-by-decoration --pretty="%ci %d"</code>); fill the
135
+ <code>0.1.x</code> dates from there rather than guessing.</div>
136
+ </div>
137
+ </li>
138
+ <li data-k="ct2">
139
+ <div class="stitle"><span class="chk"></span><span>Audit that tags match PyPI</span></div>
140
+ <div class="where">terminal — one-time reconciliation</div>
141
+ <div class="sbody">
142
+ <p>List local tags and the versions PyPI actually serves, and confirm every published version
143
+ has a tag. (<code>v0.1.1</code> and <code>v0.2.0</code> already exist — this catches any gap,
144
+ e.g. a <code>0.1.0</code> release with no tag.)</p>
145
+ <pre><code>git tag --list 'v*' # local tags
146
+ pip index versions modulearn # what PyPI serves
147
+ # for any published version with no tag, tag its release commit:
148
+ git tag -a v0.1.0 &lt;commit&gt; -m "modulearn 0.1.0"
149
+ git push --tags</code></pre>
150
+ </div>
151
+ </li>
152
+ <li data-k="ct3">
153
+ <div class="stitle"><span class="chk"></span><span>Write the release checklist into PUBLISHING.md</span></div>
154
+ <div class="where">PUBLISHING.md</div>
155
+ <div class="sbody">
156
+ <p>Codify the order so future releases can't drift. The changelog and tag are steps, not
157
+ afterthoughts.</p>
158
+ <pre><code>## Cutting a release
159
+ 1. Bump `__version__` in modulearn/__init__.py
160
+ 2. Move Unreleased notes under `## [X.Y.Z] — &lt;date&gt;` in CHANGELOG.md
161
+ 3. Commit: "modulearn X.Y.Z"
162
+ 4. Tag: git tag -a vX.Y.Z -m "modulearn X.Y.Z" &amp;&amp; git push --tags
163
+ 5. Build + upload: python -m build &amp;&amp; twine upload dist/*
164
+ 6. Verify: pip index versions modulearn shows X.Y.Z, tag vX.Y.Z exists</code></pre>
165
+ </div>
166
+ </li>
167
+ <li data-k="ct4">
168
+ <div class="stitle"><span class="chk"></span><span>Verify the story is consistent</span></div>
169
+ <div class="sbody">
170
+ <p>Three artifacts, one version — check they agree for the latest release.</p>
171
+ <pre><code>grep -m1 '\[0' CHANGELOG.md # top versioned heading, e.g. [0.2.0]
172
+ git tag --list 'v0.2.0' # matching tag exists
173
+ pip index versions modulearn # PyPI serves 0.2.0
174
+ python -c "import modulearn; print(modulearn.__version__)" # 0.2.0</code></pre>
175
+ <div class="note"><b>Tag the annotated way.</b> <code>git tag -a</code> (not a lightweight
176
+ tag) records who/when/why and is what GitHub's Releases page reads. Lightweight tags look fine
177
+ locally but carry no metadata.</div>
178
+ </div>
179
+ </li>
180
+ <li data-k="ct5">
181
+ <div class="stitle"><span class="chk"></span><span>Commit</span></div>
182
+ <div class="sbody">
183
+ <pre><code>git add CHANGELOG.md PUBLISHING.md
184
+ git commit -m "Keep-a-Changelog format + release ritual; reconcile tags with PyPI"
185
+ git push
186
+ gh run list --branch main --limit 1 # expect: success</code></pre>
187
+ </div>
188
+ </li>
189
+ <li data-k="ct6">
190
+ <div class="stitle"><span class="chk"></span><span>Mark it shipped on the roadmap</span></div>
191
+ <div class="sbody"><p>Add <code>done:true,</code> to the
192
+ <code>{area:"hygiene", … title:"CHANGELOG + tags that match PyPI"}</code> item in
193
+ <code>docs/roadmap.html</code>.</p></div>
194
+ </li>
195
+ </ol>
196
+
197
+ <h2>Worth knowing</h2>
198
+ <div class="note" style="margin-top:18px"><b>A tag is a promise you can't edit.</b> Once
199
+ <code>vX.Y.Z</code> is pushed and the wheel is on PyPI, that version is immutable — PyPI refuses a
200
+ re-upload of the same version. That's exactly why the checklist puts the changelog and tag
201
+ <em>before</em> <code>twine upload</code>: get the record right while it's still cheap to fix.</div>
202
+
203
+ <footer>ModuLearn · docs/changelog-tags-guide.html · companion to
204
+ <a href="roadmap.html">roadmap.html</a> · step state is local to this browser.</footer>
205
+ </div>
206
+ <script>
207
+ const KEY="modulearn.changelog-tags.steps";
208
+ const done=new Set(JSON.parse(localStorage.getItem(KEY)||"[]"));
209
+ const lis=[...document.querySelectorAll("#steps > li")];
210
+ function paint(){lis.forEach(li=>li.classList.toggle("done",done.has(li.dataset.k)));
211
+ document.getElementById("fill").style.width=(100*done.size/lis.length)+"%";
212
+ document.getElementById("dn").textContent=done.size; document.getElementById("tn").textContent=lis.length;}
213
+ lis.forEach(li=>li.querySelector(".chk").addEventListener("click",()=>{const k=li.dataset.k;
214
+ done.has(k)?done.delete(k):done.add(k); localStorage.setItem(KEY,JSON.stringify([...done])); paint();}));
215
+ paint();
216
+ </script>
217
+ </body>
218
+ </html>
@@ -0,0 +1,174 @@
1
+ <!doctype html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>ModuLearn — Implementing: CI status badge</title>
7
+ <style>
8
+ :root{
9
+ --bg:#14181f; --panel:#1b212b; --panel2:#20262f; --edge:#2a323f;
10
+ --ink:#e6edf3; --muted:#8b98a9; --faint:#5f6b7a; --accent:#a8542f;
11
+ --c:#c98bdb; --err:#f85149; --ok:#3fb950;
12
+ --mono:ui-monospace,SFMono-Regular,Menlo,Consolas,monospace;
13
+ --sans:ui-sans-serif,system-ui,-apple-system,sans-serif;
14
+ }
15
+ *{box-sizing:border-box} html,body{margin:0}
16
+ body{background:var(--bg); color:var(--ink); font:14.5px/1.6 var(--sans);
17
+ background-image:linear-gradient(rgba(255,255,255,.022) 1px,transparent 1px),
18
+ linear-gradient(90deg,rgba(255,255,255,.022) 1px,transparent 1px);
19
+ background-size:50px 50px; -webkit-font-smoothing:antialiased;}
20
+ .wrap{max-width:860px; margin:0 auto; padding:0 22px}
21
+ code{font:12.5px/1.5 var(--mono); background:#0f131a; border:1px solid var(--edge);
22
+ border-radius:4px; padding:1px 5px; color:#c3ccd8}
23
+ pre{font:12.5px/1.6 var(--mono); background:#0f131a; border:1px solid var(--edge);
24
+ border-radius:8px; padding:13px 15px; overflow-x:auto; color:#c3ccd8; margin:12px 0}
25
+ pre code{background:none; border:none; padding:0}
26
+ a{color:var(--c)}
27
+ header{padding:60px 0 26px}
28
+ .kicker{font:600 11px/1 var(--mono); letter-spacing:.26em; text-transform:uppercase;
29
+ color:var(--muted); display:flex; align-items:center; gap:10px}
30
+ .kicker .dot{width:9px; height:9px; border-radius:50%; background:var(--c);
31
+ box-shadow:0 0 0 4px color-mix(in srgb,var(--c) 18%,transparent)}
32
+ h1{font:600 clamp(30px,5vw,46px)/1.06 var(--sans); letter-spacing:-.02em; margin:16px 0 0}
33
+ h1 code{font-size:.7em; background:#0f131a; padding:2px 9px; vertical-align:3px}
34
+ .lede{margin:16px 0 0; color:#c3ccd8; max-width:62ch} .lede b{color:var(--ink)}
35
+ .goal{margin:22px 0 0; padding:13px 15px; border-left:3px solid var(--c);
36
+ background:#0f131a; border-radius:0 7px 7px 0; font-size:13.5px} .goal b{color:var(--c)}
37
+ .progress{margin:26px 0 0; display:flex; align-items:center; gap:14px}
38
+ .track{flex:1; height:8px; border-radius:5px; background:#0f131a; border:1px solid var(--edge); overflow:hidden}
39
+ .fill{height:100%; width:0; background:var(--c); transition:width .45s cubic-bezier(.2,.7,.2,1)}
40
+ .cnt{font:600 12px/1 var(--mono); color:var(--muted); white-space:nowrap} .cnt b{color:var(--ink); font-size:15px}
41
+ h2{font:600 13px/1 var(--mono); letter-spacing:.13em; text-transform:uppercase; color:var(--muted);
42
+ margin:52px 0 0; padding-bottom:11px; border-bottom:1px solid var(--edge); display:flex; align-items:center; gap:9px}
43
+ h2::before{content:""; width:9px; height:9px; border-radius:50%; background:var(--c)}
44
+ ol.steps{list-style:none; counter-reset:s; padding:0; margin:20px 0 0}
45
+ ol.steps > li{counter-increment:s; position:relative; padding:0 0 22px 52px; border-left:1px dashed var(--edge); margin-left:15px}
46
+ ol.steps > li:last-child{border-left-color:transparent}
47
+ ol.steps > li::before{content:counter(s); position:absolute; left:-15px; top:0; width:30px; height:30px;
48
+ border-radius:50%; background:var(--panel2); border:1px solid var(--edge); color:var(--c);
49
+ font:600 12px/28px var(--mono); text-align:center}
50
+ ol.steps > li.done::before{content:"✓"; background:var(--c); color:#0f131a; border-color:var(--c)}
51
+ .stitle{font:600 15px/1.35 var(--sans); display:flex; align-items:flex-start; gap:10px; margin-top:3px}
52
+ .stitle .chk{flex:none; width:19px; height:19px; margin-top:1px; border-radius:5px; border:1.5px solid var(--edge);
53
+ background:#0f131a; cursor:pointer; position:relative; transition:.12s}
54
+ .stitle .chk:hover{border-color:var(--c)}
55
+ li.done .chk{background:var(--c); border-color:var(--c)}
56
+ li.done .chk::after{content:"✓"; position:absolute; inset:0; display:grid; place-items:center; font-size:11px; color:#0f131a}
57
+ li.done .stitle span{color:var(--muted); text-decoration:line-through; text-decoration-color:var(--faint)}
58
+ .sbody{margin:9px 0 0; color:#c3ccd8; font-size:13.5px} .sbody p{margin:9px 0 0}
59
+ .where{font:600 11px/1 var(--mono); color:var(--faint); margin:2px 0 0; letter-spacing:.04em}
60
+ .note{margin:14px 0 0; padding:11px 13px; border-radius:7px; font-size:13px;
61
+ background:color-mix(in srgb,var(--c) 8%,transparent); border:1px solid color-mix(in srgb,var(--c) 32%,transparent); color:#ecdff2}
62
+ .note b{color:var(--c)}
63
+ .ok{margin:16px 0 0; padding:11px 13px; border-radius:7px; font-size:13px;
64
+ background:rgba(63,185,80,.07); border:1px solid rgba(63,185,80,.3); color:#a6e0ac} .ok b{color:var(--ok)}
65
+ .nav{display:flex; gap:10px; flex-wrap:wrap; margin:22px 0 0}
66
+ .nav a{font:600 12px/1 var(--mono); color:var(--muted); text-decoration:none; border:1px solid var(--edge);
67
+ border-radius:20px; padding:7px 12px} .nav a:hover{border-color:var(--c); color:var(--ink)}
68
+ footer{margin:60px 0; padding-top:20px; border-top:1px solid var(--edge); color:var(--faint); font:12px/1.6 var(--mono)}
69
+ @media (prefers-reduced-motion:reduce){*{transition:none!important}}
70
+ @media (max-width:640px){ ol.steps > li{padding-left:38px} }
71
+ </style>
72
+ </head>
73
+ <body>
74
+ <div class="wrap">
75
+ <header>
76
+ <div class="kicker"><span class="dot"></span>Roadmap · Project hygiene · P3 · S</div>
77
+ <h1>CI status badge</h1>
78
+ <p class="lede">A build-it-yourself guide. The CI workflow (<code>.github/workflows/ci.yml</code>,
79
+ testing Python 3.10–3.13) already runs on every push and PR. This surfaces its result as a badge
80
+ at the top of the README — a cheap, at-a-glance signal of health for anyone evaluating the
81
+ package.</p>
82
+ <div class="goal"><b>Done when:</b> a live CI badge sits at the top of the README, links to the
83
+ workflow's runs, and shows <em>passing</em> green on <code>main</code>.</div>
84
+ <div class="progress"><div class="track"><div class="fill" id="fill"></div></div>
85
+ <div class="cnt"><b id="dn">0</b> / <span id="tn">0</span> steps</div></div>
86
+ <div class="nav">
87
+ <a href="roadmap.html">← roadmap</a>
88
+ <a href="changelog-tags-guide.html">← changelog + tags</a>
89
+ <a href="readme-install-guide.html">← README install</a>
90
+ <a href="single-source-version-guide.html">← single-source version</a>
91
+ </div>
92
+ </header>
93
+
94
+ <h2>Prerequisite</h2>
95
+ <p style="margin-top:16px">This one <b>depends on CI landing first</b> — and it has. GitHub
96
+ auto-serves a badge SVG for any workflow at a predictable URL built from the repo and the workflow
97
+ <em>file name</em>:</p>
98
+ <ol style="margin:12px 0 0; padding-left:22px; color:#c3ccd8">
99
+ <li><b>Owner/repo:</b> <code>IsaiahKoamalu/modulearn</code></li>
100
+ <li><b>Workflow file:</b> <code>ci.yml</code> (the <code>name:</code> inside is <code>CI</code> —
101
+ that's the badge's label).</li>
102
+ </ol>
103
+ <div class="note"><b>The badge tracks the default branch.</b> By default the SVG reflects the latest
104
+ run on <code>main</code>. That's what you want at the top of a README — "is <code>main</code>
105
+ green right now?"</div>
106
+
107
+ <h2>Steps</h2>
108
+ <ol class="steps" id="steps">
109
+ <li data-k="cb1">
110
+ <div class="stitle"><span class="chk"></span><span>Add the badge to the top of the README</span></div>
111
+ <div class="where">README.md — immediately under the <code># ModuLearn</code> title</div>
112
+ <div class="sbody">
113
+ <p>The image links to the workflow's run history so a click goes straight to the logs.</p>
114
+ <pre><code># ModuLearn
115
+
116
+ [![CI](https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml/badge.svg)](https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml)</code></pre>
117
+ <div class="note"><b>Pin to a branch if you like.</b> Append
118
+ <code>?branch=main</code> to the <code>badge.svg</code> URL to make the "reflects main" behavior
119
+ explicit and immune to anyone running the workflow from a fork branch.</div>
120
+ </div>
121
+ </li>
122
+ <li data-k="cb2">
123
+ <div class="stitle"><span class="chk"></span><span>Verify the badge renders and links correctly</span></div>
124
+ <div class="sbody">
125
+ <p>The SVG is public — you can fetch it before pushing to confirm the URL is right, then eyeball
126
+ it on GitHub after.</p>
127
+ <pre><code>curl -sI "https://github.com/IsaiahKoamalu/modulearn/actions/workflows/ci.yml/badge.svg" \
128
+ | grep -i "content-type" # expect: image/svg+xml
129
+ # after pushing: open the repo on GitHub — badge shows "CI passing" (green)
130
+ # click it — lands on the workflow's runs list</code></pre>
131
+ <div class="note"><b>Grey "no status"?</b> That means the workflow file name in the URL doesn't
132
+ match a real workflow, or it hasn't run on the default branch yet. Confirm the path is exactly
133
+ <code>ci.yml</code> and that a run exists via <code>gh run list --workflow ci.yml</code>.</div>
134
+ </div>
135
+ </li>
136
+ <li data-k="cb3">
137
+ <div class="stitle"><span class="chk"></span><span>Commit</span></div>
138
+ <div class="sbody">
139
+ <pre><code>git add README.md
140
+ git commit -m "README: add CI status badge"
141
+ git push
142
+ gh run list --branch main --limit 1 # expect: success (and the badge follows it)</code></pre>
143
+ </div>
144
+ </li>
145
+ <li data-k="cb4">
146
+ <div class="stitle"><span class="chk"></span><span>Mark it shipped on the roadmap</span></div>
147
+ <div class="sbody"><p>Add <code>done:true,</code> to the
148
+ <code>{area:"hygiene", … title:"CI status badge"}</code> item in
149
+ <code>docs/roadmap.html</code>.</p></div>
150
+ </li>
151
+ </ol>
152
+
153
+ <h2>Worth knowing</h2>
154
+ <div class="note" style="margin-top:18px"><b>A badge is only as honest as the suite behind it.</b> A
155
+ green badge over a thin test suite is false comfort. It pairs best with the <a href="roadmap.html">Testing</a>
156
+ work — the more the CI matrix actually covers (the compiler's type rules, server endpoints, the
157
+ round-trip), the more that green square is worth to a reader deciding whether to trust the package.</div>
158
+
159
+ <footer>ModuLearn · docs/ci-badge-guide.html · companion to
160
+ <a href="roadmap.html">roadmap.html</a> · step state is local to this browser.</footer>
161
+ </div>
162
+ <script>
163
+ const KEY="modulearn.ci-badge.steps";
164
+ const done=new Set(JSON.parse(localStorage.getItem(KEY)||"[]"));
165
+ const lis=[...document.querySelectorAll("#steps > li")];
166
+ function paint(){lis.forEach(li=>li.classList.toggle("done",done.has(li.dataset.k)));
167
+ document.getElementById("fill").style.width=(100*done.size/lis.length)+"%";
168
+ document.getElementById("dn").textContent=done.size; document.getElementById("tn").textContent=lis.length;}
169
+ lis.forEach(li=>li.querySelector(".chk").addEventListener("click",()=>{const k=li.dataset.k;
170
+ done.has(k)?done.delete(k):done.add(k); localStorage.setItem(KEY,JSON.stringify([...done])); paint();}));
171
+ paint();
172
+ </script>
173
+ </body>
174
+ </html>