From 37fb02de57c7a245c7ff064468e0dee7b5e09b76 Mon Sep 17 00:00:00 2001 From: Joshua Date: Wed, 23 Sep 2026 20:07:38 -0400 Subject: [PATCH] Add the admin Today tab (compact design) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Render ADMIN-SPEC §2.1-2.6 from GET /api/v1/admin/snapshot: header with cycle day, the Done/Remaining/Overdue stat row, child rows with progress and away state, the needs-attention list, and the admin-only Undo permission note. The scroll column ends with 92px bottom padding, and loading and error states never leave the panel blank. Mark done / Undo render per spec but are not wired (task e57facc2). The override pill and "from panel" actor are omitted: the snapshot carries neither field. Task: e0a2b862-8c1e-4d5b-95ad-48b460bfeaa0 Co-Authored-By: Claude Opus 5.5 --- admin/src/App.css | 17 ++ admin/src/App.test.tsx | 18 +- admin/src/App.tsx | 20 +- admin/src/api/snapshot.ts | 55 ++++++ admin/src/index.css | 21 +++ admin/src/today/TodayTab.css | 300 ++++++++++++++++++++++++++++++ admin/src/today/TodayTab.test.tsx | 223 ++++++++++++++++++++++ admin/src/today/TodayTab.tsx | 277 +++++++++++++++++++++++++++ admin/src/today/format.ts | 50 +++++ admin/src/today/glyphs.tsx | 61 ++++++ 10 files changed, 1030 insertions(+), 12 deletions(-) create mode 100644 admin/src/api/snapshot.ts create mode 100644 admin/src/today/TodayTab.css create mode 100644 admin/src/today/TodayTab.test.tsx create mode 100644 admin/src/today/TodayTab.tsx create mode 100644 admin/src/today/format.ts create mode 100644 admin/src/today/glyphs.tsx diff --git a/admin/src/App.css b/admin/src/App.css index 590a28f..b38f991 100644 --- a/admin/src/App.css +++ b/admin/src/App.css @@ -31,6 +31,23 @@ padding: 14px 16px 92px; /* bottom padding clears the 70px tab bar */ } +/* A built screen supplies its own header and 92px-padded scroll column. */ +.admin-panel--screen { + padding: 0; +} + +.visually-hidden { + position: absolute; + width: 1px; + height: 1px; + margin: -1px; + padding: 0; + overflow: hidden; + clip: rect(0 0 0 0); + white-space: nowrap; + border: 0; +} + .admin-placeholder { margin: 0; padding: 13px; diff --git a/admin/src/App.test.tsx b/admin/src/App.test.tsx index 077faa7..61233c1 100644 --- a/admin/src/App.test.tsx +++ b/admin/src/App.test.tsx @@ -1,9 +1,17 @@ -import { afterEach, describe, expect, it } from "vitest"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; import { cleanup, fireEvent, render, screen } from "@testing-library/react"; import App from "./App"; describe("NestQuest Admin shell", () => { - afterEach(cleanup); + beforeEach(() => { + // The Today tab fetches the admin snapshot; keep the shell tests offline. + vi.stubGlobal("fetch", vi.fn(() => new Promise(() => {}))); + }); + + afterEach(() => { + cleanup(); + vi.unstubAllGlobals(); + }); it("renders the title and all four tab labels", () => { render(); @@ -14,12 +22,10 @@ describe("NestQuest Admin shell", () => { } }); - it("starts on the Today placeholder and switches on tab click", () => { + it("starts on the Today screen and switches on tab click", () => { render(); - expect(screen.getByRole("tabpanel").textContent).toContain( - "Today placeholder", - ); + expect(screen.getByRole("heading", { name: "Today" })).toBeTruthy(); fireEvent.click(screen.getByRole("tab", { name: "History" })); diff --git a/admin/src/App.tsx b/admin/src/App.tsx index 6d491c3..10c120d 100644 --- a/admin/src/App.tsx +++ b/admin/src/App.tsx @@ -1,5 +1,6 @@ import { useState } from "react"; import "./App.css"; +import TodayTab from "./today/TodayTab"; const TABS = [ { id: "today", label: "Today" }, @@ -12,22 +13,29 @@ type TabId = (typeof TABS)[number]["id"]; export default function App() { const [active, setActive] = useState("today"); + // Built screens own their header (ADMIN-SPEC §1); the generic shell header + // stays for placeholder tabs and is visually hidden otherwise. + const hasScreen = active === "today"; return (
-
+

NestQuest Admin

Admin console

-

- {TABS.find((tab) => tab.id === active)?.label} placeholder — screens - arrive in later tasks. -

+ {active === "today" ? ( + + ) : ( +

+ {TABS.find((tab) => tab.id === active)?.label} placeholder — screens + arrive in later tasks. +

+ )}