iCode-TUI 0.29.1__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 (1138) hide show
  1. chrys/__init__.py +16 -0
  2. chrys/app/__init__.py +3 -0
  3. chrys/app/acp/__init__.py +3 -0
  4. chrys/app/acp/bridge.py +664 -0
  5. chrys/app/acp/content.py +132 -0
  6. chrys/app/acp/doc/frontend-api.md +425 -0
  7. chrys/app/acp/history.py +585 -0
  8. chrys/app/acp/server.py +1796 -0
  9. chrys/app/acp/session_manager.py +1815 -0
  10. chrys/app/acp/tool_status.py +45 -0
  11. chrys/app/cli/__init__.py +3 -0
  12. chrys/app/cli/acp.py +178 -0
  13. chrys/app/cli/app.py +165 -0
  14. chrys/app/cli/approvals.py +101 -0
  15. chrys/app/cli/headless.py +223 -0
  16. chrys/app/cli/launch_cwd.py +42 -0
  17. chrys/app/cli/profiles.py +298 -0
  18. chrys/app/cli/progress.py +857 -0
  19. chrys/app/cli/run.py +425 -0
  20. chrys/app/cli/serve.py +1593 -0
  21. chrys/app/cli/tool_summary.py +225 -0
  22. chrys/app/cli/trajectory.py +201 -0
  23. chrys/app/cli/workflow.py +601 -0
  24. chrys/app/features/__init__.py +3 -0
  25. chrys/app/features/buddy/__init__.py +3 -0
  26. chrys/app/features/buddy/actions.py +68 -0
  27. chrys/app/features/buddy/animation.py +76 -0
  28. chrys/app/features/buddy/commands.py +196 -0
  29. chrys/app/features/buddy/docs/design.md +187 -0
  30. chrys/app/features/buddy/hatchery.py +80 -0
  31. chrys/app/features/buddy/lifecycle.py +41 -0
  32. chrys/app/features/buddy/model.py +265 -0
  33. chrys/app/features/buddy/pixel_renderer.py +113 -0
  34. chrys/app/features/buddy/pixel_sprites.py +279 -0
  35. chrys/app/features/buddy/portrait.py +76 -0
  36. chrys/app/features/buddy/progression.py +58 -0
  37. chrys/app/features/buddy/replies.py +191 -0
  38. chrys/app/features/buddy/sprites/alien.toml +70 -0
  39. chrys/app/features/buddy/sprites/axolotl.toml +71 -0
  40. chrys/app/features/buddy/sprites/bat.toml +69 -0
  41. chrys/app/features/buddy/sprites/bee.toml +69 -0
  42. chrys/app/features/buddy/sprites/cactus.toml +73 -0
  43. chrys/app/features/buddy/sprites/capybara.toml +70 -0
  44. chrys/app/features/buddy/sprites/cat.toml +69 -0
  45. chrys/app/features/buddy/sprites/crab.toml +70 -0
  46. chrys/app/features/buddy/sprites/dragon.toml +71 -0
  47. chrys/app/features/buddy/sprites/duck.toml +69 -0
  48. chrys/app/features/buddy/sprites/fox.toml +69 -0
  49. chrys/app/features/buddy/sprites/frog.toml +70 -0
  50. chrys/app/features/buddy/sprites/ghost.toml +70 -0
  51. chrys/app/features/buddy/sprites/goose.toml +69 -0
  52. chrys/app/features/buddy/sprites/jelly.toml +70 -0
  53. chrys/app/features/buddy/sprites/llama.toml +70 -0
  54. chrys/app/features/buddy/sprites/mushroom.toml +69 -0
  55. chrys/app/features/buddy/sprites/octopus.toml +70 -0
  56. chrys/app/features/buddy/sprites/owl.toml +70 -0
  57. chrys/app/features/buddy/sprites/panda.toml +71 -0
  58. chrys/app/features/buddy/sprites/penguin.toml +69 -0
  59. chrys/app/features/buddy/sprites/rabbit.toml +70 -0
  60. chrys/app/features/buddy/sprites/robot.toml +72 -0
  61. chrys/app/features/buddy/sprites/shark.toml +70 -0
  62. chrys/app/features/buddy/sprites/snail.toml +69 -0
  63. chrys/app/features/buddy/sprites/snake.toml +69 -0
  64. chrys/app/features/buddy/sprites/tabby.toml +69 -0
  65. chrys/app/features/buddy/sprites/turtle.toml +69 -0
  66. chrys/app/features/buddy/store.py +143 -0
  67. chrys/app/features/session_title/__init__.py +7 -0
  68. chrys/app/features/session_title/updater.py +582 -0
  69. chrys/app/installer.py +651 -0
  70. chrys/app/parsing.py +17 -0
  71. chrys/app/tui/__init__.py +3 -0
  72. chrys/app/tui/app.py +1287 -0
  73. chrys/app/tui/behaviors/__init__.py +3 -0
  74. chrys/app/tui/behaviors/click_outside_dismiss.py +90 -0
  75. chrys/app/tui/behaviors/insert_clipboard.py +73 -0
  76. chrys/app/tui/behaviors/right_click_copy.py +146 -0
  77. chrys/app/tui/binding_display.py +139 -0
  78. chrys/app/tui/buddy_messages.py +17 -0
  79. chrys/app/tui/buddy_reply.py +86 -0
  80. chrys/app/tui/chrys.tcss +549 -0
  81. chrys/app/tui/clipboard.py +109 -0
  82. chrys/app/tui/copy_messages.py +10 -0
  83. chrys/app/tui/i18n.py +230 -0
  84. chrys/app/tui/language.py +58 -0
  85. chrys/app/tui/notifications/__init__.py +12 -0
  86. chrys/app/tui/notifications/drivers.py +180 -0
  87. chrys/app/tui/notifications/service.py +126 -0
  88. chrys/app/tui/notifications/settings.py +141 -0
  89. chrys/app/tui/screens/__init__.py +3 -0
  90. chrys/app/tui/screens/agents/__init__.py +8 -0
  91. chrys/app/tui/screens/agents/agent_draft_store.py +937 -0
  92. chrys/app/tui/screens/agents/config.py +2142 -0
  93. chrys/app/tui/screens/agents/config.tcss +169 -0
  94. chrys/app/tui/screens/agents/panels/__init__.py +3 -0
  95. chrys/app/tui/screens/agents/panels/acp.py +1176 -0
  96. chrys/app/tui/screens/agents/panels/basic.py +497 -0
  97. chrys/app/tui/screens/agents/panels/compaction.py +231 -0
  98. chrys/app/tui/screens/agents/panels/config_card.py +218 -0
  99. chrys/app/tui/screens/agents/panels/instructions.py +91 -0
  100. chrys/app/tui/screens/agents/panels/markdown_report.py +76 -0
  101. chrys/app/tui/screens/agents/panels/mcp.py +2262 -0
  102. chrys/app/tui/screens/agents/panels/memory.py +684 -0
  103. chrys/app/tui/screens/agents/panels/path_entry.py +227 -0
  104. chrys/app/tui/screens/agents/panels/skills.py +806 -0
  105. chrys/app/tui/screens/agents/panels/subagents.py +652 -0
  106. chrys/app/tui/screens/agents/panels/tools.py +205 -0
  107. chrys/app/tui/screens/agents/picker.py +103 -0
  108. chrys/app/tui/screens/agents/skill_paths.py +38 -0
  109. chrys/app/tui/screens/agents/validation_messages.py +452 -0
  110. chrys/app/tui/screens/dialogs/__init__.py +35 -0
  111. chrys/app/tui/screens/dialogs/agent_load.py +834 -0
  112. chrys/app/tui/screens/dialogs/agent_load.tcss +78 -0
  113. chrys/app/tui/screens/dialogs/app_mode.py +55 -0
  114. chrys/app/tui/screens/dialogs/approval/__init__.py +15 -0
  115. chrys/app/tui/screens/dialogs/approval/approval.tcss +311 -0
  116. chrys/app/tui/screens/dialogs/approval/bodies/__init__.py +3 -0
  117. chrys/app/tui/screens/dialogs/approval/bodies/file_edit.py +210 -0
  118. chrys/app/tui/screens/dialogs/approval/bodies/sub_agent.py +82 -0
  119. chrys/app/tui/screens/dialogs/approval/body.py +110 -0
  120. chrys/app/tui/screens/dialogs/approval/dialog.py +565 -0
  121. chrys/app/tui/screens/dialogs/approval/mode.py +105 -0
  122. chrys/app/tui/screens/dialogs/ask_user.py +148 -0
  123. chrys/app/tui/screens/dialogs/ask_user.tcss +62 -0
  124. chrys/app/tui/screens/dialogs/base.py +84 -0
  125. chrys/app/tui/screens/dialogs/confirm.py +184 -0
  126. chrys/app/tui/screens/dialogs/confirm.tcss +46 -0
  127. chrys/app/tui/screens/dialogs/connection_test.py +183 -0
  128. chrys/app/tui/screens/dialogs/connection_test.tcss +92 -0
  129. chrys/app/tui/screens/dialogs/diagram.py +102 -0
  130. chrys/app/tui/screens/dialogs/diagram.tcss +24 -0
  131. chrys/app/tui/screens/dialogs/editor.py +378 -0
  132. chrys/app/tui/screens/dialogs/editor.tcss +132 -0
  133. chrys/app/tui/screens/dialogs/file_picker.py +582 -0
  134. chrys/app/tui/screens/dialogs/file_picker.tcss +78 -0
  135. chrys/app/tui/screens/dialogs/fork_session.py +219 -0
  136. chrys/app/tui/screens/dialogs/fork_session.tcss +75 -0
  137. chrys/app/tui/screens/dialogs/image_compression.py +102 -0
  138. chrys/app/tui/screens/dialogs/image_compression.tcss +28 -0
  139. chrys/app/tui/screens/dialogs/man_page.py +172 -0
  140. chrys/app/tui/screens/dialogs/man_page.tcss +39 -0
  141. chrys/app/tui/screens/dialogs/new_folder.py +277 -0
  142. chrys/app/tui/screens/dialogs/new_folder.tcss +51 -0
  143. chrys/app/tui/screens/dialogs/notice.tcss +14 -0
  144. chrys/app/tui/screens/dialogs/prompt_cache.py +144 -0
  145. chrys/app/tui/screens/dialogs/prompt_cache.tcss +38 -0
  146. chrys/app/tui/screens/dialogs/runtime_details.py +371 -0
  147. chrys/app/tui/screens/dialogs/runtime_details.tcss +49 -0
  148. chrys/app/tui/screens/dialogs/session_title.py +110 -0
  149. chrys/app/tui/screens/dialogs/session_title.tcss +43 -0
  150. chrys/app/tui/screens/dialogs/tool_view.py +207 -0
  151. chrys/app/tui/screens/dialogs/tool_view.tcss +43 -0
  152. chrys/app/tui/screens/dialogs/vision_unsupported.py +122 -0
  153. chrys/app/tui/screens/dialogs/vision_unsupported.tcss +35 -0
  154. chrys/app/tui/screens/dialogs/workflow_confirm.py +91 -0
  155. chrys/app/tui/screens/dialogs/workflow_confirm.tcss +17 -0
  156. chrys/app/tui/screens/dialogs/workflow_input.py +258 -0
  157. chrys/app/tui/screens/dialogs/workflow_input.tcss +73 -0
  158. chrys/app/tui/screens/dialogs/workflow_node.py +462 -0
  159. chrys/app/tui/screens/dialogs/workflow_node.tcss +41 -0
  160. chrys/app/tui/screens/dialogs/workflow_picker.py +46 -0
  161. chrys/app/tui/screens/dialogs/workflow_result.py +136 -0
  162. chrys/app/tui/screens/dialogs/workflow_result.tcss +14 -0
  163. chrys/app/tui/screens/diff/__init__.py +22 -0
  164. chrys/app/tui/screens/diff/rollback_modal.py +1381 -0
  165. chrys/app/tui/screens/diff/rollback_modal.tcss +248 -0
  166. chrys/app/tui/screens/diff/screen.py +1532 -0
  167. chrys/app/tui/screens/diff/screen.tcss +104 -0
  168. chrys/app/tui/screens/guides/__init__.py +7 -0
  169. chrys/app/tui/screens/guides/_docs/en/guides/configuration/agents.md +178 -0
  170. chrys/app/tui/screens/guides/_docs/en/guides/configuration/approval.md +117 -0
  171. chrys/app/tui/screens/guides/_docs/en/guides/configuration/chat-options.md +276 -0
  172. chrys/app/tui/screens/guides/_docs/en/guides/configuration/compaction.md +42 -0
  173. chrys/app/tui/screens/guides/_docs/en/guides/configuration/memory.md +48 -0
  174. chrys/app/tui/screens/guides/_docs/en/guides/configuration/models.md +158 -0
  175. chrys/app/tui/screens/guides/_docs/en/guides/configuration/settings.md +210 -0
  176. chrys/app/tui/screens/guides/_docs/en/guides/configuration/themes.md +61 -0
  177. chrys/app/tui/screens/guides/_docs/en/guides/configuration/tools.md +60 -0
  178. chrys/app/tui/screens/guides/_docs/en/guides/configuration/web-tools.md +286 -0
  179. chrys/app/tui/screens/guides/_docs/en/guides/daily-use/sessions.md +77 -0
  180. chrys/app/tui/screens/guides/_docs/en/guides/daily-use/workspaces.md +51 -0
  181. chrys/app/tui/screens/guides/_docs/en/guides/extensions/external-acp-agents.md +126 -0
  182. chrys/app/tui/screens/guides/_docs/en/guides/extensions/hooks.md +167 -0
  183. chrys/app/tui/screens/guides/_docs/en/guides/extensions/mcp.md +95 -0
  184. chrys/app/tui/screens/guides/_docs/en/guides/extensions/skills.md +175 -0
  185. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-acp.md +61 -0
  186. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-run.md +190 -0
  187. chrys/app/tui/screens/guides/_docs/en/guides/running/icode-serve.md +239 -0
  188. chrys/app/tui/screens/guides/_docs/en/guides/running/workflows.md +664 -0
  189. chrys/app/tui/screens/guides/_docs/en/reference/agent-profile.md +548 -0
  190. chrys/app/tui/screens/guides/_docs/en/reference/hooks.md +602 -0
  191. chrys/app/tui/screens/guides/_docs/en/reference/opentelemetry.md +177 -0
  192. chrys/app/tui/screens/guides/_docs/en/reference/settings.md +245 -0
  193. chrys/app/tui/screens/guides/_docs/en/reference/themes.md +149 -0
  194. chrys/app/tui/screens/guides/_docs/en/reference/tool-kinds-and-names.md +58 -0
  195. chrys/app/tui/screens/guides/_docs/en/reference/tui-slash-commands.md +76 -0
  196. chrys/app/tui/screens/guides/_docs/en/reference/workflows.md +1006 -0
  197. chrys/app/tui/screens/guides/_docs/en/start/getting-started.md +170 -0
  198. chrys/app/tui/screens/guides/_docs/en/start/what-is-icode.md +34 -0
  199. chrys/app/tui/screens/guides/_docs/index.yaml +100 -0
  200. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/agents.md +178 -0
  201. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/approval.md +117 -0
  202. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/chat-options.md +276 -0
  203. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/compaction.md +42 -0
  204. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/memory.md +48 -0
  205. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/models.md +158 -0
  206. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/settings.md +210 -0
  207. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/themes.md +61 -0
  208. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/tools.md +60 -0
  209. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/configuration/web-tools.md +286 -0
  210. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/sessions.md +77 -0
  211. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/daily-use/workspaces.md +51 -0
  212. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/external-acp-agents.md +126 -0
  213. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/hooks.md +167 -0
  214. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/mcp.md +95 -0
  215. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/extensions/skills.md +175 -0
  216. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-acp.md +61 -0
  217. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-run.md +190 -0
  218. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/icode-serve.md +239 -0
  219. chrys/app/tui/screens/guides/_docs/zh-Hans/guides/running/workflows.md +664 -0
  220. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/agent-profile.md +548 -0
  221. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/hooks.md +602 -0
  222. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/opentelemetry.md +177 -0
  223. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/settings.md +245 -0
  224. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/themes.md +149 -0
  225. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tool-kinds-and-names.md +58 -0
  226. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/tui-slash-commands.md +76 -0
  227. chrys/app/tui/screens/guides/_docs/zh-Hans/reference/workflows.md +1005 -0
  228. chrys/app/tui/screens/guides/_docs/zh-Hans/start/getting-started.md +170 -0
  229. chrys/app/tui/screens/guides/_docs/zh-Hans/start/what-is-icode.md +34 -0
  230. chrys/app/tui/screens/guides/index.py +215 -0
  231. chrys/app/tui/screens/guides/reader.py +173 -0
  232. chrys/app/tui/screens/guides/screen.py +442 -0
  233. chrys/app/tui/screens/guides/screen.tcss +52 -0
  234. chrys/app/tui/screens/logs/__init__.py +7 -0
  235. chrys/app/tui/screens/logs/screen.py +504 -0
  236. chrys/app/tui/screens/logs/screen.tcss +45 -0
  237. chrys/app/tui/screens/main/__init__.py +7 -0
  238. chrys/app/tui/screens/main/action_availability.py +30 -0
  239. chrys/app/tui/screens/main/buddy_command.py +143 -0
  240. chrys/app/tui/screens/main/commands.py +1073 -0
  241. chrys/app/tui/screens/main/config_actions.py +464 -0
  242. chrys/app/tui/screens/main/copy_actions.py +117 -0
  243. chrys/app/tui/screens/main/dialog_controllers.py +826 -0
  244. chrys/app/tui/screens/main/dialog_gateway.py +166 -0
  245. chrys/app/tui/screens/main/diff_controller.py +478 -0
  246. chrys/app/tui/screens/main/engine_read_model.py +184 -0
  247. chrys/app/tui/screens/main/event_handlers.py +2014 -0
  248. chrys/app/tui/screens/main/input_flow.py +241 -0
  249. chrys/app/tui/screens/main/live_diff.py +244 -0
  250. chrys/app/tui/screens/main/main_screen_presenter.py +295 -0
  251. chrys/app/tui/screens/main/model_indicator.py +217 -0
  252. chrys/app/tui/screens/main/navigation.py +343 -0
  253. chrys/app/tui/screens/main/ports.py +571 -0
  254. chrys/app/tui/screens/main/recent_dirs.py +123 -0
  255. chrys/app/tui/screens/main/rollback_controller.py +586 -0
  256. chrys/app/tui/screens/main/runtime_info.py +71 -0
  257. chrys/app/tui/screens/main/screen.py +2265 -0
  258. chrys/app/tui/screens/main/screen.tcss +21 -0
  259. chrys/app/tui/screens/main/session_handlers.py +1127 -0
  260. chrys/app/tui/screens/main/session_title.py +239 -0
  261. chrys/app/tui/screens/main/settings_coordinator.py +365 -0
  262. chrys/app/tui/screens/main/settings_persistence.py +184 -0
  263. chrys/app/tui/screens/main/shell_mode.py +112 -0
  264. chrys/app/tui/screens/main/state.py +289 -0
  265. chrys/app/tui/screens/main/submit_state.py +11 -0
  266. chrys/app/tui/screens/main/subscriptions.py +177 -0
  267. chrys/app/tui/screens/main/suggestions.py +982 -0
  268. chrys/app/tui/screens/main/tool_action_bridge.py +75 -0
  269. chrys/app/tui/screens/main/view_adapter.py +1567 -0
  270. chrys/app/tui/screens/main/workflow_browser.py +747 -0
  271. chrys/app/tui/screens/main/workflow_content.py +248 -0
  272. chrys/app/tui/screens/main/workflow_controller.py +240 -0
  273. chrys/app/tui/screens/main/workflow_feedback.py +137 -0
  274. chrys/app/tui/screens/main/workflow_flow.py +49 -0
  275. chrys/app/tui/screens/main/workflow_run_control.py +456 -0
  276. chrys/app/tui/screens/main/workflow_session_view.py +316 -0
  277. chrys/app/tui/screens/main/workspace_actions.py +272 -0
  278. chrys/app/tui/screens/main/workspace_branch.py +194 -0
  279. chrys/app/tui/screens/menus/__init__.py +8 -0
  280. chrys/app/tui/screens/menus/languages.py +114 -0
  281. chrys/app/tui/screens/models/__init__.py +7 -0
  282. chrys/app/tui/screens/models/picker.py +147 -0
  283. chrys/app/tui/screens/models/screen.py +2043 -0
  284. chrys/app/tui/screens/models/screen.tcss +255 -0
  285. chrys/app/tui/screens/sessions/__init__.py +8 -0
  286. chrys/app/tui/screens/sessions/presenter.py +586 -0
  287. chrys/app/tui/screens/sessions/screen.py +779 -0
  288. chrys/app/tui/screens/sessions/screen.tcss +127 -0
  289. chrys/app/tui/screens/settings/__init__.py +29 -0
  290. chrys/app/tui/screens/settings/danger.py +26 -0
  291. chrys/app/tui/screens/settings/dialog.py +281 -0
  292. chrys/app/tui/screens/settings/layout.py +519 -0
  293. chrys/app/tui/screens/settings/migrate_dialog.py +253 -0
  294. chrys/app/tui/screens/settings/panes/__init__.py +3 -0
  295. chrys/app/tui/screens/settings/panes/notifications.py +248 -0
  296. chrys/app/tui/screens/settings/panes/sessions.py +148 -0
  297. chrys/app/tui/screens/settings/ports.py +55 -0
  298. chrys/app/tui/screens/settings/provenance.py +102 -0
  299. chrys/app/tui/screens/settings/rows.py +546 -0
  300. chrys/app/tui/screens/settings/settings.tcss +406 -0
  301. chrys/app/tui/screens/themes/__init__.py +8 -0
  302. chrys/app/tui/screens/themes/dialogs.py +330 -0
  303. chrys/app/tui/screens/themes/editor.py +632 -0
  304. chrys/app/tui/screens/themes/editor.tcss +268 -0
  305. chrys/app/tui/screens/themes/messages.py +95 -0
  306. chrys/app/tui/screens/themes/palette.py +694 -0
  307. chrys/app/tui/screens/themes/panel.py +113 -0
  308. chrys/app/tui/screens/themes/picker.py +203 -0
  309. chrys/app/tui/screens/themes/save.py +191 -0
  310. chrys/app/tui/support/__init__.py +3 -0
  311. chrys/app/tui/support/file_manager.py +64 -0
  312. chrys/app/tui/support/gc_freeze.py +1114 -0
  313. chrys/app/tui/support/path_shortcuts.py +50 -0
  314. chrys/app/tui/support/prompt_history.py +298 -0
  315. chrys/app/tui/support/workspace_mru.py +476 -0
  316. chrys/app/tui/terminal/__init__.py +6 -0
  317. chrys/app/tui/terminal/_pty_backend.py +114 -0
  318. chrys/app/tui/terminal/emulator/__init__.py +24 -0
  319. chrys/app/tui/terminal/emulator/buffer.py +456 -0
  320. chrys/app/tui/terminal/emulator/charsets.py +113 -0
  321. chrys/app/tui/terminal/emulator/core.py +963 -0
  322. chrys/app/tui/terminal/emulator/input.py +342 -0
  323. chrys/app/tui/terminal/emulator/parser.py +227 -0
  324. chrys/app/tui/terminal/emulator/pen.py +158 -0
  325. chrys/app/tui/terminal/launcher.py +455 -0
  326. chrys/app/tui/terminal/leaked_reports.py +44 -0
  327. chrys/app/tui/terminal/panel.py +323 -0
  328. chrys/app/tui/terminal/pty_process.py +405 -0
  329. chrys/app/tui/terminal/rendering.py +151 -0
  330. chrys/app/tui/terminal/shell.py +273 -0
  331. chrys/app/tui/terminal/shell_integration.py +199 -0
  332. chrys/app/tui/terminal/title.py +224 -0
  333. chrys/app/tui/terminal/widget.py +654 -0
  334. chrys/app/tui/theme.py +422 -0
  335. chrys/app/tui/theme_loader.py +201 -0
  336. chrys/app/tui/theme_stylesheet.py +49 -0
  337. chrys/app/tui/themes/__init__.py +3 -0
  338. chrys/app/tui/themes/document.py +120 -0
  339. chrys/app/tui/themes/preview.py +170 -0
  340. chrys/app/tui/themes/store.py +176 -0
  341. chrys/app/tui/toasts.py +69 -0
  342. chrys/app/tui/util/__init__.py +3 -0
  343. chrys/app/tui/util/context_pressure.py +146 -0
  344. chrys/app/tui/util/debug_log.py +292 -0
  345. chrys/app/tui/util/diff_entries.py +64 -0
  346. chrys/app/tui/util/formatting.py +53 -0
  347. chrys/app/tui/util/git_branch.py +326 -0
  348. chrys/app/tui/util/invocation_progress.py +57 -0
  349. chrys/app/tui/util/logo.py +32 -0
  350. chrys/app/tui/util/message_gate.py +35 -0
  351. chrys/app/tui/util/mutation_sources.py +14 -0
  352. chrys/app/tui/util/picker_text.py +27 -0
  353. chrys/app/tui/util/removal.py +47 -0
  354. chrys/app/tui/util/rich_style.py +36 -0
  355. chrys/app/tui/util/source_text.py +12 -0
  356. chrys/app/tui/util/static_update.py +88 -0
  357. chrys/app/tui/util/todo_format.py +51 -0
  358. chrys/app/tui/util/visibility.py +214 -0
  359. chrys/app/tui/widgets/__init__.py +60 -0
  360. chrys/app/tui/widgets/ask_user_controls.py +652 -0
  361. chrys/app/tui/widgets/ask_user_markdown.py +17 -0
  362. chrys/app/tui/widgets/ask_user_prompt.py +606 -0
  363. chrys/app/tui/widgets/buttons.py +52 -0
  364. chrys/app/tui/widgets/chat/__init__.py +3 -0
  365. chrys/app/tui/widgets/chat/agent_transcript_surface.py +818 -0
  366. chrys/app/tui/widgets/chat/chrome.py +296 -0
  367. chrys/app/tui/widgets/chat/compaction_card.py +353 -0
  368. chrys/app/tui/widgets/chat/context_fold.py +77 -0
  369. chrys/app/tui/widgets/chat/file_snapshot.py +91 -0
  370. chrys/app/tui/widgets/chat/image_preview.py +214 -0
  371. chrys/app/tui/widgets/chat/live_renderer.py +383 -0
  372. chrys/app/tui/widgets/chat/messages.py +1039 -0
  373. chrys/app/tui/widgets/chat/panel.py +1769 -0
  374. chrys/app/tui/widgets/chat/ports.py +245 -0
  375. chrys/app/tui/widgets/chat/renderers/__init__.py +7 -0
  376. chrys/app/tui/widgets/chat/renderers/ask_user.py +747 -0
  377. chrys/app/tui/widgets/chat/renderers/execute.py +431 -0
  378. chrys/app/tui/widgets/chat/renderers/file_edit.py +925 -0
  379. chrys/app/tui/widgets/chat/renderers/hosted_code.py +83 -0
  380. chrys/app/tui/widgets/chat/renderers/hosted_generic.py +330 -0
  381. chrys/app/tui/widgets/chat/renderers/hosted_image.py +107 -0
  382. chrys/app/tui/widgets/chat/renderers/hosted_mcp.py +43 -0
  383. chrys/app/tui/widgets/chat/renderers/hosted_search.py +167 -0
  384. chrys/app/tui/widgets/chat/renderers/hosted_shell.py +132 -0
  385. chrys/app/tui/widgets/chat/renderers/read_file.py +399 -0
  386. chrys/app/tui/widgets/chat/renderers/search.py +348 -0
  387. chrys/app/tui/widgets/chat/renderers/skill.py +564 -0
  388. chrys/app/tui/widgets/chat/renderers/sleep.py +287 -0
  389. chrys/app/tui/widgets/chat/renderers/sub_agent.py +1385 -0
  390. chrys/app/tui/widgets/chat/renderers/todo.py +114 -0
  391. chrys/app/tui/widgets/chat/renderers/web.py +69 -0
  392. chrys/app/tui/widgets/chat/replay.py +1605 -0
  393. chrys/app/tui/widgets/chat/replay_mount.py +322 -0
  394. chrys/app/tui/widgets/chat/scroll_controller.py +579 -0
  395. chrys/app/tui/widgets/chat/selection_controller.py +435 -0
  396. chrys/app/tui/widgets/chat/session_json.py +508 -0
  397. chrys/app/tui/widgets/chat/toc_model.py +156 -0
  398. chrys/app/tui/widgets/chat/tool_call.py +2478 -0
  399. chrys/app/tui/widgets/chat/tool_registry.py +753 -0
  400. chrys/app/tui/widgets/chat/tool_renderers.py +247 -0
  401. chrys/app/tui/widgets/chat/tool_view_builders.py +260 -0
  402. chrys/app/tui/widgets/chat/transcript_reader.py +57 -0
  403. chrys/app/tui/widgets/checkbox.py +24 -0
  404. chrys/app/tui/widgets/chrome/__init__.py +3 -0
  405. chrys/app/tui/widgets/chrome/app_header.py +283 -0
  406. chrys/app/tui/widgets/chrome/commands.py +95 -0
  407. chrys/app/tui/widgets/chrome/file_index.py +68 -0
  408. chrys/app/tui/widgets/chrome/file_scanner.py +670 -0
  409. chrys/app/tui/widgets/chrome/footer.py +284 -0
  410. chrys/app/tui/widgets/chrome/image_paste.py +351 -0
  411. chrys/app/tui/widgets/chrome/input_bar.py +1299 -0
  412. chrys/app/tui/widgets/chrome/status_bar.py +911 -0
  413. chrys/app/tui/widgets/chrome/suggestion_list.py +652 -0
  414. chrys/app/tui/widgets/click_affordance.py +24 -0
  415. chrys/app/tui/widgets/color_picker/__init__.py +254 -0
  416. chrys/app/tui/widgets/color_picker/controls.py +171 -0
  417. chrys/app/tui/widgets/color_picker/model.py +89 -0
  418. chrys/app/tui/widgets/dialog_buttons.py +58 -0
  419. chrys/app/tui/widgets/dialog_scroll.py +24 -0
  420. chrys/app/tui/widgets/diff_view/__init__.py +20 -0
  421. chrys/app/tui/widgets/diff_view/cells.py +59 -0
  422. chrys/app/tui/widgets/diff_view/code.py +280 -0
  423. chrys/app/tui/widgets/diff_view/compute.py +286 -0
  424. chrys/app/tui/widgets/diff_view/gutter.py +161 -0
  425. chrys/app/tui/widgets/diff_view/inline.py +77 -0
  426. chrys/app/tui/widgets/diff_view/palette.py +87 -0
  427. chrys/app/tui/widgets/diff_view/protocols.py +24 -0
  428. chrys/app/tui/widgets/diff_view/rows.py +138 -0
  429. chrys/app/tui/widgets/diff_view/unified.py +267 -0
  430. chrys/app/tui/widgets/diff_view/widget.py +209 -0
  431. chrys/app/tui/widgets/editor/__init__.py +79 -0
  432. chrys/app/tui/widgets/editor/coordinates.py +30 -0
  433. chrys/app/tui/widgets/editor/highlighter.py +139 -0
  434. chrys/app/tui/widgets/editor/keymaps.py +357 -0
  435. chrys/app/tui/widgets/editor/types.py +216 -0
  436. chrys/app/tui/widgets/editor/vim.py +1195 -0
  437. chrys/app/tui/widgets/editor/widget.py +401 -0
  438. chrys/app/tui/widgets/file_preview.py +58 -0
  439. chrys/app/tui/widgets/hatch.py +138 -0
  440. chrys/app/tui/widgets/input.py +66 -0
  441. chrys/app/tui/widgets/loading.py +125 -0
  442. chrys/app/tui/widgets/markdown/__init__.py +25 -0
  443. chrys/app/tui/widgets/markdown/_utils.py +21 -0
  444. chrys/app/tui/widgets/markdown/anchors.py +71 -0
  445. chrys/app/tui/widgets/markdown/blocks.py +115 -0
  446. chrys/app/tui/widgets/markdown/copyable.py +163 -0
  447. chrys/app/tui/widgets/markdown/diagram/__init__.py +35 -0
  448. chrys/app/tui/widgets/markdown/diagram/canvas.py +409 -0
  449. chrys/app/tui/widgets/markdown/diagram/charts.py +52 -0
  450. chrys/app/tui/widgets/markdown/diagram/diagnostics.py +101 -0
  451. chrys/app/tui/widgets/markdown/diagram/geometry.py +46 -0
  452. chrys/app/tui/widgets/markdown/diagram/layout.py +494 -0
  453. chrys/app/tui/widgets/markdown/diagram/messages.py +18 -0
  454. chrys/app/tui/widgets/markdown/diagram/model.py +275 -0
  455. chrys/app/tui/widgets/markdown/diagram/parser.py +173 -0
  456. chrys/app/tui/widgets/markdown/diagram/parsers/__init__.py +3 -0
  457. chrys/app/tui/widgets/markdown/diagram/parsers/charts.py +349 -0
  458. chrys/app/tui/widgets/markdown/diagram/parsers/common.py +403 -0
  459. chrys/app/tui/widgets/markdown/diagram/parsers/graphs.py +1072 -0
  460. chrys/app/tui/widgets/markdown/diagram/parsers/planning.py +317 -0
  461. chrys/app/tui/widgets/markdown/diagram/parsers/schedule.py +298 -0
  462. chrys/app/tui/widgets/markdown/diagram/parsers/schemas.py +241 -0
  463. chrys/app/tui/widgets/markdown/diagram/parsers/structure.py +221 -0
  464. chrys/app/tui/widgets/markdown/diagram/presentation.py +32 -0
  465. chrys/app/tui/widgets/markdown/diagram/renderers/__init__.py +3 -0
  466. chrys/app/tui/widgets/markdown/diagram/renderers/charts.py +275 -0
  467. chrys/app/tui/widgets/markdown/diagram/renderers/common.py +73 -0
  468. chrys/app/tui/widgets/markdown/diagram/renderers/planning.py +157 -0
  469. chrys/app/tui/widgets/markdown/diagram/renderers/schedule.py +202 -0
  470. chrys/app/tui/widgets/markdown/diagram/renderers/structure.py +77 -0
  471. chrys/app/tui/widgets/markdown/diagram/renderers/xy.py +385 -0
  472. chrys/app/tui/widgets/markdown/diagram/router.py +651 -0
  473. chrys/app/tui/widgets/markdown/diagram/specs/__init__.py +3 -0
  474. chrys/app/tui/widgets/markdown/diagram/specs/charts.py +94 -0
  475. chrys/app/tui/widgets/markdown/diagram/specs/planning.py +68 -0
  476. chrys/app/tui/widgets/markdown/diagram/specs/schedule.py +65 -0
  477. chrys/app/tui/widgets/markdown/diagram/specs/structure.py +23 -0
  478. chrys/app/tui/widgets/markdown/diagram/viewer.py +92 -0
  479. chrys/app/tui/widgets/markdown/links.py +47 -0
  480. chrys/app/tui/widgets/markdown/math/__init__.py +36 -0
  481. chrys/app/tui/widgets/markdown/math/layout.py +739 -0
  482. chrys/app/tui/widgets/markdown/math/markdown.py +291 -0
  483. chrys/app/tui/widgets/markdown/math/parser.py +325 -0
  484. chrys/app/tui/widgets/markdown/math/symbols.py +325 -0
  485. chrys/app/tui/widgets/markdown/navigator.py +80 -0
  486. chrys/app/tui/widgets/markdown/parser.py +1057 -0
  487. chrys/app/tui/widgets/markdown/stream.py +73 -0
  488. chrys/app/tui/widgets/markdown/toc.py +98 -0
  489. chrys/app/tui/widgets/markdown/viewer.py +146 -0
  490. chrys/app/tui/widgets/markdown/widget.py +2046 -0
  491. chrys/app/tui/widgets/marquee.py +101 -0
  492. chrys/app/tui/widgets/option_menu.py +82 -0
  493. chrys/app/tui/widgets/page_navigator.py +118 -0
  494. chrys/app/tui/widgets/select.py +196 -0
  495. chrys/app/tui/widgets/selection.py +59 -0
  496. chrys/app/tui/widgets/sidebar/__init__.py +3 -0
  497. chrys/app/tui/widgets/sidebar/buddy.py +367 -0
  498. chrys/app/tui/widgets/sidebar/context.py +482 -0
  499. chrys/app/tui/widgets/sidebar/debug.py +195 -0
  500. chrys/app/tui/widgets/sidebar/empty_state.py +11 -0
  501. chrys/app/tui/widgets/sidebar/panel.py +198 -0
  502. chrys/app/tui/widgets/sidebar/tasks.py +198 -0
  503. chrys/app/tui/widgets/sidebar/toc.py +166 -0
  504. chrys/app/tui/widgets/sidebar/toc_types.py +30 -0
  505. chrys/app/tui/widgets/syntax_theme.py +85 -0
  506. chrys/app/tui/widgets/text_area.py +258 -0
  507. chrys/app/tui/widgets/trajectory/__init__.py +8 -0
  508. chrys/app/tui/widgets/trajectory/chartkit.py +264 -0
  509. chrys/app/tui/widgets/trajectory/insights.py +1266 -0
  510. chrys/app/tui/widgets/trajectory/overview.py +1101 -0
  511. chrys/app/tui/widgets/trajectory/panel.py +983 -0
  512. chrys/app/tui/widgets/trajectory/presentation.py +311 -0
  513. chrys/app/tui/widgets/trajectory/session_info.py +124 -0
  514. chrys/app/tui/widgets/trajectory/text_view.py +124 -0
  515. chrys/app/tui/widgets/trajectory/timeline.py +380 -0
  516. chrys/app/tui/widgets/welcome.py +126 -0
  517. chrys/app/tui/widgets/workflow/__init__.py +5 -0
  518. chrys/app/tui/widgets/workflow/edges.py +89 -0
  519. chrys/app/tui/widgets/workflow/graph.py +869 -0
  520. chrys/app/tui/widgets/workflow/info.py +310 -0
  521. chrys/app/tui/widgets/workflow/node_view.py +27 -0
  522. chrys/app/tui/widgets/workflow/output.py +225 -0
  523. chrys/app/tui/widgets/workflow/panel.py +658 -0
  524. chrys/app/tui/widgets/workflow/projector.py +324 -0
  525. chrys/app/tui/widgets/workflow/records.py +69 -0
  526. chrys/app/tui/widgets/workflow/scrollbar.py +82 -0
  527. chrys/app/tui/widgets/workflow/selection.py +151 -0
  528. chrys/app/tui/widgets/workflow/source.py +65 -0
  529. chrys/app/tui/widgets/workflow/text.py +257 -0
  530. chrys/app/tui/widgets/workflow/values.py +317 -0
  531. chrys/foundation/__init__.py +0 -0
  532. chrys/foundation/branding.py +15 -0
  533. chrys/foundation/config/__init__.py +7 -0
  534. chrys/foundation/config/coercion.py +374 -0
  535. chrys/foundation/config/context.py +34 -0
  536. chrys/foundation/config/env_file.py +141 -0
  537. chrys/foundation/config/env_layers.py +370 -0
  538. chrys/foundation/config/migrations.py +521 -0
  539. chrys/foundation/config/notification_events.py +22 -0
  540. chrys/foundation/config/process_settings.py +332 -0
  541. chrys/foundation/config/runtime_pointer.py +133 -0
  542. chrys/foundation/config/settings.py +1952 -0
  543. chrys/foundation/config/settings_store.py +996 -0
  544. chrys/foundation/config/spec.py +396 -0
  545. chrys/foundation/config/user_settings.py +140 -0
  546. chrys/foundation/config/warnings.py +262 -0
  547. chrys/foundation/config/web_values.py +135 -0
  548. chrys/foundation/config/yaml_store.py +487 -0
  549. chrys/foundation/errors/__init__.py +68 -0
  550. chrys/foundation/errors/_legacy.py +117 -0
  551. chrys/foundation/errors/_text.py +13 -0
  552. chrys/foundation/errors/_walk.py +85 -0
  553. chrys/foundation/errors/classify.py +278 -0
  554. chrys/foundation/errors/display.py +274 -0
  555. chrys/foundation/errors/formatting.py +283 -0
  556. chrys/foundation/errors/kinds.py +67 -0
  557. chrys/foundation/errors/network.py +497 -0
  558. chrys/foundation/errors/provider.py +445 -0
  559. chrys/foundation/errors/route.py +85 -0
  560. chrys/foundation/events/__init__.py +8 -0
  561. chrys/foundation/events/bus.py +143 -0
  562. chrys/foundation/events/types.py +1629 -0
  563. chrys/foundation/events/workflow.py +38 -0
  564. chrys/foundation/hosted_tools.py +142 -0
  565. chrys/foundation/i18n/__init__.py +32 -0
  566. chrys/foundation/i18n/_catalogs/zh-Hans/LC_MESSAGES/chrys.mo +0 -0
  567. chrys/foundation/i18n/catalogs.py +42 -0
  568. chrys/foundation/i18n/formatting.py +202 -0
  569. chrys/foundation/i18n/locale.py +92 -0
  570. chrys/foundation/i18n/localizer.py +187 -0
  571. chrys/foundation/i18n/messages.py +268 -0
  572. chrys/foundation/io/__init__.py +3 -0
  573. chrys/foundation/io/result_content.py +237 -0
  574. chrys/foundation/models/__init__.py +3 -0
  575. chrys/foundation/models/approval_reuse.py +23 -0
  576. chrys/foundation/models/ask_user.py +468 -0
  577. chrys/foundation/models/execution.py +18 -0
  578. chrys/foundation/models/history_markers.py +115 -0
  579. chrys/foundation/models/invocations.py +63 -0
  580. chrys/foundation/models/mutation_scope.py +28 -0
  581. chrys/foundation/models/session_env.py +87 -0
  582. chrys/foundation/models/session_surface.py +29 -0
  583. chrys/foundation/models/todos.py +74 -0
  584. chrys/foundation/models/turns.py +125 -0
  585. chrys/foundation/models/workflow_session.py +89 -0
  586. chrys/foundation/models/workspace.py +59 -0
  587. chrys/foundation/net/__init__.py +3 -0
  588. chrys/foundation/net/address_class.py +57 -0
  589. chrys/foundation/net/route_probe.py +94 -0
  590. chrys/foundation/net/url.py +107 -0
  591. chrys/foundation/observability/__init__.py +3 -0
  592. chrys/foundation/observability/exporters.py +174 -0
  593. chrys/foundation/observability/gate.py +31 -0
  594. chrys/foundation/observability/setup.py +497 -0
  595. chrys/foundation/observability/sink.py +228 -0
  596. chrys/foundation/patches/__init__.py +19 -0
  597. chrys/foundation/patches/mdurl_cache.py +59 -0
  598. chrys/foundation/patches/patcher.py +287 -0
  599. chrys/foundation/patches/staged_members.py +205 -0
  600. chrys/foundation/patches/textual_block_border.py +105 -0
  601. chrys/foundation/patches/textual_button_ansi.py +65 -0
  602. chrys/foundation/patches/textual_button_ansi_old.txt +68 -0
  603. chrys/foundation/patches/textual_callback_cache.py +248 -0
  604. chrys/foundation/patches/textual_callback_dispatch.py +109 -0
  605. chrys/foundation/patches/textual_compositor_cjk.py +566 -0
  606. chrys/foundation/patches/textual_dispatch_cache.py +150 -0
  607. chrys/foundation/patches/textual_ime_cursor_anchor.py +92 -0
  608. chrys/foundation/patches/textual_kitty_keyboard.py +268 -0
  609. chrys/foundation/patches/textual_lru_acyclic.py +241 -0
  610. chrys/foundation/patches/textual_message_pump.py +136 -0
  611. chrys/foundation/patches/textual_node_diet.py +875 -0
  612. chrys/foundation/patches/textual_one_shot_timer.py +48 -0
  613. chrys/foundation/patches/textual_option_list.py +90 -0
  614. chrys/foundation/patches/textual_precompose.py +89 -0
  615. chrys/foundation/patches/textual_pruned_tabs.py +69 -0
  616. chrys/foundation/patches/textual_reflow_reuse.py +1753 -0
  617. chrys/foundation/patches/textual_removed_node_caches.py +86 -0
  618. chrys/foundation/patches/textual_removed_screen_callbacks.py +55 -0
  619. chrys/foundation/patches/textual_selection_extract.py +48 -0
  620. chrys/foundation/patches/textual_strip_cycles.py +119 -0
  621. chrys/foundation/patches/textual_tab_selection.py +1611 -0
  622. chrys/foundation/patches/textual_timer_skip.py +70 -0
  623. chrys/foundation/patches/textual_utf8_decoder.py +106 -0
  624. chrys/foundation/patches/textual_win_sleep.py +167 -0
  625. chrys/foundation/patches/watchdog_fsevents.py +70 -0
  626. chrys/foundation/patches/watchdog_windows.py +52 -0
  627. chrys/foundation/platform/__init__.py +556 -0
  628. chrys/foundation/platform/c_api.py +32 -0
  629. chrys/foundation/platform/child_reap.py +107 -0
  630. chrys/foundation/platform/command_line.py +95 -0
  631. chrys/foundation/platform/files.py +1470 -0
  632. chrys/foundation/platform/output_capture.py +113 -0
  633. chrys/foundation/platform/paths.py +193 -0
  634. chrys/foundation/platform/process.py +1468 -0
  635. chrys/foundation/platform/pty_output.py +19 -0
  636. chrys/foundation/platform/runtime_paths.py +281 -0
  637. chrys/foundation/platform/win_console.py +129 -0
  638. chrys/foundation/reasoning_origin.py +67 -0
  639. chrys/foundation/recovery.py +16 -0
  640. chrys/foundation/retry.py +278 -0
  641. chrys/foundation/text/__init__.py +3 -0
  642. chrys/foundation/text/encoding.py +603 -0
  643. chrys/foundation/text/images.py +304 -0
  644. chrys/foundation/text/lines.py +24 -0
  645. chrys/foundation/text/mentions.py +90 -0
  646. chrys/foundation/text/model_json.py +25 -0
  647. chrys/foundation/text/reading_order.py +20 -0
  648. chrys/foundation/text/tokenizer.py +25 -0
  649. chrys/foundation/text/tool_output.py +287 -0
  650. chrys/foundation/text/yaml_io.py +62 -0
  651. chrys/foundation/tool_call_context.py +202 -0
  652. chrys/foundation/tool_execution_stamp.py +184 -0
  653. chrys/foundation/tool_invocation_order.py +48 -0
  654. chrys/foundation/tool_kinds.py +102 -0
  655. chrys/foundation/tool_result_metadata.py +287 -0
  656. chrys/foundation/trajectory/__init__.py +10 -0
  657. chrys/foundation/trajectory/context.py +481 -0
  658. chrys/foundation/trajectory/envelope.py +571 -0
  659. chrys/foundation/trajectory/event_types.py +294 -0
  660. chrys/foundation/trajectory/finalizers.py +46 -0
  661. chrys/foundation/trajectory/fingerprint.py +62 -0
  662. chrys/foundation/trajectory/ids.py +155 -0
  663. chrys/foundation/trajectory/keys.py +99 -0
  664. chrys/foundation/trajectory/lease.py +173 -0
  665. chrys/foundation/trajectory/metadata.py +72 -0
  666. chrys/foundation/trajectory/reader.py +271 -0
  667. chrys/foundation/trajectory/recovery.py +225 -0
  668. chrys/foundation/trajectory/revisions.py +152 -0
  669. chrys/foundation/trajectory/segments.py +225 -0
  670. chrys/foundation/trajectory/tools.py +117 -0
  671. chrys/foundation/trajectory/usage.py +151 -0
  672. chrys/foundation/trajectory/writer.py +972 -0
  673. chrys/foundation/trajectory_timing.py +97 -0
  674. chrys/foundation/util/__init__.py +3 -0
  675. chrys/foundation/util/async_tasks.py +29 -0
  676. chrys/foundation/util/chrys_headers.py +23 -0
  677. chrys/foundation/util/env_templates.py +104 -0
  678. chrys/foundation/util/filenames.py +96 -0
  679. chrys/foundation/util/header_charset.py +138 -0
  680. chrys/foundation/util/httpx_helpers.py +12 -0
  681. chrys/foundation/util/lock.py +236 -0
  682. chrys/foundation/util/once_close.py +46 -0
  683. chrys/foundation/util/session_ids.py +29 -0
  684. chrys/foundation/util/shell_tokens.py +88 -0
  685. chrys/foundation/util/sub_agent_context.py +38 -0
  686. chrys/foundation/util/time.py +35 -0
  687. chrys/foundation/util/unicode_scalars.py +36 -0
  688. chrys/foundation/vendor/__init__.py +43 -0
  689. chrys/foundation/vendor/ripgrep/rg-aarch64-apple-darwin +0 -0
  690. chrys/foundation/vendor/ripgrep/rg-aarch64-pc-windows-msvc.exe +0 -0
  691. chrys/foundation/vendor/ripgrep/rg-aarch64-unknown-linux-gnu +0 -0
  692. chrys/foundation/vendor/ripgrep/rg-x86_64-apple-darwin +0 -0
  693. chrys/foundation/vendor/ripgrep/rg-x86_64-pc-windows-msvc.exe +0 -0
  694. chrys/foundation/vendor/ripgrep/rg-x86_64-unknown-linux-musl +0 -0
  695. chrys/kernel/__init__.py +256 -0
  696. chrys/kernel/_content.py +1866 -0
  697. chrys/kernel/_loop_recorder.py +631 -0
  698. chrys/kernel/_null_overlay.py +1026 -0
  699. chrys/kernel/_result_ceiling.py +216 -0
  700. chrys/kernel/_serialization.py +431 -0
  701. chrys/kernel/_tool_arg_errors.py +1629 -0
  702. chrys/kernel/_tool_execution.py +1172 -0
  703. chrys/kernel/_tool_expansion.py +26 -0
  704. chrys/kernel/_types.py +2598 -0
  705. chrys/kernel/agent.py +994 -0
  706. chrys/kernel/client.py +1084 -0
  707. chrys/kernel/compaction.py +1315 -0
  708. chrys/kernel/exceptions.py +211 -0
  709. chrys/kernel/exchanges.py +683 -0
  710. chrys/kernel/identity.py +95 -0
  711. chrys/kernel/images.py +36 -0
  712. chrys/kernel/instrumentation.py +1618 -0
  713. chrys/kernel/loop.py +2308 -0
  714. chrys/kernel/middleware.py +616 -0
  715. chrys/kernel/sessions.py +604 -0
  716. chrys/kernel/tools.py +1061 -0
  717. chrys/kernel/types.py +126 -0
  718. chrys/orchestration/__init__.py +3 -0
  719. chrys/orchestration/engine/__init__.py +3 -0
  720. chrys/orchestration/engine/assembly.py +236 -0
  721. chrys/orchestration/engine/build/__init__.py +3 -0
  722. chrys/orchestration/engine/build/builder.py +1061 -0
  723. chrys/orchestration/engine/build/construction.py +408 -0
  724. chrys/orchestration/engine/build/loaded.py +139 -0
  725. chrys/orchestration/engine/engine.py +789 -0
  726. chrys/orchestration/engine/execution.py +771 -0
  727. chrys/orchestration/engine/loader.py +865 -0
  728. chrys/orchestration/engine/rollback.py +918 -0
  729. chrys/orchestration/engine/run/__init__.py +3 -0
  730. chrys/orchestration/engine/run/active_injection.py +419 -0
  731. chrys/orchestration/engine/run/attachments.py +519 -0
  732. chrys/orchestration/engine/run/bindings.py +1095 -0
  733. chrys/orchestration/engine/run/coordinator.py +1062 -0
  734. chrys/orchestration/engine/run/finalizer.py +417 -0
  735. chrys/orchestration/engine/run/input_refs.py +60 -0
  736. chrys/orchestration/engine/run/prompt_content.py +457 -0
  737. chrys/orchestration/engine/run/resume.py +299 -0
  738. chrys/orchestration/engine/run/retry.py +948 -0
  739. chrys/orchestration/engine/run/runner.py +625 -0
  740. chrys/orchestration/engine/run/runtime_skills.py +146 -0
  741. chrys/orchestration/engine/run/sub_agent_coordination.py +126 -0
  742. chrys/orchestration/engine/run/turn_hooks.py +258 -0
  743. chrys/orchestration/engine/run/turn_state.py +149 -0
  744. chrys/orchestration/engine/run/working_dir.py +50 -0
  745. chrys/orchestration/engine/session_lifecycle.py +2013 -0
  746. chrys/orchestration/engine/state/__init__.py +3 -0
  747. chrys/orchestration/engine/state/active_session.py +183 -0
  748. chrys/orchestration/engine/state/controls.py +639 -0
  749. chrys/orchestration/engine/state/current_agent.py +26 -0
  750. chrys/orchestration/engine/state/lifecycle_permits.py +395 -0
  751. chrys/orchestration/engine/state/machine.py +193 -0
  752. chrys/orchestration/engine/state/session_writer.py +434 -0
  753. chrys/orchestration/engine/trajectory.py +714 -0
  754. chrys/orchestration/engine/usage.py +118 -0
  755. chrys/orchestration/invoker/__init__.py +3 -0
  756. chrys/orchestration/invoker/acp.py +727 -0
  757. chrys/orchestration/invoker/acp_protocol.py +1115 -0
  758. chrys/orchestration/invoker/acp_spec.py +79 -0
  759. chrys/orchestration/invoker/attempts.py +994 -0
  760. chrys/orchestration/invoker/child_compaction.py +131 -0
  761. chrys/orchestration/invoker/child_history.py +99 -0
  762. chrys/orchestration/invoker/contracts.py +253 -0
  763. chrys/orchestration/invoker/evidence.py +94 -0
  764. chrys/orchestration/invoker/kernel.py +343 -0
  765. chrys/orchestration/invoker/origin.py +79 -0
  766. chrys/orchestration/invoker/resources.py +343 -0
  767. chrys/orchestration/invoker/runtime.py +299 -0
  768. chrys/orchestration/invoker/usage.py +80 -0
  769. chrys/orchestration/session_hooks.py +117 -0
  770. chrys/orchestration/session_host.py +1023 -0
  771. chrys/orchestration/session_resources.py +95 -0
  772. chrys/orchestration/session_usage.py +219 -0
  773. chrys/orchestration/startup.py +255 -0
  774. chrys/orchestration/sub_agents/__init__.py +3 -0
  775. chrys/orchestration/sub_agents/acp_policy.py +269 -0
  776. chrys/orchestration/sub_agents/kernel_policy.py +848 -0
  777. chrys/orchestration/sub_agents/registration.py +44 -0
  778. chrys/orchestration/sub_agents/shell.py +337 -0
  779. chrys/orchestration/sub_agents/tools.py +2228 -0
  780. chrys/orchestration/workflows/__init__.py +3 -0
  781. chrys/orchestration/workflows/agent_archive.py +156 -0
  782. chrys/orchestration/workflows/agent_node.py +894 -0
  783. chrys/orchestration/workflows/agent_node_build.py +687 -0
  784. chrys/orchestration/workflows/catalog.py +332 -0
  785. chrys/orchestration/workflows/coordinator.py +771 -0
  786. chrys/orchestration/workflows/hooks.py +176 -0
  787. chrys/orchestration/workflows/preview.py +322 -0
  788. chrys/orchestration/workflows/rollback.py +151 -0
  789. chrys/orchestration/workflows/runner.py +986 -0
  790. chrys/orchestration/workflows/session.py +390 -0
  791. chrys/orchestration/workflows/settings.py +97 -0
  792. chrys/orchestration/workflows/validation.py +674 -0
  793. chrys/orchestration/workflows/worker_client.py +1002 -0
  794. chrys/service/__init__.py +3 -0
  795. chrys/service/acp_client/__init__.py +65 -0
  796. chrys/service/acp_client/_transport.py +922 -0
  797. chrys/service/acp_client/client.py +1311 -0
  798. chrys/service/acp_client/errors.py +224 -0
  799. chrys/service/acp_client/protocol.py +220 -0
  800. chrys/service/acp_client/spawn.py +239 -0
  801. chrys/service/acp_client/spec.py +100 -0
  802. chrys/service/agent_middleware/__init__.py +60 -0
  803. chrys/service/agent_middleware/_metadata_keys.py +52 -0
  804. chrys/service/agent_middleware/control/__init__.py +3 -0
  805. chrys/service/agent_middleware/control/approval.py +769 -0
  806. chrys/service/agent_middleware/control/approval_hooks.py +62 -0
  807. chrys/service/agent_middleware/control/ask_user.py +207 -0
  808. chrys/service/agent_middleware/control/interrupt.py +50 -0
  809. chrys/service/agent_middleware/control/sleep.py +118 -0
  810. chrys/service/agent_middleware/control/todo.py +117 -0
  811. chrys/service/agent_middleware/events/__init__.py +3 -0
  812. chrys/service/agent_middleware/events/hook_dispatch.py +250 -0
  813. chrys/service/agent_middleware/events/hosted_tools.py +1762 -0
  814. chrys/service/agent_middleware/events/intermediate_text.py +128 -0
  815. chrys/service/agent_middleware/events/rejection_metadata.py +50 -0
  816. chrys/service/agent_middleware/events/result_persistence.py +98 -0
  817. chrys/service/agent_middleware/events/sub_agent_events.py +951 -0
  818. chrys/service/agent_middleware/events/tool_error_text.py +22 -0
  819. chrys/service/agent_middleware/events/tool_events.py +779 -0
  820. chrys/service/agent_middleware/injection.py +510 -0
  821. chrys/service/agent_middleware/reminders/__init__.py +91 -0
  822. chrys/service/agent_middleware/reminders/archive_pointer.py +154 -0
  823. chrys/service/agent_middleware/reminders/base.py +25 -0
  824. chrys/service/agent_middleware/reminders/context_usage.py +74 -0
  825. chrys/service/agent_middleware/reminders/file_change.py +63 -0
  826. chrys/service/agent_middleware/reminders/mcp.py +39 -0
  827. chrys/service/agent_middleware/reminders/profile_switch.py +106 -0
  828. chrys/service/agent_middleware/reminders/runtime_env.py +190 -0
  829. chrys/service/agent_middleware/reminders/skills.py +44 -0
  830. chrys/service/agent_middleware/reminders/sub_agents.py +35 -0
  831. chrys/service/agent_middleware/reminders/todo.py +35 -0
  832. chrys/service/agent_middleware/reminders/turn_line.py +48 -0
  833. chrys/service/agent_middleware/response_validation.py +1380 -0
  834. chrys/service/agent_middleware/system_reminder.py +1107 -0
  835. chrys/service/agent_middleware/validators.py +396 -0
  836. chrys/service/analytics/__init__.py +124 -0
  837. chrys/service/analytics/_actions.py +896 -0
  838. chrys/service/analytics/_context_evidence.py +357 -0
  839. chrys/service/analytics/_critical_path.py +179 -0
  840. chrys/service/analytics/_facts.py +919 -0
  841. chrys/service/analytics/_insights.py +769 -0
  842. chrys/service/analytics/_metric_ops.py +83 -0
  843. chrys/service/analytics/_session_projection.py +460 -0
  844. chrys/service/analytics/_timeline.py +434 -0
  845. chrys/service/analytics/_turn_graph.py +1237 -0
  846. chrys/service/analytics/_turns.py +1377 -0
  847. chrys/service/analytics/_workflows.py +171 -0
  848. chrys/service/analytics/aggregation.py +1474 -0
  849. chrys/service/analytics/classification.py +128 -0
  850. chrys/service/analytics/export.py +803 -0
  851. chrys/service/analytics/findings.py +282 -0
  852. chrys/service/analytics/math.py +79 -0
  853. chrys/service/analytics/model.py +886 -0
  854. chrys/service/analytics/reader.py +321 -0
  855. chrys/service/approval/__init__.py +3 -0
  856. chrys/service/approval/arbitration.py +102 -0
  857. chrys/service/approval/command_identity.py +109 -0
  858. chrys/service/approval/correlation.py +57 -0
  859. chrys/service/approval/grant_store.py +108 -0
  860. chrys/service/approval/judge.py +589 -0
  861. chrys/service/approval/level.py +15 -0
  862. chrys/service/approval/policy.py +209 -0
  863. chrys/service/approval/reuse.py +223 -0
  864. chrys/service/approval/reuse_binding.py +133 -0
  865. chrys/service/approval/safety_classifier.py +536 -0
  866. chrys/service/approval/turn_context.py +49 -0
  867. chrys/service/context/__init__.py +3 -0
  868. chrys/service/context/compaction/__init__.py +38 -0
  869. chrys/service/context/compaction/breaker.py +156 -0
  870. chrys/service/context/compaction/budgets.py +93 -0
  871. chrys/service/context/compaction/calibration.py +77 -0
  872. chrys/service/context/compaction/compression.py +690 -0
  873. chrys/service/context/compaction/current_turn_drop.py +458 -0
  874. chrys/service/context/compaction/events.py +201 -0
  875. chrys/service/context/compaction/exclusions.py +261 -0
  876. chrys/service/context/compaction/groups.py +361 -0
  877. chrys/service/context/compaction/last_words.py +1996 -0
  878. chrys/service/context/compaction/last_words_state.py +752 -0
  879. chrys/service/context/compaction/scoped.py +472 -0
  880. chrys/service/context/compaction/spill.py +1947 -0
  881. chrys/service/context/compaction/strategy.py +1116 -0
  882. chrys/service/context/compaction/summaries.py +319 -0
  883. chrys/service/context/compaction/turns.py +375 -0
  884. chrys/service/context/manager.py +174 -0
  885. chrys/service/context/memory_loader.py +792 -0
  886. chrys/service/context/middleware/__init__.py +3 -0
  887. chrys/service/context/middleware/usage.py +332 -0
  888. chrys/service/context/providers/__init__.py +3 -0
  889. chrys/service/context/providers/context_management.py +432 -0
  890. chrys/service/context/providers/history.py +647 -0
  891. chrys/service/context/providers/memory.py +50 -0
  892. chrys/service/context/tool_content.py +22 -0
  893. chrys/service/hooks/__init__.py +37 -0
  894. chrys/service/hooks/detached_worker.py +118 -0
  895. chrys/service/hooks/docs/README.md +14 -0
  896. chrys/service/hooks/docs/authoring.md +684 -0
  897. chrys/service/hooks/docs/configuration.md +499 -0
  898. chrys/service/hooks/docs/design.md +359 -0
  899. chrys/service/hooks/docs/project-hooks.md +348 -0
  900. chrys/service/hooks/events.py +157 -0
  901. chrys/service/hooks/loader.py +665 -0
  902. chrys/service/hooks/manager.py +807 -0
  903. chrys/service/hooks/matcher.py +111 -0
  904. chrys/service/hooks/outbox.py +294 -0
  905. chrys/service/hooks/runner.py +572 -0
  906. chrys/service/hooks/schema.py +407 -0
  907. chrys/service/llm/__init__.py +7 -0
  908. chrys/service/llm/_structured_outputs.py +192 -0
  909. chrys/service/llm/anthropic_messages/__init__.py +9 -0
  910. chrys/service/llm/anthropic_messages/client.py +254 -0
  911. chrys/service/llm/anthropic_messages/decode.py +331 -0
  912. chrys/service/llm/anthropic_messages/history.py +279 -0
  913. chrys/service/llm/anthropic_messages/request.py +323 -0
  914. chrys/service/llm/anthropic_messages/server_tools.py +319 -0
  915. chrys/service/llm/anthropic_messages/stream.py +297 -0
  916. chrys/service/llm/anthropic_messages/thinking_binding.py +156 -0
  917. chrys/service/llm/chat_completions/__init__.py +9 -0
  918. chrys/service/llm/chat_completions/client.py +369 -0
  919. chrys/service/llm/chat_completions/decode.py +338 -0
  920. chrys/service/llm/chat_completions/history.py +657 -0
  921. chrys/service/llm/chat_completions/reasoning.py +244 -0
  922. chrys/service/llm/chat_completions/request.py +192 -0
  923. chrys/service/llm/chat_completions/stream.py +546 -0
  924. chrys/service/llm/chat_completions/validation.py +308 -0
  925. chrys/service/llm/clients.py +613 -0
  926. chrys/service/llm/images.py +65 -0
  927. chrys/service/llm/mock.py +413 -0
  928. chrys/service/llm/observer.py +446 -0
  929. chrys/service/llm/one_shot.py +106 -0
  930. chrys/service/llm/openai_exceptions.py +93 -0
  931. chrys/service/llm/openai_responses/__init__.py +10 -0
  932. chrys/service/llm/openai_responses/client.py +323 -0
  933. chrys/service/llm/openai_responses/decode.py +432 -0
  934. chrys/service/llm/openai_responses/history.py +519 -0
  935. chrys/service/llm/openai_responses/hosted.py +625 -0
  936. chrys/service/llm/openai_responses/replay.py +629 -0
  937. chrys/service/llm/openai_responses/request.py +342 -0
  938. chrys/service/llm/openai_responses/stream.py +847 -0
  939. chrys/service/llm/openai_timestamps.py +57 -0
  940. chrys/service/llm/providers.py +107 -0
  941. chrys/service/llm/proxy_route.py +54 -0
  942. chrys/service/llm/raw_http_log.py +309 -0
  943. chrys/service/llm/route_facts.py +63 -0
  944. chrys/service/llm/route_sessions.py +49 -0
  945. chrys/service/llm/sdk_anthropic.py +18 -0
  946. chrys/service/llm/sdk_headers.py +47 -0
  947. chrys/service/llm/sdk_openai.py +18 -0
  948. chrys/service/llm/sdk_retry.py +59 -0
  949. chrys/service/llm/wire_client.py +246 -0
  950. chrys/service/mcp/__init__.py +3 -0
  951. chrys/service/mcp/_connection.py +235 -0
  952. chrys/service/mcp/_http_transport.py +350 -0
  953. chrys/service/mcp/_progressive.py +459 -0
  954. chrys/service/mcp/_stdio_transport.py +614 -0
  955. chrys/service/mcp/_tool_mixins.py +96 -0
  956. chrys/service/mcp/adapter.py +1031 -0
  957. chrys/service/mcp/cache.py +476 -0
  958. chrys/service/mcp/content.py +32 -0
  959. chrys/service/mcp/errors.py +169 -0
  960. chrys/service/mcp/owned.py +1330 -0
  961. chrys/service/mcp/result_limits.py +168 -0
  962. chrys/service/mcp/thinking_warning.py +95 -0
  963. chrys/service/mcp/validation.py +74 -0
  964. chrys/service/mutations/__init__.py +84 -0
  965. chrys/service/mutations/coordination.py +894 -0
  966. chrys/service/mutations/detector.py +458 -0
  967. chrys/service/mutations/git_calibrator.py +397 -0
  968. chrys/service/mutations/git_state.py +647 -0
  969. chrys/service/mutations/pipeline.py +779 -0
  970. chrys/service/mutations/scanner.py +466 -0
  971. chrys/service/mutations/snapshot_files.py +233 -0
  972. chrys/service/mutations/store.py +625 -0
  973. chrys/service/mutations/tool_names.py +15 -0
  974. chrys/service/mutations/trace.py +246 -0
  975. chrys/service/mutations/tracker.py +1289 -0
  976. chrys/service/mutations/types.py +559 -0
  977. chrys/service/mutations/workspace_changes.py +1583 -0
  978. chrys/service/profiles/__init__.py +2 -0
  979. chrys/service/profiles/agents/__init__.py +3 -0
  980. chrys/service/profiles/agents/builtins/Code.yaml +199 -0
  981. chrys/service/profiles/agents/builtins/Explore.yaml +62 -0
  982. chrys/service/profiles/agents/builtins/General.yaml +114 -0
  983. chrys/service/profiles/agents/builtins/QA.yaml +128 -0
  984. chrys/service/profiles/agents/loader.py +808 -0
  985. chrys/service/profiles/agents/registry.py +727 -0
  986. chrys/service/profiles/agents/schema.py +371 -0
  987. chrys/service/profiles/agents/serializer.py +196 -0
  988. chrys/service/profiles/coercion.py +22 -0
  989. chrys/service/profiles/models/__init__.py +3 -0
  990. chrys/service/profiles/models/env_bridge.py +157 -0
  991. chrys/service/profiles/models/loader.py +282 -0
  992. chrys/service/profiles/models/options.py +438 -0
  993. chrys/service/profiles/models/registry.py +104 -0
  994. chrys/service/profiles/models/resolver.py +286 -0
  995. chrys/service/profiles/models/schema.py +91 -0
  996. chrys/service/profiles/models/serializer.py +76 -0
  997. chrys/service/session/__init__.py +3 -0
  998. chrys/service/session/checkpoint.py +156 -0
  999. chrys/service/session/history.py +1616 -0
  1000. chrys/service/session/message_metadata.py +151 -0
  1001. chrys/service/session/persistence.py +435 -0
  1002. chrys/service/session/runtime_metadata.py +210 -0
  1003. chrys/service/session/sub_agent_logs.py +1069 -0
  1004. chrys/service/session/sub_agent_transcript.py +654 -0
  1005. chrys/service/skills/__init__.py +3 -0
  1006. chrys/service/skills/adapter.py +408 -0
  1007. chrys/service/skills/constants.py +8 -0
  1008. chrys/service/skills/loader.py +575 -0
  1009. chrys/service/skills/model.py +130 -0
  1010. chrys/service/skills/provider.py +841 -0
  1011. chrys/service/skills/runner.py +390 -0
  1012. chrys/service/state/__init__.py +3 -0
  1013. chrys/service/state/_fork.py +710 -0
  1014. chrys/service/state/_session_files.py +175 -0
  1015. chrys/service/state/_session_meta.py +552 -0
  1016. chrys/service/state/locks.py +154 -0
  1017. chrys/service/state/serializers.py +156 -0
  1018. chrys/service/state/session_catalog.py +361 -0
  1019. chrys/service/state/session_listing.py +84 -0
  1020. chrys/service/state/session_migration.py +446 -0
  1021. chrys/service/state/session_mru.py +315 -0
  1022. chrys/service/state/store.py +2132 -0
  1023. chrys/service/state/workflow.py +158 -0
  1024. chrys/service/text_blocks.py +47 -0
  1025. chrys/service/todos/__init__.py +3 -0
  1026. chrys/service/todos/reminder.py +31 -0
  1027. chrys/service/todos/tracker.py +58 -0
  1028. chrys/service/tools/__init__.py +3 -0
  1029. chrys/service/tools/approval_targets.py +92 -0
  1030. chrys/service/tools/builtins/__init__.py +3 -0
  1031. chrys/service/tools/builtins/ask_user.py +326 -0
  1032. chrys/service/tools/builtins/doc_converter/__init__.py +494 -0
  1033. chrys/service/tools/builtins/doc_converter/artifacts.py +392 -0
  1034. chrys/service/tools/builtins/doc_converter/parsers/__init__.py +3 -0
  1035. chrys/service/tools/builtins/doc_converter/parsers/_table.py +34 -0
  1036. chrys/service/tools/builtins/doc_converter/parsers/base.py +101 -0
  1037. chrys/service/tools/builtins/doc_converter/parsers/docx.py +88 -0
  1038. chrys/service/tools/builtins/doc_converter/parsers/pdf.py +144 -0
  1039. chrys/service/tools/builtins/doc_converter/parsers/pptx.py +103 -0
  1040. chrys/service/tools/builtins/doc_converter/parsers/xls.py +32 -0
  1041. chrys/service/tools/builtins/doc_converter/parsers/xlsx.py +51 -0
  1042. chrys/service/tools/builtins/doc_converter/registry.py +55 -0
  1043. chrys/service/tools/builtins/filesystem.py +1071 -0
  1044. chrys/service/tools/builtins/search.py +1265 -0
  1045. chrys/service/tools/builtins/shell.py +967 -0
  1046. chrys/service/tools/builtins/shell_command_filtering/__init__.py +5 -0
  1047. chrys/service/tools/builtins/shell_command_filtering/policy.py +60 -0
  1048. chrys/service/tools/builtins/shell_command_filtering/presets.py +152 -0
  1049. chrys/service/tools/builtins/shell_command_filtering/readonly/__init__.py +358 -0
  1050. chrys/service/tools/builtins/shell_command_filtering/readonly/commands.py +416 -0
  1051. chrys/service/tools/builtins/shell_command_filtering/readonly/git.py +408 -0
  1052. chrys/service/tools/builtins/shell_command_filtering/readonly/syntax.py +87 -0
  1053. chrys/service/tools/builtins/shell_command_filtering/tokenizer.py +214 -0
  1054. chrys/service/tools/builtins/shell_filter.py +24 -0
  1055. chrys/service/tools/builtins/sleep.py +91 -0
  1056. chrys/service/tools/builtins/todo.py +64 -0
  1057. chrys/service/tools/builtins/web/__init__.py +6 -0
  1058. chrys/service/tools/builtins/web/build.py +268 -0
  1059. chrys/service/tools/builtins/web/config.py +338 -0
  1060. chrys/service/tools/builtins/web/credentials.py +37 -0
  1061. chrys/service/tools/builtins/web/fetch/__init__.py +3 -0
  1062. chrys/service/tools/builtins/web/fetch/cache.py +68 -0
  1063. chrys/service/tools/builtins/web/fetch/convert.py +56 -0
  1064. chrys/service/tools/builtins/web/fetch/tool.py +223 -0
  1065. chrys/service/tools/builtins/web/hosted.py +61 -0
  1066. chrys/service/tools/builtins/web/http.py +330 -0
  1067. chrys/service/tools/builtins/web/progress.py +18 -0
  1068. chrys/service/tools/builtins/web/search/__init__.py +3 -0
  1069. chrys/service/tools/builtins/web/search/exa_mcp.py +146 -0
  1070. chrys/service/tools/builtins/web/search/html.py +152 -0
  1071. chrys/service/tools/builtins/web/search/json_api.py +227 -0
  1072. chrys/service/tools/builtins/web/search/providers.py +77 -0
  1073. chrys/service/tools/builtins/web/search/tool.py +218 -0
  1074. chrys/service/tools/builtins/web/search/types.py +28 -0
  1075. chrys/service/tools/builtins/web/tls_identity.py +66 -0
  1076. chrys/service/tools/kinds.py +115 -0
  1077. chrys/service/tools/names.py +47 -0
  1078. chrys/service/tools/registry.py +242 -0
  1079. chrys/service/tools/result_metadata.py +103 -0
  1080. chrys/service/tools/session_artifacts.py +196 -0
  1081. chrys/service/tools/spill.py +218 -0
  1082. chrys/service/tools/workspace_paths.py +81 -0
  1083. chrys/service/trajectory/__init__.py +11 -0
  1084. chrys/service/trajectory/approvals.py +201 -0
  1085. chrys/service/trajectory/compaction.py +420 -0
  1086. chrys/service/trajectory/fork.py +75 -0
  1087. chrys/service/trajectory/hooks.py +217 -0
  1088. chrys/service/trajectory/items.py +41 -0
  1089. chrys/service/trajectory/preparation.py +374 -0
  1090. chrys/service/trajectory/retries.py +95 -0
  1091. chrys/service/trajectory/revisions.py +129 -0
  1092. chrys/service/trajectory/segmented.py +163 -0
  1093. chrys/service/trajectory/session.py +762 -0
  1094. chrys/service/trajectory/state.py +83 -0
  1095. chrys/service/trajectory/sub_agents.py +169 -0
  1096. chrys/service/trajectory/tombstone.py +262 -0
  1097. chrys/service/trajectory/tools.py +323 -0
  1098. chrys/service/trajectory/validation.py +130 -0
  1099. chrys/service/trajectory/waits.py +161 -0
  1100. chrys/service/trajectory/workflow.py +148 -0
  1101. chrys/service/vision.py +128 -0
  1102. chrys/service/workflows/__init__.py +3 -0
  1103. chrys/service/workflows/admission.py +151 -0
  1104. chrys/service/workflows/artifacts.py +109 -0
  1105. chrys/service/workflows/asks.py +130 -0
  1106. chrys/service/workflows/builtins/__init__.py +12 -0
  1107. chrys/service/workflows/builtins/demo-workflow.manifest.json +635 -0
  1108. chrys/service/workflows/builtins/demo-workflow.py +756 -0
  1109. chrys/service/workflows/discovery.py +554 -0
  1110. chrys/service/workflows/environment.py +284 -0
  1111. chrys/service/workflows/graph.py +430 -0
  1112. chrys/service/workflows/history.py +82 -0
  1113. chrys/service/workflows/interpreter.py +107 -0
  1114. chrys/service/workflows/journal.py +334 -0
  1115. chrys/service/workflows/layout.py +56 -0
  1116. chrys/service/workflows/ledger.py +149 -0
  1117. chrys/service/workflows/model_selection.py +18 -0
  1118. chrys/service/workflows/orphans.py +209 -0
  1119. chrys/service/workflows/outcomes.py +46 -0
  1120. chrys/service/workflows/protocol.py +210 -0
  1121. chrys/service/workflows/records.py +142 -0
  1122. chrys/service/workflows/scheduler.py +770 -0
  1123. chrys/service/workflows/sdk/__init__.py +51 -0
  1124. chrys/service/workflows/sdk/_ask.py +167 -0
  1125. chrys/service/workflows/sdk/_builder.py +781 -0
  1126. chrys/service/workflows/sdk/_values.py +92 -0
  1127. chrys/service/workflows/sdk_artifact.py +74 -0
  1128. chrys/service/workflows/store.py +494 -0
  1129. chrys/service/workflows/transcript.py +102 -0
  1130. chrys/service/workflows/values.py +73 -0
  1131. chrys/service/workflows/worker_host.py +1820 -0
  1132. chrys/workflows.py +38 -0
  1133. icode_tui-0.29.1.dist-info/METADATA +104 -0
  1134. icode_tui-0.29.1.dist-info/RECORD +1138 -0
  1135. icode_tui-0.29.1.dist-info/WHEEL +4 -0
  1136. icode_tui-0.29.1.dist-info/entry_points.txt +2 -0
  1137. icode_tui-0.29.1.dist-info/licenses/LICENSE +202 -0
  1138. icode_tui-0.29.1.dist-info/licenses/NOTICE +1955 -0
