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,441 @@
1
+ """
2
+ CAT UI — ui/preview_panel.py: the Web Preview pane.
3
+
4
+ Lives in the right workspace pane, switchable with the Code Editor
5
+ (never mounted/unmounted — only `display` flips, so navigation history,
6
+ URL bar text and scroll survive Code↔Preview switches):
7
+
8
+ ┌──────────────────────────────────────────────────────────┐
9
+ │ ← → ⟳ [ http://127.0.0.1:5173/ ] 🖥 💻 📟 📱 ◫ ⏻ ⿻ │ BrowserBar
10
+ ├──────────────────────────────────────────────────────────┤
11
+ │ [👁 Preview] [📋 Console] [⚠ Problems (0)] │ DevToolsBar
12
+ ├──────────────────────────────────────────────────────────┤
13
+ │ ● Writing style.css │ activity strip
14
+ │ ✓ Preview synchronized │
15
+ ├──────────────────────────────────────────────────────────┤
16
+ │ # CAT Live ← LIVE DOM outline / │ viewport
17
+ │ js-ran real Chromium / Console │
18
+ └──────────────────────────────────────────────────────────┘
19
+
20
+ Terminal-first contract: the page itself renders in genuine Chromium
21
+ (browser/engine.py); this panel shows its structured snapshot — title,
22
+ headings, links, buttons, images, console errors — plus dev server & console.
23
+ No external browser window is ever opened.
24
+
25
+ All colors are `$variables` resolved by CCTApp.get_css_variables(), so
26
+ the panel follows every theme automatically.
27
+ """
28
+
29
+ if __name__ == "__main__":
30
+ print("This is a library file and is not meant to be run directly.")
31
+ import sys
32
+ sys.exit(1)
33
+
34
+ import os
35
+ import re
36
+ from typing import List, Optional
37
+
38
+ TEXTUAL_AVAILABLE = True
39
+ try:
40
+ from textual.containers import Horizontal, Vertical, VerticalScroll
41
+ from textual.widgets import Button, Input, Static
42
+ except Exception:
43
+ TEXTUAL_AVAILABLE = False
44
+
45
+ MAX_ACTIVITY_LINES = 6
46
+ MAX_CONSOLE_LINES = 100
47
+
48
+
49
+ def _outline_markup(snapshot):
50
+ """PreviewSnapshot -> Rich markup for the DOM viewport."""
51
+ lines = []
52
+ if snapshot.title:
53
+ lines.append(f"[b]{snapshot.title}[/b]")
54
+ lines.append("")
55
+ for raw in snapshot.outline_lines[:200]:
56
+ line = str(raw)
57
+ esc = line.replace("[", "\\[")
58
+ if esc.startswith("#"):
59
+ lines.append(f"[b {chr(36)}accent]{esc}[/]")
60
+ elif esc.lstrip().startswith("↗"):
61
+ lines.append(f"[{chr(36)}text-muted]{esc}[/]")
62
+ elif "✗" in esc:
63
+ lines.append(f"[{chr(36)}error]{esc}[/]")
64
+ else:
65
+ lines.append(f"[{chr(36)}text]{esc}[/]")
66
+ return "\n".join(lines)
67
+
68
+
69
+ if TEXTUAL_AVAILABLE:
70
+
71
+ class BrowserBar(Horizontal):
72
+ """Navigation, URL input, viewport presets, and window controls."""
73
+
74
+ def __init__(self):
75
+ super().__init__(id="cct-browser-bar")
76
+
77
+ def compose(self):
78
+ yield Button("←", id="cct-bv-back", classes="cct-bv-btn", tooltip="Back")
79
+ yield Button("→", id="cct-bv-fwd", classes="cct-bv-btn", tooltip="Forward")
80
+ yield Button("⟳", id="cct-bv-reload", classes="cct-bv-btn", tooltip="Reload")
81
+ yield Input(value="", placeholder="http://127.0.0.1:<port>/",
82
+ id="cct-bv-url")
83
+ # Viewport presets
84
+ yield Button("🖥", id="cct-bv-vp-desktop", classes="cct-bv-btn -vp-active", tooltip="Desktop (100%)")
85
+ yield Button("💻", id="cct-bv-vp-laptop", classes="cct-bv-btn", tooltip="Laptop (1024px)")
86
+ yield Button("📟", id="cct-bv-vp-tablet", classes="cct-bv-btn", tooltip="Tablet (768px)")
87
+ yield Button("📱", id="cct-bv-vp-mobile", classes="cct-bv-btn", tooltip="Mobile (375px)")
88
+ # Window / Layout controls
89
+ yield Button("◫", id="cct-bv-split", classes="cct-bv-btn", tooltip="Split editor / preview")
90
+ yield Button("⏻", id="cct-bv-close", classes="cct-bv-btn cct-bv-danger", tooltip="Stop preview")
91
+ yield Button("⿻", id="cct-bv-fullscreen", classes="cct-bv-btn", tooltip="Fullscreen")
92
+
93
+ class DevToolsBar(Horizontal):
94
+ """Developer Tools sub-navigation: Preview | Console | Problems."""
95
+
96
+ def __init__(self):
97
+ super().__init__(id="cct-devtools-bar")
98
+
99
+ def compose(self):
100
+ yield Button("👁 Preview", id="cct-dev-tab-preview", classes="cct-dev-tab -tab-active")
101
+ yield Button("📋 Console", id="cct-dev-tab-console", classes="cct-dev-tab")
102
+ yield Button("⚠ Problems", id="cct-dev-tab-problems", classes="cct-dev-tab")
103
+
104
+ class PreviewPanel(Vertical):
105
+ """The WEB PREVIEW half of the right pane with developer tools and responsive viewports."""
106
+
107
+ def __init__(self, **kwargs):
108
+ super().__init__(id="cct-preview", **kwargs)
109
+ self._activity_lines = []
110
+ self._console_lines = []
111
+ self._status = ""
112
+ self._last_click_time = 0
113
+ self._last_click_target = None
114
+ self._active_tab = "preview" # "preview" | "console" | "problems"
115
+ self._active_viewport = "desktop" # "desktop" | "laptop" | "tablet" | "mobile"
116
+ self._last_snapshot = None
117
+
118
+ def compose(self):
119
+ yield BrowserBar()
120
+ yield DevToolsBar()
121
+ yield Static("", id="cct-bv-status")
122
+ with VerticalScroll(id="cct-bv-scroll"):
123
+ yield Static("", id="cct-bv-viewport")
124
+ yield Static("", id="cct-bv-console")
125
+ yield Static("", id="cct-bv-problems")
126
+ yield Static("", id="cct-bv-activity")
127
+
128
+ def on_mount(self):
129
+ self.show_status("Preview stopped — click ▷ on an HTML file or run dev server.")
130
+ self._switch_dev_tab("preview")
131
+
132
+ # ------------------------------------------------------ status --
133
+ def show_status(self, text, error=False):
134
+ try:
135
+ status = self.query_one("#cct-bv-status", Static)
136
+ status.update(text)
137
+ status.set_class(bool(error), "-error")
138
+ except Exception:
139
+ pass
140
+
141
+ def set_url(self, url):
142
+ try:
143
+ self.query_one("#cct-bv-url", Input).value = url or ""
144
+ except Exception:
145
+ pass
146
+
147
+ # ---------------------------------------------------- tabs & viewport --
148
+ def _switch_dev_tab(self, tab_name: str):
149
+ """Switch between Preview, Console, and Problems."""
150
+ self._active_tab = tab_name
151
+ for name in ("preview", "console", "problems"):
152
+ btn = self.query_one(f"#cct-dev-tab-{name}", Button)
153
+ btn.set_class(name == tab_name, "-tab-active")
154
+
155
+ vp = self.query_one("#cct-bv-viewport", Static)
156
+ con = self.query_one("#cct-bv-console", Static)
157
+ prob = self.query_one("#cct-bv-problems", Static)
158
+
159
+ vp.display = (tab_name == "preview")
160
+ con.display = (tab_name == "console")
161
+ prob.display = (tab_name == "problems")
162
+
163
+ if tab_name == "console":
164
+ self._render_console()
165
+ elif tab_name == "problems":
166
+ self._render_problems()
167
+
168
+ def _switch_viewport(self, vp_name: str):
169
+ """Set responsive viewport preset."""
170
+ self._active_viewport = vp_name
171
+ for name in ("desktop", "laptop", "tablet", "mobile"):
172
+ try:
173
+ btn = self.query_one(f"#cct-bv-vp-{name}", Button)
174
+ btn.set_class(name == vp_name, "-vp-active")
175
+ except Exception:
176
+ pass
177
+
178
+ # Apply width styling to the viewport scroll container
179
+ try:
180
+ scroll = self.query_one("#cct-bv-scroll", VerticalScroll)
181
+ widths = {
182
+ "desktop": "100%",
183
+ "laptop": "1024",
184
+ "tablet": "768",
185
+ "mobile": "375",
186
+ }
187
+ w = widths.get(vp_name, "100%")
188
+ if w == "100%":
189
+ scroll.styles.width = "100%"
190
+ scroll.styles.max_width = None
191
+ else:
192
+ scroll.styles.max_width = int(w)
193
+ self.show_status(f"Viewport set to {vp_name.capitalize()} ({w}px)")
194
+ except Exception:
195
+ pass
196
+
197
+ # ---------------------------------------------------- console & problems --
198
+ def add_console_log(self, text: str, is_error: bool = False):
199
+ """Log a line from browser runtime or local dev server."""
200
+ prefix = "[error]✗[/]" if is_error else "[info]ℹ[/]"
201
+ self._console_lines.append(f"{prefix} {text}")
202
+ if len(self._console_lines) > MAX_CONSOLE_LINES:
203
+ del self._console_lines[:-MAX_CONSOLE_LINES]
204
+ if self._active_tab == "console":
205
+ self._render_console()
206
+
207
+ def _render_console(self):
208
+ try:
209
+ con = self.query_one("#cct-bv-console", Static)
210
+ if not self._console_lines:
211
+ con.update("[dim]No console output captured yet.[/dim]")
212
+ else:
213
+ con.update("\n".join(self._console_lines))
214
+ except Exception:
215
+ pass
216
+
217
+ def _render_problems(self):
218
+ """Render problems from DiagnosticsManager with clickable jump links."""
219
+ try:
220
+ prob = self.query_one("#cct-bv-problems", Static)
221
+ from ..preview.diagnostics import get_diagnostics_manager
222
+ diags = get_diagnostics_manager().list_diagnostics()
223
+
224
+ # Update the tab label with error count
225
+ err_count = len([d for d in diags if d.severity == "error"])
226
+ prob_btn = self.query_one("#cct-dev-tab-problems", Button)
227
+ prob_btn.label = f"⚠ Problems ({err_count})" if err_count else "⚠ Problems"
228
+
229
+ if not diags:
230
+ prob.update("[dim]✓ No problems detected in preview or workspace.[/dim]")
231
+ return
232
+
233
+ lines = [f"[b]Diagnostics ({len(diags)} items):[/b]\n"]
234
+ for i, d in enumerate(diags):
235
+ color = "red" if d.severity == "error" else ("yellow" if d.severity == "warning" else "cyan")
236
+ loc = ""
237
+ if d.file:
238
+ fn = os.path.basename(d.file)
239
+ pos = f":{d.line}" if d.line is not None else ""
240
+ if d.column is not None:
241
+ pos += f":{d.column}"
242
+ loc = f" [{color}][{fn}{pos}][/]"
243
+ lines.append(f"[bold {color}][{d.severity.upper()}][/] {d.message}{loc}")
244
+ if d.file and d.line:
245
+ lines.append(f" [dim cyan]→ Press or click to jump: {d.file}:{d.line}[/dim cyan]")
246
+ lines.append("")
247
+
248
+ prob.update("\n".join(lines))
249
+ except Exception:
250
+ pass
251
+
252
+ # ---------------------------------------------------- viewport --
253
+ def show_snapshot(self, snapshot):
254
+ self._last_snapshot = snapshot
255
+ try:
256
+ viewport = self.query_one("#cct-bv-viewport", Static)
257
+ scroll = self.query_one("#cct-bv-scroll", VerticalScroll)
258
+ except Exception:
259
+ return
260
+ if snapshot is None:
261
+ return
262
+ self.set_url(snapshot.url)
263
+
264
+ # Ingest JS errors into diagnostics and console
265
+ if snapshot.js_errors:
266
+ from ..preview.diagnostics import get_diagnostics_manager
267
+ dm = get_diagnostics_manager()
268
+ for err in snapshot.js_errors:
269
+ err_str = str(err)
270
+ self.add_console_log(err_str, is_error=True)
271
+ dm.add(severity="error", message=err_str, source="browser")
272
+ self._render_problems()
273
+
274
+ if not snapshot.ok and snapshot.error:
275
+ self.show_status(f"WEB PREVIEW ERROR — {snapshot.error}",
276
+ error=True)
277
+ err_lines = list(snapshot.js_errors[-4:]) or \
278
+ ["The site failed to load. Check the URL or files."]
279
+ body = "\n".join(str(e) for e in err_lines)
280
+ viewport.update(
281
+ f"[b {chr(36)}error]WEB PREVIEW ERROR[/]\n\n{body}\n\n"
282
+ f"[{chr(36)}text-faint]Chat / Files / Editor / AI remain "
283
+ f"unaffected.[/]")
284
+ else:
285
+ js_err = ""
286
+ if snapshot.js_errors:
287
+ last = str(snapshot.js_errors[-1])[:120]
288
+ js_err = f"\n[{chr(36)}warning]PREVIEW CONSOLE {last}[/]"
289
+ self.show_status(
290
+ f"● Live — {snapshot.summary_line}{js_err}")
291
+ viewport.update(_outline_markup(snapshot))
292
+
293
+ if self._active_tab == "preview":
294
+ scroll.scroll_home(animate=False)
295
+
296
+ def show_unavailable(self, detail):
297
+ try:
298
+ viewport = self.query_one("#cct-bv-viewport", Static)
299
+ except Exception:
300
+ return
301
+ self.show_status("Browser engine unavailable.", error=True)
302
+ viewport.update(
303
+ f"[b {chr(36)}warning]BROWSER ENGINE UNAVAILABLE[/]\n\n"
304
+ f"{detail}\n\n"
305
+ f"[{chr(36)}text-faint]Everything else in CAT keeps working: "
306
+ f"chat, files, editor and the AI agent are unaffected.[/]")
307
+
308
+ # ----------------------------------------------------- activity --
309
+ def push_activity(self, text, done=False):
310
+ """One line of CAT BUILD ACTIVITY (spec section 15). Kept in
311
+ a bounded ring so rapid AI writes can't flood the pane."""
312
+ mark = "✓" if done else "●"
313
+ self._activity_lines.append(f"{mark} {text}")
314
+ del self._activity_lines[:-MAX_ACTIVITY_LINES:]
315
+ try:
316
+ widget = self.query_one("#cct-bv-activity", Static)
317
+ body = "\n".join(self._activity_lines)
318
+ widget.update(body)
319
+ widget.set_class(True, "-has-lines") if body else None
320
+ except Exception:
321
+ pass
322
+
323
+ def clear_activity(self):
324
+ self._activity_lines = []
325
+ try:
326
+ self.query_one("#cct-bv-activity", Static).update("")
327
+ except Exception:
328
+ pass
329
+
330
+ # ------------------------------------------------------- events --
331
+ def on_button_pressed(self, event):
332
+ bid = event.button.id
333
+ app = getattr(self, "app", None)
334
+ if app is None:
335
+ return
336
+ event.stop()
337
+
338
+ # DevTools tabs
339
+ if bid == "cct-dev-tab-preview":
340
+ self._switch_dev_tab("preview")
341
+ return
342
+ elif bid == "cct-dev-tab-console":
343
+ self._switch_dev_tab("console")
344
+ return
345
+ elif bid == "cct-dev-tab-problems":
346
+ self._switch_dev_tab("problems")
347
+ return
348
+
349
+ # Viewport presets
350
+ if bid == "cct-bv-vp-desktop":
351
+ self._switch_viewport("desktop")
352
+ return
353
+ elif bid == "cct-bv-vp-laptop":
354
+ self._switch_viewport("laptop")
355
+ return
356
+ elif bid == "cct-bv-vp-tablet":
357
+ self._switch_viewport("tablet")
358
+ return
359
+ elif bid == "cct-bv-vp-mobile":
360
+ self._switch_viewport("mobile")
361
+ return
362
+
363
+ # Navigation & Window controls
364
+ if bid == "cct-bv-back":
365
+ app.action_preview_back()
366
+ elif bid == "cct-bv-fwd":
367
+ app.action_preview_forward()
368
+ elif bid == "cct-bv-reload":
369
+ app.action_preview_reload()
370
+ elif bid == "cct-bv-split":
371
+ try:
372
+ from ..browser import WorkspaceMode
373
+ shell = app.query_one("#cct-workspace")
374
+ if shell.mode is WorkspaceMode.SPLIT:
375
+ shell.set_right_mode(WorkspaceMode.PREVIEW)
376
+ else:
377
+ shell.set_right_mode(WorkspaceMode.SPLIT)
378
+ except Exception:
379
+ pass
380
+ elif bid == "cct-bv-close":
381
+ app.action_preview_close()
382
+ elif bid == "cct-bv-fullscreen":
383
+ app.action_toggle_right_pane_fullscreen()
384
+
385
+ def on_input_submitted(self, event):
386
+ """Enter in the URL bar navigates (local-first resolution)."""
387
+ app = getattr(self, "app", None)
388
+ if app is None:
389
+ return
390
+ event.stop()
391
+ app.preview_navigate_user(event.value)
392
+
393
+ def on_click(self, event):
394
+ """Gestures & jumping to source from problems list."""
395
+ import time as _t
396
+ try:
397
+ widget = event.widget
398
+ widget_id = getattr(widget, "id", "")
399
+
400
+ # Check if clicking inside Problems to jump to error
401
+ if self._active_tab == "problems":
402
+ from ..preview.diagnostics import get_diagnostics_manager
403
+ diags = [d for d in get_diagnostics_manager().list_diagnostics() if d.file]
404
+ if diags and hasattr(self.app, "open_file_at"):
405
+ # Jump to the first diagnostic location with a line number
406
+ first_loc = next((d for d in diags if d.file and d.line), None)
407
+ if first_loc:
408
+ self.app.open_file_at(first_loc.file, first_loc.line, first_loc.column or 1)
409
+ event.stop()
410
+ return
411
+
412
+ # Don't interfere with button clicks
413
+ if widget_id and widget_id.startswith("cct-bv-") or widget_id.startswith("cct-dev-"):
414
+ return
415
+
416
+ now = _t.time()
417
+ if now - self._last_click_time < 0.35 and self._last_click_target == "preview":
418
+ self._last_click_time = 0
419
+ self._last_click_target = None
420
+ try:
421
+ from ..gestures.manager import handle_gesture
422
+ if handle_gesture("double_click", "preview", app=getattr(self, "app", None), context={}):
423
+ event.stop()
424
+ return
425
+ except Exception:
426
+ pass
427
+ try:
428
+ self.app.action_toggle_right_pane_fullscreen()
429
+ event.stop()
430
+ except Exception:
431
+ pass
432
+ else:
433
+ self._last_click_time = now
434
+ self._last_click_target = "preview"
435
+ except Exception:
436
+ pass
437
+
438
+ else:
439
+ PreviewPanel = None
440
+ BrowserBar = None
441
+ DevToolsBar = None