Skip to content

Restore corded hangboard assets without silhouette damage - #388

Open
Asherlc wants to merge 6 commits into
mainfrom
fix-cords-backfill-gaps-nature-climbing-stone-hanger-mini
Open

Restore corded hangboard assets without silhouette damage#388
Asherlc wants to merge 6 commits into
mainfrom
fix-cords-backfill-gaps-nature-climbing-stone-hanger-mini

Conversation

@Asherlc

@Asherlc Asherlc commented Sep 1, 2026

Copy link
Copy Markdown
Owner

Summary

  • restore source-proved cords and complete silhouettes for Nature Stone Hanger Mini primary and side, KARMA8A primary, YY Baguette reverse, and YY TravelBoard reverse
  • correct TravelBoard reverse cord gravity and its affected edge overlay
  • remove the destructive backdrop-removal production path, obsolete rembg dependency, fixture, and tests
  • add an authoring-policy guard against reintroducing segmentation, generated masks, or hard-coded pixel cleanup

Catalog audit

The cord/gravity audit covers 47 relevant presentations. Six are currently correct; 41 remain explicitly blocked because the available editor could not preserve exact product pixels, alpha, canvas, framing, and source topology. No rejected bulk-generated assets remain in the final tree.

Validation

  • HangboardPackages: 394 passed
  • package validate/status: 62 boards, zero drafts
  • isolated iOS visual harness: 1/1 passed
  • all five retained repairs reviewed in normal and active states for cord gravity, silhouette completeness, and overlay alignment
  • independent final review: no Critical or Important defects

Summary by Sourcery

Restore source-correct hangboard cord and silhouette assets while removing destructive image-processing authoring paths.

Bug Fixes:

  • Restore source-supported cord details and complete silhouettes for five affected hangboard presentations.
  • Correct the YY TravelBoard reverse cord gravity and realign its affected edge overlay.

Enhancements:

  • Remove destructive backdrop-removal authoring and enforce a policy preventing segmentation, generated masks, and hard-coded pixel cleanup from production tooling.

Build:

  • Remove the obsolete rembg optional dependency and backdrop-removal script.

Documentation:

  • Update the hangboard source audit with the repaired YY TravelBoard reverse asset, evidence, and revised geometry mapping.

Tests:

  • Replace backdrop-removal tests with an authoring-policy test that blocks image-driven authoring dependencies and capabilities.

Summary by cubic

Restores five hangboard presentations that lost their source-proved cords and full silhouettes to a destructive backdrop-removal step, and removes that pipeline so it can't damage future assets. The catalog audit covers 47 presentations (6 correct, 41 still blocked by editor limits, zero rejected bulk-generated assets), and all 394 HangboardPackages tests plus the iOS visual harness pass.

Bug Fixes

  • Restores source-proved cords and complete silhouettes for Nature Stone Hanger Mini (primary/side), KARMA8A, YY Baguette reverse, and YY TravelBoard reverse.
  • Corrects TravelBoard reverse cord gravity and moves its saved reverse-10 edge frame in board.json to stay inside the recess.

Refactors

  • Deletes remove_primary_backdrops.py, the rembg dependency, its fixture, and its tests; no remaining code imports them.
  • Adds test_authoring_policy.py that fails if production tooling imports image-processing packages.

Written for commit 90b85ce. Summary will update on new commits.

Review in cubic

@codereviewbot-ai

Copy link
Copy Markdown

🤖 Review skipped: Repository rate limit exceeded. Free accounts are limited to 2 reviews per 4 hours per repository. Upgrade to a paid plan for unlimited reviews.

@qodo-code-review

Copy link
Copy Markdown

ⓘ Qodo reviews are paused because the subscription is no longer active. Ask your workspace admin to reactivate the subscription to resume reviews. Manage billing

@sourcery-ai

sourcery-ai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Reviewer's Guide

Restores source-proved hangboard cord assets without silhouette damage, corrects the YY TravelBoard reverse overlay and gravity behavior, removes destructive backdrop-processing infrastructure, and adds tests that prevent image-driven authoring capabilities from returning.

Flow diagram for cord asset restoration and policy validation

flowchart LR
    Evidence[Official product evidence] --> Edit[Built-in asset edit]
    Existing[Existing presentation geometry] --> Edit
    Edit --> Review[Visual review]
    Review -->|accepted| Assets[Restored cord and silhouette assets]
    Review -->|rejected| Edit
    Assets --> Validate[Package and visual validation]
    Policy[Authoring-policy tests] --> Validate
    Validate --> Catalog[Validated catalog]
Loading

File-Level Changes

Change Details Files
Restored source-supported cord topology and corrected reverse-face presentation geometry for YY TravelBoard.
  • Updated the reverse asset and its content hash.
  • Documented the accepted repair process, source evidence, gravity adaptation, and refined reverse grip overlay.
  • Adjusted the reverse-10 edge path to remain within the visible recess.
