mirror of
https://github.com/mvschwarz/openrig.git
synced 2026-11-11 17:32:22 -10:00
Compare commits
1
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
32ffc41937 |
No files matched your search
@@ -1,6 +1,7 @@
|
||||
import type { AttentionRead, AttentionItem } from "@openrig/daemon/attention";
|
||||
import type { Action, FleetSnapshot, ViewState } from "../types.js";
|
||||
import { listItem, wrapDetailLines, type ContentLine } from "../detail.js";
|
||||
import { displayTime } from "../time.js";
|
||||
|
||||
/** The bounded, confirmed-delivery or bridge-acceptance projection served by S02; no client classification. */
|
||||
export interface DeliveredHumanUpdates {
|
||||
@@ -11,6 +12,8 @@ export interface DeliveredHumanUpdates {
|
||||
truncated: boolean;
|
||||
}
|
||||
|
||||
const BRIDGE_ACCEPTED_SCOPE = " · accepted by bridge";
|
||||
|
||||
function isBridgeAcceptedUpdate(q: DeliveredHumanUpdates["items"][number]): boolean {
|
||||
return q.deliveryOutcome === "accepted" || q.deliveryReceipt.startsWith("relay-owner-notification-accepted");
|
||||
}
|
||||
@@ -38,13 +41,13 @@ export function composeHumanUpdates(attention: AttentionRead | null, updates: De
|
||||
const baseScope = projects.length === 1 ? `project ${projects[0]} (queue tag)` : "instance · project unknown";
|
||||
const item: AttentionItem = { id: `human-update:${q.qitemId}`, kind: "update", summary: q.summary || q.body.trim().split(/\r?\n/).find(Boolean) || (accepted ? "Accepted by bridge" : "Delivered update"),
|
||||
recipient: q.destinationSession, urgency: "update", unblocks: null, at: q.deliveredAt,
|
||||
scope: accepted ? `${baseScope} · accepted by bridge` : baseScope, project: null,
|
||||
scope: accepted ? `${baseScope}${BRIDGE_ACCEPTED_SCOPE}` : baseScope, project: null,
|
||||
source: `/api/queue/${encodeURIComponent(q.qitemId)}` };
|
||||
if (!read.items.some(current => current.id === `queue-update:${q.qitemId}`)) read.items.push(item);
|
||||
if (wanted === item.id && read.detail?.item.id !== item.id) {
|
||||
read.detailError = null;
|
||||
read.detail = { item, lines: [q.body, ...(q.humanDetail ? ["Supplemental detail:", q.humanDetail] : []),
|
||||
`From: ${q.sourceSession}`, accepted ? `Accepted by bridge: ${q.deliveredAt}` : `Delivered: ${q.deliveredAt}`, `Delivery receipt: ${q.deliveryReceipt}`, `Queue: ${q.qitemId}`, `Evidence: ${q.evidenceRef ?? "none recorded"}`],
|
||||
`From: ${q.sourceSession}`, `Delivery receipt: ${q.deliveryReceipt}`, `Queue: ${q.qitemId}`, `Evidence: ${q.evidenceRef ?? "none recorded"}`],
|
||||
files: q.evidenceRef?.startsWith("/") ? [{ label: "Update evidence", path: q.evidenceRef }] : [] };
|
||||
}
|
||||
}
|
||||
@@ -71,7 +74,10 @@ export function attentionLines(state: ViewState, snap: FleetSnapshot, width: num
|
||||
if (d.item.recipient) lines.push({ text: `To: ${d.item.recipient}` });
|
||||
if (d.item.id.startsWith("human-update:") || d.item.id.startsWith("queue-update:")) lines.push({ text: "No action needed" });
|
||||
if (d.item.unblocks) lines.push({ text: `Unblocks: ${d.item.unblocks}` });
|
||||
lines.push({ text: `Scope: ${d.item.scope}` }, { text: `Observed: ${d.item.at ?? "unknown"}` });
|
||||
// Times are drawn in the person's zone (ui.timezone), as the header's are; a delivered update's time is when it was
|
||||
// delivered or accepted by the bridge.
|
||||
const when = !d.item.id.startsWith("human-update:") ? "Observed" : d.item.scope.endsWith(BRIDGE_ACCEPTED_SCOPE) ? "Accepted by bridge" : "Delivered";
|
||||
lines.push({ text: `Scope: ${d.item.scope}` }, { text: `${when}: ${displayTime(d.item.at, state.timeZone)}` });
|
||||
lines.push(...questionLines(d.item).map(text => ({ text })));
|
||||
lines.push(...d.lines.map(text => ({ text })));
|
||||
for (const f of d.files) lines.push(listItem(f.label, { type: "attention-source", path: f.path }));
|
||||
@@ -124,10 +130,10 @@ export function attentionLines(state: ViewState, snap: FleetSnapshot, width: num
|
||||
if (i.humanQuestions?.length) lines.push({ text: ` Questions: ${i.humanQuestions.length}, ${unansweredCount(i)} unanswered` });
|
||||
if (i.id.startsWith("human-update:") || i.id.startsWith("queue-update:")) lines.push({ text: " No action needed" });
|
||||
if (i.unblocks) lines.push({ text: ` Unblocks: ${i.unblocks}` });
|
||||
lines.push({ text: ` ${i.scope} · ${i.at ?? "time unknown"}` });
|
||||
lines.push({ text: ` ${i.scope} · ${displayTime(i.at, state.timeZone)}` });
|
||||
}
|
||||
}
|
||||
lines.push({ text: "" }, { text: `Read at ${read.readAt}` }, ...read.sources.map(s => ({ text: `${s.source}: ${s.state} · ${s.detail}` })));
|
||||
lines.push({ text: "" }, { text: `Read at ${displayTime(read.readAt, state.timeZone)}` }, ...read.sources.map(s => ({ text: `${s.source}: ${s.state} · ${s.detail}` })));
|
||||
}
|
||||
lines.push({ text: "" }, { text: "Viewing is not approval. Each request's detail says whether it went to Slack and where to answer it." });
|
||||
return wrapDetailLines(lines, width);
|
||||
|
||||
@@ -50,6 +50,24 @@ it("renders an unsent local FYI without inventing an approval or a delivery rece
|
||||
expect(text).not.toContain("Delivery receipt:");
|
||||
});
|
||||
|
||||
it("draws Feed times in the person's zone, as the header does, and redraws them when the zone changes", () => {
|
||||
const item = current(), attention = read([item]);
|
||||
attention.detail = { item, lines: ["Current queue body"], files: [] };
|
||||
const merged = composeHumanUpdates(attention, delivered("other"), item.id)!;
|
||||
const draw = (timeZone: string, attentionOpen: string | null) => attentionLines(
|
||||
{ ...createViewState({ instanceId: "owned-fixture", timeZone }).get(), section: "needs" as const, attentionOpen },
|
||||
{ ...emptySnapshot(), attentionRead: merged }, 140).map(l => l.text).join("\n");
|
||||
const london = draw("Europe/London", null);
|
||||
expect(london).toContain("instance · 2026-10-01 13:00:00 GMT+1");
|
||||
expect(london).toContain("instance · project unknown · 2026-10-08 13:00:00 GMT+1");
|
||||
expect(london).toContain("Read at 2026-10-09 13:00:00 GMT+1");
|
||||
expect(london).not.toMatch(/\d{4}-\d{2}-\d{2}T\d{2}:/);
|
||||
// The same read, drawn after the zone setting arrives: nothing is re-read, and nothing keeps the earlier zone.
|
||||
const angeles = draw("America/Los_Angeles", item.id);
|
||||
expect(angeles).toContain("Observed: 2026-10-01 05:00:00 PDT");
|
||||
expect(angeles).not.toMatch(/\d{4}-\d{2}-\d{2}T\d{2}:|GMT\+1/);
|
||||
});
|
||||
|
||||
it("does not retain delivered history beyond a successful empty receipt window when only current updates fail", () => {
|
||||
const prior = composeHumanUpdates(read([]), delivered())!;
|
||||
const failed = read([]);
|
||||
@@ -155,8 +173,9 @@ it("labels relay bridge acceptance as accepted by the bridge rather than Deliver
|
||||
],
|
||||
};
|
||||
const openedBridge = composeHumanUpdates(read([]), bridgeUpdates, "human-update:bridge-fyi")!;
|
||||
const bridgeDetailLines = openedBridge.detail?.lines.join("\n") ?? "";
|
||||
expect(bridgeDetailLines).toContain("Accepted by bridge: 2026-10-08T14:00:00Z");
|
||||
const detailState = (id: string) => ({ ...createViewState({ instanceId: "owned-fixture", timeZone: "UTC" }).get(), section: "needs" as const, attentionOpen: id });
|
||||
const bridgeDetailLines = attentionLines(detailState("human-update:bridge-fyi"), { ...emptySnapshot(), attentionRead: openedBridge }, 140).map(l => l.text).join("\n");
|
||||
expect(bridgeDetailLines).toContain("Accepted by bridge: 2026-10-08 14:00:00 UTC");
|
||||
expect(bridgeDetailLines).not.toContain("Delivered:");
|
||||
expect(openedBridge.sources.find(s => s.source === "delivered updates")?.detail).toContain("accepted by the bridge");
|
||||
expect(openedBridge.sources.find(s => s.source === "delivered updates")?.detail).not.toContain("confirmed delivered updates");
|
||||
@@ -168,8 +187,8 @@ it("labels relay bridge acceptance as accepted by the bridge rather than Deliver
|
||||
|
||||
// Slack delivered wording remains unchanged
|
||||
const openedSlack = composeHumanUpdates(read([]), delivered("slack-fyi"), "human-update:slack-fyi")!;
|
||||
const slackDetailLines = openedSlack.detail?.lines.join("\n") ?? "";
|
||||
expect(slackDetailLines).toContain("Delivered: 2026-10-08T12:00:00Z");
|
||||
const slackDetailLines = attentionLines(detailState("human-update:slack-fyi"), { ...emptySnapshot(), attentionRead: openedSlack }, 140).map(l => l.text).join("\n");
|
||||
expect(slackDetailLines).toContain("Delivered: 2026-10-08 12:00:00 UTC");
|
||||
expect(slackDetailLines).not.toContain("Accepted by bridge:");
|
||||
expect(openedSlack.sources.find(s => s.source === "delivered updates")?.detail).toBe(
|
||||
"Latest 20 confirmed delivered updates; retained receipt window.",
|
||||
|
||||
Reference in new issue
Block a user