composeui 0.1.0__tar.gz

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 (251) hide show
  1. composeui-0.1.0/.gitignore +20 -0
  2. composeui-0.1.0/.python-version +1 -0
  3. composeui-0.1.0/AGENTS.md +206 -0
  4. composeui-0.1.0/CHANGELOG.md +30 -0
  5. composeui-0.1.0/LICENSE +9 -0
  6. composeui-0.1.0/MANIFEST.in +7 -0
  7. composeui-0.1.0/Makefile +206 -0
  8. composeui-0.1.0/PKG-INFO +222 -0
  9. composeui-0.1.0/README.md +165 -0
  10. composeui-0.1.0/ROADMAP.md +252 -0
  11. composeui-0.1.0/css/app.css +438 -0
  12. composeui-0.1.0/css/uPlot.min.css +1 -0
  13. composeui-0.1.0/docs/_ext/widgetcatalog.py +78 -0
  14. composeui-0.1.0/docs/_site/llms.txt +46 -0
  15. composeui-0.1.0/docs/_site/skills/composeui/SKILL.md +140 -0
  16. composeui-0.1.0/docs/_static/composeui-icon.svg +17 -0
  17. composeui-0.1.0/docs/_static/composeui-logo-dark.svg +19 -0
  18. composeui-0.1.0/docs/_static/composeui-logo-light.svg +19 -0
  19. composeui-0.1.0/docs/api/application.md +5 -0
  20. composeui-0.1.0/docs/api/backends.md +23 -0
  21. composeui-0.1.0/docs/api/chartimage.md +5 -0
  22. composeui-0.1.0/docs/api/dataframesource.md +5 -0
  23. composeui-0.1.0/docs/api/index.md +31 -0
  24. composeui-0.1.0/docs/api/jobs.md +5 -0
  25. composeui-0.1.0/docs/api/model.md +5 -0
  26. composeui-0.1.0/docs/api/registry.md +5 -0
  27. composeui-0.1.0/docs/api/reload.md +5 -0
  28. composeui-0.1.0/docs/api/renderer.md +5 -0
  29. composeui-0.1.0/docs/api/signal.md +5 -0
  30. composeui-0.1.0/docs/api/sqlitemodel.md +5 -0
  31. composeui-0.1.0/docs/api/study.md +5 -0
  32. composeui-0.1.0/docs/api/translation.md +5 -0
  33. composeui-0.1.0/docs/api/translationcheck.md +5 -0
  34. composeui-0.1.0/docs/api/view.md +5 -0
  35. composeui-0.1.0/docs/conf.py +83 -0
  36. composeui-0.1.0/docs/examples-guide.md +509 -0
  37. composeui-0.1.0/docs/index.md +92 -0
  38. composeui-0.1.0/docs/screenshots.py +478 -0
  39. composeui-0.1.0/docs/technical-guide.md +608 -0
  40. composeui-0.1.0/docs/user-guide/building-views.md +371 -0
  41. composeui-0.1.0/docs/user-guide/charts.md +153 -0
  42. composeui-0.1.0/docs/user-guide/coding-agents.md +33 -0
  43. composeui-0.1.0/docs/user-guide/core-concepts.md +52 -0
  44. composeui-0.1.0/docs/user-guide/csv-import.md +210 -0
  45. composeui-0.1.0/docs/user-guide/maps.md +97 -0
  46. composeui-0.1.0/docs/user-guide/organizing-the-window.md +459 -0
  47. composeui-0.1.0/docs/user-guide/running-the-app.md +248 -0
  48. composeui-0.1.0/docs/user-guide/sqlite-model.md +153 -0
  49. composeui-0.1.0/docs/user-guide/tables.md +417 -0
  50. composeui-0.1.0/docs/user-guide/testing.md +274 -0
  51. composeui-0.1.0/docs/user-guide/threads.md +207 -0
  52. composeui-0.1.0/docs/user-guide/translation.md +107 -0
  53. composeui-0.1.0/docs/user-guide/widgets.md +247 -0
  54. composeui-0.1.0/docs/user-guide.md +132 -0
  55. composeui-0.1.0/docs/widgets.md +24 -0
  56. composeui-0.1.0/make.ps1 +349 -0
  57. composeui-0.1.0/pyproject.toml +223 -0
  58. composeui-0.1.0/setup.cfg +4 -0
  59. composeui-0.1.0/src/composeui/__init__.py +11 -0
  60. composeui-0.1.0/src/composeui/core/__init__.py +0 -0
  61. composeui-0.1.0/src/composeui/core/application.py +368 -0
  62. composeui-0.1.0/src/composeui/core/chartcolors.py +23 -0
  63. composeui-0.1.0/src/composeui/core/chartimage.py +336 -0
  64. composeui-0.1.0/src/composeui/core/dataframesource.py +307 -0
  65. composeui-0.1.0/src/composeui/core/jobs.py +426 -0
  66. composeui-0.1.0/src/composeui/core/model.py +66 -0
  67. composeui-0.1.0/src/composeui/core/registry.py +66 -0
  68. composeui-0.1.0/src/composeui/core/reload.py +476 -0
  69. composeui-0.1.0/src/composeui/core/renderer.py +193 -0
  70. composeui-0.1.0/src/composeui/core/signal.py +358 -0
  71. composeui-0.1.0/src/composeui/core/sqlitemodel.py +1183 -0
  72. composeui-0.1.0/src/composeui/core/study.py +121 -0
  73. composeui-0.1.0/src/composeui/core/translation.py +221 -0
  74. composeui-0.1.0/src/composeui/core/translationcheck.py +254 -0
  75. composeui-0.1.0/src/composeui/core/view.py +5508 -0
  76. composeui-0.1.0/src/composeui/images/composeui.svg +15 -0
  77. composeui-0.1.0/src/composeui/locales/fr.json +73 -0
  78. composeui-0.1.0/src/composeui/mypy.py +46 -0
  79. composeui-0.1.0/src/composeui/py.typed +0 -0
  80. composeui-0.1.0/src/composeui/qtcore/__init__.py +0 -0
  81. composeui-0.1.0/src/composeui/qtcore/maprenderer.py +115 -0
  82. composeui-0.1.0/src/composeui/qtcore/qtapplication.py +222 -0
  83. composeui-0.1.0/src/composeui/qtcore/qtrenderer.py +2862 -0
  84. composeui-0.1.0/src/composeui/qtcore/widgets/__init__.py +0 -0
  85. composeui-0.1.0/src/composeui/qtcore/widgets/map.qml +123 -0
  86. composeui-0.1.0/src/composeui/qtcore/widgets/qchart.py +393 -0
  87. composeui-0.1.0/src/composeui/qtcore/widgets/qdatefield.py +101 -0
  88. composeui-0.1.0/src/composeui/qtcore/widgets/qexpander.py +60 -0
  89. composeui-0.1.0/src/composeui/qtcore/widgets/qfilepathfield.py +34 -0
  90. composeui-0.1.0/src/composeui/qtcore/widgets/qfiltertable.py +394 -0
  91. composeui-0.1.0/src/composeui/qtcore/widgets/qformdialog.py +64 -0
  92. composeui-0.1.0/src/composeui/qtcore/widgets/qformlabel.py +53 -0
  93. composeui-0.1.0/src/composeui/qtcore/widgets/qmap.py +68 -0
  94. composeui-0.1.0/src/composeui/qtcore/widgets/qmarkicon.py +37 -0
  95. composeui-0.1.0/src/composeui/qtcore/widgets/qmetric.py +44 -0
  96. composeui-0.1.0/src/composeui/qtcore/widgets/qplaintext.py +29 -0
  97. composeui-0.1.0/src/composeui/qtcore/widgets/qradiogroup.py +57 -0
  98. composeui-0.1.0/src/composeui/qtcore/widgets/qsidebar.py +116 -0
  99. composeui-0.1.0/src/composeui/qtcore/widgets/qsourcetable.py +216 -0
  100. composeui-0.1.0/src/composeui/qtcore/widgets/qsplitter.py +69 -0
  101. composeui-0.1.0/src/composeui/qtcore/widgets/qtexttable.py +703 -0
  102. composeui-0.1.0/src/composeui/qtcore/widgets/qvaluecombobox.py +41 -0
  103. composeui-0.1.0/src/composeui/qtcore/widgets/qvalueslider.py +64 -0
  104. composeui-0.1.0/src/composeui/qtcore/widgets/qwindowrelease.py +32 -0
  105. composeui-0.1.0/src/composeui/textualcore/__init__.py +0 -0
  106. composeui-0.1.0/src/composeui/textualcore/textualapplication.py +121 -0
  107. composeui-0.1.0/src/composeui/textualcore/textualrenderer.py +2893 -0
  108. composeui-0.1.0/src/composeui/textualcore/widgets/__init__.py +0 -0
  109. composeui-0.1.0/src/composeui/textualcore/widgets/composebutton.py +17 -0
  110. composeui-0.1.0/src/composeui/textualcore/widgets/composechart.py +212 -0
  111. composeui-0.1.0/src/composeui/textualcore/widgets/composecheckbox.py +35 -0
  112. composeui-0.1.0/src/composeui/textualcore/widgets/composecollapsible.py +41 -0
  113. composeui-0.1.0/src/composeui/textualcore/widgets/composecombobox.py +54 -0
  114. composeui-0.1.0/src/composeui/textualcore/widgets/composedateinput.py +325 -0
  115. composeui-0.1.0/src/composeui/textualcore/widgets/composedialog.py +90 -0
  116. composeui-0.1.0/src/composeui/textualcore/widgets/composefilepathinput.py +85 -0
  117. composeui-0.1.0/src/composeui/textualcore/widgets/composefiltertable.py +367 -0
  118. composeui-0.1.0/src/composeui/textualcore/widgets/composeformdialog.py +86 -0
  119. composeui-0.1.0/src/composeui/textualcore/widgets/composeinput.py +50 -0
  120. composeui-0.1.0/src/composeui/textualcore/widgets/composelist.py +20 -0
  121. composeui-0.1.0/src/composeui/textualcore/widgets/composemarkdown.py +17 -0
  122. composeui-0.1.0/src/composeui/textualcore/widgets/composemetric.py +60 -0
  123. composeui-0.1.0/src/composeui/textualcore/widgets/composeradioset.py +54 -0
  124. composeui-0.1.0/src/composeui/textualcore/widgets/composesidebar.py +111 -0
  125. composeui-0.1.0/src/composeui/textualcore/widgets/composeslider.py +144 -0
  126. composeui-0.1.0/src/composeui/textualcore/widgets/composesourcetable.py +288 -0
  127. composeui-0.1.0/src/composeui/textualcore/widgets/composesplitter.py +211 -0
  128. composeui-0.1.0/src/composeui/textualcore/widgets/composetabbedcontent.py +35 -0
  129. composeui-0.1.0/src/composeui/textualcore/widgets/composetable.py +476 -0
  130. composeui-0.1.0/src/composeui/textualcore/widgets/composetoolbar.py +62 -0
  131. composeui-0.1.0/src/composeui/textualcore/widgets/composetree.py +139 -0
  132. composeui-0.1.0/src/composeui/webcore/__init__.py +0 -0
  133. composeui-0.1.0/src/composeui/webcore/headers.py +57 -0
  134. composeui-0.1.0/src/composeui/webcore/icons.py +58 -0
  135. composeui-0.1.0/src/composeui/webcore/limits.py +55 -0
  136. composeui-0.1.0/src/composeui/webcore/origin.py +63 -0
  137. composeui-0.1.0/src/composeui/webcore/page.py +146 -0
  138. composeui-0.1.0/src/composeui/webcore/routes.py +303 -0
  139. composeui-0.1.0/src/composeui/webcore/session.py +436 -0
  140. composeui-0.1.0/src/composeui/webcore/static/app.css +2 -0
  141. composeui-0.1.0/src/composeui/webcore/static/cally.js +1312 -0
  142. composeui-0.1.0/src/composeui/webcore/static/daisyui.LICENSE +21 -0
  143. composeui-0.1.0/src/composeui/webcore/static/deckgl.LICENSE +19 -0
  144. composeui-0.1.0/src/composeui/webcore/static/deckgl.min.js +6423 -0
  145. composeui-0.1.0/src/composeui/webcore/static/htmx.min.js +1 -0
  146. composeui-0.1.0/src/composeui/webcore/static/page.js +1175 -0
  147. composeui-0.1.0/src/composeui/webcore/static/uPlot.LICENSE +21 -0
  148. composeui-0.1.0/src/composeui/webcore/static/uPlot.iife.min.js +2 -0
  149. composeui-0.1.0/src/composeui/webcore/webapplication.py +495 -0
  150. composeui-0.1.0/src/composeui/webcore/webrenderer.py +5113 -0
  151. composeui-0.1.0/src/composeui.egg-info/PKG-INFO +222 -0
  152. composeui-0.1.0/src/composeui.egg-info/SOURCES.txt +249 -0
  153. composeui-0.1.0/src/composeui.egg-info/dependency_links.txt +1 -0
  154. composeui-0.1.0/src/composeui.egg-info/requires.txt +30 -0
  155. composeui-0.1.0/src/composeui.egg-info/scm_file_list.json +291 -0
  156. composeui-0.1.0/src/composeui.egg-info/scm_version.json +8 -0
  157. composeui-0.1.0/src/composeui.egg-info/top_level.txt +1 -0
  158. composeui-0.1.0/src/examples/__init__.py +0 -0
  159. composeui-0.1.0/src/examples/batman.py +140 -0
  160. composeui-0.1.0/src/examples/largetable.py +219 -0
  161. composeui-0.1.0/src/examples/locales/pipeform/fr.json +37 -0
  162. composeui-0.1.0/src/examples/locales/pipesweep/fr.json +22 -0
  163. composeui-0.1.0/src/examples/locales/taskview/fr.json +31 -0
  164. composeui-0.1.0/src/examples/markdownviewer.py +91 -0
  165. composeui-0.1.0/src/examples/pipecatalogue.py +584 -0
  166. composeui-0.1.0/src/examples/pipechart.py +455 -0
  167. composeui-0.1.0/src/examples/pipeform.py +356 -0
  168. composeui-0.1.0/src/examples/pipesweep.py +292 -0
  169. composeui-0.1.0/src/examples/py.typed +0 -0
  170. composeui-0.1.0/src/examples/taskview.py +321 -0
  171. composeui-0.1.0/src/examples/textual_demo.py +79 -0
  172. composeui-0.1.0/src/examples/uberpickups.py +208 -0
  173. composeui-0.1.0/src/examples/uberpickupsframe.py +136 -0
  174. composeui-0.1.0/src/examples/webhello.py +141 -0
  175. composeui-0.1.0/tests/__init__.py +0 -0
  176. composeui-0.1.0/tests/bookings.py +28 -0
  177. composeui-0.1.0/tests/buildings.py +83 -0
  178. composeui-0.1.0/tests/charts.py +93 -0
  179. composeui-0.1.0/tests/datafiles.py +25 -0
  180. composeui-0.1.0/tests/dialogs.py +55 -0
  181. composeui-0.1.0/tests/expander.py +45 -0
  182. composeui-0.1.0/tests/formdialog.py +71 -0
  183. composeui-0.1.0/tests/grid.py +44 -0
  184. composeui-0.1.0/tests/horizontal.py +72 -0
  185. composeui-0.1.0/tests/hours.py +22 -0
  186. composeui-0.1.0/tests/listview.py +80 -0
  187. composeui-0.1.0/tests/locales/fr.json +11 -0
  188. composeui-0.1.0/tests/maps.py +68 -0
  189. composeui-0.1.0/tests/markdown.py +51 -0
  190. composeui-0.1.0/tests/markup.py +47 -0
  191. composeui-0.1.0/tests/metrics.py +23 -0
  192. composeui-0.1.0/tests/pages.py +75 -0
  193. composeui-0.1.0/tests/partial.py +118 -0
  194. composeui-0.1.0/tests/sidebar.py +36 -0
  195. composeui-0.1.0/tests/splitter.py +72 -0
  196. composeui-0.1.0/tests/tables.py +280 -0
  197. composeui-0.1.0/tests/tabs.py +46 -0
  198. composeui-0.1.0/tests/test_application.py +311 -0
  199. composeui-0.1.0/tests/test_batman.py +27 -0
  200. composeui-0.1.0/tests/test_browser.py +2377 -0
  201. composeui-0.1.0/tests/test_chartimage.py +224 -0
  202. composeui-0.1.0/tests/test_csv_typing.py +141 -0
  203. composeui-0.1.0/tests/test_csv_url.py +130 -0
  204. composeui-0.1.0/tests/test_csvsqlitetable.py +561 -0
  205. composeui-0.1.0/tests/test_dataframe_source.py +226 -0
  206. composeui-0.1.0/tests/test_dialog.py +57 -0
  207. composeui-0.1.0/tests/test_docs.py +189 -0
  208. composeui-0.1.0/tests/test_examples.py +65 -0
  209. composeui-0.1.0/tests/test_form_dialog.py +84 -0
  210. composeui-0.1.0/tests/test_form_dialog_typing.py +80 -0
  211. composeui-0.1.0/tests/test_grid.py +39 -0
  212. composeui-0.1.0/tests/test_jobs.py +639 -0
  213. composeui-0.1.0/tests/test_largetable.py +200 -0
  214. composeui-0.1.0/tests/test_list_view.py +96 -0
  215. composeui-0.1.0/tests/test_llms_txt.py +37 -0
  216. composeui-0.1.0/tests/test_maps.py +108 -0
  217. composeui-0.1.0/tests/test_markdownviewer.py +118 -0
  218. composeui-0.1.0/tests/test_pipecatalogue.py +328 -0
  219. composeui-0.1.0/tests/test_pipechart.py +158 -0
  220. composeui-0.1.0/tests/test_pipesweep.py +193 -0
  221. composeui-0.1.0/tests/test_pipeview.py +192 -0
  222. composeui-0.1.0/tests/test_qtapplication.py +38 -0
  223. composeui-0.1.0/tests/test_qtrenderer.py +3788 -0
  224. composeui-0.1.0/tests/test_record_table.py +245 -0
  225. composeui-0.1.0/tests/test_reload.py +1010 -0
  226. composeui-0.1.0/tests/test_renderer.py +69 -0
  227. composeui-0.1.0/tests/test_renderer_registry.py +77 -0
  228. composeui-0.1.0/tests/test_router.py +104 -0
  229. composeui-0.1.0/tests/test_signal.py +158 -0
  230. composeui-0.1.0/tests/test_source_table.py +543 -0
  231. composeui-0.1.0/tests/test_sqlite_source.py +353 -0
  232. composeui-0.1.0/tests/test_sqlitemodel.py +980 -0
  233. composeui-0.1.0/tests/test_statusbar_thread_safety.py +63 -0
  234. composeui-0.1.0/tests/test_table_looks.py +78 -0
  235. composeui-0.1.0/tests/test_table_sort_filter.py +300 -0
  236. composeui-0.1.0/tests/test_table_source.py +158 -0
  237. composeui-0.1.0/tests/test_taskview.py +214 -0
  238. composeui-0.1.0/tests/test_textual_renderer.py +3955 -0
  239. composeui-0.1.0/tests/test_translation.py +390 -0
  240. composeui-0.1.0/tests/test_translationcheck.py +206 -0
  241. composeui-0.1.0/tests/test_uberpickups.py +323 -0
  242. composeui-0.1.0/tests/test_uberpickupsframe.py +154 -0
  243. composeui-0.1.0/tests/test_validation.py +131 -0
  244. composeui-0.1.0/tests/test_view_state.py +872 -0
  245. composeui-0.1.0/tests/test_views.py +198 -0
  246. composeui-0.1.0/tests/test_webrenderer.py +5680 -0
  247. composeui-0.1.0/tests/toolbar.py +49 -0
  248. composeui-0.1.0/tests/translated.py +88 -0
  249. composeui-0.1.0/tests/unlabelled.py +20 -0
  250. composeui-0.1.0/tests/vertical.py +46 -0
  251. composeui-0.1.0/tests/workers.py +32 -0
