From 5e3929a8830b02735c088b54bf654674737dc3f0 Mon Sep 17 00:00:00 2001 From: elpresidank Date: Tue, 7 Apr 2026 09:15:59 -0500 Subject: [PATCH] =?UTF-8?q?fix:=20comprehensive=20QA=20audit=20=E2=80=94?= =?UTF-8?q?=20light=20mode,=20accessibility,=20error=20handling,=20code=20?= =?UTF-8?q?quality?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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) --- ts/packages/cli/package.json | 2 +- ts/packages/client/src/models/messages.ts | 1 - ts/packages/flow/package.json | 1 + .../flow/src/query/triples/falkordb.ts | 3 ++ .../flow/src/storage/triples/falkordb.ts | 7 ++++ ts/packages/mcp/package.json | 2 +- ts/packages/workbench/package.json | 1 - ts/packages/workbench/src/App.tsx | 1 + .../src/components/notification-toasts.tsx | 3 +- .../workbench/src/components/ui/dialog.tsx | 5 ++- .../workbench/src/components/ui/tabs.tsx | 42 ------------------- .../workbench/src/hooks/use-conversation.ts | 12 ++++-- ts/packages/workbench/src/index.css | 15 +++++-- ts/packages/workbench/src/pages/chat.tsx | 2 +- ts/packages/workbench/src/pages/graph.tsx | 29 ++++++++++--- ts/packages/workbench/src/pages/library.tsx | 6 ++- ts/pnpm-lock.yaml | 15 ++----- 17 files changed, 73 insertions(+), 74 deletions(-) delete mode 100644 ts/packages/workbench/src/components/ui/tabs.tsx diff --git a/ts/packages/cli/package.json b/ts/packages/cli/package.json index dc4ae3fb..d1b91b49 100644 --- a/ts/packages/cli/package.json +++ b/ts/packages/cli/package.json @@ -9,7 +9,7 @@ "build": "tsc", "dev": "tsc --watch", "clean": "rm -rf dist", - "test": "vitest run" + "test": "vitest run --passWithNoTests" }, "dependencies": { "@trustgraph/base": "workspace:*", diff --git a/ts/packages/client/src/models/messages.ts b/ts/packages/client/src/models/messages.ts index a5687aa8..c9fc753c 100644 --- a/ts/packages/client/src/models/messages.ts +++ b/ts/packages/client/src/models/messages.ts @@ -1,6 +1,5 @@ import { Triple, Term } from "./Triple.js"; -// FIXME: Better types? export type Request = object; export type Response = object; export type Error = object | string; diff --git a/ts/packages/flow/package.json b/ts/packages/flow/package.json index 88244405..d7a8cd0e 100644 --- a/ts/packages/flow/package.json +++ b/ts/packages/flow/package.json @@ -23,6 +23,7 @@ "pdfjs-dist": "^5.6.205" }, "devDependencies": { + "@types/node": "^22.0.0", "typescript": "^5.8.0", "vitest": "^3.1.0" } diff --git a/ts/packages/flow/src/query/triples/falkordb.ts b/ts/packages/flow/src/query/triples/falkordb.ts index 5ae1de02..3ff4a14b 100644 --- a/ts/packages/flow/src/query/triples/falkordb.ts +++ b/ts/packages/flow/src/query/triples/falkordb.ts @@ -46,6 +46,9 @@ export class FalkorDBTriplesQuery { this.graph = new Graph(client, database); this.connectPromise = client.connect().then(() => { console.log(`[FalkorDBTriplesQuery] Connected to ${url}, graph: ${database}`); + }).catch((err) => { + console.error(`[FalkorDBTriplesQuery] Connection failed:`, err); + throw err; }); } diff --git a/ts/packages/flow/src/storage/triples/falkordb.ts b/ts/packages/flow/src/storage/triples/falkordb.ts index b2460bb8..66670a87 100644 --- a/ts/packages/flow/src/storage/triples/falkordb.ts +++ b/ts/packages/flow/src/storage/triples/falkordb.ts @@ -40,6 +40,9 @@ export class FalkorDBTriplesStore { this.graph = new Graph(client, database); this.connectPromise = client.connect().then(() => { console.log(`[FalkorDBTriplesStore] Connected to ${url}, graph: ${database}`); + }).catch((err) => { + console.error(`[FalkorDBTriplesStore] Connection failed:`, err); + throw err; }); } @@ -56,6 +59,7 @@ export class FalkorDBTriplesStore { } async createLiteral(value: string, user: string, collection: string): Promise { + await this.ensureConnected(); await this.graph.query( "MERGE (n:Literal {value: $value, user: $user, collection: $collection})", { params: { value, user, collection } }, @@ -66,6 +70,7 @@ export class FalkorDBTriplesStore { src: string, uri: string, dest: string, user: string, collection: string, ): Promise { + await this.ensureConnected(); await this.graph.query( "MATCH (src:Node {uri: $src, user: $user, collection: $collection}) " + "MATCH (dest:Node {uri: $dest, user: $user, collection: $collection}) " + @@ -78,6 +83,7 @@ export class FalkorDBTriplesStore { src: string, uri: string, dest: string, user: string, collection: string, ): Promise { + await this.ensureConnected(); await this.graph.query( "MATCH (src:Node {uri: $src, user: $user, collection: $collection}) " + "MATCH (dest:Literal {value: $dest, user: $user, collection: $collection}) " + @@ -109,6 +115,7 @@ export class FalkorDBTriplesStore { } async deleteCollection(user: string, collection: string): Promise { + await this.ensureConnected(); await this.graph.query( "MATCH (n:Node {user: $user, collection: $collection}) DETACH DELETE n", { params: { user, collection } }, diff --git a/ts/packages/mcp/package.json b/ts/packages/mcp/package.json index 2d3430cf..e5dbffdb 100644 --- a/ts/packages/mcp/package.json +++ b/ts/packages/mcp/package.json @@ -8,7 +8,7 @@ "build": "tsc", "dev": "tsc --watch", "clean": "rm -rf dist", - "test": "vitest run" + "test": "vitest run --passWithNoTests" }, "dependencies": { "@trustgraph/base": "workspace:*", diff --git a/ts/packages/workbench/package.json b/ts/packages/workbench/package.json index 06c48738..e4b1ba23 100644 --- a/ts/packages/workbench/package.json +++ b/ts/packages/workbench/package.json @@ -11,7 +11,6 @@ "dependencies": { "@tanstack/react-query": "^5.75.0", "@trustgraph/client": "workspace:*", - "class-variance-authority": "^0.7.1", "clsx": "^2.1.0", "lucide-react": "^0.513.0", "react": "^19.1.0", diff --git a/ts/packages/workbench/src/App.tsx b/ts/packages/workbench/src/App.tsx index efd1ea8f..950bccba 100644 --- a/ts/packages/workbench/src/App.tsx +++ b/ts/packages/workbench/src/App.tsx @@ -25,6 +25,7 @@ export default function App() { } /> } /> } /> + } /> diff --git a/ts/packages/workbench/src/components/notification-toasts.tsx b/ts/packages/workbench/src/components/notification-toasts.tsx index 9472fe8c..3a417727 100644 --- a/ts/packages/workbench/src/components/notification-toasts.tsx +++ b/ts/packages/workbench/src/components/notification-toasts.tsx @@ -19,7 +19,7 @@ export function NotificationToasts() { if (notifications.length === 0) return null; return ( -
+
{notifications.map((n) => (
removeNotification(n.id)} className="shrink-0 opacity-60 hover:opacity-100" + aria-label="Dismiss notification" > diff --git a/ts/packages/workbench/src/components/ui/dialog.tsx b/ts/packages/workbench/src/components/ui/dialog.tsx index e4c93680..3b54ef45 100644 --- a/ts/packages/workbench/src/components/ui/dialog.tsx +++ b/ts/packages/workbench/src/components/ui/dialog.tsx @@ -54,6 +54,9 @@ export function Dialog({ onClick={handleBackdrop} >
{/* Header */}
-

{title}

+

{title}

- ))} -
- ); -} diff --git a/ts/packages/workbench/src/hooks/use-conversation.ts b/ts/packages/workbench/src/hooks/use-conversation.ts index 64471d3e..cfbfb91c 100644 --- a/ts/packages/workbench/src/hooks/use-conversation.ts +++ b/ts/packages/workbench/src/hooks/use-conversation.ts @@ -95,10 +95,14 @@ export const useConversation = create()( { name: "tg-conversation", // Only persist messages and chatMode, not input or transient state - partialize: (state) => ({ - messages: state.messages.filter((m) => !m.isStreaming), - chatMode: state.chatMode, - }), + partialize: (state) => { + const MAX_PERSISTED_MESSAGES = 200; + const filtered = state.messages.filter((m) => !m.isStreaming); + return { + messages: filtered.slice(-MAX_PERSISTED_MESSAGES), + chatMode: state.chatMode, + }; + }, }, ), ); diff --git a/ts/packages/workbench/src/index.css b/ts/packages/workbench/src/index.css index 233fdf44..dad83324 100644 --- a/ts/packages/workbench/src/index.css +++ b/ts/packages/workbench/src/index.css @@ -97,13 +97,13 @@ } } -/* Prose overrides for dark mode markdown rendering */ +/* Prose overrides for theme-aware markdown rendering */ @layer base { - .prose-invert code { + .prose code { color: var(--color-brand-300); } - .prose-invert pre { + .prose pre { background: var(--color-surface-200); } } @@ -123,4 +123,13 @@ html.light { --color-border: #e4e4e7; --color-border-hover: #d4d4d8; + + /* Brand adjustments for light backgrounds */ + --color-brand-300: #3b63ed; + --color-brand-400: #2d4ec4; + + /* Semantic colors stay vivid but slightly darker for contrast */ + --color-success: #16a34a; + --color-warning: #ca8a04; + --color-error: #dc2626; } diff --git a/ts/packages/workbench/src/pages/chat.tsx b/ts/packages/workbench/src/pages/chat.tsx index 477a7d6a..8d61436e 100644 --- a/ts/packages/workbench/src/pages/chat.tsx +++ b/ts/packages/workbench/src/pages/chat.tsx @@ -153,7 +153,7 @@ function MessageBubble({ msg }: { msg: ChatMessage }) { {isUser ? (

{msg.content}

) : ( -
+
{msg.content || (msg.isStreaming ? "" : "(empty)")}
)} diff --git a/ts/packages/workbench/src/pages/graph.tsx b/ts/packages/workbench/src/pages/graph.tsx index ec9b0521..75a1aa2d 100644 --- a/ts/packages/workbench/src/pages/graph.tsx +++ b/ts/packages/workbench/src/pages/graph.tsx @@ -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 & { ref?: React.Ref }>; + // --------------------------------------------------------------------------- // Types // --------------------------------------------------------------------------- @@ -226,6 +231,7 @@ function NodeDetailPanel({ @@ -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() { @@ -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" > @@ -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" > @@ -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" > @@ -532,6 +547,7 @@ export default function GraphPage() {
{/* Graph canvas */}
+
}> + {/* Search results badge overlay */} {searchTerm && matchingIds.size > 0 && ( diff --git a/ts/packages/workbench/src/pages/library.tsx b/ts/packages/workbench/src/pages/library.tsx index 7aebcffc..1a618f78 100644 --- a/ts/packages/workbench/src/pages/library.tsx +++ b/ts/packages/workbench/src/pages/library.tsx @@ -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; + onError?: (msg: string) => void; }) { const [file, setFile] = useState(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)} /> = 16'} - class-variance-authority@0.7.1: - resolution: {integrity: sha512-Ka+9Trutv7G8M6WT6SeiRWz792K5qEqIGEGzXKhAE6xOWAY6pPH8U+9IY3oCMv6kqTmLsv7Xh/2w2RigkePMsg==} - clsx@2.1.1: resolution: {integrity: sha512-eYm0QWBtUrBWZWG0d386OGAw16Z995PiOVo2B7bjWSbHedGl5e0ZWaq65kOGgUSNesEIDkB9ISbTg/JK9dhCZA==} engines: {node: '>=6'} @@ -3499,10 +3496,6 @@ snapshots: check-error@2.1.3: {} - class-variance-authority@0.7.1: - dependencies: - clsx: 2.1.1 - clsx@2.1.1: {} cluster-key-slot@1.1.2: {}