Files
stack/packages/mosaic/src/tui/hooks/use-search.ts
Jarvis c6fc090c98
All checks were successful
ci/woodpecker/push/ci Pipeline was successful
ci/woodpecker/pr/ci Pipeline was successful
feat(mosaic): merge @mosaic/cli into @mosaic/mosaic
@mosaic/mosaic is now the single package providing both:
- 'mosaic' binary (CLI: yolo, coord, prdy, tui, gateway, etc.)
- 'mosaic-wizard' binary (installation wizard)

Changes:
- Move packages/cli/src/* into packages/mosaic/src/
- Convert dynamic @mosaic/mosaic imports to static relative imports
- Add CLI deps (ink, react, socket.io-client, @mosaic/config) to mosaic
- Add jsx: react-jsx to mosaic's tsconfig
- Exclude packages/cli from workspace (pnpm-workspace.yaml)
- Update install.sh to install @mosaic/mosaic instead of @mosaic/cli
- Bump version to 0.0.17

This eliminates the circular dependency between @mosaic/cli and
@mosaic/mosaic that was blocking the build graph.
2026-04-04 20:07:27 -05:00

77 lines
1.9 KiB
TypeScript

import { useState, useMemo, useCallback } from 'react';
import type { Message } from './use-socket.js';
export interface SearchMatch {
messageIndex: number;
charOffset: number;
}
export interface UseSearchReturn {
query: string;
setQuery: (q: string) => void;
matches: SearchMatch[];
currentMatchIndex: number;
nextMatch: () => void;
prevMatch: () => void;
clear: () => void;
totalMatches: number;
}
export function useSearch(messages: Message[]): UseSearchReturn {
const [query, setQuery] = useState('');
const [currentMatchIndex, setCurrentMatchIndex] = useState(0);
const matches = useMemo<SearchMatch[]>(() => {
if (query.length < 2) return [];
const lowerQuery = query.toLowerCase();
const result: SearchMatch[] = [];
for (let i = 0; i < messages.length; i++) {
const msg = messages[i];
if (!msg) continue;
const content = msg.content.toLowerCase();
let offset = 0;
while (true) {
const idx = content.indexOf(lowerQuery, offset);
if (idx === -1) break;
result.push({ messageIndex: i, charOffset: idx });
offset = idx + 1;
}
}
return result;
}, [query, messages]);
// Reset match index when matches change
useMemo(() => {
setCurrentMatchIndex(0);
}, [matches]);
const nextMatch = useCallback(() => {
if (matches.length === 0) return;
setCurrentMatchIndex((prev) => (prev + 1) % matches.length);
}, [matches.length]);
const prevMatch = useCallback(() => {
if (matches.length === 0) return;
setCurrentMatchIndex((prev) => (prev - 1 + matches.length) % matches.length);
}, [matches.length]);
const clear = useCallback(() => {
setQuery('');
setCurrentMatchIndex(0);
}, []);
return {
query,
setQuery,
matches,
currentMatchIndex,
nextMatch,
prevMatch,
clear,
totalMatches: matches.length,
};
}