You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
cppa-cursor-browser has a well-structured models/ layer with 7 frozen @dataclass declarations (Composer, Bubble, Workspace, ExportEntry, CliSessionMeta, WorkspaceLocalComposer, ParseWarning), each with validated from_dict classmethods backed by models/from_dict_validation.py. Type annotations exist on function signatures, [tool.mypy] is configured in pyproject.toml, and CI runs mypy alongside pytest. Despite this typed boundary at the read-from-SQLite edge, the service layer passes data onward as untyped dict[str, Any] through pervasive .get() chains: services/workspace_tabs.py has 79 .get() calls, services/workspace_resolver.py has 31, services/workspace_listing.py has 20, and services/cli_tabs.py has 15 — totalling 149 .get() calls across the service layer alone. API routes add another 39 (api/search.py) and scripts/export.py adds 25. The typed @dataclass models validate data at the read boundary but the validated fields are not carried through the service pipeline — instead, code immediately drops back into raw dict access (e.g., cd.get("fullConversationHeadersOnly"), bubble.get("relevantFiles"), ctx.get("projectLayouts")). When Cursor's weekly schema changes rename a key, the from_dict validator catches it at the model boundary, but the 149+ downstream .get() calls silently return None. The gap is not "no types exist" but "types stop at models/ and don't flow through services/."
Acceptance Criteria
Typed accessor methods for top 10 most-accessed raw dict keys in the service layer
Accessors log a WARNING when expected key is missing (drift detection)
Type annotations that mypy can verify end-to-end from model to service output
Existing callers in services/workspace_tabs.py and services/workspace_resolver.py migrated to use typed accessors
Tests covering drift detection logging
Implementation Notes
Audit the 149 .get() calls in the service layer to identify the top 10 most-accessed keys
Pattern: @property methods on the existing model @dataclass classes returning Optional[T] with _logger.warning() on missing keys
Problem
cppa-cursor-browser has a well-structured
models/layer with 7 frozen@dataclassdeclarations (Composer,Bubble,Workspace,ExportEntry,CliSessionMeta,WorkspaceLocalComposer,ParseWarning), each with validatedfrom_dictclassmethods backed bymodels/from_dict_validation.py. Type annotations exist on function signatures,[tool.mypy]is configured inpyproject.toml, and CI runs mypy alongside pytest. Despite this typed boundary at the read-from-SQLite edge, the service layer passes data onward as untypeddict[str, Any]through pervasive.get()chains:services/workspace_tabs.pyhas 79.get()calls,services/workspace_resolver.pyhas 31,services/workspace_listing.pyhas 20, andservices/cli_tabs.pyhas 15 — totalling 149.get()calls across the service layer alone. API routes add another 39 (api/search.py) andscripts/export.pyadds 25. The typed@dataclassmodels validate data at the read boundary but the validated fields are not carried through the service pipeline — instead, code immediately drops back into raw dict access (e.g.,cd.get("fullConversationHeadersOnly"),bubble.get("relevantFiles"),ctx.get("projectLayouts")). When Cursor's weekly schema changes rename a key, thefrom_dictvalidator catches it at the model boundary, but the 149+ downstream.get()calls silently returnNone. The gap is not "no types exist" but "types stop at models/ and don't flow through services/."Acceptance Criteria
services/workspace_tabs.pyandservices/workspace_resolver.pymigrated to use typed accessorsImplementation Notes
.get()calls in the service layer to identify the top 10 most-accessed keys@propertymethods on the existing model@dataclassclasses returningOptional[T]with_logger.warning()on missing keysfrom_dicthelpers) andmodels/from_dict_validation.pyprovide the validation foundationservices/workspace_tabs.py(79.get()calls, 605 LOC) is the highest-value targetReferences
models/conversation.py—Composer,Bubble,WorkspaceLocalComposerdataclasses withfrom_dictmodels/from_dict_validation.py— shared validation helpersservices/workspace_tabs.py— 79.get()calls (primary migration target)services/workspace_resolver.py— 31.get()calls