Smarter emoji completion: recents, synonyms, fuzzy matching, popularity#496
Merged
Conversation
Make the inline :emoji: picker rank like a chat app instead of a raw substring search. - EmojiUsageStore: persisted recents + frequency keyed by primary alias (variant-stable). A bare ":" now shows recents (popularity-padded) instead of an empty panel; personal favorites float up within a relevance tier. - EmojiSynonymCatalog: curated intent/slang -> alias map (lol, ty, fire, congrats, ...) so intent ranks first. - EmojiMatcher: synonym pre-tier + fuzzy fallback (subsequence + OSA edit distance for typos) + popularity tiebreak, preserving existing relevance order. - EmojiPopularity: curated popularity prior, validated against the dataset. - Clear Emoji History control in General settings. EmojiUsageStore uses a nonisolated deinit to avoid the macOS 14 isolated-deinit back-deploy crash that aborts the app-hosted tests. New pure helpers are unit tested; regenerated the Xcode project for the new files.
…ge closures - EmojiMatcher.isSubsequence: the trailing `return matched == needle.count` was always false once the loop exits (the in-loop guard is the only true path); replace it with an explicit `return false`. - EmojiPickerController: annotate the emojiUsage/recordEmojiUsage closures as @mainactor so the isolation contract is explicit, since they read and mutate main-actor EmojiUsageStore state.
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
Makes the inline
:emoji:picker rank like a modern chat composer instead of a raw substring search, without changing the trigger/commit UX. Five additions, all behind the existing picker:EmojiUsageStore,EmojiUsageSnapshot): per-user usage persisted to UserDefaults, keyed by primary alias so it is stable across skin-tone/gender variants. A bare:now shows your recents (padded with popular emoji) instead of an empty panel, and personal favorites float to the top within a relevance tier.EmojiSynonymCatalog): 164 hand-mapped words people actually type (lol→😂,ty→🙏,fire/lit→🔥,congrats→🎉,omg,smh, ...) boost the canonical alias they mean.EmojiMatcher): when lexical results are sparse, a subsequence + optimal-string-alignment edit-distance pass catches typos (hapy→😄,recieve→...). Gated to queries ≥3 chars so it never pollutes good results.EmojiPopularity): a curated, dataset-validated ordering used as a within-tier tiebreak and as the bare-:starter set for new users.Relevance order is unchanged (exact alias > prefix > keyword/name > substring > fuzzy); recents/favorites/popularity only break ties, so a more relevant match is never demoted. Ranking stays a pure function of (query, catalog, usage snapshot).
Validation
New unit tests cover every pure helper (
EmojiPopularityTests,EmojiSynonymCatalogTests,EmojiRecentsTests, ranking/fuzzy additions inEmojiCatalogMatcherTests), the store (EmojiUsageStoreTests, in-memory backed), and commit-time usage recording (EmojiPickerControllerTests). Popularity and synonym tables are validated against the bundledemoji.jsonso no entry is dead. Local Team ID caveat: app-hosted suites run withCODE_SIGNING_ALLOWED=NO.Linked issues
Refs #393 (emoji & gif auto-completion/search). This advances the emoji half; GIF search is out of scope.
Risk / rollout notes
:shows recents. Conservative by construction (tie-break-only personalization; fuzzy gated to ≥3 chars).cotabbyEmojiUsagein UserDefaults (recents + frequency). User-clearable via Settings → General → Clear Emoji History.EmojiUsageStoredeclares anonisolated deinitto avoid the isolated-deinit back-deployment shim (swift_task_deinitOnExecutorMainActorBackDeploy) that over-releases and aborts the app-hosted test runner for a@MainActorclass with non-trivial stored properties.xcodegen generatefor the new source/test files; noproject.ymledit.🤖 Generated with Claude Code
Greptile Summary
Upgrades the inline
:emoji:picker from a plain substring search to a multi-tier ranked search: exact alias → alias prefix / synonym exact → keyword/name prefix / synonym prefix → alias substring → keyword/name substring → fuzzy (OSA + subsequence, ≥3 chars), with personal recents/frequency and a curated popularity prior breaking ties within each tier. A bare:now seeds the panel with the user's recent emoji instead of showing nothing.EmojiMatchergains synonym boosting (viaEmojiSynonymCatalog), a fuzzy fallback gated to sparse results and ≥3-char queries, and a four-key sort comparator (tier → favorite bucket → token length → popularity rank → catalog order); relevance is never sacrificed to personalization.EmojiUsageStorepersists per-user recents and frequency in a single JSON blob undercotabbyEmojiUsagein UserDefaults; thenonisolated deinitdodges a macOS 14 isolated-deinit back-deploy crash that was deterministically reproduced in the app-hosted test runner.EmojiCataloggains analiasIndexdictionary for O(1) alias → entry resolution, used byEmojiRecentsto fill the bare-:panel without iterating the full catalog.Confidence Score: 5/5
Safe to merge — the ranking changes are additive and tie-break-only, so no previously-relevant match is demoted, and all new code is fully unit-tested.
All new logic (OSA distance, subsequence, synonym catalog, popularity prior, usage store) is covered by dedicated unit tests with in-memory backends. The behavior change (bare
:shows recents; personalization and popularity only break ties within a tier) is conservative by construction. The macOS 14 nonisolated deinit workaround is well-documented and correctly targeted. No correctness bugs or data-safety issues were found in the changed code paths.No files require special attention.
Important Files Changed
Flowchart
%%{init: {'theme': 'neutral'}}%% flowchart TD A[User types in picker] --> B{query empty?} B -->|yes| C[EmojiRecents.suggestions\nrecents → popularity pad] B -->|no| D[EmojiSynonymCatalog\nboostedAliases for query] D --> E[Lexical pass over catalog\nalias / keyword / name hits] E --> F{scored.count < limit\nAND query.count ≥ 3?} F -->|no| G[Sort: tier → favoriteBucket\n→ tokenLength → popularityRank\n→ catalogIndex] F -->|yes| H[Fuzzy pass on unmatched entries\nOSA distance + subsequence] H --> G G --> I[prefix limit results] I --> J[EmojiVariantResolver.resolve\napply skin-tone / gender prefs] J --> K[Panel shown to user] K --> L{User commits emoji} L --> M[recordEmojiUsage alias\nEmojiUsageStore.record] M --> N[persist to UserDefaults\ncotabbyEmojiUsage JSON blob]Reviews (2): Last reviewed commit: "Address Greptile review: dead-code retur..." | Re-trigger Greptile