Pre-launch — Gluecron is in final validation. Public signups and git hosting for non-owner users open after launch review.
Commit9b07ca9unknown_key

feat(status): /status.svg shields badge + smoke tests

feat(status): /status.svg shields badge + smoke tests

Adds embeddable SVG status badge for READMEs and smoke tests for the
public /status, /status.svg, /robots.txt, /sitemap.xml routes.

https://claude.ai/code/session_017Do52tMX1P9jPTWXhEohXH
Claude committed on April 20, 2026Parent: 5618f9a
2 files changed+10009b07ca9de95aeb8358134f8ead5a4419c80460c7
2 changed files+100−0
Addedsrc/__tests__/status-seo.test.ts+59−0View fileUnifiedSplit
1/**
2 * Smoke tests for the public /status page, /status.svg badge, and SEO
3 * routes (/robots.txt + /sitemap.xml). These don't stub the DB — they
4 * tolerate either success (dev DB reachable) or a graceful error page
5 * so they work in the sandbox environment.
6 */
7
8import { test, expect } from "bun:test";
9import app from "../app";
10
11test("/status returns 200 with HTML body", async () => {
12 const res = await app.request("/status");
13 expect(res.status).toBe(200);
14 const body = await res.text();
15 expect(body).toContain("<html");
16 // Either the green or red headline should appear
17 expect(
18 body.includes("All systems operational") ||
19 body.includes("Service degraded")
20 ).toBe(true);
21});
22
23test("/status.svg returns an SVG badge", async () => {
24 const res = await app.request("/status.svg");
25 expect(res.status).toBe(200);
26 expect(res.headers.get("content-type")).toMatch(/image\/svg/);
27 const body = await res.text();
28 expect(body).toContain("<svg");
29 expect(body).toContain("</svg>");
30});
31
32test("/robots.txt returns 200 with crawler directives", async () => {
33 const res = await app.request("/robots.txt");
34 expect(res.status).toBe(200);
35 expect(res.headers.get("content-type")).toMatch(/text\/plain/);
36 const body = await res.text();
37 expect(body).toContain("User-agent:");
38 expect(body).toContain("Sitemap:");
39 expect(body).toContain("Disallow: /admin");
40});
41
42test("/sitemap.xml returns valid-looking XML", async () => {
43 const res = await app.request("/sitemap.xml");
44 expect(res.status).toBe(200);
45 expect(res.headers.get("content-type")).toMatch(/xml/);
46 const body = await res.text();
47 expect(body).toContain('<?xml');
48 expect(body).toContain("<urlset");
49 expect(body).toContain("<loc>");
50 expect(body).toContain("</urlset>");
51});
52
53test("sitemap includes the landing, status, explore URLs", async () => {
54 const res = await app.request("/sitemap.xml");
55 const body = await res.text();
56 expect(body).toMatch(/<loc>[^<]*\/<\/loc>/);
57 expect(body).toContain("/status");
58 expect(body).toContain("/explore");
59});
Modifiedsrc/routes/status.tsx+41−0View fileUnifiedSplit
280280 );
281281});
282282
283/**
284 * Shields-style status badge. Reads the latest autopilot tick + DB
285 * reachability and returns an SVG. Embed in READMEs with:
286 * ![status](https://your-host/status.svg)
287 */
288status.get("/status.svg", async (c) => {
289 let dbOk = false;
290 try {
291 await db.execute(sql`SELECT 1`);
292 dbOk = true;
293 } catch {
294 dbOk = false;
295 }
296 const tick = getLastTick();
297 const lastOk = tick ? tick.tasks.every((t) => t.ok) : true;
298 const overall = dbOk && lastOk;
299 const label = "gluecron";
300 const value = overall ? "operational" : "degraded";
301 const fill = overall ? "#2da44e" : "#cf222e";
302
303 const labelW = 70;
304 const valueW = overall ? 78 : 68;
305 const totalW = labelW + valueW;
306 const svg = `<svg xmlns="http://www.w3.org/2000/svg" width="${totalW}" height="20" role="img" aria-label="${label}: ${value}">
307 <linearGradient id="s" x2="0" y2="100%">
308 <stop offset="0" stop-color="#bbb" stop-opacity=".1"/>
309 <stop offset="1" stop-opacity=".1"/>
310 </linearGradient>
311 <rect width="${totalW}" height="20" rx="3" fill="#555"/>
312 <rect x="${labelW}" width="${valueW}" height="20" rx="3" fill="${fill}"/>
313 <rect width="${totalW}" height="20" rx="3" fill="url(#s)"/>
314 <g fill="#fff" text-anchor="middle" font-family="Verdana,Geneva,sans-serif" font-size="11">
315 <text x="${labelW / 2}" y="15">${label}</text>
316 <text x="${labelW + valueW / 2}" y="15">${value}</text>
317 </g>
318</svg>`;
319 c.header("Content-Type", "image/svg+xml; charset=utf-8");
320 c.header("Cache-Control", "no-cache, max-age=0");
321 return c.body(svg);
322});
323
283324export default status;
284325