Skip to content
11 changes: 5 additions & 6 deletions .agents/skills/operations/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,8 @@ description: Turn pending Database operator, provisioning, configuration, and pr

# Operations

1. Run `npm run workflow:operator-closeout -- --write-evidence`.
2. Inventory pending actions from docs, evidence, logs, and current change without executing them.
3. Deduplicate by outcome and order prerequisites, local proof, approval, execution, verification, and rollback.
4. Separate local/offline actions from GitHub, Supabase, OpenAI, hosting, credentials, and production work.
5. Ask for approval only when the batch is precise enough to execute safely.
6. Record owner, command, expected result, evidence, rollback, and unresolved dependency for each item.
1. Inventory pending actions from docs, evidence, logs, and current change without executing them.
2. Deduplicate by outcome and order prerequisites, local proof, approval, execution, verification, and rollback.
3. Separate local/offline actions from GitHub, Supabase, OpenAI, hosting, credentials, and production work.
4. Ask for approval only when the batch is precise enough to execute safely.
5. After approval, run `npm run workflow:operator-closeout -- --write-evidence` and record owner, command, expected result, evidence, rollback, and unresolved dependency for each item.
2 changes: 1 addition & 1 deletion .agents/skills/plan/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ description: Plan safe risk-scoped Database work by inspecting the current chang
# Plan

1. Complete the task-start preflight and preserve unrelated work.
2. Run `npm run workflow:flightplan -- --write-evidence`; add `--files pathA,pathB` for proposed paths.
2. Run `npm run workflow:flightplan`; add `--files pathA,pathB` for proposed paths. Add `--write-evidence` only when the user explicitly requests evidence capture.
3. Confirm the detected risk classes match behavior, not only filenames.
4. Start with the narrowest local check and widen only when warranted.
5. Never execute anything under `approvalRequired` without explicit confirmation.
Expand Down
6 changes: 3 additions & 3 deletions .agents/skills/review/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ description: Review the current Database diff, branch, or explicitly approved PR
# Review

1. Read `docs/codex-review-protocol.md` and `docs/branch-review-ledger.md` when present.
2. Resolve the local target SHA and check whether the same scope was already reviewed.
2. Resolve the target SHA/HEAD; skip merged, unchanged, or already-reviewed scopes.
3. Inspect changed behavior and realistic failure paths; prioritize reproducible P0-P2 findings.
4. Cite exact files and lines, trigger, impact, and the smallest proof or fix.
5. Do not call GitHub or hosted CI without explicit approval.
6. Record the completed local review in the ledger when repository instructions require it.
5. Do not run, modify, test, or otherwise interact with OpenAI, Supabase, GitHub/GitLab, hosted CI, production-like services, or provider-backed workflows without explicit user confirmation.
6. Record the completed local review in `docs/branch-review-ledger.md` whenever the ledger exists.
4 changes: 2 additions & 2 deletions .agents/skills/ui/SKILL.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,8 +6,8 @@ description: Inspect and verify the live Database interface across routes, break
# UI

