Skip to content

Restore matchability of spot-crop (unity) annotations on the React Encounter page#1656

Open
JasonWildMe wants to merge 9 commits into
mainfrom
fix/react-matchable-unity-annotations
Open

Restore matchability of spot-crop (unity) annotations on the React Encounter page#1656
JasonWildMe wants to merge 9 commits into
mainfrom
fix/react-matchable-unity-annotations

Conversation

@JasonWildMe

Copy link
Copy Markdown
Collaborator

Problem

On the React Encounter page, a spot-mapping crop annotation displays with no annotation boundary and a grayed-out "New Match" button, even though the annotation is genuinely matchable (matchAgainst=true, acmId set, valid iaClass/viewpoint) and shows as matchable in obrowse.jsp.

Root cause

Spot-mapping crops are created by SubmitSpotsAndImage.java as whole-image "unity" annotations (new Annotation(species, mediaAsset) → a single unity feature) and are deliberately marked matchAgainst=true. Annotation.isTrivial() returns true for any unity feature.

The React Encounter page gated the "New Match" button on a geometry proxy (!isTrivial && bbox width/height > 0) computed live in Encounter.opensearchDocumentSerializer(), and never consulted the real eligibility fields (matchAgainst, acmId) — which weren't even serialized in the encounter API response. The April-2026 "filter invalid bounding boxes" change therefore swept up these legitimately-matchable whole-image crops.

Confirmed against the DB for the reported annotation 2d0da5d7-…: unity feature (FEATURE.TYPE_ID_OID NULL), ANNOTATION.WIDTH/HEIGHT = 0, matchAgainst=true, acmId set, MediaAsset = the crop.

Fix

Serialize the real matchability signal and gate the button on it, mirroring the backend rule (matchAgainst=true AND acmId IS NOT NULL):

  • Backend (Encounter.opensearchDocumentSerializer): emit matchAgainst and acmId per annotation. Additive; served live via jsonForApiGetBase.opensearchDocumentAsJSONObject (no reindex, no OpenSearch mapping change).
  • Frontend: new EncounterStore.hasMatchableAnnotations getter reading the raw annotations (matchAgainst === true && acmId != null, filtered by encounterId), delegated by ImageModalStore. ImageCard and ImageModal gate the New Match button on it instead of the isTrivial/bbox proxy.

The encounterAnnotations getter, the boundary-drawing rects filter, and NewMatchStore.annotationIds (which already includes the annotation, filtered only by encounterId) are intentionally unchanged. This is strictly more correct: genuine placeholder annotations (matchAgainst=false) stay hidden, while any legitimately matchable whole-image annotation reappears.

Out of scope (known limitations)

  • No boundary rectangle for whole-image crops — cosmetic; a full-image crop has no sub-region to outline.
  • Unity annotations remain non-selectable on the Encounter page (the shared encounterAnnotations getter stays filtered), so a user cannot later re-open past match results for one from the annotation-selection UI. New Match itself works end-to-end (the initiate flow navigates straight to the fresh task's results). Deferred to avoid disturbing the edit/select/task-id flows.
  • SubmitSpotsAndTransformImage.java (a different crop path that builds a real bounding-box feature and does not set matchAgainst) is unaffected and out of scope.

Testing

  • Backend: EncounterApiTest#encounterApiGetTest extended to assert matchAgainst + acmId are serialized. PASS.
  • Frontend (Jest): new tests for hasMatchableAnnotations (true for a matchAgainst+acmId unity annotation even when isTrivial; false when matchAgainst false / acmId missing / different encounterId; delegation via ImageModalStore) and for the New Match button enable/disable in ImageCard and ImageModal. All new tests PASS.
  • Verified the pre-existing 10 failures in these Jest suites are identical on origin/main (a known CI blind spot where Jest failures don't gate) — this change introduces zero new failures.

Docs

Design spec and implementation plan included under docs/superpowers/. Reviewed by Codex at the spec, plan, and final-diff stages (converged, no Major/Critical findings).

🤖 Generated with Claude Code

JasonWildMe and others added 8 commits July 1, 2026 18:16
React Encounter page gates the New Match button on an isTrivial/bbox
geometry proxy, which hides genuinely matchable whole-image spot-crop
annotations (matchAgainst=true, acmId set). Spec proposes serializing the
real matchability signal and gating the UI on it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
- Drop SearchPages ImageModalStore from scope (shared ImageModal is
  Encounter-only; SearchPages uses ImageGalleryModal, no New Match button)
- Reword root cause: unity annotations are dropped by isTrivial, not by an
  empty bbox (getBbox returns a positive full-image box for unity features)
- Document residual geometry-proxy consumers (match-results re-selection,
  edit, task-id) as a known out-of-scope limitation
- Generalize: gate supports any annotation meeting matchAgainst && acmId

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…legation test)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Encounter.opensearchDocumentSerializer now emits matchAgainst and acmId per
annotation so the React Encounter page can gate matching on real eligibility
instead of an isTrivial/bbox geometry proxy. Additive, served live via
jsonForApiGet (no reindex).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Computes matchability from raw annotations (matchAgainst && acmId), filtered
by encounterId, without the isTrivial/bbox filter of encounterAnnotations.
ImageModalStore delegates to it.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ImageCard and ImageModal now enable New Match when the image has an
annotation with matchAgainst && acmId, instead of the isTrivial/bbox
geometry proxy that hid matchable whole-image spot-crop annotations.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Use a?.acmId != null instead of truthiness so an annotation with
matchAgainst=true and acmId="" is treated as matchable, matching
Annotation.hasAcmId() / MatchEligibilityQuery. (Codex review)

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@JasonWildMe JasonWildMe added this to the 10.12 milestone Jul 2, 2026
@JasonWildMe JasonWildMe self-assigned this Jul 14, 2026
@JasonWildMe JasonWildMe requested a review from naknomum July 14, 2026 00:31
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 86.66667% with 2 lines in your changes missing coverage. Please review.
✅ Project coverage is 51.51%. Comparing base (f7a44fe) to head (1810b01).

Files with missing lines Patch % Lines
frontend/src/components/ImageModal.jsx 50.00% 1 Missing ⚠️
...ntend/src/pages/Encounter/stores/EncounterStore.js 85.71% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main    #1656      +/-   ##
==========================================
+ Coverage   51.49%   51.51%   +0.01%     
==========================================
  Files         311      311              
  Lines       12261    12268       +7     
  Branches     3956     3850     -106     
==========================================
+ Hits         6314     6320       +6     
- Misses       5665     5666       +1     
  Partials      282      282              
Flag Coverage Δ
backend 51.51% <86.66%> (+0.01%) ⬆️
frontend 51.51% <86.66%> (+0.01%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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.

2 participants