diff --git a/frontend/src/api/systems.ts b/frontend/src/api/systems.ts index 7d19ed1..3b75050 100644 --- a/frontend/src/api/systems.ts +++ b/frontend/src/api/systems.ts @@ -1,5 +1,6 @@ import { apiGet, apiPost, apiPatch, apiDelete } from "@/api/client"; import type { CanonicalMatch } from "@/api/canonicalSystems"; +import type { RecordUsage } from "@/types/usage"; export interface System { id: number; @@ -21,6 +22,13 @@ export interface System { */ path_patterns: string[]; open_issue_count: number; + /** + * How often this area's rulings were shown to a session because its files + * were touched (`surfaced_count`), and how often it was opened (#4769). + * Present on the list and detail reads; optional because a System returned + * by a create or update carries none. + */ + usage?: RecordUsage; created_at: string | null; updated_at: string | null; } diff --git a/frontend/src/components/SystemsSection.vue b/frontend/src/components/SystemsSection.vue index f69fffd..7171436 100644 --- a/frontend/src/components/SystemsSection.vue +++ b/frontend/src/components/SystemsSection.vue @@ -7,6 +7,7 @@ import { getProjectIssues } from "@/api/systems"; import type { System, TaskLike } from "@/api/systems"; import type { CanonicalMatch } from "@/api/canonicalSystems"; import { apiErrorMessage } from "@/api/client"; +import { fmtDate } from "@/utils/dateFormat"; import { Pencil, Trash2, Archive, ArchiveRestore } from "lucide-vue-next"; const props = defineProps<{ projectId: number }>(); @@ -71,6 +72,25 @@ function areaName(system: System): string | null { return canon.byId(system.canonical_id)?.name ?? null; } +/** How many times this area's rulings reached a session (#4769). Not the + * UsageBadge: its "N/M used" reads opens over showings, and rulings are + * delivered in full in the line, so an unopened System is the arm working, + * not dead weight. */ +function rulingsShown(system: System): number { + return system.usage?.surfaced_count ?? 0; +} + +function rulingsTitle(system: System): string { + const u = system.usage; + if (!u) return ""; + const last = u.last_surfaced_at ? ` Last shown ${fmtDate(u.last_surfaced_at)}.` : ""; + return ( + `This area's rulings were shown in full to a session ${u.surfaced_count}×, ` + + `when a command or edit touched its files.${last} ` + + `Opened with get_system ${u.pull_count}×.` + ); +} + async function load() { error.value = null; try { @@ -471,6 +491,11 @@ async function confirmDelete() { class="area-chip" :title="`Filed under the shared area “${areaName(system)}” — records and rules about this area line up across projects.`" >{{ areaName(system) }} + rulings shown {{ rulingsShown(system) }}×

{{ system.description }}