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
15 changes: 14 additions & 1 deletion .agents/skills/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,20 @@
"categories": [
{
"name": "Everyday",
"skills": ["skills", "plan", "run", "test", "fix", "review", "task", "handover", "health", "audit", "export", "prompt-perfector"]
"skills": [
"skills",
"plan",
"run",
"test",
"fix",
"review",
"task",
"handover",
"health",
"audit",
"export",
"prompt-perfector"
]
},
{
"name": "Clinical and app",
Expand Down
275 changes: 28 additions & 247 deletions docs/audit/repo-wide-review-remediation-plan-2026-07-23.md

Large diffs are not rendered by default.

8 changes: 8 additions & 0 deletions docs/branch-review-ledger.md
Original file line number Diff line number Diff line change
Expand Up @@ -736,3 +736,11 @@ This file is append-only. Never rewrite or delete an existing review record; app
- Scope: Targeted review of search bar/header/footer chrome behaviour after the edge-to-edge phone dock fix, plus durable repo rules for page-adaptive search chrome.
- Outcome: No new P0/P1 search chrome defect found in the static review. Fixed one regression hazard: a stale ClinicalDashboard comment still instructed a 0.75rem hidden dock pad despite the implementation/tests requiring 0rem. Added durable search chrome behaviour rules in AGENTS.md and docs/search-chrome-behaviour.md, with a static guard tying the remembered rules to the hidden-reserve contract.
- Checks: dependency shortcut section count; git diff --check; targeted rg for stale 0.75rem hidden-pad source wording (only negative test assertions remain); targeted Vitest command attempted but blocked by missing node_modules/vitest under Node 20.20.2 in this container. No provider-backed checks run.

## 2026-07-25 — PR #1188 / execute-audit-remediation-plan exhaustive PR review

- Branch/ref: PR #1188 / `execute-audit-remediation-plan`
- HEAD: 8b8639113925601e1687bfe4f1f29c44a4308b61
- Scope: Exhaustive review of PR #1188 and its audit maintainability remediation changes, including RAG ranking regression checks, API boundary verification, and UI component integrity.
- Outcome: REJECT PR merge until P0/P1 findings are fixed. Found P0 critical build regression: unresolved Git merge conflict markers across 10 source/test files and 2 documentation files (notably nested markers in RAG-governed `src/app/api/answer/route.ts:L6-L18` and `src/app/api/upload/route.ts:L249`). Found P1 unit test regression: unconditional `useRouter()` invocation in `NavigationBackButton` fails in unit tests (such as `tests/privacy-ui.test.ts`) when App Router context is unmounted. Found P2 scope divergence: 110 files modified in PR history containing unrelated checkpoint artifacts. RAG ranking core logic (`src/lib/rag/rag.ts`) remained correctly unchanged per RAG governance rules.
- Checks: `git diff origin/main...HEAD --stat`; `npm install`; `npm run check:rag:fixtures && npm run eval:rag:offline` (failed on build transformation due to conflict markers in `api/answer/route.ts`); targeted `grep_search` across entire codebase for `<<<<<<< ` conflict markers; read-only inspection of RAG boundaries and navigation components. No provider-backed checks, live database mutations, or live PR edits were executed.
91 changes: 0 additions & 91 deletions docs/outstanding-issues.md

Large diffs are not rendered by default.

14 changes: 7 additions & 7 deletions docs/search-chrome-behaviour.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,13 @@ This repo uses one shared search experience across the global shell, dashboard r

## Page ownership model

| Page state | Composer placement | Reserve owner |
| --- | --- | --- |
| Answer home / standalone mode homes | In-flow hero composer on phones and larger breakpoints | Page content; no fixed phone dock reserve |
| Submitted/search-result views | Compact bottom dock on phones; header/inline placement on larger screens | Shell/dashboard `--mobile-composer-reserve` |
| Answer result view | Overlaid glass header plus answer composer dock | Dashboard `#main-content` top/bottom reserves |
| Document detail/source routes | `DocumentViewer` floating composer | `DocumentViewer` content padding |
| Info/detail pages with no composer | No fixed composer | Idle shell padding only |
| Page state | Composer placement | Reserve owner |
| ----------------------------------- | ------------------------------------------------------------------------ | --------------------------------------------- |
| Answer home / standalone mode homes | In-flow hero composer on phones and larger breakpoints | Page content; no fixed phone dock reserve |
| Submitted/search-result views | Compact bottom dock on phones; header/inline placement on larger screens | Shell/dashboard `--mobile-composer-reserve` |
| Answer result view | Overlaid glass header plus answer composer dock | Dashboard `#main-content` top/bottom reserves |
| Document detail/source routes | `DocumentViewer` floating composer | `DocumentViewer` content padding |
| Info/detail pages with no composer | No fixed composer | Idle shell padding only |

## Invariants

Expand Down
2 changes: 1 addition & 1 deletion scripts/check-docs-links.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -100,7 +100,7 @@ function stripSuffixes(value) {
if (result.startsWith("./")) result = result.slice(2);
// Drop #anchor fragments and :line / :line-line / :line:col suffixes.
result = result.replace(/#[^#]*$/, "");
result = result.replace(/:\d+([-:]\d+)?$/, "");
result = result.replace(/:[L]?\d+([-:][L]?\d+)?$/i, "");
return result;
}

Expand Down
13 changes: 0 additions & 13 deletions scripts/check-github-action-pins.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -173,19 +173,6 @@ if (!/^ image: semgrep\/semgrep@sha256:[0-9a-f]{64}\s*$/m.test(semgrepGateJ
// the per-line validation above only covers workflows, a composite skew (e.g.
// setup-node v5 vs v7) was previously invisible. Assert each action name resolves
// to a single SHA everywhere it is used.
function discoverCompositeActionFiles(workflowRoot) {
const actionsRoot = path.join(workflowRoot, ".github", "actions");
if (!existsSync(actionsRoot)) return [];
const files = [];
for (const entry of readdirSync(actionsRoot, { withFileTypes: true })) {
if (!entry.isDirectory()) continue;
for (const name of ["action.yml", "action.yaml"]) {
const candidate = path.join(actionsRoot, entry.name, name);
if (existsSync(candidate)) files.push(candidate);
}
}
return files;
}

const actionPinPattern = /uses:\s*([^@\s]+)@([0-9a-f]{40})(?:\s*#\s*(\S+))?/;
const shasByAction = new Map();
Expand Down
66 changes: 66 additions & 0 deletions scripts/decompose-indexing-v3.mjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
import fs from "fs";

const indexFile = "supabase/functions/indexing-v3-agent/index.ts";
const utilsFile = "supabase/functions/indexing-v3-agent/utils.ts";

let content = fs.readFileSync(indexFile, "utf-8");

const utilsToExtract = [
"normalizeText",
"tokenize",
"safeRecord",
"compactString",
"uniqueStrings",
"structuredProfileFromMetadata",
"stringArrayFrom",
"textItemsFrom",
"sourceRegionsFromMetadata",
"isLowQualityLabel",
"phraseLabelCandidates",
"sha256Hex",
"sleep",
"normalizeLabel",
"normalizeLabelCandidate",
"canonicalUnitType",
"canonicalFieldType",
];

let utilsContent = `// Extracted utilities for indexing-v3-agent\nimport { EXPECTED_EMBED_DIM, type GeneratedLabelCandidate } from "./behavior.ts";\n\n`;

const regexesToExtract = ["LABEL_STOPWORDS", "GENERIC_LABELS", "CLINICAL_PHRASE_PATTERN"];

let modifiedContent = content;

for (const constName of regexesToExtract) {
const r = new RegExp(`const ${constName} = [\\s\\S]*?;\\n+`, "g");
const match = modifiedContent.match(r);
if (match) {
utilsContent += `export ` + match[0] + "\n";
modifiedContent = modifiedContent.replace(r, "");
}
}

for (const fn of utilsToExtract) {
const r = new RegExp(`(async )?function ${fn}\\([\\s\\S]*?\\n}\\n+`, "g");
const match = modifiedContent.match(r);
if (match) {
let replaced = match[0].replace(`function ${fn}(`, `export function ${fn}(`);
replaced = replaced.replace(`async function ${fn}(`, `export async function ${fn}(`);
utilsContent += replaced + "\n";
modifiedContent = modifiedContent.replace(r, "");
}
}

const imports = `import {
${utilsToExtract.join(",\n ")}
} from "./utils.ts";\n`;

modifiedContent = modifiedContent.replace(
'import postgres from "npm:postgres@3.4.7";',
`import postgres from "npm:postgres@3.4.7";\n` + imports,
);

fs.writeFileSync(utilsFile, utilsContent);
fs.writeFileSync(indexFile, modifiedContent);

console.log("Decomposed into utils.ts");
14 changes: 1 addition & 13 deletions src/app/api/answer/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,7 @@ import { NextResponse } from "next/server";
import { z } from "zod";
import { demoAnswer, demoSummary } from "@/lib/demo-data";
import { isDemoMode } from "@/lib/env";
<<<<<<< ours
<<<<<<< ours
<<<<<<< ours
import { answerQuestionWithScope } from "@/lib/rag/rag";
=======
import { answerQuestionWithScope, summarizeDocument } from "@/lib/rag";
>>>>>>> theirs
=======
import { answerQuestionWithScope, summarizeDocument } from "@/lib/rag";
>>>>>>> theirs
=======
import { answerQuestionWithScope, summarizeDocument } from "@/lib/rag";
>>>>>>> theirs
import { answerQuestionWithScope, summarizeDocument } from "@/lib/rag/rag";

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P1 Badge Resolve the non-stream summary contract consistently

For every /api/answer request with summaryMode: true, the route returns the streaming-required 400 response before reaching this imported summarizer, while the conflict resolution retained the opposite test contract asserting a 200 response and a summarizeDocument call. The focused private-route suite therefore fails deterministically; select either the documented rejection behavior or the governed summary path in both implementation and test.

AGENTS.md reference: AGENTS.md:L171-L172

Useful? React with 👍 / 👎.

import { jsonError, PublicApiError } from "@/lib/http";
import {
allowRateLimitInMemoryFallbackOnUnavailable,
Expand Down
37 changes: 3 additions & 34 deletions src/app/api/upload/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -225,9 +225,9 @@ export async function POST(request: Request) {
content_hash: contentHash,
metadata: {
source_title: title,
publisher_code: null,
publisher: null,
jurisdiction: "Australia/WA",
publisher_code: canonicalAuthority ? (identityAuthority.code ?? canonicalAuthority.codes[0] ?? null) : null,
publisher: canonicalAuthority?.publisher ?? null,
jurisdiction: canonicalAuthority?.jurisdictions[0] ?? "Australia/WA",
version: null,
publication_date: null,
review_date: null,
Expand All @@ -246,36 +246,6 @@ export async function POST(request: Request) {
max_upload_mb: env.MAX_UPLOAD_MB,
confidentiality_scope: "guidelines-only",
content_hash: contentHash,
<<<<<<< ours
status: "queued",
metadata: {
source_title: title,
publisher_code: canonicalAuthority ? (identityAuthority.code ?? canonicalAuthority.codes[0] ?? null) : null,
publisher: canonicalAuthority?.publisher ?? null,
jurisdiction: canonicalAuthority?.jurisdictions[0] ?? "Australia/WA",
version: null,
publication_date: null,
review_date: null,
uploaded_at: uploadedAt,
indexed_at: null,
uploaded_by: uploadOwnerId,
original_file_name: namePlan.originalFileName,
original_title: namePlan.originalTitle,
smart_title_base: namePlan.baseTitle,
smart_title_group_key: namePlan.duplicateGroupKey,
smart_title_duplicate_index: namePlan.duplicateIndex,
smart_title_duplicate_reason: namePlan.duplicateReason,
document_status: "unknown",
clinical_validation_status: "unverified",
extraction_quality: "unknown",
max_upload_mb: env.MAX_UPLOAD_MB,
confidentiality_scope: "guidelines-only",
content_hash: contentHash,
},
})
.select()
.single();
=======
},
};

Expand All @@ -287,7 +257,6 @@ export async function POST(request: Request) {
p_max_attempts: env.WORKER_MAX_ATTEMPTS,
},
);
>>>>>>> theirs

if (uploadRecordError) {
if (isContentHashDuplicateError(uploadRecordError)) {
Expand Down
71 changes: 25 additions & 46 deletions src/components/ClinicalDashboard.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,6 @@ import {
ShieldAlert,
Square,
UploadCloud,
WifiOff,
Wrench,
} from "lucide-react";
import {
Expand Down Expand Up @@ -63,10 +62,6 @@ import {
ClinicalMobileSidebar,
} from "@/components/clinical-dashboard/ClinicalSidebar";
import {
SetupChecklist,
UploadPanel,
IndexingMonitor,
IngestionQualityConsole,
LibraryHealthStrip,
fallbackSetupChecks,
hasReadyRequiredPublicSearchConfig,
Expand All @@ -75,6 +70,7 @@ import {
type IngestionQualityReviewItem,
} from "@/components/clinical-dashboard/DocumentManagerPanel";
import { GuideDialog, GuideTrigger, UtilityDrawer } from "@/components/clinical-dashboard/dashboard-shell";
import { SystemNotice, DegradedNotice } from "@/components/clinical-dashboard/dashboard-notices";
import { sanitizeAnswerDisplayText, sanitizeDisplayText } from "@/components/clinical-dashboard/display-text";
import { isPreformattedGroundedAnswer, ScopeAndGovernanceNotice } from "@/components/clinical-dashboard/answer-content";
import { AnswerEmptyState, AnswerProgressStepper, AnswerSkeleton } from "@/components/clinical-dashboard/answer-status";
Expand Down Expand Up @@ -152,6 +148,26 @@ const DocumentSearchResultsPanel = dynamic(
{ ssr: false },
);

const SetupChecklist = dynamic(
() => import("@/components/clinical-dashboard/DocumentManagerPanel").then((mod) => mod.SetupChecklist),
{ ssr: false },
);

const UploadPanel = dynamic(
() => import("@/components/clinical-dashboard/DocumentManagerPanel").then((mod) => mod.UploadPanel),
{ ssr: false },
);

const IndexingMonitor = dynamic(
() => import("@/components/clinical-dashboard/DocumentManagerPanel").then((mod) => mod.IndexingMonitor),
{ ssr: false },
);

const IngestionQualityConsole = dynamic(
() => import("@/components/clinical-dashboard/DocumentManagerPanel").then((mod) => mod.IngestionQualityConsole),
{ ssr: false },
);

import { clearLegacyRecentQueries, demoRecentQueryOwnerId, recentQueryStorageKey } from "@/lib/recent-query-storage";
import type { SearchFacets } from "@/components/clinical-dashboard/document-search-results";
import { isWeakRelevance } from "@/components/clinical-dashboard/relevance";
Expand Down Expand Up @@ -3054,23 +3070,6 @@ export function ClinicalDashboard({
empty: !answer || (answerRenderModel?.reviewSources.length ?? 0) === 0,
},
] as const;
const renderSystemNotice = (className?: string) => (
<UtilityDrawer
icon={CircleAlert}
title={demoMode ? "Demo mode" : "Setup required"}
summary={
demoMode ? "Synthetic data only; not clinical guidance." : "Configuration is needed before real uploads."
}
mobileSummary={demoMode ? "Synthetic data" : "Setup needed"}
className={className}
>
<p className="text-base-minus leading-6 text-[color:var(--warning)]">
{demoMode
? "Demo mode is active with three synthetic indexed documents, citations, source cards, image captions, and document links. Synthetic data only; not clinical guidance."
: `Configure .env.local and run supabase/schema.sql before uploading or searching. ${setupWarning}`}
</p>
</UtilityDrawer>
);
const showAuthPanel = false;
const showDegradedNotice = !isOnline || (apiUnavailable && !canRunSearch);
const submittedAnswerSearchActive =
Expand Down Expand Up @@ -3125,28 +3124,6 @@ export function ClinicalDashboard({
differentialsCompareAddonActive,
}),
);
const renderDegradedNotice = () => (
<UtilityDrawer
icon={!isOnline ? WifiOff : CircleAlert}
title={!isOnline ? "Offline" : "Service unavailable"}
summary={
!isOnline
? "Your browser is offline. Existing content may remain visible, but private search and uploads need network access."
: isDeployedClinicalKb()
? "The app could not reach its API. Try again in a moment."
: "The local API did not respond. Check the app server and setup status before retrying."
}
mobileSummary={!isOnline ? "Offline" : "API unavailable"}
>
<p className="text-base-minus leading-6 text-[color:var(--warning)]">
{!isOnline
? "Reconnect before uploading documents, refreshing source URLs, or generating answers."
: isDeployedClinicalKb()
? "The app will preserve the current view. If this keeps happening, check your connection and try again shortly."
: "The app will preserve the current view. Retry after confirming the local server, Supabase, OpenAI, and worker setup."}
</p>
</UtilityDrawer>
);
const setupReadyCount = setupChecks.filter((check) => check.status === "ready").length;
const setupCheckCount = setupChecks.length || fallbackSetupChecks.length;
const activeUploadWork =
Expand Down Expand Up @@ -3519,8 +3496,10 @@ export function ClinicalDashboard({
{actionNotice.message}
</InlineNotice>
)}
{showDegradedNotice && renderDegradedNotice()}
{showSystemNotice && answer ? renderSystemNotice("hidden sm:block") : null}
{showDegradedNotice && <DegradedNotice isOnline={isOnline} />}
{showSystemNotice ? (
<SystemNotice demoMode={demoMode} setupWarning={setupWarning} className="my-6 lg:my-10" />
) : null}

<section
className={cn(
Expand Down
2 changes: 1 addition & 1 deletion src/components/calculator-mockups/calculator-fixtures.ts
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import {

export type CalculatorTone = "success" | "info" | "warning" | "danger";

export type CalculatorOption = {
type CalculatorOption = {
label: string;
/** Compact label for segmented controls on narrow screens. */
short: string;
Expand Down
Loading
Loading