Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions data/repo-awareness-snapshot.json
Original file line number Diff line number Diff line change
Expand Up @@ -2901,6 +2901,11 @@
"section": "organisation",
"catalogued": true
},
{
"path": "docs/decisions/2026-10-27-signed-out-examples-on-every-work-page.md",
"section": "decisions",
"catalogued": true
},
{
"path": "docs/decisions/2026-10-19-rag-ts-split-deferred-until-after-launch.md",
"section": "decisions",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Decision: example content on every Work mode page when signed out

- **Status:** decided 2026-10-27
- **Source:** the owner, in chat

On 27 Oct 2026 the owner asked: "Please ensure there is sample content on all modes for me in
logged out view", "And all pages in work mode", "To demonstrate what it looks like".

Every Work mode page was opened signed out. Most already showed example records. These pages
showed only a sign-in card, and now show clearly marked example content while example data is
on (the default for a signed-out visitor; turning example data off still gives the plain sign-in
state):

- CPD: AMA CPD Home, Evidence, Export, Customise and Learning. The old CPD Job applications links
follow on to Admin.
- Admin: Job applications and its CV, the Credential pack (built from the example records with
made-up all-zero numbers, never the numbers saved on the device), and Contract end (an example
contract ending five months out). The example contract is only in the signed-out example set,
never in "Load example content", so a real account never gets an end date it did not enter.
- My Day: Alerts settings, shown as a look-only example while signed out. Signed in, they are
always the reader's own.
- On Call: Who's on from your team roster.
- Roster: Manage team.
- Open shifts: the week board, Post, and Post a shift.

Left as they were, because they are forms or flows rather than records: Open shifts Alerts (the
same switches as Roster settings, which have nothing to show without phone alerts set up), Log a
shift, My Day Set up and Favourites, the On Call device tools, and Teaching check-in.

Nothing here can save: example screens refuse writes, as every example screen already did.

If this summary and the source ever differ, the source wins.
14 changes: 11 additions & 3 deletions src/app/(search-app)/admin/applications/cv/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import type { Metadata } from "next";

import { ApplicationsCvPage } from "@/components/cme/applications/applications-cv-page";
import { CmeStateNotice } from "@/components/cme/cme-state-notice";
import { SignedOutExampleSwitch } from "@/components/mode-kit/signed-out-example-switch";
import { DEMO_CME_ENTRIES, DEMO_CME_INSTANT } from "@/lib/cme/demo-year";
import { loadCmePageData } from "@/lib/cme/load-cme-page-data";

export const metadata: Metadata = {
Expand All @@ -14,9 +16,15 @@ export default async function CmeApplicationsCvRoute() {
const data = await loadCmePageData(undefined, { allYears: true });
if (data.state === "signed-out") {
return (
<main className="mx-auto w-full max-w-3xl px-4 py-6 sm:px-6">
<CmeStateNotice state="signed-out" year={data.year} heading="CV" />
</main>
<SignedOutExampleSwitch
area="admin"
example={<ApplicationsCvPage entries={DEMO_CME_ENTRIES} cpdFailed={false} demoMode now={DEMO_CME_INSTANT} />}
notice={
<main className="mx-auto w-full max-w-3xl px-4 py-6 sm:px-6">
<CmeStateNotice state="signed-out" year={data.year} heading="CV" />
</main>
}
/>
);
}
// A year with no CPD set-up still has other years' activities, read through allYears.
Expand Down
13 changes: 10 additions & 3 deletions src/app/(search-app)/admin/applications/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import type { Metadata } from "next";

import { ApplicationsPage } from "@/components/cme/applications/applications-page";
import { CmeStateNotice } from "@/components/cme/cme-state-notice";
import { SignedOutExampleSwitch } from "@/components/mode-kit/signed-out-example-switch";
import { cpdYearOf } from "@/lib/cme/cpd-year";
import { DEMO_CME_INSTANT } from "@/lib/cme/demo-year";
import { isDemoMode } from "@/lib/env";
Expand Down Expand Up @@ -32,9 +33,15 @@ export default async function CmeApplicationsRoute() {
const now = demoMode ? DEMO_CME_INSTANT : new Date();
if (!demoMode && (await signedOut())) {
return (
<main className="mx-auto w-full max-w-3xl px-4 py-6 sm:px-6">
<CmeStateNotice state="signed-out" year={cpdYearOf(now)} heading="Job applications" />
</main>
<SignedOutExampleSwitch
area="admin"
example={<ApplicationsPage demoMode now={DEMO_CME_INSTANT} />}
notice={
<main className="mx-auto w-full max-w-3xl px-4 py-6 sm:px-6">
<CmeStateNotice state="signed-out" year={cpdYearOf(now)} heading="Job applications" />
</main>
}
/>
);
}
return <ApplicationsPage demoMode={demoMode} now={now} />;
Expand Down
6 changes: 5 additions & 1 deletion src/app/(search-app)/admin/contract/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import { Suspense } from "react";

import { ContractEndPage } from "@/components/admin/contract/contract-end-page";
import { ModeHomeRouteLoading } from "@/components/mode-home-page-skeleton";
import { RosterSampleGate } from "@/components/roster/roster-sample-gate";

export const metadata: Metadata = {
title: "Contract end | Admin | PsychSift",
Expand All @@ -15,7 +16,10 @@ export default function AdminContractRoute() {
// Suspense boundary in the App Router.
return (
<Suspense fallback={<ModeHomeRouteLoading />}>
<ContractEndPage />
{/* The leave-before-the-end list reads Roster, so signed out it reads Roster's example leave. */}
<RosterSampleGate>
<ContractEndPage />
</RosterSampleGate>
</Suspense>
);
}
8 changes: 7 additions & 1 deletion src/app/(search-app)/on-call/whos-on/roster/page.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata } from "next";

import { OnCallRosterWhosOnPage } from "@/components/on-call/roster-whos-on/on-call-roster-whos-on-page";
import { RosterSampleGate } from "@/components/roster/roster-sample-gate";

export const metadata: Metadata = {
title: "From your team roster | On Call | PsychSift",
Expand All @@ -9,5 +10,10 @@ export const metadata: Metadata = {
};

export default function OnCallRosterWhosOnRoute() {
return <OnCallRosterWhosOnPage />;
// Who is on reads Roster, so signed out it shows Roster's example team.
return (
<RosterSampleGate>
<OnCallRosterWhosOnPage />
</RosterSampleGate>
);
}
7 changes: 6 additions & 1 deletion src/app/(search-app)/open-shifts/board/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { Metadata } from "next";

import { OpenShiftsBoardPage } from "@/components/open-shifts/open-shifts-board-page";
import { RosterSampleGate } from "@/components/roster/roster-sample-gate";

export const metadata: Metadata = {
title: "Board | Open shifts | PsychSift",
description: "Every open shift in your Roster teams at a glance.",
};

export default function OpenShiftsBoardRoute() {
return <OpenShiftsBoardPage />;
return (
<RosterSampleGate>
<OpenShiftsBoardPage />
</RosterSampleGate>
);
}
7 changes: 6 additions & 1 deletion src/app/(search-app)/open-shifts/post/new/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { Metadata } from "next";

import { OpenShiftsPostPage } from "@/components/open-shifts/open-shifts-post-page";
import { RosterSampleGate } from "@/components/roster/roster-sample-gate";

export const metadata: Metadata = {
title: "Post a shift | Open shifts | PsychSift",
description: "Advertise an extra shift to your Roster team.",
};

export default function OpenShiftsPostRoute() {
return <OpenShiftsPostPage />;
return (
<RosterSampleGate>
<OpenShiftsPostPage />
</RosterSampleGate>
);
}
7 changes: 6 additions & 1 deletion src/app/(search-app)/open-shifts/post/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { Metadata } from "next";

import { OpenShiftsPostedPage } from "@/components/open-shifts/open-shifts-posted-page";
import { RosterSampleGate } from "@/components/roster/roster-sample-gate";

export const metadata: Metadata = {
title: "Post | Open shifts | PsychSift",
description: "Extra shifts you have posted for your Roster teams.",
};

export default function OpenShiftsPostedRoute() {
return <OpenShiftsPostedPage />;
return (
<RosterSampleGate>
<OpenShiftsPostedPage />
</RosterSampleGate>
);
}
7 changes: 6 additions & 1 deletion src/app/(search-app)/roster/manage/page.tsx
Original file line number Diff line number Diff line change
@@ -1,12 +1,17 @@
import type { Metadata } from "next";

import { RosterManagePage } from "@/components/roster/manage/roster-manage-page";
import { RosterSampleGate } from "@/components/roster/roster-sample-gate";

export const metadata: Metadata = {
title: "Manage team | Roster | PsychSift",
description: "For roster managers: cover needs, swap and leave decisions, and your team's shifts.",
};

export default function Page() {
return <RosterManagePage />;
return (
<RosterSampleGate>
<RosterManagePage />
</RosterSampleGate>
);
}
28 changes: 23 additions & 5 deletions src/components/admin/new-job/admin-credential-pack-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,14 @@ import { guardExampleAction } from "@/lib/example-data/guards";
* The wallet is device-only, so the pack is offered only when signed in and
* not in the example corpus, the same rule Admin Today uses for the wallet.
*/
/** Made-up numbers for the signed-out example, shaped like the real ones but all zeros. */
const EXAMPLE_PACK_NUMBERS: CredentialPackNumbers = {
ahpraNumber: "MED0000000000",
prescriberNumber: "0000000",
wwccNumber: "0000000",
providerNumbers: [{ id: "example-provider", site: "Example Hospital", number: "000000AB" }],
};

export function AdminCredentialPackPage({ now: nowProp }: { now?: Date } = {}) {
const state = useOnCallEntries();
const { isAuthenticated } = useAccountData();
Expand All @@ -55,7 +63,11 @@ export function AdminCredentialPackPage({ now: nowProp }: { now?: Date } = {}) {
const [numbers, setNumbers] = useState<CredentialPackNumbers>(() => loadDoctorCredentials());
useEffect(() => subscribeAccountTransition(() => setNumbers({})), []);

const sections = useMemo(() => buildCredentialPack({ numbers, ownEntries: own }), [numbers, own]);
// Signed out with example data showing, the pack is built from the example records and made-up
// numbers, so the page can be seen working. Copy, share and save stay off for examples.
const signedOutExample = !isAuthenticated && state.sample;
const packNumbers = signedOutExample ? EXAMPLE_PACK_NUMBERS : numbers;
const sections = useMemo(() => buildCredentialPack({ numbers: packNumbers, ownEntries: own }), [packNumbers, own]);
const [excluded, setExcluded] = useState<ReadonlySet<string>>(() => new Set());
const included = useMemo(() => includedCredentialPack(sections, excluded), [sections, excluded]);
const text = useMemo(() => credentialPackText(included, now), [included, now]);
Expand Down Expand Up @@ -92,7 +104,7 @@ export function AdminCredentialPackPage({ now: nowProp }: { now?: Date } = {}) {
);
}

const available = isAuthenticated && !state.demoMode;
const available = (isAuthenticated && !state.demoMode) || signedOutExample;
Comment thread
BigSimmo marked this conversation as resolved.
Resolved
const ready = loadState === "ready" && available;
const nothingIncluded = included.length === 0;

Expand Down Expand Up @@ -159,6 +171,11 @@ export function AdminCredentialPackPage({ now: nowProp }: { now?: Date } = {}) {
</WorkCard>
) : ready ? (
<>
{signedOutExample ? (
<AdminNote testId="admin-credential-pack-example">
These are example records with made-up numbers. Sign in to make a pack from your own.
</AdminNote>
) : null}
<section className={cn(adminStyles.section, "print:hidden")} aria-labelledby="pack-choose">
<WorkSectionLabel id="pack-choose">Choose what goes in</WorkSectionLabel>
<ul className="work-card work-rows" data-testid="admin-credential-pack-choose">
Expand Down Expand Up @@ -189,12 +206,13 @@ export function AdminCredentialPackPage({ now: nowProp }: { now?: Date } = {}) {
</ul>
</section>

{/* Only this section prints: the shared print rule hides the app around `data-print-output`. */}
{/* Only this section prints: the shared print rule hides the app around `data-print-output`.
The example pack never prints: a browser Print would otherwise skip the Save as PDF guard. */}
<section
className={adminStyles.section}
className={cn(adminStyles.section, signedOutExample && "print:hidden")}
aria-label="Preview"
data-testid="admin-credential-pack-preview"
data-print-output
data-print-output={signedOutExample ? undefined : true}
>
<div data-print-hide>
<WorkSectionLabel>Preview</WorkSectionLabel>
Expand Down
26 changes: 26 additions & 0 deletions src/components/alerts/alerts-page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,11 @@ import { usePhoneAlerts } from "@/components/alerts/use-phone-alerts";
import { useRemindMe } from "@/components/alerts/use-remind-me";
import { useAppPreferences } from "@/components/clinical-dashboard/use-app-preferences";
import { ModeGroupedList, ModeRow } from "@/components/mode-kit/grouped-list";
import { SignedOutSampleNotice } from "@/components/mode-kit/signed-out-sample";
import { useSignedOut } from "@/components/mode-kit/use-signed-out-sample";
import { WorkTag } from "@/components/mode-kit/work";
import { MyDayFrame } from "@/components/my-day/my-day-frame";
import { RosterSampleGate } from "@/components/roster/roster-sample-gate";
import { useRosterSettings } from "@/components/roster/use-roster-settings";
import { useWorkModeRouteVisible } from "@/components/work-mode-launch/work-mode-launch-provider";
import { ALERT_AREA_IDS, ALERT_AREAS, areaSummary, type AlertAreaId } from "@/lib/alerts/areas";
Expand Down Expand Up @@ -47,13 +50,17 @@ export function AlertsPage({
*/
inFrame?: boolean;
} = {}) {
// Signed out, the settings show as an example to look at (the frame adds it only while My Day shows
// example data). Signed in, they are always the reader's own, never an example.
const signedOut = useSignedOut();
return (
<MyDayFrame
title={inFrame ? "Settings" : "Alerts"}
testId="my-day-alerts"
now={now}
wide
subtitle={() => "What reaches your phone, and when"}
signedOutSample={signedOut ? { render: (at) => <AlertsSignedOutSample now={at} inFrame={inFrame} /> } : undefined}
signedOut={{
title: "Sign in to get alerts",
body: "Alerts come from your own roster and records, so they need your account. Nothing is sent to this device while you're signed out.",
Expand All @@ -64,6 +71,25 @@ export function AlertsPage({
);
}

/**
* The settings as a signed-in doctor sees them, to look at only: `inert` keeps every switch and sheet
* shut, so nothing is changed on this device or sent. Roster's switches read Roster's example data.
*/
function AlertsSignedOutSample({ now, inFrame }: { now: Date; inFrame: boolean }) {
return (
<>
<SignedOutSampleNotice title="Sign in to choose your alerts" testId="alerts-signed-out-sample">
This is how the settings look. Signed in, you choose what reaches your phone, and when.
</SignedOutSampleNotice>
<RosterSampleGate>
<div inert data-testid="alerts-signed-out-sample-settings">
<AlertsBody now={now} inFrame={inFrame} />
</div>
</RosterSampleGate>
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Resolved
</>
);
}

/** Each area's icon, as the notification centre draws it. */
const AREA_ICONS: Readonly<Record<AlertAreaId, LucideIcon>> = {
roster: CalendarDays,
Expand Down
Loading
Loading