Decompose: Backend /v1/export and /v1/threads/resolve stub replacements #25

Closed
opened 2026-07-12 23:17:39 +00:00 by Mark · 2 comments
Owner

Context

The backend easystitch-service exposes two POST endpoints that are
stubbed:

  • POST /v1/export — should serialize stitch objects to PES/DST/EXP/JEF
    bytes for the user's embroidery machine. Currently returns
    {"error": "not_implemented"} with HTTP 200.
  • POST /v1/threads/resolve — should find the nearest Brother PR10
    catalog matches for a list of input hex colors. Currently returns
    {"error": "not_implemented"} with HTTP 200.

The HTTP-200-with-error-body shape is the worst of both worlds:
integration tests that assert "status 200 == success" pass, but no
real work is happening. Frontend's
readErrorBody (easystitch-client.ts:276) doesn't catch this
pattern because it gates on response.ok — it only fires for
4xx/5xx, not 200.

Both backend stubs must be replaced with real implementations.

Goal

Replace the two stubbed POST endpoints with real logic, returning
proper status codes and body shapes:

  • POST /v1/export → returns binary payload
    (application/octet-stream or appropriate format-specific MIME), with
    X-Stitch-Count and X-Color-Count response headers already
    consumed by exportPES at easystitch-client.ts:201-218.
  • POST /v1/threads/resolve → returns 200 with
    { matches: [{src, best: ThreadCatalogEntry, deltaE, alternatives: [...]}] }.

