Skip to content

Frontend/multistate license scopes#1708

Open
jsandoval81 wants to merge 4 commits into
csg-org:mainfrom
InspiringApps:frontend/multistate-discipline-updates
Open

Frontend/multistate license scopes#1708
jsandoval81 wants to merge 4 commits into
csg-org:mainfrom
InspiringApps:frontend/multistate-discipline-updates

Conversation

@jsandoval81

@jsandoval81 jsandoval81 commented Jul 2, 2026

Copy link
Copy Markdown
Collaborator

Requirements List

  • None

Description List

  • Updated the License model to include licenseScope
  • Updated the license card UI to include a license scope indicator for multi state compacts
  • Update the privilege card UI to allow for wide license type names
  • Updated the encumbrance & investigation Basis for Action types for COSM & SOCW
  • Updated the encumbrance & investigation API flows to optionally include licenseScope

Testing List

  • yarn test:unit:all should run without errors or warnings
  • yarn serve should run without errors or warnings
  • yarn build should run without errors or warnings
  • Code review
  • Testing
    • Login as a Social Work state admin
    • Search for users and view the provider detail screen to confirm:
      • License cards now have a single-state / multi-state indicator tag
      • The license types in the license & privilege cards are the fully spelled out license types
      • You can encumber a license
        • Note: The license type in the encumber modal is the abbreviated / acronym
    • Login as a state admin for Cosmetology
    • Search for users and view the provider detail screen to confirm:
      • The detail cards UI is still the same as expected, with abbreviated license types
      • You can encumber a license
    • Login as a state admin for OT / AUD / COUN
    • Search for users and view the provider detail screen to confirm:
      • The detail cards UI is still the same as expected, with abbreviated license types
      • You can encumber a license
    • Logout and go to the public search
    • Test searching each compact type, visiting the provider detail screen to confirm all the license & privilege card layout updates from above

Closes #1707

Summary by CodeRabbit

  • New Features
    • Added globe and map-pin icon components.
    • License cards now support license scope (single-state vs multi-state), displaying the correct icon and scope label; scope is included in relevant submit/update flows.
    • Updated license and privilege display behavior, plus NPDB category option filtering by app mode.
  • UI / Styling
    • Refreshed LicenseCard and PrivilegeCard layout, including new “license-type” styling and improved scope presentation.
    • Added a new shared blue color variable.
  • Localization
    • Updated English/Spanish license type and NPDB option wording/keys.
  • Tests
    • Added unit tests for the new icon components and extended license model tests for license scope.

@coderabbitai

coderabbitai Bot commented Jul 2, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: b17e51ca-de4a-493b-ac32-2987e346c6ef

📥 Commits

Reviewing files that changed from the base of the PR and between cbd203f and 271d981.

📒 Files selected for processing (2)
  • webroot/src/components/Icons/Globe/Globe.vue
  • webroot/src/components/Icons/MapPin/MapPin.vue
🚧 Files skipped from review as they are similar to previous changes (2)
  • webroot/src/components/Icons/Globe/Globe.vue
  • webroot/src/components/Icons/MapPin/MapPin.vue

📝 Walkthrough

Walkthrough

This PR adds licenseScope support across the license model, request plumbing, and compact UI. It updates LicenseCard and PrivilegeCard rendering and NPDB option logic, introduces Globe and MapPin icons, and refreshes related locale, mock, and style data.

Changes

Multistate License Scope Feature

