Numeric coordinate id. It corresponds to the sub-field answer-value keys, but those
keys are STRINGS (Object.entries over a jsonb object yields "123", never 123).
Consumers MUST normalize with String(coord.id) before any Set/Map/includes
lookup against sub-field keys. Bracket indexing (obj[coord.id]) coerces and works
by accident, which is exactly what makes the other forms dangerous: new Set(keys).has(coord.id) and keys.includes(coord.id) silently miss on every row
and render blank sub-fields with no error.
Percentage-based spatial columns, carried for fidelity/forensics ONLY.
DO NOT RENDER FROM THESE. A production audit established that v1 derived them at save time by dividing CSS-pixel coordinates by the canvas device-pixel backing store, so on DPR-2 displays the persisted percentages are roughly HALF their true value. The stored values are unreliable in production and cannot be corrected after the fact (the authoring DPR is not recorded).
V2's renderer (@repo/experimental-server/pdf drawAnswers) deliberately uses the ABSOLUTE
x/y/width/height in PDF points — v1 rendered pages at intrinsic scale, so CSS px
at zoom 1 approximates PDF points — and must NOT be switched to these percentages.
They are captured because this payload is declared frozen and lossless: taking every
spatial column now avoids a snapshotVersion bump later just to add them.
One
coordinatesrow of an annotation, as-persisted: spatial fields are TEXT in the legacy schema (stringified numbers) and are intentionally NOT parsed here — the fill pipeline owns numeric interpretation, and round-tripping the raw strings keeps the snapshot lossless.NULLABILITY IS AUTHORITATIVE: every spatial column is nullable TEXT in the legacy schema, so these are
string | null; readers must handle a null spatial value.v1 semantics of
value(binding for readers):valueIS NULL → the annotation's parent bounding box.valueIS NOT NULL → a sub-field prompt box (valueholds the sub-prompt text).