diff --git a/apps/desktop2/.cursor/rules/style.mdc b/apps/desktop2/.cursor/rules/style.mdc index 674c1fc6d..80ce531c1 100644 --- a/apps/desktop2/.cursor/rules/style.mdc +++ b/apps/desktop2/.cursor/rules/style.mdc @@ -2,6 +2,11 @@ alwaysApply: true --- -## Conditional Tailwind ClassNames +## Code Commebts -- Use `cn` (import with `import { cn } from "@hypr/ui/lib/utils"`). It is similar to `clsx`. Always pass an array. +- By default, avoid writing comments at all. +- If you write one, it should be about "Why", not "What". + +## Tailwind ClassNames + +- If there are many classNames and they have conditional logic, use `cn` (import it with `import { cn } from "@hypr/ui/lib/utils"`). It is similar to `clsx`. Always pass an array. Split by logical grouping. diff --git a/apps/desktop2/package.json b/apps/desktop2/package.json index 924cd5619..87f3c45d0 100644 --- a/apps/desktop2/package.json +++ b/apps/desktop2/package.json @@ -22,6 +22,9 @@ "@hypr/tiptap": "workspace:^", "@hypr/ui": "workspace:^", "@hypr/utils": "workspace:^", + "@orama/highlight": "^0.1.9", + "@orama/orama": "^3.1.15", + "@orama/plugin-qps": "^3.1.15", "@sentry/react": "^8.55.0", "@supabase/supabase-js": "^2.75.0", "@t3-oss/env-core": "^0.13.8", @@ -39,6 +42,7 @@ "ai": "^5.0.68", "clsx": "^2.1.1", "date-fns": "^4.1.0", + "dompurify": "^3.2.7", "lucide-react": "^0.544.0", "motion": "^11.18.2", "re-resizable": "^6.11.2", diff --git a/apps/desktop2/src/components/chat/index.tsx b/apps/desktop2/src/components/chat/index.tsx index a9d8822ba..558f14507 100644 --- a/apps/desktop2/src/components/chat/index.tsx +++ b/apps/desktop2/src/components/chat/index.tsx @@ -13,7 +13,7 @@ export function ChatFloatingButton() { const [chatGroupId, setChatGroupId] = useState(undefined); useAutoCloser(() => setIsOpen(false), { esc: isOpen, outside: false }); - useHotkeys("meta+j", () => setIsOpen((prev) => !prev)); + useHotkeys("mod+j", () => setIsOpen((prev) => !prev)); const handleClickTrigger = useCallback(async () => { const isExists = await windowsCommands.windowIsExists({ type: "chat" }); diff --git a/apps/desktop2/src/components/main/body/search.tsx b/apps/desktop2/src/components/main/body/search.tsx index dc66afb37..669d8770f 100644 --- a/apps/desktop2/src/components/main/body/search.tsx +++ b/apps/desktop2/src/components/main/body/search.tsx @@ -1,20 +1,90 @@ -import { SearchIcon } from "lucide-react"; -import { useState } from "react"; +import { Loader2Icon, SearchIcon, XIcon } from "lucide-react"; +import { useRef } from "react"; +import { useHotkeys } from "react-hotkeys-hook"; + +import { cn } from "@hypr/ui/lib/utils"; +import { useSearch } from "../../../contexts/search"; export function Search() { - const [searchQuery, setSearchQuery] = useState(""); + const { query, setQuery, isSearching, isIndexing, onFocus, onBlur } = useSearch(); + const inputRef = useRef(null); + + const showLoading = isSearching || isIndexing; + + useHotkeys("mod+k", (e) => { + e.preventDefault(); + inputRef.current?.focus(); + }); + + useHotkeys( + "down", + (event) => { + if (document.activeElement === inputRef.current) { + event.preventDefault(); + console.log("down"); + } + }, + { enableOnFormTags: true }, + ); + + useHotkeys( + "up", + (event) => { + if (document.activeElement === inputRef.current) { + event.preventDefault(); + console.log("up"); + } + }, + { enableOnFormTags: true }, + ); + + useHotkeys( + "enter", + (event) => { + if (document.activeElement === inputRef.current) { + event.preventDefault(); + console.log("enter"); + } + }, + { enableOnFormTags: true }, + ); return (
- + {showLoading + ? + : } setSearchQuery(e.target.value)} - className="w-full pl-9 pr-4 py-2 text-sm rounded-lg bg-gray-100 focus:outline-none focus:bg-gray-200 border-0" + value={query} + onChange={(e) => setQuery(e.target.value)} + onFocus={onFocus} + onBlur={onBlur} + className={cn([ + "text-sm", + "w-full pl-9 py-2", + query ? "pr-9" : "pr-4", + "rounded-lg bg-gray-100 border-0", + "focus:outline-none focus:bg-gray-200", + ])} /> + {query && ( + + )}
); diff --git a/apps/desktop2/src/components/main/body/sessions/floating-regenerate-button.tsx b/apps/desktop2/src/components/main/body/sessions/floating-regenerate-button.tsx index edede4a3b..dd7d24e67 100644 --- a/apps/desktop2/src/components/main/body/sessions/floating-regenerate-button.tsx +++ b/apps/desktop2/src/components/main/body/sessions/floating-regenerate-button.tsx @@ -12,7 +12,6 @@ const TEMPLATES = [ "Action Items", "Decision Log", "Key Insights", - "Brainstorming", ]; export function FloatingRegenerateButton() { diff --git a/apps/desktop2/src/components/main/sidebar/index.tsx b/apps/desktop2/src/components/main/sidebar/index.tsx index 4ad872d0d..6a5d94dd2 100644 --- a/apps/desktop2/src/components/main/sidebar/index.tsx +++ b/apps/desktop2/src/components/main/sidebar/index.tsx @@ -2,12 +2,17 @@ import { clsx } from "clsx"; import { PanelLeftCloseIcon } from "lucide-react"; import { useLeftSidebar } from "@hypr/utils/contexts"; +import { useSearch } from "../../../contexts/search"; import { NewNoteButton } from "./new-note-button"; import { ProfileSection } from "./profile"; +import { SearchResults } from "./search"; import { TimelineView } from "./timeline"; export function LeftSidebar() { const { togglePanel: toggleLeftPanel } = useLeftSidebar(); + const { query } = useSearch(); + + const showSearchResults = query.trim() !== ""; return (
@@ -29,12 +34,14 @@ export function LeftSidebar() {
- +
+ {showSearchResults ? : } +
diff --git a/apps/desktop2/src/components/main/sidebar/search/empty.tsx b/apps/desktop2/src/components/main/sidebar/search/empty.tsx new file mode 100644 index 000000000..0c16aeee5 --- /dev/null +++ b/apps/desktop2/src/components/main/sidebar/search/empty.tsx @@ -0,0 +1,22 @@ +import { SearchXIcon } from "lucide-react"; + +import { cn } from "@hypr/ui/lib/utils"; + +export function SearchNoResults() { + return ( +
+
+
+ +
+

+ No results found +

+

+ Try using different keywords or check your spelling. Results are filtered to show only the most relevant + matches. +

+
+
+ ); +} diff --git a/apps/desktop2/src/components/main/sidebar/search/group.tsx b/apps/desktop2/src/components/main/sidebar/search/group.tsx new file mode 100644 index 000000000..179679082 --- /dev/null +++ b/apps/desktop2/src/components/main/sidebar/search/group.tsx @@ -0,0 +1,89 @@ +import { ChevronDownIcon } from "lucide-react"; +import { useState } from "react"; + +import { cn } from "@hypr/ui/lib/utils"; +import { type SearchGroup } from "../../../../contexts/search"; +import { SearchResultItem } from "./item"; + +const ITEMS_PER_PAGE = 3; +const LOAD_MORE_STEP = 5; + +export function SearchResultGroup({ + group, + icon: Icon, + rank, + maxScore, +}: { + group: SearchGroup; + icon: React.ComponentType<{ className?: string }>; + rank: number; + maxScore: number; +}) { + const [visibleCount, setVisibleCount] = useState(ITEMS_PER_PAGE); + + if (group.totalCount === 0) { + return null; + } + + const visibleResults = group.results.slice(0, visibleCount); + const hasMore = group.totalCount > visibleCount; + const isTopRanked = rank === 1; + + return ( +
+
+ +

+ {group.title} +

+ + ({group.totalCount}) + + {isTopRanked && ( + + Best Match + + )} +
+
+ {visibleResults.map((result) => )} +
+ {hasMore && ( + + )} +
+ ); +} diff --git a/apps/desktop2/src/components/main/sidebar/search/index.tsx b/apps/desktop2/src/components/main/sidebar/search/index.tsx new file mode 100644 index 000000000..f358ccb09 --- /dev/null +++ b/apps/desktop2/src/components/main/sidebar/search/index.tsx @@ -0,0 +1,46 @@ +import { Building2Icon, FileTextIcon, UserIcon } from "lucide-react"; + +import { cn } from "@hypr/ui/lib/utils"; +import { useSearch } from "../../../../contexts/search"; +import { SearchNoResults } from "./empty"; +import { SearchResultGroup } from "./group"; + +const ICON_MAP = { + session: FileTextIcon, + human: UserIcon, + organization: Building2Icon, +}; + +export function SearchResults() { + const { results, query } = useSearch(); + + if (!query || !results) { + return null; + } + + if (results.totalResults === 0) { + return ; + } + + return ( +
+
+
+

+ {results.totalResults} result{results.totalResults !== 1 ? "s" : ""} for "{query}" +

+
+ + {results.groups.map((group, index) => ( + + ))} +
+
+ ); +} diff --git a/apps/desktop2/src/components/main/sidebar/search/item.tsx b/apps/desktop2/src/components/main/sidebar/search/item.tsx new file mode 100644 index 000000000..b2cfd38c6 --- /dev/null +++ b/apps/desktop2/src/components/main/sidebar/search/item.tsx @@ -0,0 +1,109 @@ +import DOMPurify from "dompurify"; +import { Building2Icon, FileTextIcon, UserIcon } from "lucide-react"; +import { useCallback, useMemo } from "react"; + +import { cn } from "@hypr/ui/lib/utils"; +import { type SearchResult } from "../../../../contexts/search"; +import { Tab, useTabs } from "../../../../store/zustand/tabs"; + +export function SearchResultItem({ + result, + maxScore, +}: { + result: SearchResult; + maxScore: number; +}) { + const { openCurrent } = useTabs(); + const handleClick = useCallback(() => { + const tab = getTab(result); + if (tab) { + openCurrent(tab); + } + }, [openCurrent, result]); + + const sanitizedTitle = useMemo( + () => DOMPurify.sanitize(result.titleHighlighted, { ALLOWED_TAGS: ["mark"], ALLOWED_ATTR: [] }), + [result.titleHighlighted], + ); + + const sanitizedContent = useMemo( + () => DOMPurify.sanitize(result.contentHighlighted.slice(0, 200), { ALLOWED_TAGS: ["mark"], ALLOWED_ATTR: [] }), + [result.contentHighlighted], + ); + + const Icon = result.type === "session" + ? FileTextIcon + : result.type === "human" + ? UserIcon + : Building2Icon; + + const confidence = getConfidenceLevel(result.score, maxScore); + + return ( + + ); +} + +function getTab(result: SearchResult): Tab | null { + if (result.type === "session") { + return { type: "sessions", active: true, id: result.id, state: { editor: "raw" } }; + } + if (result.type === "human") { + return { type: "humans", active: true, id: result.id }; + } + if (result.type === "organization") { + return { type: "organizations", active: true, id: result.id }; + } + + return null; +} + +function getConfidenceLevel(score: number, maxScore: number): { + label: string; + color: string; +} { + const normalizedScore = maxScore > 0 ? score / maxScore : 0; + + if (normalizedScore >= 0.8) { + return { label: "High", color: "bg-green-500" }; + } else if (normalizedScore >= 0.5) { + return { label: "Medium", color: "bg-yellow-500" }; + } else { + return { label: "Low", color: "bg-gray-400" }; + } +} diff --git a/apps/desktop2/src/components/main/sidebar/timeline.tsx b/apps/desktop2/src/components/main/sidebar/timeline.tsx index b4c2bb830..21d07aae3 100644 --- a/apps/desktop2/src/components/main/sidebar/timeline.tsx +++ b/apps/desktop2/src/components/main/sidebar/timeline.tsx @@ -16,7 +16,7 @@ export function TimelineView() { const { groupedItems, sortedDates } = useTimelineData(); return ( -
+
{sortedDates.map((date) => (
diff --git a/apps/desktop2/src/contexts/search/index.tsx b/apps/desktop2/src/contexts/search/index.tsx new file mode 100644 index 000000000..67af76600 --- /dev/null +++ b/apps/desktop2/src/contexts/search/index.tsx @@ -0,0 +1,587 @@ +import { useRouteContext } from "@tanstack/react-router"; +import { createContext, useCallback, useContext, useEffect, useMemo, useRef, useState } from "react"; + +import { Highlight } from "@orama/highlight"; +import { create, insert, Orama, search as oramaSearch } from "@orama/orama"; +import { pluginQPS } from "@orama/plugin-qps"; + +export type SearchEntityType = "session" | "human" | "organization"; + +export interface SearchFilters { + created_at?: { + gte?: number; + lte?: number; + gt?: number; + lt?: number; + eq?: number; + }; +} + +export interface SearchResult { + id: string; + type: SearchEntityType; + title: string; + titleHighlighted: string; + content: string; + contentHighlighted: string; + created_at: number; + folder_id: string; + event_id: string; + org_id: string; + is_user: boolean; + metadata: Record; + score: number; +} + +export interface SearchGroup { + key: string; + type: SearchEntityType; + title: string; + results: SearchResult[]; + totalCount: number; + topScore: number; +} + +export interface GroupedSearchResults { + groups: SearchGroup[]; + totalResults: number; + maxScore: number; +} + +interface SearchContextValue { + query: string; + setQuery: (query: string) => void; + filters: SearchFilters | null; + setFilters: (filters: SearchFilters | null) => void; + results: GroupedSearchResults | null; + isSearching: boolean; + isFocused: boolean; + isIndexing: boolean; + onFocus: () => void; + onBlur: () => void; +} + +interface SearchDocument { + id: string; + type: SearchEntityType; + title: string; + content: string; + created_at: number; + folder_id: string; + event_id: string; + org_id: string; + is_user: boolean; + metadata: string; +} + +interface SearchHit { + score: number; + document: SearchDocument; +} + +type SerializableObject = Record; + +const SCORE_PERCENTILE_THRESHOLD = 0.1; +const SPACE_REGEX = /\s+/g; + +const GROUP_TITLES: Record = { + session: "Sessions", + human: "People", + organization: "Organizations", +}; + +function safeParseJSON(value: unknown): unknown { + if (typeof value !== "string") { + return value; + } + + try { + return JSON.parse(value); + } catch { + return value; + } +} + +function normalizeQuery(query: string): string { + return query.trim().replace(SPACE_REGEX, " "); +} + +function toTrimmedString(value: unknown): string { + if (typeof value === "string") { + return value.trim(); + } + + return ""; +} + +function mergeContent(parts: unknown[]): string { + return parts + .map(toTrimmedString) + .filter(Boolean) + .join(" "); +} + +function parseMetadata(metadata: unknown): SerializableObject { + if (typeof metadata !== "string" || metadata.length === 0) { + return {}; + } + + const parsed = safeParseJSON(metadata); + if (typeof parsed === "object" && parsed !== null) { + return parsed as SerializableObject; + } + + return {}; +} + +function flattenTranscript(transcript: unknown): string { + if (transcript == null) { + return ""; + } + + const parsed = safeParseJSON(transcript); + + if (typeof parsed === "string") { + return parsed; + } + + if (Array.isArray(parsed)) { + return mergeContent( + parsed.map((segment) => { + if (!segment) { + return ""; + } + + if (typeof segment === "string") { + return segment; + } + + if (typeof segment === "object") { + const record = segment as Record; + const preferred = record.text ?? record.content; + if (typeof preferred === "string") { + return preferred; + } + + return flattenTranscript(Object.values(record)); + } + + return ""; + }), + ); + } + + if (typeof parsed === "object" && parsed !== null) { + return mergeContent(Object.values(parsed).map((value) => flattenTranscript(value))); + } + + return ""; +} + +function collectCells( + persistedStore: any, + table: string, + rowId: string, + fields: string[], +): Record { + return fields.reduce>((acc, field) => { + acc[field] = persistedStore.getCell(table, rowId, field); + return acc; + }, {}); +} + +function createSessionSearchableContent(row: Record): string { + return mergeContent([ + row.raw_md, + row.enhanced_md, + flattenTranscript(row.transcript), + ]); +} + +function createHumanSearchableContent(row: Record): string { + return mergeContent([row.email, row.job_title, row.linkedin_username]); +} + +function toNumber(value: unknown): number { + if (typeof value === "number") { + return value; + } + if (typeof value === "string") { + const parsed = Number(value); + return isNaN(parsed) ? 0 : parsed; + } + return 0; +} + +function toString(value: unknown): string { + if (typeof value === "string" && value.length > 0) { + return value; + } + return ""; +} + +function toBoolean(value: unknown): boolean { + if (typeof value === "boolean") { + return value; + } + return false; +} + +function indexSessions(db: Orama, persistedStore: any): void { + const fields = [ + "user_id", + "created_at", + "folder_id", + "event_id", + "title", + "raw_md", + "enhanced_md", + "transcript", + ]; + + persistedStore.forEachRow("sessions", (rowId: string) => { + const row = collectCells(persistedStore, "sessions", rowId, fields); + const title = toTrimmedString(row.title) || "Untitled"; + + void insert(db, { + id: rowId, + type: "session", + title, + content: createSessionSearchableContent(row), + created_at: toNumber(row.created_at), + folder_id: toString(row.folder_id), + event_id: toString(row.event_id), + org_id: "", + is_user: false, + metadata: JSON.stringify({}), + }); + }); +} + +function indexHumans(db: Orama, persistedStore: any): void { + const fields = [ + "name", + "email", + "org_id", + "job_title", + "linkedin_username", + "is_user", + "created_at", + ]; + + persistedStore.forEachRow("humans", (rowId: string) => { + const row = collectCells(persistedStore, "humans", rowId, fields); + const title = toTrimmedString(row.name) || "Unknown"; + + void insert(db, { + id: rowId, + type: "human", + title, + content: createHumanSearchableContent(row), + created_at: toNumber(row.created_at), + folder_id: "", + event_id: "", + org_id: toString(row.org_id), + is_user: toBoolean(row.is_user), + metadata: JSON.stringify({ + email: row.email, + job_title: row.job_title, + }), + }); + }); +} + +function indexOrganizations(db: Orama, persistedStore: any): void { + const fields = ["name", "created_at"]; + + persistedStore.forEachRow("organizations", (rowId: string) => { + const row = collectCells(persistedStore, "organizations", rowId, fields); + const title = toTrimmedString(row.name) || "Unknown Organization"; + + void insert(db, { + id: rowId, + type: "organization", + title, + content: "", + created_at: toNumber(row.created_at), + folder_id: "", + event_id: "", + org_id: "", + is_user: false, + metadata: JSON.stringify({}), + }); + }); +} + +function buildOramaFilters(filters: SearchFilters | null): Record | undefined { + if (!filters || !filters.created_at) { + return undefined; + } + + const createdAtConditions: Record = {}; + + if (filters.created_at.gte !== undefined) { + createdAtConditions.gte = filters.created_at.gte; + } + if (filters.created_at.lte !== undefined) { + createdAtConditions.lte = filters.created_at.lte; + } + if (filters.created_at.gt !== undefined) { + createdAtConditions.gt = filters.created_at.gt; + } + if (filters.created_at.lt !== undefined) { + createdAtConditions.lt = filters.created_at.lt; + } + if (filters.created_at.eq !== undefined) { + createdAtConditions.eq = filters.created_at.eq; + } + + return Object.keys(createdAtConditions).length > 0 + ? { created_at: createdAtConditions } + : undefined; +} + +function calculateDynamicThreshold(scores: number[]): number { + if (scores.length === 0) { + return 0; + } + + const sortedScores = [...scores].sort((a, b) => b - a); + const thresholdIndex = Math.floor(sortedScores.length * SCORE_PERCENTILE_THRESHOLD); + + return sortedScores[Math.min(thresholdIndex, sortedScores.length - 1)] || 0; +} + +function createSearchResult(hit: SearchHit, query: string): SearchResult { + const highlighter = new Highlight(); + const titleHighlighted = highlighter.highlight(hit.document.title, query); + const contentHighlighted = highlighter.highlight(hit.document.content, query); + + return { + id: hit.document.id, + type: hit.document.type, + title: hit.document.title, + titleHighlighted: titleHighlighted.HTML, + content: hit.document.content, + contentHighlighted: contentHighlighted.HTML, + created_at: hit.document.created_at, + folder_id: hit.document.folder_id, + event_id: hit.document.event_id, + org_id: hit.document.org_id, + is_user: hit.document.is_user, + metadata: parseMetadata(hit.document.metadata), + score: hit.score, + }; +} + +function sortResultsByScore(a: SearchResult, b: SearchResult): number { + return b.score - a.score; +} + +function toGroup( + type: SearchEntityType, + results: SearchResult[], +): SearchGroup { + const topScore = results[0]?.score || 0; + + return { + key: type, + type, + title: GROUP_TITLES[type], + results, + totalCount: results.length, + topScore, + }; +} + +function groupSearchResults( + hits: SearchHit[], + query: string, +): GroupedSearchResults { + if (hits.length === 0) { + return { + groups: [], + totalResults: 0, + maxScore: 0, + }; + } + + const scores = hits.map((hit) => hit.score); + const maxScore = Math.max(...scores); + const threshold = calculateDynamicThreshold(scores); + + const grouped = hits.reduce>((acc, hit) => { + if (hit.score < threshold) { + return acc; + } + + const key = hit.document.type; + const list = acc.get(key) ?? []; + list.push(createSearchResult(hit, query)); + acc.set(key, list); + return acc; + }, new Map()); + + const groups = Array.from(grouped.entries()) + .map(([type, results]) => toGroup(type, results.sort(sortResultsByScore))) + .sort((a, b) => b.topScore - a.topScore); + + const totalResults = groups.reduce((count, group) => count + group.totalCount, 0); + + return { + groups, + totalResults, + maxScore, + }; +} + +const SearchContext = createContext(null); + +export function SearchProvider({ children }: { children: React.ReactNode }) { + const { persistedStore } = useRouteContext({ from: "__root__" }); + + const [query, setQuery] = useState(""); + const [filters, setFilters] = useState(null); + const [isSearching, setIsSearching] = useState(false); + const [isFocused, setIsFocused] = useState(false); + const [isIndexing, setIsIndexing] = useState(false); + const [searchHits, setSearchHits] = useState([]); + const [searchQuery, setSearchQuery] = useState(""); + + const oramaInstance = useRef | null>(null); + + const resetSearchState = useCallback(() => { + setSearchHits([]); + setSearchQuery(""); + }, []); + + const createIndex = useCallback(async () => { + if (!persistedStore || isIndexing) { + return; + } + + setIsIndexing(true); + + try { + const db = create({ + schema: { + id: "string", + type: "enum", + title: "string", + content: "string", + created_at: "number", + folder_id: "string", + event_id: "string", + org_id: "string", + is_user: "boolean", + metadata: "string", + } as const, + plugins: [pluginQPS()], + }); + + indexSessions(db, persistedStore); + indexHumans(db, persistedStore); + indexOrganizations(db, persistedStore); + + oramaInstance.current = db; + } catch (error) { + console.error("Failed to create search index:", error); + } finally { + setIsIndexing(false); + } + }, [persistedStore, isIndexing]); + + const performSearch = useCallback( + async (searchQueryInput: string, searchFilters: SearchFilters | null) => { + const normalizedQuery = normalizeQuery(searchQueryInput); + + if (!oramaInstance.current || normalizedQuery.length < 2) { + resetSearchState(); + setIsSearching(false); + return; + } + + setIsSearching(true); + + try { + const whereClause = buildOramaFilters(searchFilters); + + const searchResults = await oramaSearch(oramaInstance.current, { + term: normalizedQuery, + boost: { + title: 3, + content: 1, + }, + limit: 100, + tolerance: 1, + ...(whereClause && { where: whereClause }), + }); + + const hits = searchResults.hits as unknown as SearchHit[]; + setSearchHits(hits); + setSearchQuery(normalizedQuery); + } catch (error) { + console.error("Search failed:", error); + resetSearchState(); + } finally { + setIsSearching(false); + } + }, + [resetSearchState], + ); + + useEffect(() => { + const normalizedQuery = normalizeQuery(query); + + if (normalizedQuery.length < 2) { + resetSearchState(); + setIsSearching(false); + } else { + void performSearch(normalizedQuery, filters); + } + }, [query, filters, performSearch, resetSearchState]); + + const onFocus = useCallback(() => { + setIsFocused(true); + if (!oramaInstance.current) { + void createIndex(); + } + }, [createIndex]); + + const onBlur = useCallback(() => { + setIsFocused(false); + }, []); + + const results = useMemo(() => { + if (searchHits.length === 0 || !searchQuery) { + return null; + } + return groupSearchResults(searchHits, searchQuery); + }, [searchHits, searchQuery]); + + const value = useMemo( + () => ({ + query, + setQuery, + filters, + setFilters, + results, + isSearching, + isFocused, + isIndexing, + onFocus, + onBlur, + }), + [query, filters, results, isSearching, isFocused, isIndexing, onFocus, onBlur], + ); + + return {children}; +} + +export function useSearch() { + const context = useContext(SearchContext); + if (!context) { + throw new Error("useSearch must be used within SearchProvider"); + } + return context; +} diff --git a/apps/desktop2/src/routes/app/main/_layout.tsx b/apps/desktop2/src/routes/app/main/_layout.tsx index 67b02ece2..b48bc75be 100644 --- a/apps/desktop2/src/routes/app/main/_layout.tsx +++ b/apps/desktop2/src/routes/app/main/_layout.tsx @@ -1,7 +1,8 @@ -import { LeftSidebarProvider, RightPanelProvider } from "@hypr/utils/contexts"; import { createFileRoute, Outlet, useRouteContext } from "@tanstack/react-router"; import { useEffect } from "react"; +import { LeftSidebarProvider, RightPanelProvider } from "@hypr/utils/contexts"; +import { SearchProvider } from "../../../contexts/search"; import { useTabs } from "../../../store/zustand/tabs"; import { id } from "../../../utils"; @@ -13,8 +14,10 @@ function Component() { return ( - - + + + + ); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index fcddaecec..f64fe7d04 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -146,7 +146,7 @@ importers: version: 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) '@tanstack/react-router-devtools': specifier: ^1.132.51 - version: 1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@22.18.9)(csstype@3.1.3)(jiti@1.21.7)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1) + version: 1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@22.18.9)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1) '@tauri-apps/api': specifier: ^2.8.0 version: 2.8.0 @@ -342,13 +342,13 @@ importers: version: 10.4.21(postcss@8.5.6) eslint: specifier: ^9.37.0 - version: 9.37.0(jiti@1.21.7) + version: 9.37.0(jiti@2.6.1) eslint-plugin-lingui: specifier: ^0.9.0 - version: 0.9.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) + version: 0.9.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) eslint-plugin-react: specifier: ^7.37.5 - version: 7.37.5(eslint@9.37.0(jiti@1.21.7)) + version: 7.37.5(eslint@9.37.0(jiti@2.6.1)) globals: specifier: ^15.15.0 version: 15.15.0 @@ -363,7 +363,7 @@ importers: version: 5.9.3 typescript-eslint: specifier: ^8.46.0 - version: 8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) + version: 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) vite: specifier: ^5.4.20 version: 5.4.20(@types/node@22.18.9)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0) @@ -406,6 +406,15 @@ importers: '@hypr/utils': specifier: workspace:^ version: link:../../packages/utils + '@orama/highlight': + specifier: ^0.1.9 + version: 0.1.9 + '@orama/orama': + specifier: ^3.1.15 + version: 3.1.15 + '@orama/plugin-qps': + specifier: ^3.1.15 + version: 3.1.15 '@sentry/react': specifier: ^8.55.0 version: 8.55.0(react@19.2.0) @@ -457,6 +466,9 @@ importers: date-fns: specifier: ^4.1.0 version: 4.1.0 + dompurify: + specifier: ^3.2.7 + version: 3.2.7 lucide-react: specifier: ^0.544.0 version: 0.544.0(react@19.2.0) @@ -499,10 +511,10 @@ importers: version: 10.0.0 '@tanstack/react-router-devtools': specifier: ^1.132.51 - version: 1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.1)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1) + version: 1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.1)(csstype@3.1.3)(jiti@1.21.7)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1) '@tanstack/router-plugin': specifier: ^1.132.51 - version: 1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + version: 1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) '@tauri-apps/cli': specifier: ^2.8.4 version: 2.8.4 @@ -517,7 +529,7 @@ importers: version: 19.2.1(@types/react@19.2.2) '@vitejs/plugin-react': specifier: ^4.7.0 - version: 4.7.0(vite@7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) + version: 4.7.0(vite@7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1)) autoprefixer: specifier: ^10.4.21 version: 10.4.21(postcss@8.5.6) @@ -532,7 +544,7 @@ importers: version: 5.8.3 vite: specifier: ^7.1.9 - version: 7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + version: 7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) vitest: specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@24.7.1)(jsdom@27.0.0(postcss@8.5.6))(lightningcss@1.30.1)(msw@2.10.4(@types/node@24.7.1)(typescript@5.8.3))(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0) @@ -3235,6 +3247,16 @@ packages: peerDependencies: '@opentelemetry/api': ^1.1.0 + '@orama/highlight@0.1.9': + resolution: {integrity: sha512-eH4uZMea4R9x9vBFJyS/87oR4Y8oIKxF7e1SItJ9DhPlexZWMVZRlFYvHS1BM/563/dU240ct4ZaGHoYKiCkyQ==} + + '@orama/orama@3.1.15': + resolution: {integrity: sha512-ltjr1WHlY+uqEKE0JG2G6Xn36mSQGmPdPGQedQyipekBdf0iAtp8oL9dckQRX8cP+nUfOZwwPWSu7km8gGciUg==} + engines: {node: '>= 20.0.0'} + + '@orama/plugin-qps@3.1.15': + resolution: {integrity: sha512-KPULE9QdKeG2mCoI/GiDS0R4/SE7P5Jhk9nOSBou5XFx0c7V2smqebX9xxsxeICxJhSBbyc+yDS0ludTUS9eUg==} + '@pivanov/utils@0.0.2': resolution: {integrity: sha512-q9CN0bFWxWgMY5hVVYyBgez1jGiLBa6I+LkG37ycylPhFvEGOOeaADGtUSu46CaZasPnlY8fCdVJZmrgKb1EPA==} peerDependencies: @@ -13396,9 +13418,9 @@ snapshots: '@esbuild/win32-x64@0.25.5': optional: true - '@eslint-community/eslint-utils@4.9.0(eslint@9.37.0(jiti@1.21.7))': + '@eslint-community/eslint-utils@4.9.0(eslint@9.37.0(jiti@2.6.1))': dependencies: - eslint: 9.37.0(jiti@1.21.7) + eslint: 9.37.0(jiti@2.6.1) eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.12.1': {} @@ -14467,6 +14489,14 @@ snapshots: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 2.1.0(@opentelemetry/api@1.9.0) + '@orama/highlight@0.1.9': {} + + '@orama/orama@3.1.15': {} + + '@orama/plugin-qps@3.1.15': + dependencies: + '@orama/orama': 3.1.15 + '@pivanov/utils@0.0.2(react-dom@19.2.0(react@19.2.0))(react@19.2.0)': dependencies: react: 19.2.0 @@ -15980,30 +16010,6 @@ snapshots: '@tanstack/query-core': 5.90.2 react: 19.2.0 - '@tanstack/react-router-devtools@1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@22.18.9)(csstype@3.1.3)(jiti@1.21.7)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)': - dependencies: - '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@tanstack/router-devtools-core': 1.132.51(@tanstack/router-core@1.132.47)(@types/node@22.18.9)(csstype@3.1.3)(jiti@1.21.7)(lightningcss@1.30.1)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1) - react: 19.2.0 - react-dom: 19.2.0(react@19.2.0) - vite: 7.1.9(@types/node@22.18.9)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - transitivePeerDependencies: - - '@tanstack/router-core' - - '@types/node' - - csstype - - jiti - - less - - lightningcss - - sass - - sass-embedded - - solid-js - - stylus - - sugarss - - terser - - tiny-invariant - - tsx - - yaml - '@tanstack/react-router-devtools@1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@22.18.9)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)': dependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) @@ -16028,13 +16034,13 @@ snapshots: - tsx - yaml - '@tanstack/react-router-devtools@1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.1)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)': + '@tanstack/react-router-devtools@1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(@tanstack/router-core@1.132.47)(@types/node@24.7.1)(csstype@3.1.3)(jiti@1.21.7)(lightningcss@1.30.1)(react-dom@19.2.0(react@19.2.0))(react@19.2.0)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)': dependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - '@tanstack/router-devtools-core': 1.132.51(@tanstack/router-core@1.132.47)(@types/node@24.7.1)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1) + '@tanstack/router-devtools-core': 1.132.51(@tanstack/router-core@1.132.47)(@types/node@24.7.1)(csstype@3.1.3)(jiti@1.21.7)(lightningcss@1.30.1)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1) react: 19.2.0 react-dom: 19.2.0(react@19.2.0) - vite: 7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vite: 7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) transitivePeerDependencies: - '@tanstack/router-core' - '@types/node' @@ -16139,29 +16145,6 @@ snapshots: tiny-invariant: 1.3.3 tiny-warning: 1.0.3 - '@tanstack/router-devtools-core@1.132.51(@tanstack/router-core@1.132.47)(@types/node@22.18.9)(csstype@3.1.3)(jiti@1.21.7)(lightningcss@1.30.1)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)': - dependencies: - '@tanstack/router-core': 1.132.47 - clsx: 2.1.1 - goober: 2.1.18(csstype@3.1.3) - solid-js: 1.9.7 - tiny-invariant: 1.3.3 - vite: 7.1.9(@types/node@22.18.9)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) - optionalDependencies: - csstype: 3.1.3 - transitivePeerDependencies: - - '@types/node' - - jiti - - less - - lightningcss - - sass - - sass-embedded - - stylus - - sugarss - - terser - - tsx - - yaml - '@tanstack/router-devtools-core@1.132.51(@tanstack/router-core@1.132.47)(@types/node@22.18.9)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)': dependencies: '@tanstack/router-core': 1.132.47 @@ -16185,14 +16168,14 @@ snapshots: - tsx - yaml - '@tanstack/router-devtools-core@1.132.51(@tanstack/router-core@1.132.47)(@types/node@24.7.1)(csstype@3.1.3)(jiti@2.6.1)(lightningcss@1.30.1)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)': + '@tanstack/router-devtools-core@1.132.51(@tanstack/router-core@1.132.47)(@types/node@24.7.1)(csstype@3.1.3)(jiti@1.21.7)(lightningcss@1.30.1)(solid-js@1.9.7)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tiny-invariant@1.3.3)(tsx@4.20.6)(yaml@2.8.1)': dependencies: '@tanstack/router-core': 1.132.47 clsx: 2.1.1 goober: 2.1.18(csstype@3.1.3) solid-js: 1.9.7 tiny-invariant: 1.3.3 - vite: 7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vite: 7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) optionalDependencies: csstype: 3.1.3 transitivePeerDependencies: @@ -16265,7 +16248,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@tanstack/router-plugin@1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': + '@tanstack/router-plugin@1.132.51(@tanstack/react-router@1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0))(vite@7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-syntax-jsx': 7.27.1(@babel/core@7.28.4) @@ -16283,7 +16266,7 @@ snapshots: zod: 3.25.76 optionalDependencies: '@tanstack/react-router': 1.132.47(react-dom@19.2.0(react@19.2.0))(react@19.2.0) - vite: 7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vite: 7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -17051,15 +17034,15 @@ snapshots: '@types/node': 22.18.9 optional: true - '@typescript-eslint/eslint-plugin@8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3))(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/eslint-plugin@8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@eslint-community/regexpp': 4.12.1 - '@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/scope-manager': 8.46.0 - '@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/type-utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.46.0 - eslint: 9.37.0(jiti@1.21.7) + eslint: 9.37.0(jiti@2.6.1) graphemer: 1.4.0 ignore: 7.0.5 natural-compare: 1.4.0 @@ -17068,14 +17051,14 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/scope-manager': 8.46.0 '@typescript-eslint/types': 8.46.0 '@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3) '@typescript-eslint/visitor-keys': 8.46.0 debug: 4.4.3(supports-color@8.1.1) - eslint: 9.37.0(jiti@1.21.7) + eslint: 9.37.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -17098,13 +17081,13 @@ snapshots: dependencies: typescript: 5.9.3 - '@typescript-eslint/type-utils@8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/type-utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: '@typescript-eslint/types': 8.46.0 '@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) debug: 4.4.3(supports-color@8.1.1) - eslint: 9.37.0(jiti@1.21.7) + eslint: 9.37.0(jiti@2.6.1) ts-api-utils: 2.1.0(typescript@5.9.3) typescript: 5.9.3 transitivePeerDependencies: @@ -17128,13 +17111,13 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3)': + '@typescript-eslint/utils@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3)': dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) '@typescript-eslint/scope-manager': 8.46.0 '@typescript-eslint/types': 8.46.0 '@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3) - eslint: 9.37.0(jiti@1.21.7) + eslint: 9.37.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -17167,7 +17150,7 @@ snapshots: transitivePeerDependencies: - supports-color - '@vitejs/plugin-react@4.7.0(vite@7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': + '@vitejs/plugin-react@4.7.0(vite@7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1))': dependencies: '@babel/core': 7.28.4 '@babel/plugin-transform-react-jsx-self': 7.27.1(@babel/core@7.28.4) @@ -17175,7 +17158,7 @@ snapshots: '@rolldown/pluginutils': 1.0.0-beta.27 '@types/babel__core': 7.20.5 react-refresh: 0.17.0 - vite: 7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) + vite: 7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1) transitivePeerDependencies: - supports-color @@ -19295,16 +19278,16 @@ snapshots: optionalDependencies: source-map: 0.6.1 - eslint-plugin-lingui@0.9.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3): + eslint-plugin-lingui@0.9.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.37.0(jiti@1.21.7) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.37.0(jiti@2.6.1) micromatch: 4.0.8 transitivePeerDependencies: - supports-color - typescript - eslint-plugin-react@7.37.5(eslint@9.37.0(jiti@1.21.7)): + eslint-plugin-react@7.37.5(eslint@9.37.0(jiti@2.6.1)): dependencies: array-includes: 3.1.9 array.prototype.findlast: 1.2.5 @@ -19312,7 +19295,7 @@ snapshots: array.prototype.tosorted: 1.1.4 doctrine: 2.1.0 es-iterator-helpers: 1.2.1 - eslint: 9.37.0(jiti@1.21.7) + eslint: 9.37.0(jiti@2.6.1) estraverse: 5.3.0 hasown: 2.0.2 jsx-ast-utils: 3.3.5 @@ -19335,9 +19318,9 @@ snapshots: eslint-visitor-keys@4.2.1: {} - eslint@9.37.0(jiti@1.21.7): + eslint@9.37.0(jiti@2.6.1): dependencies: - '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@1.21.7)) + '@eslint-community/eslint-utils': 4.9.0(eslint@9.37.0(jiti@2.6.1)) '@eslint-community/regexpp': 4.12.1 '@eslint/config-array': 0.21.0 '@eslint/config-helpers': 0.4.0 @@ -19373,7 +19356,7 @@ snapshots: natural-compare: 1.4.0 optionator: 0.9.4 optionalDependencies: - jiti: 1.21.7 + jiti: 2.6.1 transitivePeerDependencies: - supports-color @@ -24740,13 +24723,13 @@ snapshots: typedarray@0.0.6: optional: true - typescript-eslint@8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3): + typescript-eslint@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3): dependencies: - '@typescript-eslint/eslint-plugin': 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3))(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) - '@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) + '@typescript-eslint/eslint-plugin': 8.46.0(@typescript-eslint/parser@8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3))(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + '@typescript-eslint/parser': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) '@typescript-eslint/typescript-estree': 8.46.0(typescript@5.9.3) - '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@1.21.7))(typescript@5.9.3) - eslint: 9.37.0(jiti@1.21.7) + '@typescript-eslint/utils': 8.46.0(eslint@9.37.0(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.37.0(jiti@2.6.1) typescript: 5.9.3 transitivePeerDependencies: - supports-color @@ -25077,24 +25060,6 @@ snapshots: sugarss: 5.0.1(postcss@8.5.6) terser: 5.44.0 - vite@7.1.9(@types/node@22.18.9)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): - dependencies: - esbuild: 0.25.10 - fdir: 6.5.0(picomatch@4.0.3) - picomatch: 4.0.3 - postcss: 8.5.6 - rollup: 4.52.4 - tinyglobby: 0.2.15 - optionalDependencies: - '@types/node': 22.18.9 - fsevents: 2.3.3 - jiti: 1.21.7 - lightningcss: 1.30.1 - sugarss: 5.0.1(postcss@8.5.6) - terser: 5.44.0 - tsx: 4.20.6 - yaml: 2.8.1 - vite@7.1.9(@types/node@22.18.9)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): dependencies: esbuild: 0.25.10 @@ -25113,7 +25078,7 @@ snapshots: tsx: 4.20.6 yaml: 2.8.1 - vite@7.1.9(@types/node@24.7.1)(jiti@2.6.1)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): + vite@7.1.9(@types/node@24.7.1)(jiti@1.21.7)(lightningcss@1.30.1)(sugarss@5.0.1(postcss@8.5.6))(terser@5.44.0)(tsx@4.20.6)(yaml@2.8.1): dependencies: esbuild: 0.25.10 fdir: 6.5.0(picomatch@4.0.3) @@ -25124,7 +25089,7 @@ snapshots: optionalDependencies: '@types/node': 24.7.1 fsevents: 2.3.3 - jiti: 2.6.1 + jiti: 1.21.7 lightningcss: 1.30.1 sugarss: 5.0.1(postcss@8.5.6) terser: 5.44.0