Found while implementing #96 (see PR #103), and verified independently against the backend source.
The mismatch
APIClient.refreshMessageMetadata (Services/APIClient.swift:382-391) decodes:
struct Response: Decodable {
struct Meta: Decodable { let links: [MessageLinkPreview]? }
let metadata: Meta? // <- expects a nested "metadata" object
}
return response.metadata?.links ?? []
app/api/messages/[id]/metadata/route.ts returns, at both exit points (:86, :105):
return NextResponse.json(serialize({ links: linkMetadataItems }), …)
Top level links, no metadata wrapper. serialize() normalizes values; it never restructures.
So response.metadata is always nil and the function always returns [] against the live API.
Each entry is also a full LinkMetadataItem — nested metadata object, thumbnail rather than
image, plus platform / fetchStatus / fetchedAt. MessageLinkPreview's flat
{url,title,description,image} shape matches nothing the backend sends; the closest existing type is
CanonicalLinkPreview, which uses imageUrl.
The test is the worrying part
test_refreshMessageMetadata_* passes today. It passes because its fixture was hand-written to the
assumed shape rather than a captured response. So the suite actively asserts the wrong contract.
This is the response-direction twin of the defaultVisibility bug (#46): there, a request key the
route ignored; here, a response shape the route never produces. Both are green-and-wrong.
Build this
Acceptance criteria
Blocks
#103 / #96 are inert until this lands. Does not affect #94, which ignores the response — the POST
still causes the server to fetch and persist metadata.
Files: Services/APIClient.swift:382, Models/Message.swift,
InterlinedListTests/APIClientTests/*.
Found while implementing #96 (see PR #103), and verified independently against the backend source.
The mismatch
APIClient.refreshMessageMetadata(Services/APIClient.swift:382-391) decodes:app/api/messages/[id]/metadata/route.tsreturns, at both exit points (:86,:105):Top level
links, nometadatawrapper.serialize()normalizes values; it never restructures.So
response.metadatais always nil and the function always returns[]against the live API.Each entry is also a full
LinkMetadataItem— nestedmetadataobject,thumbnailrather thanimage, plusplatform/fetchStatus/fetchedAt.MessageLinkPreview's flat{url,title,description,image}shape matches nothing the backend sends; the closest existing type isCanonicalLinkPreview, which usesimageUrl.The test is the worrying part
test_refreshMessageMetadata_*passes today. It passes because its fixture was hand-written to theassumed shape rather than a captured response. So the suite actively asserts the wrong contract.
This is the response-direction twin of the
defaultVisibilitybug (#46): there, a request key theroute ignored; here, a response shape the route never produces. Both are green-and-wrong.
Build this
links, entries shaped asLinkMetadataItem.Prefer reusing
LinkMetadataItemover keepingMessageLinkPreviewalive — check whetherMessageLinkPreviewhas any other consumer first; if not, delete it with its tests.Swift type. Add a comment in the test naming the route file it was derived from.
GETon that route (:47-48returnsthe same
{ links }shape).Acceptance criteria
Blocks
#103 / #96 are inert until this lands. Does not affect #94, which ignores the response — the POST
still causes the server to fetch and persist metadata.
Files:
Services/APIClient.swift:382,Models/Message.swift,InterlinedListTests/APIClientTests/*.