Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
24 changes: 22 additions & 2 deletions REQUIREMENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

> **Note:** This document is automatically generated and verified against the live test suite by `scripts/generate_requirements.py` and `tests/backend/test_requirements_sync.py`.

**Test Verification Baseline:** **977 Automated Tests** (655 Pytest Backend + 273 Vitest Frontend + 49 Playwright E2E).
**Test Verification Baseline:** **993 Automated Tests** (656 Pytest Backend + 287 Vitest Frontend + 50 Playwright E2E).

---

Expand Down Expand Up @@ -642,7 +642,7 @@ classDiagram
- `test_process_file_content_with_custom_provider`
- `test_sync_single_git_repo_triggers_notification`

#### `tests/backend/test_navigator_router.py` (8 tests)
#### `tests/backend/test_navigator_router.py` (9 tests)
- `test_db`
- `test_api_get_navigator_tree_all`
- `test_api_get_navigator_tree_specific_repo`
Expand All @@ -651,6 +651,7 @@ classDiagram
- `test_api_get_file_outline_empty`
- `test_api_get_symbol_impact_success`
- `test_api_get_symbol_impact_not_found`
- `test_api_get_omni_search_symbols_and_files`

#### `tests/backend/test_navigator_service.py` (11 tests)
- `test_db`
Expand Down Expand Up @@ -1270,6 +1271,14 @@ and leaves the prior indexed state intact without data loss._
- displays PDF notice and hides text textarea when upload path is a PDF
- prevents direct text replacement of PDF files in replace modal

#### `NavigatorCodeViewer.test.tsx` (6 tests)
- renders line numbers and code lines
- highlights target line range and calls scrollIntoView
- toggles callers and impact drawer
- copies full code to clipboard when clicking Copy Code button
- copies permalink to clipboard when clicking Copy Link button
- renders fallback when content is empty

#### `NavigatorInspector.test.tsx` (10 tests)
- renders empty placeholder when no symbol is selected
- renders doc reader when fileContent is provided without an impact symbol
Expand All @@ -1282,6 +1291,16 @@ and leaves the prior indexed state intact without data loss._
- calls onSelectCallee when a clickable callee is clicked for cross-file navigation
- renders loading state when loading is true

#### `NavigatorOmniSearch.test.tsx` (8 tests)
- renders omni-search input with placeholder
- fetches matches when user types and displays floating overlay
- navigates with keyboard and selects on Enter
- closes dropdown on Escape key
- displays empty state when query returns no matches
- handles fetch error gracefully without crashing
- closes dropdown when clicking outside the container
- supports ArrowUp navigation within bounds

#### `NavigatorOutline.test.tsx` (9 tests)
- renders empty placeholder when outline is null or empty
- renders read document button and handles click when onReadDoc is provided
Expand Down Expand Up @@ -1561,3 +1580,4 @@ and leaves the prior indexed state intact without data loss._
- 6. Density Mode Toggling: toggles Compact, Balanced, and Spacious layout modes
- 7. Responsive Layout Audit: zero overflow, zero element collisions, and stable layout across desktop and mobile
- 8. Document Reader: opens markdown document, renders full content and switches between rendered and source view
- 9. Omni-Search & Synchronized Full-File Code Viewer: searches across symbols, opens full source, highlights target line with zero layout shift
15 changes: 15 additions & 0 deletions app/api/routers/navigator.py
Original file line number Diff line number Diff line change
Expand Up @@ -61,3 +61,18 @@ async def api_get_symbol_impact(
except Exception as e:
logger.error(f"Error getting symbol impact for {symbol_id} in {repo}: {e}")
return JSONResponse(status_code=500, content={"error": "Failed to retrieve symbol impact."})


@router.get("/admin/api/navigator/omni-search")
async def api_get_omni_search(
repo: str = Query(..., description="Repository name or '__all__'"),
q: str = Query("", description="Search query string"),
limit: int = Query(25, ge=1, le=100, description="Max matches to return")
):
try:
data = nav_service.get_omni_search(repo=repo, query=q, limit=limit)
return data
except Exception as e:
logger.error(f"Error executing omni-search for query '{q}' in {repo}: {e}")
return JSONResponse(status_code=500, content={"error": "Failed to execute omni-search."})

158 changes: 158 additions & 0 deletions app/services/navigator.py
Original file line number Diff line number Diff line change
Expand Up @@ -263,3 +263,161 @@ def get_symbol_impact(repo: str, symbol_id: int) -> Optional[Dict[str, Any]]:
"callees": [dict(c) for c in callees],
"imports": [dict(i) for i in imports]
}


