Skip to content

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

Merged
torlando-tech merged 2 commits into
mainfrom
fix/message-timestamp-clamp
Jun 1, 2026
Merged

fix(MessageBubble): clamp future-dated timestamps to now (supersedes #66)#86
torlando-tech merged 2 commits into
mainfrom
fix/message-timestamp-clamp

Conversation

@torlando-tech

Copy link
Copy Markdown
Owner

Re-applies the iOS half of #60 fresh on current main. Supersedes #66, which is 78 commits stale (predates the dual-backend refactor) and conflicts on both MessageBubble.swift and project.pbxproj.

Problem (still live on main)

Message.formattedTime is still the unclamped

Self.relativeFormatter.localizedString(for: timestamp, relativeTo: Date())

so a future-dated wire timestamp (peer clock skew, or our own past clock) renders "in 5 min" on a message that has already arrived.

Fix

  • MessageBubble.swift: clamp the displayed relative time to min(timestamp, now) → renders "Just now" instead. The absolute-time formatter in MessageDetailView is intentionally left alone (raw protocol metadata).
  • MessageFormattedTimeTests.swift: future-dated → formatter's "now" string; past-dated → relative-past (regression guard). Wired into ColumbaAppTests.

Verified

build-for-testing succeeds and both tests pass on the simulator (Executed 2 tests, 0 failures).

Refs #60 (display half only; the out-of-order-messages half was a separate LXMF-swift ordering change).

A future-dated wire timestamp (peer clock skew, or our own past clock)
renders 'in 5 min' on a message that already arrived. Clamp the displayed
relative time to min(timestamp, now) in Message.formattedTime so it shows
'Just now' instead. The absolute-time formatter in MessageDetailView is
intentionally left alone — it shows raw protocol metadata.

Adds MessageFormattedTimeTests (future -> now; past -> relative-past
regression guard). Verified: build-for-testing + both tests pass on sim.

Re-applies the iOS half of #60 fresh on current main — the original PR #66
predates the dual-backend refactor and is 78 commits stale with pbxproj +
MessageBubble conflicts. Supersedes #66.

Refs #60.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@greptile-apps

greptile-apps Bot commented Jun 1, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR clamps future-dated message timestamps to the current time before passing them to RelativeDateTimeFormatter, preventing "in 5 min" from appearing on already-arrived messages. It also wires in two new unit tests for the formatting path.

  • MessageBubble.swift: formattedTime now captures Date() once, computes display = min(timestamp, now), and passes both to the formatter — so a wire timestamp ahead of local time renders "Just now" rather than a future-relative string.
  • MessageFormattedTimeTests.swift: Adds a future-clamping assertion and a past-relative regression guard (anchored at 2 hr to avoid the 60-min formatter boundary noted in the previous review thread).

Confidence Score: 5/5

Safe to merge — the change is a small, self-contained clamp with no side effects on the absolute-time formatter or any other display path.

The clamping logic captures now once and feeds both arguments to the formatter correctly, so future-dated timestamps always resolve to "Just now" and past timestamps are unaffected. Tests cover both branches with offsets well clear of formatter rounding boundaries.

No files require special attention.

Important Files Changed

Filename Overview
Sources/ColumbaApp/Views/Messaging/MessageBubble.swift Clamping fix is minimal and correct: captures now once, uses min(timestamp, now) as the display date, and passes both to the formatter so relative output is always in the past or "Just now".
Tests/ColumbaAppTests/MessageFormattedTimeTests.swift Two tests cover the future-clamping and past-relative paths; offsets chosen (600 s future, 7200 s past) are safely away from formatter rounding boundaries.
Columba.xcodeproj/project.pbxproj Standard Xcode project entries for the new test file; PBXFileReference and PBXBuildFile UUIDs are unique and both target sections are updated correctly.

Flowchart

%%{init: {'theme': 'neutral'}}%%
flowchart TD
    A["formattedTime called"] --> B["let now = Date()"]
    B --> C{"timestamp > now?"}
    C -- "Yes (future-dated wire ts)" --> D["display = now"]
    C -- "No (past or present)" --> E["display = timestamp"]
    D --> F["localizedString(for: now, relativeTo: now)\n→ 'Just now'"]
    E --> G["localizedString(for: timestamp, relativeTo: now)\n→ '2 hr. ago', etc."]
Loading

Reviews (3): Last reviewed commit: "address greptile review feedback (greplo..." | Re-trigger Greptile

Comment thread Tests/ColumbaAppTests/MessageFormattedTimeTests.swift
@torlando-tech

Copy link
Copy Markdown
Owner Author

@greptile review

@codecov

codecov Bot commented Jun 1, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

Make the past-timestamp test boundary-safe: 1 hr sits on the abbreviated
formatter's min/hr rounding boundary, so the two independent Date() reads
(test vs inside formattedTime) could straddle it and flake. Use a 2-hr
mid-bucket offset and anchor 'now' once. Verified: both tests still pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@torlando-tech torlando-tech merged commit 92e97d9 into main Jun 1, 2026
3 checks passed
@torlando-tech torlando-tech deleted the fix/message-timestamp-clamp branch June 1, 2026 03:47
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.

1 participant