Bug: /v1/threads/resolve picks Violet (#7F00FF) over Blue (#1E90FF) for #0000FF input; alternatives limited to 3 with no way to widen #30

Closed
opened 2026-09-09 20:29:53 +00:00 by Mark · 1 comment
Owner

Summary

POST /v1/threads/resolve with input #0000FF (pure blue) returns best.code=613 Violet #7F00FF at deltaE=17.13, even though the catalog contains code=405 Blue #1E90FF which is perceptually closer to pure blue. The resolver only returns 3 alternatives per match, so the actual Blue thread is never surfaced.

Reproduction

curl -s -X POST http://127.0.0.1:8080/v1/threads/resolve \
  -H 'Content-Type: application/json' \
  -d '{"colors": ["#0000FF"]}'

# Returns:
# {"matches":[{"src":"#0000FF","best":{"code":"613","name":"Violet","hex":"#7F00FF"},"deltaE":17.1332,"alternatives":[
#   {"code":"620","name":"Magenta","hex":"#FF00FF","deltaE":57.9709},
#   {"code":"107","name":"Dark Fuchsia","hex":"#8B008B","deltaE":71.1035},
#   {"code":"420","name":"Electric Blue","hex":"#007FFF","deltaE":73.4665}]}]}

# Catalog also has Blue (code=405, hex=#1E90FF) but it's not in the alternatives.

Two distinct issues

1. Result feels wrong to users

The catalog names "Blue" the thread #1E90FF, but resolving #0000FF returns Violet as the best match. A designer uploading a #0000FF design and seeing "Violet" suggested is going to be confused.

D2000 weights perceptual dimensions unevenly — RGB-Euclidean distances are equal (both candidates are 127 units from (0,0,255) in RGB), but D2000 may legitimately choose violet. If the algorithm is correct, the catalog should add a true-blue thread (or rename Violet to acknowledge its proximity to pure blue). If the algorithm is wrong, the resolver needs tuning.

2. No way to widen the alternative set

The response always returns exactly 3 alternatives. There's no limit, max_alternatives, or include parameter in the ResolveThreadsRequest schema (/v1/openapi.json components.schemas.ResolveThreadsRequest only has colors: list[str] min=1 max=256). A client that wants to see all threads within deltaE<50 has no way to ask.

Suggested fix

  • Add alternatives_limit: int = 3 (or similar) parameter to ResolveThreadsRequest, capped at e.g. 20
  • Document the algorithm choice in docs/api.md so designers know D2000 is in play
  • Audit catalog: should #1E90FF be renamed, or should we add a #0000FF-adjacent thread?

Environment

  • service version: 0.1.0
  • Catalog: 61 entries total
  • Probed 2026-09-09

UI verification follow-up (2026-09-10)

The file upload barrier is now cracked (DataTransfer + Object.defineProperty + dispatchEvent). Confirmed via BrowserOS MCP snapshot that ThreadCraft's settings panel unlocks after upload but:

  • FABRIC TYPE / STABILIZER / HOOP SIZE comboboxes in the Design Settings panel render all options in the accessibility tree but clicking any option via act click / act kind=select / PointerEvent sequence closes the dropdown without updating React state. The next snapshot re-shows the original option as [selected]. Captured React props via __reactProps confirmed onClick exists but does not fire under synthetic dispatch.
  • Auto-Digitize (toolbar FAB e12 or inner-modal Confirm & Edit e65) sets Redux state to 'digitizing' then 'digitized' with a hardcoded "Auto-digitize complete!" message but never fires runAutoDigitize() → digitize() → POST to /v1/digitize. BrowserOS MCP hook fetch confirmed only /healthz GETs during the click sequence. Service log shows zero digitize POSTs across the entire 2026-09-09/10 verification session.

This means the UI cannot drive a digitize or export at all in the current ThreadCraft build. The API bugs (#27-31) are still the canonical surface; the UI bugs are symptom-level and tracked separately as #32 (silent no-op), #33 (combobox click swallowed), #34 (false-complete message). Those three will be closed as duplicates of this issue family once the canonical root causes land.

## Summary `POST /v1/threads/resolve` with input `#0000FF` (pure blue) returns `best.code=613 Violet #7F00FF` at deltaE=17.13, even though the catalog contains `code=405 Blue #1E90FF` which is perceptually closer to pure blue. The resolver only returns **3 alternatives** per match, so the actual Blue thread is never surfaced. ## Reproduction ```bash curl -s -X POST http://127.0.0.1:8080/v1/threads/resolve \ -H 'Content-Type: application/json' \ -d '{"colors": ["#0000FF"]}' # Returns: # {"matches":[{"src":"#0000FF","best":{"code":"613","name":"Violet","hex":"#7F00FF"},"deltaE":17.1332,"alternatives":[ # {"code":"620","name":"Magenta","hex":"#FF00FF","deltaE":57.9709}, # {"code":"107","name":"Dark Fuchsia","hex":"#8B008B","deltaE":71.1035}, # {"code":"420","name":"Electric Blue","hex":"#007FFF","deltaE":73.4665}]}]} # Catalog also has Blue (code=405, hex=#1E90FF) but it's not in the alternatives. ``` ## Two distinct issues ### 1. Result feels wrong to users The catalog names "Blue" the thread `#1E90FF`, but resolving `#0000FF` returns **Violet** as the best match. A designer uploading a `#0000FF` design and seeing "Violet" suggested is going to be confused. D2000 weights perceptual dimensions unevenly — RGB-Euclidean distances are equal (both candidates are 127 units from `(0,0,255)` in RGB), but D2000 may legitimately choose violet. If the algorithm is correct, the catalog should add a true-blue thread (or rename Violet to acknowledge its proximity to pure blue). If the algorithm is wrong, the resolver needs tuning. ### 2. No way to widen the alternative set The response always returns exactly 3 alternatives. There's no `limit`, `max_alternatives`, or `include` parameter in the `ResolveThreadsRequest` schema (`/v1/openapi.json` components.schemas.ResolveThreadsRequest only has `colors: list[str] min=1 max=256`). A client that wants to see all threads within deltaE<50 has no way to ask. ## Suggested fix - Add `alternatives_limit: int = 3` (or similar) parameter to `ResolveThreadsRequest`, capped at e.g. 20 - Document the algorithm choice in docs/api.md so designers know D2000 is in play - Audit catalog: should `#1E90FF` be renamed, or should we add a `#0000FF`-adjacent thread? ## Environment - service version: 0.1.0 - Catalog: 61 entries total - Probed 2026-09-09 --- **UI verification follow-up (2026-09-10)** The file upload barrier is now cracked (DataTransfer + Object.defineProperty + dispatchEvent). Confirmed via BrowserOS MCP `snapshot` that ThreadCraft's settings panel unlocks after upload but: - FABRIC TYPE / STABILIZER / HOOP SIZE comboboxes in the Design Settings panel render all options in the accessibility tree but **clicking any option via `act click` / `act kind=select` / PointerEvent sequence closes the dropdown without updating React state**. The next snapshot re-shows the original option as `[selected]`. Captured React props via `__reactProps` confirmed `onClick` exists but does not fire under synthetic dispatch. - Auto-Digitize (toolbar FAB `e12` or inner-modal Confirm & Edit `e65`) sets Redux state to `'digitizing'` then `'digitized'` with a hardcoded "Auto-digitize complete!" message **but never fires `runAutoDigitize()` → `digitize()` → POST to `/v1/digitize`**. BrowserOS MCP `hook fetch` confirmed only `/healthz` GETs during the click sequence. Service log shows zero digitize POSTs across the entire 2026-09-09/10 verification session. This means **the UI cannot drive a digitize or export at all in the current ThreadCraft build**. The API bugs (#27-31) are still the canonical surface; the UI bugs are symptom-level and tracked separately as #32 (silent no-op), #33 (combobox click swallowed), #34 (false-complete message). Those three will be closed as duplicates of this issue family once the canonical root causes land.
Author
Owner

Visual verification (2026-09-09): All four screenshots show empty canvas state. The thread picker / palette UI is only rendered after a successful upload + digitize. BrowserOS MCP cannot drive the hidden file input to upload a pure-blue SVG, so this bug was not reproduced through the GUI. The curl probe (showing Violet #7F00FF returned for input #0000FF while the catalog contains actual Blue #1E90FF) is still the primary evidence.

Visual verification (2026-09-09): All four screenshots show empty canvas state. The thread picker / palette UI is only rendered after a successful upload + digitize. BrowserOS MCP cannot drive the hidden file input to upload a pure-blue SVG, so this bug was not reproduced through the GUI. The curl probe (showing Violet #7F00FF returned for input `#0000FF` while the catalog contains actual Blue #1E90FF) is still the primary evidence.
Mark 2026-09-10 04:02:24 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
Mark/easystitch-service#30
No description provided.