Skip to content

Improve photo tagging#1075

Open
lodewiges wants to merge 2 commits intostagingfrom
feature/improved_photo_tagging
Open

Improve photo tagging#1075
lodewiges wants to merge 2 commits intostagingfrom
feature/improved_photo_tagging

Conversation

@lodewiges
Copy link
Copy Markdown
Contributor

@lodewiges lodewiges commented Feb 25, 2026

Follows
fixes some small issue with photo tagging

Summary by CodeRabbit

  • New Features

    • Checkbox to include old members when tagging photos (state persists in the page URL)
    • Auto-selects a user when a search returns exactly one result
    • Search input now propagates changes immediately for faster selection
  • Style

    • Updated styling for photo tag controls section

Copilot AI review requested due to automatic review settings February 25, 2026 20:22
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented Feb 25, 2026

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between e433977 and 2145171.

📒 Files selected for processing (1)
  • app/styles/components/photo-tags.scss
🚧 Files skipped from review as they are similar to previous changes (1)
  • app/styles/components/photo-tags.scss

📝 Walkthrough

Walkthrough

Adds a checkbox to include/exclude old members in photo tagging, persists the choice via a route query param, fetches users accordingly into a tracked list, and wires a search-change handler to auto-select single search results.

Changes

Cohort / File(s) Summary
Template & Styling
app/components/photo-tags/photo-tags.hbs, app/styles/components/photo-tags.scss
Added "Oude leden meenemen" checkbox in the new-tag controls and associated form styles; wired @onSearchChange argument into the PowerSelect usage.
Component Logic
app/components/photo-tags/photo-tags.js
Added router service, @tracked users, includeOldMembers getter, fetchUsers() method, toggleIncludeOldMembers() and onSearchChange() actions; addTag() now calls fetchUsers() before computing coordinates.
Route
app/routes/photo-albums/photo-album/photos/photo.js
Added includeOldMembers to queryParams with replace: true to persist/reflect the checkbox state in the URL.

Sequence Diagram

sequenceDiagram
    actor User
    participant Component as Photo Tags Component
    participant Router as Router Service
    participant API as User Data Service

    User->>Component: Toggle "Oude leden meenemen" checkbox
    Component->>Router: set query param includeOldMembers
    Component->>API: fetchUsers(includeOldMembers)
    alt includeOldMembers = true
        API-->>Component: return all users
    else
        API-->>Component: return group "Leden" users
    end
    Component->>Component: update `@tracked` users

    User->>Component: type in PowerSelect search
    Component->>Component: onSearchChange(searchResults)
    alt searchResults length == 1
        Component->>Component: auto-select the single user
    end
Loading

Estimated Code Review Effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Poem

🐇 I nudged a checkbox, small and bright,
"Oude leden" now join the sight,
The route remembers, the users arrive,
Searches hop, one result—select and thrive,
A tiny rabbit cheers the tagging night.

🚥 Pre-merge checks | ✅ 1 | ❌ 2

❌ Failed checks (1 warning, 1 inconclusive)

Check name Status Explanation Resolution
Description check ⚠️ Warning The description is largely incomplete, containing only a vague reference without following the required template structure (Summary and Other information sections). Fill in the required Summary section with a clear overview of changes, mention any related issues with 'fixes #xyz', and add Other information section with relevant context.
Title check ❓ Inconclusive The title 'Improve photo tagging' is vague and generic, using a non-specific term 'Improve' that doesn't clearly convey the main technical change of adding member visibility filtering. Consider a more descriptive title like 'Add option to include old members in photo tagging' to better reflect the primary feature being introduced.
✅ Passed checks (1 passed)
Check name Status Explanation
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
  • Post copyable unit tests in a comment
  • Commit unit tests in branch feature/improved_photo_tagging

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

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

This pull request improves the photo tagging functionality by adding a filter to include or exclude old members when tagging photos. The implementation adds a checkbox UI control, manages the filter state via query parameters, and implements an auto-select feature when search results return exactly one user.

