|
1 | | -"""Composer (conversation) and Bubble (message) typed models.""" |
2 | | - |
3 | 1 | from __future__ import annotations |
4 | 2 |
|
5 | 3 | from dataclasses import dataclass, field |
|
10 | 8 |
|
11 | 9 | @dataclass(frozen=True) |
12 | 10 | class Composer: |
13 | | - """A Cursor conversation (a.k.a. "composer") row. |
14 | | -
|
15 | | - Required fields per the schema-validation contract (issue #24): |
16 | | - - ``fullConversationHeadersOnly`` — without this, a composer cannot be |
17 | | - rendered (no message order is recoverable). |
18 | | - - ``createdAt`` — Cursor writes this on every composer (verified |
19 | | - 17/17 against a live workspaceStorage). A missing value is the |
20 | | - kind of drift this layer exists to surface. |
21 | | -
|
22 | | - The composer ID is intentionally passed in as a constructor argument |
23 | | - rather than read from ``raw`` because Cursor stores it in the row key |
24 | | - (``composerData:<id>``) rather than in the JSON value. |
25 | | - """ |
| 11 | + """Cursor conversation row from globalStorage cursorDiskKV; requires fullConversationHeadersOnly + createdAt.""" |
26 | 12 |
|
27 | 13 | composer_id: str |
28 | 14 | full_conversation_headers_only: list[dict[str, Any]] |
@@ -72,17 +58,7 @@ def from_dict(cls, raw: dict[str, Any], *, composer_id: str) -> "Composer": |
72 | 58 |
|
73 | 59 | @dataclass(frozen=True) |
74 | 60 | class WorkspaceLocalComposer: |
75 | | - """A composer entry from ``composer.composerData`` ItemTable rows. |
76 | | -
|
77 | | - These are summary records that live in each per-workspace ``state.vscdb``. |
78 | | - They share ``composerId`` and ``lastUpdatedAt`` with the global composer |
79 | | - schema, but they do **not** carry ``fullConversationHeadersOnly`` or |
80 | | - ``createdAt`` — those only exist on the global ``cursorDiskKV`` rows that |
81 | | - ``Composer.from_dict`` validates. Treating both shapes through the same |
82 | | - model would reject every workspace-local entry, so this slim model |
83 | | - exists to keep schema-drift detection at the boundary without conflating |
84 | | - the two storage paths. |
85 | | - """ |
| 61 | + """Summary composer row from per-workspace state.vscdb ItemTable; only composerId is required.""" |
86 | 62 |
|
87 | 63 | composer_id: str |
88 | 64 | last_updated_at: Any = None |
@@ -112,13 +88,7 @@ def from_dict(cls, raw: dict[str, Any]) -> "WorkspaceLocalComposer": |
112 | 88 |
|
113 | 89 | @dataclass(frozen=True) |
114 | 90 | class Bubble: |
115 | | - """A single message bubble within a composer. |
116 | | -
|
117 | | - The bubble ID lives in the row key (``bubbleId:<composer_id>:<bubble_id>``) |
118 | | - rather than the JSON value, so it is passed in explicitly. The raw dict |
119 | | - is preserved to keep downstream rendering code (which still walks the |
120 | | - untyped shape) working without modification. |
121 | | - """ |
| 91 | + """One message in a composer; bubble_id comes from the row key, not the JSON value.""" |
122 | 92 |
|
123 | 93 | bubble_id: str |
124 | 94 | raw: dict[str, Any] = field(default_factory=dict) |
|
0 commit comments