@@ -0,0 +1,20 @@
1
+ # Python-generated files
2
+ __pycache__/
3
+ *.py[oc]
4
+ build/
5
+ dist/
6
+ wheels/
7
+ *.egg-info
8
+
9
+ # Virtual environments
10
+ .venv
11
+
12
+ # coverage
13
+ *.coverage
14
+
15
+ *.pipe
16
+ # downloaded by `make css`, not committed
17
+ css/vendor/
18
+
19
+ # built by `make docs`
20
+ docs/_build/
@@ -0,0 +1 @@
1
+ 3.9
@@ -0,0 +1,206 @@
1
+ # AGENTS.md
2
+
3
+ ComposeUI is a small, framework-agnostic MVVM layer for building UIs in Python. Views are
4
+ typed `attrs` classes; a `Renderer` binds each view to a toolkit widget; `Signal` objects
5
+ carry events between view, model and application code. See `README.md` for the overview and
6
+ quickstart, `docs/user-guide/` for each feature, `docs/examples-guide.md` for the example apps, and
7
+ `docs/technical-guide.md` for the architecture and development.
8
+
9
+ ## Setup
10
+
11
+ - Package manager: [uv](https://docs.astral.sh/uv/). Python `>=3.9` (`.python-version` pins 3.9).
12
+ - Install everything needed for development:
13
+
14
+ ```bash
15
+ uv sync --group dev --all-extras # extras: pyside6, tui (Textual), web (Starlette), report, reload, dataframe
16
+ ```
17
+
18
+ - Always run tools through `uv run` (or the `make` targets below), never a global Python.
19
+
20
+ ## Commands
21
+
22
+ ```bash
23
+ make lint # ruff format + ruff check --fix
24
+ make mypy # mypy on composeui, examples and tests, Qt binding resolved to PySide6
25
+ make typecheck # mypy on source + tests; Pyright, Pyrefly and ty on tests
26
+ make pyright pyrefly ty # individual test checkers
27
+ make zuban # optional test checker; currently reports known incompatibilities
28
+ make test # pytest tests in parallel, Qt offscreen, with coverage (browser tests excluded)
29
+ make browser-test # the web backend in headless Chromium (tests/test_browser.py, Playwright)
30
+ make translations # each app's catalogs against the text it translates (TRANSLATED_MAIN_VIEWS)
31
+ make check # lint + mypy + translations
32
+ make all # clean + check + test
33
+ make css # rebuild webcore/static/app.css after changing classes in webcore (commit it)
34
+ make web-deps # htmx, uPlot, Cally and deck.gl from npm at pinned versions and SHA-256 (commit them)
35
+ make docs # Sphinx + MyST site in docs/_build/html, warnings as errors (needs `--group docs`)
36
+ make screenshots # docs/images/<example>-<backend>.png for the docs and README (Chromium)
37
+ ```
38
+
39
+ On Windows without `make`, run the same targets in PowerShell with `make.ps1`
40
+ (`.\make.ps1 check test`; `-Examples pipeform, batman` for `EXAMPLES`, `-TailwindPlatform`
41
+ for `TAILWIND_PLATFORM`). Keep it in step with the `Makefile`, including its
42
+ `$TranslatedMainViews` list (`TRANSLATED_MAIN_VIEWS`).
43
+
44
+ `make mypy` checks source, examples and tests together. It installs the `browser` group and
45
+ all extras so every test import resolves; Chromium is not needed. The other checker targets
46
+ also install the `typecheck` group. The same targets are available in `make.ps1`.
47
+ Zuban is excluded from the aggregate target and retains its nonzero exit status when
48
+ diagnostics remain.
49
+
50
+ Run a single test file or test:
51
+
52
+ ```bash
53
+ QT_QPA_PLATFORM=offscreen uv run pytest tests/test_qtrenderer.py -k combobox
54
+ ```
55
+
56
+ `QT_QPA_PLATFORM=offscreen` is required for Qt tests in a headless environment (CI, WSL,
57
+ agents). Run the examples with `uv run python -m examples.pipeform --backend qt` (or
58
+ `textual`, or `web`) or `uv run python -m examples.textual_demo` (Textual).
59
+
60
+ ## Project layout
61
+
62
+ ```
63
+ src/composeui/
64
+ core/ # toolkit-agnostic: views, signals, renderer base, application, registry
65
+ qtcore/ # Qt backend via qtpy (QtApplication, qtrenderer.py, custom widgets/)
66
+ textualcore/ # Textual (TUI) backend
67
+ webcore/ # Web backend (Starlette + htmx + daisyUI); static/app.css is built
68
+ css/ # source of webcore/static/app.css (`make css`; tools land in css/vendor/)
69
+ src/examples/ # example apps (pipeform.py, pipecatalogue.py, ...), also used by tests; not in the wheel
70
+ tests/ # pytest suite (pytest-qt, pytest-asyncio, Textual run_test pilot)
71
+ docs/ # user guide, examples guide, technical guide; Sphinx site (conf.py, api/, _ext/)
72
+ ```
73
+
74
+ ## Architecture rules
75
+
76
+ - **`composeui.core` must never import a backend** (Qt, Textual, Starlette) at import time.
77
+ Backends are resolved lazily through `composeui.core.registry`.
78
+ - **Backend registration:** each `RendererType` member's value is a `Backend`: the dotted
79
+ paths of the backend's renderers module, exposing a
80
+ `RENDERERS: dict[str, Callable[[Any], Renderer]]` mapping, and of its application module,
81
+ exposing `APPLICATION` and `MODEL_APPLICATION` (what `core.application.run` starts),
82
+ plus the extra that installs its dependencies and their top-level modules (`run` names
83
+ the extra when one is missing). A view picks its renderer by its `_renderer_key` class variable. A backend that doesn't
84
+ register a key simply doesn't render that view (returns `None`), so partial backends are
85
+ fine.
86
+ - **Adding a widget:** define the view in `core/view.py` (set `_renderer_key`), then add a
87
+ renderer + `RENDERERS` entry in each backend that should support it, plus tests per backend.
88
+ - **Adding a backend:** add one `RendererType` member in `core/registry.py` (with its extra
89
+ in `pyproject.toml`), one
90
+ `RENDERERS` mapping in the new backend's renderers module, and its application classes
91
+ (built from `build_main_view=`, plus `build_model=`/`setup_model=` for the model one) as
92
+ `APPLICATION`/`MODEL_APPLICATION`. `core/view.py` should not need changes.
93
+ - **Renderer contract:** implement `render`, `refresh_attribute` and the
94
+ `_refresh_dispatcher` (one handler per view attribute the renderer cares about; a missing
95
+ handler raises `MissingRefreshAttributeFunctionError`). The user's changes reach the view
96
+ from the widget's signals, set inside `stop_refresh()`.
97
+ - Views are reactive: setting a view attribute pushes the change to its renderer. Don't add
98
+ manual `update()`/`refresh()` calls.
99
+ - `Signal.connect` injects `main_view` and `model`, and `Application.connect` also `view`, as
100
+ keyword arguments based on the callback's parameter names; keep those names exact.
101
+ - View state is (de)serialized with `cattrs` (`MainView.save_state` / `load_state`); new view
102
+ fields must remain serializable, or be excluded like `Signal`/`Renderer`/`Validator` fields.
103
+
104
+ ## Code style
105
+
106
+ - `ruff` with `select = ["ALL"]` and line length 100; see `pyproject.toml` for the ignore list.
107
+ Fix lint properly rather than adding `noqa`, unless it is clearly justified.
108
+ - `mypy` must pass. Fully type-annotate new code; the package ships `py.typed`.
109
+ - Every Python module starts with `from __future__ import annotations` (after its docstring).
110
+ Ruff enforces it (`required-imports`, rule I002) and `make lint` adds it to a new file.
111
+ Annotations are then strings, so: don't quote them; define views that `attrs`/`cattrs`
112
+ resolve (`attrs.resolve_types`) at module level, not inside a function, so their field
113
+ types resolve from the module globals; and give `cattrs` hooks their type explicitly
114
+ (`CONVERTER.register_structure_hook(cls, func)`) rather than as a bare decorator, which
115
+ reads the annotation.
116
+ - Stay compatible with Python 3.9: use `Optional[...]`/`Union[...]`, not `X | Y`, in
117
+ annotations that `attrs`/`cattrs` evaluate at runtime (view fields), since `X | Y` fails
118
+ there on 3.9 (ruff's `keep-runtime-typing` stops pyupgrade from rewriting them).
119
+ - Use `qtpy` imports, never `PySide6`/`PyQt*` directly, in `qtcore`.
120
+ - In `webcore`, build HTML with `Markup(template).format(...)`, never an f-string: it
121
+ escapes every value (see the `web-security` skill).
122
+ - Put imports only needed for typing under `if TYPE_CHECKING:`.
123
+ - The public API is the `__all__` of each `composeui.core` module: the API reference shows
124
+ only it, and `tests/test_docs.py` checks that the docs and examples import nothing else.
125
+ Add a name that apps use to it. The other names, also those the backends import, can
126
+ change in any release.
127
+ - Match the existing comment density: brief docstrings, comments explain *why*.
128
+
129
+ ## Permission for documentation and tests
130
+
131
+ - Ask the user for explicit permission before creating, editing, moving, or deleting
132
+ documentation or tests.
133
+ - Permission to implement a feature or fix a bug does not authorize documentation or test
134
+ changes. Ask first when those changes are needed by the rules below.
135
+
136
+ ## Testing
137
+
138
+ - Every behavior change needs a test in `tests/`. Backend tests should exercise real rendered
139
+ widgets (`qtbot` for Qt, `App.run_test()` for Textual, `httpx`/Starlette test client for web).
140
+ - Tests for optional backends guard with `pytest.importorskip(...)`.
141
+ - `make test` runs the tests in parallel, a worker per CPU (pytest-xdist): a test must not
142
+ depend on another's state or order. Without `-n`, `pytest` runs them one after another.
143
+ - What only a browser shows (the page's scripts, htmx swaps) goes in `tests/test_browser.py`,
144
+ marked `browser`. It needs the `browser` group and a Chromium:
145
+ `uv run --group browser playwright install chromium` once, then `make browser-test`.
146
+ - `make test` fails below 100% coverage of lines and branches (`fail_under` in
147
+ `pyproject.toml`, child processes included). A new line comes with its test, or with a
148
+ `# pragma: no cover` that says why no test reaches it. A line only POSIX runs gets a
149
+ `# posix only` comment, which coverage leaves out on Windows (`make.ps1`).
150
+ - `make check test` must be clean before committing; CI (`.github/workflows/ci.yml`) runs the
151
+ same checks on Python 3.9 to 3.14 for every pull request and push to `main`, and the tests
152
+ on Windows (`.\make.ps1 translations test`) and macOS (`make translations test`), on
153
+ Python 3.9 and 3.14. A change of
154
+ the documentation only (`docs/`, Markdown files) runs only the docs build and its lint.
155
+ The existing mypy matrix also checks tests. Separate CI jobs run Pyright, Pyrefly and ty
156
+ on tests on Python 3.9. Zuban runs in an additional non-blocking job while its known
157
+ incompatibilities remain.
158
+
159
+ ## Documentation
160
+
161
+ Load the `documentation-style` skill before you write or review documentation: `README.md`,
162
+ `docs/`, docstrings, `ROADMAP.md`, this file, or a skill. It holds the writing rules, which
163
+ document owns what, and the checks.
164
+
165
+ - **Change the docs in the commit that changes the behavior.** A change that users see gets
166
+ a line under "Unreleased" in `CHANGELOG.md`. A new view gets a section
167
+ in its page of `docs/user-guide/` and a class docstring, which the widget catalog shows. In
168
+ `ROADMAP.md`, add it to "Already there" and remove its entry. A new example gets its
169
+ section, its overview row, its contents entry, and its screenshots in
170
+ `docs/examples-guide.md`. When a name is renamed or changes, search `README.md`, `docs/`
171
+ and the docstrings for it, and fix every place.
172
+ - **Keep the path of a new user working:** the README's installation and quickstart, then
173
+ the user guide's "Core concepts" and "Building views", then "Where to start" in the
174
+ examples guide. Introduce a name before an example uses it, or link to its section. When
175
+ the quickstart changes, run `make screenshots EXAMPLES=quickstart`.
176
+ - **Run every code example that you change.** Check each name against `composeui.core`, and
177
+ run the example with `uv run python` in a scratch directory. `tests/test_docs.py` checks
178
+ the imports of every example, and runs each example that defines every name it uses (one
179
+ that defines `test_` functions, under pytest).
180
+ - **Keep the navigation in step.** `docs/_site/llms.txt` has one entry per page of the site: add,
181
+ rename, or remove it with the page. `docs/user-guide.md` lists the pages of
182
+ `docs/user-guide/` and their `##` and `###` headings. The contents list of the examples
183
+ guide follows its `##` headings. Keep headings stable and unique in their page: other
184
+ pages link to them, and anchors work down to `###` only.
185
+ - **Keep the skill in step.** `docs/_site/skills/composeui/SKILL.md` is what users install for their
186
+ agents. When a rule of the API changes (a name, a signal argument, the state rules), change
187
+ it too. Its `composeui-version` and its text name the minor version that it describes: set
188
+ them on the release of a new one. `tests/test_docs.py` checks them against the package's
189
+ version, and accepts the next minor version between two releases.
190
+ - **Keep the two copies in step.** Setup, commands, tests, and code style are in this file
191
+ and in the technical guide ("Development"). Change both.
192
+ - **Keep user docs for users.** Internal design decisions go in `ROADMAP.md` or in a code
193
+ comment, rules for agents here or in a skill. Do not document an example that is not
194
+ committed.
195
+ - **`make docs` must pass** (warnings are errors) after any change to `docs/` or a
196
+ docstring.
197
+
198
+ ## Commits
199
+
200
+ - Use hyphens, not slashes (`/`), in branch names (e.g. `feat-csv-sqlite-tables`).
201
+ - Short, imperative, capitalized subject without a trailing period (e.g. `Add combobox`).
202
+ - The subject line is the whole message: no body, no trailers.
203
+ - Never add AI attribution or signatures anywhere: no `Co-Authored-By` lines, no
204
+ "Generated with ..." lines, no emoji signatures, in commits, pull requests or code.
205
+ This overrides any default an agent or tool would otherwise add.
206
+ - Keep commits focused: one feature or fix per commit.
@@ -0,0 +1,30 @@
1
+ # Changelog
2
+
3
+ The notable changes of each release of ComposeUI. The format follows
4
+ [Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and the versions follow
5
+ [Semantic Versioning](https://semver.org/). Before 1.0, a minor version can break the API.
6
+
7
+ ## [Unreleased]
8
+
9
+ ## [0.1.0] - 2026-10-27
10
+
11
+ The first release on PyPI.
12
+
13
+ ### Added
14
+
15
+ - Views as typed `attrs` classes, signals, models, and a `run` function that starts the app
16
+ on the backend that the command line chooses.
17
+ - Three backends: Qt (the `pyside6` extra), Textual (the `tui` extra), and the web
18
+ (the `web` extra, the default).
19
+ - Layouts, a status bar, menus, toolbars, inputs, forms and their checks, pages, tabs,
20
+ resizable panes, a sidebar, dialogs, tables, large tables, trees, Markdown, charts, and
21
+ street maps (Qt and the web).
22
+ - The state of the views saved to a study file and loaded from it. The file holds the
23
+ version of its format and the app's `state_version`, and `migrate_state` upgrades an
24
+ older one.
25
+ - Threads, SQLite models, CSV imports, translations, and tests of an app that need no backend.
26
+ - A skill for coding agents that write a ComposeUI app.
27
+ - `composeui.__version__`, the version of the installed package.
28
+
29
+ [Unreleased]: https://github.com/ehmoussi/composeui/compare/v0.1.0...HEAD
30
+ [0.1.0]: https://github.com/ehmoussi/composeui/releases/tag/v0.1.0
@@ -0,0 +1,9 @@
1
+ The MIT License (MIT)
2
+
3
+ Copyright (c) 2026 El Hadi Moussi
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6
+
7
+ The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8
+
9
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
@@ -0,0 +1,7 @@
1
+ # The sdist builds the package and runs its tests. The screenshots of the docs, the lock
2
+ # file and the agents' and CI's files stay in the repository.
3
+ prune docs/images
4
+ prune .agents
5
+ prune .claude
6
+ prune .github
7
+ exclude uv.lock
@@ -0,0 +1,206 @@
1
+ .PHONY: format
2
+ format:
3
+ uv run ruff format
4
+
5
+ .PHONY: check
6
+ check:
7
+ uv run ruff check
8
+
9
+ .PHONY: check_fix
10
+ check_fix:
11
+ uv run ruff check --fix
12
+
13
+ .PHONY: lint
14
+ lint: format check_fix
15
+
16
+ .PHONY: mypy
17
+ mypy:
18
+ uv run --locked --group browser --all-extras mypy \
19
+ --config-file pyproject.toml \
20
+ --package composeui \
21
+ --package examples \
22
+ --package tests \
23
+ --always-false=PYQT5 \
24
+ --always-false=PYSIDE2 \
25
+ --always-false=PYQT6 \
26
+ --always-true=PYSIDE6 \
27
+ --show-traceback
28
+
29
+ # Tests import every backend and Playwright; static checks need their packages, not Chromium.
30
+ TYPECHECK_RUN = uv run --locked --group typecheck --group browser --all-extras
31
+
32
+ .PHONY: typecheck pyright pyrefly ty zuban
33
+ typecheck: mypy pyright pyrefly ty
34
+
35
+ pyright:
36
+ $(TYPECHECK_RUN) pyright
37
+
38
+ pyrefly:
39
+ $(TYPECHECK_RUN) pyrefly check
40
+
41
+ ty:
42
+ $(TYPECHECK_RUN) ty check
43
+
44
+ # Keep its nonzero exit visible locally; CI runs Zuban as a non-blocking job.
45
+ zuban:
46
+ $(TYPECHECK_RUN) zuban mypy tests
47
+
48
+ # the main views of the apps with catalogs, as module:Class
49
+ TRANSLATED_MAIN_VIEWS := examples.pipeform:PipeMainView \
50
+ examples.pipesweep:PipeSweepMainView \
51
+ examples.taskview:TaskMainView
52
+
53
+ # every catalog against the text its app translates; each app reported before failing
54
+ .PHONY: translations
55
+ translations:
56
+ @status=0; for main_view in $(TRANSLATED_MAIN_VIEWS); do \
57
+ uv run python -m composeui.core.translationcheck $$main_view || status=1; \
58
+ done; exit $$status
59
+
60
+ .PHONY: check
61
+ check: lint mypy translations
62
+
63
+ .PHONY: test
64
+ # in parallel, a worker per CPU (pytest-xdist)
65
+ test:
66
+ QT_QPA_PLATFORM=offscreen uv run pytest tests -m "not browser" -n auto --cov=src --cov-report=term-missing:skip-covered
67
+
68
+ # the web backend in headless Chromium; skipped unless Chromium is installed:
69
+ # uv run --group browser playwright install chromium
70
+ # No pytest-qt: it refuses to start without a Qt binding, which these tests don't need.
71
+ .PHONY: browser-test
72
+ browser-test:
73
+ uv run --group browser pytest tests/test_browser.py -m browser -p no:pytest-qt
74
+
75
+ # the documentation site (Sphinx + MyST): the guides, the widget catalog and the API
76
+ # reference, in docs/_build/html; any warning fails it. The API reference imports every
77
+ # backend's application module: run `uv sync --group docs --all-extras` first.
78
+ .PHONY: docs
79
+ docs:
80
+ uv run --group docs sphinx-build -W --keep-going -b html docs docs/_build/html
81
+
82
+ # the screenshots of the examples guide, on every backend, into docs/images/; needs the
83
+ # browser group's Chromium (see browser-test). Pass names to take only some of them:
84
+ # make screenshots EXAMPLES="pipeform batman"
85
+ .PHONY: screenshots
86
+ screenshots:
87
+ QT_QPA_PLATFORM=offscreen uv run --group browser python docs/screenshots.py $(EXAMPLES)
88
+
89
+ # Tailwind's standalone CLI and daisyUI's plugin files: no Node/npm needed. Downloaded
90
+ # once into css/vendor/ (not committed); the built static/app.css is committed.
91
+ TAILWIND_VERSION := 4.3.3
92
+ DAISYUI_VERSION := 5.7.47
93
+ CSS_VENDOR := css/vendor
94
+ # Tailwind's release names for this machine: linux, macos or windows (Git Bash, MSYS2 or
95
+ # Cygwin report MINGW64_NT-..., MSYS_NT-..., CYGWIN_NT-...), then x64 or arm64. Override
96
+ # it for another build, e.g. `make css TAILWIND_PLATFORM=linux-x64-musl` on Alpine.
97
+ TAILWIND_PLATFORMS := linux-x64 linux-arm64 macos-x64 macos-arm64 linux-x64-musl \
98
+ linux-arm64-musl windows-x64
99
+ TAILWIND_PLATFORM ?= $(shell uname -s | sed -e 's/^Linux$$/linux/' -e 's/^Darwin$$/macos/' \
100
+ -e 's/^MINGW.*/windows/' -e 's/^MSYS.*/windows/' -e 's/^CYGWIN.*/windows/')-$(shell uname -m | sed -e 's/^x86_64$$/x64/' \
101
+ -e 's/^aarch64$$/arm64/')
102
+ # Windows only runs it as an .exe
103
+ TAILWIND_SUFFIX := $(if $(filter windows-%,$(TAILWIND_PLATFORM)),.exe)
104
+ # the platform in the name: a checkout shared between machines keeps one binary for each
105
+ TAILWIND := $(CSS_VENDOR)/tailwindcss-$(TAILWIND_VERSION)-$(TAILWIND_PLATFORM)$(TAILWIND_SUFFIX)
106
+ DAISYUI := $(CSS_VENDOR)/daisyui.mjs
107
+ DAISYUI_THEME := $(CSS_VENDOR)/daisyui-theme.mjs
108
+ # the SHA-256 of each download, from Tailwind's sha256sums.txt and the digests of daisyUI's
109
+ # release assets: change them with the versions
110
+ TAILWIND_SHA256_linux-x64 := dc61b3ac6b8c9ca874c0cc4c57b2409791a64c5540404ca5f5367360babc313a
111
+ TAILWIND_SHA256_linux-arm64 := 55fd0b241214eff3de1e8ee4f22796662f2d2e7a49bcfca7477cfd0bac398195
112
+ TAILWIND_SHA256_macos-x64 := 7922e0953f2110c05976e3bf58f14e643d90427575e766b7d433f5f80cbee7e1
113
+ TAILWIND_SHA256_macos-arm64 := cdf646702987a743464dff4d9c60fd4480d1c1e73dd819a9a67f1078815dce9d
114
+ TAILWIND_SHA256_linux-x64-musl := a04d34ceacc8f52cbe8920ad846cdeb61d3d0021dba32db0d1f77c9d9fad7a6c
115
+ TAILWIND_SHA256_linux-arm64-musl := 71ea4be79c9de9827545682df3e040053fb535d37c71ed2cfdedf9385a0868e0
116
+ TAILWIND_SHA256_windows-x64 := e0e260ce048014e9268f6237ff18f8ccf02cef521cbd0ae04e82c2cdf7aa3955
117
+ TAILWIND_SHA256 := $(TAILWIND_SHA256_$(TAILWIND_PLATFORM))
118
+ DAISYUI_SHA256 := 85819d3fe86a852237b439b13f481481aac58e562ac47694cd11c3038e994f2a
119
+ DAISYUI_THEME_SHA256 := d92d2f488933dab757f046e81ebd4986e51e9423307798dc3545d09a15542c89
120
+ # macOS before 15 has shasum, not sha256sum
121
+ SHA256SUM := $(shell command -v sha256sum >/dev/null 2>&1 && echo sha256sum || echo shasum -a 256)
122
+ # download URL $(1) into file $(2), kept only when its SHA-256 is $(3)
123
+ fetch = mkdir -p $(dir $(2)) && curl -sSfL -o $(2).tmp $(1) \
124
+ && { echo "$(3) $(2).tmp" | $(SHA256SUM) -c || { rm -f $(2).tmp; exit 1; }; } \
125
+ && mv $(2).tmp $(2)
126
+
127
+ $(TAILWIND):
128
+ $(if $(filter $(TAILWIND_PLATFORM),$(TAILWIND_PLATFORMS)),,\
129
+ $(error No Tailwind binary for '$(TAILWIND_PLATFORM)': pass one of \
130
+ $(TAILWIND_PLATFORMS) as TAILWIND_PLATFORM))
131
+ $(call fetch,https://github.com/tailwindlabs/tailwindcss/releases/download/v$(TAILWIND_VERSION)/tailwindcss-$(TAILWIND_PLATFORM)$(TAILWIND_SUFFIX),$@,$(TAILWIND_SHA256))
132
+ chmod +x $@
133
+
134
+ $(DAISYUI):
135
+ $(call fetch,https://github.com/saadeghi/daisyui/releases/download/v$(DAISYUI_VERSION)/daisyui.mjs,$@,$(DAISYUI_SHA256))
136
+
137
+ $(DAISYUI_THEME):
138
+ $(call fetch,https://github.com/saadeghi/daisyui/releases/download/v$(DAISYUI_VERSION)/daisyui-theme.mjs,$@,$(DAISYUI_THEME_SHA256))
139
+
140
+ # the files already there are checked too: they can be from an older version, or have
141
+ # changed since
142
+ .PHONY: css
143
+ css: $(TAILWIND) $(DAISYUI) $(DAISYUI_THEME)
144
+ @printf '%s %s\n' $(TAILWIND_SHA256) $(TAILWIND) $(DAISYUI_SHA256) $(DAISYUI) \
145
+ $(DAISYUI_THEME_SHA256) $(DAISYUI_THEME) | $(SHA256SUM) -c \
146
+ || { echo "Delete $(CSS_VENDOR)/ to download them again"; exit 1; }
147
+ $(TAILWIND) -i css/app.css -o src/composeui/webcore/static/app.css --minify
148
+
149
+ # the web page's scripts from npm, through unpkg, and uPlot's stylesheet, which app.css
150
+ # imports: committed, so the package needs no download. Change a version and its SHA-256,
151
+ # run `make web-deps css`, and commit the files.
152
+ STATIC := src/composeui/webcore/static
153
+ HTMX_VERSION := 2.0.10
154
+ UPLOT_VERSION := 1.6.32
155
+ CALLY_VERSION := 0.9.2
156
+ DECKGL_VERSION := 9.2.6
157
+ DECKGL_SHA256 := 234e0af97a691b8e5aea6c6ca9e703a7ba5206fd1f53daa9f670e41db089de2d
158
+ DECKGL_LICENSE_SHA256 := e499a2c2ba3b9faabe91fdfddf43e329439263264b7148e5548d0ec605cce9f2
159
+ HTMX_SHA256 := 71ea67185bfa8c98c39d31717c6fce5d852370fcdfd129db4543774d3145c0de
160
+ UPLOT_SHA256 := 19c8d4c6ad88929a79f4ae49d6f7161566dfd0ba3d15cc495e974f787eb78f1f
161
+ UPLOT_CSS_SHA256 := df630c6a8d6f8eeaff264b50f73ce5b114f646ffd9a0bb74f049b0a00135fa04
162
+ CALLY_SHA256 := 4dcba6a3b8ec63b66ff2fbfc6cf47b7dee1eb4896eac89fe7487d82e28e15870
163
+ CALLY_LICENSE_SHA256 := c48fd407090d6d752b0370d39db55bd750e6dfc678ee252d4c906bddf54132eb
164
+ UPLOT_LICENSE_SHA256 := 8f989229699b4fe2f1a0432d0e9edc338a8a911e250e2d1b01ecd770a5f5b1bd
165
+ # daisyUI's classes are built into app.css (`make css`); its license ships beside it
166
+ DAISYUI_LICENSE_SHA256 := 8709e3ac65c84637c422dc8082b893d9997fce093751c77b2f1983bf29dbf9ea
167
+
168
+ # Cally's file has no license comment: it gets Cally's LICENSE as one; uPlot's has only its
169
+ # address, and its LICENSE goes beside it
170
+ .PHONY: web-deps
171
+ web-deps:
172
+ $(call fetch,https://unpkg.com/deck.gl@$(DECKGL_VERSION)/dist.min.js,$(STATIC)/deckgl.min.js,$(DECKGL_SHA256))
173
+ $(call fetch,https://unpkg.com/deck.gl@$(DECKGL_VERSION)/LICENSE,$(STATIC)/deckgl.LICENSE,$(DECKGL_LICENSE_SHA256))
174
+ $(call fetch,https://unpkg.com/htmx.org@$(HTMX_VERSION)/dist/htmx.min.js,$(STATIC)/htmx.min.js,$(HTMX_SHA256))
175
+ $(call fetch,https://unpkg.com/uplot@$(UPLOT_VERSION)/dist/uPlot.iife.min.js,$(STATIC)/uPlot.iife.min.js,$(UPLOT_SHA256))
176
+ $(call fetch,https://unpkg.com/uplot@$(UPLOT_VERSION)/LICENSE,$(STATIC)/uPlot.LICENSE,$(UPLOT_LICENSE_SHA256))
177
+ $(call fetch,https://unpkg.com/uplot@$(UPLOT_VERSION)/dist/uPlot.min.css,css/uPlot.min.css,$(UPLOT_CSS_SHA256))
178
+ $(call fetch,https://unpkg.com/daisyui@$(DAISYUI_VERSION)/LICENSE,$(STATIC)/daisyui.LICENSE,$(DAISYUI_LICENSE_SHA256))
179
+ $(call fetch,https://unpkg.com/cally@$(CALLY_VERSION)/dist/cally.js,$(STATIC)/cally.dist.js,$(CALLY_SHA256))
180
+ $(call fetch,https://unpkg.com/cally@$(CALLY_VERSION)/LICENSE,$(STATIC)/cally.LICENSE,$(CALLY_LICENSE_SHA256))
181
+ { echo '/*! https://github.com/WickyNilliams/cally (v$(CALLY_VERSION)), its dependency atomico bundled in'; \
182
+ sed -e 's/^/ * /' -e 's/ *$$//' $(STATIC)/cally.LICENSE; echo ' */'; \
183
+ cat $(STATIC)/cally.dist.js; } > $(STATIC)/cally.js
184
+ rm -f $(STATIC)/cally.dist.js $(STATIC)/cally.LICENSE
185
+
186
+ .PHONY: clean
187
+ clean:
188
+ rm -rf `find . -name __pycache__`
189
+ rm -f `find . -type f -name '*.py[co]'`
190
+ rm -f `find . -type f -name '*~'`
191
+ rm -f `find . -type f -name '.*~'`
192
+ rm -rf .cache
193
+ rm -rf .pytest_cache
194
+ rm -rf .ruff_cache
195
+ rm -rf htmlcov
196
+ rm -rf *.egg-info
197
+ rm -f .coverage
198
+ rm -f .coverage.*
199
+ rm -rf build
200
+ rm -rf dist
201
+ rm -rf site
202
+ rm -rf docs/_build
203
+ rm -rf coverage.xml
204
+
205
+ .PHONY: all
206
+ all: clean check test