Skip to content

bug(lists): GET /api/lists/{id} envelope mismatch makes ListsService.detail(listId:) undecodable #75

Description

@Adron

Found during the PR #68 (G23) recon pass, deliberately left unfixed there because no G23 path exercised it. Split out during the 2026-09-13 tracker reconciliation.

The defect

GET /api/lists/{id} answers the {"data":{…}} envelope. Lists.get(id:) declares Request<ListDTO> and decodes a bare ListDTO.

  • Packages/InterlinedKit/Sources/InterlinedKit/Endpoints/ListsEndpoint.swift:46
  • Packages/InterlinedDomain/Sources/InterlinedDomain/Services/ListsService.swift:359

So ListsService.detail(listId:) cannot decode a live response at all — it fails on every real call.

Why nothing has broken yet

No shipped path calls it. The shared-with-me rows in G23 come from the watching payload itself, and the owned-lists surfaces use the collection routes. The method is correct-looking, tested against a fabricated bare-ListDTO fixture, and dead.

That combination is the dangerous part: the next feature that reaches for "fetch one list by id" will hit a decode failure that looks like a server problem, and the existing green test will argue it cannot be the client.

This is the same family as two shipped defects

Both were silent decode mismatches whose tests passed against invented fixtures:

  • G21 link metadata — the server nests preview fields under metadata; the flat all-optional decode succeeded to all-nil, so every timeline link rendered host-only.
  • G25 org members — OrganizationMemberDTO required userId where live rows key by id, so the roster never rendered.

Fix

Point Lists.get(id:) at the envelope, after probing the live shape read-only rather than assuming {"data":…} — this API has documented shape drift, and single-resource routes elsewhere answer {message?, <resource>}. Replace the fabricated fixture with the captured payload. Ship the BDD quartet.

Acceptance

  • A live GET /api/lists/{id} response decodes.
  • The test fixture is a captured payload, not a hand-written one.
  • Any other Request<T> in ListsEndpoint decoding a bare DTO where the server sends an envelope is caught in the same sweep.

Implementation plan (added 2026-09-15) — fixed together with #85

The probe this issue asked for was done, and it found that #75 is the least consequential member of a family. The client models a list's schema as a DSL string; the API models it as an object. Every route in the family is wrong in both directions. The full evidence is in #85 and in docs/spikes/list-schema-wire-shapes.md.

GET /api/lists/{id} is confirmed exactly as described here:

GET /api/lists/33de2874-…
200 {"data":{"id":"33de2874-…","title":"New list",…,"properties":[]}}

So this is fixed in the same PR as #85 rather than on its own — splitting them would mean touching ListsEndpoint, ListsService and the fixtures twice for one root cause.

Scope carried by this issue

  • Lists.get(id:) → ListResponse ({message?, data}), and ListsService.detail(listId:) unwraps it.
  • The same sweep across every Request<T> in ListsEndpoint that this issue asked for. It found four more: create, update, refresh (all {message, data}) and publicList ({list, ancestors} — a third envelope convention on the same resource).
  • The fabricated bare-ListDTO fixture is replaced by the captured payload, and a test pins the regression directly: decoding the real body as a bare ListDTO must throw.

Acceptance

  • ✅ A live GET /api/lists/{id} response decodes.
  • ✅ The fixture is captured, not hand-written.
  • ✅ Every other Request<T> in ListsEndpoint decoding a bare DTO against an enveloped route is caught in the same sweep.

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugSomething isn't workingparityWeb-parity gap with the InterlinedList web app

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions