Skip to content

feat: enhance tag management and user library checks#14

Merged
niklhut merged 4 commits intomainfrom
fix/fix-tags
Apr 5, 2026
Merged

feat: enhance tag management and user library checks#14
niklhut merged 4 commits intomainfrom
fix/fix-tags

Conversation

@niklhut
Copy link
Copy Markdown
Owner

@niklhut niklhut commented Apr 5, 2026

Summary by CodeRabbit

  • New Features

    • Book lookup now uses your account to personalize results against your library.
  • UI Improvements

    • “Selected Tags” list updated to use button-style tags with inline remove icon.
    • Custom tag names are normalized for consistent display.
  • Bug Fixes

    • Deselected suggested tags are now consistently tracked so tag availability behaves predictably.

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 5, 2026

Warning

Rate limit exceeded

@niklhut has exceeded the limit for the number of commits that can be reviewed per hour. Please wait 14 minutes and 39 seconds before requesting another review.

Your organization is not enrolled in usage-based pricing. Contact your admin to enable usage-based pricing to continue reviews beyond the rate limit, or try again in 14 minutes and 39 seconds.

⌛ How to resolve this issue?

After the wait time has elapsed, a review can be triggered using the @coderabbitai review command as a PR comment. Alternatively, push new commits to this PR.

We recommend that you space out your commits to avoid hitting the rate limit.

🚦 How do rate limits work?

CodeRabbit enforces hourly rate limits for each developer per organization.

Our paid plans have higher rate limits than the trial, open-source and free plans. In all cases, we re-allow further reviews after a brief timeout.

Please see our FAQ for further information.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 540fa30b-f0ef-4ab1-9de3-8f8e79298fe2

📥 Commits

Reviewing files that changed from the base of the PR and between 5e8f13d and 7c61337.

📒 Files selected for processing (1)
  • app/components/TagManagerModal.vue
📝 Walkthrough

Walkthrough

User-scoped book lookup was implemented by threading userId from the API through service to repository; the tag manager UI was refactored to track released suggested tags, normalize custom tag display names, and simplify selected-tag interaction.

Changes

Cohort / File(s) Summary
Frontend: Tag manager
app/components/TagManagerModal.vue
Added releasedSuggestedTags reactive state and reset in initialization; merged/de-duplicated suggested tags by id for availability; record removed non-custom tags to releasedSuggestedTags; normalize custom tag name using toSensibleTitleCase(normalizeTagInputText(raw)); replaced badge+remove-button UI with a single UButton per selected tag invoking removeUserTag.
API endpoint
server/api/books/lookup.post.ts
Endpoint handler now receives the authenticated user and forwards user.id to the service layer (lookupBook(user.id, body.isbn)), making the lookup user-aware.
Service layer
server/services/book.service.ts
BookServiceInterface.lookupBook and exported helper updated to accept userId; implementation normalizes ISBN and queries repository for per-user ownership, setting existsLocally based on that result and preserving OpenLibrary fallback behavior.
Repository / DB access
server/repositories/book.repository.ts
Added hasBookInUserLibrary(userId: string, isbn: string) to the repository interface and live implementation; queries join userBooks and books filtered by userId and isbn, returning boolean and annotating DatabaseError with operation: 'hasBookInUserLibrary'.

Sequence Diagram

sequenceDiagram
    actor Client
    participant API as lookup.post (endpoint)
    participant Service as BookService.lookupBook
    participant Repo as BookRepository.hasBookInUserLibrary
    participant DB as Database

    Client->>API: POST /books/lookup { isbn, auth }
    API->>Service: lookupBook(userId, isbn)
    Service->>Service: normalize ISBN
    Service->>Repo: hasBookInUserLibrary(userId, normalizedISBN)
    Repo->>DB: Query JOIN userBooks + books WHERE userId & isbn LIMIT 1
    DB-->>Repo: row / no row
    Repo-->>Service: boolean (user owns book)
    alt user owns book
        Service-->>API: BookLookupResult (existsLocally: true)
    else not in user library
        Service->>Service: fallback to OpenLibrary
        Service-->>API: BookLookupResult (existsLocally: false)
    end
    API-->>Client: Response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes

Poem

🐰 I hopped through code with joyful tags,

Released the extras from their bags,
Threaded users through each book-finding trail,
Normalized names so none would fail,
A small carrot dance for tests that sail 🥕✨

