From 7c8e5847936952bc97b6d2cf8cfaef1f040b2580 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 26 Jul 2026 04:44:01 +0000 Subject: [PATCH 1/3] Wrap Consistency, Roles, and Sprint Pulse sections for WCAG scan. Apply Section with When/How copy and clearer verb+object CTAs while keeping sticky-nav section ids stable. Co-authored-by: Damon --- apps/ui/src/pages/ConsistencyPage.tsx | 84 +++++++++++++++--------- apps/ui/src/pages/RolesPage.tsx | 93 +++++++++++++++++---------- apps/ui/src/pages/SprintPulsePage.tsx | 56 +++++++++------- 3 files changed, 144 insertions(+), 89 deletions(-) diff --git a/apps/ui/src/pages/ConsistencyPage.tsx b/apps/ui/src/pages/ConsistencyPage.tsx index 3fb640b..f64e1e5 100644 --- a/apps/ui/src/pages/ConsistencyPage.tsx +++ b/apps/ui/src/pages/ConsistencyPage.tsx @@ -4,7 +4,7 @@ import type { CalibBundleDTO, ConsistencyDTO, CycleDTO } from "@prm/shared"; import { competencyLabel } from "@prm/shared"; import { api } from "../lib/api"; import { downloadBinaryExport, openHtmlExport } from "../lib/exports"; -import { NextStep, PageHeader } from "../components/PageChrome"; +import { NextStep, PageHeader, Section } from "../components/PageChrome"; import { PageSectionsLayout } from "../components/PageSectionsLayout"; export function ConsistencyPage() { @@ -106,12 +106,28 @@ export function ConsistencyPage() { { id: "cons-cards", label: "People" }, ]} > -
-

Calibration bundles

-

- Director round-trip: export cards, capture decisions in the form (or paste JSON), then import - back into draft manager reviews for the same cycle. -

+
{ + try { + await openHtmlExport(`/api/exports/calib-room/${data.cycleId}?format=html`); + setCalibMsg("Opened calibration room pack — Print → Save as PDF"); + } catch (e) { + setCalibMsg(e instanceof Error ? e.message : "Calib pack failed"); + } + }} + > + Print calib room pack + + } + >
-
@@ -205,7 +207,7 @@ export function ConsistencyPage() { } }} > - Encrypted read-only bundle + Download encrypted bundle
@@ -316,10 +318,10 @@ export function ConsistencyPage() { await load(data.cycleId); }} > - Import decisions + Import calibration decisions {calibMsg &&

{calibMsg}

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

Overall distribution

+
{["1", "2", "3", "4", "5", "unset"].map((bucket) => { const n = data.distribution[bucket] ?? 0; @@ -360,9 +366,22 @@ export function ConsistencyPage() { ); })}
-
+ -
+
+ Open first writing desk + + ) : undefined + } + > +
{data.cards.map((card) => (
@@ -396,11 +415,12 @@ export function ConsistencyPage() { ))} - Writing desk + Open writing desk
))} -
+
+
)}
diff --git a/apps/ui/src/pages/RolesPage.tsx b/apps/ui/src/pages/RolesPage.tsx index 6e1befe..ded56c9 100644 --- a/apps/ui/src/pages/RolesPage.tsx +++ b/apps/ui/src/pages/RolesPage.tsx @@ -7,7 +7,7 @@ import type { RoleFrameworkInfo, } from "@prm/shared"; import { api, getToken, apiUrl } from "../lib/api"; -import { NextStep, PageHeader } from "../components/PageChrome"; +import { NextStep, PageHeader, Section } from "../components/PageChrome"; import { PageSectionsLayout } from "../components/PageSectionsLayout"; type ExpectationEdit = { id?: string; kind: "responsibility" | "expectation"; text: string }; @@ -386,9 +386,18 @@ export function RolesPage() { { id: "roles-assign", label: "Assign" }, ]} > -
-
-

Catalog

+
+
+ Add role + + } + > {roles.length === 0 &&

No roles yet — add one or extract from a document.

} {roles.map((r) => ( ))} -
-
-

{selected?.title ?? "Select a role"}

