feat(emoji): latest-set-wins union for custom emoji across desktop, mobile, and CLI#989
Merged
Conversation
…obile, and CLI When members claim the same shortcode, the workspace palette now prefers the most recently published kind:30030 set (created_at desc), tie-breaking equal timestamps to the lexicographically-smallest URL. created_at is signed event data, so the result stays deterministic and independent of fetch order — the original motivation for smallest-URL-wins. This also makes ownership recoverable: a member re-publishing their set reclaims their shortcodes from any older set that carries them. The CLI union previously deduped by (shortcode, url) and could emit two URLs for one shortcode; it now matches desktop and mobile exactly: one entry per shortcode, latest wins, sorted by shortcode. Co-authored-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1qyvc0c5kl4gqv2fd97fsk46tu378sqgy35vc83rvgfwne90sel7s0ed67d <011987e296fd5006292d2f930b574be47c7801048d1983c46c425d3c95f0cffd@sprout-oss.stage.blox.sqprod.co>
* origin/main: (33 commits) fix(desktop): make Windows release compile cleanly (#1029) Add production Docker Compose bundle (#985) feat(profile): show active turn badges on agent profile panel and popover (#1026) chore(release): release version 0.3.20 (#1027) fix(release): resolve Windows sidecar path and Linux AppImage updater format (#1024) chore(release): release version 0.3.19 (#1014) fix(release): ignore prerelease tags in changelog generation (#1021) fix: repair main build after cross-PR merge skew (#1020) feat(agents): show per-turn duration and prune dead turns within ~25s of host crash (#1017) fix(release): replace hermit with native tool setup on Windows job (#1018) feat(acp): surface error-class outcomes to the activity feed only, never the channel (#1010) fix(desktop): migrate Sprout workspace storage (#1016) feat(auth): force token refresh on rejected token (401/403), never the browser (#1015) fix(release): mark prerelease versions so they do not become latest (#1013) feat(acp): implement systemPrompt with protocol version gating (#981) fix(release): update repository name check from block/sprout to block/buzz (#1012) feat(release): all-OS desktop builds + universal auto-update manifest (#1011) Add relay disconnect UX: friendly errors, reconnect, cached identity (#1004) feat(agents): add active turn indicators to Agents Menu (#1005) ci: add fork guards to docker, release, and auto-tag workflows (#1007) ... Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
Co-authored-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co> Signed-off-by: npub1t2tgm7d8f995uqvmnm8h88sg3wnpp9a5xysjf6dg3tjmgt3ltulqdp8ehr <5a968df9a7494b4e019b9ecf739e088ba61097b4312124e9a88ae5b42e3f5f3e@sprout-oss.stage.blox.sqprod.co>
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.
Summary
Switch the custom-emoji union from "lexicographically-smallest URL wins" to latest-set-wins across all three clients:
desktop/src/shared/api/customEmoji.ts):unionCustomEmoji()now tracks(url, created_at)per shortcode and prefers the most recently published set, tie-breaking equal timestamps to the smallest URL.mobile/lib/features/custom_emoji/custom_emoji.dart, one Dart codebase covering iOS + Android): same rule.crates/buzz-cli/src/commands/emoji.rs): same rule. This also fixes a pre-existing divergence — the CLI deduped by(shortcode, url)and could list two URLs for one shortcode; it now collapses to one entry per shortcode like the other clients.Why
The old rule made shortcode collisions resolve arbitrarily (whichever URL sorts smaller) and meant a member re-publishing their set could never reclaim a contested shortcode. With latest-wins, the most recent signed event owns the shortcode — which is what you'd expect, and what makes the
sprout:→buzz:emoji-tag cutover clean: a bulk republished union set is automatically superseded per-shortcode whenever an original owner edits their own set.Determinism is preserved:
created_atis part of the signed event, not fetch order, so the same set of events always yields the same palette. The smallest-URL rule survives as the tie-break for equal timestamps (second resolution — ties are realistic).Verification
npm test— 628/628, typecheck cleanflutter test— 384 passing,dart analyzecleancargo test -p buzz-cli— 117 passing,cargo clippy -- -D warningsandfmt --checkclean