cct-cli 0.7.9.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (325) hide show
  1. calc_terminal/__init__.py +14 -0
  2. calc_terminal/__main__.py +14 -0
  3. calc_terminal/activity.py +1334 -0
  4. calc_terminal/agent.py +3387 -0
  5. calc_terminal/agent_runtime.py +519 -0
  6. calc_terminal/ai_context.py +447 -0
  7. calc_terminal/ai_modes.py +752 -0
  8. calc_terminal/ai_personalization.py +286 -0
  9. calc_terminal/ai_preview_feedback.py +213 -0
  10. calc_terminal/aicore.py +2572 -0
  11. calc_terminal/anim.py +367 -0
  12. calc_terminal/app.py +3685 -0
  13. calc_terminal/art.py +639 -0
  14. calc_terminal/atomsim.py +368 -0
  15. calc_terminal/attachments.py +743 -0
  16. calc_terminal/benchmark_system.py +414 -0
  17. calc_terminal/browser/__init__.py +36 -0
  18. calc_terminal/browser/browser_state.py +346 -0
  19. calc_terminal/browser/devserver.py +176 -0
  20. calc_terminal/browser/engine.py +494 -0
  21. calc_terminal/browser/navigation.py +84 -0
  22. calc_terminal/browser/preview.py +429 -0
  23. calc_terminal/browser/preview_entry.py +95 -0
  24. calc_terminal/browser/project_detector.py +144 -0
  25. calc_terminal/browser/server.py +449 -0
  26. calc_terminal/browser/state.py +75 -0
  27. calc_terminal/browser/watcher.py +99 -0
  28. calc_terminal/browser_gui/__init__.py +1 -0
  29. calc_terminal/browser_gui/__main__.py +3 -0
  30. calc_terminal/browser_gui/launcher.py +173 -0
  31. calc_terminal/browser_gui/playwright_browser.py +117 -0
  32. calc_terminal/browser_gui/qt_browser.py +1501 -0
  33. calc_terminal/browser_gui/webview_browser.py +57 -0
  34. calc_terminal/capabilities/__init__.py +35 -0
  35. calc_terminal/capabilities/adapters/__init__.py +33 -0
  36. calc_terminal/capabilities/adapters/bioinformatics.py +204 -0
  37. calc_terminal/capabilities/adapters/browser_adapter.py +205 -0
  38. calc_terminal/capabilities/adapters/filesystem.py +206 -0
  39. calc_terminal/capabilities/adapters/git_adapter.py +202 -0
  40. calc_terminal/capabilities/adapters/jupyter_adapter.py +138 -0
  41. calc_terminal/capabilities/adapters/ml_frameworks.py +158 -0
  42. calc_terminal/capabilities/adapters/platforms.py +200 -0
  43. calc_terminal/capabilities/adapters/python_exec.py +93 -0
  44. calc_terminal/capabilities/adapters/quantum_adapter.py +150 -0
  45. calc_terminal/capabilities/adapters/scientific_comp.py +123 -0
  46. calc_terminal/capabilities/adapters/structural_bio.py +161 -0
  47. calc_terminal/capabilities/adapters/terminal.py +99 -0
  48. calc_terminal/capabilities/bus.py +178 -0
  49. calc_terminal/capabilities/discovery.py +207 -0
  50. calc_terminal/capabilities/schema.py +221 -0
  51. calc_terminal/cat.ico +0 -0
  52. calc_terminal/cat_browser.py +2018 -0
  53. calc_terminal/chat_store.py +703 -0
  54. calc_terminal/cli.py +1178 -0
  55. calc_terminal/code_editor.py +640 -0
  56. calc_terminal/collaboration.py +723 -0
  57. calc_terminal/commands_data.py +139 -0
  58. calc_terminal/compatibility_engine.py +352 -0
  59. calc_terminal/compute/__init__.py +31 -0
  60. calc_terminal/compute/fabric.py +350 -0
  61. calc_terminal/config.py +227 -0
  62. calc_terminal/core/__init__.py +41 -0
  63. calc_terminal/core/checkpoint.py +156 -0
  64. calc_terminal/core/input/__init__.py +45 -0
  65. calc_terminal/core/mode_registry.py +300 -0
  66. calc_terminal/core/project_graph.py +172 -0
  67. calc_terminal/core/recovery.py +129 -0
  68. calc_terminal/core/security_layer.py +112 -0
  69. calc_terminal/core/task_graph.py +202 -0
  70. calc_terminal/core/unified_runtime.py +184 -0
  71. calc_terminal/core/verification.py +257 -0
  72. calc_terminal/customization.py +1566 -0
  73. calc_terminal/derivations.py +153 -0
  74. calc_terminal/device_control.py +263 -0
  75. calc_terminal/diagnostics/__init__.py +27 -0
  76. calc_terminal/diagnostics/doctor_engine.py +382 -0
  77. calc_terminal/diagnostics/self_test.py +247 -0
  78. calc_terminal/doctor.py +519 -0
  79. calc_terminal/easter_eggs.py +274 -0
  80. calc_terminal/editor/__init__.py +1 -0
  81. calc_terminal/editor/actions.py +263 -0
  82. calc_terminal/editor/commands.py +160 -0
  83. calc_terminal/editor/shortcuts.py +226 -0
  84. calc_terminal/engine.py +259 -0
  85. calc_terminal/errors.py +120 -0
  86. calc_terminal/event_stream.py +146 -0
  87. calc_terminal/eventbus.py +133 -0
  88. calc_terminal/extensions.py +733 -0
  89. calc_terminal/fallback_cli.py +1321 -0
  90. calc_terminal/first_run.py +265 -0
  91. calc_terminal/fomoji_auth.py +1043 -0
  92. calc_terminal/formulas.py +82 -0
  93. calc_terminal/fs_cache.py +121 -0
  94. calc_terminal/fs_watcher.py +277 -0
  95. calc_terminal/game.py +193 -0
  96. calc_terminal/gen1.py +5 -0
  97. calc_terminal/generators.py +245 -0
  98. calc_terminal/gestures/__init__.py +42 -0
  99. calc_terminal/gestures/bindings.py +175 -0
  100. calc_terminal/gestures/manager.py +477 -0
  101. calc_terminal/goodbye.py +363 -0
  102. calc_terminal/gpu3d.py +290 -0
  103. calc_terminal/graphs.py +358 -0
  104. calc_terminal/hardware_analyzer.py +440 -0
  105. calc_terminal/host/__init__.py +30 -0
  106. calc_terminal/host/browser_manager.py +187 -0
  107. calc_terminal/host/desktop.py +1386 -0
  108. calc_terminal/host/launcher.py +395 -0
  109. calc_terminal/host/terminal.py +279 -0
  110. calc_terminal/identity.py +216 -0
  111. calc_terminal/input/__init__.py +54 -0
  112. calc_terminal/input/capabilities.py +258 -0
  113. calc_terminal/input/focus.py +87 -0
  114. calc_terminal/input/gestures.py +64 -0
  115. calc_terminal/input/pointer.py +114 -0
  116. calc_terminal/input/touch.py +345 -0
  117. calc_terminal/keys.py +84 -0
  118. calc_terminal/live_automation.py +165 -0
  119. calc_terminal/mathtext.py +433 -0
  120. calc_terminal/mcp.py +386 -0
  121. calc_terminal/memory.py +337 -0
  122. calc_terminal/memory_v2.py +479 -0
  123. calc_terminal/metrics.py +333 -0
  124. calc_terminal/mode_detection.py +146 -0
  125. calc_terminal/model.py +2431 -0
  126. calc_terminal/model_router.py +665 -0
  127. calc_terminal/models/__init__.py +0 -0
  128. calc_terminal/models/active_state.py +187 -0
  129. calc_terminal/models/dynamic_registry.py +584 -0
  130. calc_terminal/models/manager.py +781 -0
  131. calc_terminal/models/model_metadata.json +3526 -0
  132. calc_terminal/models/profiles.py +194 -0
  133. calc_terminal/models/registry.py +265 -0
  134. calc_terminal/models/schema.py +197 -0
  135. calc_terminal/models/validator.py +287 -0
  136. calc_terminal/models/verification_engine.py +368 -0
  137. calc_terminal/native_picker.py +215 -0
  138. calc_terminal/ollama_catalog.py +279 -0
  139. calc_terminal/ollama_download.py +233 -0
  140. calc_terminal/orchestrator.py +304 -0
  141. calc_terminal/package_research.py +322 -0
  142. calc_terminal/packages.py +1024 -0
  143. calc_terminal/pc_specs.py +116 -0
  144. calc_terminal/permissions.py +334 -0
  145. calc_terminal/pet.py +106 -0
  146. calc_terminal/pipeline.py +505 -0
  147. calc_terminal/platform/__init__.py +491 -0
  148. calc_terminal/platform/desktop.py +491 -0
  149. calc_terminal/platform/web.py +781 -0
  150. calc_terminal/preview/__init__.py +1 -0
  151. calc_terminal/preview/dev_server.py +303 -0
  152. calc_terminal/preview/diagnostics.py +131 -0
  153. calc_terminal/preview/live_reload.py +66 -0
  154. calc_terminal/preview/manager.py +129 -0
  155. calc_terminal/project_stats.py +209 -0
  156. calc_terminal/projects.py +328 -0
  157. calc_terminal/providers/__init__.py +0 -0
  158. calc_terminal/providers/adapters/__init__.py +80 -0
  159. calc_terminal/providers/adapters/anthropic_adapter.py +127 -0
  160. calc_terminal/providers/adapters/base.py +106 -0
  161. calc_terminal/providers/adapters/chinese_adapters.py +420 -0
  162. calc_terminal/providers/adapters/gemini_adapter.py +101 -0
  163. calc_terminal/providers/adapters/ollama_adapter.py +83 -0
  164. calc_terminal/providers/adapters/openai_adapter.py +159 -0
  165. calc_terminal/providers/adapters/other_adapters.py +246 -0
  166. calc_terminal/providers/anthropic_provider.py +172 -0
  167. calc_terminal/providers/auto_update.py +416 -0
  168. calc_terminal/providers/base_provider.py +105 -0
  169. calc_terminal/providers/discovery_manager.py +207 -0
  170. calc_terminal/providers/gemini_provider.py +178 -0
  171. calc_terminal/providers/lifecycle.py +767 -0
  172. calc_terminal/providers/ollama_adapter.py +707 -0
  173. calc_terminal/providers/openai_provider.py +254 -0
  174. calc_terminal/providers/provider_manager.py +1827 -0
  175. calc_terminal/providers/providers.json +4075 -0
  176. calc_terminal/reactionsim.py +279 -0
  177. calc_terminal/registry.py +337 -0
  178. calc_terminal/report.py +162 -0
  179. calc_terminal/research/__init__.py +45 -0
  180. calc_terminal/research/artifact_intel.py +126 -0
  181. calc_terminal/research/data_lineage.py +123 -0
  182. calc_terminal/research/experiment_ledger.py +303 -0
  183. calc_terminal/research/reproducibility.py +131 -0
  184. calc_terminal/resilience/__init__.py +47 -0
  185. calc_terminal/resilience/agent_state.py +121 -0
  186. calc_terminal/resilience/capability_matcher.py +174 -0
  187. calc_terminal/resilience/circuit_breaker.py +158 -0
  188. calc_terminal/resilience/failover_engine.py +230 -0
  189. calc_terminal/resilience/health_monitor.py +192 -0
  190. calc_terminal/resilience/ollama_adapter.py +125 -0
  191. calc_terminal/resilience/orchestrator.py +312 -0
  192. calc_terminal/resilience/types.py +134 -0
  193. calc_terminal/sandbox.py +98 -0
  194. calc_terminal/scires.py +558 -0
  195. calc_terminal/security_scanner.py +126 -0
  196. calc_terminal/session.py +294 -0
  197. calc_terminal/sim3d.py +206 -0
  198. calc_terminal/solver.py +276 -0
  199. calc_terminal/sound.py +127 -0
  200. calc_terminal/task_reports.py +287 -0
  201. calc_terminal/terminal_host.py +201 -0
  202. calc_terminal/terminal_identity.py +411 -0
  203. calc_terminal/test_ai_mode_reliability.py +344 -0
  204. calc_terminal/test_browser.py +368 -0
  205. calc_terminal/test_code_editor_upgrade.py +485 -0
  206. calc_terminal/test_customization.py +1148 -0
  207. calc_terminal/test_customization_ui.py +612 -0
  208. calc_terminal/test_dynamic_registry.py +304 -0
  209. calc_terminal/test_extensions.py +436 -0
  210. calc_terminal/test_overhaul.py +557 -0
  211. calc_terminal/test_project_detect.py +255 -0
  212. calc_terminal/test_root_cause_fix.py +527 -0
  213. calc_terminal/test_stability.py +532 -0
  214. calc_terminal/test_terminal_identity.py +132 -0
  215. calc_terminal/test_v079_speed.py +460 -0
  216. calc_terminal/theme.py +1107 -0
  217. calc_terminal/timeline.py +139 -0
  218. calc_terminal/todos.py +246 -0
  219. calc_terminal/tool_call_normalizer.py +419 -0
  220. calc_terminal/tui.py +104 -0
  221. calc_terminal/ui/__init__.py +8 -0
  222. calc_terminal/ui/activity_panel.py +231 -0
  223. calc_terminal/ui/activity_stream_panel.py +238 -0
  224. calc_terminal/ui/animations.py +122 -0
  225. calc_terminal/ui/app.py +7271 -0
  226. calc_terminal/ui/attach_panel.py +597 -0
  227. calc_terminal/ui/attachments.py +424 -0
  228. calc_terminal/ui/backup_panel.py +810 -0
  229. calc_terminal/ui/browser_shell.py +887 -0
  230. calc_terminal/ui/cat_agent.py +357 -0
  231. calc_terminal/ui/chats_panel.py +899 -0
  232. calc_terminal/ui/command_palette.py +125 -0
  233. calc_terminal/ui/command_palette_modal.py +166 -0
  234. calc_terminal/ui/composer.py +1141 -0
  235. calc_terminal/ui/context_menu.py +197 -0
  236. calc_terminal/ui/conversation.py +1435 -0
  237. calc_terminal/ui/customization_panel.py +1229 -0
  238. calc_terminal/ui/dashboard.py +404 -0
  239. calc_terminal/ui/design_system.py +557 -0
  240. calc_terminal/ui/diff_panel.py +213 -0
  241. calc_terminal/ui/editor.py +2102 -0
  242. calc_terminal/ui/empty_state.py +302 -0
  243. calc_terminal/ui/events.py +487 -0
  244. calc_terminal/ui/extensions_panel.py +815 -0
  245. calc_terminal/ui/footer.py +166 -0
  246. calc_terminal/ui/gestures_panel.py +383 -0
  247. calc_terminal/ui/goodbye_screen.py +100 -0
  248. calc_terminal/ui/header.py +1034 -0
  249. calc_terminal/ui/help_panel.py +254 -0
  250. calc_terminal/ui/live_activities.py +914 -0
  251. calc_terminal/ui/mcp_panel.py +570 -0
  252. calc_terminal/ui/memory_center.py +524 -0
  253. calc_terminal/ui/mode_colors_panel.py +525 -0
  254. calc_terminal/ui/nav_screens.py +747 -0
  255. calc_terminal/ui/ollama_panel.py +536 -0
  256. calc_terminal/ui/palette.py +221 -0
  257. calc_terminal/ui/permission_panel.py +269 -0
  258. calc_terminal/ui/personalization_panel.py +517 -0
  259. calc_terminal/ui/personalize_center.py +1568 -0
  260. calc_terminal/ui/preview_panel.py +441 -0
  261. calc_terminal/ui/resizers.py +402 -0
  262. calc_terminal/ui/sidebar.py +1285 -0
  263. calc_terminal/ui/statusbar.py +168 -0
  264. calc_terminal/ui/theme_css.py +1396 -0
  265. calc_terminal/ui/thinking.py +226 -0
  266. calc_terminal/ui/timeline_panel.py +102 -0
  267. calc_terminal/ui/todo_panel.py +193 -0
  268. calc_terminal/ui/viewport.py +136 -0
  269. calc_terminal/ui/vision_panel.py +489 -0
  270. calc_terminal/ui/welcome_modal.py +343 -0
  271. calc_terminal/ui/widgets.py +160 -0
  272. calc_terminal/ui/workspace.py +831 -0
  273. calc_terminal/viewers/__init__.py +1 -0
  274. calc_terminal/viewers/document_viewer.py +252 -0
  275. calc_terminal/viewers/image_viewer.py +241 -0
  276. calc_terminal/viewers/pdf_viewer.py +203 -0
  277. calc_terminal/viewers/presentation_viewer.py +164 -0
  278. calc_terminal/viewers/registry.py +120 -0
  279. calc_terminal/viewers/spreadsheet_viewer.py +204 -0
  280. calc_terminal/vision/__init__.py +89 -0
  281. calc_terminal/vision/analysis.py +194 -0
  282. calc_terminal/vision/annotations.py +297 -0
  283. calc_terminal/vision/capture.py +171 -0
  284. calc_terminal/vision/context.py +231 -0
  285. calc_terminal/vision/correlation.py +169 -0
  286. calc_terminal/vision/cursor.py +258 -0
  287. calc_terminal/vision/events.py +66 -0
  288. calc_terminal/vision/frame_pipeline.py +259 -0
  289. calc_terminal/vision/priority.py +218 -0
  290. calc_terminal/vision/provider.py +180 -0
  291. calc_terminal/vision/safety.py +149 -0
  292. calc_terminal/vision/session.py +281 -0
  293. calc_terminal/vision/verify.py +162 -0
  294. calc_terminal/vision.py +514 -0
  295. calc_terminal/vscode_integration.py +113 -0
  296. calc_terminal/web/__init__.py +8 -0
  297. calc_terminal/web/cat_runtime.py +710 -0
  298. calc_terminal/web/server.py +2891 -0
  299. calc_terminal/web/static/css/app.css +3152 -0
  300. calc_terminal/web/static/icons/badge-72.png +0 -0
  301. calc_terminal/web/static/icons/cat.ico +0 -0
  302. calc_terminal/web/static/icons/icon-128.png +0 -0
  303. calc_terminal/web/static/icons/icon-144.png +0 -0
  304. calc_terminal/web/static/icons/icon-152.png +0 -0
  305. calc_terminal/web/static/icons/icon-192.png +0 -0
  306. calc_terminal/web/static/icons/icon-384.png +0 -0
  307. calc_terminal/web/static/icons/icon-512.png +0 -0
  308. calc_terminal/web/static/icons/icon-72.png +0 -0
  309. calc_terminal/web/static/icons/icon-96.png +0 -0
  310. calc_terminal/web/static/icons/icon.svg +34 -0
  311. calc_terminal/web/static/icons/new-project.png +0 -0
  312. calc_terminal/web/static/icons/open-project.png +0 -0
  313. calc_terminal/web/static/index.html +734 -0
  314. calc_terminal/web/static/js/app.js +2403 -0
  315. calc_terminal/web/static/manifest.json +88 -0
  316. calc_terminal/web/static/sw.js +230 -0
  317. calc_terminal/workflow_engine.py +769 -0
  318. calc_terminal/workspace.py +593 -0
  319. calc_terminal/workspace_index.py +385 -0
  320. cct_cli-0.7.9.0.dist-info/METADATA +210 -0
  321. cct_cli-0.7.9.0.dist-info/RECORD +325 -0
  322. cct_cli-0.7.9.0.dist-info/WHEEL +5 -0
  323. cct_cli-0.7.9.0.dist-info/entry_points.txt +4 -0
  324. cct_cli-0.7.9.0.dist-info/licenses/LICENSE +21 -0
  325. cct_cli-0.7.9.0.dist-info/top_level.txt +1 -0