+ +
startEdit(selected)}> + Edit role + + ) : undefined + } + > {selected ? ( <> +

{selected.title}

{selected.summary}

    {selected.expectations.map((e) => ( @@ -431,18 +452,15 @@ export function RolesPage() {

    Next keys: {selected.nextRoleKeys.join(", ")}

    )}
    -
    ) : (

    Pick a role from the catalog or create one.

    )} -
+
{editing && ( @@ -558,7 +576,7 @@ export function RolesPage() { Add expectation + } + >
-
- -
{extractMeta &&

{extractMeta}

} {drafts.length > 0 && ( <> @@ -622,19 +640,29 @@ export function RolesPage() {
)} -
+ +
+ Save role assignment + + } + >
{ e.preventDefault(); await api("/api/roles/assign", { @@ -650,7 +678,6 @@ export function RolesPage() { await load(); }} > -

Assign role

{selectedPerson ? `Editing ${selectedPerson.name}${selectedPerson.roleTitle ? ` (currently ${selectedPerson.roleTitle})` : " (unassigned)"}.` @@ -690,13 +717,9 @@ export function RolesPage() { -

- - {saved && {saved}} -
+ {saved &&

{saved}

}
+
); diff --git a/apps/ui/src/pages/SprintPulsePage.tsx b/apps/ui/src/pages/SprintPulsePage.tsx index 2c98617..9c1e797 100644 --- a/apps/ui/src/pages/SprintPulsePage.tsx +++ b/apps/ui/src/pages/SprintPulsePage.tsx @@ -2,7 +2,7 @@ import { useEffect, useState } from "react"; import { Link, useSearchParams } from "react-router-dom"; import type { SprintPulseDTO } from "@prm/shared"; import { api } from "../lib/api"; -import { NextStep, PageHeader } from "../components/PageChrome"; +import { NextStep, PageHeader, Section } from "../components/PageChrome"; import { PageSectionsLayout } from "../components/PageSectionsLayout"; const PRESETS = [ @@ -48,7 +48,7 @@ export function SprintPulsePage() { Team performance} + primary={Open team performance} /> {error && ( @@ -72,7 +72,12 @@ export function SprintPulsePage() { { id: "pulse-people", label: "People" }, ]} > -
+
@@ -119,11 +124,7 @@ export function SprintPulsePage() { />
-

- Counts achievements (including Jira / Bitbucket sync), feedback themes, documents, and open - concerns dated in the window. Flags people whose manager-draft cites are all older than the window. -

-
+ {!data ? (

Loading…

@@ -137,7 +138,7 @@ export function SprintPulsePage() { ? `${data.summary.staleCitePeople} draft(s) only cite older evidence. Prefer fresher cites before finalize.` : "Evidence freshness looks healthy for drafts that have cites." } - action={Open team} + action={Open team roster} />
@@ -165,8 +166,12 @@ export function SprintPulsePage() {
-
-

1:1 themes in window

+
{data.themes.length === 0 ? (

No themes tagged on feedback in this range.

) : ( @@ -187,12 +192,14 @@ export function SprintPulsePage() { ))}
)} -
-
-

Evidence freshness

-

- In-window dossier items vs older items, plus whether current draft cites are stale. -

+ +
Open team roster} + >
  • {data.summary.staleCitePeople} people with stale-only cites @@ -205,11 +212,16 @@ export function SprintPulsePage() { synced tickets / PRs in window
-
+ -
-

People

+
Open team roster} + >
@@ -250,7 +262,7 @@ export function SprintPulsePage() { @@ -258,7 +270,7 @@ export function SprintPulsePage() {
- Dossier + Open dossier
-
+ )} From 5ad13c2db284ea50be3154ce88110880c9e961f8 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Sun, 26 Jul 2026 04:44:43 +0000 Subject: [PATCH 2/3] Wrap Person, Writing Desk, Team Performance, and Promo detail sections for WCAG scan. Apply the shared Section component (When/How + clearer CTAs) to sticky-nav sections while keeping existing anchor ids. Co-authored-by: Damon --- apps/ui/src/pages/PersonPage.tsx | 168 ++++++++------- apps/ui/src/pages/PromotionDetailPage.tsx | 44 ++-- apps/ui/src/pages/TeamPerformancePage.tsx | 109 ++++++---- apps/ui/src/pages/WritingDeskPage.tsx | 240 ++++++++++++++-------- 4 files changed, 342 insertions(+), 219 deletions(-) diff --git a/apps/ui/src/pages/PersonPage.tsx b/apps/ui/src/pages/PersonPage.tsx index ef8c65e..bba036a 100644 --- a/apps/ui/src/pages/PersonPage.tsx +++ b/apps/ui/src/pages/PersonPage.tsx @@ -12,7 +12,7 @@ import type { PersonTimelineDTO, } from "@prm/shared"; import { api, getToken, apiUrl } from "../lib/api"; -import { NextStep, PageHeader } from "../components/PageChrome"; +import { NextStep, PageHeader, Section } from "../components/PageChrome"; import { PageSectionsLayout, type SectionNavItem } from "../components/PageSectionsLayout"; import { DeliveryRollupPanel } from "../components/DeliveryRollupPanel"; @@ -170,34 +170,12 @@ export function PersonPage() { {activeCycle && ( -
-

Mid-cycle manager change

-

- Reassign for {activeCycle.name}: supersedes prior manager drafts/finals as contribution - history and opens a fresh draft for the new manager. -

-
-
- - -
-
- - setReassignReason(e.target.value)} - placeholder="Org change / coverage" - /> -
+
Reassign in cycle + } + > +
+
+ + +
+
+ + setReassignReason(e.target.value)} + placeholder="Org change / coverage" + /> +
{reassignMsg &&

{reassignMsg}

} -
+ )} -
-

Timeline

-

- Chronological dossier, reviews, concerns, and rating decisions. Use this to prep a 1:1 or reconstruct - why the story changed. -

+
{!timeline || timeline.entries.length === 0 ? (

No timeline events yet.

) : ( @@ -249,14 +251,19 @@ export function PersonPage() { ))} )} -
+ -
-

Integrations

-

- Link remote accounts, then pull tickets / merged PRs for the active review window into achievements. - Configure credentials in Settings → Integrations. -

+
+ Open integration settings + + } + > {extLinks.length === 0 ? (

No external accounts linked yet.

) : ( @@ -318,7 +325,7 @@ export function PersonPage() { } }} > - Search + Search remote users ))} )} {linkMsg &&

{linkMsg}

} -
+ {id ? : null}
-
-

