feat(chat): gate edit and delete on the server's message windows - #724
Merged
Conversation
UserFlags gained two message-typed Duration fields (17, 18) upstream: message_edit_window and message_delete_window. Map them onto UserFlags as optional TimeIntervals, gated on hasMessageEditWindow / hasMessageDeleteWindow so an absent window is not read as zero, matching the existing billExchangeDataTimeout convention. Scaffolding only, nothing reads these yet. A follow-up wires them into MessagePolicy for the chat edit/delete affordances.
Picks up messageEditWindow and messageDeleteWindow on UserFlags. Blocked until 0.4.0 is published; ocp-client-protocol is unaffected and stays at 0.2.0.
b5432cc moved the pin in FlipcashAPI/Package.swift to 0.4.0 but left the workspace Package.resolved resolving 0.2.0, so the manifest and the lockfile disagreed and the next build to touch the workspace rewrote it. CLAUDE.md requires the workspace Package.resolved be committed. The revision matches the 0.4.0 tag (27e3f09a). ocp-client-protocol is untouched and stays at 0.2.0, which its pin and lockfile entry already agree on.
Keeps iOS on the same package version as Android. 0.3.0 over 0.2.0 is Android-only content — R8 keep rules for the generated messages, plus CHANGELOG and README. No .proto and no Swift changed, so this carries no contract change and nothing in FlipcashAPI moves. Pin and workspace lockfile updated together; the revision matches the 0.3.0 tag (7c37ecc0). Verified with Scripts/build.sh, which resolved 0.3.0 and left the lockfile entry as written.
* feat(chat): gate edit and delete on the server's message windows The transcript offered Edit and Delete on every confirmed message of your own. `UserFlags` has carried `messageEditWindow` and `messageDeleteWindow` since the parent branch, but nothing read them: `ConversationLoadCoordinator` passed `MessagePolicy.default`, which set no windows at all. `MessagePolicy` now takes a `deleteWindow` alongside `editWindow` and is built from the flags at the call site. Where the server sends nothing, it falls back to 15 minutes for edit and 48 hours for delete, as `fallbackEditWindow` and `fallbackDeleteWindow`. `Session.userFlags` is optional and a failed fetch never assigns, so optional-chaining collapses absent flags, a cached row, and an unset field into that one fallback — there is no separate failure path. That inverts the old rule, and the doc comment says so: the client can now hide an edit the server would have accepted. An affordance the server rejects is the worse failure, and Android applies the same two values, so both clients offer the same rows. Resolving against a real clock is the part `map` was written to avoid. Its comment recorded the constraint — `now: message.date` made elapsed time zero, so a window could never lapse, and reading `Date.now` inside `map` would cost the `Inputs` equality short-circuit that keeps an unrelated tick free. `now` is now carried in `Inputs` and advanced only by `scheduleWindowExpiry`, which asks `MessageCapability.nextExpiry` for the soonest deadline in the window and sleeps until it. Nothing polls: with no expiring message there is no timer, and each firing schedules only the next one. Waking a second past the deadline avoids re-granting at the inclusive boundary; a one-hour clamp keeps a 48-hour delete window from parking a task behind a transcript nobody is reading. The boundary is `<=`, matching Android — a message at exactly the window length is still actionable. * fix(chat): keep the deadline that lands exactly on now `isWithin` grants a capability at exactly the window's length, but `nextExpiry` dropped a deadline equal to `now`. At that instant the row still offers Edit or Delete with no timer armed to take it away, so it stays offered until an unrelated re-map runs. `now` is an injected parameter in both functions, so this is reachable from a test rather than only from `Date`'s resolution — the new case pins it. `>=` cannot re-arm on itself: `ConversationLoadCoordinator` wakes at `deadline + expiryGrace`, a second past the instant it just scheduled for.
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.
Brings the message-window work onto
main. #722merged into this branch rather than into
main, so none of it has landed yet.UserFlagsnow carriesmessageEditWindowandmessageDeleteWindow, andMessagePolicyturnsthem into the two gates.
MessageCapability.resolvewithholds Edit and Delete once a message ispast its window;
nextExpiryreturns the soonest deadline still ahead, andConversationLoadCoordinatorwakes atdeadline + 1sto re-map the transcript so a row loses itsactions on time instead of at the next unrelated redraw.
Both windows fall back to 15 minutes and 48 hours when the server sends nothing. That is a
deliberate reversal of the earlier rule, which left the action open on the grounds that a
client-side window could only hide something the server would have accepted. It can now do exactly
that — but an affordance the server will reject is the worse failure, and the fallbacks match
Android's, so the two clients offer the same rows for the same message.
The window boundary is inclusive on both sides:
resolvegrants the capability at exactly thewindow's length, and
nextExpirykeeps a deadline landing exactly onnow. Splitting them wouldleave a row actionable with no timer armed to take it away.
nowis injected into both, sodeadlineExactlyAtNowIsScheduledpins that instant rather than leaving it toDate's resolution.The proto pins move with it —
ocp-client-protocolto 0.3.0 andflipcash2-client-protocolto0.4.0 — since
hasMessageEditWindowcomes from the latter.Two notes for review:
diff still shows the
UserFlagsfields that are already onmain. The merge itself is clean.#723, so it is still pinned to
shared-core 0.3.1 against
main's 0.4.1. Mergingmainin picks that up.