open-notebook/frontend
Luis Novo 7d674bd53a
feat(search): scope Search and Ask to selected notebooks (#1331)
* feat(search): scope Search and Ask to selected notebooks

Search and Ask always ran against the whole knowledge base; `notebook_id`
was accepted by POST /api/search and silently ignored because the request
model never declared it. Both endpoints now take an optional notebook
scope and the Search/Ask page gets a shared notebook selector.

- Migration 24: fn::text_search / fn::vector_search gain an optional
  trailing `$notebook_ids: option<array<record<notebook>>>` parameter.
  NONE/empty keeps the unfiltered path; otherwise sources are limited via
  the `reference` edge and notes via the `artifact` edge (chunks and
  insights follow their parent source). The 4-/5-arg call shapes still
  work, so existing callers are unaffected; `24_down` restores the
  previous definitions.
- API: SearchRequest / AskRequest accept `notebook_ids` (list) and
  `notebook_id` (single, the shape clients already send); ids are
  validated (400 for non-notebook ids, 404 for unknown notebooks) before
  reaching the query so a typo never masquerades as "no matches".
- Domain: text_search / vector_search take `notebook_ids`; the highlight
  overflow fallback preserves the scope.
- Ask graph: scope travels in the thread state and reaches every fan-out
  vector search.
- Frontend: NotebookScopeSelector (collapsible checkbox list, "All
  notebooks" by default, Clear action) shared by the Ask and Search tabs;
  strings in all 14 locales.
- Docs: user guide section on scoping; API reference note.

Closes #574
Closes #87

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U53d3BNjGghLsf6tufRTKX

* fix(search): harden notebook scope validation after review

- Raise typed InvalidInputError / NotFoundError instead of bare
  HTTPException, per the API error convention.
- Check notebook existence with a single query for the whole scope; a
  database failure now surfaces as 500 instead of a misleading 404.
- Bound notebook_ids to 50 entries; keep empty ids so they are rejected
  (400) rather than silently widening the scope to everything.
- Replace the pluralized "N notebooks selected" string with a neutral
  "N selected" form: the locale parity test forbids per-language extra
  keys, so _few/_many forms for ru/pl were not an option.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U53d3BNjGghLsf6tufRTKX

* refactor(search): move scope resolution to the domain, add ADR-008

- resolve_notebook_scope() now lives in open_notebook/domain/notebook.py
  next to text_search / vector_search; the router only calls it.
- Reject malformed ids such as "notebook:" (empty key) and anything
  RecordID.parse cannot handle with InvalidInputError instead of letting
  them surface as 500.
- ADR-008 records the notebook-scope semantics, the compatibility choice
  (notebook_id + notebook_ids, optional trailing SurrealQL parameter) and
  how it relates to the #1315 retrieval contract.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U53d3BNjGghLsf6tufRTKX

* test(search): cover migration 24, validate Ask scope before model lookups

- Ask endpoints resolve the notebook scope first, so a malformed or
  unknown scope returns 400/404 before any model or embedding lookup.
- Registration and content tests for 24.surrealql / 24_down.surrealql,
  following the existing migration test pattern.
- ADR-008 context trimmed to the template's sentence budget.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U53d3BNjGghLsf6tufRTKX

* fix(search): surface a typed error when scope resolution hits the database

resolve_notebook_scope() now logs the raw driver error and raises
DatabaseOperationError with a fixed message, matching text_search /
vector_search, so connection details never reach the 500 response body.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01U53d3BNjGghLsf6tufRTKX

* fix(search): compare notebook scope ids as strings

The driver returns `id` as RecordID objects, which are unhashable and
never equal to the request strings, so every real scoped request raised
TypeError before reaching the search. Normalize to str before comparing
and add a regression test that mocks RecordID rows.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014kRBoPiBD1NKeKHCo6EyrE

---------

Co-authored-by: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-05 16:09:04 -03:00
..
public Version 1 (#160) 2025-10-18 12:46:22 -03:00
src feat(search): scope Search and Ask to selected notebooks (#1331) 2026-09-05 16:09:04 -03:00
test-results feat(i18n): add Turkish (tr-TR) translation (#871) 2026-06-13 08:46:15 -03:00
.gitignore chore: export docs for custom gpt 2025-10-18 20:26:11 -03:00
AGENTS.md chore(frontend): typed locales, drop unused deps, fix AGENTS.md drift (#1061) 2026-07-11 18:45:57 -03:00
CLAUDE.md docs: restructure documentation around AGENTS.md, VISION.md and decision records (#1032) 2026-07-10 15:33:19 -03:00
components.json Version 1 (#160) 2025-10-18 12:46:22 -03:00
eslint.config.mjs Version 1 (#160) 2025-10-18 12:46:22 -03:00
next.config.ts refactor: rename "API Keys" settings page to "Models" (#1313) 2026-09-05 13:20:20 -03:00
package-lock.json chore(deps): bump pyasn1 to 0.6.4 and brace-expansion to 1.1.16 (#1222) 2026-09-04 07:20:05 -03:00
package.json chore(deps): bump pyasn1 to 0.6.4 and brace-expansion to 1.1.16 (#1222) 2026-09-04 07:20:05 -03:00
postcss.config.mjs Version 1 (#160) 2025-10-18 12:46:22 -03:00
start-server.js fix: use Node.js wrapper for cross-platform PORT fallback 2026-01-14 22:51:39 -03:00
tsconfig.json Feat/localization tests docker (#371) 2026-01-15 13:51:05 -03:00
vitest.config.ts fix: make validate_url() async so DNS resolution doesn't block the event loop (#1011) 2026-07-10 10:43:36 -03:00