feat(messages): tag input with prefix autocomplete, and tags on the card (#28) - #116
Merged
Merged
Conversation
Adds a tag input to the composer, sends `tags[]` on `POST /api/messages`, and renders a message's tags on its card from the `tags[]` the feed already returns (no extra fetch). Tags are free-form labels, not hashtag tokens: the live API returns tags containing spaces and punctuation (e.g. "life is short, o brave girl"), so the input never splits what is typed and the card never prettifies it. A tag is committed deliberately — Add, the keyboard's Done action, or tapping a suggestion. Suggestions come from `GET /api/tags/autocomplete`, whose query parameter is `q` (`prefix` is a 400). Matching is the server's case-insensitive literal prefix and the results are shown in its order, unfiltered: re-matching client side would misrepresent the endpoint's semantics. The lookup is debounced by 300 ms and every keystroke supersedes the last, cancelling both the pending delay and any request already in flight, so a fast typist issues one request rather than a pile of concurrent ones. A response is applied only while it still answers the current text, so an overtaken reply for an older prefix can never overwrite newer suggestions. Cached messages gain a tags column (messages db 4 -> 5, destructive as before). Tests: the create request carries `tags` (and omits the field when empty); a tag with spaces and a comma round-trips out and back unchanged; autocomplete asks with `q` and keeps the server's order; debounce, per-keystroke cancellation, and a late response for an older prefix being discarded; the card renders tags. Closes #28
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.
Closes #28. Part of epic #27.
What changed
CreateMessageRequestgainedtags: List<String>?(omitted entirely when empty), plumbed throughthe repository, and the composer sends committed chips plus anything still uncommitted in the field.
Tags are free-form labels, not hashtags. Only surrounding whitespace is trimmed — nothing is
split on spaces or rewritten. That is not a hypothetical: real tags on the live account include
"life is short, o brave girl"and"light rail". Committing is deliberate (Add button, keyboardDone, or tapping a suggestion).
Autocomplete
Wired with
q, notprefix— sendingprefixreturns400 {"error":"Query parameter 'q' is required"}. Suggestions are shown in the server's order andnever re-filtered client-side: the server's case-insensitive literal prefix is the only matcher,
so the UI cannot show a suggestion the server would not have returned.
One
tagSuggestionJobis held; each keystroke cancels it, covering both the pending 300 ms debounceand a request already in flight. After a call returns, its result is applied only while
tagQuerystill matches the query it answered — so an overtaken response cannot overwrite newersuggestions.
Verification
./gradlew :app:assembleDebug testDebugUnitTest→ BUILD SUCCESSFUL, 1156 tests repo-wide, 0failures (
:feature:messagesalone: 204).Mutation-checked rather than trusting green tests: deleting the stale-query guard fails
a late response for an older prefix never overwrites a newer one; deletingtagSuggestionJob?.cancel()fails three tests (single-request debounce, per-word requests, in-flight cancellation). Both
reverted.
The stale-response test is genuinely adversarial: the fake's gate awaits under
NonCancellable, sothe overtaken response really does arrive after its coroutine was cancelled (asserted via
completedAutocompleteQueries), and the test proves the guard discards it withexpectNoEvents().Messages DB 4 → 5 for the cached
tagscolumn, destructive as the module already does.Reviewer notes
GET /api/messages?tag=) #29 (tag-filtered feed) can hang a click handler onMessageCardTags.tagTag(tag)without touching the layout. Tags: trending tags surface (GET /api/tags/trending) #30 (trending) is likewise untouched.androidTestper module convention (noRobolectric here). They compile but were not executed — no emulator.
tagson create generally, but the replypath has no tag UI and adding one was out of scope.