From 454f39600e9a32a3cb902eda73968a9b9d186184 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 26 Jul 2026 04:31:11 +0000 Subject: [PATCH 1/3] Extend sticky On this page nav to long multi-section screens Add PageSectionsLayout and wire section jump links on Home, Person, Writing desk, Team performance, Consistency, Cycles, Roles, Sprint pulse, Promotion detail, and Settings. Co-authored-by: Damon van der linde --- apps/ui/src/components/PageSectionNav.tsx | 9 ++++- apps/ui/src/components/PageSectionsLayout.tsx | 25 ++++++++++++++ apps/ui/src/pages/ConsistencyPage.tsx | 26 +++++++++------ apps/ui/src/pages/CyclesPage.tsx | 16 +++++++-- apps/ui/src/pages/HomePage.tsx | 26 +++++++++++---- apps/ui/src/pages/PersonPage.tsx | 33 ++++++++++++++----- apps/ui/src/pages/PromotionDetailPage.tsx | 12 +++++-- apps/ui/src/pages/RolesPage.tsx | 16 +++++++-- apps/ui/src/pages/SettingsPage.tsx | 12 +++---- apps/ui/src/pages/SprintPulsePage.tsx | 18 +++++++--- apps/ui/src/pages/TeamPerformancePage.tsx | 21 +++++++++--- apps/ui/src/pages/WritingDeskPage.tsx | 30 ++++++++++++----- apps/ui/src/styles.css | 12 +++++-- 13 files changed, 194 insertions(+), 62 deletions(-) create mode 100644 apps/ui/src/components/PageSectionsLayout.tsx diff --git a/apps/ui/src/components/PageSectionNav.tsx b/apps/ui/src/components/PageSectionNav.tsx index b09c69a..0b46a24 100644 --- a/apps/ui/src/components/PageSectionNav.tsx +++ b/apps/ui/src/components/PageSectionNav.tsx @@ -1,4 +1,4 @@ -import { useEffect, useState } from "react"; +import { useEffect, useRef, useState } from "react"; export type SectionNavItem = { id: string; @@ -14,6 +14,11 @@ export function PageSectionNav({ }) { const [activeId, setActiveId] = useState(items[0]?.id ?? ""); const sectionKey = items.map((item) => item.id).join("|"); + const clickLockUntil = useRef(0); + + useEffect(() => { + if (items[0]?.id) setActiveId(items[0].id); + }, [sectionKey, items]); useEffect(() => { const nodes = items @@ -24,6 +29,7 @@ export function PageSectionNav({ const ratios = new Map(); const observer = new IntersectionObserver( (entries) => { + if (Date.now() < clickLockUntil.current) return; for (const entry of entries) { ratios.set(entry.target.id, entry.isIntersecting ? entry.intersectionRatio : 0); } @@ -55,6 +61,7 @@ export function PageSectionNav({ function goTo(id: string) { const el = document.getElementById(id); if (!el) return; + clickLockUntil.current = Date.now() + 700; setActiveId(id); el.scrollIntoView({ behavior: "smooth", block: "start" }); } diff --git a/apps/ui/src/components/PageSectionsLayout.tsx b/apps/ui/src/components/PageSectionsLayout.tsx new file mode 100644 index 0000000..8f99830 --- /dev/null +++ b/apps/ui/src/components/PageSectionsLayout.tsx @@ -0,0 +1,25 @@ +import type { ReactNode } from "react"; +import { PageSectionNav, type SectionNavItem } from "./PageSectionNav"; + +/** Sticky right-rail (or top chips on phone) for long multi-section pages. */ +export function PageSectionsLayout({ + sections, + children, + ariaLabel = "On this page", +}: { + sections: SectionNavItem[]; + children: ReactNode; + ariaLabel?: string; +}) { + if (sections.length < 2) { + return
{children}
; + } + return ( +
+
{children}
+ +
+ ); +} + +export type { SectionNavItem }; diff --git a/apps/ui/src/pages/ConsistencyPage.tsx b/apps/ui/src/pages/ConsistencyPage.tsx index c67dfed..3fb640b 100644 --- a/apps/ui/src/pages/ConsistencyPage.tsx +++ b/apps/ui/src/pages/ConsistencyPage.tsx @@ -5,6 +5,7 @@ import { competencyLabel } from "@prm/shared"; import { api } from "../lib/api"; import { downloadBinaryExport, openHtmlExport } from "../lib/exports"; import { NextStep, PageHeader } from "../components/PageChrome"; +import { PageSectionsLayout } from "../components/PageSectionsLayout"; export function ConsistencyPage() { const [params, setParams] = useSearchParams(); @@ -94,8 +95,18 @@ export function ConsistencyPage() { - {data && ( -
+ + {!data ? ( +

Loading…

+ ) : ( + +

Calibration bundles

Director round-trip: export cards, capture decisions in the form (or paste JSON), then import @@ -309,12 +320,7 @@ export function ConsistencyPage() { {calibMsg &&

{calibMsg}

}
- )} - {!data ? ( -

Loading…

- ) : ( - <> {thin.length > 0 ? ( )} -
+

Overall distribution

{["1", "2", "3", "4", "5", "unset"].map((bucket) => { @@ -356,7 +362,7 @@ export function ConsistencyPage() {
-
+
{data.cards.map((card) => (
@@ -395,7 +401,7 @@ export function ConsistencyPage() {
))}
- + )}
); diff --git a/apps/ui/src/pages/CyclesPage.tsx b/apps/ui/src/pages/CyclesPage.tsx index 88a31b3..36a7f47 100644 --- a/apps/ui/src/pages/CyclesPage.tsx +++ b/apps/ui/src/pages/CyclesPage.tsx @@ -3,6 +3,7 @@ import { Link } from "react-router-dom"; import type { CycleDTO, PersonDTO } from "@prm/shared"; import { api } from "../lib/api"; import { NextStep, PageHeader } from "../components/PageChrome"; +import { PageSectionsLayout } from "../components/PageSectionsLayout"; const PHASE_STATUSES = [ { value: "self_open", label: "Self open" }, @@ -81,7 +82,15 @@ export function CyclesPage() { /> )} -
+ +

Cycles

{cycles.length === 0 ? (

No cycles yet.

@@ -156,7 +165,7 @@ export function CyclesPage() {
{selected && ( -
+

Phase wizard — {selected.name}

Soft gates only: advance phases when ready. Waive missing self/peer from the writing desk with a reason. @@ -266,7 +275,7 @@ export function CyclesPage() { -

+

2. Import completed bundle

@@ -320,6 +329,7 @@ export function CyclesPage() { action={Open command center} /> )} +
); } diff --git a/apps/ui/src/pages/HomePage.tsx b/apps/ui/src/pages/HomePage.tsx index 27b8210..0556566 100644 --- a/apps/ui/src/pages/HomePage.tsx +++ b/apps/ui/src/pages/HomePage.tsx @@ -3,6 +3,7 @@ import { Link } from "react-router-dom"; import type { CycleDTO, CycleStatsDTO, DeadlineNudge, DirectorAttentionDTO, DueThisWeekItem, WorkspaceStatus } from "@prm/shared"; import { api } from "../lib/api"; import { NextStep, PageHeader } from "../components/PageChrome"; +import { PageSectionsLayout } from "../components/PageSectionsLayout"; import { OnboardingWizard } from "../components/OnboardingWizard"; type CommandItem = { @@ -246,7 +247,19 @@ export function HomePage() { {next} -
+ 0 + ? [{ id: "home-attention", label: "Attention" }] + : []), + ...(dueWeek.length > 0 ? [{ id: "home-due", label: "Due" }] : []), + ...(cycleStats ? [{ id: "home-dashboard", label: "Dashboard" }] : []), + ...(cycleId && nudges.length > 0 ? [{ id: "home-nudges", label: "Nudges" }] : []), + { id: "home-queue", label: "Queue" }, + ]} + > +