@saasicat/ui-vue 0.21.0 → 0.22.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (132) hide show
  1. package/dist/{catalog-BY_t9prv.d.cts → catalog-DJcfm5N2.d.cts} +20 -7
  2. package/dist/{catalog-BY_t9prv.d.ts → catalog-DJcfm5N2.d.ts} +20 -7
  3. package/dist/{chunk-JN6HZGLV.js → chunk-F7NRM6KI.js} +54 -28
  4. package/dist/{chunk-7IFKVGZV.js → chunk-LY52T4N4.js} +21 -2
  5. package/dist/{chunk-OTP3OSSW.js → chunk-XMMFN2WJ.js} +74 -3
  6. package/dist/client/index.cjs +132 -29
  7. package/dist/client/index.d.cts +53 -3
  8. package/dist/client/index.d.ts +53 -3
  9. package/dist/client/index.js +14 -2
  10. package/dist/index.cjs +319 -162
  11. package/dist/index.d.cts +30 -8
  12. package/dist/index.d.ts +30 -8
  13. package/dist/index.js +176 -133
  14. package/dist/quasar/index.cjs +76 -29
  15. package/dist/quasar/index.d.cts +2 -2
  16. package/dist/quasar/index.d.ts +2 -2
  17. package/dist/quasar/index.js +6 -2
  18. package/dist/testing-e2e/admin-pages-suite.d.cts +37 -0
  19. package/dist/testing-e2e/admin-pages-suite.d.ts +37 -0
  20. package/dist/{use-super-admin-i18n-c_Eqsu9u.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
  21. package/dist/{use-super-admin-i18n-DlJRGIVq.d.cts → use-super-admin-i18n-CgTuNi8h.d.cts} +34 -7
  22. package/package.json +18 -8
  23. package/src/client/i18n/messages/bundles.ts +0 -2
  24. package/src/client/i18n/messages/common.ts +22 -0
  25. package/src/client/i18n/messages/discovery.ts +2 -2
  26. package/src/client/i18n/messages/marketing.ts +2 -4
  27. package/src/client/i18n/messages/plan-detail.ts +0 -4
  28. package/src/client/i18n/messages/plans.ts +20 -8
  29. package/src/client/i18n/messages/tenants.ts +0 -2
  30. package/src/client/index.ts +1 -0
  31. package/src/client/manifest-loader.ts +1 -1
  32. package/src/client/resolve-plans.ts +144 -0
  33. package/src/components/BundleVersionPublishDialog.vue +6 -6
  34. package/src/components/KvBlock.vue +4 -4
  35. package/src/components/MarketingPromotionsTab.vue +34 -48
  36. package/src/components/MfaPromptDialog.vue +8 -2
  37. package/src/components/TenantActionConfirmDialog.vue +4 -4
  38. package/src/components/VersionDiffPreview.vue +7 -7
  39. package/src/components/admin-page/AdminFilters.vue +12 -0
  40. package/src/components/admin-page/AdminHero.vue +22 -5
  41. package/src/components/admin-page/AdminKpi.vue +84 -0
  42. package/src/components/admin-page/AdminPaginator.vue +195 -0
  43. package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
  44. package/src/components/admin-page/AdminSection.vue +31 -16
  45. package/src/components/admin-page/AdminStatistics.vue +40 -0
  46. package/src/components/admin-page/AdminTable.vue +175 -0
  47. package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
  48. package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
  49. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
  50. package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
  51. package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
  52. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
  53. package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
  54. package/src/components/dialogs/PilotCreateDialog.vue +12 -12
  55. package/src/components/dialogs/PilotEditDialog.vue +7 -7
  56. package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
  57. package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
  58. package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
  59. package/src/components/plan/PlanGrid.vue +1 -1
  60. package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
  61. package/src/components/plan-detail/PlanDetail.vue +92 -302
  62. package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
  63. package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
  64. package/src/components/plan-list/PlanList.vue +72 -290
  65. package/src/components/plan-matrix/PlanMatrix.vue +66 -183
  66. package/src/components/plan-review/PlanReview.vue +191 -275
  67. package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
  68. package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
  69. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
  70. package/src/index.ts +1 -0
  71. package/src/pages-standard/AdminLayout.vue +34 -10
  72. package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
  73. package/src/pages-standard/AuditPage.vue +53 -72
  74. package/src/pages-standard/BundlesPage.vue +128 -144
  75. package/src/pages-standard/DashboardPage.vue +43 -107
  76. package/src/pages-standard/DiscoveryPage.vue +158 -200
  77. package/src/pages-standard/EmailHistoryPage.vue +98 -88
  78. package/src/pages-standard/MarketingCatalogPage.vue +237 -267
  79. package/src/pages-standard/PilotsPage.vue +44 -60
  80. package/src/pages-standard/PlansPage.vue +230 -105
  81. package/src/pages-standard/PlatformEmailPage.vue +37 -40
  82. package/src/pages-standard/PromoCodeDetailPage.vue +16 -30
  83. package/src/pages-standard/PromoCodesPage.vue +86 -112
  84. package/src/pages-standard/SubscriptionsPage.vue +10 -17
  85. package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
  86. package/src/pages-standard/SuperAdminSetupWizard.vue +20 -17
  87. package/src/pages-standard/TenantDetailPage.vue +45 -41
  88. package/src/pages-standard/TenantsPage.vue +184 -264
  89. package/src/pages-standard/UsersPage.vue +69 -104
  90. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
  91. package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
  92. package/src/pages-standard/bundles-page/BundlesToolbar.vue +6 -10
  93. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
  94. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
  95. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
  96. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
  97. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
  98. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +1 -1
  99. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +2 -13
  100. package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
  101. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
  102. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
  103. package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
  104. package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
  105. package/src/pages-standard/plans-page/PlansPageToast.vue +1 -1
  106. package/src/pages-standard/sa-theme.css +481 -71
  107. package/src/pages-standard/tenant-detail/TenantUsers.vue +2 -8
  108. package/src/pages-standard/tenants/StatusPill.vue +2 -2
  109. package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
  110. package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
  111. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  112. package/src/pages-tenant/TenantPlanSection.vue +12 -20
  113. package/src/quasar/create-super-admin-app.ts +4 -0
  114. package/src/vue/entitlement-provider.ts +0 -1
  115. package/src/vue/feature-router-guard.ts +0 -1
  116. package/src/vue/super-admin-context.ts +106 -1
  117. package/src/vue/use-api-list.ts +2 -1
  118. package/src/vue/use-pagination.ts +63 -0
  119. package/src/vue/use-plans.ts +1 -2
  120. package/src/vue/use-sign-out.ts +90 -0
  121. package/src/vue/use-tenant-billing.ts +1 -1
  122. package/src/vue/use-tenant-manifest.ts +0 -1
  123. package/src/components/KpiCard.vue +0 -53
  124. package/src/components/plan-cockpit/PlanCockpit.vue +0 -1027
  125. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
  126. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -99
  127. package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
  128. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
  129. package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
  130. package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -249
  131. package/src/components/plan-cockpit/types.ts +0 -72
  132. package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
@@ -5,136 +5,139 @@
5
5
  {{ msg.subtitleLead }} <b>{{ msg.subtitleEmphasis }}</b> {{ msg.subtitleTail }}
6
6
  </template>
7
7
  <template #actions>
8
- <q-btn
9
- unelevated
10
- color="primary"
11
- icon="bolt"
12
- :label="msg.runDiscovery"
13
- :loading="loading"
14
- @click="onRunDiscovery"
15
- />
8
+ <AdminRefreshBtn :loading="loading" @refresh="onRunDiscovery" />
16
9
  </template>
17
10
  </AdminHero>
18
11
 
19
- <DiscoveryMetaBanner
20
- :app-label="appLabel"
21
- :app-key="appKey"
22
- :app-version="appVersion"
23
- :scan-label="scanLabel"
24
- />
25
-
26
- <q-banner v-if="error" class="sa-discovery__error" rounded>
27
- <template #avatar><q-icon name="warning" color="negative" /></template>
28
- {{ common.error }}: {{ error.message }}
29
- </q-banner>
30
-
31
- <DiscoveryKpis
32
- :features-count="features.length"
33
- :capabilities-count="capabilities.length"
34
- :approved-count="approvedCount"
35
- :pending-count="pendingCount"
36
- :outdated-count="outdatedCount"
37
- :obsolete-count="obsoleteCount"
38
- :orphan-count="orphanCaps.length"
39
- />
40
-
41
- <q-tabs v-model="activeTab" align="left" dense class="sa-discovery__tabs">
42
- <q-tab name="features" :label="featuresTabLabel" />
43
- <q-tab name="quotas" :label="quotasTabLabel" />
44
- </q-tabs>
45
-
46
- <q-tab-panels v-model="activeTab" animated class="sa-discovery__panels">
47
- <q-tab-panel name="features" class="sa-discovery__panel">
48
- <div class="sa-discovery__toolbar">
49
- <q-input
50
- v-model="featureQuery"
51
- dense
52
- outlined
53
- clearable
54
- :placeholder="msg.searchPlaceholder"
55
- class="sa-discovery__search"
56
- >
57
- <template #prepend><q-icon name="search" /></template>
58
- </q-input>
59
- <q-select
60
- v-model="statusFilter"
61
- dense
62
- outlined
63
- emit-value
64
- map-options
65
- :options="statusFilterOptions"
66
- class="sa-discovery__filter"
67
- />
68
- </div>
69
-
70
- <AdminSection
71
- v-for="group in featureGroups"
72
- :key="group.label"
73
- :title="group.label"
74
- class="sa-discovery__group"
75
- >
76
- <template #actions>
77
- <span class="sa-discovery__group-count">{{ group.features.length }}</span>
78
- </template>
79
- <div class="sa-discovery__cardlist">
80
- <DiscoveryFeatureCard
81
- v-for="f in group.features"
82
- :key="f.featureKey"
83
- :feature="f"
84
- :capabilities="capsByFeature.get(f.featureKey) ?? []"
85
- :owners="ownersByFeature.get(f.featureKey) ?? []"
86
- :declared-at-by-key="declaredAtByKey"
87
- :active-locales="activeLocales"
88
- :expanded="expandedFeature === f.featureKey"
89
- @toggle="toggleFeature(f.featureKey)"
90
- @review="onFeatureReview"
91
- @feature-base="onFeatureBase"
92
- @feature-locale="onFeatureLocale"
93
- />
94
- </div>
95
- </AdminSection>
96
- <div v-if="filteredFeatures.length === 0" class="sa-discovery__empty-row">
97
- {{ msg.noFeaturesMatchFilters }}
98
- </div>
99
-
100
- <AdminSection
101
- v-if="orphanCaps.length"
102
- :title="msg.orphansTitle"
103
- class="sa-discovery__group sa-discovery__group--orphan"
104
- >
105
- <template #actions>
106
- <span class="sa-discovery__group-count">{{ orphanCaps.length }}</span>
107
- </template>
108
- <p class="sa-discovery__orphan-hint">
109
- {{ msg.orphanHint.before }} <code>feature:</code>{{ msg.orphanHint.middle }}
110
- <code>@ImplementsCapability</code>{{ msg.orphanHint.after }}
111
- </p>
112
- <DiscoveryCapList
113
- :capabilities="orphanCaps"
114
- :declared-at-by-key="declaredAtByKey"
115
- />
116
- </AdminSection>
117
- </q-tab-panel>
118
-
119
- <q-tab-panel name="quotas" class="sa-discovery__panel">
120
- <div class="sa-discovery__cardlist">
121
- <DiscoveryQuotaCard
122
- v-for="q in quotas"
123
- :key="q.quotaKey"
124
- :quota="q"
125
- :active-locales="activeLocales"
126
- :expanded="expandedQuota === q.quotaKey"
127
- @toggle="toggleQuota(q.quotaKey)"
128
- @review="onQuotaReview"
129
- @quota-base="onQuotaBase"
130
- @quota-locale="onQuotaLocale"
131
- />
132
- <div v-if="quotas.length === 0" class="sa-discovery__empty-row">
133
- {{ msg.noQuotasDeclared }}
134
- </div>
135
- </div>
136
- </q-tab-panel>
137
- </q-tab-panels>
12
+ <AdminBody>
13
+ <DiscoveryMetaBanner
14
+ :app-label="appLabel"
15
+ :app-key="appKey"
16
+ :app-version="appVersion"
17
+ :scan-label="scanLabel"
18
+ />
19
+ <q-banner v-if="error" class="sa-discovery__error" rounded>
20
+ <template #avatar><q-icon name="warning" color="negative" /></template>
21
+ {{ common.error }}: {{ error.message }}
22
+ </q-banner>
23
+
24
+ <AdminSection class="q-mt-lg">
25
+ <DiscoveryKpis
26
+ :features-count="features.length"
27
+ :capabilities-count="capabilities.length"
28
+ :approved-count="approvedCount"
29
+ :pending-count="pendingCount"
30
+ :outdated-count="outdatedCount"
31
+ :obsolete-count="obsoleteCount"
32
+ :orphan-count="orphanCaps.length"
33
+ />
34
+ </AdminSection>
35
+
36
+ <AdminSection class="sa-discovery__panels">
37
+ <q-tabs v-model="activeTab" align="left" dense class="sa-discovery__tabs">
38
+ <q-tab name="features" :label="featuresTabLabel" />
39
+ <q-tab name="quotas" :label="quotasTabLabel" />
40
+ </q-tabs>
41
+ <q-tab-panels v-model="activeTab" animated>
42
+ <q-tab-panel name="features" class="sa-discovery__panel">
43
+ <AdminFilters class="q-mb-md">
44
+ <q-input
45
+ v-model="featureQuery"
46
+ dense
47
+ outlined
48
+ clearable
49
+ :placeholder="msg.searchPlaceholder"
50
+ class="sa-discovery__search"
51
+ >
52
+ <template #prepend><q-icon name="search" /></template>
53
+ </q-input>
54
+ <q-select
55
+ v-model="statusFilter"
56
+ dense
57
+ outlined
58
+ emit-value
59
+ map-options
60
+ :options="statusFilterOptions"
61
+ class="sa-discovery__filter"
62
+ />
63
+ </AdminFilters>
64
+
65
+ <AdminSection
66
+ v-for="group in featureGroups"
67
+ :key="group.label"
68
+ :title="group.label"
69
+ class="sa-discovery__group"
70
+ >
71
+ <template #actions>
72
+ <span class="sa-discovery__group-count">{{
73
+ group.features.length
74
+ }}</span>
75
+ </template>
76
+ <div class="sa-discovery__cardlist">
77
+ <DiscoveryFeatureCard
78
+ v-for="f in group.features"
79
+ :key="f.featureKey"
80
+ :feature="f"
81
+ :capabilities="capsByFeature.get(f.featureKey) ?? []"
82
+ :owners="ownersByFeature.get(f.featureKey) ?? []"
83
+ :declared-at-by-key="declaredAtByKey"
84
+ :active-locales="activeLocales"
85
+ :expanded="expandedFeature === f.featureKey"
86
+ @toggle="toggleFeature(f.featureKey)"
87
+ @review="onFeatureReview"
88
+ @feature-base="onFeatureBase"
89
+ @feature-locale="onFeatureLocale"
90
+ />
91
+ </div>
92
+ </AdminSection>
93
+
94
+ <div v-if="filteredFeatures.length === 0" class="sa-discovery__empty-row">
95
+ {{ msg.noFeaturesMatchFilters }}
96
+ </div>
97
+
98
+ <AdminSection
99
+ v-if="orphanCaps.length"
100
+ :title="msg.orphansTitle"
101
+ class="sa-discovery__group sa-discovery__group--orphan"
102
+ >
103
+ <template #actions>
104
+ <span class="sa-discovery__group-count">{{
105
+ orphanCaps.length
106
+ }}</span>
107
+ </template>
108
+ <p class="sa-discovery__orphan-hint">
109
+ {{ msg.orphanHint.before }} <code>feature:</code
110
+ >{{ msg.orphanHint.middle }} <code>@ImplementsCapability</code
111
+ >{{ msg.orphanHint.after }}
112
+ </p>
113
+ <DiscoveryCapList
114
+ :capabilities="orphanCaps"
115
+ :declared-at-by-key="declaredAtByKey"
116
+ />
117
+ </AdminSection>
118
+ </q-tab-panel>
119
+
120
+ <q-tab-panel name="quotas" class="sa-discovery__panel">
121
+ <div class="sa-discovery__cardlist">
122
+ <DiscoveryQuotaCard
123
+ v-for="q in quotas"
124
+ :key="q.quotaKey"
125
+ :quota="q"
126
+ :active-locales="activeLocales"
127
+ :expanded="expandedQuota === q.quotaKey"
128
+ @toggle="toggleQuota(q.quotaKey)"
129
+ @review="onQuotaReview"
130
+ @quota-base="onQuotaBase"
131
+ @quota-locale="onQuotaLocale"
132
+ />
133
+ <div v-if="quotas.length === 0" class="sa-discovery__empty-row">
134
+ {{ msg.noQuotasDeclared }}
135
+ </div>
136
+ </div>
137
+ </q-tab-panel>
138
+ </q-tab-panels>
139
+ </AdminSection>
140
+ </AdminBody>
138
141
  </AdminPage>
139
142
  </template>
140
143
 
@@ -151,8 +154,11 @@ import type {
151
154
  ReviewCatalogEntryData,
152
155
  UpdateCatalogEntryBaseData,
153
156
  } from '@saasicat/types';
157
+ import AdminRefreshBtn from '../components/admin-page/AdminRefreshBtn.vue';
154
158
  import DiscoveryCapList from './discovery-page/DiscoveryCapList.vue';
155
159
  import DiscoveryFeatureCard from './discovery-page/DiscoveryFeatureCard.vue';
160
+ import AdminBody from '../components/admin-page/AdminBody.vue';
161
+ import AdminFilters from '../components/admin-page/AdminFilters.vue';
156
162
  import AdminHero from '../components/admin-page/AdminHero.vue';
157
163
  import AdminSection from '../components/admin-page/AdminSection.vue';
158
164
  import AdminPage from '../components/admin-page/AdminPage.vue';
@@ -202,14 +208,15 @@ const featuresTabLabel = computed(() =>
202
208
  const quotasTabLabel = computed(() =>
203
209
  formatMessage(msg.value.tabQuotas, { count: props.quotas.length }),
204
210
  );
205
- const featureQuery = ref('');
211
+ // `clearable` emits null, not '' — see Quasar's use-field clearValue().
212
+ const featureQuery = ref<string | null>('');
206
213
  const statusFilter = ref<DiscoveryStatus | 'all'>('all');
207
214
  const expandedFeature = ref<string | null>(null);
208
215
  const expandedQuota = ref<string | null>(null);
209
216
 
210
217
  const msg = useSaMessages('discovery');
211
218
  const common = useSaMessages('common');
212
- const { locale, intlLocale } = useSuperAdminI18n();
219
+ const { intlLocale } = useSuperAdminI18n();
213
220
 
214
221
  const statusFilterOptions = computed<Array<{ label: string; value: DiscoveryStatus | 'all' }>>(
215
222
  () => [
@@ -293,7 +300,7 @@ const obsoleteCount = computed(
293
300
  );
294
301
 
295
302
  const filteredFeatures = computed(() => {
296
- const q = featureQuery.value.trim().toLowerCase();
303
+ const q = (featureQuery.value ?? '').trim().toLowerCase();
297
304
  return props.features.filter((f) => {
298
305
  if (statusFilter.value !== 'all' && f.discoveryStatus !== statusFilter.value) {
299
306
  return false;
@@ -462,8 +469,8 @@ onMounted(() => {
462
469
  display: flex;
463
470
  justify-content: space-between;
464
471
  align-items: center;
465
- background: #0f172a;
466
- color: #e2e8f0;
472
+ background: var(--sa-heading);
473
+ color: var(--sa-border);
467
474
  border-radius: 12px;
468
475
  padding: 14px 18px;
469
476
  }
@@ -478,7 +485,7 @@ onMounted(() => {
478
485
  }
479
486
  .sa-discovery__banner-meta {
480
487
  font-size: 11px;
481
- color: #94a3b8;
488
+ color: var(--sa-muted-light);
482
489
  }
483
490
  .sa-discovery__banner-time {
484
491
  text-align: right;
@@ -487,7 +494,7 @@ onMounted(() => {
487
494
  font-size: 10px;
488
495
  text-transform: uppercase;
489
496
  letter-spacing: 0.5px;
490
- color: #94a3b8;
497
+ color: var(--sa-muted-light);
491
498
  }
492
499
  .sa-discovery__banner-time-val {
493
500
  font-size: 13px;
@@ -496,57 +503,8 @@ onMounted(() => {
496
503
  .sa-discovery__error {
497
504
  border-left: 4px solid #dc2626;
498
505
  }
499
- .sa-discovery__kpis {
500
- display: grid;
501
- grid-template-columns: repeat(5, 1fr);
502
- gap: 10px;
503
- }
504
- .sa-discovery__kpi {
505
- background: #fff;
506
- border: 1px solid #e2e8f0;
507
- padding: 12px 14px;
508
- border-radius: 10px;
509
- }
510
- .sa-discovery__kpi.good {
511
- border-color: #a7f3d0;
512
- }
513
- .sa-discovery__kpi.good .sa-discovery__kpi-label,
514
- .sa-discovery__kpi.good .sa-discovery__kpi-value {
515
- color: #047857;
516
- }
517
- .sa-discovery__kpi.warn {
518
- border-color: #fde68a;
519
- background: #fffbeb;
520
- }
521
- .sa-discovery__kpi.bad {
522
- border-color: #fecaca;
523
- background: #fef2f2;
524
- }
525
- .sa-discovery__kpi-label {
526
- font-size: 10px;
527
- text-transform: uppercase;
528
- letter-spacing: 0.5px;
529
- color: #64748b;
530
- }
531
- .sa-discovery__kpi-value {
532
- font-size: 26px;
533
- font-weight: 700;
534
- }
535
- .sa-discovery__kpi-sub {
536
- font-size: 11px;
537
- color: #94a3b8;
538
- }
539
506
  .sa-discovery__tabs {
540
- border-bottom: 1px solid #e2e8f0;
541
- }
542
- .sa-discovery__panel {
543
- padding: 14px 0;
544
- }
545
- .sa-discovery__toolbar {
546
- display: flex;
547
- gap: 12px;
548
- margin-bottom: 14px;
549
- align-items: center;
507
+ border-bottom: 1px solid var(--sa-border);
550
508
  }
551
509
  .sa-discovery__search {
552
510
  flex: 1;
@@ -568,8 +526,8 @@ onMounted(() => {
568
526
  .sa-discovery__group-count {
569
527
  font-size: 10px;
570
528
  font-weight: 700;
571
- background: #e2e8f0;
572
- color: #475569;
529
+ background: var(--sa-border);
530
+ color: var(--sa-muted-dark);
573
531
  padding: 1px 7px;
574
532
  border-radius: 8px;
575
533
  }
@@ -580,20 +538,20 @@ onMounted(() => {
580
538
  }
581
539
  .sa-discovery__orphan-hint code {
582
540
  font-size: 10px;
583
- background: #fffbeb;
541
+ background: var(--sa-warning-bg);
584
542
  padding: 1px 4px;
585
543
  border-radius: 4px;
586
544
  }
587
545
  .sa-discovery__empty-row {
588
546
  padding: 36px;
589
547
  text-align: center;
590
- color: #94a3b8;
548
+ color: var(--sa-muted-light);
591
549
  font-size: 13px;
592
550
  border: 1px dashed #cbd5e1;
593
551
  border-radius: 12px;
594
552
  }
595
553
  .sa-muted {
596
- color: #94a3b8;
554
+ color: var(--sa-muted-light);
597
555
  margin-right: 3px;
598
556
  }
599
557
  /* Status badge (review lifecycle) — shared by feature and quota card. */
@@ -603,8 +561,8 @@ onMounted(() => {
603
561
  text-transform: uppercase;
604
562
  padding: 3px 8px;
605
563
  border-radius: 6px;
606
- background: #f1f5f9;
607
- color: #475569;
564
+ background: var(--sa-border-soft);
565
+ color: var(--sa-muted-dark);
608
566
  flex-shrink: 0;
609
567
  }
610
568
  .sa-review--pending {
@@ -620,16 +578,16 @@ onMounted(() => {
620
578
  color: #b91c1c;
621
579
  }
622
580
  .sa-review--obsolete {
623
- background: #e2e8f0;
624
- color: #64748b;
581
+ background: var(--sa-border);
582
+ color: var(--sa-muted);
625
583
  }
626
584
  .sa-chip {
627
585
  font-size: 10px;
628
586
  font-weight: 600;
629
587
  padding: 2px 7px;
630
588
  border-radius: 6px;
631
- background: #f1f5f9;
632
- color: #475569;
589
+ background: var(--sa-border-soft);
590
+ color: var(--sa-muted-dark);
633
591
  }
634
592
  /* i18n coverage pill — shared by feature and quota card. */
635
593
  .sa-cov-pill {
@@ -640,18 +598,18 @@ onMounted(() => {
640
598
  font-weight: 700;
641
599
  padding: 2px 7px;
642
600
  border-radius: 8px;
643
- border: 1px solid #e2e8f0;
644
- background: #f8fafc;
645
- color: #475569;
601
+ border: 1px solid var(--sa-border);
602
+ background: var(--sa-bg-surface-2);
603
+ color: var(--sa-muted-dark);
646
604
  }
647
605
  .sa-cov-pill.complete {
648
606
  border-color: #a7f3d0;
649
- background: #ecfdf5;
607
+ background: var(--sa-positive-bg);
650
608
  color: #047857;
651
609
  }
652
610
  .sa-cov-pill.warn {
653
611
  border-color: #fde68a;
654
- background: #fffbeb;
612
+ background: var(--sa-warning-bg);
655
613
  color: #b45309;
656
614
  }
657
615
  .sa-cov-pill.low {