// The reviewer session identity: the name and role every confirmation is attributed to. It comes from // the per-user login (#128), set once when a reviewer signs in (LoginBar.tsx) and cleared on sign-out, // not a field re-entered on each decision. PRD sections 4.4 and 8.4: each confirmed value records who // confirmed it and in what role, so the identity has to be the session's. // // localStorage rather than sessionStorage, matching the session token: a reload should not drop a // reviewer out of their session. The corpus is public de-identified TCGA data, and this is a // local-first tool. // // Identity changes are observable. setIdentity and clearIdentity dispatch a window CustomEvent so a // screen reflects login and logout without a reload; subscribeIdentity also listens for the cross-tab // "storage" event so a sign-in in another tab updates this one. const NAME_KEY = "endopath.reviewerName"; const ROLE_KEY = "endopath.reviewerRole"; const IDENTITY_EVENT = "endopath:identity"; // The roles PRD section 4.4 distinguishes: a licensed pathologist confirms the pathology variables; a // data manager or curator abstracts the rest. Offered as a fixed set so the recorded role reads // consistently across the ledger. export const REVIEWER_ROLES = ["pathologist", "data manager", "curator"] as const; export type ReviewerRole = (typeof REVIEWER_ROLES)[number]; export interface ReviewerIdentity { name: string; role: string; } export function getIdentity(): ReviewerIdentity { return { name: localStorage.getItem(NAME_KEY) ?? "", role: localStorage.getItem(ROLE_KEY) ?? "", }; } export function setIdentity(identity: ReviewerIdentity): void { localStorage.setItem(NAME_KEY, identity.name.trim()); localStorage.setItem(ROLE_KEY, identity.role.trim()); window.dispatchEvent(new CustomEvent(IDENTITY_EVENT)); } export function clearIdentity(): void { localStorage.removeItem(NAME_KEY); localStorage.removeItem(ROLE_KEY); window.dispatchEvent(new CustomEvent(IDENTITY_EVENT)); } export function hasIdentity(): boolean { const { name, role } = getIdentity(); return name.trim().length > 0 && role.trim().length > 0; } // Subscribe to identity changes: the in-tab CustomEvent from setIdentity/clearIdentity, and the // cross-tab "storage" event so a sign-in or sign-out in another tab updates this one. Returns an // unsubscribe. export function subscribeIdentity(cb: () => void): () => void { window.addEventListener(IDENTITY_EVENT, cb); window.addEventListener("storage", cb); return () => { window.removeEventListener(IDENTITY_EVENT, cb); window.removeEventListener("storage", cb); }; }