1. Read the relevant Next.js guide under `node_modules/next/dist/docs/` before code changes.
2. Run `npm run workflow:design-sweep -- --write-evidence` and then `npm run ensure`.
3. Verify project identity before browser work; do not assume a port.
2. Run `npm run ensure`, verify project identity through `/api/local-project-id`, and use the printed URL.
3. Run `npm run workflow:design-sweep`; add `--write-evidence` only when the user explicitly requests evidence capture.
4. Inspect affected routes at phone and desktop widths plus keyboard, focus, reduced-motion, and forced-colors states.
5. Add the smallest focused browser proof, then use `npm run verify:ui` when proportionate.
6. Report routes, viewports, interactions, accessibility evidence, and residual visual risk.
2 changes: 1 addition & 1 deletion docs/operator-backlog.md
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ Findings inventory for handover: [audit-handover-2026-07-14.md](audit-handover-2
| Redeploy worker (one always-on instance) | ✅ done | — | Worker deployment recorded live 2026-07-14; re-verify with `npm run reindex:health` | [worker-deploy-runbook.md](worker-deploy-runbook.md) |
| Seed registry / differentials / medications (prod) | ⏳ pending | prod deploy | Services/Forms surfaces non-empty | [launch-operator-runbook.md §6](launch-operator-runbook.md) |
| Switch auth connection cap 10-absolute → percentage-based (dashboard) | ⏳ pending | before first vertical scale-up | dashboard — not SQL/MCP settable | [auth-connection-cap-runbook.md](auth-connection-cap-runbook.md) · [capacity-review.md](capacity-review.md) |
| Wire SLO warn/page thresholds into a real alert channel | ⏳ pending | host metrics exist | nightly eval canary green from `main` (one `workflow_dispatch`) | [observability-slos.md](observability-slos.md) |
| Wire SLO warn/page thresholds into a real alert channel | ⏳ pending | host metrics exist | weekly eval canary green from `main` (one `workflow_dispatch`) | [observability-slos.md](observability-slos.md) |

## Standing secret / config placement (per environment)

Expand Down
16 changes: 6 additions & 10 deletions src/components/DocumentViewer.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -1798,7 +1798,7 @@ export function DocumentViewer({
const canViewSourceDocuments = localProjectReady;
const canUsePrivateApis = localProjectReady && (clientDemoMode || authStatus === "authenticated");
const canUseAdministrativeApis =
localProjectReady && (serverDemoMode || (authStatus === "authenticated" && isAdministratorUser(session?.user)));
localProjectReady && !serverDemoMode && authStatus === "authenticated" && isAdministratorUser(session?.user);

useEffect(() => {
if (authStatus !== "loading") {
Expand Down Expand Up @@ -2283,16 +2283,12 @@ export function DocumentViewer({
async function summarize() {
if (!canSummarizeDocument) {
setSummaryError(
!canUseAdministrativeApis
? "Administrator access is required to summarise documents."
!canUsePrivateApis
? "Sign in before summarising private documents."
: "Load a source document before summarising.",
);
return;
}
if (!canUsePrivateApis) {
setSummaryError("Sign in before summarising private documents.");
return;
}
const summaryMode = sourceSearch.trim().length === 0;
const query = summaryMode ? documentSummaryQuestion : sourceSearch.trim();
const controller = new AbortController();
Expand Down Expand Up @@ -2411,11 +2407,11 @@ export function DocumentViewer({
? `/?mode=documents&q=${encodeURIComponent(documentDisplayTitle(readyDocument))}&documentId=${encodeURIComponent(documentId)}`
: documentHomeHref;
const usefulPageHref = (page: number) => documentPageHref(documentId, page);
const canSummarizeDocument = viewerState === "ready" && !loadingSummary && canUseAdministrativeApis;
const canSummarizeDocument = viewerState === "ready" && !loadingSummary && canUsePrivateApis;
const summarizeTitle = canSummarizeDocument
? "Answer from this document"
: !canUseAdministrativeApis
? "Administrator access is required to answer from this document"
: !canUsePrivateApis
? "Sign in required to answer from this document"
: "Load a source document before answering";
const pageByNumber = useMemo(() => new Map(pages.map((page) => [page.page_number, page])), [pages]);
const chunkById = useMemo(() => new Map(chunks.map((chunk) => [chunk.id, chunk])), [chunks]);
Expand Down
143 changes: 24 additions & 119 deletions src/components/clinical-dashboard/use-app-preferences.ts
Original file line number Diff line number Diff line change
@@ -1,8 +1,32 @@
"use client";

import { useCallback, useEffect, useSyncExternalStore } from "react";
import {
DEFAULT_PREFERENCES,
normalizePreferences,
type AppPreferences,
type LandingPreference,
} from "@/lib/account-preferences";
import { useAuthSession } from "@/lib/supabase/client";

export {
ANSWER_STYLE_OPTIONS,
DEFAULT_PREFERENCES,
DENSITY_OPTIONS,
JURISDICTION_OPTIONS,
LANDING_OPTIONS,
normalizePreferences,
POPULATION_OPTIONS,
} from "@/lib/account-preferences";
export type {
AnswerStylePreference,
AppPreferences,
DensityPreference,
LandingPreference,
MotionPreference,
PopulationPreference,
} from "@/lib/account-preferences";

const emptyAuthorizationHeader: Record<string, string> = {};
const ignoreExpiredSession = () => undefined;

Expand All @@ -22,83 +46,6 @@ function useAuthSessionIfAvailable() {
* stays in sync between open tabs. Nothing here is PHI; values are plain enums.
*/

export type DensityPreference = "comfortable" | "compact" | "spacious";
export type MotionPreference = "system" | "reduced";
export type PopulationPreference = "adults" | "older-adults" | "adolescents" | "all";
export type AnswerStylePreference = "conservative" | "balanced" | "comprehensive";
export type LandingPreference = "ask" | "search" | "browse";

export type AppPreferences = {
density: DensityPreference;
motion: MotionPreference;
jurisdiction: string;
population: PopulationPreference;
answerStyle: AnswerStylePreference;
landing: LandingPreference;
showRecentOnHome: boolean;
showProtocolsOnHome: boolean;
compactCitations: boolean;
notifyGuidelineUpdates: boolean;
notifyProductNews: boolean;
notifySavedChanges: boolean;
};

export const JURISDICTION_OPTIONS = [
{ value: "wa", label: "Western Australia" },
{ value: "nsw", label: "New South Wales" },
{ value: "vic", label: "Victoria" },
{ value: "qld", label: "Queensland" },
{ value: "sa", label: "South Australia" },
{ value: "tas", label: "Tasmania" },
{ value: "act", label: "Australian Capital Territory" },
{ value: "nt", label: "Northern Territory" },
{ value: "national", label: "National (Australia)" },
] as const;

export const POPULATION_OPTIONS: ReadonlyArray<{ value: PopulationPreference; label: string }> = [
{ value: "adults", label: "Adults" },
{ value: "older-adults", label: "Older adults" },
{ value: "adolescents", label: "Adolescents" },
{ value: "all", label: "All ages" },
];

export const ANSWER_STYLE_OPTIONS: ReadonlyArray<{
value: AnswerStylePreference;
label: string;
description: string;
}> = [
{ value: "conservative", label: "Conservative", description: "Guideline-first, cautious phrasing" },
{ value: "balanced", label: "Balanced", description: "Guidelines with practical context" },
{ value: "comprehensive", label: "Comprehensive", description: "Fuller detail and alternatives" },
];

export const DENSITY_OPTIONS: ReadonlyArray<{ value: DensityPreference; label: string }> = [
{ value: "comfortable", label: "Comfortable" },
{ value: "compact", label: "Compact" },
{ value: "spacious", label: "Spacious" },
];

export const LANDING_OPTIONS: ReadonlyArray<{ value: LandingPreference; label: string }> = [
{ value: "ask", label: "Ask" },
{ value: "search", label: "Search" },
{ value: "browse", label: "Browse" },
];

export const DEFAULT_PREFERENCES: AppPreferences = {
density: "comfortable",
motion: "system",
jurisdiction: "wa",
population: "adults",
answerStyle: "conservative",
landing: "ask",
showRecentOnHome: true,
showProtocolsOnHome: true,
compactCitations: false,
notifyGuidelineUpdates: true,
notifyProductNews: false,
notifySavedChanges: true,
};

const storageKey = "clinical-kb-preferences";
const changeEvent = "clinical-kb-preferences-change";

Expand All @@ -109,48 +56,6 @@ let inMemoryFallback: AppPreferences | null = null;
let cachedRaw: string | null = null;
let cachedValue: AppPreferences = DEFAULT_PREFERENCES;

function isPlainObject(value: unknown): value is Record<string, unknown> {
return typeof value === "object" && value !== null && !Array.isArray(value);
}

function coerceEnum<T extends string>(value: unknown, allowed: ReadonlyArray<T>, fallback: T): T {
return typeof value === "string" && (allowed as ReadonlyArray<string>).includes(value) ? (value as T) : fallback;
}

function coerceBoolean(value: unknown, fallback: boolean): boolean {
return typeof value === "boolean" ? value : fallback;
}

export function normalizePreferences(input: unknown): AppPreferences {
if (!isPlainObject(input)) return DEFAULT_PREFERENCES;
const jurisdiction =
typeof input.jurisdiction === "string" && JURISDICTION_OPTIONS.some((option) => option.value === input.jurisdiction)
? input.jurisdiction
: DEFAULT_PREFERENCES.jurisdiction;
return {
density: coerceEnum(input.density, ["comfortable", "compact", "spacious"], DEFAULT_PREFERENCES.density),
motion: coerceEnum(input.motion, ["system", "reduced"], DEFAULT_PREFERENCES.motion),
jurisdiction,
population: coerceEnum(
input.population,
["adults", "older-adults", "adolescents", "all"],
DEFAULT_PREFERENCES.population,
),
answerStyle: coerceEnum(
input.answerStyle,
["conservative", "balanced", "comprehensive"],
DEFAULT_PREFERENCES.answerStyle,
),
landing: coerceEnum(input.landing, ["ask", "search", "browse"], DEFAULT_PREFERENCES.landing),
showRecentOnHome: coerceBoolean(input.showRecentOnHome, DEFAULT_PREFERENCES.showRecentOnHome),
showProtocolsOnHome: coerceBoolean(input.showProtocolsOnHome, DEFAULT_PREFERENCES.showProtocolsOnHome),
compactCitations: coerceBoolean(input.compactCitations, DEFAULT_PREFERENCES.compactCitations),
notifyGuidelineUpdates: coerceBoolean(input.notifyGuidelineUpdates, DEFAULT_PREFERENCES.notifyGuidelineUpdates),
notifyProductNews: coerceBoolean(input.notifyProductNews, DEFAULT_PREFERENCES.notifyProductNews),
notifySavedChanges: coerceBoolean(input.notifySavedChanges, DEFAULT_PREFERENCES.notifySavedChanges),
};
}

function readStored(): AppPreferences {
let raw: string | null = null;
try {
Expand Down
4 changes: 2 additions & 2 deletions supabase/drift-manifest.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
{
"generated_at": "2026-07-19T10:43:08.249Z",
"generated_at": "2026-07-19T11:45:00.000Z",
"generator": "scripts/generate-drift-manifest.ts",
"postgres_image": "supabase/postgres:17.6.1.127",
"schema_sha256": "cf3e4a10899bbc691f21c1934be872ba01c9589302b9fd23528bb21e683b53ad",
"schema_sha256": "f6f54c9185ae5c0df0cfc3e4092c80d21bdd9d67ce965088648423ac7c7e5a96",
"replay_seconds": 44,
"snapshot": {
"views": [
Expand Down
Loading