From 98e86a0b32a7f503e75c18a74a4bbda53a2a12cc Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Sun, 19 Jul 2026 08:36:31 -0400 Subject: [PATCH 1/2] fix: stop gallery card alts truncating on dotted API paths teaches.split('.')[0] cut every bpy-style first sentence at bmesh.ops / bpy.context / mesh.color_attributes; split on period-plus-whitespace with a length cap and assert the build rejects the legacy form. Co-authored-by: Cursor --- docs/gallery/index.html | 42 ++++++++++++++--------------- scripts/build_gallery.py | 58 +++++++++++++++++++++++++++++++++++++++- 2 files changed, 78 insertions(+), 22 deletions(-) diff --git a/docs/gallery/index.html b/docs/gallery/index.html index 85f3202..535c989 100644 --- a/docs/gallery/index.html +++ b/docs/gallery/index.html @@ -205,7 +205,7 @@

Examples Gallery

- uv-layer-grid — The UV-layer authoring hazard — bmesh + uv-layer-grid — The UV-layer authoring hazard — bmesh.ops.create_grid(..., calc_uvs=True) is a silent no-op unless a UV layer already exists; without one an Image Texture…

uv-layer-grid

diff --git a/scripts/build_gallery.py b/scripts/build_gallery.py index 9d9bf4d..fd9c4bc 100644 --- a/scripts/build_gallery.py +++ b/scripts/build_gallery.py @@ -34,6 +34,60 @@ DATA = REPO / "examples" / "gallery.json" OUT_DIR = REPO / "docs" / "gallery" +# Soft cap for gallery index card alt text (accessibility + layout). +_ALT_CAP = 160 + + +def first_sentence(text: str) -> str: + """First sentence of *text*, splitting on period-followed-by-whitespace. + + Do not use ``str.split(".")[0]``: bpy teaches strings are full of dotted + API paths (``bmesh.ops.create_grid``, ``bpy.context.temp_override``), and + that split truncates mid-identifier. + """ + m = re.search(r"\.\s+", text) + if m: + return text[: m.start() + 1].strip() + return text.strip() + + +def card_alt(name: str, teaches: str, *, cap: int = _ALT_CAP) -> str: + """Gallery card ````: ``{name} — {first sentence}``, length-capped.""" + first = first_sentence(teaches) + if len(first) > cap: + cut = first[:cap].rsplit(" ", 1)[0].rstrip(".,;: —-") + first = (cut if cut else first[:cap].rstrip()) + "…" + return f"{name} — {first}" + + +def assert_alts_survive_dotted_paths(examples: list) -> None: + """Fail the build if any card alt is still truncated at the first ``.``. + + Catches regressions of the old ``teaches.split(".")[0]`` bug for every + example whose first sentence contains a dotted identifier. + """ + for ex in examples: + teaches = ex["teaches"] + name = ex["name"] + alt = card_alt(name, teaches) + first_dot = teaches.find(".") + if first_dot < 0: + continue + # First "." is a real sentence end (EOS or whitespace after it). + if first_dot == len(teaches) - 1 or teaches[first_dot + 1].isspace(): + continue + suffix = alt.split(" — ", 1)[-1] + if len(suffix) <= first_dot: + raise SystemExit( + f"gallery alt truncated at dotted API path for {name!r}: {alt!r}" + ) + # Old bug would have produced exactly this string: + legacy = f"{name} — {teaches.split('.')[0]}" + if alt == legacy: + raise SystemExit( + f"gallery alt still matches legacy split('.')[0] for {name!r}: {alt!r}" + ) + # --------------------------------------------------------------------------- # Shared page shell. __ROOT__ is the relative prefix from the page to the # gallery root ("" for the index, "../" for detail pages); __SITEROOT__ is the @@ -534,7 +588,7 @@ def build_index(data: dict, *, base: str, repo_root_url: str, site: str) -> str: cards = [] for ex in examples: - alt = f'{ex["name"]} — {ex["teaches"].split(".")[0]}' + alt = card_alt(ex["name"], ex["teaches"]) cards.append( CARD .replace("__TAGS__", html.escape(" ".join(ex.get("tags", [])), quote=True)) @@ -590,6 +644,8 @@ def main() -> int: print(f"ERROR: no .py script in {ex['dir']}", file=sys.stderr) return 4 + assert_alts_survive_dotted_paths(examples) + OUT_DIR.mkdir(parents=True, exist_ok=True) (OUT_DIR / "index.html").write_text( build_index(data, base=base, repo_root_url=repo_root_url, site=site), From fc4822894ba003d9bd2724c49da23806850f2207 Mon Sep 17 00:00:00 2001 From: fOuttaMyPaint Date: Sun, 19 Jul 2026 08:36:32 -0400 Subject: [PATCH 2/2] docs: version new-example prompt with VISUAL-STYLE and contact-sheet gate MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Track the agent prompt in-repo (was gitignored), pin the calibration set to armature-bend / damped-track-aim / bmesh-gear, and require reading generated HTML alt and callout text — not only gallery.json — after regen. Co-authored-by: Cursor --- .gitignore | 2 - docs/new-example-prompt.md | 131 +++++++++++++++++++++++++++++++++++++ 2 files changed, 131 insertions(+), 2 deletions(-) create mode 100644 docs/new-example-prompt.md diff --git a/.gitignore b/.gitignore index e42a9fb..47f3c44 100644 --- a/.gitignore +++ b/.gitignore @@ -33,8 +33,6 @@ ehthumbs.db # Local planning docs (not published) docs/site-upgrade-plan.md -docs/new-example-prompt.md - # Landing-page build outputs (generated at deploy time by scripts/site/build_site.py; # docs/gallery/ stays committed) docs/index.html diff --git a/docs/new-example-prompt.md b/docs/new-example-prompt.md new file mode 100644 index 0000000..934388e --- /dev/null +++ b/docs/new-example-prompt.md @@ -0,0 +1,131 @@ +# Prompt: Create a new Blender example + +Copy everything below this line into the agent. + +--- + +Create, ship, and merge one original, runnable example for this repository. The bar is +not "a working example"; it is an example strong enough to anchor the gallery on its own. + +First, review `CLAUDE.md`, `AGENTS.md`, `ROADMAP.md`, `examples/gallery.json`, +`docs/VISUAL-STYLE.md`, and several existing examples—study the strongest ones, not +just the nearest one. Use them to understand the repository's current conventions, +integration points, visual standard, and test structure. Check all existing examples +before choosing a subject so your contribution does not duplicate or slightly +repackage something already covered. + +Before editing, inspect the Git state. Start from an up-to-date `main`, preserve unrelated +work, and create a focused feature branch. Never force-push or bypass hooks. Do not include +secrets, temporary renders, caches, or unrelated files. + +Subject selection. Prefer the ROADMAP candidate pool if it has entries; otherwise choose +freely. Whatever the source, the subject must pass all of these tests before you write +code: + +- It witnesses a real, load-bearing API contract—something with observable behavior + that can drift, not a convenience wrapper or a tutorial topic. The strongest subjects + are ones AI-generated Blender code commonly gets wrong: object-mode versus edit-mode + data lifetimes, evaluated versus original data, ordering constraints, references that + dangle after CustomData reallocation, APIs renamed or restructured between 4.5 LTS + and 5.1. +- Its check can fail for a real reason. Before trusting any assertion, prove it catches + the failure it claims to catch: temporarily break the contract (skip the pose, swap + the buffer, use the legacy path) and confirm the check exits non-zero, then restore + it. An assertion that cannot fail witnesses nothing. State in the README what failure + each check would catch. +- Its correctness is independently derivable. The best checks compare Blender's output + against a closed-form or independently computed expectation (re-implemented math, a + round-trip through a raw buffer, a known geometric invariant), not against a value + captured from a previous run of the same code. +- Its render is legible as proof. Someone glancing at the thumbnail should be able to + infer what contract is being demonstrated. If the render would look the same whether + the API worked or not, redesign the scene until failure would be visible. + +Where an API diverges between 4.5 and 5.1, asserting each side's actual contract is part +of the witness—version-gate explicitly and document the divergence rather than papering +over it. If you discover an undocumented hazard while authoring (a crash, a dangling +reference, an ordering constraint), that discovery belongs in the code comments and +README; it is often more valuable than the original subject. + +The example must: + +- run headlessly in Blender 5.1 and Blender 4.5 LTS; +- perform deterministic checks of a real Blender API contract and exit non-zero on + failure, with numeric tolerances chosen deliberately and printed on success so CI + logs carry the measured values; +- default to a fast check-only run and optionally render its own gallery image; +- follow repository conventions and relevant Blender safety rules; +- include concise documentation explaining what it teaches, what each check witnesses, + what failure it would catch, and any version-gated divergence between 4.5 and 5.1; +- produce a deliberate, well-framed render rather than a mockup, primitive dump, or + placeholder. + +Complete every integration required for a shipped example. Infer the exact current +shape from neighboring examples and repository configuration, including the example +directory, README, gallery metadata and assets, plugin manifest, smoke workflow, +top-level README, and generated gallery pages. After regenerating the gallery with +`python scripts/build_gallery.py`, read the **generated** output character by +character — not only `examples/gallery.json` source fields. Open +`docs/gallery/index.html` and `docs/gallery//index.html` and inspect the +rendered `` text and the witnesses callout for duplicated words, truncation, +and generator artifacts (the previous `teaches.split(".")[0]` bug truncated alts at +dotted API paths like `bmesh.ops`; source JSON looked fine). Keep the ROADMAP +candidate pool in sync: remove the shipped subject, and add any promising subjects +you identified but did not build. Do not hand-edit release-owned version fields or +generated pages; use the repository's generator. + +Run the new example's check-only path on both supported Blender versions, render and +visually inspect its final image, regenerate the gallery, and run all relevant repository +validation. If Blender 4.5 is unavailable locally, say so precisely and use the repository's +4.5 CI job—do not substitute another version and report it as 4.5. If the render path +requires an engine or device unavailable locally (Cycles on a GPU-less host), say so and +identify exactly which paths were exercised only in CI or by inspection. Fix every +failure you introduce. + +Treat visual quality as a shipping gate with the same rigor as the checks. Conform to +`docs/VISUAL-STYLE.md` explicitly (Standard view transform, dark studio stage, AREA +key/fill/rim/wedge, designed hero materials, framing, 1280×720 → hero/preview webp). +Inspect the actual rendered pixels at full size and as the gallery thumbnail. +Iterate—the first render is a draft, not a candidate. Revise until the subject and +demonstrated API contract are immediately readable, the composition is intentional, +important geometry is not clipped, highlights are not blown out, lights or helper +objects are not visible unintentionally, and materials, lighting, background, and +camera feel designed rather than left at Blender defaults. + +Contact-sheet gate (required before shipping the still): place the new hero beside +the pinned calibration set — currently `armature-bend`, `damped-track-aim`, +`bmesh-gear` — and compare stage darkness, wedge warmth, subject fill, saturation, +and thumbnail legibility side by side. Do not ship until the new image holds up in +that lineup—not merely "looks fine alone." Update this named set in this prompt +whenever a new example outclasses one of them. One successful render command is not +proof that the image is good, and neither is the second. Report that the +contact-sheet comparison was done against those three references. + +After implementation and local verification: + +1. Review the complete diff and remove accidental churn or temporary files. +2. Commit the focused change with a conventional, release-worthy `feat:` subject that + explains why the example belongs in the repository. +3. Push the feature branch and open a pull request against `main` with a concise summary, + the API contract witnessed, what failure each check catches (and proof you falsified + it once), visual notes, and an exact test plan. Label explicitly what was proven by + live run versus established by inspection only. +4. Watch every attached PR check, including validation, manifest/count checks, ecosystem + drift, security checks, and Blender 4.5/5.1 smoke jobs. Investigate and fix failures + within this change's scope, push fixes, and repeat until all checks pass. +5. Review PR comments and requested changes. Apply valid feedback and re-run affected + checks. Do not merge with unresolved failures or requested changes. +6. Once the PR is mergeable and green, merge it using the repository's normal merge + strategy and delete the remote feature branch. +7. Wait for any automated release/version-sync commit triggered by the merge, then + fast-forward local `main` to `origin/main`, and verify main HEAD is green including + all post-merge jobs (release, validate, drift, pages deploy). Do not hand-edit + release-owned version fields. +8. Confirm the final working tree is clean and report the PR URL, merge commit, resulting + version (if released), measured check values, and checks completed. + +Do not stop at a proposal or ask me to choose the topic. Explore, choose, implement, +verify, ship, merge, synchronize, and report the completed result. Only stop for a real +blocker that requires user action, such as authentication, an unavailable required +runtime with no CI substitute, a merge policy requiring approval, or unrelated local +changes that cannot be safely preserved. \ No newline at end of file