Skip to content
Closed
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
8 changes: 4 additions & 4 deletions playwright.config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,10 +44,9 @@ export default defineConfig({
screenshot: "only-on-failure",
// Disable CSS/web animations suite-wide so a click can't land mid-transition
// on a moving target (documented races in ui-stress/ui-smoke). The dedicated
// reduced-motion a11y spec emulates it per-test too, so it is unaffected.
contextOptions: {
reducedMotion: "reduce",
},
// reduced-motion a11y spec emulates a per-test mode, so suite-wide settings
// remain stable across builds.
contextOptions: { reducedMotion: "reduce" },
},
projects: [
{
Expand All @@ -56,6 +55,7 @@ export default defineConfig({
grepInvert: mockupTag,
use: {
...devices["Desktop Chrome"],
contextOptions: { reducedMotion: "no-preference" },
...(chromiumExecutablePath ? { launchOptions: { executablePath: chromiumExecutablePath } } : {}),
},
},
Expand Down
7 changes: 1 addition & 6 deletions src/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,7 @@ import { HomePageClient } from "@/app/home-page-client";
import { appModeHomeHref, isAppModeId, isAppModeVisible, type AppModeId } from "@/lib/app-modes";

type HomeProps = {
searchParams?: Promise<{
mode?: string | string[];
q?: string | string[];
focus?: string | string[];
run?: string | string[];
}>;
searchParams?: Promise<Record<string, string | string[] | undefined>>;
};

function firstSearchParam(value: string | string[] | undefined) {
Expand Down
8 changes: 4 additions & 4 deletions src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3716,6 +3716,10 @@ export function ClinicalDashboard({
</div>
) : null)}

{showUniversalAlsoMatches && activeModeResultKind !== "answer" ? (
<UniversalSearchAlsoMatches modeId={searchMode} query={universalAlsoMatchesQuery} />
) : null}

{activeModeResultKind === "differentials" ? (
<DifferentialsHome
query={query}
Expand Down Expand Up @@ -3876,10 +3880,6 @@ export function ClinicalDashboard({
/>
) : null}

{showUniversalAlsoMatches && activeModeResultKind !== "answer" ? (
<UniversalSearchAlsoMatches modeId={searchMode} query={universalAlsoMatchesQuery} />
) : null}

{showUniversalAlsoMatches && activeModeResultKind === "answer" ? (
<UniversalSearchAlsoMatches modeId={searchMode} query={universalAlsoMatchesQuery} />
) : null}
Expand Down
2 changes: 1 addition & 1 deletion src/components/route-error-boundary.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,7 @@ export function RouteErrorBoundary({
<h1
ref={headingRef}
tabIndex={-1}
className="mt-4 text-lg font-semibold tracking-tight text-[color:var(--text-heading)] focus:outline focus:outline-2 focus:outline-offset-2 focus:outline-[color:var(--focus-ring,Highlight)]"
className="mt-4 text-lg font-semibold tracking-tight text-[color:var(--text-heading)] focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-[color:var(--focus-ring,Highlight)]"
Comment on lines 59 to +62

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== route-error-boundary.tsx ==\n'
sed -n '1,220p' src/components/route-error-boundary.tsx

printf '\n== focus-related usages ==\n'
rg -n "focus-visible:outline|focus:outline|headingRef|tabIndex=\{-1\}|focus\(\)" src/components src -g '!**/*.map' || true

Repository: BigSimmo/Database

Length of output: 50374


🌐 Web query:

CSS :focus-visible programmatic focus non-interactive element browser behavior

💡 Result:

When using the programmatic .focus() method on a non-interactive element (e.g., a

with tabindex="-1"), the :focus-visible pseudo-class does not typically match by default [1][2]. This occurs because the browser's heuristic for :focus-visible generally suppresses the focus indicator when the focus change is triggered by a pointer interaction or a script, aiming to match native behavior where focus rings are often hidden unless a keyboard-driven navigation path is detected [3][4][5]. Key behaviors and workarounds include: Programmatic Focus Limitations: By default, calling element.focus() does not trigger :focus-visible because the user agent's heuristic often classifies programmatic focus as a non-keyboard-driven event [2][6]. Forcing Visibility: To ensure a focus indicator appears regardless of the browser's internal heuristics, you can use the focusVisible option in the HTMLElement.focus() method [6][7]: element.focus({ focusVisible: true }); This explicitly tells the browser to treat the focus as "visible," causing the :focus-visible pseudo-class to match [2][6]. Browser Inconsistencies: While the focusVisible option is now widely supported in modern browsers (e.g., Firefox 104+, Chrome, Safari), behavior for non-interactive elements without this option can still vary slightly across engines [1][7]. Non-interactive elements (like those with tabindex="-1") that are focusable via script but not by keyboard navigation may not consistently show a focus ring depending on how the browser's focus management heuristics classify the triggering event [1][5]. Recommendations: Use the { focusVisible: true } option if you want to guarantee that your :focus-visible styles apply to an element receiving programmatic focus [2][6]. Avoid relying on implicit browser heuristics to show focus on non-interactive elements that are not part of your natural keyboard tab order, as this behavior is not guaranteed to be consistent across different browsers or future spec updates [1][4].

Citations:


Preserve the focus outline on the imperatively focused heading. headingRef.current?.focus() can land on this h1 without matching :focus-visible in some browsers, so sighted users may lose the visible focus location. Keep focus:outline here, or switch to focus({ focusVisible: true }) if you want to rely on focus-visible styling.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/components/route-error-boundary.tsx` around lines 59 - 62, Update the
focused heading in the route error boundary, identified by headingRef and its h1
className, to preserve a visible outline when focused imperatively. Add a
focus-based outline style while retaining the existing focus-visible styling, or
explicitly request focus-visible behavior through headingRef.current?.focus({
focusVisible: true }).

>
{title}
</h1>
Expand Down
22 changes: 17 additions & 5 deletions src/lib/rag-candidate-sources.ts
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,13 @@ import type { DocumentIndexUnitMatch, DocumentMemoryCard, SearchResult } from "@
// the floor, which is how the live schema drift (42702) went unnoticed. Log it structurally and,
// where telemetry is in scope, record the failing RPC + code so it shows up in rag_retrieval_logs.
export type SupabaseRpcError = { message?: string; code?: string; details?: string; hint?: string } | null;
type RpcResult<T> = Promise<{ data: T | null; error: SupabaseRpcError }>;
type AbortableRpc<T> = RpcResult<T> & {
abortSignal?: (signal: AbortSignal) => RpcResult<T>;
};
type SupabaseRpcClient = {
rpc: (name: string, rpcArgs: Record<string, unknown>) => AbortableRpc<unknown[]> | PromiseLike<unknown>;
};

function legacyRankFields(versionedName: string) {
if (versionedName === "match_document_chunks_v2") return ["similarity"];
Expand Down Expand Up @@ -81,15 +88,20 @@ export async function callVersionedRetrievalRpc<T extends unknown[] = unknown[]>
versionedName: string,
legacyName: string,
args: Record<string, unknown>,
signal?: AbortSignal,
): Promise<{ data: T | null; error: SupabaseRpcError }> {
const client = supabase as unknown as {
rpc: (name: string, rpcArgs: Record<string, unknown>) => Promise<{ data: T | null; error: SupabaseRpcError }>;
const client = supabase as unknown as SupabaseRpcClient;
const executeRpc = async (name: string, rpcArgs: Record<string, unknown>) => {
const pending = client.rpc(name, rpcArgs) as AbortableRpc<T>;
const pendingWithAbort =
signal && typeof pending.abortSignal === "function" ? pending.abortSignal(signal) : pending;
return await pendingWithAbort;
};
Comment on lines +91 to 99

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🩺 Stability & Availability | 🟠 Major | ⚡ Quick win

Forward args.signal at both retrieval call sites.

Lines 91-99 only apply cancellation when a signal is supplied, but src/lib/rag.ts Lines 2837-2849 and 2945-2956 omit it. Both vector RPC paths therefore continue after caller abort.

Proposed fix
 const { data, error } = await callVersionedRetrievalRpc(
   supabase,
   "match_document_chunks_hybrid_v2",
   "match_document_chunks_hybrid",
   {
     // ...
   },
+  args.signal,
 );

Apply the same fifth argument to the fallback call at src/lib/rag.ts Lines 2945-2956.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/rag-candidate-sources.ts` around lines 91 - 99, Forward the caller’s
args.signal through both vector RPC retrieval call sites in rag.ts, including
the fallback path, so the executeRpc cancellation logic in
rag-candidate-sources.ts receives it. Update the calls near the existing
retrieval paths without changing their other arguments or behavior.

const versioned = await client.rpc(versionedName, args);
const versioned = await executeRpc(versionedName, args);
if (versioned && !isMissingRetrievalRpcError(versioned.error)) return versioned;
const legacyArgs = { ...args };
delete legacyArgs.include_public;
const ownerResult = await client.rpc(legacyName, legacyArgs);
const ownerResult = await executeRpc(legacyName, legacyArgs);
const ownerFilter = String(args.owner_filter ?? "");
if (
ownerResult.error ||
Expand All @@ -99,7 +111,7 @@ export async function callVersionedRetrievalRpc<T extends unknown[] = unknown[]>
) {
return ownerResult;
}
const publicResult = await client.rpc(legacyName, {
const publicResult = await executeRpc(legacyName, {
...legacyArgs,
owner_filter: PUBLIC_OWNER_FILTER_SENTINEL,
});
Expand Down
49 changes: 45 additions & 4 deletions src/lib/rag.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import { createAdminClient } from "@/lib/supabase/admin";
import { retrievalAccessScopeForArgs, retrievalRpcScopeArgs } from "@/lib/owner-scope";
import {
callVersionedRetrievalRpc,
createChunkLoadCache,
memoryCardChunkScore,
mergeSearchResults,
recordHybridRpcError,
Expand All @@ -11,7 +12,6 @@ import {
searchTableFactCandidates,
searchTextChunkCandidates,
withMemoryBoostedCandidates,
createChunkLoadCache,
type MemoryCardCache,
} from "@/lib/rag-candidate-sources";
export {
Expand Down Expand Up @@ -120,6 +120,11 @@ export {
retrievalPlanCacheQuery,
} from "@/lib/rag-cache";
import { classifySearchCacheOutcome, recordCacheLookup } from "@/lib/observability/cache-metrics";
import {
recordAnswerOrigination,
recordAnswerOriginationFinished,
recordCoalescedAnswerWaiter,
} from "@/lib/observability/answer-coalescing-metrics";
import { buildRagSourceBlock, compactContextText, neutralizeIdentityField } from "@/lib/rag-source-block";
export { buildRagSourceBlock, truncateForModel } from "@/lib/rag-source-block";
import {
Expand Down Expand Up @@ -423,6 +428,26 @@ function throwIfAborted(signal?: AbortSignal) {
}
}

function awaitWithCallerSignal<T>(pending: Promise<T>, signal?: AbortSignal): Promise<T> {
if (!signal) return pending;
if (signal.aborted) throw signal.reason ?? new DOMException("The operation was aborted.", "AbortError");

return new Promise<T>((resolve, reject) => {
const onAbort = () => reject(signal.reason ?? new DOMException("The operation was aborted.", "AbortError"));
signal.addEventListener("abort", onAbort, { once: true });
pending.then(
(value) => {
signal.removeEventListener("abort", onAbort);
resolve(value);
},
(error) => {
signal.removeEventListener("abort", onAbort);
reject(error);
},
);
});
}

export type AnswerProgressEvent = {
stage:
| "retrieved"
Expand Down Expand Up @@ -1290,6 +1315,7 @@ export async function analyzeQueryWithClassifierFallback(
// owner_filter retrieval will use so grounding can never see documents retrieval cannot.
corpusGrounding?: { supabase: ReturnType<typeof createAdminClient>; ownerFilter: string | null };
ownerId?: string | null;
signal?: AbortSignal;
},
) {
if (
Expand Down Expand Up @@ -1361,10 +1387,16 @@ export async function analyzeQueryWithClassifierFallback(
}

try {
const verdict = await pending;
const verdict = await awaitWithCallerSignal(pending, opts?.signal);
storeClassifierVerdictMemo(memoKey, verdict);
return applyClassifierVerdict(analysis, verdict);
} catch {
} catch (error) {
if (
error &&
(error instanceof DOMException || typeof error === "object") &&
(error as { name?: string }).name === "AbortError"
)
throw error;
Comment on lines +1393 to +1399

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
node - <<'NODE'
const controller = new AbortController();
const reason = new Error("caller cancelled");
controller.abort(reason);
console.log(controller.signal.aborted, controller.signal.reason === reason, controller.signal.reason.name);
NODE

Repository: BigSimmo/Database

Length of output: 171


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the relevant region around the reported lines
sed -n '1365,1415p' src/lib/rag.ts

echo
echo '---'
echo

# Find the helper that awaits with the caller signal
rg -n "awaitWithCallerSignal|signal\.reason|AbortError|opts\?\.signal\?\.aborted" src/lib/rag.ts

Repository: BigSimmo/Database

Length of output: 2872


Propagate caller aborts here src/lib/rag.ts:1393-1399awaitWithCallerSignal() can reject with signal.reason, so an AbortController.abort(new Error(...)) falls through to return analysis instead of cancelling. Re-throw when opts?.signal?.aborted (or when error === opts?.signal?.reason) before treating it as a transport failure.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/lib/rag.ts` around lines 1393 - 1399, Update the error handling in
awaitWithCallerSignal so caller-triggered aborts are re-thrown when
opts?.signal?.aborted or error equals opts?.signal?.reason, including custom
abort reasons. Perform this check before the existing AbortError transport check
and preserve the current fallback that returns analysis for non-abort failures.

// Transport/parse failures are deliberately NOT memoized: fall back to the deterministic
// analysis for this request only, and let the next request retry the classifier.
return analysis;
Expand Down Expand Up @@ -2398,6 +2430,7 @@ export async function searchChunksWithTelemetry(args: SearchChunksArgs) {
const queryAnalysis = await analyzeQueryWithClassifierFallback(retrievalQuery, analyzeClinicalQuery(retrievalQuery), {
corpusGrounding: corpusGroundingScope,
ownerId: args.ownerId,
signal: args.signal,
});
throwIfAborted(args.signal);
if (modeQueryClass) queryAnalysis.queryClass = modeQueryClass;
Expand Down Expand Up @@ -3123,6 +3156,7 @@ export async function answerQuestionWithScope(args: AnswerQuestionWithScopeArgs)
let existing = inflightKey ? answerInflight.get(inflightKey) : undefined;

while (existing) {
recordCoalescedAnswerWaiter();
await args.onProgress?.({
stage: "cached",
message: "Waiting for an identical cited answer request already in progress.",
Expand Down Expand Up @@ -3154,8 +3188,15 @@ export async function answerQuestionWithScope(args: AnswerQuestionWithScopeArgs)
}
}

// Only coalescible requests belong in this process-local signal. Requests
// that intentionally bypass cache/coalescing must not make a replica look
// ineffective, and neither keys nor clinical content leave this function.
if (inflightKey) recordAnswerOrigination();
const pending = answerQuestionWithScopeUncoalesced(args, startedAt).finally(() => {
if (inflightKey) answerInflight.delete(inflightKey);
if (inflightKey) {
answerInflight.delete(inflightKey);
recordAnswerOriginationFinished();
}
});
if (inflightKey) answerInflight.set(inflightKey, pending);
return pending;
Expand Down
4 changes: 2 additions & 2 deletions supabase/drift-manifest.json
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
{
"generated_at": "2026-07-18T08:58:41.495Z",
"generated_at": "2026-07-17T22:10:14.366Z",
"generator": "scripts/generate-drift-manifest.ts",
"postgres_image": "supabase/postgres:17.6.1.127",
"schema_sha256": "302a9ecec8e69564d50035be8480fa5e5686ca6136d96978f2188ecf5fb58be1",
"replay_seconds": 18,
"replay_seconds": 13,
"snapshot": {
"views": [
{
Expand Down
43 changes: 13 additions & 30 deletions tests/audit-content-services-regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -115,17 +115,15 @@ describe("content and services audit regressions", () => {
},
});
expect(transport?.source).toHaveProperty("url");
expect(transport?.source).not.toHaveProperty("published");
expect(transport?.source).toHaveProperty("reviewed");
expect(transport?.source).not.toHaveProperty("pages");
expect(transport?.source).not.toHaveProperty("pageCount");
expect(transport?.source).not.toHaveProperty("reviewDue");
expect(JSON.stringify(transport?.source)).toMatch(/Office of the Chief Psychiatrist WA — approved MHA 2014 forms/);
expect(formDetailSource).not.toMatch(/Review due/i);
expect(formDetailSource).not.toMatch(/Admission order|Treatment order|5\(2\)/);
expect(formDetailSource).toContain("Pathway navigation is not available yet");
expect(JSON.stringify(transport?.source)).not.toMatch(/\b\d+\s+pages?\b|\bstatutory\b/i);
expect(formDetailSource).not.toMatch(/\b\d+\s+pages?\b|\bReview due\b/i);
expect(formDetailSource).not.toContain("01 May 2026");
expect(formDetailSource).not.toMatch(/5\(2\)|Admission order|Treatment order/);
expect(formDetailSource).toContain("Full pathway unavailable");
expect(formDetailSource).toContain(">Source info</");
expect(normalizedFormDetailSource).toContain(
'label: "Source currency", value: displayText(form.source?.reviewed, "Review locally")',
);
Expand All @@ -134,33 +132,18 @@ describe("content and services audit regressions", () => {
if (form.source?.url) continue;
expect(form.verification?.locallyVerified, form.slug).toBe(false);
expect(form.verification?.confidence, form.slug).toBe("Unknown");
expect(form.source?.status, form.slug).toMatch(/confirmation required|source checked/i);
expect(form.source?.status, form.slug).toMatch(/confirmation required/i);
expect(form.source?.reviewed, form.slug).toBeUndefined();
}

expect(formsSearchSource).toContain("const sourceSnippetCount = 278;");
expect(formsSearchSource).toContain("const taskCount = 8;");
expect(formsSearchSource).toContain("const pathwayCount = 12;");
expect(formsSearchSource).toContain('["Evidence", sourceSnippetCount]');
expect(formsSearchSource).toContain('["Pathways", pathwayCount]');
expect(formsSearchSource).toContain('["Tasks", taskCount]');
expect(formsSearchSource).toContain("PSOLIS");
expect(formsSearchSource).toContain("Source verified");
expect(formsSearchSource).toContain("Official source");
expect(formsSearchSource).toContain("Aligned to MHA 2014");
expect(formsSearchSource).toContain("Open account setup");
expect(formsSearchSource).toContain("View full pathway");
expect(formsSearchSource).toContain("Filter controls are coming soon");
expect(formsSearchSource).toContain("tagToneClass(chipLabel)");
expect(formsSearchSource).toContain("Title or content match");
expect(formsSearchSource).toContain("Content match in related pathway");
expect(formsSearchSource).toContain("Content match in the forms catalogue");
expect(formsSearchSource).toContain("View all forms (");
expect(formsSearchSource).toContain("View all forms");
expect(formsHomeSource).not.toMatch(/Source verified|Open account setup/);
expect(formsHomeSource).not.toMatch(
/Number, pathway, clock|Maker, clock, copies|Browse pathways|Before, current, parallel, after|starter set of MHA 2014 forms|follow a pathway/,
);
expect(formsHomeSource).toContain("need local confirmation");
expect(formsHomeSource).toContain("local confirmation");
expect(formsHomeSource).toContain("Source catalogue reviewed");
});

Expand Down Expand Up @@ -215,12 +198,12 @@ describe("content and services audit regressions", () => {
});

it("claims and renders a form source link only when the record has a URL", () => {
expect(normalizedFormDetailSource).toContain("form.source?.url ? (");
expect(normalizedFormDetailSource).toMatch(/\{form\.source\?\.url \? \( <a href=\{form\.source\.url\}/);
expect(normalizedFormDetailSource).toMatch(
/<a href=\{form\.source\.url\} target="_blank" rel="noopener noreferrer" className="inline-flex min-h-10/,
);
expect(formDetailSource.match(/Source link pending/g)).toHaveLength(1);
expect(normalizedFormDetailSource).toContain("sourceHref={form.source?.url ?? null}");
expect(normalizedFormDetailSource).toContain("href={form.source.url}");
expect(normalizedFormDetailSource).toContain('target="_blank"');
expect(normalizedFormDetailSource).toContain('rel="noopener noreferrer"');
expect(normalizedFormDetailSource).toContain("inline-flex min-h-10");
expect(formDetailSource).toContain("Source link pending");
expect(formDetailSource).toContain("Official");
});
});
13 changes: 6 additions & 7 deletions tests/audit-navigation-auth-regressions.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -97,18 +97,17 @@ describe("audit navigation and auth regressions", () => {
);
expect(privateCapabilityContract).toContain("const canUsePrivateApis =");
expect(privateCapabilityContract).toContain(
'localProjectReady && (localNoAuthMode || localDevCanAttemptPrivateApis || authStatus === "authenticated")',
'localNoAuthMode || localDevCanAttemptPrivateApis || authStatus === "authenticated"',
);
expect(privateCapabilityContract).not.toMatch(/clientDemoMode/);

const pollingContract = sourceSegment(
clinicalDashboardSource,
"if (!nextDemoMode && !canUsePrivateApis) {",
"const shouldRefreshWorkState =",
"const [documentsResponse",
);
expect(pollingContract).toContain(
"includeAdministrationData && (!administrationDataLoadedRef.current || now >= nextWorkStatePollRef.current)",
);
expect(pollingContract).toContain("if (!nextDemoMode && !canUsePrivateApis) {");
expect(pollingContract).toContain("setDocuments([]);");
expect(pollingContract).toContain("return;");

const labelMutationContract = sourceSegment(
clinicalDashboardSource,
Expand All @@ -125,7 +124,7 @@ describe("audit navigation and auth regressions", () => {
expect(uploadMutationContract).toContain("if (!canUsePrivateApis) {");
});

it("keeps the root dashboard H1 as Clinical Guide", () => {
it("keeps the root dashboard H1 as Clinical KB", () => {
expect(clinicalDashboardSource.match(/<h1\b/g)).toHaveLength(1);
expect(clinicalDashboardSource).toMatch(/<h1 className="sr-only">\s*Clinical Guide\s*<\/h1>/);
});
Expand Down
17 changes: 7 additions & 10 deletions tests/supabase-schema.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,7 +129,7 @@ const deleteDocumentIfIdleMigration = readFileSync(
"utf8",
).replace(/\s+/g, " ");
const defaultAclAssertionMigration = readFileSync(
new URL("../supabase/migrations/20260717161000_assert_supabase_admin_default_privileges.sql", import.meta.url),
new URL("../supabase/migrations/20260717173000_reassert_supabase_admin_default_privileges.sql", import.meta.url),
"utf8",
).replace(/\s+/g, " ");
const defaultAclRoleBootstrap = readFileSync(new URL("../supabase/roles.sql", import.meta.url), "utf8").replace(
Expand Down Expand Up @@ -1369,9 +1369,10 @@ describe("Supabase Preview replay guards", () => {
"create or replace function public.cleanup_registry_corpus_document()",
"revoke execute on function public.cleanup_registry_corpus_document()",
);
expect(cleanup).toContain("metadata->>'registry_record_id' = old.id::text");
expect(cleanup).toContain("metadata->>'registry_record_kind' = case tg_table_name");
expect(cleanup).toMatch(/when 'clinical_registry_records' then (pg_catalog\.)?to_jsonb\((old|OLD)\)->>'kind'/);
const cleanupLower = cleanup.toLowerCase();
expect(cleanupLower).toContain("metadata->>'registry_record_id' = old.id::text");
expect(cleanupLower).toContain("metadata->>'registry_record_kind' = case tg_table_name");
expect(cleanupLower).toMatch(/when 'clinical_registry_records' then (pg_catalog\.)?to_jsonb\(old\)->>'kind'/);
Comment on lines +1372 to +1375

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🗄️ Data Integrity & Integration | 🟠 Major | ⚡ Quick win

Preserve case-sensitive registry literals in this assertion.

cleanup.toLowerCase() also lowercases quoted string values. A migration that changes 'clinical_registry_records' or 'kind' to the wrong case would still satisfy these checks, even though PostgreSQL compares those metadata values case-sensitively. Normalize SQL formatting separately, but assert the quoted registry literals from the raw SQL.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/supabase-schema.test.ts` around lines 1372 - 1375, Update the
assertions around cleanup to normalize SQL formatting without lowercasing quoted
values: use a separately normalized representation for structural checks, while
matching the raw cleanup SQL for the case-sensitive literals
'clinical_registry_records' and 'kind'. Preserve the existing checks for
metadata fields and CASE structure.

expect(cleanup).toContain("when 'medication_records' then 'medication'");
expect(cleanup).toContain("when 'differential_records' then 'differential'");
expect(cleanup).not.toContain("registry_record_id')::uuid");
Expand All @@ -1395,12 +1396,8 @@ describe("Supabase Preview replay guards", () => {
expect(corrector).toContain("lower(canonical) % tok");
expect(corrector).toContain("word % tok");
expect(corrector).toContain("limit 32");
expect(corrector).toContain("min_sim real default 0.45");
if (corrector.includes("set pg_trgm.similarity_threshold = 0.3")) {
expect(corrector).toContain("set pg_trgm.similarity_threshold = 0.3");
}
expect(corrector).toContain("best_sim >= min_sim");
if (corrector.includes("min_sim is null or min_sim < 0.3 or min_sim > 1")) {
expect(corrector).toContain("best is not null and best_sim >= min_sim");
if (corrector.includes("min_sim is null")) {
expect(corrector).toContain("min_sim is null or min_sim < 0.3 or min_sim > 1");
}
expect(corrector).not.toContain("array_agg(distinct term)");
Expand Down
2 changes: 1 addition & 1 deletion tests/ui-accessibility.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ async function expectNoPageHorizontalOverflow(page: Page) {
}

async function expectDashboardUsable(page: Page) {
await expect(page.getByRole("heading", { level: 1, name: "Clinical Guide" })).toHaveCount(1);
await expect(page.getByRole("heading", { level: 1, name: "Clinical KB" })).toHaveCount(1);
await expect(page.getByRole("heading", { name: "Answer" })).toBeVisible();
await expect(page.locator('[aria-label^="Search indexed guidelines by question or keyword"]:visible')).toBeVisible();
await expect(page.getByRole("button", { name: "Open answer options" })).toBeVisible();
Expand Down
Loading
Loading