Skip to content

fix(MessageBubble): clamp future-dated timestamps to now#66

Draft
torlando-agent[bot] wants to merge 1 commit into
mainfrom
columba-suite/issue-60-message-timestamp-clamp
Draft

fix(MessageBubble): clamp future-dated timestamps to now#66
torlando-agent[bot] wants to merge 1 commit into
mainfrom
columba-suite/issue-60-message-timestamp-clamp

Conversation

@torlando-agent
Copy link
Copy Markdown
Contributor

@torlando-agent torlando-agent Bot commented May 5, 2026

Implements the iOS half of the approved plan for #60.

Summary

LXMF wire format puts a single timestamp on each message: the sender's time.time() at pack time (set in LXMessage.swift:171-179, matching RNS/LXMF/LXMessage.py:362). Columba-iOS treats that field as the canonical timestamp for both display and ordering — see MessageBubble.swift:329, 384 for display, and LXMFDatabase.swift:447, 213 for the SQL ORDER BY timestamp DESC that drives the chat ScrollView. Result: any clock skew between two devices (or any clock drift on the local device) leaks straight into the UI as out-of-order messages and "in 5 min" relative-time strings. Both observed symptoms collapse to that one cause.

The fix has two halves: (1) sort the chat scroll by local arrival time (created_at, already populated on every save in MessageRecord.swift:182-184), so a peer with a stale or fast clock can't reorder my own thread; (2) clamp displayed relative time so a future-dated wire timestamp renders as "Just now" instead of "in 5 min".

This PR is half (2) — the iOS clamp. Half (1) lives in LXMF-swift and ships as a separate PR per the plan's split.

Changes

  • Sources/ColumbaApp/Views/Messaging/MessageBubble.swift — clamp Message.formattedTime to min(timestamp, Date()) before handing to RelativeDateTimeFormatter. The bubble's absolute-time formatter at MessageDetailView.swift:527 is intentionally left alone — that view shows raw protocol-level metadata.
  • Tests/ColumbaAppTests/MessageFormattedTimeTests.swift — new test file with two cases: future-dated timestamps render as the formatter's "now" string; past-dated timestamps still render the formatter's relative-past string.
  • Columba.xcodeproj/project.pbxproj — wire the new test file into the ColumbaAppTests target (PBXBuildFile / PBXFileReference / group / sources phase).

Test plan

  • test_formattedTime_clampsFutureTimestampToNow — constructs a 600 s future-dated Message, asserts formattedTime == formatter.localizedString(for: now, relativeTo: now). Captures whatever the formatter actually emits rather than hard-coding "Just now" so locale variation doesn't break it.
  • test_formattedTime_pastTimestampsRenderRelativePast — regression guard that the clamp doesn't affect past timestamps.
  • Full suite: xcodebuild test -only-testing:ColumbaAppTests — 67 passed, 0 failed.
  • xcodebuild build clean (warnings present in the build log are pre-existing Swift 6 sendability warnings unrelated to this change).

Implementer notes

  • The full issue resolution depends on a companion LXMF-swift PR that swaps the chat-scroll ordering from wire timestamp to created_at. This iOS PR alone covers the "in 5 min" symptom only; the out-of-order-messages symptom needs the LXMF-swift PR. Commit message uses Refs #60 (not Closes) for that reason.
  • No Package.swift version bump for LXMF-swift in this PR — the LXMF-swift change isn't released yet, so bumping the pin would break SPM resolution. The bump should land in the LXMF-swift PR's follow-up or a coordination PR once the LXMF-swift release is tagged.

🤖 Generated with Claude Code

A peer with a fast clock (or our own clock corrected backward by NTP
between pack-time and view-time) can stamp a wire timestamp ahead of
local time. Rendering it through RelativeDateTimeFormatter then yields
strings like "in 5 min" on a message that has already arrived. Clamp
the timestamp passed to the formatter to min(timestamp, now) so future
wire timestamps render as the formatter's "now" string instead.

Refs #60

Co-Authored-By: Claude claude-opus-4-7 <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

0 participants