Hangboards/yy-travelboard/board.json
docs/source-audits/2026-08-20-complete-hangboard-catalog.md
Removed the destructive image-processing authoring workflow and its dependency surface.
  • Deleted backdrop-removal production tooling.
  • Removed the rembg optional dependency.
  • Deleted tests and fixtures specific to backdrop removal.
Tools/HangboardPackages/pyproject.toml
Tools/HangboardPackages/scripts/remove_primary_backdrops.py
Tools/HangboardPackages/tests/test_remove_primary_backdrops.py
Added automated policy enforcement against image-driven asset authoring.
  • Scans production Python tooling for forbidden raster/segmentation imports.
  • Checks optional dependencies for forbidden image-processing packages.
  • Fails when production tooling can reintroduce masks, contours, cropping, or pixel cleanup.
Tools/HangboardPackages/tests/test_authoring_policy.py

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@coderabbitai

coderabbitai Bot commented Sep 1, 2026

Copy link
Copy Markdown

Warning

Review limit reached

Next included review available in 17 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available.

You've used all free OSS reviews for now. Wait for the free limit to reset to keep reviewing this public repository.

Learn how review limits work.

Review configuration:

⚙️ Run configuration

Configuration used: Organization UI

Review profile: ASSERTIVE

Plan: Team

Run ID: c0dbeb0a-5371-4328-bc55-b95060d36b51

📥 Commits

Reviewing files that changed from the base of the PR and between fabb453 and 90b85ce.

⛔ Files ignored due to path filters (6)
  • Hangboards/nature-stone-hanger-mini-karma8a/assets/primary.png is excluded by !**/*.png
  • Hangboards/nature-stone-hanger-mini/assets/primary.png is excluded by !**/*.png
  • Hangboards/nature-stone-hanger-mini/assets/side.png is excluded by !**/*.png
  • Hangboards/yy-baguette/assets/reverse.png is excluded by !**/*.png
  • Hangboards/yy-travelboard/assets/reverse.png is excluded by !**/*.png
  • Tools/HangboardPackages/tests/fixtures/metolius-contact-boundary.png is excluded by !**/*.png
📒 Files selected for processing (6)
  • Hangboards/yy-travelboard/board.json
  • Tools/HangboardPackages/pyproject.toml
  • Tools/HangboardPackages/scripts/remove_primary_backdrops.py
  • Tools/HangboardPackages/tests/test_authoring_policy.py
  • Tools/HangboardPackages/tests/test_remove_primary_backdrops.py
  • docs/source-audits/2026-08-20-complete-hangboard-catalog.md

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue

Prompt for AI Agents
Please address the comments from this code review:

## Individual Comments

