feat(search): sidebar panel + Ask mode for workspace Q&A

The workspace-search:query and doc-qa:ask IPC channels were reachable
from the renderer but had no UI. This commit wires them into a sidebar
panel that consumes both backends through a single input.

- src/sidebar/search-panel.js — one input, two modes:
    Search (default): routes to workspace-search:query, returns file hits
    Ask: routes to doc-qa:ask, returns chunk-level passages with offsets
  Click a result → open the file (offset passed through for Q&A hits).
  All dynamic content is escaped before innerHTML — hostile filenames
  or snippets stay as text instead of becoming script/img nodes.
- src/renderer.js — registers the panel; reads the explorer's folder
  input on each open so the search dir stays in sync.
- src/index.html — search sidebar icon (magnifier) next to the others.

Tests (18 new, tests/search-panel.test.js):
  - mount + DOM structure
  - Enter / click run → search() with query + dir
  - result rendering (filePath, snippet, tag facet)
  - onOpenFile receives (filePath, offset)
  - Ask tab switches placeholder + routes to ask()
  - Ask chunks carry +offset in the meta line
  - empty query, no folder, search error → handled
  - XSS: filename/snippet/tag with <script>, <img>, <unsafe> are escaped
  - Escape clears, Clear button resets, host API (setDir/focus/clear)

Full suite: 67 suites, 766 tests, lint+format clean.

Amit Haridas
This commit is contained in:
2026-09-14 08:53:27 +05:30
parent 3f856bc857
commit da97369b44
4 changed files with 551 additions and 0 deletions
+16
View File
@@ -2558,6 +2558,22 @@
<polyline points="12 6 12 12 16 14" />
</svg>
</button>
<!-- Search + Q&A: workspace content search / doc-aware Q&A -->
<button class="sidebar-icon" data-panel="search" title="Search &amp; Ask (Ctrl+Shift+F)">
<svg
width="20"
height="20"
viewBox="0 0 24 24"
fill="none"
stroke="currentColor"
stroke-width="2"
stroke-linecap="round"
stroke-linejoin="round"
>
<circle cx="11" cy="11" r="8" />
<line x1="21" y1="21" x2="16.65" y2="16.65" />
</svg>
</button>
</div>
<div class="sidebar-panel" id="sidebar-panel">
<div class="sidebar-panel-header">