Files
FabledGrimoire/frontend/src/components/rules/RuleSweepPane.vue
T
bvandeusenandClaude Opus 5.5 baf22179ef
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 14s
CI & Build / TypeScript typecheck (push) Successful in 53s
CI & Build / integration (push) Successful in 53s
CI & Build / Python tests (push) Successful in 1m34s
CI & Build / Build & push image (push) Successful in 34s
refactor(frontend): the sweep pane's column wrapper joins sweep-shared.css (#3207)
.sweep was the one rule still written identically in both sweep panes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-24 06:53:11 -04:00

126 lines
4.1 KiB
Vue

<script setup lang="ts">
/**
* The staleness sweep: rules that assert a FACT, oldest verification first.
*
* Cross-cutting by nature — a rule that has gone false does not care which
* rulebook it sits in — so this is its own pane rather than a filter on the
* per-topic rule list. That list can only ever show one topic of one
* rulebook, so filtering it would quietly under-report, which is the exact
* failure this surface exists to catch.
*/
import { onMounted, ref } from "vue";
import { useRulebooksStore } from "@/stores/rulebooks";
const emit = defineEmits<{ "open-rule": [id: number] }>();
const store = useRulebooksStore();
const neverOnly = ref(false);
const busyId = ref<number | null>(null);
function reload() {
return store.fetchRulesDue({
neverOnly: neverOnly.value || undefined,
});
}
async function verify(id: number, stillTrue: boolean) {
busyId.value = id;
try {
await store.verifyRule(id, stillTrue);
} finally {
busyId.value = null;
}
}
onMounted(reload);
</script>
<template>
<section class="pane sweep">
<header>
<h2>Due for verification</h2>
<p class="sweep-lede">
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.
</p>
</header>
<div class="sweep-filters">
<label class="sweep-filter">
<input v-model="neverOnly" type="checkbox" @change="reload" />
<span>Never checked only</span>
</label>
</div>
<p v-if="store.loading" class="sweep-state">Loading…</p>
<!-- An empty sweep is GOOD NEWS, and must not read like a broken page. -->
<p v-else-if="!store.rulesDue.length" class="sweep-state empty">
Nothing to check.
{{ neverOnly ? "No rule matches these filters." : "No rule carries a check yet — add one to a rule that asserts a fact." }}
</p>
<ol v-else class="sweep-rows">
<li v-for="r in store.rulesDue" :key="r.id" class="sweep-row">
<div class="sweep-row-head">
<button class="sweep-row-title" @click="emit('open-rule', r.id)">{{ r.title }}</button>
<span
v-if="!r.when_to_apply"
class="rule-chip rule-chip-inert"
title="No trigger, so nothing can retrieve it — this rule will never reach a session"
>never surfaces</span>
<span class="sweep-age" :class="{ unchecked: r.days_since_verified === null }">
{{ r.days_since_verified === null
? "never checked"
: `${r.days_since_verified}d ago` }}
</span>
</div>
<p class="statement">{{ r.statement }}</p>
<dl class="sweep-check">
<dt>Check</dt>
<dd><code>{{ r.verify_with }}</code></dd>
<template v-if="r.expires_when">
<dt>Ends when</dt>
<dd>{{ r.expires_when }}</dd>
</template>
</dl>
<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, false)">No longer true</button>
</div>
</li>
</ol>
<p v-if="store.rulesDue.length" class="sweep-footnote">
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
place here until you correct or retire it.
</p>
</section>
</template>
<style src="@/assets/rules-shared.css" />
<style scoped>
.statement { margin: 0.35rem 0 0; font-size: 0.88rem; color: var(--fs-text-secondary); }
.sweep-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;
}
.sweep-check code {
font-family: var(--fs-font-mono);
background: var(--fs-surface-code-inline);
border-radius: var(--fs-radius-sm);
padding: 0.05rem 0.3rem;
overflow-wrap: anywhere;
}
</style>