fix(chat): a non-text message type rendered an empty bubble — the approval card's fallback text was dropped (TASK-056) - #1758
Merged
Conversation
The approval card posts `message_type: 'card'` and this bubble returned a body only for `text` (or `image`), so a member reading the room here saw the author and a timestamp over nothing. `content` is written as the plain-text fallback for exactly these legacy surfaces — the card component is v2-only — so render it. TASK-056.
…-056) Wren's read of the pair is on the PR; this commits the files it read. - before = main dbd5270, after = dbd5270 + this PR's head a740d5f - surface: the legacy ChatRoom at /pods/team/6aace2b11df344bc6ccdf0ef, one seeded PG card message (`message_type='card'`, `[approval needed] …`), one backend at main - harness as in #1770, so the 390 files are 780x1800 rather than an unlabelled 1440 Measured: the bubble under the author line gains the fallback body at both widths — 807->863ch at 390, 819->875ch at 1200.
lilyshen0722
force-pushed
the
kai/task056-card-fallback-text
branch
from
September 19, 2026 17:27
6bb5ce8 to
c340541
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes TASK-056 (C4-9): a member saw "C4 smoke · less than a minute ago" over an empty bubble while the card was visible to the approver.
What I could and could not measure
I could not reproduce this live: the C4 run room is not one of my pods, the cluster is unreachable from here (no
gke-gcloud-auth-plugin), and I must not post as a human. So this is a code read, and I have separated what it settles from what it does not.Settled. The approval card is posted with
message_type: 'card'(backend/services/approvalActionService.ts:365) andcontent: '[approval needed] ' + summary, deliberately written as "the plain-text fallback so legacy surfaces (v1, digests, notifications) still show something meaningful". Infrontend/src/components/ChatRoom.tsxthe generic bubble rendered a body only formessageType === 'text'(and'image'), and that component has no payload renderer at all — ADR-020 D3's card is v2-only.systemreturns earlier,'card'matches nothing, so the message rendered an author line, a timestamp and no body. That is the reported symptom, in a component that is supposed to be its fallback consumer.Settled — the discriminator is the SURFACE, not the role. Vera checked the routing rather than reasoning about it, and I re-derived both halves myself:
frontend/src/v2/V2App.tsx:237mountspods/:podId→V2PodIdRoute, while:239(pods/:podType/:roomId) and:252(chat/:podId) still mount the legacyChatRoom. The stranger-view capture in #1749 (t3-create-card-stranger-view.png) shows the composer reading "Message #C4 run 2026-09-18" — a placeholder that exists in exactly one place in the tree (grep -rn 'Message #' frontend/src→ChatRoom.tsx:4894,Message #${room?.name || 'chat'}). So the stranger was on the legacy renderer, reachable from the v2 shell through the two-segment pod route, and the v2 path is not broken for a non-owner (V2MessageRowrendersV2ApprovalCardfor any viewer whenpayload.kind === 'approval-card'; the non-owner branch is asserted atV2ApprovalCardAction.test.tsx:178).That widens the defect rather than narrowing it, and it is why this PR is worth more than a non-approver's view: the empty body came from the message type, not the viewer, so on this surface every reader gets it — including the person who is supposed to approve. The failure is not "a non-approver cannot see the arguments"; it is "a card posted to a legacy room is invisible to everyone". Residual, named and out of scope: the legacy surface still offers no approve/decline action, so a legacy-surface approver now reads that something needs approval but cannot act from there — the same class, one step further along, and a v2-link or a legacy card control rather than a text fallback.
The fix
showTextBody = messageType === 'text' || (messageType !== 'image' && !!messageContent.trim()). The text path is unchanged,imagekeeps its own branch,systemstill returns above, and no type gains a second renderer — I enumerated the branches in this component: onlysystem,textandimageexist, so a widened gate cannot double-render one of them. The body is the server's fallback string, verbatim: a human being asked to approve something now reads that something is being asked, on the surface that is the fallback's only reader.Mutation-proven: restoring
{messageType === 'text' && (reds exactly the new case and nothing else (1 failed / 8 passed).tsc --noEmitclean.cli/srcandcommonly-mcp/src).Related, and deliberately untouched
The stored card message's counterpart on the other legacy surface is the row's own suggestion — "at least read awaiting a person's approval". The fallback text says
[approval needed], so the requirement is met without inventing a second wording. If the pod would rather the legacy bubble carry richer phrasing, that is a wording decision, not this defect.