Skip to content

fix: print label uses wrong tenant on mobile#1480

Open
lhns wants to merge 1 commit into
sysadminsmedia:mainfrom
lhns:fix/labelmaker-mobile-tenant
Open

fix: print label uses wrong tenant on mobile#1480
lhns wants to merge 1 commit into
sysadminsmedia:mainfrom
lhns:fix/labelmaker-mobile-tenant

Conversation

@lhns
Copy link
Copy Markdown

@lhns lhns commented May 7, 2026

What type of PR is this?

  • bug

What this PR does / why we need it:

On mobile, opening Print Label on an item from a non-default collection rendered the label for a different item - the item that happened to share the same assetId in the default collection. Switching the mobile browser to "desktop mode" worked around it; on desktop it always worked.

Root cause

LabelMaker.vue#getLabelUrl read the active tenant from useCollections().selectedId and appended it as a ?tenant= query param. selectedId is a module-level ref that is only populated when Collection/Selector.vue runs useCollections().load() in its onMounted hook.

The Selector lives inside the layout's <Sidebar>. On desktop the sidebar is a regular always-mounted <div>. On mobile (components/ui/sidebar/Sidebar.vue lines 29–43) it's rendered inside a <Sheet> (Reka UI DialogPortal / DialogContent), which is conditionally rendered - its slot is not mounted until the user opens the hamburger menu. Until that happens, selectedId.value is null, so:

  1. The label URL is built without ?tenant=….
  2. The backend's mwTenant middleware falls back to user.DefaultGroupID.
  3. HandleGetAssetLabel calls QueryByAssetID(auth, defaultGroupID, assetID). Because asset ids are tenant-scoped sequential numbers (e.g. 000-001), the lookup happily returns a different item with the same id from the default collection.

This also explains the "no network request on mobile" symptom: with the tenant param missing, the URL on mobile is byte-identical to the cached URL from a previous visit to the default collection's matching asset id, so the browser serves it from cache without firing a request. On desktop the URL includes a unique tenant uuid, so it never collides with the cache.

Fix

  • frontend/components/global/LabelMaker.vue - read the active tenant from useViewPreferences().value.collectionId instead of useCollections().selectedId. Preferences are persisted via useLocalStorage and updated by useCollections.set() / load() whenever the user switches collection, so they don't depend on the Selector ever being mounted.

This is the same fix @tonyaellie applied to attachment URLs in lib/api/base/base-api.ts in 24e79952 ("fix: use prefs instead of use collection for attachments"). LabelMaker.vue was missed by that earlier pass.

Which issue(s) this PR fixes:

none

Special notes:

I audited every consumer of useCollections(). Two other files have the same root cause but a different user-visible symptom - both early-return when selectedCollection.value is null:

  • frontend/pages/collection/index.vue (members page) - loadMembers() (line 90), isActionDisabled (line 87), handleLeaveCollection (line 123).
  • frontend/pages/collection/index/settings.vue - loadSettings() (line 34), save() (line 94).

If a user deep-links / bookmarks /collection/members or /collection/settings on mobile and lands on it without ever opening the sidebar, they'll see an empty members list or a "Select a collection" empty state. Opening and closing the sidebar mounts the Selector once, populates selectedId, and the page works. The underlying API calls would actually succeed because base-api.ts already reads from prefs.

Two ways to fix these, both reasonable - happy to do either as a follow-up:

  1. Local fix, mirroring this PR: switch the selectedCollection.value checks/reads in those two files to use prefs.value.collectionId (and re-derive the collection from collections.value when the display name is needed).
  2. Layout-level fix: call void useCollections().load() once in layouts/default.vue's onMounted. load() already self-guards against duplicate / concurrent calls (see use-collections.ts:22-25), so this is safe and would populate selectedId for every authenticated page, fixing the latent collection-page issue and any future component that reads selectedId directly.

A couple of other call sites build /qrcode?data=… URLs without a tenant param (components/global/PageQRCode.vue, pages/reports/label-generator.vue), but the /qrcode endpoint just renders the supplied data string into a QR image, so the rendered output doesn't depend on the active tenant - no visible bug there.

Testing

  • In a multi-collection account with asset id 000-001 present in two collections, open the non-default collection's 000-001 item on a mobile viewport (or hard-reload mobile after clearing cache so the previous bad cache entry is gone).
  • Click Print Label. Confirm DevTools → Network shows a fresh request to /api/v1/labelmaker/asset/000-001?print=false&tenant=<non-default-uuid> and the rendered image shows the correct item's name and parent location.
  • Repeat on desktop - no regression.
  • Test an item without an assetId (LabelMaker is invoked with type="item" / id=item.id) and a location detail page (type="location") to confirm the other branches of getLabelUrl() still work.

