@saasicat/ui-vue 0.19.0 → 0.22.0

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 (145) hide show
  1. package/dist/{catalog-JXdKdIqG.d.cts → catalog-DJcfm5N2.d.cts} +21 -7
  2. package/dist/{catalog-JXdKdIqG.d.ts → catalog-DJcfm5N2.d.ts} +21 -7
  3. package/dist/{chunk-DPTIOQXX.js → chunk-F7NRM6KI.js} +56 -28
  4. package/dist/{chunk-RVG6LQHL.js → chunk-LY52T4N4.js} +21 -2
  5. package/dist/{chunk-ORX3ZG47.js → chunk-XMMFN2WJ.js} +74 -3
  6. package/dist/client/index.cjs +134 -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 +321 -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 +78 -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-BlqcD0N7.d.ts → use-super-admin-i18n-CHNdsSlm.d.ts} +34 -7
  21. package/dist/{use-super-admin-i18n-1nCOkvgi.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 +24 -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/AdminBody.vue +40 -0
  40. package/src/components/admin-page/AdminFilters.vue +12 -0
  41. package/src/components/admin-page/AdminHero.vue +49 -0
  42. package/src/components/admin-page/AdminKpi.vue +84 -0
  43. package/src/components/admin-page/AdminPage.vue +16 -0
  44. package/src/components/admin-page/AdminPaginator.vue +195 -0
  45. package/src/components/admin-page/AdminRefreshBtn.vue +39 -0
  46. package/src/components/admin-page/AdminSection.vue +53 -0
  47. package/src/components/admin-page/AdminStatistics.vue +40 -0
  48. package/src/components/admin-page/AdminTable.vue +175 -0
  49. package/src/components/bundle-editor/BundleCreatePanel.vue +23 -23
  50. package/src/components/bundle-editor/BundleFeaturesEditor.vue +7 -7
  51. package/src/components/bundle-editor/BundlePlanCompatPicker.vue +9 -9
  52. package/src/components/bundle-editor/BundleQuotasEditor.vue +11 -11
  53. package/src/components/bundle-editor/BundleStatusBanner.vue +6 -6
  54. package/src/components/bundle-editor/BundleVersionInlineEditor.vue +18 -18
  55. package/src/components/bundle-editor/BundleVersionStrip.vue +12 -12
  56. package/src/components/dialogs/PilotCreateDialog.vue +12 -12
  57. package/src/components/dialogs/PilotEditDialog.vue +7 -7
  58. package/src/components/dialogs/PromoCodeCreateDialog.vue +8 -8
  59. package/src/components/dialogs/PromoCodeDialogFields.vue +33 -19
  60. package/src/components/dialogs/PromoCodeEditDialog.vue +10 -10
  61. package/src/components/plan/PlanGrid.vue +1 -1
  62. package/src/components/plan-create-dialog/PlanCreateDialog.vue +34 -46
  63. package/src/components/plan-detail/PlanAuditLog.vue +7 -8
  64. package/src/components/plan-detail/PlanDetail.vue +95 -292
  65. package/src/components/plan-detail/PlanDetailKpis.vue +22 -27
  66. package/src/components/plan-detail/PlanTitleEdit.vue +133 -0
  67. package/src/components/plan-detail/PlanVersionDiffPanel.vue +5 -8
  68. package/src/components/plan-detail/PlanVersionsPanel.vue +9 -11
  69. package/src/components/plan-list/PlanList.vue +206 -457
  70. package/src/components/plan-matrix/PlanMatrix.vue +66 -183
  71. package/src/components/plan-review/PlanReview.vue +191 -275
  72. package/src/components/plan-version-editor/PlanCatalogPreview.vue +7 -5
  73. package/src/components/plan-version-editor/PlanVersionEditor.vue +200 -295
  74. package/src/components/plan-version-editor/PlanVersionEditorHeader.vue +23 -55
  75. package/src/index.ts +1 -0
  76. package/src/pages-standard/AdminLayout.vue +38 -12
  77. package/src/pages-standard/AdminManifestErrorPage.vue +79 -9
  78. package/src/pages-standard/AuditPage.vue +65 -109
  79. package/src/pages-standard/BundlesPage.vue +146 -179
  80. package/src/pages-standard/DashboardPage.vue +59 -154
  81. package/src/pages-standard/DiscoveryPage.vue +172 -227
  82. package/src/pages-standard/EmailHistoryPage.vue +118 -159
  83. package/src/pages-standard/MarketingCatalogPage.vue +399 -440
  84. package/src/pages-standard/PilotsPage.vue +69 -117
  85. package/src/pages-standard/PlansPage.vue +240 -112
  86. package/src/pages-standard/PlatformEmailPage.vue +56 -91
  87. package/src/pages-standard/PromoCodeDetailPage.vue +49 -74
  88. package/src/pages-standard/PromoCodesPage.vue +95 -128
  89. package/src/pages-standard/SubscriptionsPage.vue +19 -52
  90. package/src/pages-standard/SuperAdminLoginPage.vue +12 -12
  91. package/src/pages-standard/SuperAdminSetupWizard.vue +22 -18
  92. package/src/pages-standard/TenantDetailPage.vue +100 -191
  93. package/src/pages-standard/TenantsPage.vue +190 -295
  94. package/src/pages-standard/UsersPage.vue +89 -157
  95. package/src/pages-standard/bundles-page/BundleAccordionList.vue +20 -17
  96. package/src/pages-standard/bundles-page/BundlesKpis.vue +13 -22
  97. package/src/pages-standard/bundles-page/BundlesToolbar.vue +63 -0
  98. package/src/pages-standard/discovery-page/CatalogEntryTransPanel.vue +10 -10
  99. package/src/pages-standard/discovery-page/DiscoveryCapList.vue +5 -5
  100. package/src/pages-standard/discovery-page/DiscoveryFeatureCard.vue +16 -16
  101. package/src/pages-standard/discovery-page/DiscoveryKpis.vue +31 -27
  102. package/src/pages-standard/discovery-page/DiscoveryQuotaCard.vue +7 -7
  103. package/src/pages-standard/marketing-catalog/MarketingCatalogAdmin.vue +111 -73
  104. package/src/pages-standard/marketing-catalog/MarketingCatalogHeader.vue +43 -62
  105. package/src/pages-standard/marketing-catalog/MarketingCatalogPreview.vue +53 -35
  106. package/src/pages-standard/marketing-catalog/MarketingCatalogToolbar.vue +7 -7
  107. package/src/pages-standard/plan-versions/PlanDiffCard.vue +6 -6
  108. package/src/pages-standard/plan-versions/PlanVersionsDiff.vue +6 -6
  109. package/src/pages-standard/plan-versions/PlanVersionsTimeline.vue +16 -16
  110. package/src/pages-standard/plans-page/PlanBundleOverview.vue +14 -29
  111. package/src/pages-standard/plans-page/PlanPublishDialog.vue +1 -1
  112. package/src/pages-standard/plans-page/PlansPageToast.vue +6 -6
  113. package/src/pages-standard/sa-theme.css +552 -86
  114. package/src/pages-standard/tenant-detail/TenantMasterData.vue +28 -0
  115. package/src/pages-standard/tenant-detail/TenantUsage.vue +31 -0
  116. package/src/pages-standard/tenant-detail/TenantUsers.vue +15 -0
  117. package/src/pages-standard/tenant-detail/types.ts +38 -0
  118. package/src/pages-standard/tenants/StatusPill.vue +2 -2
  119. package/src/pages-tenant/MySubscriptionBundlesPage.vue +25 -25
  120. package/src/pages-tenant/OnboardingConfigurator.vue +1 -5
  121. package/src/pages-tenant/PackageSnapshotPanel.vue +1 -1
  122. package/src/pages-tenant/TenantPlanSection.vue +12 -20
  123. package/src/quasar/create-super-admin-app.ts +4 -0
  124. package/src/vue/entitlement-provider.ts +0 -1
  125. package/src/vue/feature-router-guard.ts +0 -1
  126. package/src/vue/super-admin-context.ts +106 -1
  127. package/src/vue/use-api-list.ts +2 -1
  128. package/src/vue/use-mfa-prompt.ts +72 -0
  129. package/src/vue/use-pagination.ts +63 -0
  130. package/src/vue/use-plans.ts +1 -2
  131. package/src/vue/use-sign-out.ts +90 -0
  132. package/src/vue/use-tenant-billing.ts +1 -1
  133. package/src/vue/use-tenant-manifest.ts +0 -1
  134. package/src/components/KpiCard.vue +0 -53
  135. package/src/components/plan-cockpit/PlanCockpit.vue +0 -1012
  136. package/src/components/plan-cockpit/PlanCockpitAuditLog.vue +0 -72
  137. package/src/components/plan-cockpit/PlanCockpitDiffPanel.vue +0 -100
  138. package/src/components/plan-cockpit/PlanCockpitHeader.vue +0 -101
  139. package/src/components/plan-cockpit/PlanCockpitImpactPanel.vue +0 -112
  140. package/src/components/plan-cockpit/PlanCockpitKpis.vue +0 -68
  141. package/src/components/plan-cockpit/PlanCockpitVersions.vue +0 -251
  142. package/src/components/plan-cockpit/types.ts +0 -72
  143. package/src/components/plan-detail/PlanDetailHeader.vue +0 -194
  144. package/src/pages-standard/bundles-page/BundlesHeader.vue +0 -68
  145. package/src/pages-standard/discovery-page/DiscoveryHeader.vue +0 -34
@@ -1,123 +1,144 @@
1
1
  <template>
2
- <div class="sa-discovery">
3
- <DiscoveryHeader :loading="loading" @run-discovery="onRunDiscovery" />
4
-
5
- <DiscoveryMetaBanner
6
- :app-label="appLabel"
7
- :app-key="appKey"
8
- :app-version="appVersion"
9
- :scan-label="scanLabel"
10
- />
11
-
12
- <q-banner v-if="error" class="sa-discovery__error" rounded>
13
- <template #avatar><q-icon name="warning" color="negative" /></template>
14
- {{ common.error }}: {{ error.message }}
15
- </q-banner>
16
-
17
- <DiscoveryKpis
18
- :features-count="features.length"
19
- :capabilities-count="capabilities.length"
20
- :approved-count="approvedCount"
21
- :pending-count="pendingCount"
22
- :outdated-count="outdatedCount"
23
- :obsolete-count="obsoleteCount"
24
- :orphan-count="orphanCaps.length"
25
- />
26
-
27
- <q-tabs v-model="activeTab" align="left" dense class="sa-discovery__tabs">
28
- <q-tab name="features" :label="featuresTabLabel" />
29
- <q-tab name="quotas" :label="quotasTabLabel" />
30
- </q-tabs>
31
-
32
- <q-tab-panels v-model="activeTab" animated class="sa-discovery__panels">
33
- <q-tab-panel name="features" class="sa-discovery__panel">
34
- <div class="sa-discovery__toolbar">
35
- <q-input
36
- v-model="featureQuery"
37
- dense
38
- outlined
39
- clearable
40
- :placeholder="msg.searchPlaceholder"
41
- class="sa-discovery__search"
42
- >
43
- <template #prepend><q-icon name="search" /></template>
44
- </q-input>
45
- <q-select
46
- v-model="statusFilter"
47
- dense
48
- outlined
49
- emit-value
50
- map-options
51
- :options="statusFilterOptions"
52
- class="sa-discovery__filter"
53
- />
54
- </div>
55
-
56
- <div v-for="group in featureGroups" :key="group.label" class="sa-discovery__group">
57
- <div class="sa-discovery__group-head">
58
- <span class="sa-discovery__group-title">{{ group.label }}</span>
59
- <span class="sa-discovery__group-count">{{ group.features.length }}</span>
60
- </div>
61
- <div class="sa-discovery__cardlist">
62
- <DiscoveryFeatureCard
63
- v-for="f in group.features"
64
- :key="f.featureKey"
65
- :feature="f"
66
- :capabilities="capsByFeature.get(f.featureKey) ?? []"
67
- :owners="ownersByFeature.get(f.featureKey) ?? []"
68
- :declared-at-by-key="declaredAtByKey"
69
- :active-locales="activeLocales"
70
- :expanded="expandedFeature === f.featureKey"
71
- @toggle="toggleFeature(f.featureKey)"
72
- @review="onFeatureReview"
73
- @feature-base="onFeatureBase"
74
- @feature-locale="onFeatureLocale"
75
- />
76
- </div>
77
- </div>
78
- <div v-if="filteredFeatures.length === 0" class="sa-discovery__empty-row">
79
- {{ msg.noFeaturesMatchFilters }}
80
- </div>
81
-
82
- <div v-if="orphanCaps.length" class="sa-discovery__group">
83
- <div class="sa-discovery__group-head">
84
- <span class="sa-discovery__group-title sa-discovery__group-title--orphan">
85
- <q-icon name="warning" size="14px" />
86
- {{ msg.orphansTitle }}
87
- </span>
88
- <span class="sa-discovery__group-count">{{ orphanCaps.length }}</span>
89
- </div>
90
- <p class="sa-discovery__orphan-hint">
91
- {{ msg.orphanHint.before }} <code>feature:</code>{{ msg.orphanHint.middle }}
92
- <code>@ImplementsCapability</code>{{ msg.orphanHint.after }}
93
- </p>
94
- <DiscoveryCapList
95
- :capabilities="orphanCaps"
96
- :declared-at-by-key="declaredAtByKey"
97
- />
98
- </div>
99
- </q-tab-panel>
100
-
101
- <q-tab-panel name="quotas" class="sa-discovery__panel">
102
- <div class="sa-discovery__cardlist">
103
- <DiscoveryQuotaCard
104
- v-for="q in quotas"
105
- :key="q.quotaKey"
106
- :quota="q"
107
- :active-locales="activeLocales"
108
- :expanded="expandedQuota === q.quotaKey"
109
- @toggle="toggleQuota(q.quotaKey)"
110
- @review="onQuotaReview"
111
- @quota-base="onQuotaBase"
112
- @quota-locale="onQuotaLocale"
113
- />
114
- <div v-if="quotas.length === 0" class="sa-discovery__empty-row">
115
- {{ msg.noQuotasDeclared }}
116
- </div>
117
- </div>
118
- </q-tab-panel>
119
- </q-tab-panels>
120
- </div>
2
+ <AdminPage class="sa-discovery">
3
+ <AdminHero :title="msg.title">
4
+ <template #subtitle>
5
+ {{ msg.subtitleLead }} <b>{{ msg.subtitleEmphasis }}</b> {{ msg.subtitleTail }}
6
+ </template>
7
+ <template #actions>
8
+ <AdminRefreshBtn :loading="loading" @refresh="onRunDiscovery" />
9
+ </template>
10
+ </AdminHero>
11
+
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>
141
+ </AdminPage>
121
142
  </template>
122
143
 
123
144
  <script setup lang="ts">
@@ -133,9 +154,14 @@ import type {
133
154
  ReviewCatalogEntryData,
134
155
  UpdateCatalogEntryBaseData,
135
156
  } from '@saasicat/types';
157
+ import AdminRefreshBtn from '../components/admin-page/AdminRefreshBtn.vue';
136
158
  import DiscoveryCapList from './discovery-page/DiscoveryCapList.vue';
137
159
  import DiscoveryFeatureCard from './discovery-page/DiscoveryFeatureCard.vue';
138
- import DiscoveryHeader from './discovery-page/DiscoveryHeader.vue';
160
+ import AdminBody from '../components/admin-page/AdminBody.vue';
161
+ import AdminFilters from '../components/admin-page/AdminFilters.vue';
162
+ import AdminHero from '../components/admin-page/AdminHero.vue';
163
+ import AdminSection from '../components/admin-page/AdminSection.vue';
164
+ import AdminPage from '../components/admin-page/AdminPage.vue';
139
165
  import DiscoveryKpis from './discovery-page/DiscoveryKpis.vue';
140
166
  import DiscoveryMetaBanner from './discovery-page/DiscoveryMetaBanner.vue';
141
167
  import DiscoveryQuotaCard from './discovery-page/DiscoveryQuotaCard.vue';
@@ -182,14 +208,15 @@ const featuresTabLabel = computed(() =>
182
208
  const quotasTabLabel = computed(() =>
183
209
  formatMessage(msg.value.tabQuotas, { count: props.quotas.length }),
184
210
  );
185
- const featureQuery = ref('');
211
+ // `clearable` emits null, not '' — see Quasar's use-field clearValue().
212
+ const featureQuery = ref<string | null>('');
186
213
  const statusFilter = ref<DiscoveryStatus | 'all'>('all');
187
214
  const expandedFeature = ref<string | null>(null);
188
215
  const expandedQuota = ref<string | null>(null);
189
216
 
190
217
  const msg = useSaMessages('discovery');
191
218
  const common = useSaMessages('common');
192
- const { locale, intlLocale } = useSuperAdminI18n();
219
+ const { intlLocale } = useSuperAdminI18n();
193
220
 
194
221
  const statusFilterOptions = computed<Array<{ label: string; value: DiscoveryStatus | 'all' }>>(
195
222
  () => [
@@ -273,7 +300,7 @@ const obsoleteCount = computed(
273
300
  );
274
301
 
275
302
  const filteredFeatures = computed(() => {
276
- const q = featureQuery.value.trim().toLowerCase();
303
+ const q = (featureQuery.value ?? '').trim().toLowerCase();
277
304
  return props.features.filter((f) => {
278
305
  if (statusFilter.value !== 'all' && f.discoveryStatus !== statusFilter.value) {
279
306
  return false;
@@ -438,29 +465,12 @@ onMounted(() => {
438
465
  flex-direction: column;
439
466
  gap: 14px;
440
467
  }
441
- .sa-discovery__head {
442
- display: flex;
443
- justify-content: space-between;
444
- align-items: flex-end;
445
- gap: 16px;
446
- }
447
- .sa-discovery__title {
448
- font-size: 20px;
449
- font-weight: 700;
450
- margin: 0;
451
- }
452
- .sa-discovery__sub {
453
- margin: 4px 0 0;
454
- color: #64748b;
455
- font-size: 12px;
456
- max-width: 640px;
457
- }
458
468
  .sa-discovery__banner {
459
469
  display: flex;
460
470
  justify-content: space-between;
461
471
  align-items: center;
462
- background: #0f172a;
463
- color: #e2e8f0;
472
+ background: var(--sa-heading);
473
+ color: var(--sa-border);
464
474
  border-radius: 12px;
465
475
  padding: 14px 18px;
466
476
  }
@@ -475,7 +485,7 @@ onMounted(() => {
475
485
  }
476
486
  .sa-discovery__banner-meta {
477
487
  font-size: 11px;
478
- color: #94a3b8;
488
+ color: var(--sa-muted-light);
479
489
  }
480
490
  .sa-discovery__banner-time {
481
491
  text-align: right;
@@ -484,7 +494,7 @@ onMounted(() => {
484
494
  font-size: 10px;
485
495
  text-transform: uppercase;
486
496
  letter-spacing: 0.5px;
487
- color: #94a3b8;
497
+ color: var(--sa-muted-light);
488
498
  }
489
499
  .sa-discovery__banner-time-val {
490
500
  font-size: 13px;
@@ -493,57 +503,8 @@ onMounted(() => {
493
503
  .sa-discovery__error {
494
504
  border-left: 4px solid #dc2626;
495
505
  }
496
- .sa-discovery__kpis {
497
- display: grid;
498
- grid-template-columns: repeat(5, 1fr);
499
- gap: 10px;
500
- }
501
- .sa-discovery__kpi {
502
- background: #fff;
503
- border: 1px solid #e2e8f0;
504
- padding: 12px 14px;
505
- border-radius: 10px;
506
- }
507
- .sa-discovery__kpi.good {
508
- border-color: #a7f3d0;
509
- }
510
- .sa-discovery__kpi.good .sa-discovery__kpi-label,
511
- .sa-discovery__kpi.good .sa-discovery__kpi-value {
512
- color: #047857;
513
- }
514
- .sa-discovery__kpi.warn {
515
- border-color: #fde68a;
516
- background: #fffbeb;
517
- }
518
- .sa-discovery__kpi.bad {
519
- border-color: #fecaca;
520
- background: #fef2f2;
521
- }
522
- .sa-discovery__kpi-label {
523
- font-size: 10px;
524
- text-transform: uppercase;
525
- letter-spacing: 0.5px;
526
- color: #64748b;
527
- }
528
- .sa-discovery__kpi-value {
529
- font-size: 26px;
530
- font-weight: 700;
531
- }
532
- .sa-discovery__kpi-sub {
533
- font-size: 11px;
534
- color: #94a3b8;
535
- }
536
506
  .sa-discovery__tabs {
537
- border-bottom: 1px solid #e2e8f0;
538
- }
539
- .sa-discovery__panel {
540
- padding: 14px 0;
541
- }
542
- .sa-discovery__toolbar {
543
- display: flex;
544
- gap: 12px;
545
- margin-bottom: 14px;
546
- align-items: center;
507
+ border-bottom: 1px solid var(--sa-border);
547
508
  }
548
509
  .sa-discovery__search {
549
510
  flex: 1;
@@ -559,30 +520,14 @@ onMounted(() => {
559
520
  .sa-discovery__group {
560
521
  margin-bottom: 16px;
561
522
  }
562
- .sa-discovery__group-head {
563
- display: flex;
564
- align-items: center;
565
- gap: 8px;
566
- margin-bottom: 8px;
567
- }
568
- .sa-discovery__group-title {
569
- font-size: 11px;
570
- font-weight: 700;
571
- text-transform: uppercase;
572
- letter-spacing: 0.5px;
573
- color: #475569;
574
- display: flex;
575
- align-items: center;
576
- gap: 5px;
577
- }
578
- .sa-discovery__group-title--orphan {
523
+ .sa-discovery__group--orphan .sa-section__title {
579
524
  color: #b45309;
580
525
  }
581
526
  .sa-discovery__group-count {
582
527
  font-size: 10px;
583
528
  font-weight: 700;
584
- background: #e2e8f0;
585
- color: #475569;
529
+ background: var(--sa-border);
530
+ color: var(--sa-muted-dark);
586
531
  padding: 1px 7px;
587
532
  border-radius: 8px;
588
533
  }
@@ -593,20 +538,20 @@ onMounted(() => {
593
538
  }
594
539
  .sa-discovery__orphan-hint code {
595
540
  font-size: 10px;
596
- background: #fffbeb;
541
+ background: var(--sa-warning-bg);
597
542
  padding: 1px 4px;
598
543
  border-radius: 4px;
599
544
  }
600
545
  .sa-discovery__empty-row {
601
546
  padding: 36px;
602
547
  text-align: center;
603
- color: #94a3b8;
548
+ color: var(--sa-muted-light);
604
549
  font-size: 13px;
605
550
  border: 1px dashed #cbd5e1;
606
551
  border-radius: 12px;
607
552
  }
608
553
  .sa-muted {
609
- color: #94a3b8;
554
+ color: var(--sa-muted-light);
610
555
  margin-right: 3px;
611
556
  }
612
557
  /* Status badge (review lifecycle) — shared by feature and quota card. */
@@ -616,8 +561,8 @@ onMounted(() => {
616
561
  text-transform: uppercase;
617
562
  padding: 3px 8px;
618
563
  border-radius: 6px;
619
- background: #f1f5f9;
620
- color: #475569;
564
+ background: var(--sa-border-soft);
565
+ color: var(--sa-muted-dark);
621
566
  flex-shrink: 0;
622
567
  }
623
568
  .sa-review--pending {
@@ -633,16 +578,16 @@ onMounted(() => {
633
578
  color: #b91c1c;
634
579
  }
635
580
  .sa-review--obsolete {
636
- background: #e2e8f0;
637
- color: #64748b;
581
+ background: var(--sa-border);
582
+ color: var(--sa-muted);
638
583
  }
639
584
  .sa-chip {
640
585
  font-size: 10px;
641
586
  font-weight: 600;
642
587
  padding: 2px 7px;
643
588
  border-radius: 6px;
644
- background: #f1f5f9;
645
- color: #475569;
589
+ background: var(--sa-border-soft);
590
+ color: var(--sa-muted-dark);
646
591
  }
647
592
  /* i18n coverage pill — shared by feature and quota card. */
648
593
  .sa-cov-pill {
@@ -653,18 +598,18 @@ onMounted(() => {
653
598
  font-weight: 700;
654
599
  padding: 2px 7px;
655
600
  border-radius: 8px;
656
- border: 1px solid #e2e8f0;
657
- background: #f8fafc;
658
- color: #475569;
601
+ border: 1px solid var(--sa-border);
602
+ background: var(--sa-bg-surface-2);
603
+ color: var(--sa-muted-dark);
659
604
  }
660
605
  .sa-cov-pill.complete {
661
606
  border-color: #a7f3d0;
662
- background: #ecfdf5;
607
+ background: var(--sa-positive-bg);
663
608
  color: #047857;
664
609
  }
665
610
  .sa-cov-pill.warn {
666
611
  border-color: #fde68a;
667
- background: #fffbeb;
612
+ background: var(--sa-warning-bg);
668
613
  color: #b45309;
669
614
  }
670
615
  .sa-cov-pill.low {