ios: the row-height cache key counts replies, so a new thread link fits (#1481)
A message row is measured once and cached by content key — process-global AND persisted across launches — so anything that draws must appear in that key or the row keeps a stale frame forever. Two things draw and were missing: replyCount (the root's "N Replies" tally) and, since #1479, threadReplyCount (the same line on a quoted reply's card). A thread growing 1 -> 2 adds ~15pt to a row whose key never moved: the reconfigure notices the change, then height(for:) hands back the pre-link height and FeedMeasuredLayout lays the row out clipped. Nothing self-corrects (no estimated self-sizing), and warm() restores the same stale entry on the next launch, so the row stays wrong until the text, reactions or attachments change. Deleting a reply left the mirror gap. The tally half was already broken on main before the card link existed; one line fixes both. The cache is stamped with the binary, so no migration. Claude-Session: https://claude.ai/code/session_01EXy6kzmYE9rChy3KgciJvZ
Co-authored-by: Claude Opus 5 (1M context) <noreply@anthropic.com>
1 file changed,
+7
−0
ios/LChat/Views/StockChatFeed.swift+7 −0
diff --git a/ios/LChat/Views/StockChatFeed.swift b/ios/LChat/Views/StockChatFeed.swift| index 2c2ca64..1c4af78 100644 |
| --- a/ios/LChat/Views/StockChatFeed.swift |
| +++ b/ios/LChat/Views/StockChatFeed.swift |
| @@ -451,9 +451,16 @@ struct StockChatFeed: UIViewControllerRepresentable { |
| // so persisted keys silently never matched across real launches. |
| var textHash: UInt64 = 0xcbf29ce484222325 |
| for b in m.text.utf8 { textHash = (textHash ^ UInt64(b)) &* 0x100000001b3 } |
| + // Both counts are in the key because both DRAW: replyCount is the |
| + // root's "N Replies" tally, threadReplyCount the same line on a |
| + // quoted reply's card. A row whose thread grows 1 -> 2 gains ~15pt |
| + // it never had, and this cache outlives the reconfigure that |
| + // noticed the change (process-global, and persisted), so without |
| + // them the row keeps its pre-link frame and lays out clipped. |
| return "\(id)|\(m.at)|\(m.edited)|\(textHash)|" + |
| "\(m.reactions.count)|\(m.attachments.count)|" + |
| "\(m.reply != nil)|\(m.action)|\(hooks.foldsAsMuted(m))|" + |
| + "\(m.replyCount)|\(m.threadReplyCount)|" + |
| "\(sep)|\(startsRun)|\(width)|\(hooks.chatFontSize)|" + |
| "\(hooks.themeName)|\(hooks.isGroup)" |
| } |