Watch: snooze finished threads, name the files a file approval would edit, Settle on finished notifications, an instant launch from the saved feed, plain Loading states, and no more cancelled errors
Gateway: an approval push for a file change says which files it would edit
This commit is contained in:
1 parent
4dc4e57dac
commit
edc4b88990
35 files changed
+1162
-144
No files matched your search
+25
-5
@@ -4,7 +4,7 @@
|
||||
// a host loads its first snapshot, so a restart never replays old news.
|
||||
|
||||
import { threadPhase, type Phase } from "./awareness.ts";
|
||||
import type { PushCategory, PushPayload } from "./contract.ts";
|
||||
import type { PendingRequest, PushCategory, PushPayload } from "./contract.ts";
|
||||
import type { HostRow } from "./db.ts";
|
||||
import type { T3Host, ThreadChange } from "./t3/host.ts";
|
||||
import { trimText, type SummaryCache, type ThreadSummary } from "./views.ts";
|
||||
@@ -139,12 +139,32 @@ function category(phase: Phase): PushCategory {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* An approval push's body: the files a file-change approval would edit, as "Wants to edit
|
||||
* compose.yml +9 −6" or "Wants to edit 3 files: a.ts, b.ts, c.ts", or else the command it wants to
|
||||
* run. The watch's cards word it the same way.
|
||||
*/
|
||||
export function approvalBody(request: PendingRequest | null): string {
|
||||
const files = request?.kind === "file-change" ? request.files : [];
|
||||
const [file] = files;
|
||||
if (files.length === 1 && file) {
|
||||
const counts = [
|
||||
file.additions === null ? null : `+${file.additions}`,
|
||||
file.deletions === null ? null : `\u2212${file.deletions}`,
|
||||
].filter((count) => count !== null);
|
||||
return trimText(["Wants to edit", file.name, ...counts].join(" "), BODY_CHARS);
|
||||
}
|
||||
if (files.length > 1) {
|
||||
return trimText(`Wants to edit ${files.length} files: ${files.map((file) => file.name).join(", ")}`, BODY_CHARS);
|
||||
}
|
||||
const prompt = request?.prompt;
|
||||
return prompt ? `Wants to run: ${trimText(prompt, BODY_CHARS)}` : "Needs your approval.";
|
||||
}
|
||||
|
||||
function body(phase: Phase, lastError: string | null, summary: ThreadSummary | undefined): string {
|
||||
switch (phase) {
|
||||
case "waiting_for_approval": {
|
||||
const prompt = summary?.request?.prompt;
|
||||
return prompt ? `Wants to run: ${trimText(prompt, BODY_CHARS)}` : "Needs your approval.";
|
||||
}
|
||||
case "waiting_for_approval":
|
||||
return approvalBody(summary?.request ?? null);
|
||||
case "waiting_for_input": {
|
||||
const question = summary?.request?.questions[0]?.question;
|
||||
return question ? trimText(question, BODY_CHARS) : "Has a question for you.";
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, expect, it } from "bun:test";
|
||||
|
||||
import { Notifier, type Push } from "../src/notify.ts";
|
||||
import { approvalBody, Notifier, type Push } from "../src/notify.ts";
|
||||
import type { T3Host } from "../src/t3/host.ts";
|
||||
import type { ThreadShell } from "../src/t3/types.ts";
|
||||
import { SummaryCache } from "../src/views.ts";
|
||||
@@ -145,3 +145,34 @@ describe("question pushes", () => {
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe("approvalBody", () => {
|
||||
const approval = (kind: string, files: Array<{ name: string; additions: number | null; deletions: number | null }>) => ({
|
||||
id: "r-1",
|
||||
kind,
|
||||
prompt: "Edit: deploy/compose.yml",
|
||||
reason: null,
|
||||
options: [],
|
||||
questions: [],
|
||||
files: files.map((file) => ({ ...file, path: `deploy/${file.name}`, preview: [] })),
|
||||
});
|
||||
|
||||
it("names the file a file approval would edit, with counts only when known", () => {
|
||||
expect(approvalBody(approval("file-change", [{ name: "compose.yml", additions: 9, deletions: 6 }]))).toBe(
|
||||
"Wants to edit compose.yml +9 \u22126",
|
||||
);
|
||||
expect(approvalBody(approval("file-change", [{ name: "compose.yml", additions: null, deletions: null }]))).toBe(
|
||||
"Wants to edit compose.yml",
|
||||
);
|
||||
});
|
||||
|
||||
it("counts several files and lists their names", () => {
|
||||
const files = ["compose.yml", "Containerfile", "health.ts"].map((name) => ({ name, additions: null, deletions: null }));
|
||||
expect(approvalBody(approval("file-change", files))).toBe("Wants to edit 3 files: compose.yml, Containerfile, health.ts");
|
||||
});
|
||||
|
||||
it("falls back to the prompt without files", () => {
|
||||
expect(approvalBody(approval("file-change", []))).toBe("Wants to run: Edit: deploy/compose.yml");
|
||||
expect(approvalBody(null)).toBe("Needs your approval.");
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user