refactor(frontend): derive the sweep-row visual language into sweep-shared.css (#3207)
CI & Build / Python lint (push) Successful in 2s
CI & Build / Plugin hooks (push) Successful in 12s
CI & Build / integration (push) Successful in 52s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m32s
CI & Build / Build & push image (push) Failing after 22s

The note sweep, rule sweep, preference drift and rule history panes each
restated the same row recipe in a scoped block. It now lives once in
assets/sweep-shared.css under `sweep-` names (unprefixed globals would
leak into the unrelated scoped .row-title/.lede/.age/.actions elsewhere),
imported unscoped beside each pane's own scoped remainder.

Moved only what was shared: RuleHistoryPanel takes .sweep-state and keeps
its button row head; PreferenceDrift keeps its tiny-type footnote and
overrides the action row's layout. Lede and state now use the body-sm
token instead of 0.85rem/0.9rem (13px vs 13.6/14.4px) — one value, from
the design system.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
2026-09-24 06:45:43 -04:00
co-authored by Claude Opus 5.5
parent 952e56ee75
commit 22b7a928da
5 changed files with 193 additions and 178 deletions
+134
View File
@@ -0,0 +1,134 @@
/* The sweep-row visual language (#3207).
*
* A "sweep" is a pane that lists records in the order they most need a human
* — the note verification sweep, the rule sweep, preference drift — as one
* raised row per record: an italic title that opens it, an age or date pushed
* right, a small grid of facts, and a row of plain buttons. It was written
* out four times, scoped, before this sheet existed.
*
* Imported UNSCOPED (`<style src="@/assets/sweep-shared.css" />`), so every
* class is prefixed `sweep-`: `.row-title`, `.lede`, `.age` and `.actions`
* also exist, scoped, in views that have nothing to do with sweeps, and an
* unprefixed global would leak into all of them. What a pane does
* differently stays in its own scoped block — spacing remainders, a `code`
* style — rather than growing a modifier here per pane.
*/
.sweep-lede {
margin: 0;
max-width: 62ch;
font-size: var(--fs-size-body-sm);
color: var(--fs-text-secondary);
line-height: var(--fs-leading-body);
}
.sweep-filters {
display: flex;
gap: var(--fs-space-5);
align-items: center;
flex-wrap: wrap;
}
.sweep-filter {
display: flex;
align-items: center;
gap: var(--fs-space-2);
font-size: 0.82rem;
color: var(--fs-text-secondary);
}
.sweep-filter input[type="checkbox"] { accent-color: var(--fs-accent); }
.sweep-state {
margin: 0;
font-size: var(--fs-size-body-sm);
color: var(--fs-text-secondary);
}
.sweep-state.empty { color: var(--fs-text-tertiary); }
.sweep-rows {
list-style: none;
margin: 0;
padding: 0;
display: flex;
flex-direction: column;
gap: var(--fs-space-3);
}
.sweep-row {
background: var(--fs-surface-raised);
border-radius: var(--fs-radius-md);
padding: var(--fs-space-3);
}
.sweep-row-head {
display: flex;
align-items: baseline;
gap: var(--fs-space-2);
flex-wrap: wrap;
}
.sweep-row-title {
background: none;
border: none;
padding: 0;
cursor: pointer;
font-family: Fraunces, serif;
font-style: italic;
font-size: 1.02rem;
color: var(--fs-text-primary);
text-align: left;
}
.sweep-row-title:hover { text-decoration: underline; }
/* The ORDER carries urgency — the top of a sweep is the thing that most needs
a look. No red/amber ramp: it would restate the ordering and force an
invented "stale after N days" threshold. "Never" is marked because it is
categorically DIFFERENT from a date, not a worse one. */
.sweep-age {
margin-left: auto;
font-size: 0.78rem;
color: var(--fs-text-secondary);
font-variant-numeric: tabular-nums;
}
.sweep-age.unchecked { font-style: italic; color: var(--fs-text-tertiary); }
.sweep-check {
display: grid;
grid-template-columns: auto 1fr;
gap: 0.15rem var(--fs-space-3);
margin: var(--fs-space-3) 0 0;
}
.sweep-check dt {
font-size: 0.7rem;
text-transform: uppercase;
letter-spacing: 0.05em;
color: var(--fs-text-tertiary);
}
.sweep-check dd {
margin: 0;
font-size: 0.82rem;
color: var(--fs-text-primary);
min-width: 0;
}
.sweep-actions {
display: flex;
gap: var(--fs-space-2);
margin-top: var(--fs-space-3);
}
.sweep-actions button {
cursor: pointer;
font: inherit;
font-size: 0.78rem;
background: var(--fs-surface-page);
color: var(--fs-text-primary);
border: 1px solid var(--fs-border-color);
border-radius: var(--fs-radius-md);
padding: 0.25rem 0.6rem;
}
.sweep-actions button:hover:not(:disabled) { background: var(--fs-surface-hover); }
.sweep-actions button:disabled { opacity: var(--fs-disabled-opacity); cursor: default; }
.sweep-footnote {
margin: 0;
max-width: 62ch;
font-size: 0.78rem;
color: var(--fs-text-tertiary);
line-height: 1.45;
}
+17 -63
View File
@@ -83,42 +83,42 @@ defineExpose({ reload });
<section class="sweep"> <section class="sweep">
<header> <header>
<h2>Due for verification</h2> <h2>Due for verification</h2>
<p class="lede"> <p class="sweep-lede">
Notes that assert a fact about something outside your control — what a Notes that assert a fact about something outside your control — what a
service does, how a tool behaves. Most notes are decisions and never service does, how a tool behaves. Most notes are decisions and never
appear here; they have no truth value to go stale. appear here; they have no truth value to go stale.
</p> </p>
</header> </header>
<div class="filters"> <div class="sweep-filters">
<label class="filter"> <label class="sweep-filter">
<input v-model="neverOnly" type="checkbox" @change="reload" /> <input v-model="neverOnly" type="checkbox" @change="reload" />
<span>Never checked only</span> <span>Never checked only</span>
</label> </label>
</div> </div>
<p v-if="loading" class="state">Loading…</p> <p v-if="loading" class="sweep-state">Loading…</p>
<!-- An empty sweep is GOOD NEWS and must not read like a broken page. --> <!-- An empty sweep is GOOD NEWS and must not read like a broken page. -->
<p v-else-if="!rows.length" class="state empty"> <p v-else-if="!rows.length" class="sweep-state empty">
Nothing to check. Nothing to check.
{{ neverOnly {{ neverOnly
? "Every note that carries a check has been confirmed at least once." ? "Every note that carries a check has been confirmed at least once."
: "No note carries a check yet — add one to a note that asserts a fact." }} : "No note carries a check yet — add one to a note that asserts a fact." }}
</p> </p>
<ol v-else class="rows"> <ol v-else class="sweep-rows">
<li v-for="n in rows" :key="n.id" class="row"> <li v-for="n in rows" :key="n.id" class="sweep-row">
<div class="row-head"> <div class="sweep-row-head">
<button class="row-title" @click="emit('open-note', n.id)">{{ n.title }}</button> <button class="sweep-row-title" @click="emit('open-note', n.id)">{{ n.title }}</button>
<span class="age" :class="{ unchecked: n.days_since_verified === null }"> <span class="sweep-age" :class="{ unchecked: n.days_since_verified === null }">
{{ n.days_since_verified === null {{ n.days_since_verified === null
? "never checked" ? "never checked"
: `${n.days_since_verified}d ago` }} : `${n.days_since_verified}d ago` }}
</span> </span>
</div> </div>
<dl class="check"> <dl class="sweep-check">
<dt>Check</dt> <dt>Check</dt>
<dd>{{ n.verify_with }}</dd> <dd>{{ n.verify_with }}</dd>
<template v-if="n.expires_when"> <template v-if="n.expires_when">
@@ -127,14 +127,14 @@ defineExpose({ reload });
</template> </template>
</dl> </dl>
<div class="actions"> <div class="sweep-actions">
<button :disabled="busyId === n.id" @click="verify(n.id, true)">Still true</button> <button :disabled="busyId === n.id" @click="verify(n.id, true)">Still true</button>
<button :disabled="busyId === n.id" @click="verify(n.id, false)">No longer true</button> <button :disabled="busyId === n.id" @click="verify(n.id, false)">No longer true</button>
</div> </div>
</li> </li>
</ol> </ol>
<p v-if="rows.length" class="footnote"> <p v-if="rows.length" class="sweep-footnote">
Record a result only after actually running the check. “No longer true” stores nothing Record a result only after actually running the check. “No longer true” stores nothing
on purpose — the note is wrong rather than in a state worth recording, so it keeps its on purpose — the note is wrong rather than in a state worth recording, so it keeps its
place here until you correct it, supersede it, or remove its check. place here until you correct it, supersede it, or remove its check.
@@ -145,54 +145,8 @@ defineExpose({ reload });
<style scoped> <style scoped>
.sweep { display: flex; flex-direction: column; gap: var(--fs-space-3); } .sweep { display: flex; flex-direction: column; gap: var(--fs-space-3); }
h2 { margin: 0; font-size: 1.05rem; } h2 { margin: 0; font-size: 1.05rem; }
.lede { .sweep-lede { margin-top: 0.35rem; }
margin: 0.35rem 0 0; /* A check can be a long URL or command with no break point. */
max-width: 62ch; .sweep-check dd { overflow-wrap: anywhere; }
font-size: 0.85rem;
color: var(--fs-text-secondary);
line-height: 1.5;
}
.filters { display: flex; gap: var(--fs-space-5); align-items: center; flex-wrap: wrap; }
.filter { display: flex; align-items: center; gap: var(--fs-space-2); font-size: 0.82rem; color: var(--fs-text-secondary); }
.filter input[type="checkbox"] { accent-color: var(--fs-accent); }
.state { margin: 0; font-size: 0.9rem; color: var(--fs-text-secondary); }
.state.empty { color: var(--fs-text-tertiary); }
.rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--fs-space-3); }
.row {
background: var(--fs-surface-raised);
border-radius: var(--fs-radius-md);
padding: var(--fs-space-3);
}
.row-head { display: flex; align-items: baseline; gap: var(--fs-space-2); flex-wrap: wrap; }
.row-title {
background: none; border: none; padding: 0; cursor: pointer;
font-family: Fraunces, serif; font-style: italic; font-size: 1.02rem;
color: var(--fs-text-primary); text-align: left;
}
.row-title:hover { text-decoration: underline; }
/* The ORDER carries urgency — the top of this list is the least-confirmed
thing in the corpus. No red/amber ramp: it would restate the ordering and
force an invented "stale after N days" threshold. "Never" is marked because
it is categorically DIFFERENT from a date, not a worse one. */
.age { margin-left: auto; font-size: 0.78rem; color: var(--fs-text-secondary); font-variant-numeric: tabular-nums; }
.age.unchecked { font-style: italic; color: var(--fs-text-tertiary); }
.check { display: grid; grid-template-columns: auto 1fr; gap: 0.15rem var(--fs-space-3); margin: var(--fs-space-3) 0 0; }
.check dt { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--fs-text-tertiary); }
.check dd { margin: 0; font-size: 0.82rem; color: var(--fs-text-primary); min-width: 0; overflow-wrap: anywhere; }
.actions { display: flex; gap: var(--fs-space-2); margin-top: var(--fs-space-3); }
.actions button {
cursor: pointer; font: inherit; font-size: 0.78rem;
background: var(--fs-surface-page); color: var(--fs-text-primary);
border: 1px solid var(--fs-border-color); border-radius: var(--fs-radius-md);
padding: 0.25rem 0.6rem;
}
.actions button:hover:not(:disabled) { background: var(--fs-surface-hover); }
.actions button:disabled { opacity: var(--fs-disabled-opacity); cursor: default; }
.footnote { margin: 0; max-width: 62ch; font-size: 0.78rem; color: var(--fs-text-tertiary); line-height: 1.45; }
</style> </style>
<style src="@/assets/sweep-shared.css" />
@@ -77,7 +77,7 @@ onMounted(() => store.fetchDrift());
<section class="pane drift"> <section class="pane drift">
<header> <header>
<h2>Recent changes</h2> <h2>Recent changes</h2>
<p class="lede"> <p class="sweep-lede">
Preferences Scribe rewrote while working, most recently changed first. A Preferences Scribe rewrote while working, most recently changed first. A
preference is how you want work done, so sessions keep it current preference is how you want work done, so sessions keep it current
without asking — this is where you see what they decided. Rules are not without asking — this is where you see what they decided. Rules are not
@@ -85,18 +85,18 @@ onMounted(() => store.fetchDrift());
</p> </p>
</header> </header>
<p v-if="store.loading" class="state">Loading…</p> <p v-if="store.loading" class="sweep-state">Loading…</p>
<!-- Nothing changed is the ordinary state and must not read as a fault. --> <!-- Nothing changed is the ordinary state and must not read as a fault. -->
<p v-else-if="!store.drift.length" class="state empty"> <p v-else-if="!store.drift.length" class="sweep-state empty">
Nothing has been rewritten. A preference appears here the first time a Nothing has been rewritten. A preference appears here the first time a
session changes one — until then there is nothing to review. session changes one — until then there is nothing to review.
</p> </p>
<ol v-else class="rows"> <ol v-else class="sweep-rows">
<li v-for="row in store.drift" :key="row.rule.id" class="row"> <li v-for="row in store.drift" :key="row.rule.id" class="sweep-row">
<div class="row-head"> <div class="sweep-row-head">
<button class="row-title" @click="emit('open-rule', row.rule.id)"> <button class="sweep-row-title" @click="emit('open-rule', row.rule.id)">
{{ row.rule.title }} {{ row.rule.title }}
</button> </button>
<span class="when">{{ stamp(row.previous.created_at) }}</span> <span class="when">{{ stamp(row.previous.created_at) }}</span>
@@ -122,10 +122,10 @@ onMounted(() => store.fetchDrift());
<span class="was">Was:</span> {{ row.previous.when_to_apply || "nothing" }} <span class="was">Was:</span> {{ row.previous.when_to_apply || "nothing" }}
</p> </p>
<DiffView v-if="diffFor(row).length" :diff="diffFor(row)" /> <DiffView v-if="diffFor(row).length" :diff="diffFor(row)" />
<p v-else class="state"> <p v-else class="sweep-state">
The statement is unchanged — this edit moved another field. The statement is unchanged — this edit moved another field.
</p> </p>
<div class="actions"> <div class="sweep-actions">
<button <button
:disabled="busyId === row.rule.id" :disabled="busyId === row.rule.id"
@click="restore(row)" @click="restore(row)"
@@ -150,27 +150,6 @@ onMounted(() => store.fetchDrift());
<style src="@/assets/rules-shared.css" /> <style src="@/assets/rules-shared.css" />
<style scoped> <style scoped>
.drift { display: flex; flex-direction: column; gap: var(--fs-space-3); } .drift { display: flex; flex-direction: column; gap: var(--fs-space-3); }
.lede {
margin: 0; max-width: 62ch; font-size: var(--fs-size-body-sm);
color: var(--fs-text-secondary); line-height: var(--fs-leading-body);
}
.state { margin: 0; font-size: var(--fs-size-body-sm); color: var(--fs-text-secondary); }
.state.empty { color: var(--fs-text-tertiary); }
.rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--fs-space-3); }
.row {
background: var(--fs-surface-raised);
border-radius: var(--fs-radius-md);
padding: var(--fs-space-3);
}
.row-head { display: flex; align-items: baseline; gap: var(--fs-space-2); flex-wrap: wrap; }
.row-title {
background: none; border: none; padding: 0; cursor: pointer;
font-family: Fraunces, serif; font-style: italic; font-size: 1.02rem;
color: var(--fs-text-primary); text-align: left;
}
.row-title:hover { text-decoration: underline; }
.when { .when {
margin-left: auto; font-size: var(--fs-size-tiny); margin-left: auto; font-size: var(--fs-size-tiny);
color: var(--fs-text-secondary); font-variant-numeric: tabular-nums; color: var(--fs-text-secondary); font-variant-numeric: tabular-nums;
@@ -205,19 +184,14 @@ onMounted(() => store.fetchDrift());
} }
.was { color: var(--fs-text-tertiary); } .was { color: var(--fs-text-tertiary); }
.actions { display: flex; align-items: baseline; gap: var(--fs-space-3); flex-wrap: wrap; }
.actions button {
cursor: pointer; font: inherit; font-size: 0.78rem;
background: var(--fs-surface-page); color: var(--fs-text-primary);
border: 1px solid var(--fs-border-color); border-radius: var(--fs-radius-md);
padding: 0.25rem 0.6rem;
}
.actions button:hover:not(:disabled) { background: var(--fs-surface-hover); }
.actions button:disabled { opacity: var(--fs-disabled-opacity); cursor: default; }
.actions-note { .actions-note {
flex: 1; min-width: 18ch; font-size: var(--fs-size-tiny); flex: 1; min-width: 18ch; font-size: var(--fs-size-tiny);
color: var(--fs-text-tertiary); line-height: var(--fs-leading-body); color: var(--fs-text-tertiary); line-height: var(--fs-leading-body);
} }
.footnote { margin: 0; max-width: 62ch; font-size: var(--fs-size-tiny); color: var(--fs-text-tertiary); line-height: var(--fs-leading-body); } .footnote { margin: 0; max-width: 62ch; font-size: var(--fs-size-tiny); color: var(--fs-text-tertiary); line-height: var(--fs-leading-body); }
/* Drift's buttons sit beside a note inside the expanded detail, not under a
row of facts. */
.sweep-actions { align-items: baseline; gap: var(--fs-space-3); flex-wrap: wrap; margin-top: 0; }
</style> </style>
<style src="@/assets/sweep-shared.css" />
@@ -144,10 +144,10 @@ watch(() => props.ruleId, () => { selected.value = null; load(); });
</button> </button>
<div v-if="expanded" class="body"> <div v-if="expanded" class="body">
<p v-if="loading" class="state">Loading…</p> <p v-if="loading" class="sweep-state">Loading…</p>
<!-- Never reworded is the ordinary case, and must not read as a fault. --> <!-- Never reworded is the ordinary case, and must not read as a fault. -->
<p v-else-if="!versions.length" class="state empty"> <p v-else-if="!versions.length" class="sweep-state empty">
This rule has never been reworded. Nothing was recorded before the history This rule has never been reworded. Nothing was recorded before the history
existed, so an older rule starts empty too. existed, so an older rule starts empty too.
</p> </p>
@@ -172,7 +172,7 @@ watch(() => props.ruleId, () => { selected.value = null; load(); });
</button> </button>
<div v-if="selected?.id === v.id" class="detail"> <div v-if="selected?.id === v.id" class="detail">
<p v-if="loadingDetail" class="state">Loading…</p> <p v-if="loadingDetail" class="sweep-state">Loading…</p>
<template v-else> <template v-else>
<p v-if="checkChanged(i)" class="warn"> <p v-if="checkChanged(i)" class="warn">
This edit changed the rule's check, which cleared its verification This edit changed the rule's check, which cleared its verification
@@ -188,7 +188,7 @@ watch(() => props.ruleId, () => { selected.value = null; load(); });
</dl> </dl>
<h4>Statement</h4> <h4>Statement</h4>
<DiffView v-if="diff.length" :diff="diff" /> <DiffView v-if="diff.length" :diff="diff" />
<p v-else class="state">The statement did not change in this edit.</p> <p v-else class="sweep-state">The statement did not change in this edit.</p>
</template> </template>
</div> </div>
</li> </li>
@@ -213,8 +213,6 @@ watch(() => props.ruleId, () => { selected.value = null; load(); });
} }
.body { margin-top: var(--fs-space-3); display: flex; flex-direction: column; gap: var(--fs-space-3); } .body { margin-top: var(--fs-space-3); display: flex; flex-direction: column; gap: var(--fs-space-3); }
.state { margin: 0; font-size: var(--fs-size-body-sm); color: var(--fs-text-secondary); }
.state.empty { color: var(--fs-text-tertiary); }
.lede { .lede {
margin: 0; max-width: 62ch; font-size: var(--fs-size-tiny); margin: 0; max-width: 62ch; font-size: var(--fs-size-tiny);
color: var(--fs-text-tertiary); line-height: var(--fs-leading-body); color: var(--fs-text-tertiary); line-height: var(--fs-leading-body);
@@ -271,3 +269,4 @@ watch(() => props.ruleId, () => { selected.value = null; load(); });
} }
h4 { margin: var(--fs-space-2) 0 0; font-size: var(--fs-size-tiny); color: var(--fs-text-tertiary); } h4 { margin: var(--fs-space-2) 0 0; font-size: var(--fs-size-tiny); color: var(--fs-text-tertiary); }
</style> </style>
<style src="@/assets/sweep-shared.css" />
+24 -70
View File
@@ -39,37 +39,37 @@ onMounted(reload);
<section class="pane sweep"> <section class="pane sweep">
<header> <header>
<h2>Due for verification</h2> <h2>Due for verification</h2>
<p class="lede"> <p class="sweep-lede">
Rules that assert a fact about something outside your control. Most rules are Rules that assert a fact about something outside your control. Most rules are
decisions and never appear here — they have no truth value to go stale. decisions and never appear here — they have no truth value to go stale.
</p> </p>
</header> </header>
<div class="filters"> <div class="sweep-filters">
<label class="filter"> <label class="sweep-filter">
<input v-model="neverOnly" type="checkbox" @change="reload" /> <input v-model="neverOnly" type="checkbox" @change="reload" />
<span>Never checked only</span> <span>Never checked only</span>
</label> </label>
</div> </div>
<p v-if="store.loading" class="state">Loading…</p> <p v-if="store.loading" class="sweep-state">Loading…</p>
<!-- An empty sweep is GOOD NEWS, and must not read like a broken page. --> <!-- An empty sweep is GOOD NEWS, and must not read like a broken page. -->
<p v-else-if="!store.rulesDue.length" class="state empty"> <p v-else-if="!store.rulesDue.length" class="sweep-state empty">
Nothing to check. Nothing to check.
{{ neverOnly ? "No rule matches these filters." : "No rule carries a check yet — add one to a rule that asserts a fact." }} {{ neverOnly ? "No rule matches these filters." : "No rule carries a check yet — add one to a rule that asserts a fact." }}
</p> </p>
<ol v-else class="rows"> <ol v-else class="sweep-rows">
<li v-for="r in store.rulesDue" :key="r.id" class="row"> <li v-for="r in store.rulesDue" :key="r.id" class="sweep-row">
<div class="row-head"> <div class="sweep-row-head">
<button class="row-title" @click="emit('open-rule', r.id)">{{ r.title }}</button> <button class="sweep-row-title" @click="emit('open-rule', r.id)">{{ r.title }}</button>
<span <span
v-if="!r.when_to_apply" v-if="!r.when_to_apply"
class="rule-chip rule-chip-inert" class="rule-chip rule-chip-inert"
title="No trigger, so nothing can retrieve it — this rule will never reach a session" title="No trigger, so nothing can retrieve it — this rule will never reach a session"
>never surfaces</span> >never surfaces</span>
<span class="age" :class="{ unchecked: r.days_since_verified === null }"> <span class="sweep-age" :class="{ unchecked: r.days_since_verified === null }">
{{ r.days_since_verified === null {{ r.days_since_verified === null
? "never checked" ? "never checked"
: `${r.days_since_verified}d ago` }} : `${r.days_since_verified}d ago` }}
@@ -78,7 +78,7 @@ onMounted(reload);
<p class="statement">{{ r.statement }}</p> <p class="statement">{{ r.statement }}</p>
<dl class="check"> <dl class="sweep-check">
<dt>Check</dt> <dt>Check</dt>
<dd><code>{{ r.verify_with }}</code></dd> <dd><code>{{ r.verify_with }}</code></dd>
<template v-if="r.expires_when"> <template v-if="r.expires_when">
@@ -87,14 +87,14 @@ onMounted(reload);
</template> </template>
</dl> </dl>
<div class="actions"> <div class="sweep-actions">
<button :disabled="busyId === r.id" @click="verify(r.id, true)">Still true</button> <button :disabled="busyId === r.id" @click="verify(r.id, true)">Still true</button>
<button :disabled="busyId === r.id" @click="verify(r.id, false)">No longer true</button> <button :disabled="busyId === r.id" @click="verify(r.id, false)">No longer true</button>
</div> </div>
</li> </li>
</ol> </ol>
<p v-if="store.rulesDue.length" class="footnote"> <p v-if="store.rulesDue.length" class="sweep-footnote">
Record a result only after actually running the check. “No longer true” stores nothing Record a result only after actually running the check. “No longer true” stores nothing
on purpose — the rule is wrong rather than in a state worth recording, so it keeps its on purpose — the rule is wrong rather than in a state worth recording, so it keeps its
place here until you correct or retire it. place here until you correct or retire it.
@@ -105,69 +105,23 @@ onMounted(reload);
<style src="@/assets/rules-shared.css" /> <style src="@/assets/rules-shared.css" />
<style scoped> <style scoped>
.sweep { display: flex; flex-direction: column; gap: var(--fs-space-3); } .sweep { display: flex; flex-direction: column; gap: var(--fs-space-3); }
.lede {
margin: 0;
max-width: 62ch;
font-size: 0.85rem;
color: var(--fs-text-secondary);
line-height: 1.5;
}
.filters { display: flex; gap: var(--fs-space-5); align-items: center; flex-wrap: wrap; }
.filter { display: flex; align-items: center; gap: var(--fs-space-2); font-size: 0.82rem; color: var(--fs-text-secondary); }
.filter input[type="checkbox"] { accent-color: var(--fs-accent); }
.filter select {
font: inherit; font-size: 0.82rem;
background: var(--fs-surface-page); color: var(--fs-text-primary);
border: 1px solid var(--fs-border-color); border-radius: var(--fs-radius-md);
padding: 0.2rem 0.4rem;
}
.state { margin: 0; font-size: 0.9rem; color: var(--fs-text-secondary); }
.state.empty { color: var(--fs-text-tertiary); }
.rows { list-style: none; margin: 0; padding: 0; display: flex; flex-direction: column; gap: var(--fs-space-3); }
.row {
background: var(--fs-surface-raised);
border-radius: var(--fs-radius-md);
padding: var(--fs-space-3);
}
.row-head { display: flex; align-items: baseline; gap: var(--fs-space-2); flex-wrap: wrap; }
.row-title {
background: none; border: none; padding: 0; cursor: pointer;
font-family: Fraunces, serif; font-style: italic; font-size: 1.02rem;
color: var(--fs-text-primary); text-align: left;
}
.row-title:hover { text-decoration: underline; }
/* The ORDER carries urgency — the top of this list is the most overdue thing
in the rulebook. No red/amber ramp: it would restate the ordering and force
an invented "stale after N days" threshold. "Never" is marked because it is
categorically different from a date, not a worse one. */
.age { margin-left: auto; font-size: 0.78rem; color: var(--fs-text-secondary); font-variant-numeric: tabular-nums; }
.age.unchecked { font-style: italic; color: var(--fs-text-tertiary); }
.statement { margin: 0.35rem 0 0; font-size: 0.88rem; color: var(--fs-text-secondary); } .statement { margin: 0.35rem 0 0; font-size: 0.88rem; color: var(--fs-text-secondary); }
.check { display: grid; grid-template-columns: auto 1fr; gap: 0.15rem var(--fs-space-3); margin: var(--fs-space-3) 0 0; } .sweep-filter select {
.check dt { font-size: 0.7rem; text-transform: uppercase; letter-spacing: 0.05em; color: var(--fs-text-tertiary); } font: inherit;
.check dd { margin: 0; font-size: 0.82rem; color: var(--fs-text-primary); min-width: 0; } font-size: 0.82rem;
.check code { background: var(--fs-surface-page);
color: var(--fs-text-primary);
border: 1px solid var(--fs-border-color);
border-radius: var(--fs-radius-md);
padding: 0.2rem 0.4rem;
}
.sweep-check code {
font-family: var(--fs-font-mono); font-family: var(--fs-font-mono);
background: var(--fs-surface-code-inline); background: var(--fs-surface-code-inline);
border-radius: var(--fs-radius-sm); border-radius: var(--fs-radius-sm);
padding: 0.05rem 0.3rem; padding: 0.05rem 0.3rem;
overflow-wrap: anywhere; overflow-wrap: anywhere;
} }
.actions { display: flex; gap: var(--fs-space-2); margin-top: var(--fs-space-3); }
.actions button {
cursor: pointer; font: inherit; font-size: 0.78rem;
background: var(--fs-surface-page); color: var(--fs-text-primary);
border: 1px solid var(--fs-border-color); border-radius: var(--fs-radius-md);
padding: 0.25rem 0.6rem;
}
.actions button:hover:not(:disabled) { background: var(--fs-surface-hover); }
.actions button:disabled { opacity: var(--fs-disabled-opacity); cursor: default; }
.footnote { margin: 0; max-width: 62ch; font-size: 0.78rem; color: var(--fs-text-tertiary); line-height: 1.45; }
</style> </style>
<style src="@/assets/sweep-shared.css" />