@@ -0,0 +1,1753 @@
1
+ # Copyright (c) 2021 Will McGugan
2
+ # Copyright (c) Huawei Technologies Co., Ltd. 2026. All rights reserved.
3
+ # Contains code adapted from Textual (MIT License; see NOTICE).
4
+
5
+ """Patch: a Textual reflow replays the arrangement of every subtree that has not changed.
6
+
7
+ Problem
8
+ -------
9
+ Each layout refresh (``Screen._refresh_layout`` → ``Compositor.reflow``) arranges the whole
10
+ widget tree from scratch: box models, placements and map entries for every mounted transcript
11
+ widget, and then compares every widget's old and new geometry. A streaming reply, a sidebar tab
12
+ switch or a panel toggle changes a handful of widgets, yet each frame's layout costs time and
13
+ garbage in proportion to the mounted transcript.
14
+
15
+ Solution
16
+ --------
17
+ Nodes carry the layout epoch in which they, or a node beneath them, last changed in a way the
18
+ compositor reads (``DOMNode._mark_layout_dirty``): a mount, a child-list update, any
19
+ ``refresh()``, a layout or scroll change, an absolute offset, anchoring, a raw style-rule
20
+ write (``Styles.set_rule``/``clear_rule``, which chrome widgets use to skip layout escalation)
21
+ or an anchored widget's container-size change. The compositor pins an anchored widget from its
22
+ container size, which ``_size_updated`` stores only after the arrangement that pinned it, and
23
+ nothing else stamps the widget when its scrollbar is hidden. A reactive that refreshes stamps
24
+ before its watchers run rather than at that refresh: a watcher may look up geometry, which can
25
+ rebuild the full map (``TextArea`` sizes its wrapping when its scrollbar shows).
26
+ A full arrangement records what arranging each widget produced, and the next reflow replays the
27
+ record of every subtree unchanged since then, translating it when only its origin moved.
28
+ ``Widget.layers`` takes the outermost ``layers`` declaration among a widget and its ancestors, so
29
+ a declaration above a subtree restacks it without stamping it: a record also keeps the
30
+ declaration its widget inherits, ``None`` when none does, which an explicit ``default`` must not
31
+ match.
32
+ Replayed placements are the recorded objects, so the reflow diff settles most widgets by
33
+ identity.
34
+
35
+ Only a reflow from ``Screen._refresh_layout``, ``full_map`` or ``reflow_reusing_records`` reuses
36
+ records. The last serves code that writes raw style rules outside a layout pass (chrome visibility
37
+ and width flips) and then resynchronizes the compositor: the rule writes stamp their widgets, so
38
+ only those widgets' ancestor paths are arranged again. ``_arrange_root`` keeps its signature and
39
+ takes the choice from ``_reuse_next_arrangement``. As upstream's, a reflow leaves a pending
40
+ full-map rebuild pending: a repaint outside the visible map requests one, and a widget shown after
41
+ the reflow then reports the region its next layout gives it. That rebuild must not decide which
42
+ widgets are new, though: a reflow reports Show against the map the previous reflow produced, so a
43
+ widget shown in between still gets Show and its first Resize after a lookup mapped it. Tests set
44
+ ``textual._compositor._VERIFY_REUSE`` to check every reusing arrangement against a from-scratch
45
+ one that starts from the same scroll and anchor state: anchoring and ``arrange`` overrides such
46
+ as the chat panel's write that state, so arranging twice in a row can differ. The check compares
47
+ the state each leaves as well as the maps, and restores the layout epochs the from-scratch
48
+ arrangement's watchers stamp, which would otherwise hide a change that was not stamped.
49
+ ``Widget._set_dirty`` also stops marking the lines of a styles cache it has just cleared:
50
+ reading ``self.size`` for that could rebuild the full map.
51
+
52
+ The patch is runtime only. Its fragments span nine modules, and file patches apply all or
53
+ nothing per file: after a Textual change a compositor could be rewritten to reuse records while
54
+ another module kept the upstream code that does not stamp, painting stale placements from then
55
+ on. The runtime install stages every module before installing any, and must precede the first
56
+ widget: ``absolute_offset``, ``_anchored`` and ``_anchor_released`` become properties, which
57
+ hide the values an existing instance keeps in its ``__dict__``.
58
+ """
59
+
60
+ from __future__ import annotations
61
+
62
+ import logging
63
+ from typing import TYPE_CHECKING, Protocol, TypeGuard
64
+
65
+ from chrys.foundation.patches.patcher import FilePatch
66
+ from chrys.foundation.patches.staged_members import StagedSourceDriftError, members_installed, stage_members
67
+
68
+ if TYPE_CHECKING:
69
+ from textual._compositor import Compositor, ReflowResult
70
+ from textual.geometry import Size
71
+ from textual.widget import Widget
72
+
73
+ _RUNTIME_PATCH_MARKER = "_chrys_reflow_reuse"
74
+ _RUNTIME_PATCH_TEXTUAL_VERSION = "8.2.7"
75
+ _RUNTIME_MEMBERS: dict[str, dict[str, tuple[str, ...]]] = {
76
+ "textual._compositor": {
77
+ "Compositor": (
78
+ "__init__",
79
+ "clear",
80
+ "reflow",
81
+ "full_map",
82
+ "_arrange_root",
83
+ "_arrange",
84
+ "_arrangement_state",
85
+ "_restore_arrangement_state",
86
+ "_verify_reuse",
87
+ ),
88
+ },
89
+ "textual.css.styles": {"Styles": ("clear_rule", "set_rule")},
90
+ "textual._node_list": {"NodeList": ("updated",)},
91
+ "textual.dom": {"DOMNode": ("_layout_epoch", "_layout_dirty_epoch", "_mark_layout_dirty", "_on_mounted_state")},
92
+ "textual.message_pump": {"MessagePump": ("_pre_process", "_on_mounted_state", "_process_messages_loop")},
93
+ "textual.reactive": {"Reactive": ("_set",)},
94
+ "textual.screen": {"Screen": ("_refresh_layout",)},
95
+ "textual.scroll_view": {"ScrollView": ("_size_updated",)},
96
+ "textual.widget": {
97
+ "Widget": (
98
+ "_absolute_offset",
99
+ "_anchored_flag",
100
+ "_anchor_released_flag",
101
+ "absolute_offset",
102
+ "_anchored",
103
+ "_anchor_released",
104
+ "_set_dirty",
105
+ "set_scroll",
106
+ "_refresh_scroll",
107
+ "refresh",
108
+ "_check_refresh",
109
+ "_size_updated",
110
+ ),
111
+ },
112
+ }
113
+ logger = logging.getLogger(__name__)
114
+
115
+ # _compositor.py: <import>
116
+ _REFLOW_1_OLD = """\
117
+ from textual._context import visible_screen_stack
118
+ from textual._loop import loop_last
119
+ from textual.geometry import NULL_SPACING, Offset, Region, Size, Spacing
120
+ from textual.map_geometry import MapGeometry"""
121
+
122
+ _REFLOW_1_NEW = """\
123
+ from textual._context import visible_screen_stack
124
+ from textual._loop import loop_last
125
+ from textual.dom import DOMNode
126
+ from textual.geometry import NULL_SPACING, Offset, Region, Size, Spacing
127
+ from textual.map_geometry import MapGeometry"""
128
+
129
+ # _compositor.py: <if>, _ArrangeRecord=, _VERIFY_REUSE=
130
+ _REFLOW_2_OLD = """\
131
+ from typing_extensions import TypeAlias
132
+
133
+ from textual.screen import Screen
134
+
135
+ """
136
+
137
+ _REFLOW_2_NEW = """\
138
+ from typing_extensions import TypeAlias
139
+
140
+ from textual.layout import WidgetPlacement
141
+ from textual.screen import Screen
142
+
143
+
144
+ _VERIFY_REUSE = False
145
+ \"\"\"Check every reusing arrangement against a from-scratch one (a debug aid for tests).\"\"\"
146
+
147
+ _ArrangeRecord: TypeAlias = "tuple[int, tuple[Region, Region, tuple[tuple[int, int, int], ...], int, Region, bool, Spacing, tuple[str, ...] | None], bool, set[Widget] | None, tuple[Widget, MapGeometry] | None, list[Widget], list[tuple[Widget, MapGeometry]], Region | None, frozenset[Widget], bool]"
148
+ \"\"\"What arranging one widget produced: (epoch, arguments, visible, arranged widgets, cover entry, placed children,
149
+ map entries, children's clipping region, overlay children, whether the subtree may be translated).\"\"\"
150
+
151
+ """
152
+
153
+ # _compositor.py: Compositor.__init__, Compositor.clear
154
+ _REFLOW_3_OLD = """\
155
+ self._layers_visible: list[list[tuple[Widget, Region, Region]]] | None = None
156
+
157
+ def clear(self) -> None:
158
+ \"\"\"Remove all references to widgets (used when the screen closes).\"\"\""""
159
+
160
+ _REFLOW_3_NEW = """\
161
+ self._layers_visible: list[list[tuple[Widget, Region, Region]]] | None = None
162
+
163
+ # What the last full arrangement produced per widget, for replaying unchanged subtrees
164
+ self._arrange_records: dict[Widget, _ArrangeRecord] = {}
165
+ self._arrange_records_size: Size | None = None
166
+ self._arrange_records_root: Widget | None = None
167
+ # Set by a caller for the next `_arrange_root` call, which consumes it
168
+ self._reuse_next_arrangement = False
169
+ # The map the last reflow produced, which Show is reported against
170
+ self._reflowed_map: CompositorMap = {}
171
+
172
+ def clear(self) -> None:
173
+ \"\"\"Remove all references to widgets (used when the screen closes).\"\"\""""
174
+
175
+ # _compositor.py: Compositor._regions_to_spans, Compositor.clear
176
+ _REFLOW_4_OLD = """\
177
+ self._visible_widgets = None
178
+ self._layers_visible = None
179
+
180
+ @classmethod"""
181
+
182
+ _REFLOW_4_NEW = """\
183
+ self._visible_widgets = None
184
+ self._layers_visible = None
185
+ self._arrange_records = {}
186
+ self._arrange_records_root = None
187
+ self._reflowed_map = {}
188
+
189
+ @classmethod"""
190
+
191
+ # _compositor.py: Compositor.__rich_repr__, Compositor.reflow
192
+ _REFLOW_5_OLD = """\
193
+ yield "widgets", self.widgets
194
+
195
+ def reflow(self, parent: Widget, size: Size) -> ReflowResult:
196
+ \"\"\"Reflow (layout) widget and its children.
197
+ """
198
+
199
+ _REFLOW_5_NEW = """\
200
+ yield "widgets", self.widgets
201
+
202
+ def reflow(self, parent: Widget, size: Size, reuse: bool = False) -> ReflowResult:
203
+ \"\"\"Reflow (layout) widget and its children.
204
+ """
205
+
206
+ # _compositor.py: Compositor.reflow
207
+ _REFLOW_6_OLD = """\
208
+ Args:
209
+ parent: The root widget.
210
+ size: Size of the area to be filled.
211
+
212
+ Returns:
213
+ Hidden, shown, and resized widgets."""
214
+
215
+ _REFLOW_6_NEW = """\
216
+ Args:
217
+ parent: The root widget.
218
+ size: Size of the area to be filled.
219
+ reuse: Replay the placements of subtrees unchanged since the last full arrangement.
220
+
221
+ Returns:
222
+ Hidden, shown, and resized widgets."""
223
+
224
+ # _compositor.py: Compositor.reflow
225
+ _REFLOW_7_OLD = """\
226
+ old_widgets = old_map.keys()
227
+
228
+ map, widgets = self._arrange_root(parent, size, visible_only=False)
229
+
230
+ new_widgets = map.keys()
231
+
232
+ # Newly visible widgets
233
+ shown_widgets = new_widgets - old_widgets
234
+ """
235
+
236
+ _REFLOW_7_NEW = """\
237
+ old_widgets = old_map.keys()
238
+
239
+ self._reuse_next_arrangement = reuse
240
+ state = self._arrangement_state(parent) if reuse and _VERIFY_REUSE else None
241
+ map, widgets = self._arrange_root(parent, size, visible_only=False)
242
+ if state is not None:
243
+ self._verify_reuse(parent, size, map, widgets, state)
244
+
245
+ new_widgets = map.keys()
246
+
247
+ # Newly visible widgets: those the last reflow did not place. A geometry lookup since
248
+ # then may have rebuilt the full map with them already in it, and compared with that
249
+ # map they would never get Show, or the Resize a shown widget gets.
250
+ shown_widgets = new_widgets - self._reflowed_map.keys()
251
+ self._reflowed_map = map
252
+ """
253
+
254
+ # _compositor.py: Compositor.reflow
255
+ _REFLOW_8_OLD = """\
256
+ # Replace map and widgets
257
+ self._full_map = map
258
+ self.widgets = widgets
259
+
260
+ # Contains widgets + geometry for every widget that changed (added, removed, or updated)
261
+ changes = map.items() ^ old_map.items()
262
+
263
+ # Widgets in both new and old
264
+ common_widgets = old_widgets & new_widgets
265
+
266
+ # Mark dirty regions.
267
+ screen_region = size.region
268
+ if screen_region not in self._dirty_regions:
269
+ regions = {
270
+ region
271
+ for region in (
272
+ map_geometry.clip.intersection(map_geometry.region)
273
+ for _, map_geometry in changes
274
+ )
275
+ if region
276
+ }
277
+ self._dirty_regions.update(regions)
278
+
279
+ resized_widgets = {
280
+ widget
281
+ for widget, (region, *_) in changes
282
+ if (widget in common_widgets and old_map[widget].region.size != region.size)
283
+ }
284
+ return ReflowResult(
285
+ hidden=hidden_widgets,"""
286
+
287
+ _REFLOW_8_NEW = """\
288
+ # Replace map and widgets
289
+ self._full_map = map
290
+ self.widgets = widgets
291
+
292
+ # Compare each widget's geometry with its previous one (the symmetric difference of
293
+ # the two maps' items). Replayed placements are shared objects, so most unchanged
294
+ # widgets are settled by identity.
295
+ screen_region = size.region
296
+ mark_dirty = screen_region not in self._dirty_regions
297
+ dirty_regions: set[Region] = set()
298
+ add_dirty_region = dirty_regions.add
299
+ resized_widgets: set[Widget] = set()
300
+ add_resized_widget = resized_widgets.add
301
+ get_old_geometry = old_map.get
302
+ intersection = Region.intersection
303
+ for widget, geometry in map.items():
304
+ old_geometry = get_old_geometry(widget)
305
+ if old_geometry is geometry:
306
+ continue
307
+ if old_geometry is not None:
308
+ if old_geometry == geometry:
309
+ continue
310
+ old_region = old_geometry[0]
311
+ region = geometry[0]
312
+ if old_region[2] != region[2] or old_region[3] != region[3]:
313
+ add_resized_widget(widget)
314
+ if mark_dirty:
315
+ add_dirty_region(intersection(old_geometry[2], old_region))
316
+ if mark_dirty:
317
+ add_dirty_region(intersection(geometry[2], geometry[0]))
318
+ if mark_dirty:
319
+ for widget in old_widgets - new_widgets:
320
+ old_geometry = old_map[widget]
321
+ add_dirty_region(intersection(old_geometry[2], old_geometry[0]))
322
+ self._dirty_regions.update(region for region in dirty_regions if region)
323
+
324
+ return ReflowResult(
325
+ hidden=hidden_widgets,"""
326
+
327
+ # _compositor.py: Compositor.full_map
328
+ _REFLOW_9_OLD = """\
329
+ if self._full_map_invalidated:
330
+ self._full_map_invalidated = False
331
+ map, _widgets = self._arrange_root(self.root, self.size, visible_only=False)
332
+ # Update any widgets which became visible in the interim
333
+ self._full_map = map"""
334
+
335
+ _REFLOW_9_NEW = """\
336
+ if self._full_map_invalidated:
337
+ self._full_map_invalidated = False
338
+ self._reuse_next_arrangement = True
339
+ state = self._arrangement_state(self.root) if _VERIFY_REUSE else None
340
+ map, _widgets = self._arrange_root(self.root, self.size, visible_only=False)
341
+ if state is not None:
342
+ self._verify_reuse(self.root, self.size, map, _widgets, state)
343
+ # Update any widgets which became visible in the interim
344
+ self._full_map = map"""
345
+
346
+ # _compositor.py: Compositor._arrange, Compositor._arrange_root
347
+ _REFLOW_10_OLD = """\
348
+ \"\"\"Arrange a widget's children based on its layout attribute.
349
+
350
+ Args:
351
+ root: Top level widget.
352
+ size: Size of visible area (screen).
353
+ visible_only: Only update visible widgets (used in scrolling).
354
+
355
+ Returns:"""
356
+
357
+ _REFLOW_10_NEW = """\
358
+ \"\"\"Arrange a widget's children based on its layout attribute.
359
+
360
+ A full arrangement records its placements. It replays those of subtrees
361
+ unchanged since the last one only if its caller set `_reuse_next_arrangement`,
362
+ which this call consumes.
363
+
364
+ Args:
365
+ root: Top level widget.
366
+ size: Size of visible area (screen).
367
+ visible_only: Only update visible widgets (used in scrolling).
368
+
369
+ Returns:
370
+ Compositor map and set of widgets.
371
+ \"\"\"
372
+ reuse = self._reuse_next_arrangement
373
+ self._reuse_next_arrangement = False
374
+ return self._arrange(
375
+ root, size, visible_only, reuse=reuse, record=not visible_only
376
+ )
377
+
378
+ def _arrange(
379
+ self,
380
+ root: Widget,
381
+ size: Size,
382
+ visible_only: bool,
383
+ *,
384
+ reuse: bool,
385
+ record: bool,
386
+ ) -> tuple[CompositorMap, set[Widget]]:
387
+ \"\"\"Arrange a widget's children (see `_arrange_root`).
388
+
389
+ Args:
390
+ root: Top level widget.
391
+ size: Size of visible area (screen).
392
+ visible_only: Only update visible widgets (used in scrolling).
393
+ reuse: Replay the recorded placements of subtrees that have not changed
394
+ since they were recorded (full arrangements only).
395
+ record: Record placements so a later arrangement may reuse them
396
+ (full arrangements only).
397
+
398
+ Returns:"""
399
+
400
+ # _compositor.py: Compositor._arrange
401
+ _REFLOW_11_OLD = """\
402
+ no_clip = size.region
403
+
404
+ def add_widget(
405
+ widget: Widget,"""
406
+
407
+ _REFLOW_11_NEW = """\
408
+ no_clip = size.region
409
+
410
+ records: dict[Widget, _ArrangeRecord] | None = None
411
+ old_records: dict[Widget, _ArrangeRecord] | None = None
412
+ epoch = 0
413
+ if record:
414
+ DOMNode._layout_epoch += 1
415
+ epoch = DOMNode._layout_epoch
416
+ records = {}
417
+ if (
418
+ reuse
419
+ and self._arrange_records_size == size
420
+ and self._arrange_records_root is root
421
+ ):
422
+ old_records = self._arrange_records
423
+
424
+ def replay(widget: Widget, arrange_record: _ArrangeRecord) -> None:
425
+ \"\"\"Re-emit the recorded placements of an unchanged subtree.\"\"\"
426
+ records[widget] = arrange_record
427
+ _, _, visible, arranged_widgets, cover_entry, children, entries, *_ = (
428
+ arrange_record
429
+ )
430
+ if visible:
431
+ add_new_widget(widget)
432
+ else:
433
+ add_new_invisible_widget(widget)
434
+ if arranged_widgets is not None:
435
+ widgets.update(arranged_widgets)
436
+ if cover_entry is not None:
437
+ map[cover_entry[0]] = cover_entry[1]
438
+ for child in children:
439
+ replay(child, old_records[child])
440
+ for chrome_widget, geometry in entries:
441
+ map[chrome_widget] = geometry
442
+
443
+ def replay_moved(
444
+ widget: Widget,
445
+ arrange_record: _ArrangeRecord,
446
+ dx: int,
447
+ dy: int,
448
+ arrange_args: tuple,
449
+ _MapGeometry: type[MapGeometry] = MapGeometry,
450
+ _Region: type[Region] = Region,
451
+ _new: Callable[..., tuple] = tuple.__new__,
452
+ ) -> None:
453
+ \"\"\"Re-emit an unchanged subtree whose position or clip changed.
454
+
455
+ Every region within the subtree moves by `(dx, dy)`; clips are re-derived
456
+ from the new clip and the (moved) regions that clip children.
457
+ \"\"\"
458
+ # Regions and geometries are built with `tuple.__new__`, which skips the
459
+ # named tuples' Python-level constructors: this runs for every moved widget.
460
+ (
461
+ record_epoch,
462
+ _,
463
+ visible,
464
+ arranged_widgets,
465
+ cover_entry,
466
+ children,
467
+ entries,
468
+ child_region,
469
+ overlay_children,
470
+ translatable,
471
+ ) = arrange_record
472
+ clip = arrange_args[4]
473
+ if visible:
474
+ add_new_widget(widget)
475
+ else:
476
+ add_new_invisible_widget(widget)
477
+ if arranged_widgets is not None:
478
+ widgets.update(arranged_widgets)
479
+ if cover_entry is not None:
480
+ cover_widget, (
481
+ (x, y, width, height),
482
+ order,
483
+ _,
484
+ virtual_size,
485
+ container_size,
486
+ virtual_region,
487
+ dock_gutter,
488
+ ) = cover_entry
489
+ geometry = _new(
490
+ _MapGeometry,
491
+ (
492
+ _new(_Region, (x + dx, y + dy, width, height)),
493
+ order,
494
+ clip,
495
+ virtual_size,
496
+ container_size,
497
+ virtual_region,
498
+ dock_gutter,
499
+ ),
500
+ )
501
+ cover_entry = (cover_widget, geometry)
502
+ map[cover_widget] = geometry
503
+ if child_region is not None:
504
+ x, y, width, height = child_region
505
+ child_region = _new(_Region, (x + dx, y + dy, width, height))
506
+ sub_clip = clip.intersection(child_region)
507
+ for child in children:
508
+ child_record = old_records[child]
509
+ (
510
+ child_virtual_region,
511
+ (x, y, width, height),
512
+ child_order,
513
+ child_layer_order,
514
+ _,
515
+ child_visible,
516
+ child_dock_gutter,
517
+ child_inherited_layers,
518
+ ) = child_record[1]
519
+ replay_moved(
520
+ child,
521
+ child_record,
522
+ dx,
523
+ dy,
524
+ (
525
+ child_virtual_region,
526
+ _new(_Region, (x + dx, y + dy, width, height)),
527
+ child_order,
528
+ child_layer_order,
529
+ no_clip if child in overlay_children else sub_clip,
530
+ child_visible,
531
+ child_dock_gutter,
532
+ child_inherited_layers,
533
+ ),
534
+ )
535
+ if entries:
536
+ # Chrome (scrollbars) first, the widget itself last; chrome regions are
537
+ # absolute, the widget's virtual region is relative to its container.
538
+ last = len(entries) - 1
539
+ moved_entries: list[tuple[Widget, MapGeometry]] = []
540
+ for index, (
541
+ chrome_widget,
542
+ (
543
+ (x, y, width, height),
544
+ order,
545
+ _,
546
+ virtual_size,
547
+ container_size,
548
+ virtual_region,
549
+ dock_gutter,
550
+ ),
551
+ ) in enumerate(entries):
552
+ if index != last:
553
+ vx, vy, vwidth, vheight = virtual_region
554
+ virtual_region = _new(
555
+ _Region, (vx + dx, vy + dy, vwidth, vheight)
556
+ )
557
+ moved = _new(
558
+ _MapGeometry,
559
+ (
560
+ _new(_Region, (x + dx, y + dy, width, height)),
561
+ order,
562
+ clip,
563
+ virtual_size,
564
+ container_size,
565
+ virtual_region,
566
+ dock_gutter,
567
+ ),
568
+ )
569
+ map[chrome_widget] = moved
570
+ moved_entries.append((chrome_widget, moved))
571
+ entries = moved_entries
572
+ records[widget] = (
573
+ record_epoch,
574
+ arrange_args,
575
+ visible,
576
+ arranged_widgets,
577
+ cover_entry,
578
+ children,
579
+ entries,
580
+ child_region,
581
+ overlay_children,
582
+ translatable,
583
+ )
584
+
585
+ def add_widget(
586
+ widget: Widget,"""
587
+
588
+ # _compositor.py: Compositor._arrange
589
+ _REFLOW_12_OLD = """\
590
+ dock_gutter: Spacing,
591
+ _MapGeometry: type[MapGeometry] = MapGeometry,
592
+ ) -> None:
593
+ \"\"\"Called recursively to place a widget and its children in the map.
594
+ """
595
+
596
+ _REFLOW_12_NEW = """\
597
+ dock_gutter: Spacing,
598
+ inherited_layers: tuple[str, ...] | None,
599
+ _MapGeometry: type[MapGeometry] = MapGeometry,
600
+ ) -> bool:
601
+ \"\"\"Called recursively to place a widget and its children in the map.
602
+ """
603
+
604
+ # _compositor.py: Compositor._arrange
605
+ _REFLOW_13_OLD = """\
606
+ visible: Whether the widget should be visible by default.
607
+ This may be overridden by the CSS rule `visibility`.
608
+ \"\"\"
609
+ if not widget._is_mounted:
610
+ return
611
+ styles = widget.styles
612
+ """
613
+
614
+ _REFLOW_13_NEW = """\
615
+ visible: Whether the widget should be visible by default.
616
+ This may be overridden by the CSS rule `visibility`.
617
+ inherited_layers: The outermost `layers` declaration among the widget's
618
+ ancestors, or `None` if none declares any.
619
+
620
+ Returns:
621
+ `True` if the widget was placed, `False` if it isn't mounted.
622
+ \"\"\"
623
+ if not widget._is_mounted:
624
+ return False
625
+
626
+ if records is not None:
627
+ arrange_args = (
628
+ virtual_region,
629
+ region,
630
+ order,
631
+ layer_order,
632
+ clip,
633
+ visible,
634
+ dock_gutter,
635
+ inherited_layers,
636
+ )
637
+ if old_records is not None:
638
+ arrange_record = old_records.get(widget)
639
+ if (
640
+ arrange_record is not None
641
+ and widget._layout_dirty_epoch < arrange_record[0]
642
+ ):
643
+ old_args = arrange_record[1]
644
+ if old_args == arrange_args:
645
+ replay(widget, arrange_record)
646
+ return True
647
+ old_region = old_args[1]
648
+ if (
649
+ arrange_record[9]
650
+ and old_region.size == region.size
651
+ and old_args[0] == virtual_region
652
+ and old_args[2] == order
653
+ and old_args[3] == layer_order
654
+ and old_args[5] == visible
655
+ and old_args[6] == dock_gutter
656
+ and old_args[7] == inherited_layers
657
+ ):
658
+ replay_moved(
659
+ widget,
660
+ arrange_record,
661
+ region[0] - old_region[0],
662
+ region[1] - old_region[1],
663
+ arrange_args,
664
+ )
665
+ return True
666
+
667
+ styles = widget.styles
668
+ """
669
+
670
+ # _compositor.py: Compositor._arrange
671
+ _REFLOW_14_OLD = """\
672
+ else:
673
+ add_new_invisible_widget(widget)
674
+
675
+ # Container region is minus border"""
676
+
677
+ _REFLOW_14_NEW = """\
678
+ else:
679
+ add_new_invisible_widget(widget)
680
+
681
+ arranged_widgets: set[Widget] | None = None
682
+ cover_entry: tuple[Widget, MapGeometry] | None = None
683
+ children: list[Widget] = []
684
+ entries: list[tuple[Widget, MapGeometry]] = []
685
+ clip_region: Region | None = None
686
+ overlay_children: frozenset[Widget] = frozenset()
687
+ translatable = True
688
+
689
+ # Container region is minus border"""
690
+
691
+ # _compositor.py: Compositor._arrange
692
+ _REFLOW_15_OLD = """\
693
+ )
694
+ )
695
+ widget.set_reactive(Widget.scroll_y, new_scroll_y)
696
+ widget.set_reactive(Widget.scroll_target_y, new_scroll_y)"""
697
+
698
+ _REFLOW_15_NEW = """\
699
+ )
700
+ )
701
+ if new_scroll_y != widget.scroll_y:
702
+ # Placements beneath this widget move with its scroll offset.
703
+ widget._mark_layout_dirty()
704
+ widget.set_reactive(Widget.scroll_y, new_scroll_y)
705
+ widget.set_reactive(Widget.scroll_target_y, new_scroll_y)"""
706
+
707
+ # _compositor.py: Compositor._arrange
708
+ _REFLOW_16_OLD = """\
709
+ placement_scroll_offset = placement_offset - widget.scroll_offset
710
+
711
+ placements = [
712
+ placement.process_offset(size.region, placement_scroll_offset)
713
+ for placement in placements
714
+ ]
715
+
716
+ layers_to_index = {"""
717
+
718
+ _REFLOW_16_NEW = """\
719
+ placement_scroll_offset = placement_offset - widget.scroll_offset
720
+
721
+ if records is None:
722
+ placements = [
723
+ placement.process_offset(
724
+ size.region, placement_scroll_offset
725
+ )
726
+ for placement in placements
727
+ ]
728
+ else:
729
+ clip_region = child_region
730
+ processed_placements: list[WidgetPlacement] = []
731
+ for placement in placements:
732
+ placement_widget = placement.widget
733
+ if (
734
+ placement_widget.absolute_offset is not None
735
+ or placement_widget.styles.has_any_rules(
736
+ "constrain_x", "constrain_y"
737
+ )
738
+ ):
739
+ # Placed relative to the screen: moving this subtree
740
+ # would not simply move the placement.
741
+ translatable = False
742
+ placement = placement.process_offset(
743
+ size.region, placement_scroll_offset
744
+ )
745
+ processed_placements.append(placement)
746
+ placements = processed_placements
747
+
748
+ # `Widget.layers` takes the outermost declaration, which a replayed
749
+ # subtree inherits without being stamped when it changes.
750
+ children_inherited_layers = inherited_layers
751
+ if children_inherited_layers is None and styles.has_rule("layers"):
752
+ children_inherited_layers = styles.layers
753
+
754
+ layers_to_index = {"""
755
+
756
+ # _compositor.py: Compositor._arrange
757
+ _REFLOW_17_OLD = """\
758
+
759
+ if widget._cover_widget is not None:
760
+ map[widget._cover_widget] = _MapGeometry(
761
+ region.shrink(widget.styles.gutter),
762
+ order,
763
+ clip,
764
+ region.size,
765
+ container_size,
766
+ virtual_region,
767
+ dock_gutter,
768
+ )
769
+
770
+ # Add all the widgets"""
771
+
772
+ _REFLOW_17_NEW = """\
773
+
774
+ if widget._cover_widget is not None:
775
+ cover_entry = (
776
+ widget._cover_widget,
777
+ _MapGeometry(
778
+ region.shrink(widget.styles.gutter),
779
+ order,
780
+ clip,
781
+ region.size,
782
+ container_size,
783
+ virtual_region,
784
+ dock_gutter,
785
+ ),
786
+ )
787
+ map[cover_entry[0]] = cover_entry[1]
788
+
789
+ # Add all the widgets"""
790
+
791
+ # _compositor.py: Compositor._arrange
792
+ _REFLOW_18_OLD = """\
793
+
794
+ if widget._cover_widget is None:
795
+ add_widget(
796
+ sub_widget,
797
+ sub_region,"""
798
+
799
+ _REFLOW_18_NEW = """\
800
+
801
+ if widget._cover_widget is None:
802
+ if add_widget(
803
+ sub_widget,
804
+ sub_region,"""
805
+
806
+ # _compositor.py: Compositor._arrange
807
+ _REFLOW_19_OLD = """\
808
+ visible,
809
+ arrange_result.scroll_spacing,
810
+ )
811
+ layer_order -= 1
812
+ else:"""
813
+
814
+ _REFLOW_19_NEW = """\
815
+ visible,
816
+ arrange_result.scroll_spacing,
817
+ children_inherited_layers,
818
+ ):
819
+ children.append(sub_widget)
820
+ if overlay:
821
+ overlay_children = overlay_children | {sub_widget}
822
+ layer_order -= 1
823
+ else:"""
824
+
825
+ # _compositor.py: Compositor._arrange
826
+ _REFLOW_20_OLD = """\
827
+ container_region
828
+ ):
829
+ map[chrome_widget] = _MapGeometry(
830
+ chrome_region,
831
+ order,
832
+ clip,
833
+ container_size,
834
+ container_size,
835
+ chrome_region,
836
+ dock_gutter,
837
+ )
838
+
839
+ map[widget._render_widget] = _MapGeometry(
840
+ region,
841
+ order,
842
+ clip,
843
+ total_region.size,
844
+ container_size,
845
+ virtual_region,
846
+ dock_gutter,
847
+ )
848
+
849
+ elif visible:
850
+ # Add the widget to the map
851
+ map[widget._render_widget] = _MapGeometry(
852
+ region,
853
+ order,
854
+ clip,
855
+ region.size,
856
+ container_size,
857
+ virtual_region,
858
+ dock_gutter,
859
+ )
860
+
861
+ # Add top level (root) widget"""
862
+
863
+ _REFLOW_20_NEW = """\
864
+ container_region
865
+ ):
866
+ entries.append(
867
+ (
868
+ chrome_widget,
869
+ _MapGeometry(
870
+ chrome_region,
871
+ order,
872
+ clip,
873
+ container_size,
874
+ container_size,
875
+ chrome_region,
876
+ dock_gutter,
877
+ ),
878
+ )
879
+ )
880
+
881
+ entries.append(
882
+ (
883
+ widget._render_widget,
884
+ _MapGeometry(
885
+ region,
886
+ order,
887
+ clip,
888
+ total_region.size,
889
+ container_size,
890
+ virtual_region,
891
+ dock_gutter,
892
+ ),
893
+ )
894
+ )
895
+
896
+ elif visible:
897
+ # Add the widget to the map
898
+ entries.append(
899
+ (
900
+ widget._render_widget,
901
+ _MapGeometry(
902
+ region,
903
+ order,
904
+ clip,
905
+ region.size,
906
+ container_size,
907
+ virtual_region,
908
+ dock_gutter,
909
+ ),
910
+ )
911
+ )
912
+
913
+ for chrome_widget, geometry in entries:
914
+ map[chrome_widget] = geometry
915
+
916
+ if records is not None:
917
+ if translatable:
918
+ for child in children:
919
+ if not records[child][9]:
920
+ translatable = False
921
+ break
922
+ records[widget] = (
923
+ epoch,
924
+ arrange_args,
925
+ visible,
926
+ arranged_widgets,
927
+ cover_entry,
928
+ children,
929
+ entries,
930
+ clip_region,
931
+ overlay_children,
932
+ translatable,
933
+ )
934
+ return True
935
+
936
+ root_inherited_layers: tuple[str, ...] | None = None
937
+ ancestor = root._parent
938
+ while isinstance(ancestor, Widget):
939
+ if ancestor.styles.has_rule("layers"):
940
+ root_inherited_layers = ancestor.styles.layers
941
+ ancestor = ancestor._parent
942
+
943
+ # Add top level (root) widget"""
944
+
945
+ # _compositor.py: Compositor._arrange, Compositor._arrangement_state, Compositor._restore_arrangement_state, Compositor._verify_reuse, Compositor.layers
946
+ _REFLOW_21_OLD = """\
947
+ NULL_SPACING,
948
+ )
949
+ widgets -= invisible_widgets
950
+ return map, widgets
951
+
952
+ @property"""
953
+
954
+ _REFLOW_21_NEW = """\
955
+ NULL_SPACING,
956
+ root_inherited_layers,
957
+ )
958
+ # The nested functions refer to themselves through closure cells; clearing the
959
+ # cells breaks that cycle, so this arrangement's maps and records are freed by
960
+ # reference counting instead of waiting for the cyclic garbage collector.
961
+ del add_widget, replay, replay_moved
962
+ widgets -= invisible_widgets
963
+ if records is not None:
964
+ self._arrange_records = records
965
+ self._arrange_records_size = size
966
+ self._arrange_records_root = root
967
+ return map, widgets
968
+
969
+ @staticmethod
970
+ def _arrangement_state(
971
+ root: Widget,
972
+ ) -> list[tuple[DOMNode, tuple[str, ...], dict[str, object]]]:
973
+ \"\"\"Snapshot the state an arrangement writes (debug aid).
974
+
975
+ Scroll offsets, anchoring and container sizes: anchoring and `arrange` overrides
976
+ change them, so a second arrangement only matches the first when it starts from the
977
+ same state. And every node's layout epoch, which the watchers of the offsets an
978
+ arrangement sets stamp, from the node they refresh up to the App.
979
+ \"\"\"
980
+ widget_names = (
981
+ "_reactive_scroll_x",
982
+ "_reactive_scroll_y",
983
+ "_reactive_scroll_target_x",
984
+ "_reactive_scroll_target_y",
985
+ "_anchored_flag",
986
+ "_anchor_released_flag",
987
+ "_container_size",
988
+ "_layout_dirty_epoch",
989
+ )
990
+ scrollbar_names = ("_reactive_position", "_layout_dirty_epoch")
991
+ epoch_names = ("_layout_dirty_epoch",)
992
+ nodes: list[tuple[DOMNode, tuple[str, ...]]] = [
993
+ (ancestor, epoch_names) for ancestor in root.ancestors
994
+ ]
995
+ for node in root.walk_children(with_self=True):
996
+ if not isinstance(node, Widget):
997
+ nodes.append((node, epoch_names))
998
+ continue
999
+ nodes.append((node, widget_names if node.is_scrollable else epoch_names))
1000
+ nodes.extend(
1001
+ (scrollbar, scrollbar_names)
1002
+ for scrollbar in (node._vertical_scrollbar, node._horizontal_scrollbar)
1003
+ if scrollbar is not None
1004
+ )
1005
+ if node._scrollbar_corner is not None:
1006
+ nodes.append((node._scrollbar_corner, epoch_names))
1007
+ return [
1008
+ (
1009
+ node,
1010
+ names,
1011
+ {name: vars(node)[name] for name in names if name in vars(node)},
1012
+ )
1013
+ for node, names in nodes
1014
+ ]
1015
+
1016
+ @staticmethod
1017
+ def _restore_arrangement_state(
1018
+ state: list[tuple[DOMNode, tuple[str, ...], dict[str, object]]],
1019
+ ) -> None:
1020
+ \"\"\"Restore a snapshot taken by `_arrangement_state` (debug aid).\"\"\"
1021
+ for node, names, values in state:
1022
+ namespace = vars(node)
1023
+ for name in names:
1024
+ if name in values:
1025
+ namespace[name] = values[name]
1026
+ else:
1027
+ namespace.pop(name, None)
1028
+
1029
+ def _verify_reuse(
1030
+ self,
1031
+ root: Widget,
1032
+ size: Size,
1033
+ map: CompositorMap,
1034
+ widgets: set[Widget],
1035
+ state: list[tuple[DOMNode, tuple[str, ...], dict[str, object]]],
1036
+ ) -> None:
1037
+ \"\"\"Check a reusing arrangement against a from-scratch one (debug aid).
1038
+
1039
+ The from-scratch arrangement starts from the state the reusing one started from. Both
1040
+ must place the same widgets and leave the same scroll, anchor and container state,
1041
+ which the map does not show for a widget that scrolls its own content. The state the
1042
+ reusing one left is restored afterwards, layout epochs included: the from-scratch
1043
+ arrangement's watchers stamp the widgets they refresh, which would invalidate the
1044
+ records under check and hide a change that was not stamped.
1045
+ \"\"\"
1046
+ after = self._arrangement_state(root)
1047
+ self._restore_arrangement_state(state)
1048
+ try:
1049
+ full_map, full_widgets = self._arrange(
1050
+ root, size, False, reuse=False, record=False
1051
+ )
1052
+ full_after = self._arrangement_state(root)
1053
+ finally:
1054
+ self._restore_arrangement_state(after)
1055
+ unset = object()
1056
+ full_values = {
1057
+ (node, name): value
1058
+ for node, _, values in full_after
1059
+ for name, value in values.items()
1060
+ }
1061
+ state_changed = [
1062
+ (node, name, values.get(name, unset), full_values.get((node, name), unset))
1063
+ for node, names, values in after
1064
+ for name in names
1065
+ if name != "_layout_dirty_epoch"
1066
+ and values.get(name, unset) != full_values.get((node, name), unset)
1067
+ ]
1068
+ if (
1069
+ list(full_map.items()) != list(map.items())
1070
+ or full_widgets != widgets
1071
+ or state_changed
1072
+ ):
1073
+ missing = full_map.keys() - map.keys()
1074
+ extra = map.keys() - full_map.keys()
1075
+ changed = [
1076
+ (widget, map[widget], geometry)
1077
+ for widget, geometry in full_map.items()
1078
+ if widget in map and map[widget] != geometry
1079
+ ]
1080
+ raise AssertionError(
1081
+ "compositor reuse diverged: "
1082
+ f"missing={list(missing)[:5]} extra={list(extra)[:5]} "
1083
+ f"changed={changed[:3]} "
1084
+ f"order_equal={list(full_map) == list(map)} "
1085
+ f"widgets_missing={list(full_widgets - widgets)[:5]} "
1086
+ f"widgets_extra={list(widgets - full_widgets)[:5]} "
1087
+ f"state_changed={state_changed[:3]}"
1088
+ )
1089
+
1090
+ @property"""
1091
+
1092
+ # _node_list.py: NodeList.updated
1093
+ _REFLOW_22_OLD = """\
1094
+ self._updates += 1
1095
+ node = None if self._parent is None else self._parent()
1096
+ while node is not None and (node := node._parent) is not None:
1097
+ node._nodes._updates += 1"""
1098
+
1099
+ _REFLOW_22_NEW = """\
1100
+ self._updates += 1
1101
+ node = None if self._parent is None else self._parent()
1102
+ if node is not None:
1103
+ node._mark_layout_dirty()
1104
+ while node is not None and (node := node._parent) is not None:
1105
+ node._nodes._updates += 1"""
1106
+
1107
+ # dom.py: DOMNode._PSEUDO_CLASSES, DOMNode.__init__, DOMNode._layout_dirty_epoch, DOMNode._layout_epoch, DOMNode._mark_layout_dirty, DOMNode._on_mounted_state
1108
+ _REFLOW_23_OLD = """\
1109
+ _PSEUDO_CLASSES: ClassVar[dict[str, Callable[[App[Any]], bool]]] = {}
1110
+ \"\"\"Pseudo class checks.\"\"\"
1111
+
1112
+ def __init__("""
1113
+
1114
+ _REFLOW_23_NEW = """\
1115
+ _PSEUDO_CLASSES: ClassVar[dict[str, Callable[[App[Any]], bool]]] = {}
1116
+ \"\"\"Pseudo class checks.\"\"\"
1117
+
1118
+ _layout_epoch: ClassVar[int] = 1
1119
+ \"\"\"Global arrangement epoch, advanced by every full compositor arrangement.\"\"\"
1120
+
1121
+ _layout_dirty_epoch: int = 0
1122
+ \"\"\"The epoch in which this node, or a node beneath it, last changed in a way the compositor reads.\"\"\"
1123
+
1124
+ def _mark_layout_dirty(self) -> None:
1125
+ \"\"\"Record that this node's compositor placement may have changed.
1126
+
1127
+ Stamps the node and its ancestors with the current epoch, stopping at the first
1128
+ ancestor already stamped (whose own ancestors are then stamped too).
1129
+ \"\"\"
1130
+ epoch = DOMNode._layout_epoch
1131
+ node: DOMNode | None = self
1132
+ while node is not None and node._layout_dirty_epoch != epoch:
1133
+ node._layout_dirty_epoch = epoch
1134
+ node = node._parent
1135
+
1136
+ def _on_mounted_state(self) -> None:
1137
+ # The compositor skips unmounted widgets, so mounting changes placement.
1138
+ self._mark_layout_dirty()
1139
+
1140
+ def __init__("""
1141
+
1142
+ # message_pump.py: MessagePump._on_mounted_state, MessagePump._post_mount, MessagePump._pre_process
1143
+ _REFLOW_24_OLD = """\
1144
+ self._mounted_event.set()
1145
+ self._is_mounted = True
1146
+ return True
1147
+
1148
+ def _post_mount(self):"""
1149
+
1150
+ _REFLOW_24_NEW = """\
1151
+ self._mounted_event.set()
1152
+ self._is_mounted = True
1153
+ self._on_mounted_state()
1154
+ return True
1155
+
1156
+ def _on_mounted_state(self) -> None:
1157
+ \"\"\"Called once `_is_mounted` becomes `True`.\"\"\"
1158
+
1159
+ def _post_mount(self):"""
1160
+
1161
+ # message_pump.py: MessagePump._process_messages_loop
1162
+ _REFLOW_25_OLD = """\
1163
+ self._mounted_event.set()
1164
+ self._is_mounted = True
1165
+ self.app._handle_exception(error)
1166
+ break"""
1167
+
1168
+ _REFLOW_25_NEW = """\
1169
+ self._mounted_event.set()
1170
+ self._is_mounted = True
1171
+ self._on_mounted_state()
1172
+ self.app._handle_exception(error)
1173
+ break"""
1174
+
1175
+ # screen.py: Screen._refresh_layout
1176
+ _REFLOW_26_OLD = """\
1177
+
1178
+ else:
1179
+ hidden, shown, resized = self._compositor.reflow(self, size)
1180
+ self._layout_widgets.clear()
1181
+ Hide = events.Hide"""
1182
+
1183
+ _REFLOW_26_NEW = """\
1184
+
1185
+ else:
1186
+ hidden, shown, resized = self._compositor.reflow(
1187
+ self, size, reuse=True
1188
+ )
1189
+ self._layout_widgets.clear()
1190
+ Hide = events.Hide"""
1191
+
1192
+ # widget.py: Widget._absolute_offset, Widget._anchor_released, Widget._anchor_released_flag, Widget._anchored, Widget._anchored_flag, Widget._cover, Widget.absolute_offset, Widget.pre_render
1193
+ _REFLOW_27_OLD = """\
1194
+ \"\"\"
1195
+ self._visual_style = None
1196
+
1197
+ def _cover(self, widget: Widget) -> None:"""
1198
+
1199
+ _REFLOW_27_NEW = """\
1200
+ \"\"\"
1201
+ self._visual_style = None
1202
+
1203
+ # Backing fields of the properties below, which stamp the layout epoch on change.
1204
+ _absolute_offset: Offset | None = None
1205
+ _anchored_flag: bool = False
1206
+ _anchor_released_flag: bool = False
1207
+
1208
+ @property
1209
+ def absolute_offset(self) -> Offset | None:
1210
+ \"\"\"Force an absolute offset for the widget (used by tooltips).\"\"\"
1211
+ return self._absolute_offset
1212
+
1213
+ @absolute_offset.setter
1214
+ def absolute_offset(self, offset: Offset | None) -> None:
1215
+ if offset != self._absolute_offset:
1216
+ self._absolute_offset = offset
1217
+ self._mark_layout_dirty()
1218
+
1219
+ @property
1220
+ def _anchored(self) -> bool:
1221
+ \"\"\"Has this widget been anchored?\"\"\"
1222
+ return self._anchored_flag
1223
+
1224
+ @_anchored.setter
1225
+ def _anchored(self, anchored: bool) -> None:
1226
+ if anchored != self._anchored_flag:
1227
+ self._anchored_flag = anchored
1228
+ self._mark_layout_dirty()
1229
+
1230
+ @property
1231
+ def _anchor_released(self) -> bool:
1232
+ \"\"\"Has the anchor been released?\"\"\"
1233
+ return self._anchor_released_flag
1234
+
1235
+ @_anchor_released.setter
1236
+ def _anchor_released(self, released: bool) -> None:
1237
+ if released != self._anchor_released_flag:
1238
+ self._anchor_released_flag = released
1239
+ self._mark_layout_dirty()
1240
+
1241
+ def _cover(self, widget: Widget) -> None:"""
1242
+
1243
+ # widget.py: Widget._set_dirty
1244
+ _REFLOW_28_OLD = """\
1245
+ self._dirty_regions.clear()
1246
+ self._repaint_regions.clear()
1247
+ self._styles_cache.clear()
1248
+ self._styles_cache.set_dirty(self.size.region)
1249
+ outer_size = self.outer_size
1250
+ self._dirty_regions.add(outer_size.region)"""
1251
+
1252
+ _REFLOW_28_NEW = """\
1253
+ self._dirty_regions.clear()
1254
+ self._repaint_regions.clear()
1255
+ # A cleared cache re-renders every line; marking lines dirty as well would
1256
+ # only cost a compositor lookup (`self.size`), which may rebuild the full map.
1257
+ self._styles_cache.clear()
1258
+ outer_size = self.outer_size
1259
+ self._dirty_regions.add(outer_size.region)"""
1260
+
1261
+ # widget.py: Widget.scroll_to, Widget.set_scroll
1262
+ _REFLOW_29_OLD = """\
1263
+ if y is not None:
1264
+ self.set_reactive(Widget.scroll_y, round(y))
1265
+
1266
+ def scroll_to("""
1267
+
1268
+ _REFLOW_29_NEW = """\
1269
+ if y is not None:
1270
+ self.set_reactive(Widget.scroll_y, round(y))
1271
+ self._mark_layout_dirty()
1272
+
1273
+ def scroll_to("""
1274
+
1275
+ # widget.py: Widget._refresh_scroll
1276
+ _REFLOW_30_OLD = """\
1277
+ \"\"\"Refreshes the scroll position.\"\"\"
1278
+ self._scroll_required = True
1279
+ self.check_idle()
1280
+ """
1281
+
1282
+ _REFLOW_30_NEW = """\
1283
+ \"\"\"Refreshes the scroll position.\"\"\"
1284
+ self._scroll_required = True
1285
+ self._mark_layout_dirty()
1286
+ self.check_idle()
1287
+ """
1288
+
1289
+ # widget.py: Widget.refresh
1290
+ _REFLOW_31_OLD = """\
1291
+ \"\"\"
1292
+
1293
+ if layout and not self._layout_required:
1294
+ self._layout_required = True"""
1295
+
1296
+ _REFLOW_31_NEW = """\
1297
+ \"\"\"
1298
+
1299
+ self._mark_layout_dirty()
1300
+ if layout and not self._layout_required:
1301
+ self._layout_required = True"""
1302
+
1303
+ # widget.py: Widget._check_refresh
1304
+ _REFLOW_32_OLD = """\
1305
+ if self._layout_required:
1306
+ self._layout_required = False
1307
+ for ancestor in self.ancestors:
1308
+ if not isinstance(ancestor, Widget):"""
1309
+
1310
+ _REFLOW_32_NEW = """\
1311
+ if self._layout_required:
1312
+ self._layout_required = False
1313
+ self._mark_layout_dirty()
1314
+ for ancestor in self.ancestors:
1315
+ if not isinstance(ancestor, Widget):"""
1316
+
1317
+ # css/styles.py: Styles.clear_rule, Styles.get_rules
1318
+ _REFLOW_33_OLD = """\
1319
+ changed = self._rules.pop(rule_name, None) is not None # type: ignore
1320
+ if changed:
1321
+ self._updates += 1
1322
+ return changed
1323
+
1324
+ def get_rules(self) -> RulesMap:"""
1325
+
1326
+ _REFLOW_33_NEW = """\
1327
+ changed = self._rules.pop(rule_name, None) is not None # type: ignore
1328
+ if changed:
1329
+ self._updates += 1
1330
+ if self.node is not None:
1331
+ # A raw rule write skips the refresh a style property makes.
1332
+ self.node._mark_layout_dirty()
1333
+ return changed
1334
+
1335
+ def get_rules(self) -> RulesMap:"""
1336
+
1337
+ # css/styles.py: Styles.set_rule
1338
+ _REFLOW_34_OLD = """\
1339
+ if value is None:
1340
+ changed = self._rules.pop(rule, None) is not None # type: ignore
1341
+ if changed:
1342
+ self._updates += 1
1343
+ return changed
1344
+ current = self._rules.get(rule)
1345
+ self._rules[rule] = value # type: ignore
1346
+ changed = current != value
1347
+ if changed:
1348
+ self._updates += 1
1349
+ return changed
1350
+ """
1351
+
1352
+ _REFLOW_34_NEW = """\
1353
+ if value is None:
1354
+ changed = self._rules.pop(rule, None) is not None # type: ignore
1355
+ else:
1356
+ current = self._rules.get(rule)
1357
+ self._rules[rule] = value # type: ignore
1358
+ changed = current != value
1359
+ if changed:
1360
+ self._updates += 1
1361
+ if self.node is not None:
1362
+ # A raw rule write skips the refresh a style property makes.
1363
+ self.node._mark_layout_dirty()
1364
+ return changed
1365
+ """
1366
+
1367
+ # widget.py: Widget._size_updated
1368
+ _REFLOW_35_OLD = """\
1369
+ else:
1370
+ self.set_reactive(Widget.virtual_size, virtual_size)
1371
+ self._container_size = container_size"""
1372
+
1373
+ _REFLOW_35_NEW = """\
1374
+ else:
1375
+ self.set_reactive(Widget.virtual_size, virtual_size)
1376
+ if (
1377
+ self._container_size != container_size
1378
+ and self._anchored
1379
+ and not self._anchor_released
1380
+ ):
1381
+ # An anchored widget is pinned from its container size, which is only
1382
+ # known after the arrangement that pinned it.
1383
+ self._mark_layout_dirty()
1384
+ self._container_size = container_size"""
1385
+
1386
+ # scroll_view.py: ScrollView._size_updated
1387
+ _REFLOW_36_OLD = """\
1388
+ virtual_size = self.virtual_size
1389
+ self._container_size = size - self.styles.gutter.totals"""
1390
+
1391
+ _REFLOW_36_NEW = """\
1392
+ virtual_size = self.virtual_size
1393
+ new_container_size = size - self.styles.gutter.totals
1394
+ if (
1395
+ self._container_size != new_container_size
1396
+ and self._anchored
1397
+ and not self._anchor_released
1398
+ ):
1399
+ # An anchored widget is pinned from its container size, which is only
1400
+ # known after the arrangement that pinned it.
1401
+ self._mark_layout_dirty()
1402
+ self._container_size = new_container_size"""
1403
+
1404
+ # reactive.py: Reactive._set
1405
+ _REFLOW_37_OLD = """\
1406
+ # Store the internal value
1407
+ setattr(obj, self.internal_name, value)
1408
+
1409
+ # Check all watchers
1410
+ self._check_watchers(obj, name, current_value)"""
1411
+
1412
+ _REFLOW_37_NEW = """\
1413
+ # Store the internal value
1414
+ setattr(obj, self.internal_name, value)
1415
+
1416
+ if self._layout or self._repaint or self._recompose:
1417
+ # The refresh below would stamp the change only after the watchers, which
1418
+ # may look up geometry and so rebuild the full map from records.
1419
+ obj._mark_layout_dirty()
1420
+
1421
+ # Check all watchers
1422
+ self._check_watchers(obj, name, current_value)"""
1423
+
1424
+ _PATCHES = (
1425
+ FilePatch(
1426
+ package="textual",
1427
+ module_file="_compositor.py",
1428
+ old_fragment=_REFLOW_1_OLD,
1429
+ new_fragment=_REFLOW_1_NEW,
1430
+ description="Reuse unchanged arrangements: <import>",
1431
+ ),
1432
+ FilePatch(
1433
+ package="textual",
1434
+ module_file="_compositor.py",
1435
+ old_fragment=_REFLOW_2_OLD,
1436
+ new_fragment=_REFLOW_2_NEW,
1437
+ description="Reuse unchanged arrangements: <if>, _ArrangeRecord=, _VERIFY_REUSE=",
1438
+ ),
1439
+ FilePatch(
1440
+ package="textual",
1441
+ module_file="_compositor.py",
1442
+ old_fragment=_REFLOW_3_OLD,
1443
+ new_fragment=_REFLOW_3_NEW,
1444
+ description="Reuse unchanged arrangements: Compositor.__init__, Compositor.clear",
1445
+ ),
1446
+ FilePatch(
1447
+ package="textual",
1448
+ module_file="_compositor.py",
1449
+ old_fragment=_REFLOW_4_OLD,
1450
+ new_fragment=_REFLOW_4_NEW,
1451
+ description="Reuse unchanged arrangements: Compositor._regions_to_spans, Compositor.clear",
1452
+ ),
1453
+ FilePatch(
1454
+ package="textual",
1455
+ module_file="_compositor.py",
1456
+ old_fragment=_REFLOW_5_OLD,
1457
+ new_fragment=_REFLOW_5_NEW,
1458
+ description="Reuse unchanged arrangements: Compositor.__rich_repr__, Compositor.reflow",
1459
+ ),
1460
+ FilePatch(
1461
+ package="textual",
1462
+ module_file="_compositor.py",
1463
+ old_fragment=_REFLOW_6_OLD,
1464
+ new_fragment=_REFLOW_6_NEW,
1465
+ description="Reuse unchanged arrangements: Compositor.reflow",
1466
+ ),
1467
+ FilePatch(
1468
+ package="textual",
1469
+ module_file="_compositor.py",
1470
+ old_fragment=_REFLOW_7_OLD,
1471
+ new_fragment=_REFLOW_7_NEW,
1472
+ description="Reuse unchanged arrangements: Compositor.reflow",
1473
+ ),
1474
+ FilePatch(
1475
+ package="textual",
1476
+ module_file="_compositor.py",
1477
+ old_fragment=_REFLOW_8_OLD,
1478
+ new_fragment=_REFLOW_8_NEW,
1479
+ description="Reuse unchanged arrangements: Compositor.reflow",
1480
+ ),
1481
+ FilePatch(
1482
+ package="textual",
1483
+ module_file="_compositor.py",
1484
+ old_fragment=_REFLOW_9_OLD,
1485
+ new_fragment=_REFLOW_9_NEW,
1486
+ description="Reuse unchanged arrangements: Compositor.full_map",
1487
+ ),
1488
+ FilePatch(
1489
+ package="textual",
1490
+ module_file="_compositor.py",
1491
+ old_fragment=_REFLOW_10_OLD,
1492
+ new_fragment=_REFLOW_10_NEW,
1493
+ description="Reuse unchanged arrangements: Compositor._arrange, Compositor._arrange_root",
1494
+ ),
1495
+ FilePatch(
1496
+ package="textual",
1497
+ module_file="_compositor.py",
1498
+ old_fragment=_REFLOW_11_OLD,
1499
+ new_fragment=_REFLOW_11_NEW,
1500
+ description="Reuse unchanged arrangements: Compositor._arrange",
1501
+ ),
1502
+ FilePatch(
1503
+ package="textual",
1504
+ module_file="_compositor.py",
1505
+ old_fragment=_REFLOW_12_OLD,
1506
+ new_fragment=_REFLOW_12_NEW,
1507
+ description="Reuse unchanged arrangements: Compositor._arrange",
1508
+ ),
1509
+ FilePatch(
1510
+ package="textual",
1511
+ module_file="_compositor.py",
1512
+ old_fragment=_REFLOW_13_OLD,
1513
+ new_fragment=_REFLOW_13_NEW,
1514
+ description="Reuse unchanged arrangements: Compositor._arrange",
1515
+ ),
1516
+ FilePatch(
1517
+ package="textual",
1518
+ module_file="_compositor.py",
1519
+ old_fragment=_REFLOW_14_OLD,
1520
+ new_fragment=_REFLOW_14_NEW,
1521
+ description="Reuse unchanged arrangements: Compositor._arrange",
1522
+ ),
1523
+ FilePatch(
1524
+ package="textual",
1525
+ module_file="_compositor.py",
1526
+ old_fragment=_REFLOW_15_OLD,
1527
+ new_fragment=_REFLOW_15_NEW,
1528
+ description="Reuse unchanged arrangements: Compositor._arrange",
1529
+ ),
1530
+ FilePatch(
1531
+ package="textual",
1532
+ module_file="_compositor.py",
1533
+ old_fragment=_REFLOW_16_OLD,
1534
+ new_fragment=_REFLOW_16_NEW,
1535
+ description="Reuse unchanged arrangements: Compositor._arrange",
1536
+ ),
1537
+ FilePatch(
1538
+ package="textual",
1539
+ module_file="_compositor.py",
1540
+ old_fragment=_REFLOW_17_OLD,
1541
+ new_fragment=_REFLOW_17_NEW,
1542
+ description="Reuse unchanged arrangements: Compositor._arrange",
1543
+ ),
1544
+ FilePatch(
1545
+ package="textual",
1546
+ module_file="_compositor.py",
1547
+ old_fragment=_REFLOW_18_OLD,
1548
+ new_fragment=_REFLOW_18_NEW,
1549
+ description="Reuse unchanged arrangements: Compositor._arrange",
1550
+ ),
1551
+ FilePatch(
1552
+ package="textual",
1553
+ module_file="_compositor.py",
1554
+ old_fragment=_REFLOW_19_OLD,
1555
+ new_fragment=_REFLOW_19_NEW,
1556
+ description="Reuse unchanged arrangements: Compositor._arrange",
1557
+ ),
1558
+ FilePatch(
1559
+ package="textual",
1560
+ module_file="_compositor.py",
1561
+ old_fragment=_REFLOW_20_OLD,
1562
+ new_fragment=_REFLOW_20_NEW,
1563
+ description="Reuse unchanged arrangements: Compositor._arrange",
1564
+ ),
1565
+ FilePatch(
1566
+ package="textual",
1567
+ module_file="_compositor.py",
1568
+ old_fragment=_REFLOW_21_OLD,
1569
+ new_fragment=_REFLOW_21_NEW,
1570
+ description="Reuse unchanged arrangements: Compositor._arrange, Compositor._arrangement_state, Compositor._restore_arrangement_state, Compositor._verify_reuse, Compositor.layers",
1571
+ ),
1572
+ FilePatch(
1573
+ package="textual",
1574
+ module_file="_node_list.py",
1575
+ old_fragment=_REFLOW_22_OLD,
1576
+ new_fragment=_REFLOW_22_NEW,
1577
+ description="Reuse unchanged arrangements: NodeList.updated",
1578
+ ),
1579
+ FilePatch(
1580
+ package="textual",
1581
+ module_file="dom.py",
1582
+ old_fragment=_REFLOW_23_OLD,
1583
+ new_fragment=_REFLOW_23_NEW,
1584
+ description="Reuse unchanged arrangements: DOMNode._PSEUDO_CLASSES, DOMNode.__init__, DOMNode._layout_dirty_epoch, DOMNode._layout_epoch, DOMNode._mark_layout_dirty, DOMNode._on_mounted_state",
1585
+ ),
1586
+ FilePatch(
1587
+ package="textual",
1588
+ module_file="message_pump.py",
1589
+ old_fragment=_REFLOW_24_OLD,
1590
+ new_fragment=_REFLOW_24_NEW,
1591
+ description="Reuse unchanged arrangements: MessagePump._on_mounted_state, MessagePump._post_mount, MessagePump._pre_process",
1592
+ ),
1593
+ FilePatch(
1594
+ package="textual",
1595
+ module_file="message_pump.py",
1596
+ old_fragment=_REFLOW_25_OLD,
1597
+ new_fragment=_REFLOW_25_NEW,
1598
+ description="Reuse unchanged arrangements: MessagePump._process_messages_loop",
1599
+ ),
1600
+ FilePatch(
1601
+ package="textual",
1602
+ module_file="screen.py",
1603
+ old_fragment=_REFLOW_26_OLD,
1604
+ new_fragment=_REFLOW_26_NEW,
1605
+ description="Reuse unchanged arrangements: Screen._refresh_layout",
1606
+ ),
1607
+ FilePatch(
1608
+ package="textual",
1609
+ module_file="widget.py",
1610
+ old_fragment=_REFLOW_27_OLD,
1611
+ new_fragment=_REFLOW_27_NEW,
1612
+ description="Reuse unchanged arrangements: Widget._absolute_offset, Widget._anchor_released, Widget._anchor_released_flag, Widget._anchored, Widget._anchored_flag, Widget._cover, Widget.absolute_offset, Widget.pre_render",
1613
+ ),
1614
+ FilePatch(
1615
+ package="textual",
1616
+ module_file="widget.py",
1617
+ old_fragment=_REFLOW_28_OLD,
1618
+ new_fragment=_REFLOW_28_NEW,
1619
+ description="Reuse unchanged arrangements: Widget._set_dirty",
1620
+ ),
1621
+ FilePatch(
1622
+ package="textual",
1623
+ module_file="widget.py",
1624
+ old_fragment=_REFLOW_29_OLD,
1625
+ new_fragment=_REFLOW_29_NEW,
1626
+ description="Reuse unchanged arrangements: Widget.scroll_to, Widget.set_scroll",
1627
+ ),
1628
+ FilePatch(
1629
+ package="textual",
1630
+ module_file="widget.py",
1631
+ old_fragment=_REFLOW_30_OLD,
1632
+ new_fragment=_REFLOW_30_NEW,
1633
+ description="Reuse unchanged arrangements: Widget._refresh_scroll",
1634
+ ),
1635
+ FilePatch(
1636
+ package="textual",
1637
+ module_file="widget.py",
1638
+ old_fragment=_REFLOW_31_OLD,
1639
+ new_fragment=_REFLOW_31_NEW,
1640
+ description="Reuse unchanged arrangements: Widget.refresh",
1641
+ ),
1642
+ FilePatch(
1643
+ package="textual",
1644
+ module_file="widget.py",
1645
+ old_fragment=_REFLOW_32_OLD,
1646
+ new_fragment=_REFLOW_32_NEW,
1647
+ description="Reuse unchanged arrangements: Widget._check_refresh",
1648
+ ),
1649
+ FilePatch(
1650
+ package="textual",
1651
+ module_file="css/styles.py",
1652
+ old_fragment=_REFLOW_33_OLD,
1653
+ new_fragment=_REFLOW_33_NEW,
1654
+ description="Reuse unchanged arrangements: Styles.clear_rule, Styles.get_rules",
1655
+ ),
1656
+ FilePatch(
1657
+ package="textual",
1658
+ module_file="css/styles.py",
1659
+ old_fragment=_REFLOW_34_OLD,
1660
+ new_fragment=_REFLOW_34_NEW,
1661
+ description="Reuse unchanged arrangements: Styles.set_rule",
1662
+ ),
1663
+ FilePatch(
1664
+ package="textual",
1665
+ module_file="widget.py",
1666
+ old_fragment=_REFLOW_35_OLD,
1667
+ new_fragment=_REFLOW_35_NEW,
1668
+ description="Reuse unchanged arrangements: Widget._size_updated",
1669
+ ),
1670
+ FilePatch(
1671
+ package="textual",
1672
+ module_file="scroll_view.py",
1673
+ old_fragment=_REFLOW_36_OLD,
1674
+ new_fragment=_REFLOW_36_NEW,
1675
+ description="Reuse unchanged arrangements: ScrollView._size_updated",
1676
+ ),
1677
+ FilePatch(
1678
+ package="textual",
1679
+ module_file="reactive.py",
1680
+ old_fragment=_REFLOW_37_OLD,
1681
+ new_fragment=_REFLOW_37_NEW,
1682
+ description="Reuse unchanged arrangements: Reactive._set",
1683
+ ),
1684
+ )
1685
+
1686
+
1687
+ def apply_runtime_patch() -> None:
1688
+ """Install the reusing reflow on classes already imported; on drift in any module, in none."""
1689
+ try:
1690
+ import importlib
1691
+
1692
+ import textual
1693
+ except ImportError:
1694
+ return
1695
+ if textual.__version__ != _RUNTIME_PATCH_TEXTUAL_VERSION:
1696
+ logger.warning(
1697
+ "Skipping Textual reflow reuse runtime patch: loaded Textual is not the pinned %s that the patch targets.",
1698
+ _RUNTIME_PATCH_TEXTUAL_VERSION,
1699
+ )
1700
+ return
1701
+ modules = {name: importlib.import_module(name) for name in _RUNTIME_MEMBERS}
1702
+ if all(
1703
+ members_installed(modules[name], members, _RUNTIME_PATCH_MARKER) for name, members in _RUNTIME_MEMBERS.items()
1704
+ ):
1705
+ return
1706
+ try:
1707
+ # Stage every module before installing any: a compositor that reuses records while
1708
+ # widgets do not stamp their changes would paint stale placements.
1709
+ staged = [
1710
+ stage_members(
1711
+ modules[name],
1712
+ [patch for patch in _PATCHES if _module_name(patch) == name],
1713
+ members,
1714
+ label="reflow reuse",
1715
+ )
1716
+ for name, members in _RUNTIME_MEMBERS.items()
1717
+ ]
1718
+ except StagedSourceDriftError as exc:
1719
+ logger.warning("Skipping Textual reflow reuse runtime patch: %s", exc)
1720
+ return
1721
+ compositor_globals = vars(modules["textual._compositor"])
1722
+ compositor_globals.setdefault("DOMNode", vars(modules["textual.dom"])["DOMNode"])
1723
+ compositor_globals.setdefault("_VERIFY_REUSE", False)
1724
+ for members in staged:
1725
+ members.install(_RUNTIME_PATCH_MARKER)
1726
+
1727
+
1728
+ def reflow_reusing_records(compositor: Compositor, root: Widget, size: Size) -> ReflowResult:
1729
+ """``compositor.reflow(root, size)`` that replays every subtree unchanged since the last full arrangement.
1730
+
1731
+ For a caller that resynchronizes the compositor outside a layout pass after changes that stamp
1732
+ their widgets (a raw style-rule write does): only the stamped widgets' ancestor paths are
1733
+ arranged again, instead of every displayed widget. Without the installed patch (another
1734
+ Textual version) this is the stock reflow, which arranges the whole tree.
1735
+ """
1736
+ if _reflow_reuses_records(compositor):
1737
+ return compositor.reflow(root, size, reuse=True)
1738
+ return compositor.reflow(root, size)
1739
+
1740
+
1741
+ class _ReusingCompositor(Protocol):
1742
+ """A compositor whose ``reflow`` is the installed one, which Textual's own signature does not describe."""
1743
+
1744
+ def reflow(self, parent: Widget, size: Size, reuse: bool = False) -> ReflowResult: ...
1745
+
1746
+
1747
+ def _reflow_reuses_records(compositor: Compositor) -> TypeGuard[_ReusingCompositor]:
1748
+ """Whether ``compositor.reflow`` is the installed reusing reflow, which takes ``reuse``."""
1749
+ return bool(vars(type(compositor).reflow).get(_RUNTIME_PATCH_MARKER, False))
1750
+
1751
+
1752
+ def _module_name(patch: FilePatch) -> str:
1753
+ return "textual." + patch.module_file.removesuffix(".py").replace("/", ".")