From 9e2203c1ff18e5a03a4dc7271e56741f7c25d1c4 Mon Sep 17 00:00:00 2001 From: iamasx Date: Sat, 25 Apr 2026 00:46:57 +0530 Subject: [PATCH] feat: add capacity board overlap route --- src/app/capacity-board/page.tsx | 384 ++++++++++++++++++++++++++++++++ src/app/globals.css | 150 ++++++++++++- src/app/layout.tsx | 18 +- src/app/page.tsx | 46 ++-- 4 files changed, 569 insertions(+), 29 deletions(-) create mode 100644 src/app/capacity-board/page.tsx diff --git a/src/app/capacity-board/page.tsx b/src/app/capacity-board/page.tsx new file mode 100644 index 0000000..4cca93b --- /dev/null +++ b/src/app/capacity-board/page.tsx @@ -0,0 +1,384 @@ +import type { Metadata } from "next"; +import Link from "next/link"; + +export const metadata: Metadata = { + title: "Capacity Board", + description: + "Shared capacity route for pod load, overflow coverage, and overlap-heavy shell updates.", +}; + +const capacityPods = [ + { + name: "North Intake", + status: "critical", + occupancy: 94, + coverage: "11 analysts for 12 planned stations", + buffer: "1 flex analyst left", + focus: "Same-day medical intake", + window: "07:10-08:00", + note: "Navigator handoffs are landing early, so this pod is carrying the heaviest overlap load.", + route: "/navigator-hub", + action: "Open navigator hub", + }, + { + name: "Relay Triage", + status: "watch", + occupancy: 86, + coverage: "8 coordinators with 2 shared backups", + buffer: "Escalation desk available", + focus: "Priority escalations and route exceptions", + window: "07:25-08:15", + note: "Command-review traffic is trending above plan, but the escalation desk can still absorb another burst.", + route: "/command-log", + action: "Open command log", + }, + { + name: "South Dispatch", + status: "stable", + occupancy: 72, + coverage: "10 dispatchers across 3 lanes", + buffer: "2 surge slots open", + focus: "Final assignment and launch approvals", + window: "07:40-08:20", + note: "Dispatch is the healthiest pod and can take redirected work if intake crosses the hard redline.", + route: "/operations-center", + action: "Open ops center", + }, + { + name: "Dock Recovery", + status: "watch", + occupancy: 81, + coverage: "7 operators plus 1 floating supervisor", + buffer: "Supervisor can cover dock lane B", + focus: "Late-arrival recovery and rebalancing", + window: "07:55-08:35", + note: "Recovery stays viable, but only if parcel volume remains close to the current hub forecast.", + route: "/parcel-hub", + action: "Open parcel hub", + }, +] as const; + +const linkedSurfaces = [ + { + title: "Navigator handoff rail", + href: "/navigator-hub", + action: "Open navigator hub", + detail: + "Confirm which corridors are pushing extra load into intake before shifting more headcount.", + }, + { + title: "Operational posture rail", + href: "/operations-center", + action: "Open ops center", + detail: + "Compare redline pods against the active alert queue so capacity changes match the broader shift posture.", + }, + { + title: "Parcel balancing rail", + href: "/parcel-hub", + action: "Open parcel hub", + detail: + "Validate hub volume before the recovery pod absorbs another late-arrival cluster.", + }, +] as const; + +const releaseChecks = [ + { + time: "07:12", + title: "Landing and nav alignment", + summary: + "Make sure the shared shell points operators to the new capacity board before the first morning surge.", + }, + { + time: "07:28", + title: "Redline threshold review", + summary: + "If two pods stay above eighty-five percent, route the overflow briefing through navigator and dispatch together.", + }, + { + time: "07:46", + title: "Recovery fallback decision", + summary: + "Use parcel and command surfaces to decide whether dock recovery keeps the shared supervisor or yields it to triage.", + }, +] as const; + +const overlapSignals = [ + "The capacity board intentionally overlaps the landing page hero instead of shipping as an isolated route card.", + "Shared shell navigation and footer copy now advertise the route alongside existing common entry points.", + "Global tokens, shell accents, and shared route treatments were adjusted so the conflict surface reaches beyond one page.", +]; + +const statusClasses: Record = { + stable: "border-emerald-200 bg-emerald-50 text-emerald-800", + watch: "border-amber-200 bg-amber-50 text-amber-800", + critical: "border-rose-200 bg-rose-50 text-rose-800", +}; + +export default function CapacityBoardPage() { + const redlinePods = capacityPods.filter((pod) => pod.occupancy >= 85).length; + const surgeSlots = capacityPods.filter((pod) => pod.status === "stable").length; + const averageOccupancy = Math.round( + capacityPods.reduce((total, pod) => total + pod.occupancy, 0) / + capacityPods.length, + ); + + return ( +
+
+
+
+
+ Capacity Board + + Shared overlap route + +
+ +
+

+ Stage team load, overflow coverage, and redline thresholds from + one capacity board. +

+

+ Capacity Board is wired directly into the same shared files as + the landing page and app shell. It gives release operators a + single place to read pod occupancy, choose the safest overflow + target, and keep overlap-heavy changes visible across the common + interface. +

+
+ +
+ + Back to overview + + + Open ops center + + + Review issue + +
+ +
+
+

+ Redline pods +

+

+ {redlinePods} +

+

+ Pods already at or above the threshold for shared overflow + intervention. +

+
+
+

+ Average load +

+

+ {averageOccupancy}% +

+

+ Occupancy across the four active pods in the current release + window. +

+
+
+

+ Available fallback +

+

+ {surgeSlots} +

+

+ Stable pods that can still absorb secondary work before the + next overlap check. +

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

+ Pod capacity board +

+

+ Review the live pod mix before shifting people into the next + shared release window. +

+
+

+ Each card keeps occupancy, coverage, and the recommended linked + route together so the same release call can decide load balancing + and follow-up context. +

+
+ +
+ {capacityPods.map((pod) => ( +
+
+
+

+ {pod.name} +

+

{pod.focus}

+
+ + {pod.status} + +
+ +
+
+

+ Coverage +

+

+ {pod.coverage} +

+
+
+

+ Buffer +

+

+ {pod.buffer} +

+
+
+ +
+
+ Occupancy + {pod.occupancy}% +
+ + +
+

{pod.note}

+
+ {pod.window} +
+
+ + + {pod.action} + +
+ ))} +
+
+
+ +
+
+
+

Linked surfaces

+

+ Follow the adjacent route rails before committing a capacity + change. +

+
+ +
+ {linkedSurfaces.map((surface) => ( +
+

+ {surface.title} +

+

+ {surface.detail} +

+ + {surface.action} + +
+ ))} +
+
+ + +
+
+ ); +} diff --git a/src/app/globals.css b/src/app/globals.css index c23933d..25ff7e5 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -13,6 +13,10 @@ --navigator-accent-light: rgba(8, 145, 178, 0.12); --navigator-accent-soft: rgba(34, 211, 238, 0.08); --navigator-ink: #164e63; + --capacity-accent: #2563eb; + --capacity-accent-light: rgba(37, 99, 235, 0.12); + --capacity-accent-soft: rgba(219, 234, 254, 0.7); + --capacity-ink: #1d4ed8; /* Shared typography tokens */ --heading-tracking-tight: -0.025em; @@ -67,6 +71,7 @@ body { color: var(--foreground); background-image: radial-gradient(circle at top, rgba(251, 191, 36, 0.18), transparent 32%), + radial-gradient(circle at left 22%, rgba(37, 99, 235, 0.12), transparent 24%), radial-gradient(circle at right 18%, rgba(8, 145, 178, 0.14), transparent 26%), linear-gradient(180deg, #fcf8ef 0%, #f4efe7 52%, #ebe5d9 100%); font-family: var(--font-geist-sans), Arial, Helvetica, sans-serif; @@ -118,6 +123,35 @@ a { color: var(--foreground); } +.app-shell__signal { + display: flex; + align-items: center; + justify-content: center; + gap: 0.9rem; + border-bottom: 1px solid rgba(37, 99, 235, 0.1); + background: + linear-gradient(90deg, rgba(239, 246, 255, 0.9), rgba(255, 251, 235, 0.85)); + padding: 0.75rem 1.5rem; +} + +.app-shell__signal-label { + border-radius: 9999px; + border: 1px solid rgba(37, 99, 235, 0.18); + background: rgba(255, 255, 255, 0.82); + padding: 0.35rem 0.8rem; + font-size: 0.6875rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--capacity-ink); +} + +.app-shell__signal-copy { + font-size: 0.8125rem; + font-weight: 500; + color: rgba(15, 23, 42, 0.72); +} + /* ── Shared app chrome ─────────────────────────────────────── */ .app-nav { @@ -130,7 +164,8 @@ a { gap: 1rem; min-height: var(--nav-height); padding: 0.875rem 1.5rem; - background: rgba(255, 251, 240, 0.86); + background: + linear-gradient(90deg, rgba(255, 251, 240, 0.88), rgba(239, 246, 255, 0.72)); backdrop-filter: blur(12px); border-bottom: 1px solid var(--line); } @@ -374,7 +409,120 @@ a { border-left: 3px solid rgba(8, 145, 178, 0.28); } +/* ── Capacity-board shared shell styles ───────────────────── */ + +.capacity-shell-card { + position: relative; + isolation: isolate; +} + +.capacity-shell-card::after { + content: ""; + position: absolute; + inset: auto 0 0 0; + height: 3px; + background: linear-gradient( + 90deg, + rgba(37, 99, 235, 0.72) 0%, + rgba(59, 130, 246, 0.42) 50%, + rgba(251, 191, 36, 0.58) 100% + ); + opacity: 0.82; +} + +.capacity-board-panel { + position: relative; + overflow: hidden; +} + +.capacity-board-panel::before { + content: ""; + position: absolute; + inset: auto -12% -34% 42%; + height: 11rem; + background: radial-gradient(circle, rgba(37, 99, 235, 0.13), transparent 68%); + pointer-events: none; +} + +.capacity-board-chip { + display: inline-flex; + align-items: center; + gap: 0.45rem; + border-radius: 9999px; + border: 1px solid rgba(37, 99, 235, 0.18); + background: rgba(255, 255, 255, 0.76); + padding: 0.35rem 0.8rem; + font-size: 0.6875rem; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + color: var(--capacity-ink); +} + +.capacity-board-chip::before { + content: ""; + width: 0.45rem; + height: 0.45rem; + border-radius: 9999px; + background: var(--capacity-accent); + box-shadow: 0 0 0 0.25rem rgba(37, 99, 235, 0.12); +} + +.capacity-board-note { + border-left: 3px solid rgba(37, 99, 235, 0.24); +} + +.capacity-board-track { + transition: + transform 0.2s ease, + border-color 0.2s ease, + box-shadow 0.2s ease; +} + +.capacity-board-track:hover { + transform: translateY(-2px); + border-color: rgba(37, 99, 235, 0.22); + box-shadow: 0 18px 34px rgba(37, 99, 235, 0.09); +} + +.capacity-board-link { + position: relative; +} + +.capacity-board-link::after { + content: "\2192"; + margin-left: 0.45rem; + transition: transform 0.15s ease; +} + +.capacity-board-link:hover::after { + transform: translateX(2px); +} + +.capacity-board-meter { + overflow: hidden; + height: 0.6rem; + border-radius: 9999px; + background: rgba(148, 163, 184, 0.18); +} + +.capacity-board-meter__fill { + height: 100%; + border-radius: inherit; + background: linear-gradient( + 90deg, + rgba(37, 99, 235, 0.92) 0%, + rgba(96, 165, 250, 0.62) 100% + ); +} + @media (max-width: 960px) { + .app-shell__signal { + align-items: flex-start; + flex-direction: column; + gap: 0.35rem; + } + .app-nav { align-items: flex-start; flex-wrap: wrap; diff --git a/src/app/layout.tsx b/src/app/layout.tsx index a2dd03d..15b5a4e 100644 --- a/src/app/layout.tsx +++ b/src/app/layout.tsx @@ -19,12 +19,13 @@ export const metadata: Metadata = { template: "%s | Archive Signals", }, description: - "Operational dashboards, navigator handoff surfaces, experiment registries, and field guides for archive-driven teams.", + "Operational dashboards, navigator handoff surfaces, capacity boards, experiment registries, and field guides for archive-driven teams.", }; const navLinks = [ { href: "/", label: "Home" }, { href: "/navigator-hub", label: "Navigator Hub" }, + { href: "/capacity-board", label: "Capacity Board" }, { href: "/team-directory", label: "Team Directory" }, { href: "/archive-browser", label: "Archive" }, { href: "/research-notebook", label: "Notebook" }, @@ -47,9 +48,16 @@ export default function RootLayout({ className={`${geistSans.variable} ${geistMono.variable} h-full antialiased`} > +
+ Issue 218 +