@@ -0,0 +1,402 @@
1
+ """
2
+ CAT v0.7.9.0 — resizers.py: real draggable split-pane handles.
3
+
4
+ Two independent resizable areas (spec sections 12, 15-16):
5
+
6
+ 1. ExplorerResizeHandle — the vertical divider between the FILE
7
+ EXPLORER and the MAIN CHAT AREA. Drag left => explorer narrower,
8
+ drag right => explorer wider. Clamped to [MIN, max % of shell].
9
+
10
+ 2. ComposerResizeHandle — the subtle horizontal grip between the chat
11
+ history and the CHAT COMPOSER. Drag up => taller composer, drag
12
+ down => shorter. Clamped so a usable message area always remains.
13
+
14
+ Both are plain Textual widgets using capture_mouse() drags — no timers,
15
+ no busy loops. Chosen sizes persist to ~/.cct_ui_layout.json so they
16
+ survive restarts, and are re-clamped when the window itself resizes.
17
+
18
+ The handles exist to keep the layout architecture honest: the composer
19
+ is a flow child of the main chat column (NEVER docked/positioned across
20
+ the whole screen), so it can never overlap the explorer again.
21
+ """
22
+
23
+ if __name__ == "__main__":
24
+ print("This is a library file and is not meant to be run directly.")
25
+ import sys
26
+ sys.exit(1)
27
+
28
+ import json
29
+ import os
30
+
31
+ TEXTUAL_AVAILABLE = True
32
+ try:
33
+ from textual.widgets import Static
34
+ from textual.containers import Vertical
35
+ except Exception:
36
+ TEXTUAL_AVAILABLE = False
37
+
38
+ LAYOUT_PATH = os.path.join(os.path.expanduser("~"), ".cct_ui_layout.json")
39
+
40
+ # ---- Explorer width bounds ------------------------------------------------
41
+ EXPLORER_MIN_WIDTH = 14 # never disappears by accident while dragging
42
+ EXPLORER_MAX_FRACTION = 0.55 # never wider than ~half the shell
43
+ EXPLORER_DEFAULT_WIDTH = 32
44
+
45
+ # ---- Composer height bounds ----------------------------------------------
46
+ COMPOSER_MIN_HEIGHT = 5 # input + footer stay usable
47
+ COMPOSER_MAX_FRACTION = 0.35 # compact bounds — chat history keeps majority of shell
48
+ COMPOSER_MARGIN_ROWS = 2 # composer's vertical margin rows (1 top + 1 bottom)
49
+
50
+ # ---- Right pane (Code / Preview) width bounds ------------------------------
51
+ RIGHTPANE_MIN_WIDTH = 24 # code/preview stays usable
52
+ RIGHTPANE_MAX_FRACTION = 0.60
53
+ RIGHTPANE_DEFAULT_WIDTH = 44
54
+ CHAT_MIN_FRACTION = 0.25 # chat column keeps at least this much of the shell
55
+
56
+
57
+ def load_layout():
58
+ """Persisted {explorer_width, composer_height, rightpane_width}
59
+ (ints or None)."""
60
+ data = {}
61
+ try:
62
+ with open(LAYOUT_PATH, "r", encoding="utf-8") as f:
63
+ data = json.load(f)
64
+ except Exception:
65
+ pass
66
+ out = {}
67
+ for key in ("explorer_width", "composer_height", "rightpane_width"):
68
+ try:
69
+ v = data.get(key)
70
+ if key == "composer_height" and (v is None or int(v) <= COMPOSER_MIN_HEIGHT or int(v) > 14):
71
+ out[key] = None
72
+ else:
73
+ out[key] = int(v) if v else None
74
+ except Exception:
75
+ out[key] = None
76
+ return out
77
+
78
+
79
+ def save_layout(explorer_width=None, composer_height=None,
80
+ rightpane_width=None):
81
+ try:
82
+ current = {}
83
+ if os.path.exists(LAYOUT_PATH):
84
+ with open(LAYOUT_PATH, "r", encoding="utf-8") as f:
85
+ current = json.load(f)
86
+ if explorer_width is not None:
87
+ current["explorer_width"] = int(explorer_width)
88
+ if composer_height is not None:
89
+ current["composer_height"] = int(composer_height)
90
+ if rightpane_width is not None:
91
+ current["rightpane_width"] = int(rightpane_width)
92
+ with open(LAYOUT_PATH, "w", encoding="utf-8") as f:
93
+ json.dump(current, f)
94
+ except Exception:
95
+ pass
96
+
97
+
98
+ if TEXTUAL_AVAILABLE:
99
+
100
+ class _DragHandle(Static):
101
+ """Shared mouse-drag plumbing: capture on press, track moves,
102
+ release + persist on drop. Subclasses implement `_apply(x, y)`
103
+ and `_persist()`."""
104
+
105
+ def __init__(self, renderable="", **kwargs):
106
+ super().__init__(renderable, **kwargs)
107
+ self._dragging = False
108
+
109
+ def _apply(self, event):
110
+ raise NotImplementedError
111
+
112
+ def _persist(self):
113
+ return
114
+
115
+ def on_mouse_down(self, event):
116
+ event.stop()
117
+ self._dragging = True
118
+ self.add_class("dragging")
119
+ # Grab the pointer even when it leaves this 1-cell widget —
120
+ # that's what makes dragging past the handle smooth.
121
+ self.capture_mouse()
122
+
123
+ def on_mouse_move(self, event):
124
+ if not self._dragging:
125
+ return
126
+ event.stop()
127
+ self._apply(event)
128
+
129
+ def on_mouse_up(self, event):
130
+ if not self._dragging:
131
+ return
132
+ event.stop()
133
+ self._dragging = False
134
+ self.remove_class("dragging")
135
+ try:
136
+ # NOTE: Widget.release_mouse() takes no arguments.
137
+ self.release_mouse()
138
+ except Exception:
139
+ pass
140
+ self._persist()
141
+
142
+ def on_unmount(self):
143
+ self._dragging = False
144
+
145
+ class ExplorerResizeHandle(_DragHandle):
146
+ """1-column divider between Explorer and the main area."""
147
+
148
+ DEFAULT_CSS = """
149
+ ExplorerResizeHandle {
150
+ width: 1;
151
+ height: 100%;
152
+ background: $border;
153
+ transition: background 100ms;
154
+ }
155
+ ExplorerResizeHandle:hover,
156
+ ExplorerResizeHandle.dragging {
157
+ background: $accent;
158
+ }
159
+ """
160
+
161
+ def __init__(self, shell, **kwargs):
162
+ super().__init__(id="cct-explorer-resizer", **kwargs)
163
+ self._shell = shell
164
+
165
+ def _clamp(self, width):
166
+ total = max(10, self._shell.size.width) if self._shell.is_attached \
167
+ else EXPLORER_DEFAULT_WIDTH * 2
168
+ ceiling = max(EXPLORER_MIN_WIDTH + 4,
169
+ int(total * EXPLORER_MAX_FRACTION))
170
+ return max(EXPLORER_MIN_WIDTH, min(ceiling, width))
171
+
172
+ def _apply(self, event):
173
+ explorer = self._shell.explorer
174
+ left_edge = explorer.region.x if explorer.region.x >= 0 else 0
175
+ target = event.screen_x - left_edge
176
+ explorer.set_width(self._clamp(target))
177
+ self._shell.sync_resizer()
178
+ try:
179
+ if not self._shell._fullscreen:
180
+ self._shell._relayout()
181
+ except Exception:
182
+ pass
183
+
184
+ def _persist(self):
185
+ save_layout(explorer_width=self._shell.explorer.width)
186
+
187
+ class ComposerResizeHandle(_DragHandle):
188
+ """Subtle full-width grip between chat history and composer."""
189
+
190
+ DEFAULT_CSS = """
191
+ ComposerResizeHandle {
192
+ width: 100%;
193
+ height: 1;
194
+ color: $text-faint;
195
+ content-align: center middle;
196
+ background: transparent;
197
+ transition: color 100ms;
198
+ }
199
+ ComposerResizeHandle:hover,
200
+ ComposerResizeHandle.dragging {
201
+ color: $accent;
202
+ background: transparent;
203
+ }
204
+ """
205
+
206
+ GRIP = "\u2500 \u00b7 \u2500 \u00b7 \u2500 \u00b7 \u2500"
207
+
208
+ def __init__(self, shell, **kwargs):
209
+ super().__init__(self.GRIP, id="cct-composer-resizer", **kwargs)
210
+ self._shell = shell
211
+
212
+ def reset_to_default(self):
213
+ """Reset composer to normal default compact height."""
214
+ from .composer import StickyComposer
215
+ try:
216
+ composer = self._shell.query_one(StickyComposer)
217
+ composer.set_explicit_height(None)
218
+ except Exception:
219
+ pass
220
+ save_layout(composer_height=None)
221
+
222
+ def on_click(self, event):
223
+ import time
224
+ now = time.monotonic()
225
+ if hasattr(self, "_last_click_time") and (now - self._last_click_time) < 0.6:
226
+ self._last_click_time = 0.0
227
+ event.stop()
228
+ self.reset_to_default()
229
+ return
230
+ self._last_click_time = now
231
+
232
+ def on_mouse_down(self, event):
233
+ import time
234
+ now = time.monotonic()
235
+ if hasattr(self, "_last_down_time") and (now - self._last_down_time) < 0.8:
236
+ # Double-click resets to normal default compact height
237
+ self._last_down_time = 0.0
238
+ event.stop()
239
+ self._dragging = False
240
+ self.remove_class("dragging")
241
+ try:
242
+ self.release_mouse()
243
+ except Exception:
244
+ pass
245
+ self.reset_to_default()
246
+ return
247
+ self._last_down_time = now
248
+ super().on_mouse_down(event)
249
+
250
+ def _bounds(self):
251
+ """(min_height, max_height) for the composer inside the main
252
+ column right now."""
253
+ try:
254
+ main = self._shell.query_one("#cct-workspace-main", Vertical)
255
+ available = max(20, main.region.height)
256
+ except Exception:
257
+ available = 20
258
+ max_h = min(14, max(COMPOSER_MIN_HEIGHT + 4, int(available * COMPOSER_MAX_FRACTION)))
259
+ return COMPOSER_MIN_HEIGHT, max_h
260
+
261
+ def _apply(self, event):
262
+ from .composer import StickyComposer
263
+ try:
264
+ composer = self._shell.query_one(StickyComposer)
265
+ except Exception:
266
+ return
267
+ # Derive the new height from the MAIN COLUMN's static
268
+ # geometry (never from the composer's live region — reading
269
+ # a widget you are simultaneously resizing feeds the next
270
+ # layout pass back into the math and oscillates).
271
+ try:
272
+ main = self._shell.query_one("#cct-workspace-main", Vertical)
273
+ main_bottom = main.region.y + main.region.height
274
+ except Exception:
275
+ return
276
+ lo, hi = self._bounds()
277
+ # Rows reserved below the drag point: the grip row itself +
278
+ # the composer's bottom margin. Height grows as the handle
279
+ # is dragged up, shrinks as it is dragged down.
280
+ target = int(main_bottom - COMPOSER_MARGIN_ROWS - event.screen_y)
281
+ if target <= lo + 1:
282
+ composer.set_explicit_height(None)
283
+ else:
284
+ target = max(lo, min(hi, target))
285
+ composer.set_explicit_height(target)
286
+
287
+ def _persist(self):
288
+ from .composer import StickyComposer
289
+ try:
290
+ composer = self._shell.query_one(StickyComposer)
291
+ except Exception:
292
+ return
293
+ h = getattr(composer, "_explicit_height", None)
294
+ save_layout(composer_height=h)
295
+
296
+ class RightPaneResizeHandle(_DragHandle):
297
+ """v0.7.10: 1-column divider between the CHAT column and the
298
+ CODE / PREVIEW pane. Drag left => right pane wider, right =>
299
+ narrower. Mirrors ExplorerResizeHandle's contract and persists
300
+ to the same layout file (rightpane_width)."""
301
+
302
+ DEFAULT_CSS = """
303
+ RightPaneResizeHandle {
304
+ width: 1;
305
+ height: 100%;
306
+ background: $border;
307
+ transition: background 100ms;
308
+ }
309
+ RightPaneResizeHandle:hover,
310
+ RightPaneResizeHandle.dragging {
311
+ background: $accent;
312
+ }
313
+ """
314
+
315
+ def __init__(self, shell, **kwargs):
316
+ super().__init__(id="cct-rightpane-resizer", **kwargs)
317
+ self._shell = shell
318
+
319
+ def _clamp(self, width):
320
+ total = max(20, self._shell.size.width) if self._shell.is_attached \
321
+ else RIGHTPANE_DEFAULT_WIDTH * 2
322
+ ceiling = max(RIGHTPANE_MIN_WIDTH + 4,
323
+ int(total * RIGHTPANE_MAX_FRACTION))
324
+ return max(RIGHTPANE_MIN_WIDTH, min(ceiling, width))
325
+
326
+ def _apply(self, event):
327
+ pane = self._shell.right_pane
328
+ if pane is None:
329
+ return
330
+ # Width grows as the handle moves LEFT of its own right edge.
331
+ right_edge = pane.region.x + pane.region.width
332
+ target = right_edge - event.screen_x
333
+ # Route through the shell's width owner (clamps + remembers
334
+ # the value for persistence). The raw Vertical container has
335
+ # no set_width of its own.
336
+ self._shell.set_right_width(target)
337
+ self._shell.sync_right_resizer()
338
+
339
+ def _persist(self):
340
+ # The shell remembers the last clamped width — never depend
341
+ # on a layout pass having landed for the live region read.
342
+ save_layout(rightpane_width=getattr(self._shell,
343
+ "_rightpane_width", None))
344
+
345
+ class SplitPreviewResizer(_DragHandle):
346
+ """v0.7.9 split view divider between editor and preview inside right pane.
347
+ Drag left => preview wider, right => editor wider. Only visible in SPLIT mode."""
348
+
349
+ DEFAULT_CSS = """
350
+ SplitPreviewResizer {
351
+ width: 1;
352
+ height: 100%;
353
+ background: $border;
354
+ transition: background 100ms;
355
+ }
356
+ SplitPreviewResizer:hover,
357
+ SplitPreviewResizer.dragging {
358
+ background: $accent;
359
+ }
360
+ """
361
+
362
+ def __init__(self, shell, **kwargs):
363
+ super().__init__(id="cct-split-resizer", **kwargs)
364
+ self._shell = shell
365
+
366
+ def _apply(self, event):
367
+ # Adjust split ratio based on handle position inside right pane
368
+ right = self._shell.right_pane
369
+ if right is None:
370
+ return
371
+ # Right pane's left edge
372
+ left_edge = right.region.x if right.region.x >=0 else 0
373
+ total_w = max(20, right.region.width)
374
+ # Position of handle relative to right pane left
375
+ pos = event.screen_x - left_edge
376
+ # Clamp so both editor and preview keep min width
377
+ min_w = 12
378
+ pos = max(min_w, min(total_w - min_w, pos))
379
+ ratio = pos / max(1, total_w)
380
+ # Apply as widths to editor and preview
381
+ try:
382
+ editor = self._shell.editor
383
+ preview = self._shell.preview_panel
384
+ if editor and preview:
385
+ # Use integer widths based on total
386
+ ed_w = max(min_w, int(total_w * ratio))
387
+ pv_w = max(min_w, total_w - ed_w - 1) # -1 for handle
388
+ editor.styles.width = ed_w
389
+ preview.styles.width = pv_w
390
+ except Exception:
391
+ pass
392
+
393
+ def _persist(self):
394
+ pass
395
+
396
+ else:
397
+
398
+ _DragHandle = None
399
+ ExplorerResizeHandle = None
400
+ ComposerResizeHandle = None
401
+ RightPaneResizeHandle = None
402
+ SplitPreviewResizer = None