Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
29 commits
Select commit Hold shift + click to select a range
e2e2134
feat: add full-text session search to project view
Mar 13, 2026
3cb95d6
fix: address code review findings for session search
Mar 13, 2026
f732b22
fix: address review pass 2 - spawn_blocking, path validation, stale r…
Mar 13, 2026
a31ff9e
fix: address review pass 3 - input validation and error fallback
Mar 13, 2026
ca3282b
fix: address review pass 4 - a11y, project switch, page reset, API en…
Mar 13, 2026
5a25655
fix: address review pass 5 - a11y, state reset, cross-platform valida…
Mar 13, 2026
2eafdc2
fix: address review pass 6 - result cap, canonical path, skip stale s…
Mar 13, 2026
11db48e
fix: address review pass 7 - canonical validation, truncate after sort
Mar 13, 2026
9b4d22f
fix: address review pass 8 - TOCTOU, pagination clamp, blank query pa…
Mar 13, 2026
9e60ba3
fix: wrap search status in aria-live region for screen readers
Mar 13, 2026
8575f75
feat: add accordion search results with highlighted matching snippets
Mar 13, 2026
de5724f
fix: accordion accessibility and symlink hardening
Mar 13, 2026
5a16c37
fix: Unicode-safe highlighting, deduplicate click paths
Mar 13, 2026
3070670
fix: preserve original casing in search snippets
Mar 13, 2026
f4fda21
fix: prevent search view flicker during query transitions
Mar 13, 2026
a0c3796
fix: use debouncedQuery for search-active state to prevent blank-out
Mar 13, 2026
7215007
fix: require minimum 2-char query to avoid expensive single-char scans
Mar 13, 2026
571bf76
fix: match frontend 2-char minimum with backend to prevent blank grid
Mar 13, 2026
8f5fdbe
feat: add open session and copy resume command buttons to accordion h…
Mar 13, 2026
a2db8eb
fix: debounce-aware search mode toggle and async clipboard write
Mar 13, 2026
46d52d8
fix: skip bad session files gracefully instead of aborting search
Mar 13, 2026
3f19b57
fix: prevent blank flash when clearing search or switching projects
Mar 13, 2026
f2a21b0
fix: update search placeholder to reflect project scope
Mar 13, 2026
3a0e40b
fix: normalize search query once and reuse for search and highlighting
Mar 13, 2026
5a9b73c
fix: use non-absolute result count wording for search results
Mar 13, 2026
15c1fda
style: apply cargo fmt formatting
Mar 13, 2026
0ef6d23
feat: add search operators (AND, OR, NOT, exact phrase)
Mar 13, 2026
5464dd5
fix: treat AND as explicit operator instead of literal search term
Mar 13, 2026
b3e38f9
feat: add global cross-project session search to projects view
Mar 13, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
fix: address review pass 6 - result cap, canonical path, skip stale s…
…earch

- Cap search results at 100 and stop scanning early
- Use canonical path validation instead of substring matching for
  path traversal prevention
- Skip stale debounced search after project switch to avoid
  unnecessary backend I/O

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
  • Loading branch information
Jose Monteiro and claude committed Mar 13, 2026
commit 2eafdc2005697ff848092da3f5cc185e1ca00075
26 changes: 20 additions & 6 deletions src-tauri/src/commands/claude.rs
Original file line number Diff line number Diff line change
Expand Up @@ -602,24 +602,34 @@ pub async fn search_project_sessions(
);

// Validate project_id to prevent path traversal
if project_id.is_empty()
|| project_id.contains('/')
|| project_id.contains('\\')
|| project_id.contains("..")
{
if project_id.is_empty() || project_id.contains('/') || project_id.contains('\\') {
return Err("Invalid project id".to_string());
}

let query_lower = query.to_lowercase();
let claude_dir = get_claude_dir().map_err(|e| e.to_string())?;
let project_dir = claude_dir.join("projects").join(&project_id);
let projects_dir = claude_dir.join("projects");
let project_dir = projects_dir.join(&project_id);

// Verify resolved path stays under the projects root
let canonical_projects_dir = projects_dir
.canonicalize()
.map_err(|e| e.to_string())?;
let canonical_project_dir = project_dir
.canonicalize()
.map_err(|_| format!("Project directory not found: {}", project_id))?;
if !canonical_project_dir.starts_with(&canonical_projects_dir) {
return Err("Invalid project id".to_string());
}
let todos_dir = claude_dir.join("todos");

if !project_dir.exists() {
return Err(format!("Project directory not found: {}", project_id));
}

tokio::task::spawn_blocking(move || {
const MAX_RESULTS: usize = 100;

let project_path = match get_project_path_from_sessions(&project_dir) {
Ok(path) => path,
Err(_) => decode_project_path(&project_id),
Expand All @@ -631,6 +641,10 @@ pub async fn search_project_sessions(
.map_err(|e| format!("Failed to read project directory: {}", e))?;

for entry in entries {
if sessions.len() >= MAX_RESULTS {
break;
}

let entry = entry.map_err(|e| format!("Failed to read directory entry: {}", e))?;
let path = entry.path();

Expand Down
6 changes: 6 additions & 0 deletions src/components/SessionList.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -71,9 +71,11 @@ export const SessionList: React.FC<SessionListProps> = ({
const [searching, setSearching] = useState(false);
const debouncedQuery = useDebounce(searchQuery, 300);
const searchRequestId = useRef(0);
const skipNextSearch = useRef(false);

// Hard reset when project context changes
useEffect(() => {
skipNextSearch.current = true;
searchRequestId.current += 1;
setSearchQuery("");
setSearchResults(null);
Expand All @@ -95,6 +97,10 @@ export const SessionList: React.FC<SessionListProps> = ({

// Perform search when debounced query changes
useEffect(() => {
if (skipNextSearch.current) {
skipNextSearch.current = false;
return;
}
if (!debouncedQuery.trim()) {
setSearchResults(null);
setSearchError(null);
Expand Down