Skip to content

feat(tags): add autocomplete to tags input#8

Open
axelav wants to merge 5 commits intomainfrom
feat/tag-autocomplete
Open

feat(tags): add autocomplete to tags input#8
axelav wants to merge 5 commits intomainfrom
feat/tag-autocomplete

Conversation

@axelav
Copy link
Copy Markdown
Owner

@axelav axelav commented Feb 17, 2026

Summary

  • Add GET /api/tags/search?q= endpoint that returns matching tags as HTML buttons, scoped to the authenticated user
  • Add vanilla JS autocomplete on the entry form tags input with debounced fetch, keyboard navigation (arrow keys, enter, escape), click-to-select, and client-side filtering of already-entered tags
  • Add dropdown CSS matching the existing design system

Test Plan

  • 4 new integration tests: auth required, prefix matching, empty results, user scoping (92 total, all passing)
  • Manual: go to /entries/new, type a tag prefix, verify dropdown appears after 300ms
  • Manual: arrow keys highlight items, Enter selects, Escape closes
  • Manual: clicking a suggestion inserts it with trailing comma
  • Manual: already-entered tags are excluded from suggestions
  • Manual: edit an existing entry — autocomplete works the same way

Copilot AI review requested due to automatic review settings February 17, 2026 06:20
Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds tag autocomplete for the entry form by introducing a tag search API endpoint and wiring up client-side dropdown behavior/styling.

Changes:

  • Add GET /api/tags/search?q= to return matching tags (scoped to the authenticated user) as an HTML fragment.
  • Add vanilla JS autocomplete behavior to the entry form tags input (debounced fetch + keyboard/click interactions).
  • Add CSS styling for the autocomplete dropdown; add integration tests for the new endpoint.

Reviewed changes

Copilot reviewed 5 out of 5 changed files in this pull request and generated 3 comments.

Show a summary per file
File Description
src/routes/tags.rs Adds /api/tags/search route + handler that queries user-scoped tags and renders HTML buttons.
templates/entries/form.html Wraps tags input and adds JS to fetch/render suggestions and support keyboard/click selection.
static/style.css Adds dropdown and item styling for tag autocomplete UI.
tests/tags.rs Adds integration tests covering auth, matching, empty results, and user scoping for tag search.
docs/plans/2026-02-17-tag-autocomplete.md Documents the implementation plan and test plan for the feature.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread src/routes/tags.rs
Comment on lines +202 to +208
let prefix = params.q.unwrap_or_default().trim().to_lowercase();
if prefix.is_empty() {
return Ok(Html(String::new()));
}

let pattern = format!("{}%", prefix);
let tags: Vec<(String,)> = sqlx::query_as(
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The LIKE pattern is built directly from user input, so % and _ in q act as wildcards (e.g., q=% returns arbitrary tags) and the behavior is no longer strict prefix matching. Escape %/_ in the prefix (and use ... LIKE ? ESCAPE '\\'), or switch to a safer prefix predicate if available for the chosen DB/collation.

Copilot uses AI. Check for mistakes.
Comment on lines +263 to +266
fetch('/api/tags/search?q=' + encodeURIComponent(segment.toLowerCase()))
.then(function(r) { return r.text(); })
.then(function(html) {
var existing = getExistingTags();
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The fetch chain doesn’t handle non-OK responses or redirects (e.g., an expired session will redirect to /login, and the login HTML may be rendered inside the dropdown). Check r.ok/r.redirected (or r.status === 401 if you change the API behavior) and hide the dropdown / trigger a navigation to login instead of blindly rendering r.text().

Copilot uses AI. Check for mistakes.
Comment on lines +262 to +265
debounceTimer = setTimeout(function() {
fetch('/api/tags/search?q=' + encodeURIComponent(segment.toLowerCase()))
.then(function(r) { return r.text(); })
.then(function(html) {
Copy link

Copilot AI Feb 17, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There’s a race condition between in-flight requests: a slower response for an older segment can arrive after a newer one and overwrite the dropdown with stale suggestions. Consider using AbortController to cancel the previous request, or track an incrementing request id and ignore responses that aren’t the latest.

Copilot uses AI. Check for mistakes.
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.

2 participants