dev → main: rule-arm review, moment usage by rule, no-subject auto-inject, copy check, S3 test wording #215

Merged
bvandeusen merged 15 commits from dev into main 2026-10-22 20:26:40 -04:00
4 changed files with 64 additions and 3 deletions
Showing only changes of commit 67d3c68fbf - Show all commits
+10
View File
@@ -44,6 +44,14 @@ export interface RemovedDefault {
created_at: string | null;
}
export interface MomentRuleUsage {
rule_id: number;
/** Null for a rule deleted since it was delivered. */
title: string | null;
delivered: number;
opened: boolean;
}
export interface MomentUsage {
/** How many times a mounted rule arrived at this moment. */
delivered: number;
@@ -53,6 +61,8 @@ export interface MomentUsage {
* the door, not the line that prompted it. */
opened: number;
last_delivered_at: string | null;
/** Each rule delivered here, most delivered first. */
by_rule: MomentRuleUsage[];
}
export interface MomentsPayload {
@@ -0,0 +1,37 @@
<script setup lang="ts">
import type { MomentRuleUsage } from "@/api/moments";
/**
* Which rules a moment delivered, and whether an agent went on to read each.
* The moment's own counts cannot say this once two rules are mounted there.
* Closed by default: the counts in the row above are what most visits want.
*/
defineProps<{ rules: MomentRuleUsage[] }>();
</script>
<template>
<details v-if="rules.length" class="moment-rules">
<summary>Rules delivered here</summary>
<ul class="delivered-list">
<li v-for="r in rules" :key="r.rule_id" class="delivered-row">
<span class="delivered-title">{{ r.title ?? `Rule ${r.rule_id} (deleted)` }}</span>
<span class="delivered-count">delivered {{ r.delivered }}</span>
<span :class="r.opened ? 'delivered-opened' : 'delivered-unopened'">
{{ r.opened ? "opened" : "not opened" }}
</span>
</li>
</ul>
</details>
</template>
<style scoped>
.moment-rules { margin-top: var(--fs-space-2); font-size: var(--fs-size-tiny); }
.moment-rules summary { cursor: pointer; color: var(--fs-text-tertiary); }
.delivered-list { list-style: none; margin: var(--fs-space-1) 0 0; padding: 0; display: flex; flex-direction: column; gap: var(--fs-space-1); }
.delivered-row { display: flex; flex-wrap: wrap; align-items: baseline; gap: var(--fs-space-2); font-variant-numeric: tabular-nums; }
.delivered-title { color: var(--fs-text-primary); font-size: var(--fs-size-body-sm); }
.delivered-count { color: var(--fs-text-secondary); }
.delivered-opened { color: var(--fs-text-secondary); }
/* Not read is a fact about this window, not a fault: quiet, like "nothing mounted". */
.delivered-unopened { color: var(--fs-text-tertiary); font-style: italic; }
</style>
@@ -2,6 +2,7 @@
import { computed, onMounted, ref } from "vue";
import { useMomentsStore } from "@/stores/moments";
import MomentProposals from "@/components/MomentProposals.vue";
import MomentRuleUsage from "@/components/MomentRuleUsage.vue";
import type { MomentAction, MomentUsage } from "@/api/moments";
import { fmtDate } from "@/utils/dateFormat";
@@ -167,6 +168,7 @@ onMounted(() => store.load(true));
>×</button>
</span>
</div>
<MomentRuleUsage v-if="usage(m.name)" :rules="usage(m.name)!.by_rule" />
</li>
</ul>
@@ -186,6 +188,7 @@ onMounted(() => store.load(true));
</template>
</span>
</div>
<MomentRuleUsage v-if="usage(name)" :rules="usage(name)!.by_rule" />
</li>
</ul>
</template>
+14 -3
View File
@@ -238,9 +238,10 @@ async def test_the_delivery_lookup_answers_by_home(world):
async def test_moment_usage_counts_deliveries_and_the_agent_opens_that_followed(world):
"""The per-moment readout, on real SQL: an EXISTS inside a
COUNT(CASE …) is a shape no mock can vouch for, and the block's own guard
would turn a database refusal into a quiet `moment_usage_failed`."""
"""The per-moment readout, on real SQL: a correlated EXISTS selected as a
column beside an outer join is a shape no mock can vouch for, and the
block's own guard would turn a database refusal into a quiet
`moment_usage_failed`."""
from datetime import datetime, timedelta, timezone
from sqlalchemy import delete
@@ -289,3 +290,13 @@ async def test_moment_usage_counts_deliveries_and_the_agent_opens_that_followed(
report = block["by_moment"]["reply.report"]
assert (report["delivered"], report["rules"], report["opened"]) == (1, 1, 0)
assert report["last_delivered_at"]
# Two rules share work.finish: which one arrived, and which was opened.
assert finish["by_rule"] == [
{"rule_id": rule.id, "title": rule.title, "delivered": 2, "opened": True},
{"rule_id": other.id, "title": "Close the task with the push",
"delivered": 1, "opened": False},
]
assert report["by_rule"] == [
{"rule_id": rule.id, "title": rule.title, "delivered": 1, "opened": False},
]