feat: add web UI, query engine, session management, and 20 E2E tests

- Web UI: login, dashboard, links CRUD, collections, API keys, admin pages
- Query engine: AND/OR/XOR with field filters, tag search, preview endpoint
- Session management: token expiry detection, 401 interceptor, expiry banner
- Links search: tags included, multi-word AND, query mode with set operations
- Collections: static/dynamic, query builder with preview, public tree view
- Save as Collection: convert search results (static) or query (dynamic)
- Dashboard stats: resilient loading with allSettled pattern
- Login page: redesigned with public collections tree view
- Bug fix: query executor None fields crash (notes/description/url/title)
- E2E tests: 20 Playwright tests covering all critical user flows
- All 104 tests passing (84 unit/integration + 20 E2E)
This commit is contained in:
DavidSaylor
2026-05-22 07:46:53 -05:00
parent 77b076c7d7
commit fe4cbc3537
29 changed files with 1410 additions and 78 deletions

View File

@@ -51,10 +51,11 @@ def _evaluate_node(node: Dict[str, Any], bookmarks: List[Dict[str, Any]]) -> set
return {
b["id"]
for b in bookmarks
if value in b.get("title", "").lower()
or value in b.get("description", "").lower()
or value in b.get("url", "").lower()
or value in b.get("notes", "").lower()
if value in (b.get("title") or "").lower()
or value in (b.get("description") or "").lower()
or value in (b.get("url") or "").lower()
or value in (b.get("notes") or "").lower()
or any(value in t.lower() for t in (b.get("tags") or []))
}
if operation == "TERM_SET":
@@ -63,9 +64,10 @@ def _evaluate_node(node: Dict[str, Any], bookmarks: List[Dict[str, Any]]) -> set
result = set()
for b in bookmarks:
text = (
f"{b.get('title', '')} {b.get('description', '')} {b.get('url', '')} {b.get('notes', '')}"
f"{(b.get('title') or '')} {(b.get('description') or '')} {(b.get('url') or '')} {(b.get('notes') or '')}"
).lower()
if any(term in text for term in terms_lower):
tags_lower = [t.lower() for t in (b.get("tags") or [])]
if any(term in text for term in terms_lower) or any(term in tags_lower for term in terms_lower):
result.add(b["id"])
return result