Changes:

  • Added a checkbox control to toggle between current members (Leden) and all users when tagging photos
  • Implemented query parameter handling to persist the "includeOldMembers" filter state in the URL
  • Added auto-select functionality that automatically selects a user when the search narrows down to a single result

Reviewed changes

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

File Description
app/styles/components/photo-tags.scss Added styling for the new photo-tag-controls section with checkbox styling that matches the dark overlay theme
app/routes/photo-albums/photo-album/photos/photo.js Configured includeOldMembers query parameter with replace: true to avoid cluttering browser history
app/components/photo-tags/photo-tags.js Converted users getter to tracked property, added fetchUsers method to conditionally load users based on filter, implemented toggle and auto-select actions
app/components/photo-tags/photo-tags.hbs Added checkbox UI control for toggling old members filter and connected onSearchChange handler to PowerSelect

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

.form-check-input {
background-color: rgba(255, 255, 255, 0.2);
border-color: rgba(255, 255, 255, 0.5);

Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

This line contains trailing whitespace. Consider removing it for code cleanliness.

Suggested change

Copilot uses AI. Check for mistakes.

@action
addTag(e) {
if (e.target.tagName.toLowerCase() != 'img' || this.newTagX || this.newTagY)
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

Use strict inequality operator (!==) instead of loose inequality (!=) for consistent comparison. The codebase predominantly uses strict equality operators, as seen in lines 73-74 and throughout other files in the codebase.

Suggested change
if (e.target.tagName.toLowerCase() != 'img' || this.newTagX || this.newTagY)
if (e.target.tagName.toLowerCase() !== 'img' || this.newTagX || this.newTagY)

Copilot uses AI. Check for mistakes.
Comment on lines +37 to +40
toggleIncludeOldMembers() {
const newValue = !this.includeOldMembers;
this.router.transitionTo({ queryParams: { includeOldMembers: newValue ? 'true' : 'false' } });
this.fetchUsers();
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The toggleIncludeOldMembers method calls fetchUsers() without awaiting it, which could lead to race conditions where the user list is not yet populated when the component re-renders. Consider awaiting the fetchUsers() call to ensure the users list is populated before the component re-renders. Additionally, consider adding error handling for the fetchUsers() call in case the API request fails.

Copilot uses AI. Check for mistakes.
Comment on lines 54 to +58
addTag(e) {
if (e.target.tagName.toLowerCase() != 'img' || this.newTagX || this.newTagY)
return;
e.stopPropagation();
this.fetchUsers();
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The fetchUsers() method is called in addTag without awaiting it, which means the PowerSelect dropdown might open with an empty or stale users list. Consider awaiting fetchUsers() before opening the select dropdown to ensure users are loaded first.

Suggested change
addTag(e) {
if (e.target.tagName.toLowerCase() != 'img' || this.newTagX || this.newTagY)
return;
e.stopPropagation();
this.fetchUsers();
async addTag(e) {
if (e.target.tagName.toLowerCase() != 'img' || this.newTagX || this.newTagY)
return;
e.stopPropagation();
await this.fetchUsers();

Copilot uses AI. Check for mistakes.
Comment on lines +22 to +28
async fetchUsers() {
if (this.includeOldMembers) {
this.users = await this.store.findAll('user');
} else {
const params = { filter: { group: 'Leden' } };
this.users = await this.store.query('user', params);
}
Copy link

Copilot AI Feb 25, 2026

Choose a reason for hiding this comment

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

The fetchUsers() method lacks error handling. If the API call fails, there's no indication to the user, and the users list will remain empty. Consider adding try-catch error handling and displaying an error message to the user if the fetch fails.

Copilot uses AI. Check for mistakes.
@codecov
Copy link
Copy Markdown

codecov Bot commented Feb 25, 2026

Codecov Report

❌ Patch coverage is 7.69231% with 12 lines in your changes missing coverage. Please review.
✅ Project coverage is 12.96%. Comparing base (e5dc0bd) to head (2145171).
⚠️ Report is 1 commits behind head on staging.

Files with missing lines Patch % Lines
app/components/photo-tags/photo-tags.js 0.00% 12 Missing ⚠️
Additional details and impacted files
@@             Coverage Diff             @@
##           staging    #1075      +/-   ##
===========================================
- Coverage    13.02%   12.96%   -0.07%     
===========================================
  Files          450      450              
  Lines         3124     3139      +15     
===========================================
  Hits           407      407              
- Misses        2717     2732      +15     

☔ View full report in Codecov by Sentry.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

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: 2

Caution

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

⚠️ Outside diff range comments (1)
app/components/photo-tags/photo-tags.js (1)

53-66: ⚠️ Potential issue | 🔴 Critical

Race condition: PowerSelect opens before fetchUsers() resolves, showing an empty list.

this.fetchUsers() is fire-and-forget. The next() callback runs in the very next runloop tick — long before the async fetch can complete — so on the first click the dropdown renders with this.users === [].

addTag must be async and await the fetch before scheduling the open:

🛠️ Proposed fix
  `@action`
- addTag(e) {
+ async addTag(e) {
    if (e.target.tagName.toLowerCase() != 'img' || this.newTagX || this.newTagY)
      return;
    e.stopPropagation();
-   this.fetchUsers();
+   await this.fetchUsers();
    let x = (e.offsetX / e.target.width) * 100;
    let y = (e.offsetY / e.target.height) * 100;
    this.newTagX = x;
    this.newTagY = y;
    next(this, () => {
      this.selectApi.actions.open();
    });
  }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/components/photo-tags/photo-tags.js` around lines 53 - 66, The addTag
action (addTag) is opening the PowerSelect before fetchUsers() completes; make
addTag async, await this.fetchUsers() after setting newTagX/newTagY (or before
calling selectApi.actions.open) so the users list is populated before opening,
then call next(this, () => this.selectApi.actions.open()); ensure fetchUsers()
returns a promise so awaiting works and preserve the existing early-return
checks for e.target and newTagX/newTagY.
🧹 Nitpick comments (2)
app/components/photo-tags/photo-tags.js (1)

22-29: fetchUsers() is called on every photo click — consider loading once on init and only re-fetching on filter change.

As-is, every click on the photo triggers a full user fetch. Users should be fetched once when the tag UI initialises (e.g. in a constructor or a {{did-insert}} action) and only refreshed inside toggleIncludeOldMembers. This removes the need for the fetchUsers() call in addTag entirely.

Additionally, fetchUsers() currently has no error handling; a failed request leaves this.users unchanged (or empty on first load) with no user feedback.

♻️ Sketch of the suggested approach
+ constructor(owner, args) {
+   super(owner, args);
+   this.fetchUsers();
+ }

  async fetchUsers() {
-   if (this.includeOldMembers) {
-     this.users = await this.store.findAll('user');
-   } else {
-     const params = { filter: { group: 'Leden' } };
-     this.users = await this.store.query('user', params);
+   try {
+     if (this.includeOldMembers) {
+       this.users = await this.store.findAll('user');
+     } else {
+       const params = { filter: { group: 'Leden' } };
+       this.users = await this.store.query('user', params);
+     }
+   } catch (e) {
+     this.flashNotice.sendError('Gebruikerslijst laden mislukt.');
    }
  }

  `@action`
  addTag(e) {
    if (e.target.tagName.toLowerCase() != 'img' || this.newTagX || this.newTagY)
      return;
    e.stopPropagation();
-   this.fetchUsers();  // ← remove
    ...
  }

Also applies to: 53-66

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

In `@app/components/photo-tags/photo-tags.js` around lines 22 - 29, fetchUsers is
being called on every photo click; move the initial load to component
initialization (constructor or did-insert) so fetchUsers runs once, remove the
fetchUsers call from addTag, and only call fetchUsers inside
toggleIncludeOldMembers when includeOldMembers changes; update fetchUsers to use
a try/catch around the store.findAll/store.query calls, set this.users
appropriately on success, and set a fallback (e.g. empty array) or an error flag
on failure so UI can show feedback without leaving users undefined.
app/styles/components/photo-tags.scss (1)

68-71: Consider using a CSS custom property instead of the hardcoded Bootstrap primary colour.

#007bff is the Bootstrap 4 primary. If the project uses Bootstrap 5 or custom theming, this won't follow theme changes. Using var(--bs-primary) (or whatever the project's custom property is) would keep the checked state consistent with the rest of the UI.

♻️ Proposed fix
    &:checked {
-     background-color: `#007bff`;
-     border-color: `#007bff`;
+     background-color: var(--bs-primary);
+     border-color: var(--bs-primary);
    }
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@app/styles/components/photo-tags.scss` around lines 68 - 71, The
checked-state styles in photo-tags.scss use the hardcoded Bootstrap v4 primary
hex (`#007bff`); change both background-color and border-color inside the
&:checked rule to use the project's CSS custom property (e.g. var(--bs-primary))
(you can include a fallback like var(--bs-primary, `#007bff`) if desired) so the
checked state follows Bootstrap/custom theming consistently; update the
&:checked block accordingly.
🤖 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/photo-tags/photo-tags.js`:
- Around line 36-41: The toggleIncludeOldMembers action flips includeOldMembers
but calls this.fetchUsers immediately, risking reading stale query params
because this.router.transitionTo is async and also leaves fetchUsers rejections
unhandled; update toggleIncludeOldMembers (the method) to await
this.router.transitionTo(...) before calling this.fetchUsers(), and call
this.fetchUsers().catch(...) (or use try/catch around await) to handle network
errors; also add the missing trailing comma in the transitionTo queryParams
object to satisfy ESLint/Prettier; ensure fetchUsers itself has proper error
handling as noted.

In `@app/routes/photo-albums/photo-album/photos/photo.js`:
- Around line 4-8: The object literal assigned to queryParams is missing
trailing commas which ESLint/Prettier expect; update the includeOldMembers inner
object and the outer queryParams object to include trailing commas (i.e., add a
trailing comma after the replace property inside the includeOldMembers object
and a trailing comma after the includeOldMembers property in queryParams) so the
assignment to queryParams and the nested object follow the project's
trailing-comma linting rules.

---

Outside diff comments:
In `@app/components/photo-tags/photo-tags.js`:
- Around line 53-66: The addTag action (addTag) is opening the PowerSelect
before fetchUsers() completes; make addTag async, await this.fetchUsers() after
setting newTagX/newTagY (or before calling selectApi.actions.open) so the users
list is populated before opening, then call next(this, () =>
this.selectApi.actions.open()); ensure fetchUsers() returns a promise so
awaiting works and preserve the existing early-return checks for e.target and
newTagX/newTagY.

---

Nitpick comments:
In `@app/components/photo-tags/photo-tags.js`:
- Around line 22-29: fetchUsers is being called on every photo click; move the
initial load to component initialization (constructor or did-insert) so
fetchUsers runs once, remove the fetchUsers call from addTag, and only call
fetchUsers inside toggleIncludeOldMembers when includeOldMembers changes; update
fetchUsers to use a try/catch around the store.findAll/store.query calls, set
this.users appropriately on success, and set a fallback (e.g. empty array) or an
error flag on failure so UI can show feedback without leaving users undefined.

In `@app/styles/components/photo-tags.scss`:
- Around line 68-71: The checked-state styles in photo-tags.scss use the
hardcoded Bootstrap v4 primary hex (`#007bff`); change both background-color and
border-color inside the &:checked rule to use the project's CSS custom property
(e.g. var(--bs-primary)) (you can include a fallback like var(--bs-primary,
`#007bff`) if desired) so the checked state follows Bootstrap/custom theming
consistently; update the &:checked block accordingly.

ℹ️ Review info

Configuration used: defaults

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between bd515d5 and e433977.

📒 Files selected for processing (4)
  • app/components/photo-tags/photo-tags.hbs
  • app/components/photo-tags/photo-tags.js
  • app/routes/photo-albums/photo-album/photos/photo.js
  • app/styles/components/photo-tags.scss

Comment on lines +36 to +41
@action
toggleIncludeOldMembers() {
const newValue = !this.includeOldMembers;
this.router.transitionTo({ queryParams: { includeOldMembers: newValue ? 'true' : 'false' } });
this.fetchUsers();
}
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟠 Major

Unawaited fetchUsers() may read stale query params and silently drops errors.

this.router.transitionTo(...) is async, so this.includeOldMembers (which reads currentRoute.queryParams) may still reflect the old value when fetchUsers() runs immediately after. Additionally, if the network request fails the rejected promise goes unhandled.

Await the transition first, then fetch (and handle errors):

🛠️ Proposed fix
  `@action`
- toggleIncludeOldMembers() {
+ async toggleIncludeOldMembers() {
    const newValue = !this.includeOldMembers;
-   this.router.transitionTo({ queryParams: { includeOldMembers: newValue ? 'true' : 'false' } });
-   this.fetchUsers();
+   await this.router.transitionTo({ queryParams: { includeOldMembers: newValue ? 'true' : 'false' } });
+   await this.fetchUsers();
  }

Error handling in fetchUsers itself should also be added (see the addTag note).

Also, ESLint/Prettier requires a trailing comma on line 39:

-   this.router.transitionTo({ queryParams: { includeOldMembers: newValue ? 'true' : 'false' } });
+   this.router.transitionTo({ queryParams: { includeOldMembers: newValue ? 'true' : 'false' }, });
🧰 Tools
🪛 ESLint

[error] 39-39: Replace ·queryParams:·{·includeOldMembers:·newValue·?·'true'·:·'false'·} with ⏎······queryParams:·{·includeOldMembers:·newValue·?·'true'·:·'false'·},⏎···

(prettier/prettier)

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

In `@app/components/photo-tags/photo-tags.js` around lines 36 - 41, The
toggleIncludeOldMembers action flips includeOldMembers but calls this.fetchUsers
immediately, risking reading stale query params because this.router.transitionTo
is async and also leaves fetchUsers rejections unhandled; update
toggleIncludeOldMembers (the method) to await this.router.transitionTo(...)
before calling this.fetchUsers(), and call this.fetchUsers().catch(...) (or use
try/catch around await) to handle network errors; also add the missing trailing
comma in the transitionTo queryParams object to satisfy ESLint/Prettier; ensure
fetchUsers itself has proper error handling as noted.

Comment on lines +4 to +8
queryParams = {
includeOldMembers: {
replace: true
}
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

⚠️ Potential issue | 🟡 Minor

Fix missing trailing commas flagged by ESLint/Prettier.

🔧 Proposed fix
  queryParams = {
    includeOldMembers: {
-     replace: true
-   }
+     replace: true,
+   },
  };
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
queryParams = {
includeOldMembers: {
replace: true
}
};
queryParams = {
includeOldMembers: {
replace: true,
},
};
🧰 Tools
🪛 ESLint

[error] 6-6: Insert ,

(prettier/prettier)


[error] 7-7: Insert ,

(prettier/prettier)

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

In `@app/routes/photo-albums/photo-album/photos/photo.js` around lines 4 - 8, The
object literal assigned to queryParams is missing trailing commas which
ESLint/Prettier expect; update the includeOldMembers inner object and the outer
queryParams object to include trailing commas (i.e., add a trailing comma after
the replace property inside the includeOldMembers object and a trailing comma
after the includeOldMembers property in queryParams) so the assignment to
queryParams and the nested object follow the project's trailing-comma linting
rules.

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