Non-goals

  • Authoring catalogs (catalog lookup is its own concern — see #24).
  • Real-time progress streaming (already shipped — see issues #90, #91).
  • Adding new export formats beyond PES/DST/EXP/JEF.

Pipeline stages

P1: Implement POST /v1/threads/resolve

Goal: Match a list of input hex colors against the Brother PR10
catalog (depends on #24's catalog endpoint being loadable; we'll
load the JSON in-process here).

Approach candidates:

  • A. Loop-and-distance: O(n*m) — for each input color, compute Delta-E
    against every catalog entry, return top-K.
  • B. Vectorized numpy: pre-compute the catalog Lab color array, use
    scipy.spatial.distance.cdist.

Recommend B. Already have vectorized thread-pair Delta-E check in the
engine codebase (commit history shows numpy adoption elsewhere) and a
one-time vectorized loop beats O(n*m) for any realistic palette size.

Outputs: new route handler in easystitch-service/app.py that:

  1. Reads colors: [...] from the request body (the shape already
    established by the frontend's resolveThreads).
  2. Loads the catalog (same loader as P2 of #24 — share code if we
    can).
  3. Returns {matches: [{src, best, deltaE, alternatives}, ...]}.

Acceptance:

  • POST http://localhost:8080/v1/threads/resolve with
    {"colors": ["#ff0000", "#0000ff"]} returns 200 with the
    matches shape.
  • For each input color, the best entry must have a hex field
    that is exactly equal (or very close, Delta-E < 2.0) to the input.
  • alternatives returns the next 3 closest catalog entries.
  • Malformed input (missing colors, empty colors, invalid hex) returns
    422 with structured error envelope.

Files touched:

  • /home/b/Projects/easystitch-service/app.py (add the route)
  • /home/b/Projects/easystitch-service/app.py (extract catalog
    loading helper shared with #24/P1)

P2: Add tests for /v1/threads/resolve

Goal: Backend test coverage for the resolve endpoint.

Outputs: new test file
/home/b/Projects/easystitch-service/tests/unit/test_threads_resolve.py.

Acceptance:

  • python -m pytest tests/unit/test_threads_resolve.py -v exits 0
  • Tests cover: happy path, nearest-match selection, alternative
    ordering, malformed input, empty color list.

Files touched:

  • /home/b/Projects/easystitch-service/tests/unit/test_threads_resolve.py (new)

P3: Implement POST /v1/export

Goal: Replace the stub with a real serializer. Format parameter
selects PES/DST/EXP/JEF.

Approach candidates:

  • A. Use pyembroidery library (already in service deps for format
    writer support; uses standard format specs).
  • B. Roll our own PES/DST serializers.

Recommend A. pyembroidery is the established community library for
this; re-implementing format serialization correctly is deep work
that's easy to get wrong.

Outputs: route handler in easystitch-service/app.py that:

  1. Reads stitchObjects: [...] and optional format: "pes"|"dst"|"exp"|"jef"
    from the body.
  2. Serializes via pyembroidery.write to bytes.
  3. Returns bytes with Content-Type: application/octet-stream and the
    filename in Content-Disposition.
  4. Sets X-Stitch-Count and X-Color-Count headers (count from the
    input array).

Acceptance:

  • POST /v1/export with {"stitchObjects": [...], "format": "pes"}
    returns 200, Content-Type application/octet-stream, body is
    valid PES bytes (verify with pyembroidery.read).
  • Each supported format (PES, DST, EXP, JEF) round-trips through
    pyembroidery.read.
  • Malformed input or unsupported format returns 422 with error envelope.

Files touched:

  • /home/b/Projects/easystitch-service/app.py (route handler)
  • /home/b/Projects/easystitch-service/models.py (if new request
    models are needed for the body shape)

P4: Add tests for /v1/export

Goal: Lock down P3 with backend tests including a round-trip
write → read verification for each format.

Outputs: new test file
/home/b/Projects/easystitch-service/tests/unit/test_export.py.

Acceptance:

  • python -m pytest tests/unit/test_export.py -v exits 0
  • Each format (PES, DST, EXP, JEF) gets a round-trip test.
  • Error cases (invalid format, empty stitch list, malformed body)
    return 422.

Files touched:

  • /home/b/Projects/easystitch-service/tests/unit/test_export.py (new)

Dependencies

  • P1 (resolve) and P2 (its tests) depend on the catalog-loading
    helper added by #24/P1. If #24's loader isn't ready, P1 here can do
    its own minimal catalog loader inline.
  • P3 (export) has no dependencies on the catalog work.
  • Blocks: ThreadCraft's "Export PES" button (#25 surfaces when the
    user clicks Export in the workspace).

Research required

  • Quick spike to confirm pyembroidery is on the service's deps list
    and can serialize the stitch-object shape the frontend produces.
    If not, this work moves to a different module that pulls in
    pyembroidery.
# Context The backend `easystitch-service` exposes two POST endpoints that are stubbed: - `POST /v1/export` — should serialize stitch objects to PES/DST/EXP/JEF bytes for the user's embroidery machine. Currently returns `{"error": "not_implemented"}` with HTTP 200. - `POST /v1/threads/resolve` — should find the nearest Brother PR10 catalog matches for a list of input hex colors. Currently returns `{"error": "not_implemented"}` with HTTP 200. The HTTP-200-with-error-body shape is the worst of both worlds: integration tests that assert "status 200 == success" pass, but no real work is happening. Frontend's `readErrorBody` (`easystitch-client.ts:276`) doesn't catch this pattern because it gates on `response.ok` — it only fires for 4xx/5xx, not 200. Both backend stubs must be replaced with real implementations. ## Goal Replace the two stubbed POST endpoints with real logic, returning proper status codes and body shapes: - `POST /v1/export` → returns binary payload (`application/octet-stream` or appropriate format-specific MIME), with `X-Stitch-Count` and `X-Color-Count` response headers already consumed by `exportPES` at `easystitch-client.ts:201-218`. - `POST /v1/threads/resolve` → returns 200 with `{ matches: [{src, best: ThreadCatalogEntry, deltaE, alternatives: [...]}] }`. ## Non-goals - Authoring catalogs (catalog lookup is its own concern — see #24). - Real-time progress streaming (already shipped — see issues #90, #91). - Adding new export formats beyond PES/DST/EXP/JEF. ## Pipeline stages ### P1: Implement POST /v1/threads/resolve **Goal:** Match a list of input hex colors against the Brother PR10 catalog (depends on #24's catalog endpoint being loadable; we'll load the JSON in-process here). **Approach candidates:** - A. Loop-and-distance: O(n*m) — for each input color, compute Delta-E against every catalog entry, return top-K. - B. Vectorized numpy: pre-compute the catalog Lab color array, use `scipy.spatial.distance.cdist`. Recommend B. Already have `vectorized thread-pair Delta-E check` in the engine codebase (commit history shows numpy adoption elsewhere) and a one-time vectorized loop beats O(n*m) for any realistic palette size. **Outputs:** new route handler in `easystitch-service/app.py` that: 1. Reads `colors: [...]` from the request body (the shape already established by the frontend's `resolveThreads`). 2. Loads the catalog (same loader as P2 of #24 — share code if we can). 3. Returns `{matches: [{src, best, deltaE, alternatives}, ...]}`. **Acceptance:** - `POST http://localhost:8080/v1/threads/resolve` with `{"colors": ["#ff0000", "#0000ff"]}` returns 200 with the `matches` shape. - For each input color, the `best` entry must have a `hex` field that is exactly equal (or very close, Delta-E < 2.0) to the input. - `alternatives` returns the next 3 closest catalog entries. - Malformed input (missing `colors`, empty `colors`, invalid hex) returns 422 with structured error envelope. **Files touched:** - `/home/b/Projects/easystitch-service/app.py` (add the route) - `/home/b/Projects/easystitch-service/app.py` (extract catalog loading helper shared with #24/P1) ### P2: Add tests for /v1/threads/resolve **Goal:** Backend test coverage for the resolve endpoint. **Outputs:** new test file `/home/b/Projects/easystitch-service/tests/unit/test_threads_resolve.py`. **Acceptance:** - `python -m pytest tests/unit/test_threads_resolve.py -v` exits 0 - Tests cover: happy path, nearest-match selection, alternative ordering, malformed input, empty color list. **Files touched:** - `/home/b/Projects/easystitch-service/tests/unit/test_threads_resolve.py` (new) ### P3: Implement POST /v1/export **Goal:** Replace the stub with a real serializer. Format parameter selects PES/DST/EXP/JEF. **Approach candidates:** - A. Use `pyembroidery` library (already in service deps for format writer support; uses standard format specs). - B. Roll our own PES/DST serializers. Recommend A. `pyembroidery` is the established community library for this; re-implementing format serialization correctly is deep work that's easy to get wrong. **Outputs:** route handler in `easystitch-service/app.py` that: 1. Reads `stitchObjects: [...]` and optional `format: "pes"|"dst"|"exp"|"jef"` from the body. 2. Serializes via `pyembroidery.write` to bytes. 3. Returns bytes with `Content-Type: application/octet-stream` and the filename in `Content-Disposition`. 4. Sets `X-Stitch-Count` and `X-Color-Count` headers (count from the input array). **Acceptance:** - `POST /v1/export` with `{"stitchObjects": [...], "format": "pes"}` returns 200, Content-Type `application/octet-stream`, body is valid PES bytes (verify with `pyembroidery.read`). - Each supported format (PES, DST, EXP, JEF) round-trips through `pyembroidery.read`. - Malformed input or unsupported format returns 422 with error envelope. **Files touched:** - `/home/b/Projects/easystitch-service/app.py` (route handler) - `/home/b/Projects/easystitch-service/models.py` (if new request models are needed for the body shape) ### P4: Add tests for /v1/export **Goal:** Lock down P3 with backend tests including a round-trip `write → read` verification for each format. **Outputs:** new test file `/home/b/Projects/easystitch-service/tests/unit/test_export.py`. **Acceptance:** - `python -m pytest tests/unit/test_export.py -v` exits 0 - Each format (PES, DST, EXP, JEF) gets a round-trip test. - Error cases (invalid format, empty stitch list, malformed body) return 422. **Files touched:** - `/home/b/Projects/easystitch-service/tests/unit/test_export.py` (new) ## Dependencies - **P1 (resolve) and P2 (its tests) depend on** the catalog-loading helper added by #24/P1. If #24's loader isn't ready, P1 here can do its own minimal catalog loader inline. - **P3 (export) has no dependencies** on the catalog work. - **Blocks:** ThreadCraft's "Export PES" button (#25 surfaces when the user clicks Export in the workspace). ## Research required - Quick spike to confirm `pyembroidery` is on the service's deps list and can serialize the stitch-object shape the frontend produces. If not, this work moves to a different module that pulls in pyembroidery.
Author
Owner

Auto-triage: needs decompose

Marked triage-approved + needs-decompose. decompose-executor will pick this up and break it into implementable sub-issues.

## Auto-triage: needs decompose Marked `triage-approved` + `needs-decompose`. decompose-executor will pick this up and break it into implementable sub-issues.
Mark changed title from Backend /v1/export and /v1/threads/resolve return 200 {"error":"not_implemented"} — looks healthy, isn't to Decompose: Backend /v1/export and /v1/threads/resolve stub replacements 2026-07-13 01:53:50 +00:00
Author
Owner

This issue has been decomposed into the following sub-issues:

# Title Phase Scope
#31 Decompose: Backend /v1/export and /v1/threads/resolve stub r Goal: Match a list of input hex colors against the Brother PR10 catalog (dep
#32 Decompose: Backend /v1/export and /v1/threads/resolve stub r Goal: Backend test coverage for the resolve endpoint. Outputs: new test
#33 Decompose: Backend /v1/export and /v1/threads/resolve stub r Goal: Replace the stub with a real serializer. Format parameter selects PES/
#34 Decompose: Backend /v1/export and /v1/threads/resolve stub r Goal: Lock down P3 with backend tests including a round-trip write → read

Each sub-issue has been labeled with triage-approved and is ready for queue routing.

This issue has been decomposed into the following sub-issues: | # | Title | Phase Scope | |---|-------|-------------| | #31 | Decompose: Backend /v1/export and /v1/threads/resolve stub r | **Goal:** Match a list of input hex colors against the Brother PR10 catalog (dep | | #32 | Decompose: Backend /v1/export and /v1/threads/resolve stub r | **Goal:** Backend test coverage for the resolve endpoint. **Outputs:** new test | | #33 | Decompose: Backend /v1/export and /v1/threads/resolve stub r | **Goal:** Replace the stub with a real serializer. Format parameter selects PES/ | | #34 | Decompose: Backend /v1/export and /v1/threads/resolve stub r | **Goal:** Lock down P3 with backend tests including a round-trip `write → read` | Each sub-issue has been labeled with `triage-approved` and is ready for queue routing.
Mark closed this issue 2026-07-13 01:54:55 +00:00
Sign in to join this conversation.
No description provided.