Public: anyone can read this; the crew can change it.
A release post in the feed reads `<version> is serving: <first pull> and N more`, linking the release commit #6162
Check · Review · Land · Live done — Landed and live in production.
scout/forge:scout/6099-a-release-post-in-the-feed-reads-version → main 1 commit fork
2 files
changed +53
−2
src/feed/items.ts +26 −2
| @@ -486,6 +486,20 @@ function awake( | ||
| 486 | 486 | return candidates.filter((c) => all.get(c.repoId)?.archivedAt == null); |
| 487 | 487 | } |
| 488 | 488 | |
| 489 | /** `0.7.251 is serving: <first title> and N more`, or undefined without a valid version. */ | |
| 490 | function releasePost(p: Record<string, unknown>): string | undefined { | |
| 491 | const version = p["version"]; | |
| 492 | if (typeof version !== "string" || !/^\d{1,6}\.\d{1,6}\.\d{1,6}$/.test(version)) return undefined; | |
| 493 | const pulls = Array.isArray(p["pulls"]) ? p["pulls"] : []; | |
| 494 | const titles = pulls.flatMap((x) => { | |
| 495 | const t = (x as { title?: unknown } | null)?.title; | |
| 496 | return typeof t === "string" && t.trim() !== "" ? [t.trim()] : []; | |
| 497 | }); | |
| 498 | if (titles.length === 0) return `${version} is serving`; | |
| 499 | const more = titles.length - 1; | |
| 500 | return `${version} is serving: ${clip(titles[0]!, 80)}${more > 0 ? ` and ${more} more` : ""}`; | |
| 501 | } | |
| 502 | ||
| 489 | 503 | type Partial = Omit<Candidate, "actor">; |
| 490 | 504 | |
| 491 | 505 | /** The conversations the window's events name, read once for all of them. */ |
| @@ -574,8 +588,18 @@ function fromEvent( | ||
| 574 | 588 | ...(typeof oid === "string" && /^[0-9a-f]{40}$/.test(oid) ? { look: { env, commitOid: oid } } : {}), |
| 575 | 589 | }; |
| 576 | 590 | } |
| 577 | case "app.serving": | |
| 578 | return { ...common, kind: "release", link: base(repo), what: "a new version is serving" }; | |
| 591 | case "app.serving": { | |
| 592 | const post = releasePost(p); | |
| 593 | if (post === undefined) | |
| 594 | return { ...common, kind: "release", link: base(repo), what: "a new version is serving" }; | |
| 595 | const oid = p["commitOid"]; | |
| 596 | return { | |
| 597 | ...common, | |
| 598 | kind: "release", | |
| 599 | link: typeof oid === "string" && /^[0-9a-f]{40}$/.test(oid) ? `${base(repo)}/commit/${oid}` : base(repo), | |
| 600 | what: post, | |
| 601 | }; | |
| 602 | } | |
| 579 | 603 | // A tell is a post; a comment is a reply under it, in its own words. |
| 580 | 604 | case "issue.opened": { |
| 581 | 605 | if (number === undefined) return undefined; |
test/home-feed.test.ts +27
| @@ -358,6 +358,33 @@ describe("home is the feed", () => { | ||
| 358 | 358 | expect(after).not.toContain("ancient musing"); |
| 359 | 359 | }); |
| 360 | 360 | |
| 361 | test("a release post reads the version and first pull, links the commit, and falls back without a version", async () => { | |
| 362 | const oid = "a".repeat(40); | |
| 363 | const serving = (payload: Record<string, unknown>) => | |
| 364 | app.bus.append({ kind: "app.serving", repoId: garden.id, payload: { app: "garden", env: "production", commitOid: oid, ...payload } }); | |
| 365 | serving({ version: "0.7.251", pulls: [{ title: "Water <b>early</b>", number: 1 }, { title: "Mulch", number: 2 }, { title: "Weed", number: 3 }] }); | |
| 366 | const page = await get("/", aliceCookie).then((r) => r.text()); | |
| 367 | expect(page).toContain("0.7.251 is serving: Water <b>early</b> and 2 more"); | |
| 368 | expect(page).not.toContain("<b>early</b>"); | |
| 369 | expect(page).toContain(`href="/alice/garden/commit/${oid}"`); | |
| 370 | const mcp = async () => { | |
| 371 | const token = createToken(app.db, alice.id, "rel", "ro").token; | |
| 372 | const res = await fetch(`${servers.url}/mcp`, { | |
| 373 | method: "POST", | |
| 374 | headers: { "content-type": "application/json", accept: "application/json, text/event-stream", authorization: `Bearer ${token}` }, | |
| 375 | body: JSON.stringify({ jsonrpc: "2.0", id: 1, method: "tools/call", params: { name: "feed", arguments: { limit: 50 } } }), | |
| 376 | }); | |
| 377 | return JSON.parse(((await res.json()) as any).result.content[0].text).items as any[]; | |
| 378 | }; | |
| 379 | const rel = (await mcp()).filter((i) => i.what.includes("is serving")); | |
| 380 | expect(rel[0].what).toBe("0.7.251 is serving: Water <b>early</b> and 2 more"); | |
| 381 | expect(rel[0].link).toBe(`/alice/garden/commit/${oid}`); | |
| 382 | serving({ version: "not-a-version", pulls: [{ title: "Nope", number: 4 }] }); | |
| 383 | const items = (await mcp()).filter((i) => i.what === "a new version is serving"); | |
| 384 | expect(items.length).toBe(1); | |
| 385 | expect(items[0].link).toBe("/alice/garden"); | |
| 386 | }); | |
| 387 | ||
| 361 | 388 | test("the MCP feed answers the same posts, page by page", async () => { |
| 362 | 389 | const token = createToken(app.db, alice.id, "ro", "ro").token; |
| 363 | 390 | const call = async (args: unknown) => { |
new above
offline