clutterxp 1.1.1__tar.gz → 1.2.1__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 (217) hide show
  1. clutterxp-1.2.1/PKG-INFO +220 -0
  2. clutterxp-1.2.1/README.md +186 -0
  3. clutterxp-1.2.1/assets/clutterxp_logo.png +0 -0
  4. {clutterxp-1.1.1 → clutterxp-1.2.1}/pyproject.toml +7 -0
  5. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/__init__.py +1 -1
  6. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/app.py +9 -2
  7. clutterxp-1.2.1/src/clutterxp/gui/theme.py +378 -0
  8. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/views/dashboard.py +45 -23
  9. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/views/duplicates.py +23 -5
  10. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/views/folders.py +18 -3
  11. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/views/history.py +29 -6
  12. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/views/preview.py +35 -9
  13. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/views/rules.py +74 -12
  14. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/views/settings.py +12 -2
  15. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/workers.py +11 -3
  16. clutterxp-1.1.1/PKG-INFO +0 -149
  17. clutterxp-1.1.1/README.md +0 -120
  18. {clutterxp-1.1.1 → clutterxp-1.2.1}/.github/workflows/ci.yml +0 -0
  19. {clutterxp-1.1.1 → clutterxp-1.2.1}/.gitignore +0 -0
  20. {clutterxp-1.1.1 → clutterxp-1.2.1}/CHANGELOG.md +0 -0
  21. {clutterxp-1.1.1 → clutterxp-1.2.1}/config.example.yaml +0 -0
  22. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/00_PROJECT_PLAN.md +0 -0
  23. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/01_ARCHITECTURE.md +0 -0
  24. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/02_RULE_ENGINE.md +0 -0
  25. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/03_CONFIG.md +0 -0
  26. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/04_SAFE_MOVER.md +0 -0
  27. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/05_HISTORY_UNDO.md +0 -0
  28. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/06_WATCHER.md +0 -0
  29. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/07_DUPLICATES.md +0 -0
  30. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/08_CLI.md +0 -0
  31. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/09_SAFETY.md +0 -0
  32. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/10_TESTING.md +0 -0
  33. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/11_PACKAGING.md +0 -0
  34. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/12_GUI.md +0 -0
  35. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/13_ROADMAP.md +0 -0
  36. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/14_DECISIONS.md +0 -0
  37. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/15_ACCEPTANCE.md +0 -0
  38. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/16_RISKS.md +0 -0
  39. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/reviews/.gitkeep +0 -0
  40. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/reviews/acceptance_matrix.md +0 -0
  41. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/reviews/perf_report.md +0 -0
  42. {clutterxp-1.1.1 → clutterxp-1.2.1}/docs/reviews/security_review.md +0 -0
  43. {clutterxp-1.1.1 → clutterxp-1.2.1}/packaging/clutterxp.service +0 -0
  44. {clutterxp-1.1.1 → clutterxp-1.2.1}/scripts/.gitkeep +0 -0
  45. {clutterxp-1.1.1 → clutterxp-1.2.1}/scripts/check_contracts.py +0 -0
  46. {clutterxp-1.1.1 → clutterxp-1.2.1}/scripts/check_ownership.py +0 -0
  47. {clutterxp-1.1.1 → clutterxp-1.2.1}/scripts/contracts.lock.json +0 -0
  48. {clutterxp-1.1.1 → clutterxp-1.2.1}/scripts/e2e.sh +0 -0
  49. {clutterxp-1.1.1 → clutterxp-1.2.1}/scripts/smoke_readme.sh +0 -0
  50. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/__init__.py +0 -0
  51. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/app.py +0 -0
  52. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_config.py +0 -0
  53. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_doctor.py +0 -0
  54. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_duplicates.py +0 -0
  55. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_gui.py +0 -0
  56. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_history.py +0 -0
  57. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_init.py +0 -0
  58. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_organize.py +0 -0
  59. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_rules.py +0 -0
  60. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_scan.py +0 -0
  61. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_service.py +0 -0
  62. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_undo.py +0 -0
  63. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/cmd_watch.py +0 -0
  64. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/errors.py +0 -0
  65. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/cli/render.py +0 -0
  66. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/config/__init__.py +0 -0
  67. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/config/jsonschema.py +0 -0
  68. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/config/loader.py +0 -0
  69. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/config/placeholders.py +0 -0
  70. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/config/runtime.py +0 -0
  71. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/config/schema.py +0 -0
  72. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/config/validate.py +0 -0
  73. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/config/writer.py +0 -0
  74. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/duplicates/__init__.py +0 -0
  75. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/duplicates/detector.py +0 -0
  76. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/duplicates/hashcache.py +0 -0
  77. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/duplicates/namenorm.py +0 -0
  78. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/duplicates/quarantine.py +0 -0
  79. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/duplicates/report.py +0 -0
  80. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/__init__.py +0 -0
  81. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/api.py +0 -0
  82. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/models_qt.py +0 -0
  83. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/gui/views/__init__.py +0 -0
  84. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/history/__init__.py +0 -0
  85. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/history/db.py +0 -0
  86. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/history/pathcodec.py +0 -0
  87. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/history/recovery.py +0 -0
  88. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/history/schema.sql +0 -0
  89. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/history/service.py +0 -0
  90. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/history/undo.py +0 -0
  91. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/interfaces.py +0 -0
  92. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/locking.py +0 -0
  93. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/logging_setup.py +0 -0
  94. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/models.py +0 -0
  95. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/mover/__init__.py +0 -0
  96. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/mover/collision.py +0 -0
  97. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/mover/fsops.py +0 -0
  98. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/mover/safe_move.py +0 -0
  99. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/organizer.py +0 -0
  100. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/paths.py +0 -0
  101. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/planner.py +0 -0
  102. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/py.typed +0 -0
  103. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/rules/__init__.py +0 -0
  104. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/rules/engine.py +0 -0
  105. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/rules/matcher.py +0 -0
  106. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/rules/template.py +0 -0
  107. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/rules/tokenizer.py +0 -0
  108. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/safety.py +0 -0
  109. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/scanner.py +0 -0
  110. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/service_install.py +0 -0
  111. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/units.py +0 -0
  112. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/watch/__init__.py +0 -0
  113. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/watch/debounce.py +0 -0
  114. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/watch/events.py +0 -0
  115. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/watch/ignore.py +0 -0
  116. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/watch/service.py +0 -0
  117. {clutterxp-1.1.1 → clutterxp-1.2.1}/src/clutterxp/watch/settle.py +0 -0
  118. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/.gitkeep +0 -0
  119. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/__init__.py +0 -0
  120. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/__init__.py +0 -0
  121. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/conftest.py +0 -0
  122. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/schemas/__init__.py +0 -0
  123. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/schemas/doctor.json +0 -0
  124. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/schemas/duplicates.json +0 -0
  125. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/schemas/history.json +0 -0
  126. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/schemas/organize.json +0 -0
  127. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/schemas/rules_list.json +0 -0
  128. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/schemas/rules_test.json +0 -0
  129. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/schemas/scan.json +0 -0
  130. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_config.py +0 -0
  131. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_doctor.py +0 -0
  132. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_duplicates.py +0 -0
  133. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_general.py +0 -0
  134. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_gui.py +0 -0
  135. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_history.py +0 -0
  136. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_init.py +0 -0
  137. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_organize.py +0 -0
  138. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_rules.py +0 -0
  139. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_scan.py +0 -0
  140. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_service.py +0 -0
  141. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_undo.py +0 -0
  142. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/cli/test_watch.py +0 -0
  143. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/config/__init__.py +0 -0
  144. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/config/conftest.py +0 -0
  145. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/config/test_jsonschema_and_logging.py +0 -0
  146. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/config/test_loader.py +0 -0
  147. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/config/test_paths.py +0 -0
  148. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/config/test_placeholders.py +0 -0
  149. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/config/test_runtime.py +0 -0
  150. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/config/test_validate.py +0 -0
  151. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/config/test_writer.py +0 -0
  152. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/conftest.py +0 -0
  153. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/duplicates/__init__.py +0 -0
  154. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/duplicates/test_detector.py +0 -0
  155. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/duplicates/test_hashcache.py +0 -0
  156. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/duplicates/test_namenorm.py +0 -0
  157. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/duplicates/test_quarantine.py +0 -0
  158. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/__init__.py +0 -0
  159. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/conftest.py +0 -0
  160. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A81_gui_launches_and_cli_never_imports_qt.py +0 -0
  161. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A82_dashboard_shows_watcher_and_recent_operations.py +0 -0
  162. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A83_folders_view_edits_watch_folders_and_settings.py +0 -0
  163. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A84_rules_editor_includes_live_trace.py +0 -0
  164. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A85_preview_data_equals_cli_json_plan.py +0 -0
  165. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A86_history_view_undo_shows_exact_refusal_reasons.py +0 -0
  166. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A87_duplicates_view_progress_and_cancel.py +0 -0
  167. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A88_duplicates_quarantine_gui.py +0 -0
  168. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A89_ui_thread_never_blocks_worker_cancellation.py +0 -0
  169. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_A90_gui_extra_documented.py +0 -0
  170. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_api_extra.py +0 -0
  171. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/gui/test_models_qt.py +0 -0
  172. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/helpers.py +0 -0
  173. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/history/__init__.py +0 -0
  174. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/history/test_db.py +0 -0
  175. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/history/test_pathcodec.py +0 -0
  176. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/history/test_recovery.py +0 -0
  177. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/history/test_service.py +0 -0
  178. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/history/test_undo.py +0 -0
  179. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/integration/__init__.py +0 -0
  180. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/integration/test_acceptance_sweep.py +0 -0
  181. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/integration/test_fault_injection.py +0 -0
  182. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/integration/test_properties.py +0 -0
  183. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/integration/test_scenarios.py +0 -0
  184. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/mover/__init__.py +0 -0
  185. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/mover/conftest.py +0 -0
  186. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/mover/test_collision.py +0 -0
  187. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/mover/test_fsops.py +0 -0
  188. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/mover/test_locking.py +0 -0
  189. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/mover/test_safe_move.py +0 -0
  190. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/organizer/__init__.py +0 -0
  191. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/organizer/conftest.py +0 -0
  192. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/organizer/test_organizer.py +0 -0
  193. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/organizer/test_planner.py +0 -0
  194. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/organizer/test_scanner.py +0 -0
  195. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/perf/__init__.py +0 -0
  196. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/perf/test_perf_budgets.py +0 -0
  197. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/rules/__init__.py +0 -0
  198. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/rules/conftest.py +0 -0
  199. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/rules/test_engine.py +0 -0
  200. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/rules/test_matcher.py +0 -0
  201. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/rules/test_template.py +0 -0
  202. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/rules/test_tokenizer.py +0 -0
  203. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/rules/test_vectors.py +0 -0
  204. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/safety/__init__.py +0 -0
  205. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/safety/test_invariants_full.py +0 -0
  206. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/safety/test_safety_module.py +0 -0
  207. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/test_harness.py +0 -0
  208. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/test_service_install.py +0 -0
  209. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/watch/__init__.py +0 -0
  210. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/watch/conftest.py +0 -0
  211. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/watch/test_acceptance.py +0 -0
  212. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/watch/test_debounce.py +0 -0
  213. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/watch/test_events.py +0 -0
  214. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/watch/test_ignore.py +0 -0
  215. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/watch/test_invariants.py +0 -0
  216. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/watch/test_service.py +0 -0
  217. {clutterxp-1.1.1 → clutterxp-1.2.1}/tests/watch/test_settle.py +0 -0
