refactor(ui): single global .fc-muted token (DRY pattern sweep)
CI / lint (push) Successful in 3s
CI / frontend-build (push) Successful in 23s
CI / backend-lint-and-test (push) Successful in 28s
CI / integration (push) Successful in 3m21s

The muted-text token was redefined identically in 12 component <style scoped>
blocks. Consolidate to one global utility in styles/app.css; remove the 12
copies. Keeps the explicit on-surface-variant (vellum) token, NOT Vuetify's
opacity-based text-medium-emphasis (per the muted-text-token rule). Behavior-
preserving: every class=fc-muted usage now resolves to the single source.

§8b exhaustiveness caught (and I fixed) my own sed clobbering the new app.css
rule — now exactly one .fc-muted definition exists, zero component-local.
Catalog updated.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-06-09 22:42:26 -04:00
parent c774042a85
commit 4e83b4225a
13 changed files with 8 additions and 14 deletions
@@ -91,5 +91,4 @@ async function onConfirm(token) {
border-color: rgb(var(--v-theme-error)); border-color: rgb(var(--v-theme-error));
border-radius: 8px; border-radius: 8px;
} }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
</style> </style>
@@ -121,5 +121,4 @@ async function onConfirmedDelete(token) {
<style scoped> <style scoped>
.fc-clean-card { border-radius: 8px; } .fc-clean-card { border-radius: 8px; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
</style> </style>
@@ -190,5 +190,4 @@ async function onConfirmedApply(token) {
<style scoped> <style scoped>
.fc-clean-card { border-radius: 8px; } .fc-clean-card { border-radius: 8px; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
</style> </style>
@@ -173,5 +173,4 @@ async function onConfirmedApply(token) {
<style scoped> <style scoped>
.fc-clean-card { border-radius: 8px; } .fc-clean-card { border-radius: 8px; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
</style> </style>
@@ -192,7 +192,6 @@ async function onConfirmSubmit(token) {
<style scoped> <style scoped>
.fc-backup-card { border-radius: 8px; } .fc-backup-card { border-radius: 8px; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
.fc-section-title { .fc-section-title {
font-family: 'Fraunces', Georgia, serif; font-family: 'Fraunces', Georgia, serif;
font-size: 16px; font-weight: 500; font-size: 16px; font-weight: 500;
@@ -97,5 +97,4 @@ function formatRelative(iso) {
<style scoped> <style scoped>
.fc-backup-table { background: transparent; } .fc-backup-table { background: transparent; }
.fc-tabular { font-variant-numeric: tabular-nums; } .fc-tabular { font-variant-numeric: tabular-nums; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
</style> </style>
@@ -187,7 +187,4 @@ async function copy(text, label) {
.fc-ext-install { .fc-ext-install {
display: flex; flex-wrap: wrap; gap: 8px; display: flex; flex-wrap: wrap; gap: 8px;
} }
.fc-muted {
color: rgb(var(--v-theme-on-surface-variant));
}
</style> </style>
@@ -94,7 +94,6 @@ async function onCommit() {
<style scoped> <style scoped>
.fc-post-maint { border-radius: 8px; } .fc-post-maint { border-radius: 8px; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
.fc-name-grid { .fc-name-grid {
display: flex; flex-wrap: wrap; gap: 4px 8px; display: flex; flex-wrap: wrap; gap: 4px 8px;
max-height: 200px; overflow-y: auto; max-height: 200px; overflow-y: auto;
@@ -101,7 +101,6 @@ function recentErr(name) { return recentByQueue.value[name]?.err || 0 }
.fc-ok { color: rgb(var(--v-theme-success, 76 175 80)); } .fc-ok { color: rgb(var(--v-theme-success, 76 175 80)); }
.fc-err { color: rgb(var(--v-theme-error, 220 80 80)); } .fc-err { color: rgb(var(--v-theme-error, 220 80 80)); }
.fc-warn { color: rgb(var(--v-theme-warning, 255 179 0)); font-weight: 500; } .fc-warn { color: rgb(var(--v-theme-warning, 255 179 0)); font-weight: 500; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
.fc-sep { .fc-sep {
margin: 0 4px; margin: 0 4px;
color: rgb(var(--v-theme-on-surface-variant)); color: rgb(var(--v-theme-on-surface-variant));
@@ -305,5 +305,4 @@ function formatRelative(iso) {
cursor: pointer; cursor: pointer;
} }
.fc-err-link:hover { text-decoration: underline; } .fc-err-link:hover { text-decoration: underline; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
</style> </style>
@@ -304,7 +304,6 @@ async function onNormCommit() {
<style scoped> <style scoped>
.fc-tag-maint { border-radius: 8px; } .fc-tag-maint { border-radius: 8px; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
.fc-name-grid { .fc-name-grid {
display: flex; flex-wrap: wrap; gap: 4px 8px; display: flex; flex-wrap: wrap; gap: 4px 8px;
max-height: 200px; overflow-y: auto; max-height: 200px; overflow-y: auto;
+8
View File
@@ -31,3 +31,11 @@
border: 1px solid rgb(var(--v-theme-on-surface-variant) / 0.25); border: 1px solid rgb(var(--v-theme-on-surface-variant) / 0.25);
box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4); box-shadow: 0 2px 8px rgba(0, 0, 0, 0.4);
} }
/* Canonical muted/secondary text token (DRY pattern sweep 2026-06-09).
Was redefined identically in 12 component <style scoped> blocks; now one
global utility. Muted text uses the explicit on-surface-variant (vellum)
token, NOT opacity — Vuetify's text-medium-emphasis is opacity-based and is
deliberately not used here. `.fc-muted` is a custom class Vuetify never
emits, so no specificity/reorder fight — no !important needed. */
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
-1
View File
@@ -29,5 +29,4 @@ import TagMaintenanceCard from '../components/settings/TagMaintenanceCard.vue'
<style scoped> <style scoped>
.fc-cleanup { max-width: 900px; } .fc-cleanup { max-width: 900px; }
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
</style> </style>