### Comment 1
<location path="Tools/HangboardPackages/tests/test_authoring_policy.py" line_range="30-35" />
<code_context>
+    return roots
+
+
+def test_production_tooling_has_no_image_driven_authoring_capability() -> None:
+    """Keep raster segmentation, masks, contours, and cropping out of tooling."""
+    violations: list[str] = []
+    for root in PRODUCTION_PYTHON_ROOTS:
+        for path in sorted(root.rglob("*.py")):
+            forbidden = sorted(_imported_roots(path) & FORBIDDEN_IMPORT_ROOTS)
+            if forbidden:
+                violations.append(
</code_context>
<issue_to_address>
**issue (broader_impact):** The authoring-policy guard only rejects four imported module roots and non-dev dependency names; production tooling that performs segmentation, generates masks, or hard-codes pixel cleanup using the standard library or an unlisted/custom image library passes this test. This does not enforce the policy described by the test and allows the deleted destructive authoring path to be reintroduced in a different implementation.

**Triggers:** When a future production script implements image manipulation without importing PIL, cv2, rembg, or skimage.

**Suggested fix:** Add structural checks for the prohibited authoring operations and production-file patterns, or enforce the policy through an explicit allowlist of production modules and capabilities rather than only checking imports and dependency prefixes.
</issue_to_address>

Sourcery assessment

Approval pending. 1 finding to address first.

Blocking findings: Tools/HangboardPackages/tests/test_authoring_policy.py:35


Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

Comment on lines +30 to +35
def test_production_tooling_has_no_image_driven_authoring_capability() -> None:
"""Keep raster segmentation, masks, contours, and cropping out of tooling."""
violations: list[str] = []
for root in PRODUCTION_PYTHON_ROOTS:
for path in sorted(root.rglob("*.py")):
forbidden = sorted(_imported_roots(path) & FORBIDDEN_IMPORT_ROOTS)

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

issue (broader_impact): The authoring-policy guard only rejects four imported module roots and non-dev dependency names; production tooling that performs segmentation, generates masks, or hard-codes pixel cleanup using the standard library or an unlisted/custom image library passes this test. This does not enforce the policy described by the test and allows the deleted destructive authoring path to be reintroduced in a different implementation.

Triggers: When a future production script implements image manipulation without importing PIL, cv2, rembg, or skimage.

Suggested fix: Add structural checks for the prohibited authoring operations and production-file patterns, or enforce the policy through an explicit allowlist of production modules and capabilities rather than only checking imports and dependency prefixes.

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

4 issues found across 12 files

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="Tools/HangboardPackages/tests/test_authoring_policy.py">

<violation number="1" location="Tools/HangboardPackages/tests/test_authoring_policy.py:10">
P1: This guard only detects four imported roots, so production tooling can still perform segmentation, mask generation, or pixel cleanup through the standard library or another image library without failing the policy test. Inspect the prohibited operations or enforce an explicit production-capability allowlist instead of relying on this incomplete import list.</violation>

<violation number="2" location="Tools/HangboardPackages/tests/test_authoring_policy.py:34">
P3: The test passes vacuously if `src/` or `scripts/` are renamed or removed: `rglob("*.py")` on a missing directory yields nothing, so no files are scanned and `assert violations == []` still succeeds. A fail-closed policy guard should assert that production files were actually scanned.</violation>

<violation number="3" location="Tools/HangboardPackages/tests/test_authoring_policy.py:44">
P3: The guard only inspects `optional-dependencies` and never checks the top-level `project.dependencies` list. A forbidden package (rembg, Pillow, opencv, scikit-image) added to `dependencies` would bypass the policy even though the README contract says validation is stdlib-only. Check both lists.</violation>
</file>

<file name="docs/source-audits/2026-08-20-complete-hangboard-catalog.md">

<violation number="1" location="docs/source-audits/2026-08-20-complete-hangboard-catalog.md:1113">
P3: The new audit prose contradicts itself about which output is accepted. The first paragraph calls the accepted output "fix-round-2 untouched output" and says it restores cord behind both routing holes, but the fix-round-3 paragraph says the final edit makes the loops "leave their side routing paths and sag downward under gravity." The shipped reverse.png is therefore the fix-round-3 result, not the fix-round-2 output. Reword the first paragraph so the accepted base (fix-round-2) and the final accepted output (fix-round-3) are distinct, or the audit trail misidentifies the shipped asset.</violation>
</file>

Reply with feedback, questions, or to request a fix.

Re-trigger cubic


TOOL_ROOT = Path(__file__).resolve().parents[1]
PRODUCTION_PYTHON_ROOTS = (TOOL_ROOT / "src", TOOL_ROOT / "scripts")
FORBIDDEN_IMPORT_ROOTS = frozenset({"PIL", "cv2", "rembg", "skimage"})

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1: This guard only detects four imported roots, so production tooling can still perform segmentation, mask generation, or pixel cleanup through the standard library or another image library without failing the policy test. Inspect the prohibited operations or enforce an explicit production-capability allowlist instead of relying on this incomplete import list.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Tools/HangboardPackages/tests/test_authoring_policy.py, line 10:

<comment>This guard only detects four imported roots, so production tooling can still perform segmentation, mask generation, or pixel cleanup through the standard library or another image library without failing the policy test. Inspect the prohibited operations or enforce an explicit production-capability allowlist instead of relying on this incomplete import list.</comment>

<file context>
@@ -0,0 +1,53 @@
+
+TOOL_ROOT = Path(__file__).resolve().parents[1]
+PRODUCTION_PYTHON_ROOTS = (TOOL_ROOT / "src", TOOL_ROOT / "scripts")
+FORBIDDEN_IMPORT_ROOTS = frozenset({"PIL", "cv2", "rembg", "skimage"})
+FORBIDDEN_DEPENDENCY_PREFIXES = (
+    "opencv",
</file context>

"""Keep raster segmentation, masks, contours, and cropping out of tooling."""
violations: list[str] = []
for root in PRODUCTION_PYTHON_ROOTS:
for path in sorted(root.rglob("*.py")):

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The test passes vacuously if src/ or scripts/ are renamed or removed: rglob("*.py") on a missing directory yields nothing, so no files are scanned and assert violations == [] still succeeds. A fail-closed policy guard should assert that production files were actually scanned.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Tools/HangboardPackages/tests/test_authoring_policy.py, line 34:

<comment>The test passes vacuously if `src/` or `scripts/` are renamed or removed: `rglob("*.py")` on a missing directory yields nothing, so no files are scanned and `assert violations == []` still succeeds. A fail-closed policy guard should assert that production files were actually scanned.</comment>

<file context>
@@ -0,0 +1,53 @@
+    """Keep raster segmentation, masks, contours, and cropping out of tooling."""
+    violations: list[str] = []
+    for root in PRODUCTION_PYTHON_ROOTS:
+        for path in sorted(root.rglob("*.py")):
+            forbidden = sorted(_imported_roots(path) & FORBIDDEN_IMPORT_ROOTS)
+            if forbidden:
</file context>

Comment on lines +44 to +51
optional_dependencies = pyproject["project"].get("optional-dependencies", {})
for extra, dependencies in sorted(optional_dependencies.items()):
if extra == "dev":
continue
for dependency in dependencies:
normalized = dependency.casefold()
if normalized.startswith(FORBIDDEN_DEPENDENCY_PREFIXES):
violations.append(f"optional extra {extra} includes {dependency}")

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The guard only inspects optional-dependencies and never checks the top-level project.dependencies list. A forbidden package (rembg, Pillow, opencv, scikit-image) added to dependencies would bypass the policy even though the README contract says validation is stdlib-only. Check both lists.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At Tools/HangboardPackages/tests/test_authoring_policy.py, line 44:

<comment>The guard only inspects `optional-dependencies` and never checks the top-level `project.dependencies` list. A forbidden package (rembg, Pillow, opencv, scikit-image) added to `dependencies` would bypass the policy even though the README contract says validation is stdlib-only. Check both lists.</comment>

<file context>
@@ -0,0 +1,53 @@
+    pyproject = tomllib.loads(
+        (TOOL_ROOT / "pyproject.toml").read_text(encoding="utf-8")
+    )
+    optional_dependencies = pyproject["project"].get("optional-dependencies", {})
+    for extra, dependencies in sorted(optional_dependencies.items()):
+        if extra == "dev":
</file context>
Suggested change
optional_dependencies = pyproject["project"].get("optional-dependencies", {})
for extra, dependencies in sorted(optional_dependencies.items()):
if extra == "dev":
continue
for dependency in dependencies:
normalized = dependency.casefold()
if normalized.startswith(FORBIDDEN_DEPENDENCY_PREFIXES):
violations.append(f"optional extra {extra} includes {dependency}")
project = pyproject["project"]
for dependency in project.get("dependencies", []):
normalized = dependency.casefold()
if normalized.startswith(FORBIDDEN_DEPENDENCY_PREFIXES):
violations.append(f"project dependencies include {dependency}")
optional_dependencies = project.get("optional-dependencies", {})
for extra, dependencies in sorted(optional_dependencies.items()):
if extra == "dev":
continue
for dependency in dependencies:
normalized = dependency.casefold()
if normalized.startswith(FORBIDDEN_DEPENDENCY_PREFIXES):
violations.append(f"optional extra {extra} includes {dependency}")

loops and suspension cord behind both routing holes. The built-in image edit
used the existing reverse presentation as the geometry/composition target and
YY Vertical's official current-product gallery views 3 and 4 as the physical
evidence inputs. The accepted fix-round-2 untouched output restores every

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P3: The new audit prose contradicts itself about which output is accepted. The first paragraph calls the accepted output "fix-round-2 untouched output" and says it restores cord behind both routing holes, but the fix-round-3 paragraph says the final edit makes the loops "leave their side routing paths and sag downward under gravity." The shipped reverse.png is therefore the fix-round-3 result, not the fix-round-2 output. Reword the first paragraph so the accepted base (fix-round-2) and the final accepted output (fix-round-3) are distinct, or the audit trail misidentifies the shipped asset.

Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At docs/source-audits/2026-08-20-complete-hangboard-catalog.md, line 1113:

<comment>The new audit prose contradicts itself about which output is accepted. The first paragraph calls the accepted output "fix-round-2 untouched output" and says it restores cord behind both routing holes, but the fix-round-3 paragraph says the final edit makes the loops "leave their side routing paths and sag downward under gravity." The shipped reverse.png is therefore the fix-round-3 result, not the fix-round-2 output. Reword the first paragraph so the accepted base (fix-round-2) and the final accepted output (fix-round-3) are distinct, or the audit trail misidentifies the shipped asset.</comment>

<file context>
@@ -1103,7 +1103,34 @@ Reverse prompt (verbatim):
+loops and suspension cord behind both routing holes. The built-in image edit
+used the existing reverse presentation as the geometry/composition target and
+YY Vertical's official current-product gallery views 3 and 4 as the physical
+evidence inputs. The accepted fix-round-2 untouched output restores every
+source-proved cord segment—both short side loops and cord behind both routing
+holes—on the original 1774 × 887 opaque canvas.
</file context>

@github-actions

github-actions Bot commented Sep 1, 2026

Copy link
Copy Markdown

The Hangboard Workbench build for 90b85ce2fd0e0328aa65756cbbbc0a3f3705750b is ready: download the unsigned macOS arm64 artifact.

@Asherlc
Asherlc enabled auto-merge (squash) September 1, 2026 13:30
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant