Files
FabledGrimoire/frontend/src/stores/systems.ts
T
bvandeusenandClaude Opus 5.5 0a1bb68808
CI & Build / Python lint (push) Successful in 3s
CI & Build / Plugin hooks (push) Successful in 13s
CI & Build / integration (push) Successful in 50s
CI & Build / TypeScript typecheck (push) Successful in 52s
CI & Build / Python tests (push) Successful in 1m44s
CI & Build / Build & push image (push) Successful in 36s
feat(rulings): system_usage_events is read back — per-System counts and a telemetry block (#4769)
#4769 "Rulings are counted where someone will read them": milestone 444
step 4 wrote system_usage_events and nothing read it.

- retrieval_telemetry gains a `system_usage` block: surfacings and opens by
  source, distinct counts, and `by_system` naming the areas most shown.
  There is deliberately no pull-through ratio, because rulings travel in full
  in the line and opens are the exception.
- usage_for_systems (one GROUP BY) adds `usage` to the REST Systems list and
  detail, and to MCP get_system. MCP list_systems is unchanged.
- The Systems UI shows a "rulings shown N×" chip.
- rulings_pre_tool, rulings_write_path and mcp_get_system are now declared
  registry points; the registry guard covers their recorders.
- The Systems store merges a PATCH reply instead of replacing the row.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-03 08:49:20 -04:00

77 lines
2.3 KiB
TypeScript

import { ref } from "vue";
import { defineStore } from "pinia";
import * as api from "@/api/systems";
import type { System } from "@/api/systems";
import { useToastStore } from "@/stores/toast";
export const useSystemsStore = defineStore("systems", () => {
const systemsByProject = ref<Record<number, System[]>>({});
const loading = ref(false);
async function fetchSystems(projectId: number) {
loading.value = true;
try {
systemsByProject.value[projectId] = await api.listSystems(projectId);
} catch (e) {
useToastStore().show("Failed to load systems", "error");
throw e;
} finally {
loading.value = false;
}
}
async function createSystem(
projectId: number,
data: Parameters<typeof api.createSystem>[1],
) {
const system = await api.createSystem(projectId, data);
if (!systemsByProject.value[projectId]) systemsByProject.value[projectId] = [];
systemsByProject.value[projectId].push(system);
return system;
}
async function updateSystem(
projectId: number,
systemId: number,
data: Parameters<typeof api.updateSystem>[2],
) {
const system = await api.updateSystem(projectId, systemId, data);
const list = systemsByProject.value[projectId];
if (list) {
const idx = list.findIndex((s) => s.id === systemId);
// Merged, not replaced: the PATCH reply is the bare System, and the
// list's read-side decorations (issue count, rulings usage) would
// otherwise vanish from the row until the next reload.
if (idx >= 0) list[idx] = { ...list[idx], ...system };
}
return system;
}
async function archiveSystem(projectId: number, systemId: number) {
return updateSystem(projectId, systemId, { status: "archived" });
}
async function unarchiveSystem(projectId: number, systemId: number) {
return updateSystem(projectId, systemId, { status: "active" });
}
async function deleteSystem(projectId: number, systemId: number) {
await api.deleteSystem(projectId, systemId);
const list = systemsByProject.value[projectId];
if (list) {
systemsByProject.value[projectId] = list.filter((s) => s.id !== systemId);
}
}
return {
systemsByProject,
loading,
fetchSystems,
createSystem,
updateSystem,
archiveSystem,
unarchiveSystem,
deleteSystem,
};
});