diff --git a/src/app/work-mode.css b/src/app/work-mode.css
index 9714f5ca10..ea2587856e 100644
--- a/src/app/work-mode.css
+++ b/src/app/work-mode.css
@@ -235,6 +235,11 @@ body {
gap: calc(var(--spacing) * 2.25);
}
+/* A column with nothing in it takes no gap; a dock spans every column. */
+.work-columns > :empty {
+ display: none;
+}
+
@media (min-width: 1024px) {
.work-split {
grid-template-columns: minmax(17rem, 22rem) minmax(0, 1fr);
@@ -259,6 +264,32 @@ body {
.work-columns[data-columns="3"] {
grid-template-columns: repeat(3, minmax(0, 1fr));
}
+
+ /* Keep a remaining dock in the first column when the right column is empty. */
+ .work-columns:has(> :nth-child(2):empty) {
+ grid-template-columns: minmax(0, 1fr);
+ }
+}
+
+/* The CPD log's filter column (from 1024px, desktop layout audit 11 Oct 2026):
+ it holds every filter, so the chip row over the list steps aside, the
+ column's chips wrap rather than cut off mid-word, and the Log button sits
+ over the list instead of across the column. */
+@media (min-width: 1024px) {
+ [data-filter-column] .work-chips {
+ flex-wrap: wrap;
+ row-gap: calc(var(--spacing) * 1.5);
+ overflow-x: visible;
+ mask-image: none;
+ }
+
+ .work-chips[data-beside-filters] {
+ display: none;
+ }
+
+ .work-dock[data-beside-filters] {
+ margin-inline-start: calc(var(--spacing) * 72);
+ }
}
/* ------------------------------------------------------------ the band */
diff --git a/src/components/clinical-dashboard/ClinicalSidebar.tsx b/src/components/clinical-dashboard/ClinicalSidebar.tsx
index 9ac201bfe1..eadd8378cc 100644
--- a/src/components/clinical-dashboard/ClinicalSidebar.tsx
+++ b/src/components/clinical-dashboard/ClinicalSidebar.tsx
@@ -1,5 +1,6 @@
"use client";
+import dynamic from "next/dynamic";
import {
useEffect,
useRef,
@@ -60,7 +61,6 @@ import {
ClinicalMenuWorkAreas,
clinicalMenuTabId,
} from "@/components/clinical-dashboard/clinical-menu-drawer";
-import { LazyWorkSideCounts } from "@/components/work-frame/lazy-work-side-nav";
import { useWorkSideCounts } from "@/components/work-frame/work-frame-store";
import { useNewWorkMode } from "@/components/work-mode-launch/work-mode-launch-provider";
import { needsYouBellVisibleForAuth } from "@/lib/needs-you/homes";
@@ -68,6 +68,18 @@ import type { ModeMenuSideId } from "@/lib/phone-mode-groups";
import { useAuthSession } from "@/lib/supabase/client";
import type { WorkAreaId } from "@/lib/work-frame/areas";
+// The counts reader is loaded straight from its own file. Going through
+// work-frame/lazy-work-side-nav would pull in the two-pane menu, which imports
+// this file back and makes an import cycle.
+const LazyCountsReader = dynamic(
+ () => import("@/components/work-frame/work-side-counts-reader").then((module) => module.WorkSideCountsReader),
+ { ssr: false },
+);
+
+function LazyWorkSideCounts({ active }: { readonly active: boolean }) {
+ return active ? : null;
+}
+
export type SidebarIdentity = {
displayName: string;
initials: string;
diff --git a/src/components/cme/cme-dashboard-whats-left.tsx b/src/components/cme/cme-dashboard-whats-left.tsx
index 7e97ea438b..942ab06bd7 100644
--- a/src/components/cme/cme-dashboard-whats-left.tsx
+++ b/src/components/cme/cme-dashboard-whats-left.tsx
@@ -304,7 +304,7 @@ export function CmeWhatsLeft({
end={
// A short word: held to the 48 px tap width, its words kept at the right edge.
- Report
+ End of year
}
>
diff --git a/src/components/cme/cme-dashboard.tsx b/src/components/cme/cme-dashboard.tsx
index 68e4602ec1..a34f9936e9 100644
--- a/src/components/cme/cme-dashboard.tsx
+++ b/src/components/cme/cme-dashboard.tsx
@@ -29,7 +29,7 @@ import { CME_LOG_TRIGGER_ATTRIBUTE, openCmeQuickLog } from "@/components/cme/cme
import { useCmeTeachingUnloggedCount } from "@/components/cme/cme-teaching-prompt";
import { CmeBandAction, CmeKvCard, cmeFreshnessEyebrow } from "@/components/cme/cme-work-kit";
import { CmeYearInWeeks } from "@/components/cme/cme-year-in-weeks";
-import { WorkBody } from "@/components/mode-kit/work";
+import { WorkColumns } from "@/components/work-frame/work-desktop-layout";
import { Button } from "@/components/ui/button";
import { NewWorkModeOnly } from "@/components/work-mode-launch/work-mode-launch-provider";
import { addDays, expandEvents } from "@/lib/calendar/calendar-event";
@@ -607,15 +607,15 @@ export function CmeDashboard({
/>,
);
}
- // "What's left" carries the only other link to the Report; when it is hidden this row keeps the
- // Report (exports, closing the year, Customise) one tap away.
+ // "What's left" carries the only other link to End of year; when it is hidden this row keeps
+ // End of year (what is missing, evidence, handing it in) one tap away.
if (!showWhatsLeft) {
aboutRows.push(
,
);
@@ -660,20 +660,26 @@ export function CmeDashboard({
{status}
-
- {summary}
- {nextStepNode}
- {toLog}
- {whatsLeft}
- {chipsNode}
-
-
-
- {byCategory}
- {alsoForYou}
- {about}
+ {/* One column on a phone, in this order. From 1024px the year and what is left sit on the left, and
+ the dates and the year's details on the right (11 Oct 2026 layout audit). */}
+
+
+ {/* From 1024px the side column holds the year, category and audit filters, so the chip row would repeat them. */}
+
{yearChip}
);
+ // The log with its filter column is a wide page from 1024px, so the list keeps a readable width; To finish stays a reading page.
+ const LogBody = showFinish ? WorkBody : WorkWidePage;
+
return (
{showFinish ? null : (
@@ -636,7 +647,7 @@ export function CmeLogPage({
testId="cme-log-filter-action"
/>
)}
-
+ {showFinish ? "To finish" : "Log"}
{justSaved ? Saved to your log. : null}
@@ -657,7 +668,12 @@ export function CmeLogPage({
{showFinish ? finish : activities}
-
+
-
+
{!showFinish ? (
<>
diff --git a/tests/challenger-m4-2-empirical.dom.test.tsx b/tests/challenger-m4-2-empirical.dom.test.tsx
index 769a514021..396e01b008 100644
--- a/tests/challenger-m4-2-empirical.dom.test.tsx
+++ b/tests/challenger-m4-2-empirical.dom.test.tsx
@@ -301,16 +301,15 @@ describe("Empirical Challenger M4-2 Verification Suite", () => {
expect(diffContent).toContain("pb-[calc(6.25rem+var(--safe-area-bottom))]");
});
- // The CPD Year page now sits in the shared WorkBody, which owns the home-bar
- // clearance (work-mode redesign, owner request 6 Oct 2026).
- it("verifies var(--safe-area-bottom) clears the CPD Year page through WorkBody", () => {
- expect(cmeContent).toContain("");
+ // The CPD Year page sits in the desktop frame's WorkColumns (desktop layout audit,
+ // 11 Oct 2026), whose shared rule owns the home-bar clearance like WorkBody's.
+ it("verifies var(--safe-area-bottom) clears the CPD Year page through WorkColumns", () => {
+ expect(cmeContent).toContain("");
const workCss = readFileSync(resolve(process.cwd(), "src/app/work-mode.css"), "utf8");
- const workBodyRule = workCss.slice(
- workCss.indexOf(".work-body {"),
- workCss.indexOf("}", workCss.indexOf(".work-body {")),
- );
- expect(workBodyRule).toContain("var(--safe-area-bottom, 0px)");
+ const start = workCss.indexOf(".work-columns {");
+ const columnsRule = workCss.slice(start, workCss.indexOf("}", start));
+ expect(start).toBeGreaterThan(-1);
+ expect(columnsRule).toContain("var(--safe-area-bottom, 0px)");
});
it("confirms zero raw env(safe-area-inset-bottom) occurrences in differential presentation and cme dashboard", () => {
diff --git a/tests/ui-smoke.spec.ts b/tests/ui-smoke.spec.ts
index 077e8f3414..1aa861384a 100644
--- a/tests/ui-smoke.spec.ts
+++ b/tests/ui-smoke.spec.ts
@@ -1245,7 +1245,9 @@ test.describe("PsychSift UI smoke coverage", () => {
await waitForDemoDashboardReady(page);
const menu = await openMobileClinicalGuideMenu(page);
- await menu.getByRole("link", { name: "Medication" }).click();
+ // The menu lists Medication twice by design: once as a pinned shortcut and once
+ // under its group. The shortcut is the one this test is about.
+ await menu.getByRole("navigation", { name: "Pinned shortcuts" }).getByRole("link", { name: "Medication" }).click();
await expect.poll(() => new URL(page.url()).pathname, { timeout: 30_000 }).toBe("/");
await expect.poll(() => new URL(page.url()).searchParams.get("mode"), { timeout: 30_000 }).toBe("prescribing");
@@ -1568,7 +1570,7 @@ test.describe("PsychSift UI smoke coverage", () => {
await moreModesSheet.getByRole("button", { name: "Close more modes" }).click();
expect(
await navigation.getByRole("link").evaluateAll((links) => links.map((link) => link.getAttribute("aria-label"))),
- ).toEqual(["My Day", "Answer", "Documents", "Services", "Medication", "Factsheets", "Forms", "Tools"]);
+ ).toEqual(["My Day", "Answer", "Documents", "Services", "Psychiatry", "Medication", "Forms", "Factsheets"]);
await expect(rail.getByTestId("sidebar-more-modes")).toBeVisible();
await expect(rail.getByTestId("sidebar-more-modes")).toBeFocused();
@@ -1584,14 +1586,18 @@ test.describe("PsychSift UI smoke coverage", () => {
{ path: "/documents", label: "Documents" },
{ path: "/favourites", label: "Favourites" },
{ path: "/medications", label: "Medication" },
- { path: "/tools", label: "Tools" },
+ // Tools left the menu (usefulness and layout audit, 11 Oct 2026); Psychiatry,
+ // which now holds Calculators and Dictionary, is pinned in its place.
+ { path: "/psychiatry", label: "Psychiatry" },
] as const) {
await gotoApp(page, route.path);
if (route.path.includes("mode=answer")) {
await waitForDemoDashboardReady(page);
}
- const activeLink = page.getByRole("link", { name: route.label, exact: true });
+ const activeLink = page
+ .getByLabel("PsychSift collapsed sidebar")
+ .getByRole("link", { name: route.label, exact: true });
await expect(activeLink).toBeVisible();
await expect(activeLink).toHaveAttribute("aria-current", "page");
}
diff --git a/tests/ui-tools.spec.ts b/tests/ui-tools.spec.ts
index a8c711bc9d..f5c918a97c 100644
--- a/tests/ui-tools.spec.ts
+++ b/tests/ui-tools.spec.ts
@@ -782,7 +782,8 @@ test.describe("PsychSift tools directory and legacy launcher", () => {
await expect(servicesMenu.getByRole("menuitemradio", { name: /^Forms\b/ })).toBeVisible();
await expect(servicesMenu.getByRole("menuitemradio", { name: /^Differentials\b/ })).toBeVisible();
await expect(servicesMenu.getByRole("menuitemradio", { name: /^Medication\b/ })).toBeVisible();
- await expect(servicesMenu.getByRole("menuitemradio", { name: /^Tools\b/ })).toBeVisible();
+ // Tools left the mode menu (usefulness and layout audit, 11 Oct 2026).
+ await expect(servicesMenu.getByRole("menuitemradio", { name: /^Tools\b/ })).toHaveCount(0);
const formsMode = servicesMenu.getByRole("menuitemradio", { name: /^Forms\b/ });
await waitForReactEventHandler(formsMode);
await formsMode.click();