diff --git a/CLAUDE.md b/CLAUDE.md index f686ca0..4f1032a 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -144,8 +144,9 @@ the secrets exist, and attaches zips + SHA256s to a draft release. C++17), as TapTools-Max does. - **Keep in sync when behavior changes:** `ReadMe.md`, the object's min metadata (`MIN_DESCRIPTION`, argument and message descriptions — min regenerates - `docs/tap.python~.maxref.xml` from them whenever the external is newer, so never hand-edit the - page; commit the regenerated one), `help/tap.python~.maxhelp`, the examples and their notebook + `docs/tap.python~.maxref.xml` from them when Max loads an external newer than the page, so never + hand-edit the page: rebuild, run Max once — `runtime-tests/run.py` says when the page was + rewritten — and commit it), `help/tap.python~.maxhelp`, the examples and their notebook (committed executed), and the plan. - **Implement from documentation and published sources only** — the CPython C-API docs, the Max SDK docs — never by reverse-engineering another product. diff --git a/docs/PRODUCTION-PLAN.md b/docs/PRODUCTION-PLAN.md index c199088..094cca6 100644 --- a/docs/PRODUCTION-PLAN.md +++ b/docs/PRODUCTION-PLAN.md @@ -300,8 +300,8 @@ passed; a third ran the soak (6.2) and measured performance (6.3). To continue: only to check a universal build), then `cmake -S . -B build -DCMAKE_BUILD_TYPE=Release && cmake --build build && ctest --test-dir build`. The external lands in `externals/`. When Max loads an external newer than `docs/tap.python~.maxref.xml`, min rewrites the page from the - object's metadata; the committed one was generated against the mock kernel, so if Max's - differs, commit Max's. (A symlinked package works for externals, but Max loads a package's + object's metadata (6.8: the build dates the `.mxo` for it, and `run.py` says when it happened); + commit Max's page when it differs. (A symlinked package works for externals, but Max loads a package's *extensions* only from a real folder — why `run.py` installs the harness as a copy.) 2. *5.2 — the help patcher.* Open `help/tap.python~.maxhelp`: its new boxes were added by hand (as JSON, in Max's layout), so check they sit sensibly and every message box works, then @@ -419,12 +419,19 @@ passed; a third ran the soak (6.2) and measured performance (6.3). To continue: nothing, and the external's own reload line is gone. Errors particular to an instance are unchanged. Pinned by a core test (two processors, a change, an unchanged reload) and a runtime test (five objects in Max: the class's diagnostic once per run of the file). -- [ ] **6.8 The reference page from Max** — runbook step 1 expects min to rewrite +- [x] **6.8 The reference page from Max** — runbook step 1 expects min to rewrite `docs/tap.python~.maxref.xml` when Max loads an external newer than it; in the Mac sessions it did not. Max's standard output had "file not found" and "failed to get date modified" lines at start-up — probably min's `doc_update` failing to resolve a path, not yet shown to come from this object. Find out why, and whether the committed page (generated against the mock kernel) is the - one Max would write. + one Max would write. *Done:* min's `doc_update` dates the external by its `.mxo` folder, and a + rebuild changes only the files inside it — the checkout's folder dated from its first build + (2026-08-05), older than the page, so the page never looked stale; a freshly unzipped release + has a new folder, which is why Max rewrote the installed package's page. The macOS build now + touches the folder after each link (a Windows `.mxe64` is one file, dated by its link already), + and `run.py` says when Max has rewritten the page, to commit it. Max's page matches the committed + one but for the description, which predated 2.4's text — now committed. The start-up lines are + not this object's: they still appear while its page is written. - [ ] **6.9 The help patcher and `numpy_allpass.py`** — the help patcher points to the `numpy_gain` and `allpass` examples but not to `numpy_allpass`, the one that shows what the block path is for; add it in Max (and re-save), perhaps with the measured comparison. diff --git a/docs/tap.python~.maxref.xml b/docs/tap.python~.maxref.xml index ab99833..6cc5050 100644 --- a/docs/tap.python~.maxref.xml +++ b/docs/tap.python~.maxref.xml @@ -7,7 +7,7 @@ Process audio with a Python class - Process audio with a Python class. The argument names a file in the package's python folder, which must define a class of the same name. The class's type-annotated public fields become attributes (int, float, bool and str; Optional allowed), and its public methods become messages called according to their signatures, with methods named int, float, symbol and bang answering those standard messages. Its process() method runs on the signal: once per signal vector when annotated with numpy arrays, or once per sample when annotated with float. An optional prepare(sample_rate, vector_size) method receives the audio settings before audio starts and whenever they change. The file is watched and reloaded when saved, keeping attribute values; errors are printed to the Max console and never take Max down. + Process audio with a Python class. The argument names a file in the package's python folder, which must define a class of the same name. The class's type-annotated public fields become attributes (int, float, bool and str; Optional allowed), and its public methods become messages called according to their signatures, with methods named int, float, symbol and bang answering those standard messages. Its process() method runs on the signal: once per signal vector when annotated with numpy arrays, or once per sample when annotated with float; its parameters are the object's signal inlets, and its return hint its outlets (a tuple of values for more than one). An optional prepare(sample_rate, vector_size) method receives the audio settings before audio starts and whenever they change. The file is watched and reloaded when saved, keeping attribute values; a save that changes process()'s inputs or outputs changes the inlets and outlets to match, keeping the patch cords of those that stay. Errors are printed to the Max console and never take Max down. diff --git a/runtime-tests/run.py b/runtime-tests/run.py index 5578a7c..e3ded73 100644 --- a/runtime-tests/run.py +++ b/runtime-tests/run.py @@ -58,6 +58,8 @@ SUPPORT = PACKAGE / "support" SUPPORT_ASIDE = PACKAGE / "support.maxtest-aside" EXTERNAL = PACKAGE / "externals" / "tap.python~.mxo" +# The reference page min rewrites from the object's descriptions (plan 6.8) +REFPAGE = ROOT / "docs" / "tap.python~.maxref.xml" OSCAR = HARNESS / "extensions" / "oscar.mxo" # Must match misc/max-test-config.json: Max listens on one port and sends to the other. @@ -556,6 +558,10 @@ def main() -> int: link.unlink(missing_ok=True) print() + if PACKAGE == ROOT and REFPAGE.exists() and subprocess.run( + ["git", "-C", str(ROOT), "diff", "--quiet", "--", str(REFPAGE)]).returncode == 1: + # min rewrites it when Max loads an external newer than it (plan 6.8) + print(f"Max rewrote {REFPAGE.relative_to(ROOT)} from the object's descriptions: review and commit it.") if failures: print(f"{len(failures)} failure(s):") for failure in failures: diff --git a/source/projects/tap.python_tilde/CMakeLists.txt b/source/projects/tap.python_tilde/CMakeLists.txt index 6147d7a..b35e712 100644 --- a/source/projects/tap.python_tilde/CMakeLists.txt +++ b/source/projects/tap.python_tilde/CMakeLists.txt @@ -120,6 +120,12 @@ if (APPLE) string(REGEX REPLACE "[^A-Za-z0-9.-]" "-" BUNDLE_IDENTIFIER "${PROJECT_NAME}") set_target_properties(${PROJECT_NAME} PROPERTIES XCODE_ATTRIBUTE_PRODUCT_BUNDLE_IDENTIFIER "${AUTHOR_DOMAIN}.${BUNDLE_IDENTIFIER}") + + # min rewrites docs/tap.python~.maxref.xml when Max loads an external newer than the page, and + # dates the external by its .mxo folder — which a rebuild leaves alone (only files inside it + # change), so the page was never rewritten from a checkout (plan 6.8). Date the folder with + # each link. + add_custom_command(TARGET ${PROJECT_NAME} POST_BUILD COMMAND touch "$") endif ()