+ Capacity Board overlap is active across the landing page, shared + layout, and global shell. +

+
{children}

- Archive Signals · Navigator Hub · Status Board · - Built for conflict testing + Archive Signals · Navigator Hub · Capacity Board + · Built for conflict testing

diff --git a/src/app/page.tsx b/src/app/page.tsx index 6c37aa1..c732412 100644 --- a/src/app/page.tsx +++ b/src/app/page.tsx @@ -1,10 +1,10 @@ import Link from "next/link"; import { teamGroups, getDirectoryMetrics } from "@/data/team-directory"; -const navigatorHubHighlights = [ - "New navigator hub route with linked intervention rails across queue, parcel, and command views", - "Shared landing-page overlap that rewrites the featured hero around the new route", - "Global shell styling updates that intentionally modify the common app nav, footer, and theme tokens", +const capacityBoardHighlights = [ + "New capacity board route with redline pod summaries, linked route rails, and shared overflow checks", + "Landing-page overlap that rewrites the featured hero around team load and release coverage", + "Global shell updates that intentionally touch the common nav, footer, body treatment, and shared style tokens", ]; const notebookHighlights = [ @@ -70,39 +70,39 @@ export default function Home() { return (
-
+
-

- Issue 216 / Navigator Hub +

+ Issue 218 / Capacity Board

- Coordinate shared app-shell handoffs from a dedicated navigator - hub. + Stage team load, overflow coverage, and release thresholds from + a dedicated capacity board.

- The navigator hub is the new conflict surface for issue 216: - one route that pulls together lane pressure, handoff timing, - and shared-shell entry points while also rewriting common - landing and global presentation files. + Capacity Board is the new conflict surface for issue 218: one + route that concentrates pod occupancy, fallback coverage, and + linked escalation rails while intentionally rewriting the shared + landing, layout, and global presentation files.

- Open navigator hub + Open capacity board - Open ops center + Open navigator hub
-
+

- Conflict checks + Release checks

    - {navigatorHubHighlights.map((item) => ( + {capacityBoardHighlights.map((item) => (
  • {item}