Layer / File(s) Summary
License model and tests
webroot/src/models/License/License.model.ts, webroot/src/models/License/License.model.spec.ts
Adds LicenseScope, licenseScope, display helpers, serializer mapping, and model test coverage.
API wrappers and request payloads
webroot/src/network/data.api.ts, webroot/src/network/licenseApi/data.api.ts
Extends encumbrance and investigation request methods to accept and forward optional licenseScope.
Vuex actions
webroot/src/store/users/users.actions.ts
Passes licenseScope from user actions into the network layer.
Globe and MapPin icons
webroot/src/components/Icons/Globe/*, webroot/src/components/Icons/MapPin/*
Adds two new SVG icon components with TypeScript wrappers, Less stubs, and shallow-mount tests.
LicenseCard scope UI and submit flow
webroot/src/components/LicenseCard/LicenseCard.ts, webroot/src/components/LicenseCard/LicenseCard.vue, webroot/src/components/LicenseCard/LicenseCard.less
Registers new icons, adds scope-related computed state, rewrites NPDB filtering, includes licenseScope in submit payloads, renders scope UI, and updates styling.
PrivilegeCard display and NPDB options
webroot/src/components/PrivilegeCard/PrivilegeCard.ts, webroot/src/components/PrivilegeCard/PrivilegeCard.vue, webroot/src/components/PrivilegeCard/PrivilegeCard.less
Adds privilegeTypeDisplay, rewrites NPDB filtering, updates the license-type template, and adjusts styling.
Locales, fixtures, and color
webroot/src/locales/en.json, webroot/src/locales/es.json, webroot/src/network/mocks/mock.data.ts, webroot/src/styles.common/_colors.less
Updates license and NPDB locale entries, adds licenseScope to fixture data, and adds a new color variable.

Estimated code review effort: 4 (Complex) | ~60 minutes

Possibly related issues

Possibly related PRs

  • csg-org/CompactConnect#1161: Touches NPDB category selection in LicenseCard and PrivilegeCard, overlapping with the rewritten option filtering here.
  • csg-org/CompactConnect#1670: Modifies shared app-mode-driven logic in PrivilegeCard.ts, overlapping with the NPDB rewrite here.

Suggested reviewers: rmolinares, landonshumway-ia

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title is concise and accurately reflects the main frontend change around multistate license scopes.
Description check ✅ Passed The description follows the template well, covering requirements, changes, testing notes, and the closing issue.
Linked Issues check ✅ Passed The PR implements UI support for single-state and multi-state scopes and updates API flows to carry licenseScope as required by #1707.
Out of Scope Changes check ✅ Passed The added icons, locale updates, privilege card changes, and color token all support the stated scope and do not appear unrelated.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

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.

@coderabbitai coderabbitai Bot left a comment

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.

Actionable comments posted: 1

Caution

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

⚠️ Outside diff range comments (1)
webroot/src/network/licenseApi/data.api.ts (1)

376-408: 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

encumbrance.licenseScope is declared but never read.

The encumbrance parameter type declares an independent licenseScope?: string field (Line 388), but the request-body construction always uses the outer licenseScope parameter instead of encumbrance.licenseScope, both for the top-level field (Line 393) and inside the nested encumbrance object (Line 400). If a caller ever passes a different value in encumbrance.licenseScope than the outer licenseScope, it will be silently dropped.

Currently harmless because LicenseCard.ts always passes the same value to both, but the type contract is misleading and error-prone for future callers.

🐛 Proposed fix
                     encumbrance: {
                         encumbranceType: encumbrance.encumbranceType,
                         clinicalPrivilegeActionCategories: encumbrance.npdbCategories,
                         encumbranceEffectiveDate: encumbrance.startDate,
-                        ...(licenseScope && { licenseScope }),
+                        ...((encumbrance.licenseScope || licenseScope) && { licenseScope: encumbrance.licenseScope || licenseScope }),
                     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/network/licenseApi/data.api.ts` around lines 376 - 408, The
updateLicenseInvestigation method in data.api.ts defines
encumbrance.licenseScope but never uses it, since the request body always reads
the outer licenseScope instead. Update the payload construction in
updateLicenseInvestigation so it consistently uses the intended scope source,
either by removing encumbrance.licenseScope from the encumbrance type or by
wiring it into both the top-level and nested encumbrance objects where
appropriate. Make sure the fields in the patch body match the contract used by
callers such as LicenseCard.
🧹 Nitpick comments (7)
webroot/src/models/License/License.model.ts (1)

158-182: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Consider extracting a shared translate-lookup helper.

licenseTypeDisplay(), licenseTypeAbbreviation(), and the new licenseScopeDisplay() all repeat the same $tm(...).find(...) lookup pattern. A small private helper (e.g. translateLookup(tmKey, matchValue)) would remove the duplication.

♻️ Example refactor
+    private translateLookupEntry(tmKey: string, matchValue: any): any {
+        const translations = this.$tm(tmKey) || [];
+
+        return translations.find((translate) => translate.key === matchValue);
+    }
+
     public licenseTypeDisplay(): string {
-        const licenseTypes = this.$tm('licensing.licenseTypes') || [];
-        const licenseType = licenseTypes.find((translate) => translate.key === this.licenseType);
-        const typeDisplay = licenseType?.name || '';
-
-        return typeDisplay;
+        return this.translateLookupEntry('licensing.licenseTypes', this.licenseType)?.name || '';
     }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/models/License/License.model.ts` around lines 158 - 182, The
three methods licenseTypeDisplay(), licenseTypeAbbreviation(), and
licenseScopeDisplay() duplicate the same $tm(...).find(...) lookup logic. Add a
small private helper in License.model.ts that takes the translation key and
matching value, performs the lookup once, and reuse it from these methods so the
repeated pattern is centralized and easier to maintain.
webroot/src/components/PrivilegeCard/PrivilegeCard.ts (2)

280-290: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Merge identical cosmetology/social-work branches.

The isAppModeCosmetology and isAppModeSocialWork branches (Lines 280-290) are byte-for-byte identical. Combining them avoids future divergence bugs (e.g., someone updates one list but forgets the other).

♻️ Proposed consolidation
-        } else if (isAppModeCosmetology) {
-            isMultiSelect = false;
-            includeList.push('fraud');
-            includeList.push('consumer harm');
-            includeList.push('other');
-        } else if (isAppModeSocialWork) {
+        } else if (isAppModeCosmetology || isAppModeSocialWork) {
             isMultiSelect = false;
             includeList.push('fraud');
             includeList.push('consumer harm');
             includeList.push('other');
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/components/PrivilegeCard/PrivilegeCard.ts` around lines 280 -
290, Merge the duplicate handling in PrivilegeCard’s app-mode branch logic: the
isAppModeCosmetology and isAppModeSocialWork cases do the exact same work, so
consolidate them into one shared branch in the relevant conditional block. Keep
the existing behavior in PrivilegeCard intact by setting isMultiSelect and
pushing the same includeList values from the combined branch, so future edits
only need to be made in one place.

293-299: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Remove leftover debug console.log.

console.log('???') at Line 297 is a debug artifact and shouldn't ship.

🧹 Proposed cleanup
         // For a single-select, include the blank option
         if (!isMultiSelect) {
-            console.log('???');
             options.unshift({ value: '', name: computed(() => this.$t('common.selectOption')) });
         }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/components/PrivilegeCard/PrivilegeCard.ts` around lines 293 -
299, Remove the leftover debug logging from PrivilegeCard’s single-select
branch: the console.log('???') inside the option setup should be deleted from
the code path in PrivilegeCard so only the blank option insertion remains. Keep
the existing behavior in the isMultiSelect check and the options.unshift call,
but eliminate the debug artifact before shipping.
webroot/src/components/LicenseCard/LicenseCard.less (1)

200-220: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Globe icon stroke color relies on cascade order, not specificity.

.scope-icon.stroke-fill-type (Line 212-215, matched via the class hardcoded in Globe.vue's template) and .scope-icon.globe-icon (Line 217-219) have identical specificity. The globe stroke correctly ends up as @fontColor only because this rule is declared after .stroke-fill-type in the stylesheet — if these rules are ever reordered (e.g. during a later refactor), the globe icon would silently revert to a white stroke on the light multi-state pill background, hurting contrast/readability.

Consider increasing specificity explicitly instead of relying on declaration order:

♻️ Suggested fix
-            &.stroke-fill-type,
-            &.stroke-type {
-                stroke: `@white`;
-            }
-
-            &.globe-icon {
-                stroke: `@fontColor`;
-            }
+            &.globe-icon {
+                stroke: `@fontColor`;
+            }
+
+            &.stroke-fill-type,
+            &.stroke-type {
+                &:not(.globe-icon) {
+                    stroke: `@white`;
+                }
+            }
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/components/LicenseCard/LicenseCard.less` around lines 200 - 220,
The globe icon stroke color in LicenseCard.less is depending on rule order
instead of specificity, so make the `.scope-icon.globe-icon` styling explicitly
override the shared `.scope-icon.stroke-fill-type` / `.scope-icon.stroke-type`
rules. Update the `.scope-icon` block in LicenseCard.less so the Globe.vue icon
class wins by specificity rather than declaration order, keeping the stroke as
`@fontColor` regardless of future CSS reordering.
webroot/src/components/LicenseCard/LicenseCard.ts (2)

596-604: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Redundant licenseScope inside the nested encumbrance object.

licenseScope is already forwarded at the top level of the updateInvestigationLicenseRequest payload (Line 596). Based on the LicenseDataApi.updateLicenseInvestigation implementation, the nested encumbrance object is built using the outer licenseScope param, not encumbrance.licenseScope, so the value set here at Line 603 has no effect and is dead weight in the payload.

🧹 Proposed cleanup
                     encumbrance: {
                         encumbranceType: formData.encumberModalDisciplineAction.value,
                         npdbCategories: (Array.isArray(formData.encumberModalNpdbCategories.value))
                             ? formData.encumberModalNpdbCategories.value
                             : [formData.encumberModalNpdbCategories.value],
                         startDate: formData.encumberModalStartDate.value,
-                        licenseScope,
                     },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/components/LicenseCard/LicenseCard.ts` around lines 596 - 604,
The nested licenseScope inside the encumbrance payload in LicenseCard is
redundant because updateInvestigationLicenseRequest already passes licenseScope
at the top level and LicenseDataApi.updateLicenseInvestigation uses the outer
value. Remove the inner licenseScope from the encumbrance object in the
LicenseCard request builder so the payload only includes the effective top-level
field.

310-350: 🎯 Functional Correctness | 🔵 Trivial | 💤 Low value

Consolidate the Cosmetology/SocialWork branch. Both paths set the same include list and isMultiSelect = false; merge them to avoid divergence.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/components/LicenseCard/LicenseCard.ts` around lines 310 - 350,
The npdbCategoryOptions getter has duplicated logic for the cosmetology and
social work modes, which should be consolidated to avoid divergence. Update
LicenseCard.ts so the isAppModeCosmetology and isAppModeSocialWork branches
share the same includeList and single-select behavior, keeping the unique
behavior in npdbCategoryOptions while removing the redundant branch split.
webroot/src/components/Icons/MapPin/MapPin.ts (1)

18-18: 📐 Maintainability & Code Quality | 🔵 Trivial | 💤 Low value

Remove leftover commented-out export.

Dead code artifact from scaffolding.

🧹 Proposed cleanup
 export default toNative(MapPin);
-
-// export default MapPin;
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@webroot/src/components/Icons/MapPin/MapPin.ts` at line 18, Remove the
leftover commented-out export in MapPin and keep the module clean by deleting
the dead scaffold artifact. Update the MapPin component file so only the active
MapPin export remains, and leave no commented-out export statements behind.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@webroot/src/locales/es.json`:
- Around line 967-981: Update the Spanish locale entries used by
PrivilegeCard.ts’s npdbCategoryOptions so the option keys match the lowercase
identifiers expected by the COSM/SocialWork includeList. In es.json, align the
"Fraude" and "Daños al consumidor" items to use the same stable keys as the new
en.json entries (for example the lowercase fraud/consumer harm/other
identifiers), and remove the duplicate value collision caused by reusing "Fraud,
Deception, or Misrepresentation" for multiple Spanish labels. Ensure the
localized names remain Spanish while the keys are unique and consistent with the
option filtering logic.

---

Outside diff comments:
In `@webroot/src/network/licenseApi/data.api.ts`:
- Around line 376-408: The updateLicenseInvestigation method in data.api.ts
defines encumbrance.licenseScope but never uses it, since the request body
always reads the outer licenseScope instead. Update the payload construction in
updateLicenseInvestigation so it consistently uses the intended scope source,
either by removing encumbrance.licenseScope from the encumbrance type or by
wiring it into both the top-level and nested encumbrance objects where
appropriate. Make sure the fields in the patch body match the contract used by
callers such as LicenseCard.

---

Nitpick comments:
In `@webroot/src/components/Icons/MapPin/MapPin.ts`:
- Line 18: Remove the leftover commented-out export in MapPin and keep the
module clean by deleting the dead scaffold artifact. Update the MapPin component
file so only the active MapPin export remains, and leave no commented-out export
statements behind.

In `@webroot/src/components/LicenseCard/LicenseCard.less`:
- Around line 200-220: The globe icon stroke color in LicenseCard.less is
depending on rule order instead of specificity, so make the
`.scope-icon.globe-icon` styling explicitly override the shared
`.scope-icon.stroke-fill-type` / `.scope-icon.stroke-type` rules. Update the
`.scope-icon` block in LicenseCard.less so the Globe.vue icon class wins by
specificity rather than declaration order, keeping the stroke as `@fontColor`
regardless of future CSS reordering.

In `@webroot/src/components/LicenseCard/LicenseCard.ts`:
- Around line 596-604: The nested licenseScope inside the encumbrance payload in
LicenseCard is redundant because updateInvestigationLicenseRequest already
passes licenseScope at the top level and
LicenseDataApi.updateLicenseInvestigation uses the outer value. Remove the inner
licenseScope from the encumbrance object in the LicenseCard request builder so
the payload only includes the effective top-level field.
- Around line 310-350: The npdbCategoryOptions getter has duplicated logic for
the cosmetology and social work modes, which should be consolidated to avoid
divergence. Update LicenseCard.ts so the isAppModeCosmetology and
isAppModeSocialWork branches share the same includeList and single-select
behavior, keeping the unique behavior in npdbCategoryOptions while removing the
redundant branch split.

In `@webroot/src/components/PrivilegeCard/PrivilegeCard.ts`:
- Around line 280-290: Merge the duplicate handling in PrivilegeCard’s app-mode
branch logic: the isAppModeCosmetology and isAppModeSocialWork cases do the
exact same work, so consolidate them into one shared branch in the relevant
conditional block. Keep the existing behavior in PrivilegeCard intact by setting
isMultiSelect and pushing the same includeList values from the combined branch,
so future edits only need to be made in one place.
- Around line 293-299: Remove the leftover debug logging from PrivilegeCard’s
single-select branch: the console.log('???') inside the option setup should be
deleted from the code path in PrivilegeCard so only the blank option insertion
remains. Keep the existing behavior in the isMultiSelect check and the
options.unshift call, but eliminate the debug artifact before shipping.

In `@webroot/src/models/License/License.model.ts`:
- Around line 158-182: The three methods licenseTypeDisplay(),
licenseTypeAbbreviation(), and licenseScopeDisplay() duplicate the same
$tm(...).find(...) lookup logic. Add a small private helper in License.model.ts
that takes the translation key and matching value, performs the lookup once, and
reuse it from these methods so the repeated pattern is centralized and easier to
maintain.
🪄 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: defaults

Review profile: CHILL

Plan: Pro

Run ID: c144699e-7266-4ce2-998b-f6140750d4ab

📥 Commits

Reviewing files that changed from the base of the PR and between a7d8d07 and 2bdd6d3.

📒 Files selected for processing (23)
  • webroot/src/components/Icons/Globe/Globe.less
  • webroot/src/components/Icons/Globe/Globe.spec.ts
  • webroot/src/components/Icons/Globe/Globe.ts
  • webroot/src/components/Icons/Globe/Globe.vue
  • webroot/src/components/Icons/MapPin/MapPin.less
  • webroot/src/components/Icons/MapPin/MapPin.spec.ts
  • webroot/src/components/Icons/MapPin/MapPin.ts
  • webroot/src/components/Icons/MapPin/MapPin.vue
  • webroot/src/components/LicenseCard/LicenseCard.less
  • webroot/src/components/LicenseCard/LicenseCard.ts
  • webroot/src/components/LicenseCard/LicenseCard.vue
  • webroot/src/components/PrivilegeCard/PrivilegeCard.less
  • webroot/src/components/PrivilegeCard/PrivilegeCard.ts
  • webroot/src/components/PrivilegeCard/PrivilegeCard.vue
  • webroot/src/locales/en.json
  • webroot/src/locales/es.json
  • webroot/src/models/License/License.model.spec.ts
  • webroot/src/models/License/License.model.ts
  • webroot/src/network/data.api.ts
  • webroot/src/network/licenseApi/data.api.ts
  • webroot/src/network/mocks/mock.data.ts
  • webroot/src/store/users/users.actions.ts
  • webroot/src/styles.common/_colors.less

Comment thread webroot/src/locales/es.json
@jsandoval81 jsandoval81 requested a review from rmolinares July 6, 2026 16:08
Comment thread webroot/src/components/PrivilegeCard/PrivilegeCard.ts Outdated
@jsandoval81 jsandoval81 requested a review from jlkravitz July 6, 2026 18:43
@jsandoval81

Copy link
Copy Markdown
Collaborator Author

@jlkravitz This is ready for your review.

@jlkravitz jlkravitz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

One small nit, otherwise looks great!

Comment thread webroot/src/components/Icons/Globe/Globe.vue Outdated
@jsandoval81 jsandoval81 requested a review from jlkravitz July 8, 2026 16:52
@jsandoval81

Copy link
Copy Markdown
Collaborator Author

@jlkravitz Ready for re-review

@jlkravitz jlkravitz left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

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

@isabeleliassen This is good to go!

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.

Multistate compact license scopes

3 participants