Skip to content

Commit ca5fd97

Browse files
TMHSDigitalclaude
andauthored
fix(gallery): render inline code in callouts; add the asset-sheet gate to the example prompt (#422)
The parent-inverse-orrery witness callout showed raw markdown ("Bare `.parent =` ...") because build_gallery.py only html-escaped the teaches and witnessesFix text. Both now render backtick spans as <code>, like the landing page's inline_code; alt text, an attribute that cannot carry markup, now fails the build if it contains a backtick. docs/new-example-prompt.md covered the contact-sheet gate but never the asset-sheet gate CLAUDE.md requires for asset-type examples (three of the pinned asset references are examples). It now describes the gate, the SELECT row it needs, and the asset-quality floors. Closes #368 Signed-off-by: TMHSDigital <154358121+TMHSDigital@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
1 parent 7df9a19 commit ca5fd97

4 files changed

Lines changed: 35 additions & 7 deletions

File tree

‎docs/gallery/index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -349,7 +349,7 @@ <h2><a class="stretch" href="color-attribute-wheel/">Color Attribute Wheel</a></
349349
<h2><a class="stretch" href="parent-inverse-orrery/">Parent Inverse Orrery</a></h2>
350350
<p class="slug">parent-inverse-orrery</p>
351351
<p class="teaches">Data-API parenting for a brass orrery — the keep-world idiom (child.parent = pivot; child.matrix_parent_inverse = pivot.matrix_world.inverted()) carrying arms, planets, and a two-level moon through spinning pivots.</p>
352-
<p class="witnesses"><span class="tag">witnesses</span> Bare `.parent =` really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</p>
352+
<p class="witnesses"><span class="tag">witnesses</span> Bare <code>.parent =</code> really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</p>
353353
<span class="card-link" aria-hidden="true">View example &rarr;</span>
354354
</div>
355355
</article>

‎docs/gallery/parent-inverse-orrery/index.html‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@ <h1>Parent Inverse Orrery</h1>
5353
<img src="../assets/parent-inverse-orrery-hero.webp" alt="A brass orrery on a turned walnut base: a glowing gold sun atop the column, mottled blue, green and orange planets on riser posts, orbit rings beneath them, and a silver moon." width="1280" height="720" fetchpriority="high" />
5454
</button>
5555
<p class="zoom-hint">Rendered headless by the example itself. Select it to enlarge.</p>
56-
<div class="callout"><span class="tag">witnesses</span> Bare `.parent =` really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</div>
56+
<div class="callout"><span class="tag">witnesses</span> Bare <code>.parent =</code> really does teleport the child; the idiom restores world position exactly; matrix_world stays stale until view_layer.update(); every orbit lands on its closed form.</div>
5757
<p class="taglist"><span class="tag">tags</span> <a href="../#tag=objects">objects</a> <a href="../#tag=transforms">transforms</a></p>
5858
<div class="runline">
5959
<pre id="runCmd">blender --background --python examples/parent-inverse-orrery/parent_inverse_orrery.py --</pre>

‎docs/new-example-prompt.md‎

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -148,6 +148,20 @@ is not acceptable evidence. When a new example outclasses a member, update the
148148
pinned set in `CLAUDE.md`, its canonical home. One successful render command is not
149149
proof that the image is good, and neither is the second.
150150

151+
Asset-sheet gate (required when the subject is a game asset: a prop, kit piece or
152+
vehicle the example ships as a model): render the hero asset alone (neutral
153+
three-quarter view, plain studio light, no staging, labels or comparison props)
154+
beside the pinned asset-quality reference set, whose canonical membership is also in
155+
`CLAUDE.md` § Quality Gates for Example Runs, with
156+
`python scripts/asset_sheet.py --blender PATH NAME`. Add the entry's row to that
157+
script's `SELECT` table first (`tests/check_asset_sheet_select.py` enforces it for
158+
showcase pieces). Commit the composite under `docs/gallery/asset-sheets/`, link it in
159+
the PR body, and report a verdict. The asset ships only if it is not identifiable as
160+
the least-designed object in the lineup: a strong scene can carry a weak model, and
161+
this gate removes the scene. Its measurable floors (naming, material variation, edge
162+
treatment) live in `examples/gallery_asset_quality.py`, called on the render path
163+
(exit 11 on violation).
164+
151165
After implementation and local verification:
152166