🚥 Pre-merge checks | ✅ 3
✅ Passed checks (3 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title comprehensively captures the main changes across all modified files: tag management enhancements in TagManagerModal and user-scoped library checks throughout the book service and repository layers.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/fix-tags

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
app/components/TagManagerModal.vue (1)

240-250: Make the remove action explicit for screen readers.

On Line 249, the button text is only the tag name; the destructive intent is mostly conveyed by the icon. Consider adding hidden assistive text like “Remove tag …” for clearer accessibility.

♿ Suggested refinement
 <UButton
   v-for="tag in workingUserTags"
   :key="tag.id"
   color="primary"
   variant="subtle"
   size="xs"
   trailing-icon="i-lucide-x"
   `@click`="removeUserTag(tag)"
 >
-  {{ tag.name }}
+  <span>{{ tag.name }}</span>
+  <span class="sr-only">Remove tag {{ tag.name }}</span>
 </UButton>
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/TagManagerModal.vue` around lines 240 - 250, The UButton list
renders only the tag name with a trailing "x" icon, which hides the destructive
action from screen readers; update the UButton (rendered in the v-for over
workingUserTags, using tag.id/tag.name and removeUserTag) to include explicit
assistive text such as "Remove tag {tag.name}" for screen readers (either by
adding a visually-hidden span inside the button or setting an appropriate
aria-label on the button) and ensure the decorative icon is aria-hidden so
screen readers hear only the remove text.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Inline comments:
In `@app/components/TagManagerModal.vue`:
- Around line 54-57: The combined array passed into getAvailableSuggestedTags
can contain duplicate tag.id values because allSuggestedTags.value and
releasedSuggestedTags.value may both include the same non-custom tag; to fix,
deduplicate by id before calling getAvailableSuggestedTags (or update
getAvailableSuggestedTags to dedupe internally) by building a map/Set keyed on
tag.id from the merged arrays and then passing the unique list along with
workingUserTags.value; apply the same dedupe change to every place that
currently does [...allSuggestedTags.value, ...releasedSuggestedTags.value] so
rendering with :key="tag.id" is stable.

---

Nitpick comments:
In `@app/components/TagManagerModal.vue`:
- Around line 240-250: The UButton list renders only the tag name with a
trailing "x" icon, which hides the destructive action from screen readers;
update the UButton (rendered in the v-for over workingUserTags, using
tag.id/tag.name and removeUserTag) to include explicit assistive text such as
"Remove tag {tag.name}" for screen readers (either by adding a visually-hidden
span inside the button or setting an appropriate aria-label on the button) and
ensure the decorative icon is aria-hidden so screen readers hear only the remove
text.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 15163bcb-66fd-4ced-a370-80559ddd19ca

📥 Commits

Reviewing files that changed from the base of the PR and between 018e8ec and f10caf7.

⛔ Files ignored due to path filters (1)
  • server/db/migrations/sqlite/0005_add_normalized_name_to_tags.sql is excluded by !server/db/migrations/**
📒 Files selected for processing (4)
  • app/components/TagManagerModal.vue
  • server/api/books/lookup.post.ts
  • server/repositories/book.repository.ts
  • server/services/book.service.ts

@niklhut
Copy link
Copy Markdown
Owner Author

niklhut commented Apr 5, 2026

@coderabbitai review

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai bot commented Apr 5, 2026

✅ Actions performed

Review triggered.

Note: CodeRabbit is an incremental review system and does not re-review already reviewed commits. This command is applicable only when automatic reviews are paused.

Copy link
Copy Markdown
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

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

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
app/components/TagManagerModal.vue (1)

101-123: ⚠️ Potential issue | 🟠 Major

Normalize the canonical tag value before matching or creating.

normalizeTagInputText(raw) now changes the stored/displayed value, but the duplicate/suggested-tag checks still compare the pre-normalized raw string. That lets inputs like "History!" bypass the existing-tag match and lets punctuation-only input collapse into an empty custom tag.

🔧 Proposed fix
 function addCustomTag() {
   const raw = customTagInput.value.trim()
   if (!raw) return

-  const normalized = normalizeTagName(raw)
+  const normalizedInput = normalizeTagInputText(raw)
+  if (!normalizedInput) {
+    customTagInput.value = ''
+    return
+  }
+
+  const displayName = toSensibleTitleCase(normalizedInput)
+  const normalized = normalizeTagName(displayName)
   const alreadyInUserTags = workingUserTags.value.some(tag => normalizeTagName(tag.name) === normalized)
   if (alreadyInUserTags) {
     customTagInput.value = ''
     return
   }
@@
-  const displayName = toSensibleTitleCase(normalizeTagInputText(raw))
-
   workingUserTags.value.push({
     id: `custom:${crypto.randomUUID()}`,
     name: displayName,
     isCustom: true
   })

I’d also mirror the same canonical-empty check in the Add button disabled state so the UI doesn’t offer a submit that becomes a no-op.

🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/TagManagerModal.vue` around lines 101 - 123, In addCustomTag,
normalize the input first by calling normalizeTagInputText(raw) (or a new const
canonical = normalizeTagInputText(raw)) and use normalizeTagName(canonical) for
all duplicate and suggested-tag checks instead of using raw; if the canonical
string is empty after normalization, clear customTagInput.value and return to
avoid creating empty tags; keep displayName =
toSensibleTitleCase(normalizeTagInputText(raw)) but base its source on the
canonical variable; also update the Add button disabled logic to consult the
same canonical-empty check so the button is disabled when the normalized input
is empty (referencing customTagInput, normalizeTagInputText and the Add button
computed/prop used in the template).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.

Outside diff comments:
In `@app/components/TagManagerModal.vue`:
- Around line 101-123: In addCustomTag, normalize the input first by calling
normalizeTagInputText(raw) (or a new const canonical =
normalizeTagInputText(raw)) and use normalizeTagName(canonical) for all
duplicate and suggested-tag checks instead of using raw; if the canonical string
is empty after normalization, clear customTagInput.value and return to avoid
creating empty tags; keep displayName =
toSensibleTitleCase(normalizeTagInputText(raw)) but base its source on the
canonical variable; also update the Add button disabled logic to consult the
same canonical-empty check so the button is disabled when the normalized input
is empty (referencing customTagInput, normalizeTagInputText and the Add button
computed/prop used in the template).

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: 06874ba8-a232-4fd4-949d-97be82abe153

📥 Commits

Reviewing files that changed from the base of the PR and between f10caf7 and 5e8f13d.

📒 Files selected for processing (1)
  • app/components/TagManagerModal.vue

@niklhut niklhut merged commit 1adec17 into main Apr 5, 2026
3 checks passed
@niklhut niklhut deleted the fix/fix-tags branch April 5, 2026 17:50
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.

1 participant