@@ -0,0 +1,220 @@
1
+ Metadata-Version: 2.5
2
+ Name: clutterxp
3
+ Version: 1.2.1
4
+ Summary: Configurable, safe, lightweight file organizer: rules, preview, undo, watch, duplicates.
5
+ Project-URL: Homepage, https://github.com/aigo-admin/clutterxp
6
+ Project-URL: Documentation, https://github.com/aigo-admin/clutterxp#readme
7
+ Project-URL: Repository, https://github.com/aigo-admin/clutterxp.git
8
+ Project-URL: Issue Tracker, https://github.com/aigo-admin/clutterxp/issues
9
+ Project-URL: Changelog, https://github.com/aigo-admin/clutterxp/blob/main/CHANGELOG.md
10
+ Keywords: automation,cli,downloads,file-organizer,linux
11
+ Classifier: Environment :: Console
12
+ Classifier: Operating System :: POSIX :: Linux
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Topic :: System :: Filesystems
15
+ Classifier: Topic :: Utilities
16
+ Requires-Python: >=3.10
17
+ Requires-Dist: platformdirs>=4.0
18
+ Requires-Dist: pydantic>=2.6
19
+ Requires-Dist: ruamel-yaml-clib>=0.2.12; platform_python_implementation == 'CPython'
20
+ Requires-Dist: ruamel-yaml>=0.18
21
+ Requires-Dist: typer>=0.12
22
+ Requires-Dist: watchdog>=4.0
23
+ Provides-Extra: dev
24
+ Requires-Dist: build>=1.0; extra == 'dev'
25
+ Requires-Dist: hypothesis>=6.100; extra == 'dev'
26
+ Requires-Dist: mypy>=1.10; extra == 'dev'
27
+ Requires-Dist: pytest-cov>=5; extra == 'dev'
28
+ Requires-Dist: pytest-timeout>=2.3; extra == 'dev'
29
+ Requires-Dist: pytest>=8; extra == 'dev'
30
+ Requires-Dist: ruff>=0.5; extra == 'dev'
31
+ Provides-Extra: gui
32
+ Requires-Dist: pyside6>=6.6; extra == 'gui'
33
+ Description-Content-Type: text/markdown
34
+
35
+ <p align="center">
36
+ <img src="https://raw.githubusercontent.com/aigo-admin/clutterxp/0ec772e48eaf1022d81824e19f89d171e1df8640/assets/clutterxp_logo.png" alt="ClutterXP Logo" width="240">
37
+ </p>
38
+
39
+ <h1 align="center">ClutterXP</h1>
40
+
41
+ <p align="center">
42
+ <strong>Configurable, safe, lightweight file organizer for Linux.</strong><br>
43
+ Tidies downloads, documents, and messy directories into place—on your terms, with zero risk of file loss.
44
+ </p>
45
+
46
+ ---
47
+
48
+ | Category | Badges |
49
+ |:---|:---|
50
+ | **Package** | [![PyPI Latest Release](https://img.shields.io/pypi/v/clutterxp.svg)](https://pypi.org/project/clutterxp/) [![Supported Python](https://img.shields.io/pypi/pyversions/clutterxp.svg)](https://pypi.org/project/clutterxp/) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/aigo-admin/clutterxp/blob/main/LICENSE) |
51
+ | **Quality** | [![Test Suite](https://img.shields.io/badge/tests-584%20passed-brightgreen.svg)](https://github.com/aigo-admin/clutterxp) [![Code Style: Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff) [![Checked with mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](http://mypy-lang.org/) |
52
+ | **Safety** | [![Safety: Zero Deletions](https://img.shields.io/badge/safety-zero%20deletions-10b981.svg)](https://github.com/aigo-admin/clutterxp) [![Privacy: 100% Offline](https://img.shields.io/badge/privacy-100%25%20offline-6366f1.svg)](https://github.com/aigo-admin/clutterxp) |
53
+
54
+ ---
55
+
56
+ ## What is it?
57
+
58
+ **ClutterXP** is a modern, fast, and reliable file organization toolkit designed for developers, power users, and everyday Linux users who want an organized workspace. Whether your `Downloads` folder is buried under months of receipts, PDFs, installers, wallpapers, and screenshots, or you want background auto-sorting for incoming files, ClutterXP handles the job with ironclad safety guarantees:
59
+
60
+ - **Never deletes or overwrites**: Files only move when the destination is safe. If a file with the same name exists, ClutterXP automatically renames the incoming file (e.g., `report (1).pdf`) or skips it. Your existing files are never destroyed.
61
+ - **Only moves what you tell it to**: If a file does not match a configured rule, it stays untouched in its original location. No unexpected moves, no mystery dump directories.
62
+ - **One-click instant undo**: Every operation is logged in an ACID SQLite journal. Run `clutterxp undo` (or click Undo in the GUI) to restore files to their exact original locations.
63
+ - **100% private and offline**: Operates entirely on your local machine. No tracking, no cloud telemetry, and zero network calls.
64
+
65
+ ---
66
+
67
+ ## Table of Contents
68
+
69
+ - [Main Features](#main-features)
70
+ - [Where to Get It](#where-to-get-it)
71
+ - [Quickstart: Desktop App](#quickstart-desktop-app)
72
+ - [Quickstart: Command Line](#quickstart-command-line)
73
+ - [Background Watcher Daemon](#background-watcher-daemon)
74
+ - [Duplicate File Quarantine](#duplicate-file-quarantine)
75
+ - [Customize Your Rules](#customize-your-rules)
76
+ - [Cheat Sheet](#cheat-sheet)
77
+ - [Dependencies](#dependencies)
78
+ - [License](#license)
79
+
80
+ ---
81
+
82
+ ## Main Features
83
+
84
+ - **Modern Minimalist Desktop App**: Clean obsidian-dark interface with one-click presets, visual plan previews, and real-time rule match testing.
85
+ - **Token-Aware Rule Engine**: Intelligent keyword matching that understands camelCase, PascalCase, hyphenated names, and compound extensions (e.g. `.tar.gz`).
86
+ - **Background Watcher**: Monitors designated folders using inotify with built-in settle and debounce to ensure browser downloads finish before moving.
87
+ - **Duplicate Detector & Safe Quarantine**: Multi-stage duplicate detection (size -> hardlinks -> head/tail partial hash -> streaming SHA-256) with safe quarantine isolation.
88
+ - **Instant LIFO Batch Undo**: Complete rollback capability for any batch of moves or quarantine actions.
89
+ - **Dual CLI Aliases**: Use full `clutterxp` or short alias `cxp` interchangeably.
90
+
91
+ ---
92
+
93
+ ## Where to Get It
94
+
95
+ ### Binary Installation via PyPI
96
+
97
+ Install ClutterXP using Python 3.10+:
98
+
99
+ ```bash
100
+ # Recommended: install with modern desktop app (GUI) support
101
+ pip install "clutterxp[gui]"
102
+
103
+ # CLI-only lightweight install
104
+ pip install clutterxp
105
+
106
+ # Or on Kali Linux / Debian / Ubuntu via pipx:
107
+ pipx install "clutterxp[gui]"
108
+ ```
109
+
110
+ ---
111
+
112
+ ## Quickstart: Desktop App
113
+
114
+ Launch the visual application with:
115
+
116
+ ```bash
117
+ clutterxp gui
118
+ ```
119
+
120
+ The desktop app provides:
121
+ - **Interactive Plan Preview**: Review every planned move with clear status badges before touching any files.
122
+ - **Rule Editor with Presets**: One-click setup for common categories (Invoices, Photos, Installers) plus live matching test as you type.
123
+ - **Duplicate Quarantine**: Safely isolate duplicate downloads without risking accidental deletion.
124
+ - **Batch History & Undo**: Restore any previous batch with a single click.
125
+
126
+ ---
127
+
128
+ ## Quickstart: Command Line
129
+
130
+ Prefer the terminal? Use `clutterxp` or the shorthand `cxp`:
131
+
132
+ ```bash
133
+ # 1. Create your default starter config
134
+ clutterxp init
135
+
136
+ # 2. Test which rule matches a filename
137
+ clutterxp rules test "invoice_march.pdf"
138
+
139
+ # 3. Preview what would be organized (dry-run, touches nothing)
140
+ clutterxp organize ~/Downloads --dry-run
141
+
142
+ # 4. Organize your Downloads folder
143
+ clutterxp organize ~/Downloads --yes
144
+
145
+ # 5. Changed your mind? Undo the entire batch instantly!
146
+ clutterxp undo --yes
147
+ ```
148
+
149
+ ---
150
+
151
+ ## Background Watcher Daemon
152
+
153
+ Turn on automatic sorting. When a new file is downloaded or moved into a watched folder, ClutterXP waits until it is fully written to disk, then quietly sorts it into place:
154
+
155
+ ```bash
156
+ clutterxp watch
157
+ ```
158
+
159
+ You can also install it as a systemd user service:
160
+
161
+ ```bash
162
+ clutterxp service install
163
+ clutterxp service status
164
+ ```
165
+
166
+ ---
167
+
168
+ ## Duplicate File Quarantine
169
+
170
+ Identify duplicate files taking up storage and safely isolate them:
171
+
172
+ ```bash
173
+ clutterxp duplicates scan ~/Downloads
174
+ ```
175
+
176
+ ---
177
+
178
+ ## Customize Your Rules
179
+
180
+ Rules are clean and human-readable. Group by keywords, file extensions, size, or date:
181
+
182
+ - *"Move files containing `invoice` or `receipt` to `~/Documents/Invoices/{year}/{month}`"*
183
+ - *"Move `.png` and `.jpg` with `wallpaper` to `~/Pictures/Wallpapers`"*
184
+ - *"Move `.deb`, `.iso`, `.rpm` to `~/Downloads/Installers`"*
185
+
186
+ Edit rules in the desktop GUI or directly via `clutterxp rules add`.
187
+
188
+ ---
189
+
190
+ ## Cheat Sheet
191
+
192
+ | What you want to do | Command |
193
+ |:---|:---|
194
+ | Open the visual desktop app | `clutterxp gui` |
195
+ | Check version | `clutterxp --version` or `cxp --version` |
196
+ | Create default starter config | `clutterxp init` |
197
+ | Test rule matching on a filename | `clutterxp rules test "invoice_march.pdf"` |
198
+ | Preview organization without moving files | `cxp organize ~/Downloads --dry-run` |
199
+ | Organize a folder | `cxp organize ~/Downloads` |
200
+ | Undo the last batch of moves | `cxp undo` |
201
+ | Watch a folder and sort new files automatically | `cxp watch` |
202
+ | Install background watcher service | `cxp service install` |
203
+ | Check background service status | `cxp service status` |
204
+ | Scan for duplicate files | `cxp duplicates scan ~/Downloads` |
205
+ | Check system health and file integrity | `cxp doctor` |
206
+ | View organization history | `cxp history` |
207
+
208
+ ---
209
+
210
+ ## Dependencies
211
+
212
+ - **Python**: `>= 3.10`
213
+ - **Core CLI**: `typer`, `pydantic`, `ruamel.yaml`, `watchdog`, `platformdirs`
214
+ - **Optional GUI**: `PySide6` (`pip install "clutterxp[gui]"`)
215
+
216
+ ---
217
+
218
+ ## License
219
+
220
+ [MIT](LICENSE)
@@ -0,0 +1,186 @@
1
+ <p align="center">
2
+ <img src="https://raw.githubusercontent.com/aigo-admin/clutterxp/0ec772e48eaf1022d81824e19f89d171e1df8640/assets/clutterxp_logo.png" alt="ClutterXP Logo" width="240">
3
+ </p>
4
+
5
+ <h1 align="center">ClutterXP</h1>
6
+
7
+ <p align="center">
8
+ <strong>Configurable, safe, lightweight file organizer for Linux.</strong><br>
9
+ Tidies downloads, documents, and messy directories into place—on your terms, with zero risk of file loss.
10
+ </p>
11
+
12
+ ---
13
+
14
+ | Category | Badges |
15
+ |:---|:---|
16
+ | **Package** | [![PyPI Latest Release](https://img.shields.io/pypi/v/clutterxp.svg)](https://pypi.org/project/clutterxp/) [![Supported Python](https://img.shields.io/pypi/pyversions/clutterxp.svg)](https://pypi.org/project/clutterxp/) [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](https://github.com/aigo-admin/clutterxp/blob/main/LICENSE) |
17
+ | **Quality** | [![Test Suite](https://img.shields.io/badge/tests-584%20passed-brightgreen.svg)](https://github.com/aigo-admin/clutterxp) [![Code Style: Ruff](https://img.shields.io/badge/code%20style-ruff-000000.svg)](https://github.com/astral-sh/ruff) [![Checked with mypy](https://img.shields.io/badge/mypy-checked-blue.svg)](http://mypy-lang.org/) |
18
+ | **Safety** | [![Safety: Zero Deletions](https://img.shields.io/badge/safety-zero%20deletions-10b981.svg)](https://github.com/aigo-admin/clutterxp) [![Privacy: 100% Offline](https://img.shields.io/badge/privacy-100%25%20offline-6366f1.svg)](https://github.com/aigo-admin/clutterxp) |
19
+
20
+ ---
21
+
22
+ ## What is it?
23
+
24
+ **ClutterXP** is a modern, fast, and reliable file organization toolkit designed for developers, power users, and everyday Linux users who want an organized workspace. Whether your `Downloads` folder is buried under months of receipts, PDFs, installers, wallpapers, and screenshots, or you want background auto-sorting for incoming files, ClutterXP handles the job with ironclad safety guarantees:
25
+
26
+ - **Never deletes or overwrites**: Files only move when the destination is safe. If a file with the same name exists, ClutterXP automatically renames the incoming file (e.g., `report (1).pdf`) or skips it. Your existing files are never destroyed.
27
+ - **Only moves what you tell it to**: If a file does not match a configured rule, it stays untouched in its original location. No unexpected moves, no mystery dump directories.
28
+ - **One-click instant undo**: Every operation is logged in an ACID SQLite journal. Run `clutterxp undo` (or click Undo in the GUI) to restore files to their exact original locations.
29
+ - **100% private and offline**: Operates entirely on your local machine. No tracking, no cloud telemetry, and zero network calls.
30
+
31
+ ---
32
+
33
+ ## Table of Contents
34
+
35
+ - [Main Features](#main-features)
36
+ - [Where to Get It](#where-to-get-it)
37
+ - [Quickstart: Desktop App](#quickstart-desktop-app)
38
+ - [Quickstart: Command Line](#quickstart-command-line)
39
+ - [Background Watcher Daemon](#background-watcher-daemon)
40
+ - [Duplicate File Quarantine](#duplicate-file-quarantine)
41
+ - [Customize Your Rules](#customize-your-rules)
42
+ - [Cheat Sheet](#cheat-sheet)
43
+ - [Dependencies](#dependencies)
44
+ - [License](#license)
45
+
46
+ ---
47
+
48
+ ## Main Features
49
+
50
+ - **Modern Minimalist Desktop App**: Clean obsidian-dark interface with one-click presets, visual plan previews, and real-time rule match testing.
51
+ - **Token-Aware Rule Engine**: Intelligent keyword matching that understands camelCase, PascalCase, hyphenated names, and compound extensions (e.g. `.tar.gz`).
52
+ - **Background Watcher**: Monitors designated folders using inotify with built-in settle and debounce to ensure browser downloads finish before moving.
53
+ - **Duplicate Detector & Safe Quarantine**: Multi-stage duplicate detection (size -> hardlinks -> head/tail partial hash -> streaming SHA-256) with safe quarantine isolation.
54
+ - **Instant LIFO Batch Undo**: Complete rollback capability for any batch of moves or quarantine actions.
55
+ - **Dual CLI Aliases**: Use full `clutterxp` or short alias `cxp` interchangeably.
56
+
57
+ ---
58
+
59
+ ## Where to Get It
60
+
61
+ ### Binary Installation via PyPI
62
+
63
+ Install ClutterXP using Python 3.10+:
64
+
65
+ ```bash
66
+ # Recommended: install with modern desktop app (GUI) support
67
+ pip install "clutterxp[gui]"
68
+
69
+ # CLI-only lightweight install
70
+ pip install clutterxp
71
+
72
+ # Or on Kali Linux / Debian / Ubuntu via pipx:
73
+ pipx install "clutterxp[gui]"
74
+ ```
75
+
76
+ ---
77
+
78
+ ## Quickstart: Desktop App
79
+
80
+ Launch the visual application with:
81
+
82
+ ```bash
83
+ clutterxp gui
84
+ ```
85
+
86
+ The desktop app provides:
87
+ - **Interactive Plan Preview**: Review every planned move with clear status badges before touching any files.
88
+ - **Rule Editor with Presets**: One-click setup for common categories (Invoices, Photos, Installers) plus live matching test as you type.
89
+ - **Duplicate Quarantine**: Safely isolate duplicate downloads without risking accidental deletion.
90
+ - **Batch History & Undo**: Restore any previous batch with a single click.
91
+
92
+ ---
93
+
94
+ ## Quickstart: Command Line
95
+
96
+ Prefer the terminal? Use `clutterxp` or the shorthand `cxp`:
97
+
98
+ ```bash
99
+ # 1. Create your default starter config
100
+ clutterxp init
101
+
102
+ # 2. Test which rule matches a filename
103
+ clutterxp rules test "invoice_march.pdf"
104
+
105
+ # 3. Preview what would be organized (dry-run, touches nothing)
106
+ clutterxp organize ~/Downloads --dry-run
107
+
108
+ # 4. Organize your Downloads folder
109
+ clutterxp organize ~/Downloads --yes
110
+
111
+ # 5. Changed your mind? Undo the entire batch instantly!
112
+ clutterxp undo --yes
113
+ ```
114
+
115
+ ---
116
+
117
+ ## Background Watcher Daemon
118
+
119
+ Turn on automatic sorting. When a new file is downloaded or moved into a watched folder, ClutterXP waits until it is fully written to disk, then quietly sorts it into place:
120
+
121
+ ```bash
122
+ clutterxp watch
123
+ ```
124
+
125
+ You can also install it as a systemd user service:
126
+
127
+ ```bash
128
+ clutterxp service install
129
+ clutterxp service status
130
+ ```
131
+
132
+ ---
133
+
134
+ ## Duplicate File Quarantine
135
+
136
+ Identify duplicate files taking up storage and safely isolate them:
137
+
138
+ ```bash
139
+ clutterxp duplicates scan ~/Downloads
140
+ ```
141
+
142
+ ---
143
+
144
+ ## Customize Your Rules
145
+
146
+ Rules are clean and human-readable. Group by keywords, file extensions, size, or date:
147
+
148
+ - *"Move files containing `invoice` or `receipt` to `~/Documents/Invoices/{year}/{month}`"*
149
+ - *"Move `.png` and `.jpg` with `wallpaper` to `~/Pictures/Wallpapers`"*
150
+ - *"Move `.deb`, `.iso`, `.rpm` to `~/Downloads/Installers`"*
151
+
152
+ Edit rules in the desktop GUI or directly via `clutterxp rules add`.
153
+
154
+ ---
155
+
156
+ ## Cheat Sheet
157
+
158
+ | What you want to do | Command |
159
+ |:---|:---|
160
+ | Open the visual desktop app | `clutterxp gui` |
161
+ | Check version | `clutterxp --version` or `cxp --version` |
162
+ | Create default starter config | `clutterxp init` |
163
+ | Test rule matching on a filename | `clutterxp rules test "invoice_march.pdf"` |
164
+ | Preview organization without moving files | `cxp organize ~/Downloads --dry-run` |
165
+ | Organize a folder | `cxp organize ~/Downloads` |
166
+ | Undo the last batch of moves | `cxp undo` |
167
+ | Watch a folder and sort new files automatically | `cxp watch` |
168
+ | Install background watcher service | `cxp service install` |
169
+ | Check background service status | `cxp service status` |
170
+ | Scan for duplicate files | `cxp duplicates scan ~/Downloads` |
171
+ | Check system health and file integrity | `cxp doctor` |
172
+ | View organization history | `cxp history` |
173
+
174
+ ---
175
+
176
+ ## Dependencies
177
+
178
+ - **Python**: `>= 3.10`
179
+ - **Core CLI**: `typer`, `pydantic`, `ruamel.yaml`, `watchdog`, `platformdirs`
180
+ - **Optional GUI**: `PySide6` (`pip install "clutterxp[gui]"`)
181
+
182
+ ---
183
+
184
+ ## License
185
+
186
+ [MIT](LICENSE)
@@ -44,6 +44,13 @@ cxp = "clutterxp.cli.app:main"
44
44
  [project.gui-scripts]
45
45
  clutterxp-gui = "clutterxp.gui.app:main"
46
46
 
47
+ [project.urls]
48
+ Homepage = "https://github.com/aigo-admin/clutterxp"
49
+ Documentation = "https://github.com/aigo-admin/clutterxp#readme"
50
+ Repository = "https://github.com/aigo-admin/clutterxp.git"
51
+ "Issue Tracker" = "https://github.com/aigo-admin/clutterxp/issues"
52
+ Changelog = "https://github.com/aigo-admin/clutterxp/blob/main/CHANGELOG.md"
53
+
47
54
  [tool.hatch.version]
48
55
  path = "src/clutterxp/__init__.py"
49
56
 
@@ -1,4 +1,4 @@
1
1
  """ClutterXP - configurable, safe, fast file organizer."""
2
2
 
3
- __version__ = "1.1.1"
3
+ __version__ = "1.2.1"
4
4
 
@@ -28,6 +28,7 @@ from PySide6.QtWidgets import (
28
28
 
29
29
  from clutterxp import paths
30
30
  from clutterxp.gui.api import GuiApi
31
+ from clutterxp.gui.theme import apply_theme
31
32
  from clutterxp.gui.views import (
32
33
  DashboardView,
33
34
  DuplicatesView,
@@ -45,8 +46,13 @@ class ClutterXPMainWindow(QMainWindow):
45
46
  def __init__(self, api: GuiApi | None = None, parent: QWidget | None = None) -> None:
46
47
  super().__init__(parent)
47
48
  self.api = api if api is not None else GuiApi()
48
- self.setWindowTitle("ClutterXP - Intelligent File Organizer")
49
- self.resize(1000, 700)
49
+ self.setWindowTitle("ClutterXP - Safe, Intelligent File Organizer")
50
+ self.resize(1080, 720)
51
+ self.setMinimumSize(880, 580)
52
+
53
+ app_instance = QApplication.instance()
54
+ if app_instance is not None:
55
+ apply_theme(app_instance)
50
56
 
51
57
  self._init_ui()
52
58
 
@@ -181,6 +187,7 @@ def main() -> None:
181
187
  if app is None:
182
188
  app = QApplication(sys.argv)
183
189
  app.setApplicationName("ClutterXP")
190
+ apply_theme(app)
184
191
 
185
192
  # Hook up SIGINT to cleanly exit
186
193
  signal.signal(signal.SIGINT, lambda *_: app.quit())