153167
1. Review the complete diff and remove accidental churn or temporary files. Stage

‎scripts/build_gallery.py‎

Lines changed: 19 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -136,6 +136,20 @@ def check_alts(entries: list) -> None:
136136
if len(alt) > ALT_MAX:
137137
raise SystemExit(f"gallery entry {e['name']!r} alt is {len(alt)} chars "
138138
f"(max {ALT_MAX}); describe the still in one sentence")
139+
if "`" in alt:
140+
# An attribute cannot carry <code>, so a backtick would be read
141+
# aloud and shown literally; teaches/witnessesFix render it (#368).
142+
raise SystemExit(f"gallery entry {e['name']!r} alt contains a backtick; "
143+
"alt text is plain prose")
144+
145+
146+
def inline_code(text: str) -> str:
147+
"""Escape text and render its `backtick` spans as <code>, as the landing
148+
page does. Unbalanced backticks stay literal."""
149+
parts = html.escape(text).split("`")
150+
if len(parts) % 2 == 0:
151+
return "`".join(parts)
152+
return "".join(f"<code>{p}</code>" if i % 2 else p for i, p in enumerate(parts))
139153

140154

141155
def check_categories(entries: list) -> None:
@@ -1446,15 +1460,15 @@ def build_detail(ex: dict, entries: list, *, base: str, repo_root_url: str, site
14461460
f'<li aria-current="page">{html.escape(name)}</li></ol></nav>')
14471461
parts.append(f' <h1>{html.escape(display_title(ex))}</h1>')
14481462
parts.append(f' <p class="slug">{html.escape(ex["dir"])}/</p>')
1449-
parts.append(f' <p>{html.escape(ex["teaches"])}</p>')
1463+
parts.append(f' <p>{inline_code(ex["teaches"])}</p>')
14501464
parts.append(" </header>")
14511465
parts.append(' <main id="main">')
14521466
parts.append(pager_html(prev, nxt, idx + 1, len(peers), noun))
14531467
parts.append(f' <button class="detail-hero" id="heroZoom" type="button" aria-label="View full size: {html.escape(ex["alt"], quote=True)}">')
14541468
parts.append(f' <img src="../assets/{html.escape(hero_file)}" alt="{html.escape(ex["alt"], quote=True)}" width="1280" height="720" fetchpriority="high" />')
14551469
parts.append(" </button>")
14561470
parts.append(f' <p class="zoom-hint">Rendered headless by the {noun} itself. Select it to enlarge.</p>')
1457-
parts.append(f' <div class="callout"><span class="tag">witnesses</span> {html.escape(ex["witnessesFix"])}</div>')
1471+
parts.append(f' <div class="callout"><span class="tag">witnesses</span> {inline_code(ex["witnessesFix"])}</div>')
14581472
cat = ex.get("category")
14591473
if cat:
14601474
parts.append(f' <p class="taglist"><span class="tag">category</span> '
@@ -1523,7 +1537,7 @@ def build_detail(ex: dict, entries: list, *, base: str, repo_root_url: str, site
15231537
.replace("__SIZES__", MINI_SIZES)
15241538
.replace("__ALT__", html.escape(r["alt"], quote=True))
15251539
.replace("__TITLE__", html.escape(display_title(r)))
1526-
.replace("__TEACHES__", html.escape(r["teaches"])))
1540+
.replace("__TEACHES__", inline_code(r["teaches"])))
15271541
parts.append(" </div>")
15281542
parts.append(" </section>")
15291543

@@ -1673,8 +1687,8 @@ def build_index(data: dict, *, base: str, repo_root_url: str, site: str, css_v:
16731687
.replace("__CATATTR__", f' data-category="{html.escape(ex["category"], quote=True)}"'
16741688
if ex.get("category") else "")
16751689
.replace("__KIND__", kind_noun(ex))
1676-
.replace("__TEACHES__", html.escape(ex["teaches"]))
1677-
.replace("__WITNESSES__", html.escape(ex["witnessesFix"]))
1690+
.replace("__TEACHES__", inline_code(ex["teaches"]))
1691+
.replace("__WITNESSES__", inline_code(ex["witnessesFix"]))
16781692
)
16791693

16801694
content = (

0 commit comments

Comments
 (0)