Two milestones: a note can carry its own check (317), and a rule keeps what it used to say (323) #135

Merged
bvandeusen merged 23 commits from dev into main 2026-08-31 00:01:15 -04:00
20 changed files with 74 additions and 61 deletions
Showing only changes of commit d0a2733cb6 - Show all commits
+3 -3
View File
@@ -127,7 +127,7 @@
border: 1px solid var(--fs-error); border: 1px solid var(--fs-error);
border-radius: var(--fs-radius-sm); border-radius: var(--fs-radius-sm);
font-size: 0.85rem; font-size: 0.85rem;
color: var(--fs-error); color: var(--fs-error-fg);
} }
.diff-view { .diff-view {
border: 1px solid var(--fs-border-color); border: 1px solid var(--fs-border-color);
@@ -147,11 +147,11 @@
} }
.diff-delete { .diff-delete {
background: color-mix(in srgb, var(--fs-error) 12%, transparent); background: color-mix(in srgb, var(--fs-error) 12%, transparent);
color: var(--fs-error); color: var(--fs-error-fg);
} }
.diff-insert { .diff-insert {
background: color-mix(in srgb, var(--fs-success) 12%, transparent); background: color-mix(in srgb, var(--fs-success) 12%, transparent);
color: var(--fs-success); color: var(--fs-success-fg);
} }
.diff-equal { .diff-equal {
color: var(--fs-text-tertiary); color: var(--fs-text-tertiary);
+6
View File
@@ -31,6 +31,7 @@
--fs-accent-faint: color-mix(in srgb, var(--fs-accent) 8%, transparent); /* The faintest accent wash */ --fs-accent-faint: color-mix(in srgb, var(--fs-accent) 8%, transparent); /* The faintest accent wash */
--fs-accent-deep: color-mix(in srgb, var(--fs-accent) 70%, black); /* The accent, darkened */ --fs-accent-deep: color-mix(in srgb, var(--fs-accent) 70%, black); /* The accent, darkened */
--fs-accent-wash: color-mix(in srgb, var(--fs-accent) 22%, transparent); /* Heaviest accent tint */ --fs-accent-wash: color-mix(in srgb, var(--fs-accent) 22%, transparent); /* Heaviest accent tint */
--fs-accent-fg: color-mix(in srgb, var(--fs-accent) 45%, var(--fs-text-primary)); /* Accent TEXT on an accent tint */
--fs-gradient-cta: linear-gradient(135deg, var(--fs-accent), var(--fs-accent-deep)); --fs-gradient-cta: linear-gradient(135deg, var(--fs-accent), var(--fs-accent-deep));
--fs-glow-cta: 0 2px 10px color-mix(in srgb, var(--fs-accent) 35%, transparent); --fs-glow-cta: 0 2px 10px color-mix(in srgb, var(--fs-accent) 35%, transparent);
--fs-glow-cta-hover: 0 4px 24px color-mix(in srgb, var(--fs-accent) 65%, transparent); --fs-glow-cta-hover: 0 4px 24px color-mix(in srgb, var(--fs-accent) 65%, transparent);
@@ -102,8 +103,11 @@
/* semantic */ /* semantic */
--fs-success: var(--fs-action-primary); --fs-success: var(--fs-action-primary);
--fs-success-fg: color-mix(in srgb, var(--fs-success) 45%, var(--fs-text-primary)); /* Success TEXT on a success tint */
--fs-warning: #8B6F1E; --fs-warning: #8B6F1E;
--fs-warning-fg: color-mix(in srgb, var(--fs-warning) 50%, var(--fs-text-primary)); /* Warning TEXT on a warning tint */
--fs-error: #C04A1F; --fs-error: #C04A1F;
--fs-error-fg: color-mix(in srgb, var(--fs-error) 50%, var(--fs-text-primary)); /* Error TEXT on an error tint */
--fs-info: #3D5A6E; --fs-info: #3D5A6E;
--fs-destructive: #6B2118; /* irreversible — deliberately not the error colour */ --fs-destructive: #6B2118; /* irreversible — deliberately not the error colour */
@@ -148,7 +152,9 @@
/* text */ /* text */
--fs-text-primary: #E8E4D8; /* body, headings, labels — inverts by mode */ --fs-text-primary: #E8E4D8; /* body, headings, labels — inverts by mode */
--fs-text-secondary: #C2BFB4; --fs-text-secondary: #C2BFB4;
--fs-text-secondary-fg: color-mix(in srgb, var(--fs-text-secondary) 90%, var(--fs-text-primary)); /* Secondary TEXT on a secondary tint (barely moves; no exceptions) */
--fs-text-tertiary: #9C9A92; --fs-text-tertiary: #9C9A92;
--fs-text-tertiary-fg: color-mix(in srgb, var(--fs-text-tertiary) 55%, var(--fs-text-primary)); /* Tertiary TEXT on a tertiary tint */
--fs-text-on-action: #E8E4D8; /* text on a filled colour — NOT mode-dependent */ --fs-text-on-action: #E8E4D8; /* text on a filled colour — NOT mode-dependent */
/* type */ /* type */
+1 -1
View File
@@ -25,7 +25,7 @@
border-color: var(--fs-accent); border-color: var(--fs-accent);
} }
.ctx-crumb-project { .ctx-crumb-project {
color: var(--fs-accent); color: var(--fs-accent-fg);
background: color-mix(in srgb, var(--fs-accent) 10%, transparent); background: color-mix(in srgb, var(--fs-accent) 10%, transparent);
border: 1px solid color-mix(in srgb, var(--fs-accent) 30%, transparent); border: 1px solid color-mix(in srgb, var(--fs-accent) 30%, transparent);
text-decoration: none; text-decoration: none;
+2 -2
View File
@@ -206,7 +206,7 @@ router.afterEach(() => {
background: var(--fs-accent-soft); background: var(--fs-accent-soft);
} }
.nav-link.router-link-active { .nav-link.router-link-active {
color: var(--fs-accent); color: var(--fs-accent-fg);
font-weight: 500; font-weight: 500;
background: color-mix(in srgb, var(--fs-accent) 25%, transparent); background: color-mix(in srgb, var(--fs-accent) 25%, transparent);
box-shadow: 0 0 16px color-mix(in srgb, var(--fs-accent) 30%, transparent); box-shadow: 0 0 16px color-mix(in srgb, var(--fs-accent) 30%, transparent);
@@ -257,7 +257,7 @@ router.afterEach(() => {
font-weight: 500; font-weight: 500;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.05em; letter-spacing: 0.05em;
color: var(--fs-accent); color: var(--fs-accent-fg);
background: color-mix(in srgb, var(--fs-accent) 15%, transparent); background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
padding: 0.1rem 0.35rem; padding: 0.1rem 0.35rem;
border-radius: var(--fs-radius-sm); border-radius: var(--fs-radius-sm);
+2 -2
View File
@@ -137,12 +137,12 @@ function markerFor(type: DiffLine['type']): string {
.diff-delete { .diff-delete {
background: color-mix(in srgb, var(--fs-error) 12%, transparent); background: color-mix(in srgb, var(--fs-error) 12%, transparent);
color: var(--fs-error); color: var(--fs-error-fg);
} }
.diff-insert { .diff-insert {
background: color-mix(in srgb, var(--fs-success) 12%, transparent); background: color-mix(in srgb, var(--fs-success) 12%, transparent);
color: var(--fs-success); color: var(--fs-success-fg);
} }
.diff-equal { .diff-equal {
@@ -227,11 +227,11 @@ const markers: Record<DiffLine["type"], string> = {
.iap-diff-equal { color: var(--fs-text-tertiary); } .iap-diff-equal { color: var(--fs-text-tertiary); }
.iap-diff-delete { .iap-diff-delete {
background: color-mix(in srgb, var(--fs-error) 10%, transparent); background: color-mix(in srgb, var(--fs-error) 10%, transparent);
color: var(--fs-error); color: var(--fs-error-fg);
} }
.iap-diff-insert { .iap-diff-insert {
background: color-mix(in srgb, var(--fs-success) 10%, transparent); background: color-mix(in srgb, var(--fs-success) 10%, transparent);
color: var(--fs-success); color: var(--fs-success-fg);
} }
.iap-diff-marker { .iap-diff-marker {
+1 -1
View File
@@ -156,7 +156,7 @@ const groups = [
.md-btn.active { .md-btn.active {
background: color-mix(in srgb, var(--fs-accent) 14%, transparent); background: color-mix(in srgb, var(--fs-accent) 14%, transparent);
color: var(--fs-accent); color: var(--fs-accent-fg);
box-shadow: 0 0 0 1px color-mix(in srgb, var(--fs-accent) 35%, transparent); box-shadow: 0 0 0 1px color-mix(in srgb, var(--fs-accent) 35%, transparent);
} }
+2 -2
View File
@@ -679,7 +679,7 @@ async function confirmDelete() {
font-weight: 500; font-weight: 500;
background: color-mix(in srgb, var(--fs-accent) 12%, transparent); background: color-mix(in srgb, var(--fs-accent) 12%, transparent);
border: 1px solid color-mix(in srgb, var(--fs-accent) 30%, transparent); border: 1px solid color-mix(in srgb, var(--fs-accent) 30%, transparent);
color: var(--fs-accent); color: var(--fs-accent-fg);
border-radius: 999px; border-radius: 999px;
padding: 0.05rem 0.45rem; padding: 0.05rem 0.45rem;
flex-shrink: 0; flex-shrink: 0;
@@ -689,7 +689,7 @@ async function confirmDelete() {
font-weight: 500; font-weight: 500;
text-transform: uppercase; text-transform: uppercase;
letter-spacing: 0.04em; letter-spacing: 0.04em;
color: var(--fs-text-tertiary); color: var(--fs-text-tertiary-fg);
background: color-mix(in srgb, var(--fs-text-tertiary) 12%, transparent); background: color-mix(in srgb, var(--fs-text-tertiary) 12%, transparent);
border-radius: 999px; border-radius: 999px;
padding: 0.05rem 0.45rem; padding: 0.05rem 0.45rem;
+1 -1
View File
@@ -168,7 +168,7 @@ function focusInput() {
border-radius: 999px; border-radius: 999px;
background: color-mix(in srgb, var(--fs-accent) 15%, transparent); background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
border: 1px solid var(--fs-accent); border: 1px solid var(--fs-accent);
color: var(--fs-accent); color: var(--fs-accent-fg);
font-size: 0.8rem; font-size: 0.8rem;
white-space: nowrap; white-space: nowrap;
} }
@@ -548,7 +548,7 @@ defineExpose({ reload: loadProjectNotes });
.note-tag-pill { .note-tag-pill {
font-size: 0.58rem; font-size: 0.58rem;
color: var(--fs-accent); color: var(--fs-accent-fg);
background: color-mix(in srgb, var(--fs-accent) 10%, transparent); background: color-mix(in srgb, var(--fs-accent) 10%, transparent);
border-radius: 999px; border-radius: 999px;
padding: 0 0.3rem; padding: 0 0.3rem;
@@ -645,7 +645,7 @@ defineExpose({ reload: loadProjectNotes });
.btn-tag-suggestion.applied { .btn-tag-suggestion.applied {
background: color-mix(in srgb, var(--fs-accent) 15%, transparent); background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
border-color: var(--fs-accent); border-color: var(--fs-accent);
color: var(--fs-accent); color: var(--fs-accent-fg);
} }
.link-suggest-strip { .link-suggest-strip {
@@ -424,8 +424,8 @@ defineExpose({ reload: loadAll });
border-radius: 10px; border-radius: 10px;
text-transform: capitalize; text-transform: capitalize;
} }
.ms-status-active { background: color-mix(in srgb, var(--fs-accent) 15%, transparent); color: var(--fs-accent); } .ms-status-active { background: color-mix(in srgb, var(--fs-accent) 15%, transparent); color: var(--fs-accent-fg); }
.ms-status-completed { background: color-mix(in srgb, var(--fs-success) 15%, transparent); color: var(--fs-success); } .ms-status-completed { background: color-mix(in srgb, var(--fs-success) 15%, transparent); color: var(--fs-success-fg); }
.task-items { .task-items {
list-style: none; list-style: none;
@@ -516,8 +516,8 @@ defineExpose({ reload: loadAll });
user-select: none; user-select: none;
margin-left: auto; margin-left: auto;
} }
.status-cycler.status-in_progress { border-color: var(--fs-accent); color: var(--fs-accent); background: color-mix(in srgb, var(--fs-accent) 10%, transparent); } .status-cycler.status-in_progress { border-color: var(--fs-accent); color: var(--fs-accent-fg); background: color-mix(in srgb, var(--fs-accent) 10%, transparent); }
.status-cycler.status-done { border-color: var(--fs-success); color: var(--fs-success); background: color-mix(in srgb, var(--fs-success) 10%, transparent); } .status-cycler.status-done { border-color: var(--fs-success); color: var(--fs-success-fg); background: color-mix(in srgb, var(--fs-success) 10%, transparent); }
.btn-edit-task { margin-left: 0.25rem; } .btn-edit-task { margin-left: 0.25rem; }
.btn-edit-task:hover { text-decoration: underline; } .btn-edit-task:hover { text-decoration: underline; }
+2 -2
View File
@@ -774,7 +774,7 @@ onUnmounted(() => {
.tag-chip { .tag-chip {
font-size: 0.7rem; font-size: 0.7rem;
background: color-mix(in srgb, var(--fs-accent) 15%, transparent); background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
color: var(--fs-accent); color: var(--fs-accent-fg);
border-radius: 999px; border-radius: 999px;
padding: 0.1rem 0.4rem; padding: 0.1rem 0.4rem;
} }
@@ -928,7 +928,7 @@ onUnmounted(() => {
} }
.peek-linked-item:hover { .peek-linked-item:hover {
background: color-mix(in srgb, var(--fs-accent) 8%, var(--fs-surface-raised)); background: color-mix(in srgb, var(--fs-accent) 8%, var(--fs-surface-raised));
color: var(--fs-accent); color: var(--fs-accent-fg);
} }
.peek-linked-type { .peek-linked-type {
+2 -2
View File
@@ -753,7 +753,7 @@ onUnmounted(() => {
} }
.filter-btn.active .filter-count { .filter-btn.active .filter-count {
background: color-mix(in srgb, var(--fs-accent) 20%, transparent); background: color-mix(in srgb, var(--fs-accent) 20%, transparent);
color: var(--fs-accent); color: var(--fs-accent-fg);
} }
.filter-tag { font-size: 0.78rem; } .filter-tag { font-size: 0.78rem; }
@@ -926,7 +926,7 @@ onUnmounted(() => {
border-radius: 4px; border-radius: 4px;
white-space: nowrap; white-space: nowrap;
background: color-mix(in srgb, var(--fs-text-secondary) 15%, transparent); background: color-mix(in srgb, var(--fs-text-secondary) 15%, transparent);
color: var(--fs-text-secondary); color: var(--fs-text-secondary-fg);
} }
/* ── Task card ──────────────────────────────────────────── */ /* ── Task card ──────────────────────────────────────────── */
+1 -1
View File
@@ -416,7 +416,7 @@ async function convertToTask() {
} }
.badge-note { .badge-note {
background: color-mix(in srgb, var(--fs-accent) 12%, transparent); background: color-mix(in srgb, var(--fs-accent) 12%, transparent);
color: var(--fs-accent); color: var(--fs-accent-fg);
border: 1px solid color-mix(in srgb, var(--fs-accent) 25%, transparent); border: 1px solid color-mix(in srgb, var(--fs-accent) 25%, transparent);
} }
.badge-task { .badge-task {
+4 -4
View File
@@ -1287,8 +1287,8 @@ async function confirmDelete() {
.stat-todo { background: color-mix(in srgb, var(--fs-text-tertiary) 8%, transparent); color: var(--fs-text-secondary); border-color: var(--fs-border-color); } .stat-todo { background: color-mix(in srgb, var(--fs-text-tertiary) 8%, transparent); color: var(--fs-text-secondary); border-color: var(--fs-border-color); }
.stat-inprogress { background: color-mix(in srgb, #3b82f6 10%, transparent); color: #3b82f6; border-color: color-mix(in srgb, #3b82f6 28%, transparent); } .stat-inprogress { background: color-mix(in srgb, #3b82f6 10%, transparent); color: #3b82f6; border-color: color-mix(in srgb, #3b82f6 28%, transparent); }
.stat-done { background: color-mix(in srgb, var(--fs-success) 10%, transparent); color: var(--fs-success); border-color: color-mix(in srgb, var(--fs-success) 28%, transparent); } .stat-done { background: color-mix(in srgb, var(--fs-success) 10%, transparent); color: var(--fs-success-fg); border-color: color-mix(in srgb, var(--fs-success) 28%, transparent); }
.stat-notes { background: color-mix(in srgb, var(--fs-accent) 8%, transparent); color: var(--fs-accent); border-color: color-mix(in srgb, var(--fs-accent) 22%, transparent); } .stat-notes { background: color-mix(in srgb, var(--fs-accent) 8%, transparent); color: var(--fs-accent-fg); border-color: color-mix(in srgb, var(--fs-accent) 22%, transparent); }
/* ── Pattern-library coverage card ───────────────────────────── */ /* ── Pattern-library coverage card ───────────────────────────── */
.coverage-card { .coverage-card {
@@ -1471,7 +1471,7 @@ async function confirmDelete() {
.tab-btn.active .tab-count { .tab-btn.active .tab-count {
background: color-mix(in srgb, var(--fs-accent) 12%, transparent); background: color-mix(in srgb, var(--fs-accent) 12%, transparent);
border-color: color-mix(in srgb, var(--fs-accent) 30%, transparent); border-color: color-mix(in srgb, var(--fs-accent) 30%, transparent);
color: var(--fs-accent); color: var(--fs-accent-fg);
} }
/* ── Tasks view ──────────────────────────────────────────────── */ /* ── Tasks view ──────────────────────────────────────────────── */
@@ -1707,7 +1707,7 @@ async function confirmDelete() {
border-radius: 3px; border-radius: 3px;
margin-left: auto; margin-left: auto;
} }
.col-add-btn:hover { color: var(--fs-accent); background: color-mix(in srgb, var(--fs-accent) 10%, transparent); } .col-add-btn:hover { color: var(--fs-accent-fg); background: color-mix(in srgb, var(--fs-accent) 10%, transparent); }
.kanban-cards { display: flex; flex-direction: column; gap: 0.3rem; } .kanban-cards { display: flex; flex-direction: column; gap: 0.3rem; }
+8 -8
View File
@@ -2732,7 +2732,7 @@ async function deleteUser(userId: number) {
background: var(--fs-surface-raised); background: var(--fs-surface-raised);
} }
.sidebar-item.active { .sidebar-item.active {
color: var(--fs-accent); color: var(--fs-accent-fg);
background: color-mix(in srgb, var(--fs-accent) 8%, transparent); background: color-mix(in srgb, var(--fs-accent) 8%, transparent);
border-left-color: var(--fs-accent); border-left-color: var(--fs-accent);
font-weight: 500; font-weight: 500;
@@ -3099,7 +3099,7 @@ async function deleteUser(userId: number) {
border-radius: var(--fs-radius-sm); border-radius: var(--fs-radius-sm);
} }
.role-admin { .role-admin {
color: var(--fs-accent); color: var(--fs-accent-fg);
background: color-mix(in srgb, var(--fs-accent) 15%, transparent); background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
} }
.role-user { .role-user {
@@ -3179,9 +3179,9 @@ async function deleteUser(userId: number) {
text-transform: uppercase; letter-spacing: 0.05em; text-transform: uppercase; letter-spacing: 0.05em;
padding: 0.1rem 0.35rem; border-radius: var(--fs-radius-sm); padding: 0.1rem 0.35rem; border-radius: var(--fs-radius-sm);
} }
.cat-audit { color: var(--fs-accent); background: color-mix(in srgb, var(--fs-accent) 15%, transparent); } .cat-audit { color: var(--fs-accent-fg); background: color-mix(in srgb, var(--fs-accent) 15%, transparent); }
.cat-usage { color: var(--fs-success); background: color-mix(in srgb, var(--fs-success) 15%, transparent); } .cat-usage { color: var(--fs-success-fg); background: color-mix(in srgb, var(--fs-success) 15%, transparent); }
.cat-error { color: var(--fs-error); background: color-mix(in srgb, var(--fs-error) 15%, transparent); } .cat-error { color: var(--fs-error-fg); background: color-mix(in srgb, var(--fs-error) 15%, transparent); }
.method-tag { .method-tag {
display: inline-block; display: inline-block;
font-size: 0.65rem; font-weight: 500; font-family: monospace; font-size: 0.65rem; font-weight: 500; font-family: monospace;
@@ -3346,8 +3346,8 @@ async function deleteUser(userId: number) {
padding: 0.15rem 0.4rem; padding: 0.15rem 0.4rem;
border-radius: 4px; border-radius: 4px;
} }
.role-owner { background: color-mix(in srgb, var(--fs-warning) 15%, transparent); color: var(--fs-warning); } .role-owner { background: color-mix(in srgb, var(--fs-warning) 15%, transparent); color: var(--fs-warning-fg); }
.role-member { background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent); color: var(--fs-text-tertiary); } .role-member { background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent); color: var(--fs-text-tertiary-fg); }
.members-empty { .members-empty {
color: var(--fs-text-tertiary); color: var(--fs-text-tertiary);
@@ -3528,7 +3528,7 @@ async function deleteUser(userId: number) {
.day-btn.active { .day-btn.active {
background: color-mix(in srgb, var(--fs-accent) 15%, transparent); background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
border-color: var(--fs-accent); border-color: var(--fs-accent);
color: var(--fs-accent); color: var(--fs-accent-fg);
font-weight: 500; font-weight: 500;
} }
+3 -3
View File
@@ -242,9 +242,9 @@ onMounted(async () => {
border-radius: 4px; border-radius: 4px;
white-space: nowrap; white-space: nowrap;
} }
.perm-viewer { background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent); color: var(--fs-text-tertiary); } .perm-viewer { background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent); color: var(--fs-text-tertiary-fg); }
.perm-editor { background: color-mix(in srgb, var(--fs-accent) 15%, transparent); color: var(--fs-accent); } .perm-editor { background: color-mix(in srgb, var(--fs-accent) 15%, transparent); color: var(--fs-accent-fg); }
.perm-admin { background: color-mix(in srgb, var(--fs-warning) 15%, transparent); color: var(--fs-warning); } .perm-admin { background: color-mix(in srgb, var(--fs-warning) 15%, transparent); color: var(--fs-warning-fg); }
.empty-msg { .empty-msg {
margin: 0; margin: 0;
+1 -1
View File
@@ -288,7 +288,7 @@ async function confirmDelete() {
font-family: var(--fs-font-mono); font-family: var(--fs-font-mono);
font-size: 0.82rem; font-size: 0.82rem;
background: color-mix(in srgb, var(--fs-accent) 12%, transparent); background: color-mix(in srgb, var(--fs-accent) 12%, transparent);
color: var(--fs-accent); color: var(--fs-accent-fg);
padding: 0.08rem 0.35rem; padding: 0.08rem 0.35rem;
border-radius: var(--fs-radius-sm); border-radius: var(--fs-radius-sm);
word-break: break-all; word-break: break-all;
+5 -5
View File
@@ -710,7 +710,7 @@ function usageTitle(s: SnippetListItem): string {
flex-shrink: 0; flex-shrink: 0;
white-space: nowrap; white-space: nowrap;
background: color-mix(in srgb, var(--fs-accent) 15%, transparent); background: color-mix(in srgb, var(--fs-accent) 15%, transparent);
color: var(--fs-accent); color: var(--fs-accent-fg);
} }
.snippet-when { .snippet-when {
@@ -739,7 +739,7 @@ function usageTitle(s: SnippetListItem): string {
border-radius: 4px; border-radius: 4px;
white-space: nowrap; white-space: nowrap;
background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent); background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent);
color: var(--fs-text-tertiary); color: var(--fs-text-tertiary-fg);
} }
.dup-action { .dup-action {
@@ -754,7 +754,7 @@ function usageTitle(s: SnippetListItem): string {
border-radius: 4px; border-radius: 4px;
white-space: nowrap; white-space: nowrap;
background: color-mix(in srgb, var(--fs-error) 15%, transparent); background: color-mix(in srgb, var(--fs-error) 15%, transparent);
color: var(--fs-error); color: var(--fs-error-fg);
} }
.usage-tag { .usage-tag {
@@ -764,14 +764,14 @@ function usageTitle(s: SnippetListItem): string {
white-space: nowrap; white-space: nowrap;
font-variant-numeric: tabular-nums; font-variant-numeric: tabular-nums;
background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent); background: color-mix(in srgb, var(--fs-text-tertiary) 15%, transparent);
color: var(--fs-text-tertiary); color: var(--fs-text-tertiary-fg);
} }
/* Dead weight is a nudge, not an error — it warns in the warning colour rather /* Dead weight is a nudge, not an error — it warns in the warning colour rather
than the danger one, because the record isn't broken, just unearned. */ than the danger one, because the record isn't broken, just unearned. */
.usage-tag.usage-dead { .usage-tag.usage-dead {
background: color-mix(in srgb, var(--fs-warning) 18%, transparent); background: color-mix(in srgb, var(--fs-warning) 18%, transparent);
color: var(--fs-warning); color: var(--fs-warning-fg);
} }
/* Header + select-mode */ /* Header + select-mode */
+22 -15
View File
@@ -119,15 +119,20 @@ def same_hue_text_on_tint(css: str) -> tuple[list[str], list[str]]:
dangerous of the two — it does not even name a `-bg` token, so nothing dangerous of the two — it does not even name a `-bg` token, so nothing
about it looks like the pattern until you measure it. about it looks like the pattern until you measure it.
Returned separately because they are at different stages. The token form Returned separately because they were paid down separately — the token
is CLEAN and therefore gates. The inline form has a live backlog (48 sites form first (7 badge pairs), then the inline form (46 sites across 18
when this split was written, 26 of them --fs-accent), so it reports with a files, 26 of them --fs-accent). Both are clean now, so BOTH gate. The
count: a gate nobody can satisfy today gets switched off, and then it split is kept because the two spellings need different error text: one
guards nothing. names a -bg token you can search for, the other names nothing at all.
""" """
token_form, inline_form = [], [] token_form, inline_form = [], []
for body in re.findall(r"\{([^{}]*)\}", css): for body in re.findall(r"\{([^{}]*)\}", css):
fg = set(re.findall(r"color\s*:\s*var\(\s*(--fs-[\w-]+?)\s*\)", body)) # (?<![-\w]) or `border-color`, `border-left-color` and `outline-color`
# all match as if they were text. They are not: a border is a non-text
# graphic and its floor is 3:1, not 4.5. Without this the check reported
# seven rules that were already correct — and a check that cries wolf on
# correct code is one that gets muted.
fg = set(re.findall(r"(?<![-\w])color\s*:\s*var\(\s*(--fs-[\w-]+?)\s*\)", body))
bg_tok = set(re.findall(r"background(?:-color)?\s*:\s*var\(\s*(--fs-[\w-]+?)-bg\s*\)", body)) bg_tok = set(re.findall(r"background(?:-color)?\s*:\s*var\(\s*(--fs-[\w-]+?)-bg\s*\)", body))
bg_inl = set(re.findall( bg_inl = set(re.findall(
r"background(?:-color)?\s*:\s*color-mix\([^;]*?var\(\s*(--fs-[\w-]+?)\s*\)[^;]*?\)", r"background(?:-color)?\s*:\s*color-mix\([^;]*?var\(\s*(--fs-[\w-]+?)\s*\)[^;]*?\)",
@@ -232,21 +237,23 @@ def main() -> int:
print("OK — no text painted with a token on a tint of its own -bg.\n") print("OK — no text painted with a token on a tint of its own -bg.\n")
if inline_tint_hits: if inline_tint_hits:
by_tok: dict[str, int] = {} print(f"FAIL — {len(inline_tint_hits)} rule(s) paint text with a token on an "
for _p, tok in inline_tint_hits: f"INLINE color-mix tint of that same token.")
by_tok[tok] = by_tok.get(tok, 0) + 1 print(" Identical defect to the block above, spelled without a -bg token —")
print(f"REPORT — {len(inline_tint_hits)} rule(s) paint text with a token on " print(" which is what let it hide: nothing about it LOOKS like the pattern.")
f"an INLINE color-mix tint of that same token.") print(" Use the token's -fg sibling.\n")
print(" Same defect, spelled without a -bg token so it does not gate yet.") for path, tok in inline_tint_hits:
print(" Worst offenders: " + ", ".join( print(f" {path}: color: var({tok}) on an inline tint -> var({tok}-fg)")
f"{t} x{n}" for t, n in sorted(by_tok.items(), key=lambda kv: -kv[1])[:4]) + "\n") print()
else:
print("OK — no text painted with a token on an inline tint of itself.\n")
if args.report_literals: if args.report_literals:
print(f"REPORT — {literal_count} raw colour literal(s) in component CSS.") print(f"REPORT — {literal_count} raw colour literal(s) in component CSS.")
print(" Advisory: a literal is a value stated outside the system, so it " print(" Advisory: a literal is a value stated outside the system, so it "
"cannot follow a palette change.\n") "cannot follow a palette change.\n")
return 1 if (unresolved or same_hue_hits) else 0 return 1 if (unresolved or same_hue_hits or inline_tint_hits) else 0
if __name__ == "__main__": if __name__ == "__main__":