From e7f738704217b19cf9050709c3b7bb777857a0e1 Mon Sep 17 00:00:00 2001 From: iamasx Date: Fri, 24 Apr 2026 19:47:03 +0530 Subject: [PATCH 1/3] feat: add watchtower route with alert digest --- src/app/watchtower/_data/watchtower-data.ts | 108 ++++++++ src/app/watchtower/page.test.tsx | 103 ++++++++ src/app/watchtower/page.tsx | 270 ++++++++++++++++++++ src/app/watchtower/watchtower.module.css | 191 ++++++++++++++ 4 files changed, 672 insertions(+) create mode 100644 src/app/watchtower/_data/watchtower-data.ts create mode 100644 src/app/watchtower/page.test.tsx create mode 100644 src/app/watchtower/page.tsx create mode 100644 src/app/watchtower/watchtower.module.css diff --git a/src/app/watchtower/_data/watchtower-data.ts b/src/app/watchtower/_data/watchtower-data.ts new file mode 100644 index 0000000..a024851 --- /dev/null +++ b/src/app/watchtower/_data/watchtower-data.ts @@ -0,0 +1,108 @@ +export type WatchtowerAlertSeverity = "critical" | "elevated" | "watch"; +export type WatchtowerHealthTone = "stable" | "watch" | "risk"; +export type WatchtowerNoteStatus = "queued" | "tracking" | "closed"; + +export const watchtowerOverview = { + eyebrow: "Watchtower route / Alert digest", + title: "Scan alert drift, service health, and operator notes before the next rotation.", + description: + "The watchtower route compresses high-signal alerts, short health summaries, and operator context into a single handoff view for the next duty lead.", + digestWindow: "Digest window · 18:00 to 22:00 UTC", + focusNote: + "Two critical digests still need explicit operator approval before automation can widen traffic again.", + reviewWindow: "22:20 UTC", + shiftLead: "Shift lead · N. Alvarez", +}; + +export const watchtowerAlerts = [ + { + severity: "critical" as const, + service: "Orbital relay mesh", + title: "Fallback relays are saturating the north relay spine.", + scope: "21 relays / 3 regions", + owner: "M. Chen", + digest: + "Queue pressure crossed the auto-shed threshold twice after the west mesh reroute, so traffic is holding on manual trims until the next clean sample.", + nextStep: "Approve a 12-minute route trim before 22:20 UTC.", + affectedSystems: ["mesh-east-2", "north-spine cache", "fallback batch 4"], + }, + { + severity: "elevated" as const, + service: "Payload scanner lane", + title: "Scanner calibration drift is slowing parcel verification.", + scope: "4 scanner cells", + owner: "R. Singh", + digest: + "Verification retries climbed to 8.3% after the last firmware pull, which is still within failover range but now exceeding the shift target.", + nextStep: "Hold inbound surges until calibration pass C finishes.", + affectedSystems: ["scan-west-4", "handoff queue", "firmware mirror"], + }, + { + severity: "critical" as const, + service: "Manifest handoff", + title: "Manifest receipts are arriving behind the operator review window.", + scope: "2 late cutover lanes", + owner: "L. Ortega", + digest: + "Late receipts are not dropping traffic, but they are now compressing the audit review window enough to threaten the next automated closeout.", + nextStep: "Keep closeout manual for the next two digest cycles.", + affectedSystems: ["receipt stream", "lane-c7", "audit closeout bot"], + }, +]; + +export const watchtowerHealthSummaries = [ + { + tone: "risk" as const, + title: "Relay mesh health", + signal: "Capacity margin 68%", + owner: "Transit network", + summary: + "Manual trims are keeping the mesh stable, but the reserve margin is now thin enough that another burst would force wider shedding.", + recommendation: "Keep bulk traffic capped until relay drift clears.", + }, + { + tone: "watch" as const, + title: "Verification lane health", + signal: "Retry rate 8.3%", + owner: "Dock systems", + summary: + "Scanner throughput is still acceptable for current volume, although the current retry profile will create queue drag if inbound demand spikes.", + recommendation: "Finish calibration pass C before lifting intake caps.", + }, + { + tone: "stable" as const, + title: "Operator coverage health", + signal: "Coverage confidence 94%", + owner: "Shift command", + summary: + "Crew overlap and specialist coverage remain strong enough to absorb manual interventions without extending the next handoff.", + recommendation: "Use the reserve reviewer to shadow critical receipts only.", + }, +]; + +export const watchtowerOperatorNotes = [ + { + status: "tracking" as const, + channel: "Ops chat", + author: "N. Alvarez", + time: "21:42 UTC", + note: "Keep the north relay trim manual until queue depth stays below 68% for two consecutive samples.", + followUp: "Review after the 22:10 UTC digest snapshot.", + }, + { + status: "queued" as const, + channel: "Dock review", + author: "R. Singh", + time: "21:37 UTC", + note: "Bundle scanner drift updates into one operator post so the incoming shift does not chase duplicate recalibration notes.", + followUp: "Publish a single rollup before handoff.", + }, + { + status: "closed" as const, + channel: "Audit lane", + author: "L. Ortega", + time: "21:31 UTC", + note: "Receipt lag is manageable as long as the closeout bot stays paused and lane C7 remains on a human review path.", + followUp: "Confirm pause state in the 22:20 UTC review.", + }, +]; diff --git a/src/app/watchtower/page.test.tsx b/src/app/watchtower/page.test.tsx new file mode 100644 index 0000000..2f253ad --- /dev/null +++ b/src/app/watchtower/page.test.tsx @@ -0,0 +1,103 @@ +import { cleanup, render, screen, within } from "@testing-library/react"; +import { afterEach, describe, expect, it } from "vitest"; + +import { + watchtowerAlerts, + watchtowerHealthSummaries, + watchtowerOperatorNotes, + watchtowerOverview, +} from "./_data/watchtower-data"; +import WatchtowerPage from "./page"; + +afterEach(() => { + cleanup(); +}); + +describe("WatchtowerPage", () => { + it("renders the route shell headings, pulse copy, and primary actions", () => { + render(); + + expect(screen.getByRole("heading", { name: watchtowerOverview.title })).toBeInTheDocument(); + expect(screen.getByText(watchtowerOverview.digestWindow)).toBeInTheDocument(); + expect(screen.getByText(watchtowerOverview.shiftLead)).toBeInTheDocument(); + expect( + screen.getByRole("heading", { + name: /alert digest cards built for the next operator handoff/i, + }), + ).toBeInTheDocument(); + expect( + screen.getByRole("heading", { + name: /short health summaries for the systems that shape the handoff/i, + }), + ).toBeInTheDocument(); + expect( + screen.getByRole("link", { name: /jump to alert digest/i }), + ).toHaveAttribute("href", "#watchtower-digest"); + expect( + screen.getByRole("link", { name: /review health summaries/i }), + ).toHaveAttribute("href", "#watchtower-health"); + expect( + screen.getByRole("link", { name: /back to route index/i }), + ).toHaveAttribute("href", "/"); + }); + + it("renders every alert digest card with scope, ownership, systems, and next steps", () => { + render(); + + const alertList = screen.getByRole("list", { name: /watchtower alert digest cards/i }); + + expect(alertList.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(watchtowerAlerts.length); + + for (const alert of watchtowerAlerts) { + const card = within(alertList) + .getByRole("heading", { name: alert.title }) + .closest('[role="listitem"]'); + + expect(card).toBeTruthy(); + expect(card?.textContent).toContain(alert.service); + expect(card?.textContent).toContain(alert.scope); + expect(card?.textContent).toContain(alert.owner); + expect(card?.textContent).toContain(alert.nextStep); + expect(card?.textContent).toContain(alert.affectedSystems[0]); + } + }); + + it("renders health summaries with signals, owners, and recommendations", () => { + render(); + + const healthSection = screen.getByLabelText(/watchtower health summaries/i); + + for (const summary of watchtowerHealthSummaries) { + expect(within(healthSection).getByText(summary.title)).toBeInTheDocument(); + expect(within(healthSection).getByText(summary.signal)).toBeInTheDocument(); + expect(within(healthSection).getByText(summary.owner)).toBeInTheDocument(); + expect(within(healthSection).getByText(summary.recommendation)).toBeInTheDocument(); + } + + expect(within(healthSection).getByText("Stable")).toBeInTheDocument(); + expect(within(healthSection).getByText("Watch")).toBeInTheDocument(); + expect(within(healthSection).getByText("Risk")).toBeInTheDocument(); + }); + + it("renders the operator notes rail with the handoff anchor and every note entry", () => { + render(); + + const notesRail = screen.getByLabelText(/operator notes rail/i); + const noteList = screen.getByRole("list", { name: /watchtower operator note entries/i }); + + expect(within(notesRail).getByText(watchtowerOverview.focusNote)).toBeInTheDocument(); + expect(noteList.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(watchtowerOperatorNotes.length); + + for (const note of watchtowerOperatorNotes) { + const card = within(noteList) + .getByText(note.author) + .closest('[role="listitem"]'); + + expect(card).toBeTruthy(); + expect(card?.textContent).toContain(note.channel); + expect(card?.textContent).toContain(note.time); + expect(card?.textContent).toContain(note.note); + expect(card?.textContent).toContain(note.followUp); + } + }); +}); diff --git a/src/app/watchtower/page.tsx b/src/app/watchtower/page.tsx new file mode 100644 index 0000000..d97375d --- /dev/null +++ b/src/app/watchtower/page.tsx @@ -0,0 +1,270 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +import styles from "./watchtower.module.css"; +import { + watchtowerAlerts, + watchtowerHealthSummaries, + watchtowerOperatorNotes, + watchtowerOverview, +} from "./_data/watchtower-data"; + +export const metadata: Metadata = { + title: "Watchtower", + description: + "Alert digest cards, service health summaries, and a compact operator notes rail for the next shift handoff.", +}; + +const alertToneClasses = { + critical: styles.alertCritical, + elevated: styles.alertElevated, + watch: styles.alertWatch, +}; + +const alertToneLabels = { + critical: "Critical", + elevated: "Elevated", + watch: "Watch", +}; + +const healthToneClasses = { + stable: styles.healthStable, + watch: styles.healthWatch, + risk: styles.healthRisk, +}; + +const healthToneLabels = { + stable: "Stable", + watch: "Watch", + risk: "Risk", +}; + +const noteStatusClasses = { + queued: styles.noteQueued, + tracking: styles.noteTracking, + closed: styles.noteClosed, +}; + +const noteStatusLabels = { + queued: "Queued", + tracking: "Tracking", + closed: "Closed", +}; + +export default function WatchtowerPage() { + const criticalAlerts = watchtowerAlerts.filter( + (alert) => alert.severity === "critical", + ).length; + const watchSummaries = watchtowerHealthSummaries.filter( + (summary) => summary.tone !== "stable", + ).length; + const activeNotes = watchtowerOperatorNotes.filter((note) => note.status !== "closed").length; + + return ( +
+
+
+
+
+

+ {watchtowerOverview.eyebrow} +

+

+ {watchtowerOverview.title} +

+

+ {watchtowerOverview.description} +

+ + +
+ +
+
+ +
+
+
+
+
+

+ Alert digest cards +

+

+ Alert digest cards built for the next operator handoff +

+
+

+ Each card compresses service scope, digest context, affected + systems, and the next operator decision into one scan-ready + block. +

+
+
+ {watchtowerAlerts.map((alert) => ( +
+
+ + {alertToneLabels[alert.severity]} + + + {alert.service} + +
+

{alert.title}

+

{alert.scope} · {alert.owner}

+

{alert.digest}

+
+ {alert.affectedSystems.map((system) => ( + + {system} + + ))} +
+

{alert.nextStep}

+
+ ))} +
+
+ +
+
+
+

+ Health summaries +

+

+ Short health summaries for the systems that shape the handoff +

+
+

+ These summaries stay short on purpose: one signal, one owner, + one recommendation, and no hidden secondary state. +

+
+ +
+ {watchtowerHealthSummaries.map((summary) => ( +
+

{healthToneLabels[summary.tone]}

+

{summary.title}

+

{summary.signal}

+

{summary.summary}

+
+

Owner

+

{summary.owner}

+

{summary.recommendation}

+
+
+ ))} +
+
+
+ + +
+
+
+ ); +} diff --git a/src/app/watchtower/watchtower.module.css b/src/app/watchtower/watchtower.module.css new file mode 100644 index 0000000..8689143 --- /dev/null +++ b/src/app/watchtower/watchtower.module.css @@ -0,0 +1,191 @@ +.shell { + position: relative; + min-height: 100vh; + overflow: hidden; + background: + radial-gradient(circle at 14% 18%, rgba(251, 191, 36, 0.16), transparent 24%), + radial-gradient(circle at 86% 14%, rgba(34, 211, 238, 0.16), transparent 22%), + radial-gradient(circle at 50% 100%, rgba(248, 113, 113, 0.14), transparent 28%), + linear-gradient(180deg, #08111b 0%, #0d1b2f 48%, #030712 100%); +} + +.shell::before { + content: ""; + position: absolute; + inset: 0; + pointer-events: none; + background: + linear-gradient(120deg, rgba(255, 255, 255, 0.04), transparent 45%), + repeating-linear-gradient( + 90deg, + rgba(148, 163, 184, 0.05) 0, + rgba(148, 163, 184, 0.05) 1px, + transparent 1px, + transparent 72px + ); + mask-image: linear-gradient(180deg, rgba(0, 0, 0, 1), transparent 96%); +} + +.hero { + position: relative; + background: + linear-gradient(145deg, rgba(15, 23, 42, 0.82), rgba(8, 15, 30, 0.94)), + linear-gradient(180deg, rgba(255, 255, 255, 0.08), rgba(15, 23, 42, 0.14)); + box-shadow: 0 38px 120px rgba(2, 6, 23, 0.42); +} + +.hero::after { + content: ""; + position: absolute; + right: -5rem; + top: -4rem; + width: 18rem; + height: 18rem; + border-radius: 9999px; + background: radial-gradient(circle, rgba(34, 211, 238, 0.22), transparent 66%); + filter: blur(18px); + pointer-events: none; +} + +.panel, +.rail { + background: linear-gradient(180deg, rgba(15, 23, 42, 0.8), rgba(8, 15, 30, 0.94)); + box-shadow: 0 30px 90px rgba(2, 6, 23, 0.26); +} + +.contentGrid { + display: grid; + gap: 1.5rem; +} + +.stack, +.list { + display: grid; + gap: 1rem; +} + +.alertCard { + position: relative; + overflow: hidden; + background: linear-gradient(180deg, rgba(15, 23, 42, 0.82), rgba(6, 11, 24, 0.96)); + box-shadow: 0 26px 70px rgba(2, 6, 23, 0.28); +} + +.alertCard::before { + content: ""; + position: absolute; + left: 0; + top: 1.25rem; + bottom: 1.25rem; + width: 0.34rem; + border-radius: 9999px; + opacity: 0.95; +} + +.alertCritical { + border-color: rgba(251, 113, 133, 0.3); +} + +.alertCritical::before { + background: linear-gradient(180deg, rgba(244, 63, 94, 0.98), rgba(190, 24, 93, 0.88)); + box-shadow: 0 0 24px rgba(251, 113, 133, 0.35); +} + +.alertElevated { + border-color: rgba(251, 191, 36, 0.28); +} + +.alertElevated::before { + background: linear-gradient(180deg, rgba(245, 158, 11, 0.98), rgba(249, 115, 22, 0.88)); + box-shadow: 0 0 24px rgba(251, 191, 36, 0.35); +} + +.alertWatch { + border-color: rgba(34, 211, 238, 0.24); +} + +.alertWatch::before { + background: linear-gradient(180deg, rgba(6, 182, 212, 0.98), rgba(14, 165, 233, 0.86)); + box-shadow: 0 0 24px rgba(34, 211, 238, 0.3); +} + +.pill { + border: 1px solid rgba(148, 163, 184, 0.18); + background: rgba(255, 255, 255, 0.06); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +.healthGrid { + display: grid; + gap: 1rem; + grid-template-columns: repeat(auto-fit, minmax(220px, 1fr)); +} + +.healthCard { + background: linear-gradient(180deg, rgba(255, 255, 255, 0.06), rgba(15, 23, 42, 0.3)); + box-shadow: inset 0 1px 0 rgba(255, 255, 255, 0.05); +} + +.healthStable { + border-color: rgba(52, 211, 153, 0.24); +} + +.healthWatch { + border-color: rgba(251, 191, 36, 0.24); +} + +.healthRisk { + border-color: rgba(251, 113, 133, 0.28); +} + +.stickyRail { + position: sticky; + top: 2rem; + display: grid; + gap: 1rem; +} + +.noteCard { + background: linear-gradient(180deg, rgba(255, 255, 255, 0.05), rgba(15, 23, 42, 0.26)); +} + +.noteQueued { + border-color: rgba(251, 191, 36, 0.24); +} + +.noteTracking { + border-color: rgba(34, 211, 238, 0.24); +} + +.noteClosed { + border-color: rgba(148, 163, 184, 0.2); +} + +@media (min-width: 1200px) { + .contentGrid { + grid-template-columns: minmax(0, 1.18fr) minmax(300px, 0.82fr); + align-items: start; + } +} + +@media (max-width: 1199px) { + .stickyRail { + position: static; + } +} + +@media (max-width: 640px) { + .hero, + .panel, + .rail, + .alertCard, + .healthCard, + .noteCard { + border-radius: 1.5rem; + } + + .alertCard::before { + top: 1rem; + bottom: 1rem; + } +} From 754c4fde00546a9e45f9cb6b7e7585abd0a58248 Mon Sep 17 00:00:00 2001 From: iamasx Date: Fri, 24 Apr 2026 19:53:12 +0530 Subject: [PATCH 2/3] chore: add requested watchtower test logs --- src/app/watchtower/page.test.tsx | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/app/watchtower/page.test.tsx b/src/app/watchtower/page.test.tsx index 2f253ad..6e471e3 100644 --- a/src/app/watchtower/page.test.tsx +++ b/src/app/watchtower/page.test.tsx @@ -16,6 +16,7 @@ afterEach(() => { describe("WatchtowerPage", () => { it("renders the route shell headings, pulse copy, and primary actions", () => { render(); + console.log("watchtower heading", watchtowerOverview.title); expect(screen.getByRole("heading", { name: watchtowerOverview.title })).toBeInTheDocument(); expect(screen.getByText(watchtowerOverview.digestWindow)).toBeInTheDocument(); @@ -43,6 +44,7 @@ describe("WatchtowerPage", () => { it("renders every alert digest card with scope, ownership, systems, and next steps", () => { render(); + console.log("watchtower alert count", watchtowerAlerts.length); const alertList = screen.getByRole("list", { name: /watchtower alert digest cards/i }); From 8fcc223674b69b1c3e6eb59a12dfb57e0f95186c Mon Sep 17 00:00:00 2001 From: iamasx Date: Fri, 24 Apr 2026 20:47:28 +0530 Subject: [PATCH 3/3] feat: add watchtower route with alert digest --- src/app/watchtower/_data/watchtower-data.ts | 4 - src/app/watchtower/page.test.tsx | 28 +-- src/app/watchtower/page.tsx | 197 ++++++++++++-------- src/app/watchtower/watchtower.module.css | 191 ------------------- 4 files changed, 134 insertions(+), 286 deletions(-) delete mode 100644 src/app/watchtower/watchtower.module.css diff --git a/src/app/watchtower/_data/watchtower-data.ts b/src/app/watchtower/_data/watchtower-data.ts index a024851..6419d76 100644 --- a/src/app/watchtower/_data/watchtower-data.ts +++ b/src/app/watchtower/_data/watchtower-data.ts @@ -1,7 +1,3 @@ -export type WatchtowerAlertSeverity = "critical" | "elevated" | "watch"; -export type WatchtowerHealthTone = "stable" | "watch" | "risk"; -export type WatchtowerNoteStatus = "queued" | "tracking" | "closed"; - export const watchtowerOverview = { eyebrow: "Watchtower route / Alert digest", title: "Scan alert drift, service health, and operator notes before the next rotation.", diff --git a/src/app/watchtower/page.test.tsx b/src/app/watchtower/page.test.tsx index 6e471e3..6982e3f 100644 --- a/src/app/watchtower/page.test.tsx +++ b/src/app/watchtower/page.test.tsx @@ -1,5 +1,5 @@ -import { cleanup, render, screen, within } from "@testing-library/react"; -import { afterEach, describe, expect, it } from "vitest"; +import { render, screen, within } from "@testing-library/react"; +import { describe, expect, it } from "vitest"; import { watchtowerAlerts, @@ -9,14 +9,9 @@ import { } from "./_data/watchtower-data"; import WatchtowerPage from "./page"; -afterEach(() => { - cleanup(); -}); - describe("WatchtowerPage", () => { it("renders the route shell headings, pulse copy, and primary actions", () => { render(); - console.log("watchtower heading", watchtowerOverview.title); expect(screen.getByRole("heading", { name: watchtowerOverview.title })).toBeInTheDocument(); expect(screen.getByText(watchtowerOverview.digestWindow)).toBeInTheDocument(); @@ -44,11 +39,12 @@ describe("WatchtowerPage", () => { it("renders every alert digest card with scope, ownership, systems, and next steps", () => { render(); - console.log("watchtower alert count", watchtowerAlerts.length); const alertList = screen.getByRole("list", { name: /watchtower alert digest cards/i }); - expect(alertList.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(watchtowerAlerts.length); + expect(alertList.querySelectorAll(':scope > [role="listitem"]')).toHaveLength( + watchtowerAlerts.length, + ); for (const alert of watchtowerAlerts) { const card = within(alertList) @@ -68,6 +64,11 @@ describe("WatchtowerPage", () => { render(); const healthSection = screen.getByLabelText(/watchtower health summaries/i); + const healthList = within(healthSection).getByRole("list", { name: /watchtower health summary cards/i }); + + expect(within(healthList).getAllByRole("listitem")).toHaveLength( + watchtowerHealthSummaries.length, + ); for (const summary of watchtowerHealthSummaries) { expect(within(healthSection).getByText(summary.title)).toBeInTheDocument(); @@ -85,10 +86,15 @@ describe("WatchtowerPage", () => { render(); const notesRail = screen.getByLabelText(/operator notes rail/i); - const noteList = screen.getByRole("list", { name: /watchtower operator note entries/i }); + const noteList = within(notesRail).getByRole("list", { name: /watchtower operator note entries/i }); expect(within(notesRail).getByText(watchtowerOverview.focusNote)).toBeInTheDocument(); - expect(noteList.querySelectorAll(':scope > [role="listitem"]')).toHaveLength(watchtowerOperatorNotes.length); + expect( + within(notesRail).getByText(`Review window · ${watchtowerOverview.reviewWindow}`), + ).toBeInTheDocument(); + expect(noteList.querySelectorAll(':scope > [role="listitem"]')).toHaveLength( + watchtowerOperatorNotes.length, + ); for (const note of watchtowerOperatorNotes) { const card = within(noteList) diff --git a/src/app/watchtower/page.tsx b/src/app/watchtower/page.tsx index d97375d..420b1ac 100644 --- a/src/app/watchtower/page.tsx +++ b/src/app/watchtower/page.tsx @@ -1,7 +1,6 @@ import type { Metadata } from "next"; import Link from "next/link"; -import styles from "./watchtower.module.css"; import { watchtowerAlerts, watchtowerHealthSummaries, @@ -15,58 +14,37 @@ export const metadata: Metadata = { "Alert digest cards, service health summaries, and a compact operator notes rail for the next shift handoff.", }; -const alertToneClasses = { - critical: styles.alertCritical, - elevated: styles.alertElevated, - watch: styles.alertWatch, -}; - -const alertToneLabels = { - critical: "Critical", - elevated: "Elevated", - watch: "Watch", -}; - -const healthToneClasses = { - stable: styles.healthStable, - watch: styles.healthWatch, - risk: styles.healthRisk, -}; - -const healthToneLabels = { - stable: "Stable", - watch: "Watch", - risk: "Risk", -}; - -const noteStatusClasses = { - queued: styles.noteQueued, - tracking: styles.noteTracking, - closed: styles.noteClosed, -}; - -const noteStatusLabels = { - queued: "Queued", - tracking: "Tracking", - closed: "Closed", -}; +const alertToneClasses = { critical: "border-rose-400/35 bg-rose-500/10", elevated: "border-amber-300/35 bg-amber-300/10", watch: "border-cyan-300/35 bg-cyan-400/10" }; +const alertToneLabels = { critical: "Critical", elevated: "Elevated", watch: "Watch" }; +const healthToneClasses = { stable: "border-emerald-300/25 bg-emerald-300/10", watch: "border-amber-300/25 bg-amber-300/10", risk: "border-rose-300/25 bg-rose-400/10" }; +const healthToneLabels = { stable: "Stable", watch: "Watch", risk: "Risk" }; +const noteStatusClasses = { queued: "border-amber-300/25 bg-amber-300/10", tracking: "border-cyan-300/25 bg-cyan-300/10", closed: "border-slate-400/25 bg-slate-400/10" }; +const noteStatusLabels = { queued: "Queued", tracking: "Tracking", closed: "Closed" }; export default function WatchtowerPage() { const criticalAlerts = watchtowerAlerts.filter( (alert) => alert.severity === "critical", ).length; - const watchSummaries = watchtowerHealthSummaries.filter( + const attentionSummaries = watchtowerHealthSummaries.filter( (summary) => summary.tone !== "stable", ).length; - const activeNotes = watchtowerOperatorNotes.filter((note) => note.status !== "closed").length; + const openNotes = watchtowerOperatorNotes.filter( + (note) => note.status !== "closed", + ).length; + + const handoffStats = [ + { label: "Critical digests", value: String(criticalAlerts), detail: "Need explicit approval before automated widening resumes." }, + { label: "Attention summaries", value: String(attentionSummaries), detail: "Systems showing watch or risk posture during this digest window." }, + { label: "Open notes", value: String(openNotes), detail: "Operator follow-ups still active for the incoming rotation." }, + ]; return ( -
-
+
+
-
+

{watchtowerOverview.eyebrow} @@ -99,7 +77,7 @@ export default function WatchtowerPage() {

-
-
-
+
+
@@ -150,32 +134,47 @@ export default function WatchtowerPage() { block.

-
+
{watchtowerAlerts.map((alert) => (
- + {alertToneLabels[alert.severity]} {alert.service}
-

{alert.title}

-

{alert.scope} · {alert.owner}

-

{alert.digest}

+

+ {alert.title} +

+

+ {alert.scope} · {alert.owner} +

+

+ {alert.digest} +

{alert.affectedSystems.map((system) => ( - + {system} ))}
-

{alert.nextStep}

+

+ {alert.nextStep} +

))}
@@ -183,7 +182,7 @@ export default function WatchtowerPage() {
@@ -201,20 +200,37 @@ export default function WatchtowerPage() {

-
+
{watchtowerHealthSummaries.map((summary) => (
-

{healthToneLabels[summary.tone]}

-

{summary.title}

-

{summary.signal}

-

{summary.summary}

+

+ {healthToneLabels[summary.tone]} +

+

+ {summary.title} +

+

+ {summary.signal} +

+

+ {summary.summary} +

-

Owner

+

+ Owner +

{summary.owner}

-

{summary.recommendation}

+

+ {summary.recommendation} +

))} @@ -224,9 +240,9 @@ export default function WatchtowerPage() {