Problem
The client's payload guard treats an explicit null on an optional field as a type violation rather than as "absent", and because the guard is all-or-nothing, that rejects the entire payload and blanks the view.
In src/client/api.ts, the loops over RECORD_TEXT_FIELDS and RECORD_NUMBER_FIELDS, the SOURCE_TEXT_FIELDS loop in isRenderSource, and isOptionalString for message all special-case undefined only. An explicit null reaches isString/isFiniteNumber, returns false, and fails the whole payload.
This is not reachable today. projectAttentionRecord in src/shared/attention.ts returns undefined for an invalid or absent value, and JSON.stringify then omits the key, so the server cannot currently emit null for these fields. The genuinely nullable fields — target, walkthrough_url, open_uri — are handled separately and correctly.
What makes it worth filing is that nothing enforces that invariant across the boundary. The server's "omit, never null" behavior is an implementation detail of one function, the client's "null is a violation" behavior is an implementation detail of another, and the failure mode if they ever disagree is the whole dashboard going blank rather than one field degrading.
Proposed fix
Either accept null as absent in those four loops, which costs one predicate and removes the coupling, or assert the invariant on the server side so a regression there fails a test rather than a user's page. The first is cheaper and can be done independently; the second is the one that would have caught it.
This is closely related to #146, which proposes a per-field shape table for the projection, and to #164, on the same theme of the client depending on undocumented server behavior. Whoever picks up #146 should decide whether this collapses into it.
Evidence
Problem
The client's payload guard treats an explicit
nullon an optional field as a type violation rather than as "absent", and because the guard is all-or-nothing, that rejects the entire payload and blanks the view.In
src/client/api.ts, the loops overRECORD_TEXT_FIELDSandRECORD_NUMBER_FIELDS, theSOURCE_TEXT_FIELDSloop inisRenderSource, andisOptionalStringformessageall special-caseundefinedonly. An explicitnullreachesisString/isFiniteNumber, returnsfalse, and fails the whole payload.This is not reachable today.
projectAttentionRecordinsrc/shared/attention.tsreturnsundefinedfor an invalid or absent value, andJSON.stringifythen omits the key, so the server cannot currently emitnullfor these fields. The genuinely nullable fields —target,walkthrough_url,open_uri— are handled separately and correctly.What makes it worth filing is that nothing enforces that invariant across the boundary. The server's "omit, never null" behavior is an implementation detail of one function, the client's "null is a violation" behavior is an implementation detail of another, and the failure mode if they ever disagree is the whole dashboard going blank rather than one field degrading.
Proposed fix
Either accept
nullas absent in those four loops, which costs one predicate and removes the coupling, or assert the invariant on the server side so a regression there fails a test rather than a user's page. The first is cheaper and can be done independently; the second is the one that would have caught it.This is closely related to #146, which proposes a per-field shape table for the projection, and to #164, on the same theme of the client depending on undocumented server behavior. Whoever picks up #146 should decide whether this collapses into it.
Evidence
src/client/api.ts— theundefined-only checks in the field loops andisOptionalString.src/shared/attention.ts—projectValuereturningundefined, which is why the case is latent rather than live.claude-reviewon PR Add the Human Attention view with numbered cards, degraded states, and visibility-aware polling #150 at head0c44dbe6, which checked reachability against the current server before reporting it.