mirror of
https://github.com/trustgraph-ai/trustgraph.git
synced 2026-07-07 20:32:12 +02:00
fix: comprehensive QA audit — light mode, accessibility, error handling, code quality
- Fix light mode: theme-aware graph node labels, remove prose-invert for theme-safe markdown, add brand/semantic color overrides for light backgrounds - Add 404 catch-all route redirecting unknown paths to /chat - FalkorDB: add .catch() to connectPromise, add ensureConnected() to all store methods (createLiteral, relateNode, relateLiteral, deleteCollection) - Accessibility: dialog role/aria-modal, toast aria-live, dismiss/zoom/search button aria-labels, close panel aria-label - Lazy-load ForceGraph2D (splits 189KB into separate chunk, main bundle -26%) - Cap conversation localStorage at 200 messages to prevent quota overflow - Fix pnpm test: add --passWithNoTests to cli/mcp packages - Add upload error notification instead of silent catch - Remove unused class-variance-authority dep and dead tabs.tsx component - Add @types/node to flow package devDependencies - Remove stale FIXME comment in messages.ts Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
parent
9ef9ef854f
commit
5e3929a883
17 changed files with 73 additions and 74 deletions
|
|
@ -153,7 +153,7 @@ function MessageBubble({ msg }: { msg: ChatMessage }) {
|
|||
{isUser ? (
|
||||
<p className="whitespace-pre-wrap">{msg.content}</p>
|
||||
) : (
|
||||
<div className="prose prose-invert prose-sm max-w-none prose-p:my-1 prose-pre:bg-surface-200 prose-pre:text-fg prose-code:text-brand-300">
|
||||
<div className="prose prose-sm max-w-none text-fg prose-headings:text-fg prose-strong:text-fg prose-p:my-1 prose-a:text-brand-400 prose-pre:bg-surface-200 prose-pre:text-fg prose-code:text-brand-300">
|
||||
<Markdown>{msg.content || (msg.isStreaming ? "" : "(empty)")}</Markdown>
|
||||
</div>
|
||||
)}
|
||||
|
|
|
|||
|
|
@ -1,4 +1,6 @@
|
|||
import {
|
||||
lazy,
|
||||
Suspense,
|
||||
useCallback,
|
||||
useEffect,
|
||||
useMemo,
|
||||
|
|
@ -28,13 +30,16 @@ import type { Triple, Term } from "@trustgraph/client";
|
|||
// Lazy-load ForceGraph2D to keep bundle size down
|
||||
// ---------------------------------------------------------------------------
|
||||
|
||||
// react-force-graph-2d ships a default export
|
||||
import ForceGraph2D, {
|
||||
type ForceGraphMethods,
|
||||
type NodeObject,
|
||||
type LinkObject,
|
||||
import type {
|
||||
ForceGraphMethods,
|
||||
NodeObject,
|
||||
LinkObject,
|
||||
ForceGraphProps,
|
||||
} from "react-force-graph-2d";
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any
|
||||
const ForceGraph2D = lazy(() => import("react-force-graph-2d")) as unknown as React.ComponentType<ForceGraphProps<any, any> & { ref?: React.Ref<any> }>;
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Types
|
||||
// ---------------------------------------------------------------------------
|
||||
|
|
@ -226,6 +231,7 @@ function NodeDetailPanel({
|
|||
<button
|
||||
onClick={onClose}
|
||||
className="rounded p-1 text-fg-subtle hover:bg-surface-200 hover:text-fg"
|
||||
aria-label="Close detail panel"
|
||||
>
|
||||
<X className="h-4 w-4" />
|
||||
</button>
|
||||
|
|
@ -401,7 +407,12 @@ export default function GraphPage() {
|
|||
ctx.font = `${fontSize}px Inter, sans-serif`;
|
||||
ctx.textAlign = "center";
|
||||
ctx.textBaseline = "top";
|
||||
ctx.fillStyle = dim ? "rgba(100,100,100,0.3)" : "rgba(250,250,250,0.9)";
|
||||
const isLight = document.documentElement.classList.contains("light");
|
||||
ctx.fillStyle = dim
|
||||
? "rgba(100,100,100,0.3)"
|
||||
: isLight
|
||||
? "rgba(24,24,27,0.9)"
|
||||
: "rgba(250,250,250,0.9)";
|
||||
ctx.fillText(node.label, x, y + radius + 1);
|
||||
},
|
||||
[selectedNode, matchingIds],
|
||||
|
|
@ -456,6 +467,7 @@ export default function GraphPage() {
|
|||
<button
|
||||
onClick={() => setSearchTerm("")}
|
||||
className="absolute right-2 top-1/2 -translate-y-1/2 text-fg-subtle hover:text-fg"
|
||||
aria-label="Clear search"
|
||||
>
|
||||
<X className="h-3 w-3" />
|
||||
</button>
|
||||
|
|
@ -468,6 +480,7 @@ export default function GraphPage() {
|
|||
onClick={zoomIn}
|
||||
className="px-2 py-1.5 text-fg-muted hover:text-fg"
|
||||
title="Zoom in"
|
||||
aria-label="Zoom in"
|
||||
>
|
||||
<ZoomIn className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
|
@ -475,6 +488,7 @@ export default function GraphPage() {
|
|||
onClick={zoomOut}
|
||||
className="border-l border-r border-border px-2 py-1.5 text-fg-muted hover:text-fg"
|
||||
title="Zoom out"
|
||||
aria-label="Zoom out"
|
||||
>
|
||||
<ZoomOut className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
|
@ -482,6 +496,7 @@ export default function GraphPage() {
|
|||
onClick={zoomFit}
|
||||
className="px-2 py-1.5 text-fg-muted hover:text-fg"
|
||||
title="Fit to view"
|
||||
aria-label="Fit to view"
|
||||
>
|
||||
<Maximize className="h-3.5 w-3.5" />
|
||||
</button>
|
||||
|
|
@ -532,6 +547,7 @@ export default function GraphPage() {
|
|||
<div className="flex flex-1 overflow-hidden rounded-lg border border-border">
|
||||
{/* Graph canvas */}
|
||||
<div className="relative flex-1 bg-surface-0">
|
||||
<Suspense fallback={<div className="flex h-full items-center justify-center"><Loader2 className="h-5 w-5 animate-spin text-fg-subtle" /></div>}>
|
||||
<ForceGraph2D
|
||||
ref={fgRef}
|
||||
graphData={graphData}
|
||||
|
|
@ -558,6 +574,7 @@ export default function GraphPage() {
|
|||
width={undefined}
|
||||
height={undefined}
|
||||
/>
|
||||
</Suspense>
|
||||
|
||||
{/* Search results badge overlay */}
|
||||
{searchTerm && matchingIds.size > 0 && (
|
||||
|
|
|
|||
|
|
@ -26,6 +26,7 @@ function UploadDialog({
|
|||
open,
|
||||
onClose,
|
||||
onUpload,
|
||||
onError,
|
||||
}: {
|
||||
open: boolean;
|
||||
onClose: () => void;
|
||||
|
|
@ -36,6 +37,7 @@ function UploadDialog({
|
|||
comments: string,
|
||||
tags: string[],
|
||||
) => Promise<void>;
|
||||
onError?: (msg: string) => void;
|
||||
}) {
|
||||
const [file, setFile] = useState<File | null>(null);
|
||||
const [title, setTitle] = useState("");
|
||||
|
|
@ -78,7 +80,8 @@ function UploadDialog({
|
|||
await onUpload(base64, file.type || "application/octet-stream", title, comments, tagList);
|
||||
reset();
|
||||
onClose();
|
||||
} catch {
|
||||
} catch (err) {
|
||||
onError?.(err instanceof Error ? err.message : "Upload failed");
|
||||
setUploading(false);
|
||||
}
|
||||
};
|
||||
|
|
@ -455,6 +458,7 @@ export default function LibraryPage() {
|
|||
open={uploadOpen}
|
||||
onClose={() => setUploadOpen(false)}
|
||||
onUpload={handleUpload}
|
||||
onError={(msg) => notify.error("Upload failed", msg)}
|
||||
/>
|
||||
|
||||
<ConfirmDeleteDialog
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue