diff --git a/frontend/components/ai-elements/attachments.tsx b/frontend/components/ai-elements/attachments.tsx
index 6a3c036..3876e1f 100644
--- a/frontend/components/ai-elements/attachments.tsx
+++ b/frontend/components/ai-elements/attachments.tsx
@@ -369,15 +369,18 @@ export const AttachmentRemove = ({
// AttachmentHoverCard - Hover preview
// ============================================================================
-export type AttachmentHoverCardProps = ComponentProps;
+export type AttachmentHoverCardProps = ComponentProps & {
+ // Base UI moved hover delay to the Trigger (`delay`); kept here for API
+ // back-compat but no longer forwarded to the Root.
+ openDelay?: number;
+ closeDelay?: number;
+};
export const AttachmentHoverCard = ({
- openDelay = 0,
- closeDelay = 0,
+ openDelay,
+ closeDelay,
...props
-}: AttachmentHoverCardProps) => (
-
-);
+}: AttachmentHoverCardProps) => ;
export type AttachmentHoverCardTriggerProps = ComponentProps<
typeof HoverCardTrigger
diff --git a/frontend/components/ai-elements/context.tsx b/frontend/components/ai-elements/context.tsx
index c4fe7b6..45cbb0c 100644
--- a/frontend/components/ai-elements/context.tsx
+++ b/frontend/components/ai-elements/context.tsx
@@ -56,7 +56,7 @@ export const Context = ({
return (
-
+
);
};
diff --git a/frontend/components/ai-elements/inline-citation.tsx b/frontend/components/ai-elements/inline-citation.tsx
index 8194d38..70707d4 100644
--- a/frontend/components/ai-elements/inline-citation.tsx
+++ b/frontend/components/ai-elements/inline-citation.tsx
@@ -50,7 +50,7 @@ export const InlineCitationText = ({
export type InlineCitationCardProps = ComponentProps;
export const InlineCitationCard = (props: InlineCitationCardProps) => (
-
+
);
export type InlineCitationCardTriggerProps = ComponentProps & {
diff --git a/frontend/components/ai-elements/plan.tsx b/frontend/components/ai-elements/plan.tsx
index 67d9e79..bdb256d 100644
--- a/frontend/components/ai-elements/plan.tsx
+++ b/frontend/components/ai-elements/plan.tsx
@@ -128,5 +128,18 @@ export const PlanFooter = (props: PlanFooterProps) => (
export type PlanTriggerProps = ComponentProps;
export const PlanTrigger = ({ className, ...props }: PlanTriggerProps) => (
- }>Toggle plan
+
+ }
+ >
+
+ Toggle plan
+
);
diff --git a/frontend/components/ai-elements/prompt-input.tsx b/frontend/components/ai-elements/prompt-input.tsx
index 5c3fe8b..0eca87d 100644
--- a/frontend/components/ai-elements/prompt-input.tsx
+++ b/frontend/components/ai-elements/prompt-input.tsx
@@ -1311,15 +1311,18 @@ export const PromptInputSelectValue = ({
);
-export type PromptInputHoverCardProps = ComponentProps;
+export type PromptInputHoverCardProps = ComponentProps & {
+ // Base UI moved hover delay to the Trigger (`delay`); kept here for API
+ // back-compat but no longer forwarded to the Root.
+ openDelay?: number;
+ closeDelay?: number;
+};
export const PromptInputHoverCard = ({
- openDelay = 0,
- closeDelay = 0,
+ openDelay,
+ closeDelay,
...props
-}: PromptInputHoverCardProps) => (
-
-);
+}: PromptInputHoverCardProps) => ;
export type PromptInputHoverCardTriggerProps = ComponentProps<
typeof HoverCardTrigger
diff --git a/frontend/components/ai-elements/schema-display.tsx b/frontend/components/ai-elements/schema-display.tsx
index 24f7b92..ae13246 100644
--- a/frontend/components/ai-elements/schema-display.tsx
+++ b/frontend/components/ai-elements/schema-display.tsx
@@ -107,7 +107,9 @@ export const SchemaDisplayPath = ({
);
diff --git a/frontend/components/chat/ai-setup-notice.tsx b/frontend/components/chat/ai-setup-notice.tsx
index bac0149..a4e7d41 100644
--- a/frontend/components/chat/ai-setup-notice.tsx
+++ b/frontend/components/chat/ai-setup-notice.tsx
@@ -1,6 +1,6 @@
"use client";
-import { Sparkles, X } from "lucide-react";
+import { TriangleAlert, X } from "lucide-react";
import Link from "next/link";
import { useEffect, useState } from "react";
import { useTranslation } from "react-i18next";
@@ -8,9 +8,12 @@ import { useTranslation } from "react-i18next";
import { Button } from "@/components/ui/button";
import { getAiConfig } from "@/lib/ai-settings";
-// A single, dismissible heads-up shown above the chat input on a fresh chat when
-// no AI provider is configured yet (no API key, and no local Ollama). It only
-// renders on the empty state, so it naturally disappears once a message is sent.
+// A thin, dismissible warning bar shown flush above the chat input whenever no
+// AI provider is wired up yet — either no API key for any cloud provider, or the
+// app is in local mode with no Ollama endpoint set. Without this, sending a
+// message just fails silently, so the banner spells out the missing setup and
+// links straight to AI settings. Rendered in both the empty and active chat
+// states so a user mid-conversation with no provider still sees why replies fail.
export function AiSetupNotice() {
const { t } = useTranslation();
const [needsSetup, setNeedsSetup] = useState(false);
@@ -40,24 +43,25 @@ export function AiSetupNotice() {
return (
diff --git a/frontend/components/patients/patient-detail.tsx b/frontend/components/patients/patient-detail.tsx
index b41cfd4..e3df3e4 100644
--- a/frontend/components/patients/patient-detail.tsx
+++ b/frontend/components/patients/patient-detail.tsx
@@ -21,16 +21,6 @@ import { useTranslation } from "react-i18next";
import { Sparkline } from "@/components/chat/sparkline";
import { AttachmentsSection } from "@/components/patients/patient-files";
import { Avatar, AvatarFallback } from "@/components/ui/avatar";
-import {
- Timeline,
- TimelineContent,
- TimelineDate,
- TimelineHeader,
- TimelineIndicator,
- TimelineItem,
- TimelineSeparator,
- TimelineTitle,
-} from "@/components/ui/timeline";
import {
type ActivityEntityType,
type ActivityEntry,
@@ -182,36 +172,41 @@ function RecordHistory({ fileNumber }: { fileNumber: string }) {
{t("patientCard.history.empty")}
) : (
- // Every entry is a past, audited event, so mark them all completed
- // (filled indicators) by seeding the active step past the last item.
-
+ // A plain vertical rail so EVERY audited change renders in full — the
+ // icon column draws a connector that stretches to the next entry, and
+ // the flex layout mirrors correctly under RTL.
+
{entries.map((e, i) => {
const Icon = historyIcon[e.entityType] ?? Pencil;
+ const isLast = i === entries.length - 1;
return (
-
-
-
-
- {e.actorName}
-
-
+
+
+
-
-
-
- {e.action}
-
+
+ {!isLast && (
+
+ )}
+
+
+
+ {e.actorName}
+
+
{e.action}
+
+
+
);
})}
-
+
)}
);
diff --git a/frontend/components/patients/patients-view.tsx b/frontend/components/patients/patients-view.tsx
index 0ee517b..35230bd 100644
--- a/frontend/components/patients/patients-view.tsx
+++ b/frontend/components/patients/patients-view.tsx
@@ -13,8 +13,23 @@ import { Badge } from "@/components/ui/badge";
import { Button } from "@/components/ui/button";
import { Input } from "@/components/ui/input";
import { ListPagination } from "@/components/ui/list-pagination";
+import {
+ Select,
+ SelectItem,
+ SelectPopup,
+ SelectTrigger,
+ SelectValue,
+} from "@/components/ui/select";
import { listPatients, type Patient } from "@/lib/patients";
+type StatusFilter = "all" | Patient["status"];
+const STATUS_FILTERS: StatusFilter[] = [
+ "all",
+ "active",
+ "inpatient",
+ "discharged",
+];
+
// Rows shown per page on the patients table before paginating.
const PAGE_SIZE = 10;
@@ -32,6 +47,7 @@ const statusVariant: Record = {
export function PatientsView() {
const { t } = useTranslation();
const [query, setQuery] = useState("");
+ const [statusFilter, setStatusFilter] = useState("all");
const [addOpen, setAddOpen] = useState(false);
const [importOpen, setImportOpen] = useState(false);
// Bumped on open so the create dialog remounts with a fresh file # / form.
@@ -69,9 +85,18 @@ export function PatientsView() {
}, []);
const q = query.trim().toLowerCase();
- const patients = allPatients.filter(
- (p) => !q || p.name.toLowerCase().includes(q) || p.fileNumber.includes(q)
- );
+ // Search matches name, MRN, problem/condition labels, and allergy substances;
+ // the status filter narrows to one clinical status.
+ const patients = allPatients.filter((p) => {
+ if (statusFilter !== "all" && p.status !== statusFilter) return false;
+ if (!q) return true;
+ return (
+ p.name.toLowerCase().includes(q) ||
+ p.fileNumber.includes(q) ||
+ p.problems.some((problem) => problem.label.toLowerCase().includes(q)) ||
+ p.allergies.some((a) => a.substance.toLowerCase().includes(q))
+ );
+ });
// Client-side pagination over the filtered list (10/page). Searching resets to
// the first page (done in the search handler); `page` is clamped at render so a
@@ -133,6 +158,29 @@ export function PatientsView() {
value={query}
/>
+