diff --git a/src/components/open-shifts/open-shifts-browse-page.tsx b/src/components/open-shifts/open-shifts-browse-page.tsx
index 38219a61c8..c00c69d5ba 100644
--- a/src/components/open-shifts/open-shifts-browse-page.tsx
+++ b/src/components/open-shifts/open-shifts-browse-page.tsx
@@ -20,6 +20,7 @@ import { rosterCoveredUntil } from "@/lib/open-shifts/roster-check";
import { perthDateOf, perthTimeOf } from "@/lib/roster/shifts/perth-time";
import { OpenShiftsCalendar } from "./open-shifts-calendar";
+import { UrgentDot } from "./urgent-dot";
import { OpenShiftsFitsYou, OpenShiftsRequestsStrip, fitsYou } from "./open-shifts-for-you";
import { clearSavedFilters, readSavedFilters, saveFilters } from "./open-shifts-saved-filters";
import { useSignedOut } from "@/components/mode-kit/use-signed-out-sample";
@@ -315,9 +316,9 @@ export function OpenShiftsBrowsePage() {
-
+
Includes an urgent shift
diff --git a/src/components/open-shifts/open-shifts-calendar.tsx b/src/components/open-shifts/open-shifts-calendar.tsx
index fa80baf1e3..96312cc08f 100644
--- a/src/components/open-shifts/open-shifts-calendar.tsx
+++ b/src/components/open-shifts/open-shifts-calendar.tsx
@@ -1,21 +1,22 @@
"use client";
-import { ChevronLeft, ChevronRight, TriangleAlert } from "lucide-react";
+import { ChevronLeft, ChevronRight } from "lucide-react";
import { useState } from "react";
+import { UrgentDot } from "./urgent-dot";
import { cn } from "@/components/ui-primitives";
import { addDaysToDate } from "@/lib/roster/shifts/perth-time";
/**
* The month on Browse. Each date in the 14-day window carries the number of
* shifts that match the filters (the same rows as the list below it); a date
- * with an urgent one carries a small warning mark, and a ring on the date
+ * with an urgent one carries a small amber dot, and a ring on the date
* means the reader is rostered that day. Past dates and dates outside the
* window carry no number.
*
* Drawn like the Roster month (mockup `.rost-mc rost-oc`, work-mode redesign
- * 6 Oct 2026): a filled tile for the chosen day, a count pill per day (amber,
- * with the warning mark, when it includes an urgent shift) and a ring on the
+ * 6 Oct 2026): a filled tile for the chosen day, a count pill per day (with an
+ * amber dot, the shared "needs attention" shape, when it includes an urgent shift) and a ring on the
* date when you're rostered. The meanings never rest on colour alone: the
* mark, the number and each day's spoken label carry them too.
*/
@@ -173,14 +174,10 @@ export function OpenShiftsCalendar({
- {urgent ? : null}
- {count ? count : null}
+ {urgent ? {count || null} : count || null}
) : (
diff --git a/src/components/open-shifts/open-shifts-ui.tsx b/src/components/open-shifts/open-shifts-ui.tsx
index ccaa004660..aa8f1346cc 100644
--- a/src/components/open-shifts/open-shifts-ui.tsx
+++ b/src/components/open-shifts/open-shifts-ui.tsx
@@ -1,6 +1,7 @@
"use client";
import { ChevronRight, CircleHelp, CircleX, Info, TriangleAlert } from "lucide-react";
+
import Link from "next/link";
import type { ReactNode } from "react";
@@ -17,6 +18,8 @@ import {
import type { RosterCheck } from "@/lib/open-shifts/roster-check";
import { MONTHS, WEEKDAYS, perthDateOf, perthTimeOf } from "@/lib/roster/shifts/perth-time";
import { zonedDateOf } from "@/lib/work-time/format";
+
+import { UrgentDot } from "./urgent-dot";
import { currentWorkTimeZone } from "@/lib/work-time/current-zone";
import { WorkPageBack } from "@/components/work-frame/work-page-back";
@@ -181,12 +184,7 @@ export function ShiftRow({
status?: ReactNode;
}) {
const hours = hoursBetween(listing.startsAt, listing.endsAt);
- const meta = [
- endsNextDay(listing.startsAt, listing.endsAt) ? "Ends next day" : null,
- listing.urgent ? "Urgent" : null,
- ]
- .filter(Boolean)
- .join(" · ");
+ const meta = endsNextDay(listing.startsAt, listing.endsAt) ? "Ends next day" : null;
return (
{shiftPlace(listing)}
- {meta ? {meta} : null}
+ {meta || listing.urgent ? (
+
+ {listing.urgent ? Urgent : null}
+ {meta && listing.urgent ? " · " : null}
+ {meta}
+
+ ) : null}
{status ?? (check ? {} : null)}
+
+ {children}
+ >
+ );
+}
diff --git a/tests/open-shifts-browse-sample.dom.test.tsx b/tests/open-shifts-browse-sample.dom.test.tsx
index 2ea4d56c56..8bcc458a97 100644
--- a/tests/open-shifts-browse-sample.dom.test.tsx
+++ b/tests/open-shifts-browse-sample.dom.test.tsx
@@ -69,6 +69,19 @@ describe("Open shifts, signed-out preview", () => {
).toBeTruthy();
});
+ it("marks urgent rows and the calendar key with the shared dot", () => {
+ render();
+
+ const legend = screen.getByRole("list", { name: "Calendar key" });
+ expect(legend.querySelector('[data-mark="dot"]')).not.toBeNull();
+ expect(legend.querySelector("svg.lucide-triangle-alert")).toBeNull();
+
+ const urgentRow = screen.getAllByRole("listitem").find((row) => row.textContent?.includes("Urgent"));
+ expect(urgentRow).toBeDefined();
+ expect(urgentRow?.querySelector('[data-mark="dot"]')).not.toBeNull();
+ expect(urgentRow?.querySelector("svg.lucide-triangle-alert")).toBeNull();
+ });
+
it("marks the days the example roster has the doctor working", () => {
render();
// Mon 5 Oct: on call tonight, off Tuesday, day shifts Wednesday to Friday.
diff --git a/tests/open-shifts-calendar.dom.test.tsx b/tests/open-shifts-calendar.dom.test.tsx
index 3ae1629e02..712de3f4bc 100644
--- a/tests/open-shifts-calendar.dom.test.tsx
+++ b/tests/open-shifts-calendar.dom.test.tsx
@@ -35,6 +35,10 @@ describe("Open shifts Browse calendar", () => {
name: "Friday 9 October, 3 shifts match, includes an urgent shift",
}),
).toBeTruthy();
+ // The urgent day carries the shared amber dot, never the overdue triangle.
+ const urgentDay = screen.getByRole("button", { name: /^Friday 9 October/ });
+ expect(urgentDay.querySelector('[data-mark="dot"]')).not.toBeNull();
+ expect(urgentDay.querySelector("svg.lucide-triangle-alert")).toBeNull();
expect(screen.getByRole("button", { name: "Monday 12 October, you're rostered" })).toBeTruthy();
// A past rostered day is not called rostered.
expect(screen.getByRole("button", { name: "Thursday 1 October" })).toBeTruthy();