feat: unified smart search with exploration tracking and query program#285
feat: unified smart search with exploration tracking and query program#285
Conversation
Extract five reusable components to web/src/components/shared/search/: - ConceptSearchInput: search input + spinner + dropdown (was 4x duplicated) - SelectedConceptChip: concept selection display with Change button (5x) - SliderControl: labeled range slider for similarity/depth/hops (6x) - LoadButtons: Clean Graph / Add to Existing button pair (3x) - PathResults: path list, selection, and load buttons SearchBar uses these in all three mode sections. No behavior change — same three tabs, same state, same handlers. Reduces SearchBar from 1170 to 801 lines; extracted components are 260 lines total.
Replace mode-discriminated SearchParams (mode: concept|neighborhood|path) with parameter-presence model where mode is derived: - primaryConceptId only → explore (covers old concept + neighborhood) - primaryConceptId + destinationConceptId → path - depth controls neighborhood expansion (1=concept, >1=neighborhood) Add deriveMode() helper function. Collapse ExplorerView from 3 React Query hooks to 2 (explore + path). Update URL sync with short params (c, to, d, h, s) and backward-compatible legacy URL parsing. SearchBar still uses three tabs but maps to the new param shape.
Remove SmartSearchSubMode pill selector and per-mode state. Single search flow progressively reveals controls as parameters are populated: search → concept chip + depth slider + load buttons → optional destination → path controls. Mode is derived from parameter presence, matching the store's deriveMode() pattern.
New usePathEnrichment hook uses React Query useQueries to fetch subgraph neighborhoods around each path node in parallel. Performance guards cap enrichment depth at 2 and skip enrichment for paths with >50 nodes. In path mode, the depth slider becomes "Context" (range 0-2, default 0). Setting context > 0 expands neighborhoods around each path node, providing richer graph context for the discovered connections.
Add min-w-0 and overflow-hidden through the flex chain so controls shrink gracefully instead of clipping off-screen. Concept labels in SelectedConceptChip now truncate with ellipsis.
… slider Move GraphSettingsPanel and Settings3DPanel from floating PanelStack overlays into the sidebar Settings tab. Add embedded prop to both panels to strip floating-card chrome when rendered inside the sidebar. Remove the duplicate similarity threshold slider from the sidebar — SearchBar owns it as part of the search workflow. The graph canvas now only shows Legend and StatsPanel overlays.
The overflow-hidden added for responsive layout was preventing the absolutely-positioned SearchResultsDropdown from escaping its container to overlay the graph canvas. min-w-0 alone handles flex shrinking.
Add scrollbar styling using CSS custom properties so scrollbar thumb and track colors adapt to light, dark, and twilight modes. Uses both standard scrollbar-color property and WebKit pseudo-elements.
The URL init effect was re-triggering on every URL change (caused by store-to-URL sync), overwriting loadMode back to 'clean'. Fixed by running URL initialization only once per mount using a ref guard. Also give both load buttons equal visual weight (both secondary) and highlight the last-clicked button with primary color for feedback.
- Fix add-to-existing-graph: use ref-based loadMode tracking so data effect reads intended mode when data arrives, not stale closure value. Initialize lastProcessedData ref to current query data on mount so cached React Query results don't re-process on view switches. - Fix isolated nodes filtered out: filterByEdgeCategory now preserves nodes with no connections (from additive loading) instead of dropping them when edge category filters are active. - Simplify search UX: remove SelectedConceptChip toggle for both primary and destination searches. Input stays visible with concept label after selection; typing clears selection and restarts search. - Context menu: rename "Add X to Graph" to "Add Adjacent Nodes", route both follow and add-adjacent through rawGraphData store (persists across view switches). - Add warmth physics control (0.1-1.0) for D3 force simulation initial energy. Lower values = gentler settle on load/merge.
Track graph exploration as ordered +/- Cypher statements that can be saved, exported, and replayed. Each action (explore, follow, add-adjacent, load-path) records a step with its equivalent openCypher statement. - Add ExplorationStep/Session types and actions to graphStore - Wrap store with Zustand persist middleware (localStorage) - Add subtractRawGraphData for future subtractive operations - Create cypherGenerator.ts (stepToCypher, generateCypher, parse) - Instrument SearchBar, context menu, and ExplorerView action points
Right-clicking a node now shows "Remove from Graph" which removes the node and its connections via subtractRawGraphData, recording a step with op: '-' for the exploration session's Cypher trail.
D3Node now extends APIGraphNode and D3Link extends APIGraphLink. The transform spreads all API fields through instead of stripping them, so the full payload (search_terms, grounding_display, confidence_level, etc.) is available on graph nodes without re-fetching from the API.
The /query/related BFS can miss neighbors (stale accelerator, edge type gaps). Now getSubgraph discovers relationship targets from concept details and fetches any missing ones, so the subgraph is complete even when traversal is incomplete. Also fixes duplicate edges: normalizes dedup key to treat A→B and B→A with same type as one edge.
- FolderOpen icon in sidebar nav rail (consistent with report explorer) - Save button appears when exploration has steps, stores as QueryDefinition with definition_type 'exploration' - Load replays +/- Cypher statements sequentially via executeCypherQuery - Query list shows step count and date for exploration queries - Delete uses existing queryDefinitionStore infrastructure
Migration 050 adds 'exploration' to the query_definitions CHECK constraint and Pydantic model, enabling graph explorations to be saved as ordered +/- Cypher statement sequences.
API's QueryDefinitionCreateResponse omits `definition` and `metadata` fields, so saved queries showed "0 steps" until the next full fetch.
…ry replay Server: individual edges returned from RETURN c, r, n have both `id` and `start_id`/`end_id`. The parser checked `id` first, so edges were silently captured as nodes and `result.relationships` was empty. Client: replay mapped AGE internal vertex IDs as concept_ids. Added internalToConceptId translation map (matching BlockBuilder's pattern).
Context menu gains three new actions when origin+destination are set: - Travel submenu (From Origin / From Destination) finds path, merges nodes into graph, and animates camera through each node sequentially - Send to Polarity Explorer sets origin/destination as poles - Send Path to Reports creates a traversal report with per-path tables Traversal report type added to reportStore with rendering, CSV export, and Markdown export in ReportWorkspace.
…arch
All three query modes now compile to the same IR: { op, cypher }[].
Smart search generates it from UI actions, Cypher editor lets users
write +/- prefixed statements directly, and saved queries store it.
Fixes in the Cypher editor:
- Routes through rawGraphData pipeline (was bypassing with setGraphData)
- Correct AGE internal ID → concept_id mapping via shared mapper
- Multi-statement execution with +/- set algebra operators
- Records exploration steps so Save/Export work after execution
New capabilities:
- Export to Editor button pushes exploration session as Cypher text
- Loading saved queries reconstructs the exploration session
- Clear Graph eraser icon in the icon rail (clears graph + search state)
- Navigating to explorer without URL params no longer replays stale queries
Code Review: Unified Smart Search with Exploration TrackingScope: 30 files, +2535/-1162 lines. Large feature branch with new exploration session system, unified search model, Cypher generator/parser, and significant context menu expansion. Overall assessment: The architecture is sound. The parameter-presence model ( Bug: Exploration step always records
|
| Category | Count | Severity |
|---|---|---|
| Bugs / logic errors | 1 (always-+ ternary) |
Low -- cosmetic/confusing but not functionally broken |
| Race conditions | 1 (step before API) | Medium -- causes phantom steps on failure |
| Duplication | 1 (path extraction x3) | Medium -- maintenance risk |
| Type safety | 1 (pervasive any) |
Low -- existing pattern, not new debt |
| Misleading semantics | 1 (remove uses add-adjacent action) | Low -- affects script readability |
| Stale state window | 1 (subtract without clearing graphData) | Low |
None of these are merge-blockers. The most actionable items are the path extraction duplication (extract to shared utility) and the step-before-API-call pattern (move recording to success path).
AI-assisted review via Claude
- Remove dead ternary that always resolved to '+' (SearchBar.tsx) - Move addExplorationStep after API success to prevent phantom steps on failure (useGraphContextMenu.ts) - Use 'cypher' action type for remove operations so exported Cypher comments correctly reflect subtraction (useGraphContextMenu.ts)
- Extract duplicated AGE path extraction logic (SearchBar + context
menu) into shared extractGraphFromPath() in cypherResultMapper.ts
- Add RawGraphData, PathResult, PathResultNode types to cypherResultMapper
- Type graphStore rawGraphData interface with RawGraphData instead of
{ nodes: any[]; links: any[] }
- Replace ~28 any annotations across store, context menu, SearchBar,
and ExplorerView with RawGraphNode, RawGraphLink, and proper types
- Change catch (error: any) to catch (error: unknown) with narrowing
- Simplify store merge/subtract to use concept_id directly (canonical
field) instead of defensive n.id || n.concept_id fallbacks
Summary
Replaces the three separate search tabs (concept, neighborhood, path) with a unified progressive interface. Introduces exploration tracking that records graph actions as ordered Cypher statements, enabling save/load/replay of graph explorations. Establishes a unified query program IR (
{ op: '+' | '-', cypher: string }[]) that all query modes compile to.Search UX
Exploration Tracking (Phase 1)
+/-set algebra operators for additive/subtractive graph operationsSaved Queries (Phase 2)
{ op, cypher }[]statementsUnified Query Program
+/-script execution (was single statement only)Canvas Context Menu
Bug Fixes
start_id/end_idcheck)definitionfieldTest plan
+ MATCH ...;script, execute, graph renders+/-operators, verify set algebra