def get_omni_search(repo: str, query: str, limit: int = 25) -> Dict[str, Any]:
raw_query = (query or "").strip()
if not raw_query:
return {
"query": "",
"repo": repo,
"total_matches": 0,
"matches": []
}

matches: List[Dict[str, Any]] = []
like_q = f"%{raw_query}%"
lower_q = raw_query.lower()

with get_db_connection() as conn:
repo_clause = "" if repo == "__all__" else " AND repo = ?"
repo_params = [] if repo == "__all__" else [repo]

# 1. Search AST Symbols
sym_sql = f"""
SELECT id, repo, filepath, name, full_symbol, kind, start_line, end_line, signature
FROM ast_symbols
WHERE (name LIKE ? OR full_symbol LIKE ?){repo_clause}
LIMIT ?
"""
sym_params = [like_q, like_q] + repo_params + [limit]
for row in conn.execute(sym_sql, sym_params).fetchall():
sym_name = row["name"] or ""
sym_lower = sym_name.lower()

if sym_lower == lower_q:
score = 0.99
label = "99% AST exact match"
elif sym_lower.startswith(lower_q):
score = 0.94
label = "94% AST prefix match"
else:
score = 0.88
label = "88% AST symbol match"

preview = row["signature"] or f"{row['kind']} {sym_name}"
matches.append({
"id": f"sym_{row['id']}",
"type": "symbol",
"symbol_id": row["id"],
"name": sym_name,
"kind": row["kind"],
"filepath": _clean_path(row["filepath"]),
"repo": row["repo"],
"start_line": row["start_line"],
"end_line": row["end_line"],
"score": score,
"score_label": label,
"preview": preview
})

# 2. Search File Paths
file_sql = f"""
SELECT filepath, repo, doc_type, language
FROM indexed_files
WHERE filepath LIKE ?{repo_clause}
LIMIT ?
"""
file_params = [like_q] + repo_params + [limit]
for row in conn.execute(file_sql, file_params).fetchall():
fp = _clean_path(row["filepath"])
fname = os.path.basename(fp)
fname_lower = fname.lower()

if fname_lower == lower_q:
score = 0.96
label = "96% Exact filename"
elif fname_lower.startswith(lower_q):
score = 0.92
label = "92% Filename prefix"
else:
score = 0.85
label = "85% Path substring"

matches.append({
"id": f"file_{fp}",
"type": "file",
"name": fname,
"kind": "file",
"filepath": fp,
"repo": row["repo"],
"start_line": 1,
"end_line": 1,
"score": score,
"score_label": label,
"preview": fp
})

# 3. Search API Routes
route_sql = f"""
SELECT id, repo, filepath, framework, http_method, path_pattern, handler_symbol, start_line, end_line
FROM api_routes
WHERE (path_pattern LIKE ? OR handler_symbol LIKE ?){repo_clause}
LIMIT ?
"""
route_params = [like_q, like_q] + repo_params + [limit]
for row in conn.execute(route_sql, route_params).fetchall():
pat = row["path_pattern"] or ""
score = 0.95 if pat.lower() == lower_q else 0.89
matches.append({
"id": f"route_{row['id']}",
"type": "route",
"name": f"{row['http_method']} {pat}",
"kind": "route",
"filepath": _clean_path(row["filepath"]),
"repo": row["repo"],
"start_line": row["start_line"],
"end_line": row["end_line"],
"score": score,
"score_label": f"{int(score * 100)}% Route match",
"preview": f"{row['http_method']} {pat} -> {row['handler_symbol'] or ''}"
})

