All notable changes to this project will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
0.2.1 - 2026-07-30
- highlight.js SRI pins for script and theme (#145)
- Content-Security-Policy on all served HTML pages: per-response nonce for
inline bootstrap;
frame-ancestors 'none'blocks iframe embedding,img-src 'self' data:blocks remote images (relax inbuild_content_security_policy) (#147) - Playwright XSS tests for
renderMarkdownSafe(#151)
- Structured
{error, code}bodies on workspaces, composers, search, export, PDF, and config routes (#146) - CI regression gate excludes
composers-50export benchmark (#148)
- highlight.js CDN miss: workspace view skips
hljs.highlightElementwhen the script did not load (#145) - Multi-worker set-workspace:
/api/set-workspacereturns HTTP 409; setWORKSPACE_PATHor pass--base-dirat startup (#150) - Summary-cache fingerprint lock: read-compare-write serialised under a module lock (#158)
0.2.0 - 2026-07-17
- Strict mypy —
strict = trueinpyproject.toml; core TypedDict models (SearchResult,ConversationSummary) and full annotations on API routes andutils/(#100, #103) - Shared export engine — web
POST /api/exportand CLIcursor-chat-exportshareservices/export_engine.py; CLI wired to summary cache (#112) - FTS search index — faster workspace listing and search on large global DBs (#113)
- pytest-benchmark suite with CI regression gate and stored baselines (#120, #121)
- Bubble typed accessors —
Bubble/Composerboundary tightened with typed field access instead of raw dict reads (#133) RuleTokenstyping — exclusion-rules consumers threaded end-to-end; mypy strict verifies the filter boundary (#134)raw_accesshelpers — shared_optional_*parsers for Bubble/Composer optional properties (#137)- Google-style docstrings on public
api/,services/,utils/, andmodels/surfaces (#119) - Flask API test coverage for search, workspaces, and export routes (#101, #104)
- CI typecheck job runs
mypy .using pyproject config (strict production code; per-module overrides forscripts/export.pyandtests.*) (#100) - Search handler decomposed from a monolithic module into per-source functions (#99, #102)
models/__init__.pyexports trimmed —BubbleRoleandBubbleMetadataimport frommodels.bubble_display(#135)workspace_tabs/cursor_md_exporter/display_bubblehygiene — phase helpers, shared loops, narrowed exception handling, typed metadata returns (#138)- Invalid-workspace alias cache keyed by storage fingerprint (#116, #125)
- Desktop optional dep
pywebviewrange widened to>=5.0,<7(#58)
- Export workspace path override when
CURSOR_WORKSPACE_PATHis set (#114) - Search UI tooltip and distinct
/api/searcherror codes (#117, #126) - Per-composer tab assembly skips drifted metadata instead of 500ing the listing (#138)
- Tab metadata aggregation edge cases (
ctx_token_limit, CLI meta guard,tool_result_statsfor search/web-only sessions) (#138)
0.1.0 - 2026-06-04
- Summary disk cache (Phase 3) — project list and tab summaries cached under
~/.cache/cursor-chat-browser/, invalidated when global or per-workspace DB mtimes change; bypass with?nocache=1orCURSOR_CHAT_BROWSER_NOCACHE=1(#84) - Lazy-load workspace UI — workspace sidebar renders from a lightweight summary payload; full bubble content is fetched per-conversation when the user selects it, reducing first-paint time from 1–2 min to < 3 s on large local fixtures (#84)
GET /api/workspaces/<id>/tabs?summary=1— new summary-only variant returnsid,title,timestamp,messageCount, and optionalmetadata.modelsUsedwithout loading any bubble data (#84)GET /api/workspaces/<id>/tabs/<composer_id>— new single-conversation endpoint loads only scopedbubbleId:{id}:%,messageRequestContext:{id}:%, andcodeBlockDiff:{id}:%KV rows, avoiding a full global bubble scan (#84)- Scoped KV loaders in
services/workspace_db.py:load_bubbles_for_composer,load_message_request_context_for_composer,load_code_block_diffs_for_composer— used by the single-tab path (#84) - Web UI — browse and search all Cursor AI workspaces; conversation view with syntax-highlighted code blocks, dark/light mode, and bookmarkable chat URLs (#63)
- Export formats — one-click export of chats as Markdown, HTML, PDF, JSON, and CSV from the web UI (#63)
- CLI export (
cursor-chat-export/scripts/export.py) — zip archive or individual Markdown files with YAML frontmatter; incremental mode (--since last) preserves state across runs (#63, #42, #61) - Cursor CLI agent session support — browse and export sessions stored in
~/.cursor/chats/by thecursor agentCLI; gracefully degrades when the IDE database is absent (#7, #8, #63) - Desktop app packaging — Windows
.exevia PyInstaller + pywebview; no Python installation required on the target machine (#63) - Type-safe models with schema validation at SQLite read boundaries (#24, #30)
- CI matrix (Linux / macOS / Windows) running pytest, mypy, and gitleaks (#13, #19, #44, #62)
- Python packaging infrastructure (
pyproject.tomlwith hatchling, bounded dependency pins,requirements-lock.txt, Dependabot) (#45, #47, #49, #53) - Optional exclusion rules for sensitive projects and chats (#1, #2)
- Full-text search with workspace and log-type filters (#63)
- Hypothesis property-based tests for blob and bubble parsing (#71, #81)
- PDF export endpoint coverage in CI (#72)
- Unit tests for
determine_project_for_conversationfallback chain (#87, #89)
- List-path performance — skip full
messageRequestContextscan unless invalid workspace aliases are needed; filtercomposerDatain SQL; skipComposer.from_dicton list/summary paths; cachecomposer_id_to_wsmapping (#84) GET /api/workspaces(list_workspace_projects) no longer performs a globalbubbleId:%scan; conversation presence is determined fromfullConversationHeadersOnlyheaders alone, and workspace assignment relies oncomposer_id_to_ws(primary) plusprojectLayoutsfrom MRC (#84)assemble_workspace_tabsinner per-composer loop refactored into a shared_assemble_tab_from_composer_datahelper reused byassemble_single_tab; full path behaviour is unchanged (#84)- Extract shared
from_dictvalidation helpers for model classes, reducing duplication (#70, #80) - Enable mypy
strict-optionaland fix nullability gaps across the codebase (#69, #79)
- Direct use of
GET /api/workspaces/<id>/tabs(no?summary=1) from the workspace UI on page load; the UI now calls?summary=1for first paint and lazy-fetches individual tabs. The full-assembly endpoint remains available for export, search, and backward-compatible consumers (planned removal: post-1.0) (#84)
- Path traversal and symlink-escape protection on
/api/set-workspace(#15, #22) - Disabled Werkzeug debug mode by default; opt-in via
--debug/FLASK_DEBUG=1(#9, #20) - Sanitise Marked.js HTML output with DOMPurify (#11, #21)
- Wrapped all production
sqlite3.connect()calls in context managers (#17, #23) - Skip NULL bubble rows in workspace tabs loader (#50, #52)
- Thread-unsafe
_workspace_path_overriderace condition (#43, #54) - Normalise Windows-style paths on non-Windows hosts (#8)
- Add incomplete-result signaling on parse failure so callers can distinguish partial vs. complete data (#67, #78)
- Replace
print()error output with structured logging throughout (#68, #77) - Replace silent
except Exception: passwith structured logging in workspace and bubble load paths (#66, #76) - Decouple API handlers from private
_-prefixed service internals (#73)