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" },

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

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
rg -n 'reducedMotion|contextOptions|test\.use' playwright.config.ts tests || true

Repository: BigSimmo/Database

Length of output: 405


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo "## playwright.config.ts"
wc -l playwright.config.ts
sed -n '1,140p' playwright.config.ts

echo
echo "## tests/ui-accessibility.spec.ts excerpt"
wc -l tests/ui-accessibility.spec.ts
sed -n '80,130p' tests/ui-accessibility.spec.ts

Repository: BigSimmo/Database

Length of output: 5556


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect Playwright config structure and relevant test usage around reduced motion.
echo "## playwright.config.ts"
wc -l playwright.config.ts
sed -n '1,140p' playwright.config.ts

echo
echo "## tests/ui-accessibility.spec.ts excerpt"
wc -l tests/ui-accessibility.spec.ts
sed -n '80,130p' tests/ui-accessibility.spec.ts

Repository: BigSimmo/Database

Length of output: 5556


🏁 Script executed:

#!/bin/bash
set -euo pipefail
wc -l playwright.config.ts
sed -n '1,140p' playwright.config.ts
echo '---'
wc -l tests/ui-accessibility.spec.ts
sed -n '80,130p' tests/ui-accessibility.spec.ts

Repository: BigSimmo/Database

Length of output: 5493


🏁 Script executed:

#!/bin/bash
set -euo pipefail
sed -n '1,140p' playwright.config.ts

Repository: BigSimmo/Database

Length of output: 3268


Project-level motion override should stay on reduced motion
contextOptions: { reducedMotion: "no-preference" } on the Chromium project overrides the suite-wide guard in use, so ui-smoke/ui-stress can still hit transition races. Scope this to the tests that need motion, or keep reduce here.

🤖 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 `@playwright.config.ts` at line 58, Update the Chromium project’s
contextOptions in the Playwright configuration so it preserves reduced motion
instead of overriding the suite-wide motion guard with "no-preference"; if
motion is required by specific tests, scope that override to those tests rather
than the project-level configuration.

...(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)]"
>
{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;
};
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);
Comment on lines +91 to +104

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

Wire the caller signal through retrieval RPC call sites.

callVersionedRetrievalRpc now accepts an AbortSignal, but the two current callers in src/lib/rag.ts (Lines [2837]-[2849] and [2945]-[2956]) still pass only four arguments. Aborts during vector retrieval therefore leave the Supabase RPC running.

Pass args.signal as the fifth argument at both call sites.

Proposed call-site fix
@@ hybrid retrieval call
       },
+      args.signal,
     );

@@ document-filter fallback call
       },
+      args.signal,
     );
🤖 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 - 104, Update both call
sites of callVersionedRetrievalRpc in rag.ts to pass args.signal as the fifth
argument, preserving the existing four arguments and ensuring vector-retrieval
RPCs receive the caller’s AbortSignal.

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 });
Comment on lines +431 to +437

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

# Map the relevant area first
ast-grep outline src/lib/rag.ts --view expanded || true

# Read the surrounding implementation around the cited lines
sed -n '400,470p' src/lib/rag.ts

# Read the catch block area mentioned in the comment
sed -n '1360,1425p' src/lib/rag.ts

# Search for tests or other abort-handling helpers
rg -n "awaitWithCallerSignal|AbortError|signal\.reason|aborted" src test tests __tests__ . 2>/dev/null || true

Repository: BigSimmo/Database

Length of output: 50373


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Look for non-DOMException abort reasons in the repo
rg -n "abort\\(new Error|abort\\([^)]*reason|abort\\(signal\\.reason|abort\\([^)]*DOMException|AbortController\\(" src tests . 2>/dev/null | sed -n '1,220p'

# Read the rag abort-related test file if present
if [ -f tests/rag-abort-signal.test.ts ]; then
  sed -n '1,220p' tests/rag-abort-signal.test.ts
fi

# Show the classifier fallback function with enough context to assess the catch behavior
sed -n '1300,1415p' src/lib/rag.ts

Repository: BigSimmo/Database

Length of output: 18866


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect the route budget test that uses a custom abort reason
sed -n '1,120p' tests/rag-route-budget.test.ts

# Inspect the rag abort signal tests for expected semantics
sed -n '1,180p' tests/rag-abort-signal.test.ts

# Inspect the universal-search wrapper since it forwards caller reasons
sed -n '150,230p' src/lib/universal-search.ts

Repository: BigSimmo/Database

Length of output: 8031


Preserve non-AbortError cancellation reasons in src/lib/rag.ts:1390-1399. awaitWithCallerSignal() rejects with signal.reason, but this catch only rethrows AbortError. If a caller aborts with a custom Error, the classifier fallback is treated as a transport failure and returns analysis instead of propagating cancellation. Check opts?.signal?.aborted here or normalize abort reasons to AbortError.

🤖 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 431 - 437, Update the catch handling around the
classifier fallback near the existing awaitWithCallerSignal usage to rethrow any
cancellation when opts?.signal?.aborted is true, regardless of whether the
reason is an AbortError or a custom Error. Preserve the current
transport-failure handling for non-aborted requests, and keep
awaitWithCallerSignal’s caller-provided reason intact.

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;
// 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
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");
});
});
11 changes: 5 additions & 6 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 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'/);
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
Loading
Loading