# 4. Search Code Chunks if table exists
try:
chunk_sql = f"""
SELECT id, repo, filepath, chunk_text, start_line, end_line
FROM code_chunks
WHERE chunk_text LIKE ?{repo_clause}
LIMIT ?
"""
chunk_params = [like_q] + repo_params + [limit]
for row in conn.execute(chunk_sql, chunk_params).fetchall():
text = (row["chunk_text"] or "").strip()
preview = text.split("\n")[0][:120]
matches.append({
"id": f"code_{row['id']}",
"type": "code",
"name": preview[:50],
"kind": "code",
"filepath": _clean_path(row["filepath"]),
"repo": row["repo"],
"start_line": row["start_line"],
"end_line": row["end_line"],
"score": 0.86,
"score_label": "86% Code match",
"preview": preview
})
except Exception:
pass

matches.sort(key=lambda m: m["score"], reverse=True)
final_matches = matches[:limit]

return {
"query": raw_query,
"repo": repo,
"total_matches": len(final_matches),
"matches": final_matches
}

17 changes: 16 additions & 1 deletion docs/guide/user-guide/navigator.md
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,27 @@ The Codebase Navigator organizes project structure into three synchronized inter
- **API Routes** (`@app.get`, `app.post`, etc.)
- View parameter signatures, return types, and starting/ending line ranges.

### Pane 3: Code Intelligence & Impact Analysis (or Document Reader)
### Universal Omni-Search
- **Global Command Palette**: Search across AST symbols, files, API routes, and raw code text in real time from the center toolbar.
- **Match Types & Confidence Scoring**:
- `[symbol]` – High confidence AST matches (e.g., `99% AST exact match`, `94% AST prefix match`).
- `[file]` – File path and module matches (`92% filename match`).
- `[route]` – REST endpoint paths (`95% route match`).
- `[code]` – Raw source code substring occurrences (`88% code match`).
- **Keyboard Navigation**: Navigate results with <kbd>&uarr;</kbd> and <kbd>&darr;</kbd>, press <kbd>Enter</kbd> to jump, or <kbd>Esc</kbd> to dismiss.
- **Zero-Shift Floating Overlay**: Floats smoothly over the 3 panes without shifting header layout or altering pane heights.

### Pane 3: Code Intelligence & Full Source Viewer (or Document Reader)
- **Symbol Intelligence Mode**:
- **Callers & Callees**: Inspect incoming callers and outgoing references identified by AST cross-file analysis.
- **Click-Through Navigation**: Click any caller or callee chip to jump directly to its declaration in Pane 1 and Pane 2.
- **HTTP Route Specifications**: View endpoint paths, HTTP verbs, and request/response models.
- **Syntax Preview**: Read formatted implementation code blocks with line numbers and syntax highlighting.
- **Full Source Code Viewer**:
- Automatically loads full file contents with line numbering.
- **Target Line Highlighting**: Seamlessly scrolls to and highlights target line ranges (`targetStartLine` / `targetEndLine`) when jumping from Omni-Search or symbols outline.
- **Docked Symbol Impact Drawer**: Collapsible bottom drawer showing caller and callee counts with instant click-to-jump.
- **Copy Code & Link**: One-click actions to copy clean source code or deep links to specific lines.
- **Full Document & Markdown Reader Mode**:
- Automatically activates when selecting non-code files (`.md`, `.markdown`, `.txt`, `.json`, etc.) or clicking **Read Full Document** from Pane 2.
- **Rendered View**: Safe Markdown parser supporting formatted headings, lists, blockquotes, inline code, and fenced code blocks.
Expand Down
Loading
Loading