Achievements

+
    {dossier.achievements.map((a) => (
  • @@ -443,9 +454,13 @@ export function PersonPage() { setAchTitle(e.target.value)} placeholder="Shipped X / unblocked Y…" required /> -
-
-

Goals

+ +
    {dossier.goals.map((g) => (
  • {g.title} {g.status}
  • @@ -464,12 +479,15 @@ export function PersonPage() { setGoalTitle(e.target.value)} placeholder="Outcome-oriented goal" required /> -
+
-
-

Feedback

-

EM-entered notes from 1:1s, partners, or incidents. Author names stay on your workspace.

+
{dossier.feedback.length === 0 &&

No feedback yet — capture a coaching or praise note.

} {dossier.feedback.map((f) => (

@@ -532,11 +550,14 @@ export function PersonPage() {

-
+ -
-

Documents

-

PDF/DOCX/PPTX/CSV/images — allowlisted. Sealed into the encrypted vault when you lock the workspace.

+
    {(dossier.documents ?? []).map((d) => (
  • @@ -592,16 +613,16 @@ export function PersonPage() { {docMsg &&

    {docMsg}

    } -
+ {captureMsg &&

{captureMsg}

} -
-

Meeting summaries

-

- Structured notes from 1:1s / meetings. Default visibility is EM only. Raw transcripts stay - private on the dossier. Confirm consent before storing — local vault notes are not a legal privilege. -

+
{meetings.length === 0 ? (

No meeting summaries yet.

) : ( @@ -721,13 +742,14 @@ export function PersonPage() { Save meeting summary -
+ -
-

Concern log

-

- Private performance concerns and follow-up plans. AI is off by default — these stay out of digests and drafts unless you later opt a concern in. -

+
{concerns.length === 0 ? (

No concerns logged.

) : ( @@ -828,7 +850,7 @@ export function PersonPage() {
- + {person.targetNextRoleTitle && (
-
-
-

Next-role expectations

+
{ @@ -226,9 +229,10 @@ export function PromotionDetailPage() { } }} > - AI draft case + AI draft promotion case -
+ } + > {(detail.targetRole?.expectations ?? []).map((e) => (
@@ -243,10 +247,26 @@ export function PromotionDetailPage() {
))} {aiOut &&
{aiOut}
} -
+ -
-

Case narrative

+
+ + + + ) : undefined + } + >