Skip to content

Commit 188d9ec

Browse files
committed
fix: review findings
1 parent 95f5011 commit 188d9ec

7 files changed

Lines changed: 52 additions & 70 deletions

File tree

CHANGELOG.md

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
1111
- **Strict mypy**`strict = true` in `pyproject.toml`; core TypedDict models
1212
(`SearchResult`, `ConversationSummary`) and full annotations on API routes and
1313
`utils/` (#100)
14-
15-
### Changed
16-
- CI typecheck job runs `mypy .` using pyproject config (strict production code;
17-
per-module overrides for `scripts/export.py` and `tests.*`)
18-
19-
### Added
2014
- **Summary disk cache (Phase 3)** — project list and tab summaries cached under
2115
`~/.cache/cursor-chat-browser/`, invalidated when global or per-workspace DB
2216
mtimes change; bypass with `?nocache=1` or `CURSOR_CHAT_BROWSER_NOCACHE=1` (#84)
@@ -47,6 +41,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
4741
- Unit tests for `determine_project_for_conversation` fallback chain (#87, #89)
4842

4943
### Changed
44+
- CI typecheck job runs `mypy .` using pyproject config (strict production code;
45+
per-module overrides for `scripts/export.py` and `tests.*`)
5046
- **List-path performance** — skip full `messageRequestContext` scan unless
5147
invalid workspace aliases are needed; filter `composerData` in SQL; skip
5248
`Composer.from_dict` on list/summary paths; cache `composer_id_to_ws` mapping (#84)

api/search.py

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,12 @@ def search() -> tuple[Response, int] | Response:
3636
query_lower = query.lower()
3737

3838
results: list[SearchResult] = []
39-
results.extend(
40-
search_global_storage(workspace_path, query, query_lower, rules, parse_warnings)
41-
)
39+
if search_type != "chat":
40+
results.extend(
41+
search_global_storage(
42+
workspace_path, query, query_lower, rules, parse_warnings
43+
)
44+
)
4245
results.extend(
4346
search_legacy_workspaces(workspace_path, query, query_lower, search_type, rules)
4447
)

models/search.py

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,7 @@
22

33
from __future__ import annotations
44

5-
from typing import Any, TypedDict
5+
from typing import Any, Literal, TypedDict
66

77

88
class ConversationSummary(TypedDict, total=False):
@@ -21,11 +21,11 @@ class _SearchResultRequired(TypedDict):
2121
chatTitle: str
2222
timestamp: int | str
2323
matchingText: str
24-
type: str # "composer" | "chat" | "cli_agent"
24+
type: Literal["composer", "chat", "cli_agent"]
2525

2626

2727
class _SearchResultOptional(TypedDict, total=False):
28-
source: str # "cli" for CLI agent sessions
28+
source: Literal["cli"]
2929

3030

3131
class SearchResult(_SearchResultRequired, _SearchResultOptional):

services/search.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -397,6 +397,7 @@ def search_legacy_workspaces(
397397
data = json.loads(chat_row[0])
398398
for tab in (data.get("tabs") or []):
399399
ct = tab.get("chatTitle") or ""
400+
tab_id = str(tab.get("tabId") or "")
400401

401402
tab_model_names: list[str] | None = None
402403
tab_meta = tab.get("metadata")
@@ -434,8 +435,8 @@ def search_legacy_workspaces(
434435
results.append({
435436
"workspaceId": name,
436437
"workspaceFolder": workspace_folder,
437-
"chatId": tab.get("tabId"),
438-
"chatTitle": ct or f"Chat {(tab.get('tabId') or '')[:8]}",
438+
"chatId": tab_id,
439+
"chatTitle": ct or f"Chat {tab_id[:8]}",
439440
"timestamp": tab.get("lastSendTime") or datetime.now().isoformat(),
440441
"matchingText": matching_text,
441442
"type": "chat",
@@ -560,11 +561,17 @@ def rank_results(results: list[SearchResult]) -> list[SearchResult]:
560561
"""
561562
def _ts(r: SearchResult) -> float:
562563
t = r.get("timestamp", 0)
564+
if t is None:
565+
return 0.0
563566
if isinstance(t, str):
564567
try:
565568
return datetime.fromisoformat(t.replace("Z", "+00:00")).timestamp()
566569
except Exception:
567570
return 0.0
571+
if isinstance(t, bool) or not isinstance(t, (int, float)):
572+
return 0.0
573+
if t > 1e12:
574+
return float(t) / 1000.0
568575
return float(t) if t else 0.0
569576

570577
return sorted(results, key=_ts, reverse=True)

services/workspace_db.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
from collections.abc import Iterator
88
from contextlib import closing, contextmanager
99
from pathlib import Path
10-
from typing import Any
10+
from typing import Any, cast
1111

1212
_logger = logging.getLogger(__name__)
1313

@@ -22,6 +22,18 @@
2222
# corrupt table cannot propagate to callers.
2323

2424

25+
def safe_fetchall(
26+
conn: sqlite3.Connection,
27+
query: str,
28+
params: tuple[Any, ...] = (),
29+
) -> list[sqlite3.Row]:
30+
"""Run *query* on *conn*; return rows or ``[]`` on sqlite3.Error."""
31+
try:
32+
return cast(list[sqlite3.Row], conn.execute(query, params).fetchall())
33+
except sqlite3.Error:
34+
return []
35+
36+
2537
def load_bubble_map(global_db: sqlite3.Connection) -> dict[str, dict[str, Any]]:
2638
"""Load all ``bubbleId:*`` KV entries into ``{bubble_id: bubble_dict}``.
2739

services/workspace_listing.py

Lines changed: 3 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@
55
import os
66
import sqlite3
77
from datetime import datetime, timezone
8-
from typing import Any, cast
8+
from typing import Any
99

1010
_logger = logging.getLogger(__name__)
1111

@@ -33,6 +33,7 @@
3333
load_project_layouts_for_composer,
3434
load_project_layouts_map,
3535
open_global_db,
36+
safe_fetchall,
3637
)
3738
from utils.workspace_path import get_cli_chats_path
3839
from services.workspace_resolver import (
@@ -138,19 +139,8 @@ def _build_workspace_projects_uncached(
138139

139140
with open_global_db(workspace_path) as (global_db, _):
140141
if global_db:
141-
def _safe_fetchall(
142-
query: str, params: tuple[Any, ...] = (),
143-
) -> list[sqlite3.Row]:
144-
try:
145-
return cast(
146-
list[sqlite3.Row],
147-
global_db.execute(query, params).fetchall(),
148-
)
149-
except sqlite3.Error:
150-
return []
151-
152142
try:
153-
composer_rows = _safe_fetchall(COMPOSER_ROWS_WITH_HEADERS_SQL)
143+
composer_rows = safe_fetchall(global_db, COMPOSER_ROWS_WITH_HEADERS_SQL)
154144

155145
project_layouts_map: dict[str, list[str]] = {}
156146
if invalid_workspace_ids:

services/workspace_tabs.py

Lines changed: 16 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@
77
import sqlite3
88
from collections.abc import Mapping
99
from datetime import datetime
10-
from typing import Any, cast
10+
from typing import Any
1111

1212
_logger = logging.getLogger(__name__)
1313

@@ -44,6 +44,7 @@
4444
load_project_layouts_for_composer,
4545
load_project_layouts_map,
4646
open_global_db,
47+
safe_fetchall,
4748
)
4849
from utils.workspace_path import get_cli_chats_path
4950
from services.workspace_resolver import (
@@ -548,22 +549,11 @@ def _build_workspace_tab_summaries_uncached(
548549

549550
workspace_display_name = lookup_workspace_display_name(workspace_path, workspace_id)
550551

551-
def _safe_fetchall(
552-
query: str, params: tuple[Any, ...] = (),
553-
) -> list[sqlite3.Row]:
554-
try:
555-
return cast(
556-
list[sqlite3.Row],
557-
global_db.execute(query, params).fetchall(),
558-
)
559-
except sqlite3.Error:
560-
return []
561-
562552
project_layouts_map: dict[str, list[str]] = {}
563553
if invalid_workspace_ids:
564554
project_layouts_map = load_project_layouts_map(global_db)
565555

566-
composer_rows = _safe_fetchall(COMPOSER_ROWS_WITH_HEADERS_SQL)
556+
composer_rows = safe_fetchall(global_db, COMPOSER_ROWS_WITH_HEADERS_SQL)
567557

568558
invalid_workspace_aliases: dict[str, str] = {}
569559
if invalid_workspace_ids:
@@ -709,18 +699,8 @@ def assemble_single_tab(
709699

710700
workspace_display_name = lookup_workspace_display_name(workspace_path, workspace_id)
711701

712-
def _safe_fetchall(
713-
query: str, params: tuple[Any, ...] = (),
714-
) -> list[sqlite3.Row]:
715-
try:
716-
return cast(
717-
list[sqlite3.Row],
718-
global_db.execute(query, params).fetchall(),
719-
)
720-
except sqlite3.Error:
721-
return []
722-
723-
rows = _safe_fetchall(
702+
rows = safe_fetchall(
703+
global_db,
724704
"SELECT key, value FROM cursorDiskKV WHERE key = ?",
725705
(f"composerData:{composer_id}",),
726706
)
@@ -760,7 +740,7 @@ def _safe_fetchall(
760740
if invalid_workspace_ids:
761741
# Alias resolution still needs the composer roster, but project layouts
762742
# are intentionally limited to this composer (single-tab scope).
763-
composer_rows_for_aliases = _safe_fetchall(COMPOSER_ROWS_WITH_HEADERS_SQL)
743+
composer_rows_for_aliases = safe_fetchall(global_db, COMPOSER_ROWS_WITH_HEADERS_SQL)
764744
invalid_workspace_aliases = infer_invalid_workspace_aliases(
765745
composer_rows=composer_rows_for_aliases,
766746
project_layouts_map=project_layouts_map,
@@ -848,21 +828,11 @@ def assemble_workspace_tabs(
848828

849829
workspace_display_name = lookup_workspace_display_name(workspace_path, workspace_id)
850830

851-
def _safe_fetchall(
852-
query: str, params: tuple[Any, ...] = (),
853-
) -> list[sqlite3.Row]:
854-
try:
855-
return cast(
856-
list[sqlite3.Row],
857-
global_db.execute(query, params).fetchall(),
858-
)
859-
except sqlite3.Error:
860-
return []
861-
862831
# Load bubbles
863-
for row in _safe_fetchall(
832+
for row in safe_fetchall(
833+
global_db,
864834
"SELECT key, value FROM cursorDiskKV WHERE key LIKE 'bubbleId:%'"
865-
" AND value IS NOT NULL"
835+
" AND value IS NOT NULL",
866836
):
867837
parts = row["key"].split(":")
868838
if len(parts) >= 3:
@@ -901,7 +871,10 @@ def _safe_fetchall(
901871
# Load messageRequestContext rows once; build both
902872
# message_request_context_map and project_layouts_map from the same pass.
903873
project_layouts_map: dict[str, list[str]] = {}
904-
for row in _safe_fetchall("SELECT key, value FROM cursorDiskKV WHERE key LIKE 'messageRequestContext:%'"):
874+
for row in safe_fetchall(
875+
global_db,
876+
"SELECT key, value FROM cursorDiskKV WHERE key LIKE 'messageRequestContext:%'",
877+
):
905878
parts = row["key"].split(":")
906879
if len(parts) < 2:
907880
continue
@@ -934,11 +907,12 @@ def _safe_fetchall(
934907
project_layouts_map[chat_id].append(layout["rootPath"])
935908

936909
# Get composer data entries with conversations
937-
composer_rows = _safe_fetchall(
910+
composer_rows = safe_fetchall(
911+
global_db,
938912
"SELECT key, value FROM cursorDiskKV WHERE key LIKE 'composerData:%'"
939913
" AND value IS NOT NULL"
940914
" AND value LIKE '%fullConversationHeadersOnly%'"
941-
" AND value NOT LIKE '%fullConversationHeadersOnly\":[]%'"
915+
" AND value NOT LIKE '%fullConversationHeadersOnly\":[]%'",
942916
)
943917

944918
invalid_workspace_aliases = infer_invalid_workspace_aliases(

0 commit comments

Comments
 (0)