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
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:
@@ -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;
|
||||||
|
}
|
||||||
@@ -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" />
|
||||||
|
|||||||
@@ -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" />
|
||||||
|
|||||||
Reference in New Issue
Block a user