diff --git a/apps/web/package.json b/apps/web/package.json index 7186162..0ee4a5a 100644 --- a/apps/web/package.json +++ b/apps/web/package.json @@ -21,9 +21,11 @@ "@mosaic/shared": "workspace:*", "@mosaic/ui": "workspace:*", "@tanstack/react-query": "^5.90.20", + "@types/dompurify": "^3.2.0", "@xyflow/react": "^12.5.3", "better-auth": "^1.4.17", "date-fns": "^4.1.0", + "dompurify": "^3.3.1", "elkjs": "^0.9.3", "lucide-react": "^0.563.0", "mermaid": "^11.4.1", diff --git a/apps/web/src/app/(authenticated)/knowledge/search/page.tsx b/apps/web/src/app/(authenticated)/knowledge/search/page.tsx index 9e128ea..77a8062 100644 --- a/apps/web/src/app/(authenticated)/knowledge/search/page.tsx +++ b/apps/web/src/app/(authenticated)/knowledge/search/page.tsx @@ -23,6 +23,7 @@ export default function SearchPage(): React.JSX.Element { const [results, setResults] = useState([]); const [totalResults, setTotalResults] = useState(0); const [isLoading, setIsLoading] = useState(false); + const [error, setError] = useState(null); const [selectedTags, setSelectedTags] = useState([]); const [selectedStatus, setSelectedStatus] = useState(); const [availableTags, setAvailableTags] = useState([]); @@ -51,6 +52,7 @@ export default function SearchPage(): React.JSX.Element { } setIsLoading(true); + setError(null); try { // Build query params const params = new URLSearchParams({ q: query }); @@ -67,6 +69,7 @@ export default function SearchPage(): React.JSX.Element { setTotalResults(response.pagination.total); } catch (error) { console.error("Search failed:", error); + setError("Search temporarily unavailable. Please try again in a moment."); setResults([]); setTotalResults(0); } finally { @@ -99,8 +102,21 @@ export default function SearchPage(): React.JSX.Element { + {/* Error state */} + {error && ( +
+
+ ⚠️ +
+

Search Unavailable

+

{error}

+
+
+
+ )} + {/* Results area */} - {query && ( + {query && !error && (
)} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 9e94579..55edc26 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -271,6 +271,12 @@ importers: bullmq: specifier: ^5.67.2 version: 5.67.2 + class-transformer: + specifier: ^0.5.1 + version: 0.5.1 + class-validator: + specifier: ^0.14.1 + version: 0.14.3 dockerode: specifier: ^4.0.2 version: 4.0.9 @@ -344,6 +350,9 @@ importers: '@tanstack/react-query': specifier: ^5.90.20 version: 5.90.20(react@19.2.4) + '@types/dompurify': + specifier: ^3.2.0 + version: 3.2.0 '@xyflow/react': specifier: ^12.5.3 version: 12.10.0(@types/react@19.2.10)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) @@ -353,6 +362,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + dompurify: + specifier: ^3.3.1 + version: 3.3.1 elkjs: specifier: ^0.9.3 version: 0.9.3 @@ -2780,6 +2792,10 @@ packages: '@types/dockerode@3.3.47': resolution: {integrity: sha512-ShM1mz7rCjdssXt7Xz0u1/R2BJC7piWa3SJpUBiVjCf2A3XNn4cP6pUVaD8bLanpPVVn4IKzJuw3dOvkJ8IbYw==} + '@types/dompurify@3.2.0': + resolution: {integrity: sha512-Fgg31wv9QbLDA0SpTOXO3MaxySc4DKGLi8sna4/Utjo4r3ZRPdCt4UQee8BWr+Q5z21yifghREPJGYaEOEIACg==} + deprecated: This is a stub types definition. dompurify provides its own type definitions, so you do not need this installed. + '@types/eslint-scope@3.7.7': resolution: {integrity: sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg==} @@ -6938,7 +6954,7 @@ snapshots: chalk: 5.6.2 commander: 12.1.0 dotenv: 17.2.3 - drizzle-orm: 0.41.0(@opentelemetry/api@1.9.0)(@prisma/client@6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)) + drizzle-orm: 0.41.0(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)) open: 10.2.0 pg: 8.17.2 prettier: 3.8.1 @@ -9164,6 +9180,10 @@ snapshots: '@types/node': 22.19.7 '@types/ssh2': 1.15.5 + '@types/dompurify@3.2.0': + dependencies: + dompurify: 3.3.1 + '@types/eslint-scope@3.7.7': dependencies: '@types/eslint': 9.6.1 @@ -9823,7 +9843,7 @@ snapshots: optionalDependencies: '@prisma/client': 5.22.0(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)) better-sqlite3: 12.6.2 - drizzle-orm: 0.41.0(@opentelemetry/api@1.9.0)(@prisma/client@6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)) + drizzle-orm: 0.41.0(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)) next: 16.1.6(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) pg: 8.17.2 prisma: 6.19.2(magicast@0.3.5)(typescript@5.9.3) @@ -9848,7 +9868,7 @@ snapshots: optionalDependencies: '@prisma/client': 6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3) better-sqlite3: 12.6.2 - drizzle-orm: 0.41.0(@opentelemetry/api@1.9.0)(@prisma/client@6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)) + drizzle-orm: 0.41.0(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)) next: 16.1.6(@babel/core@7.28.6)(@opentelemetry/api@1.9.0)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) pg: 8.17.2 prisma: 6.19.2(magicast@0.3.5)(typescript@5.9.3) @@ -10596,6 +10616,16 @@ snapshots: dotenv@17.2.3: {} + drizzle-orm@0.41.0(@opentelemetry/api@1.9.0)(@prisma/client@5.22.0(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)): + optionalDependencies: + '@opentelemetry/api': 1.9.0 + '@prisma/client': 5.22.0(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)) + '@types/pg': 8.16.0 + better-sqlite3: 12.6.2 + kysely: 0.28.10 + pg: 8.17.2 + prisma: 6.19.2(magicast@0.3.5)(typescript@5.9.3) + drizzle-orm@0.41.0(@opentelemetry/api@1.9.0)(@prisma/client@6.19.2(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3))(typescript@5.9.3))(@types/pg@8.16.0)(better-sqlite3@12.6.2)(kysely@0.28.10)(pg@8.17.2)(prisma@6.19.2(magicast@0.3.5)(typescript@5.9.3)): optionalDependencies: '@opentelemetry/api': 1.9.0 @@ -10605,6 +10635,7 @@ snapshots: kysely: 0.28.10 pg: 8.17.2 prisma: 6.19.2(magicast@0.3.5)(typescript@5.9.3) + optional: true dunder-proto@1.0.1: dependencies: