refactor(ui): DRY the settings-card CSS tokens + fix unstyled headers (#161)
- Promote .fc-section-h to a global token (app.css). It was copied identically into 4 cards, and TranslationCard used the class with NO local def — so its section headers rendered unstyled. Now fixed everywhere. - Promote .fc-good / .fc-weak status colours to globals; delete the local copies in the GPU/heads cards. (.fc-ok stays local — divergent: on-surface in HeadsCard vs success in QueuesTable. .fc-bad stays — different name.) - Delete 10 identical local .fc-muted redefinitions that crept back after the 2026-06-09 sweep; the global utility already covers them. - DbMaintenanceCard: opacity:0.6 muted text → the .fc-muted token (the exact anti-pattern that token's comment forbids). - HeadsCard: collapse byte-identical ratePct() into pct(). CSS-only + one template class swap; no logic change. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NsmJSQxnNxGgtM5Yz4GAqi
This commit is contained in:
@@ -131,11 +131,6 @@ function saveToggle (p, v) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
.fc-section-h {
|
|
||||||
font-size: 13px; font-weight: 700; letter-spacing: 0.03em;
|
|
||||||
text-transform: uppercase; color: rgb(var(--v-theme-on-surface));
|
|
||||||
}
|
|
||||||
.fc-proposer {
|
.fc-proposer {
|
||||||
border-top: 1px solid rgb(var(--v-theme-surface-light)); padding-top: 14px;
|
border-top: 1px solid rgb(var(--v-theme-surface-light)); padding-top: 14px;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -112,7 +112,6 @@ async function onCommit() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
.fc-code {
|
.fc-code {
|
||||||
background: rgb(var(--v-theme-surface-light));
|
background: rgb(var(--v-theme-surface-light));
|
||||||
border-radius: 4px; padding: 2px 8px;
|
border-radius: 4px; padding: 2px 8px;
|
||||||
|
|||||||
@@ -42,7 +42,7 @@
|
|||||||
</tr>
|
</tr>
|
||||||
</tbody>
|
</tbody>
|
||||||
</v-table>
|
</v-table>
|
||||||
<p v-else class="text-caption mt-3" style="opacity: 0.6;">
|
<p v-else class="text-caption mt-3 fc-muted">
|
||||||
No table statistics yet.
|
No table statistics yet.
|
||||||
</p>
|
</p>
|
||||||
</MaintenanceTile>
|
</MaintenanceTile>
|
||||||
|
|||||||
@@ -72,6 +72,5 @@ onUnmounted(() => { if (pollId) clearInterval(pollId) })
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
.fc-bad { color: rgb(var(--v-theme-error)); }
|
.fc-bad { color: rgb(var(--v-theme-error)); }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -95,7 +95,6 @@ onUnmounted(() => { if (pollId) clearInterval(pollId) })
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
.fc-cells { display: flex; gap: 28px; }
|
.fc-cells { display: flex; gap: 28px; }
|
||||||
.fc-cell__n {
|
.fc-cell__n {
|
||||||
font-size: 20px; font-weight: 700; line-height: 1.1;
|
font-size: 20px; font-weight: 700; line-height: 1.1;
|
||||||
@@ -105,6 +104,5 @@ onUnmounted(() => { if (pollId) clearInterval(pollId) })
|
|||||||
font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
|
font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
|
||||||
color: rgb(var(--v-theme-on-surface-variant));
|
color: rgb(var(--v-theme-on-surface-variant));
|
||||||
}
|
}
|
||||||
.fc-good { color: rgb(var(--v-theme-success)); }
|
|
||||||
.fc-bad { color: rgb(var(--v-theme-error)); }
|
.fc-bad { color: rgb(var(--v-theme-error)); }
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -367,11 +367,6 @@ async function onReprocess() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
.fc-section-h {
|
|
||||||
font-size: 13px; font-weight: 700; letter-spacing: 0.03em;
|
|
||||||
text-transform: uppercase; color: rgb(var(--v-theme-on-surface));
|
|
||||||
}
|
|
||||||
.fc-token {
|
.fc-token {
|
||||||
display: flex; align-items: center; gap: 4px;
|
display: flex; align-items: center; gap: 4px;
|
||||||
background: rgb(var(--v-theme-surface-light)); border-radius: 6px;
|
background: rgb(var(--v-theme-surface-light)); border-radius: 6px;
|
||||||
@@ -390,6 +385,4 @@ async function onReprocess() {
|
|||||||
font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
|
font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
|
||||||
color: rgb(var(--v-theme-on-surface-variant));
|
color: rgb(var(--v-theme-on-surface-variant));
|
||||||
}
|
}
|
||||||
.fc-good { color: rgb(var(--v-theme-success)); }
|
|
||||||
.fc-weak { color: rgb(var(--v-theme-error)); }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -155,11 +155,6 @@ async function onRecover(it) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
.fc-section-h {
|
|
||||||
font-size: 13px; font-weight: 700; letter-spacing: 0.03em;
|
|
||||||
text-transform: uppercase; color: rgb(var(--v-theme-on-surface));
|
|
||||||
}
|
|
||||||
.fc-queue { display: flex; gap: 24px; }
|
.fc-queue { display: flex; gap: 24px; }
|
||||||
.fc-q__n {
|
.fc-q__n {
|
||||||
font-size: 20px; font-weight: 700; line-height: 1.1;
|
font-size: 20px; font-weight: 700; line-height: 1.1;
|
||||||
@@ -169,8 +164,6 @@ async function onRecover(it) {
|
|||||||
font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
|
font-size: 11px; text-transform: uppercase; letter-spacing: 0.04em;
|
||||||
color: rgb(var(--v-theme-on-surface-variant));
|
color: rgb(var(--v-theme-on-surface-variant));
|
||||||
}
|
}
|
||||||
.fc-good { color: rgb(var(--v-theme-success)); }
|
|
||||||
.fc-weak { color: rgb(var(--v-theme-error)); }
|
|
||||||
.fc-defect {
|
.fc-defect {
|
||||||
display: flex; align-items: center; gap: 12px;
|
display: flex; align-items: center; gap: 12px;
|
||||||
background: rgb(var(--v-theme-surface-light)); border-radius: 8px;
|
background: rgb(var(--v-theme-surface-light)); border-radius: 8px;
|
||||||
|
|||||||
@@ -256,7 +256,7 @@
|
|||||||
<td class="fc-r fc-mono">{{ c.n_auto_applied }}</td>
|
<td class="fc-r fc-mono">{{ c.n_auto_applied }}</td>
|
||||||
<td class="fc-r fc-mono">{{ c.n_misfires }}</td>
|
<td class="fc-r fc-mono">{{ c.n_misfires }}</td>
|
||||||
<td class="fc-r fc-mono" :class="rateClass(c.misfire_rate)">
|
<td class="fc-r fc-mono" :class="rateClass(c.misfire_rate)">
|
||||||
{{ ratePct(c.misfire_rate) }}
|
{{ pct(c.misfire_rate) }}
|
||||||
</td>
|
</td>
|
||||||
<td class="fc-r fc-mono">{{ c.n_underfires }}</td>
|
<td class="fc-r fc-mono">{{ c.n_underfires }}</td>
|
||||||
</tr>
|
</tr>
|
||||||
@@ -495,7 +495,6 @@ function sweepConcepts(run) {
|
|||||||
.sort((a, b) => b.applied - a.applied)
|
.sort((a, b) => b.applied - a.applied)
|
||||||
}
|
}
|
||||||
function sweepTotal(run) { return run?.n_applied ?? 0 }
|
function sweepTotal(run) { return run?.n_applied ?? 0 }
|
||||||
function ratePct(x) { return x == null ? '—' : `${Math.round(x * 100)}%` }
|
|
||||||
function rateClass(x) {
|
function rateClass(x) {
|
||||||
if (x == null) return ''
|
if (x == null) return ''
|
||||||
if (x <= 0.03) return 'fc-good'
|
if (x <= 0.03) return 'fc-good'
|
||||||
@@ -526,12 +525,6 @@ function relTime(iso) {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
|
|
||||||
.fc-section-h {
|
|
||||||
font-size: 13px; font-weight: 700; letter-spacing: 0.03em;
|
|
||||||
text-transform: uppercase; color: rgb(var(--v-theme-on-surface));
|
|
||||||
}
|
|
||||||
.fc-auto {
|
.fc-auto {
|
||||||
border-top: 1px solid rgb(var(--v-theme-surface-light)); padding-top: 16px;
|
border-top: 1px solid rgb(var(--v-theme-surface-light)); padding-top: 16px;
|
||||||
}
|
}
|
||||||
@@ -588,7 +581,5 @@ function relTime(iso) {
|
|||||||
background: rgb(var(--v-theme-surface-light));
|
background: rgb(var(--v-theme-surface-light));
|
||||||
padding: 1px 6px; border-radius: 999px;
|
padding: 1px 6px; border-radius: 999px;
|
||||||
}
|
}
|
||||||
.fc-good { color: rgb(var(--v-theme-success)); }
|
|
||||||
.fc-ok { color: rgb(var(--v-theme-on-surface)); }
|
.fc-ok { color: rgb(var(--v-theme-on-surface)); }
|
||||||
.fc-weak { color: rgb(var(--v-theme-error)); }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -80,5 +80,4 @@ async function run() {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
</style>
|
</style>
|
||||||
|
|||||||
@@ -40,6 +40,20 @@
|
|||||||
emits, so no specificity/reorder fight — no !important needed. */
|
emits, so no specificity/reorder fight — no !important needed. */
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
||||||
|
|
||||||
|
/* Section sub-heading in settings cards (DRY pass #161): was redefined
|
||||||
|
identically in 4 cards, and TranslationCard used the class with NO local def
|
||||||
|
so its section headers rendered unstyled. Now one global utility. */
|
||||||
|
.fc-section-h {
|
||||||
|
font-size: 13px; font-weight: 700; letter-spacing: 0.03em;
|
||||||
|
text-transform: uppercase; color: rgb(var(--v-theme-on-surface));
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Status text colours (DRY pass #161): fc-good = success, fc-weak = error,
|
||||||
|
consolidated from the GPU / heads cards. fc-ok is intentionally NOT global —
|
||||||
|
it means on-surface in HeadsCard but success in QueuesTable. */
|
||||||
|
.fc-good { color: rgb(var(--v-theme-success)); }
|
||||||
|
.fc-weak { color: rgb(var(--v-theme-error)); }
|
||||||
|
|
||||||
/* Vuetify 4 dropped its global CSS reset (normalisation moved into each
|
/* Vuetify 4 dropped its global CSS reset (normalisation moved into each
|
||||||
component). FC's layouts assumed the reset zeroed margins on text elements, so
|
component). FC's layouts assumed the reset zeroed margins on text elements, so
|
||||||
restore just that — the "minimal reset" from the v4 upgrade guide — inside
|
restore just that — the "minimal reset" from the v4 upgrade guide — inside
|
||||||
|
|||||||
@@ -284,7 +284,6 @@ onUnmounted(() => {
|
|||||||
</script>
|
</script>
|
||||||
|
|
||||||
<style scoped>
|
<style scoped>
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
|
|
||||||
/* Full-height workspace under the sticky top nav. --fc-nav-h is the nav's REAL
|
/* Full-height workspace under the sticky top nav. --fc-nav-h is the nav's REAL
|
||||||
measured height (set by TopNav) — a hardcoded 64px here overflowed the
|
measured height (set by TopNav) — a hardcoded 64px here overflowed the
|
||||||
|
|||||||
@@ -349,7 +349,6 @@ async function onDeleteTagConfirm() {
|
|||||||
.fc-tags__sentinel {
|
.fc-tags__sentinel {
|
||||||
display: flex; justify-content: center; padding: 32px 0; min-height: 60px;
|
display: flex; justify-content: center; padding: 32px 0; min-height: 60px;
|
||||||
}
|
}
|
||||||
.fc-muted { color: rgb(var(--v-theme-on-surface-variant)); }
|
|
||||||
.fc-merge-preview {
|
.fc-merge-preview {
|
||||||
padding: 10px 12px;
|
padding: 10px 12px;
|
||||||
border: 1px solid rgba(var(--v-theme-on-surface), 0.12);
|
border: 1px solid rgba(var(--v-theme-on-surface), 0.12);
|
||||||
|
|||||||
Reference in New Issue
Block a user