Disclaimer: I have written this MR with the help of Claude Code

Summary by CodeRabbit

  • Bug Fixes
    • Fixed label URL generation to use the correct data source for collection identification, ensuring consistent behavior across label operations.

LabelMaker built the label URL from useCollections().selectedId, which is
only populated when Collection/Selector.vue mounts. On mobile the sidebar
(and the Selector inside it) lives in a Sheet that is conditionally
rendered, so until the user opens the hamburger menu, selectedId stays
null and the ?tenant= query param is omitted from the label URL. The
backend then falls back to the user's default group, returning the wrong
tenant's item — and because the URL is byte-identical between visits,
the browser serves a cached image without firing a request.

Switch to useViewPreferences().value.collectionId, matching the same
pattern that 24e7995 applied to base-api.ts for attachments.
@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 7, 2026

Review Change Stack
No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 6b4e2b4d-8652-442c-b022-b968f68a67e7

📥 Commits

Reviewing files that changed from the base of the PR and between 0e0c171 and d5b8efe.

📒 Files selected for processing (1)
  • frontend/components/global/LabelMaker.vue

Walkthrough

The getLabelUrl() function in LabelMaker.vue now retrieves the tenant query parameter value from useViewPreferences().value.collectionId instead of useCollections().selectedId. The conditional inclusion logic and URL routing behavior for label types remain unchanged.

Changes

Label URL Tenant Source Refactoring

Layer / File(s) Summary
Tenant Source Change
frontend/components/global/LabelMaker.vue
getLabelUrl() refactored to read tenant from view preferences collectionId instead of collection selection state. Conditional inclusion and label type routing unchanged.

Estimated code review effort

🎯 1 (Trivial) | ⏱️ ~3 minutes

Possibly related PRs

  • sysadminsmedia/homebox#1313: Both PRs change how tenant/collectionId is sourced in generated URLs, switching to useViewPreferences().value.collectionId.
  • sysadminsmedia/homebox#1303: Both PRs modify getLabelUrl to include tenant GET parameters, but this PR switches the tenant source from useCollections().selectedId.

Suggested reviewers

  • tankerkiller125
  • tonyaellie

Security Recommendations

When reviewing this change, consider the following security considerations:

  1. Data Source Validation: Verify that useViewPreferences().value.collectionId is properly validated and sanitized before being included in the query parameter. Ensure the new source enforces the same validation rules as the prior useCollections().selectedId.

  2. Tenant Isolation: Confirm that this change does not create any tenant isolation vulnerabilities. The tenant parameter must only contain values that the current user is authorized to access. Verify that useViewPreferences() respects the same access control boundaries as the collections module.

  3. State Consistency: Ensure that switching the source does not create race conditions or state inconsistencies between view preferences and actual user permissions. Test scenarios where view preferences may become stale or out-of-sync with authorized collections.

  4. Cross-Origin Considerations: If label URLs are shared or transmitted externally, verify that the tenant parameter in the query string does not leak sensitive tenant identifiers or create enumeration vulnerabilities.

Poem

🏷️ A tenant moves house from one shelf to another,
From Collections it leaves, to ViewPrefs it discovers,
The label still prints with its usual grace,
Just reads from a fresher, more preferred place! ✨

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title 'fix: print label uses wrong tenant on mobile' accurately and concisely describes the main bug fix in the changeset.
Description check ✅ Passed The description comprehensively covers all required template sections: PR type (bug), detailed explanation of what was changed and why (with root cause analysis), impact assessment, and testing instructions.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Tip

💬 Introducing Slack Agent: The best way for teams to turn conversations into code.

Slack Agent is built on CodeRabbit's deep understanding of your code, so your team can collaborate across the entire SDLC without losing context.

  • Generate code and open pull requests
  • Plan features and break down work
  • Investigate incidents and troubleshoot customer tickets together
  • Automate recurring tasks and respond to alerts with triggers
  • Summarize progress and report instantly

Built for teams:

  • Shared memory across your entire org—no repeating context
  • Per-thread sandboxes to safely plan and execute work
  • Governance built-in—scoped access, auditability, and budget controls

One agent for your entire SDLC. Right inside Slack.

👉 Get started


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.

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