diff --git a/tools/release-plan-dashboard/public/index.html b/tools/release-plan-dashboard/public/index.html
index 85e65498b95..955ef9316b7 100644
--- a/tools/release-plan-dashboard/public/index.html
+++ b/tools/release-plan-dashboard/public/index.html
@@ -644,76 +644,113 @@
PM View — Release Plan Health
diff --git a/tools/release-plan-dashboard/public/style.css b/tools/release-plan-dashboard/public/style.css
index 0f1be01e475..a40555a1660 100644
--- a/tools/release-plan-dashboard/public/style.css
+++ b/tools/release-plan-dashboard/public/style.css
@@ -452,6 +452,47 @@ main {
opacity: 0.8;
}
+.pm-section-group {
+ border: 1px solid #e1dfdd;
+ border-radius: var(--radius);
+ background: #faf9f8;
+ padding: 16px 18px 8px;
+ margin-bottom: 20px;
+}
+
+.pm-section-group-header {
+ margin-bottom: 8px;
+}
+
+.pm-section-group-header.section-header {
+ padding-top: 0;
+}
+
+.pm-section-group-header.section-header > div {
+ min-width: 0;
+}
+
+.pm-section-group-header.section-header:hover .pm-section-group-title {
+ color: var(--azure-blue);
+}
+
+.pm-section-group-title {
+ margin: 0;
+ font-size: 1.1rem;
+ font-weight: 700;
+ color: var(--azure-dark);
+}
+
+.pm-section-group-desc {
+ margin: 4px 0 0;
+ color: var(--gray);
+ font-size: 0.9rem;
+}
+
+.pm-section-group.collapsed > .pm-section-group-content {
+ display: none !important;
+}
+
.section-header {
cursor: pointer;
user-select: none;
diff --git a/tools/release-plan-dashboard/tests/pm-view-layout.test.js b/tools/release-plan-dashboard/tests/pm-view-layout.test.js
new file mode 100644
index 00000000000..089100ade08
--- /dev/null
+++ b/tools/release-plan-dashboard/tests/pm-view-layout.test.js
@@ -0,0 +1,74 @@
+import { describe, expect, test } from "vitest";
+import { readFileSync } from "node:fs";
+import path from "node:path";
+import { fileURLToPath } from "node:url";
+
+const __dirname = path.dirname(fileURLToPath(import.meta.url));
+const indexHtml = readFileSync(
+ path.join(__dirname, "../public/index.html"),
+ "utf8",
+);
+
+function expectOrdered(block, labels) {
+ let lastIndex = -1;
+ for (const label of labels) {
+ const index = block.indexOf(label);
+ expect(index, `Expected to find "${label}"`).toBeGreaterThanOrEqual(0);
+ expect(index, `Expected "${label}" after prior sections`).toBeGreaterThan(
+ lastIndex,
+ );
+ lastIndex = index;
+ }
+}
+
+describe("PM View Attention Required layout", () => {
+ test("groups informational and action sections in the expected order", () => {
+ const informationalIndex = indexHtml.indexOf('id="pm-informational"');
+ const needsAttentionIndex = indexHtml.indexOf('id="pm-needs-attention"');
+
+ expect(needsAttentionIndex).toBeGreaterThanOrEqual(0);
+ expect(indexHtml).toMatch(
+ /aria-labelledby="pm-needs-attention"[\s\S]*class="pm-section-group-header section-header"[\s\S]*data-target="list-pm-needs-attention"[\s\S]*id="pm-needs-attention"[\s\S]*>\s*Needs Attention\s*,
+ );
+ expect(informationalIndex).toBeGreaterThanOrEqual(0);
+ expect(indexHtml).toMatch(
+ /aria-labelledby="pm-informational"[\s\S]*class="pm-section-group-header section-header"[\s\S]*data-target="list-pm-informational"[\s\S]*id="pm-informational"[\s\S]*>\s*Informational\s*,
+ );
+ expect(informationalIndex).toBeGreaterThan(needsAttentionIndex);
+
+ expectOrdered(indexHtml.slice(needsAttentionIndex, informationalIndex), [
+ "Ready to Complete Private",
+ "Past Due Release Plans",
+ "Stale Release Plans",
+ "Release Plans Without SDK",
+ "Partially Released",
+ "Missing Namespace Approval",
+ "Missing Product Details",
+ ]);
+
+ expectOrdered(indexHtml.slice(informationalIndex), [
+ "Recently Finished (Last 2",
+ "Approaching SDK Release",
+ ]);
+ });
+
+ test("keeps each PM section present exactly once", () => {
+ const sectionIds = [
+ "section-pm-finished",
+ "section-pm-approaching",
+ "section-pm-pp-ready",
+ "section-pm-pastdue",
+ "section-pm-inactive",
+ "section-pm-tier1",
+ "section-pm-partial",
+ "section-pm-ns-missing",
+ "section-pm-product-missing",
+ ];
+
+ for (const sectionId of sectionIds) {
+ const matches =
+ indexHtml.match(new RegExp(`id="${sectionId}"`, "g")) || [];
+ expect(matches).toHaveLength(1);
+ }
+ });
+});