pycascadeui 2.0.0__tar.gz → 3.0.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 (166) hide show
  1. pycascadeui-3.0.0/PKG-INFO +774 -0
  2. pycascadeui-3.0.0/README.md +732 -0
  3. pycascadeui-3.0.0/cascadeui/__init__.py +271 -0
  4. pycascadeui-3.0.0/cascadeui/__main__.py +47 -0
  5. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/components/__init__.py +30 -9
  6. pycascadeui-3.0.0/cascadeui/components/base.py +199 -0
  7. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/components/buttons.py +1 -1
  8. pycascadeui-3.0.0/cascadeui/components/inputs.py +441 -0
  9. pycascadeui-3.0.0/cascadeui/components/patterns/__init__.py +64 -0
  10. pycascadeui-2.0.0/cascadeui/components/v1_patterns.py → pycascadeui-3.0.0/cascadeui/components/patterns/v1.py +30 -64
  11. pycascadeui-3.0.0/cascadeui/components/patterns/v2.py +1178 -0
  12. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/components/wrappers.py +40 -11
  13. pycascadeui-3.0.0/cascadeui/devtools.py +1470 -0
  14. pycascadeui-3.0.0/cascadeui/exceptions.py +94 -0
  15. pycascadeui-3.0.0/cascadeui/persistence/__init__.py +31 -0
  16. pycascadeui-3.0.0/cascadeui/persistence/backends/__init__.py +19 -0
  17. pycascadeui-3.0.0/cascadeui/persistence/backends/memory.py +157 -0
  18. pycascadeui-3.0.0/cascadeui/persistence/backends/sqlite.py +336 -0
  19. pycascadeui-3.0.0/cascadeui/persistence/config.py +202 -0
  20. pycascadeui-3.0.0/cascadeui/persistence/manager.py +840 -0
  21. pycascadeui-3.0.0/cascadeui/persistence/migrations.py +148 -0
  22. pycascadeui-3.0.0/cascadeui/persistence/protocols.py +171 -0
  23. pycascadeui-3.0.0/cascadeui/persistence/schema.py +121 -0
  24. pycascadeui-3.0.0/cascadeui/setup.py +74 -0
  25. pycascadeui-3.0.0/cascadeui/state/__init__.py +26 -0
  26. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/state/actions.py +54 -9
  27. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/state/computed.py +11 -3
  28. pycascadeui-3.0.0/cascadeui/state/middleware/__init__.py +15 -0
  29. pycascadeui-3.0.0/cascadeui/state/middleware/logging.py +36 -0
  30. pycascadeui-3.0.0/cascadeui/state/middleware/persistence.py +730 -0
  31. pycascadeui-3.0.0/cascadeui/state/middleware/undo.py +245 -0
  32. pycascadeui-3.0.0/cascadeui/state/reducers.py +593 -0
  33. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/state/singleton.py +2 -3
  34. pycascadeui-3.0.0/cascadeui/state/slots.py +243 -0
  35. pycascadeui-3.0.0/cascadeui/state/store.py +1370 -0
  36. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/state/types.py +10 -1
  37. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/theming/__init__.py +2 -0
  38. pycascadeui-3.0.0/cascadeui/theming/context.py +37 -0
  39. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/theming/core.py +3 -25
  40. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/theming/themes.py +6 -16
  41. pycascadeui-3.0.0/cascadeui/tracing.py +115 -0
  42. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/utils/__init__.py +3 -0
  43. pycascadeui-3.0.0/cascadeui/utils/coercion.py +72 -0
  44. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/utils/decorators.py +20 -7
  45. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/utils/errors.py +3 -6
  46. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/utils/logging.py +167 -11
  47. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/utils/tasks.py +16 -7
  48. pycascadeui-3.0.0/cascadeui/views/__init__.py +45 -0
  49. pycascadeui-3.0.0/cascadeui/views/_interaction.py +351 -0
  50. pycascadeui-3.0.0/cascadeui/views/_navigation.py +492 -0
  51. pycascadeui-3.0.0/cascadeui/views/base.py +2451 -0
  52. pycascadeui-3.0.0/cascadeui/views/layout.py +151 -0
  53. pycascadeui-3.0.0/cascadeui/views/patterns/__init__.py +27 -0
  54. pycascadeui-3.0.0/cascadeui/views/patterns/form.py +957 -0
  55. pycascadeui-3.0.0/cascadeui/views/patterns/leaderboard.py +552 -0
  56. pycascadeui-3.0.0/cascadeui/views/patterns/menu.py +298 -0
  57. pycascadeui-3.0.0/cascadeui/views/patterns/paginated.py +875 -0
  58. pycascadeui-3.0.0/cascadeui/views/patterns/tabs.py +461 -0
  59. pycascadeui-3.0.0/cascadeui/views/patterns/types.py +303 -0
  60. pycascadeui-3.0.0/cascadeui/views/patterns/wizard.py +544 -0
  61. pycascadeui-3.0.0/cascadeui/views/persistent.py +297 -0
  62. pycascadeui-3.0.0/cascadeui/views/view.py +44 -0
  63. pycascadeui-3.0.0/pycascadeui.egg-info/PKG-INFO +774 -0
  64. pycascadeui-3.0.0/pycascadeui.egg-info/SOURCES.txt +119 -0
  65. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/pyproject.toml +1 -1
  66. pycascadeui-3.0.0/tests/test_auto_defer.py +383 -0
  67. pycascadeui-3.0.0/tests/test_backends.py +383 -0
  68. pycascadeui-3.0.0/tests/test_batching.py +375 -0
  69. pycascadeui-3.0.0/tests/test_button_grid.py +71 -0
  70. pycascadeui-3.0.0/tests/test_components.py +230 -0
  71. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_computed.py +33 -6
  72. pycascadeui-3.0.0/tests/test_devtools.py +503 -0
  73. pycascadeui-3.0.0/tests/test_emoji_grid.py +168 -0
  74. pycascadeui-3.0.0/tests/test_ephemeral_refresh.py +749 -0
  75. pycascadeui-3.0.0/tests/test_five_pillars.py +350 -0
  76. pycascadeui-3.0.0/tests/test_form_patterns.py +1429 -0
  77. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_hooks.py +16 -7
  78. pycascadeui-3.0.0/tests/test_input_coercion.py +85 -0
  79. pycascadeui-3.0.0/tests/test_inputs.py +518 -0
  80. pycascadeui-3.0.0/tests/test_instance_limit.py +1454 -0
  81. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_layout_pagination.py +3 -3
  82. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_layout_patterns.py +9 -9
  83. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_layout_persistent.py +14 -10
  84. pycascadeui-3.0.0/tests/test_layout_view.py +1354 -0
  85. pycascadeui-3.0.0/tests/test_leaderboard_patterns.py +836 -0
  86. pycascadeui-3.0.0/tests/test_menu_patterns.py +462 -0
  87. pycascadeui-3.0.0/tests/test_message_cleanup.py +194 -0
  88. pycascadeui-3.0.0/tests/test_middleware.py +207 -0
  89. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_nav_version.py +4 -3
  90. pycascadeui-3.0.0/tests/test_navigation.py +457 -0
  91. pycascadeui-3.0.0/tests/test_owner_only.py +283 -0
  92. pycascadeui-3.0.0/tests/test_paginated_patterns.py +244 -0
  93. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_pagination.py +405 -14
  94. pycascadeui-3.0.0/tests/test_persistence.py +881 -0
  95. pycascadeui-3.0.0/tests/test_persistence_middleware.py +755 -0
  96. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_persistent_views.py +106 -37
  97. pycascadeui-3.0.0/tests/test_pillar_isolation.py +400 -0
  98. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_rebuild_callback.py +4 -8
  99. pycascadeui-3.0.0/tests/test_reducers.py +644 -0
  100. pycascadeui-3.0.0/tests/test_scoping.py +1128 -0
  101. pycascadeui-3.0.0/tests/test_state_slots.py +452 -0
  102. pycascadeui-3.0.0/tests/test_state_store.py +771 -0
  103. pycascadeui-3.0.0/tests/test_tab_patterns.py +379 -0
  104. pycascadeui-3.0.0/tests/test_theming.py +275 -0
  105. pycascadeui-3.0.0/tests/test_undo.py +1141 -0
  106. pycascadeui-3.0.0/tests/test_update_coalescing.py +281 -0
  107. pycascadeui-3.0.0/tests/test_v2_helpers.py +557 -0
  108. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/tests/test_validation.py +11 -3
  109. pycascadeui-3.0.0/tests/test_view_init.py +370 -0
  110. pycascadeui-3.0.0/tests/test_wizard_patterns.py +737 -0
  111. pycascadeui-3.0.0/tests/test_wrappers.py +314 -0
  112. pycascadeui-2.0.0/PKG-INFO +0 -562
  113. pycascadeui-2.0.0/README.md +0 -520
  114. pycascadeui-2.0.0/cascadeui/__init__.py +0 -161
  115. pycascadeui-2.0.0/cascadeui/components/base.py +0 -89
  116. pycascadeui-2.0.0/cascadeui/components/inputs.py +0 -152
  117. pycascadeui-2.0.0/cascadeui/components/v2_patterns.py +0 -312
  118. pycascadeui-2.0.0/cascadeui/devtools.py +0 -407
  119. pycascadeui-2.0.0/cascadeui/persistence/__init__.py +0 -34
  120. pycascadeui-2.0.0/cascadeui/persistence/migration.py +0 -47
  121. pycascadeui-2.0.0/cascadeui/persistence/redis.py +0 -104
  122. pycascadeui-2.0.0/cascadeui/persistence/serialization.py +0 -33
  123. pycascadeui-2.0.0/cascadeui/persistence/sqlite.py +0 -114
  124. pycascadeui-2.0.0/cascadeui/persistence/storage.py +0 -133
  125. pycascadeui-2.0.0/cascadeui/state/__init__.py +0 -12
  126. pycascadeui-2.0.0/cascadeui/state/middleware.py +0 -118
  127. pycascadeui-2.0.0/cascadeui/state/reducers.py +0 -434
  128. pycascadeui-2.0.0/cascadeui/state/store.py +0 -672
  129. pycascadeui-2.0.0/cascadeui/state/undo.py +0 -119
  130. pycascadeui-2.0.0/cascadeui/views/__init__.py +0 -33
  131. pycascadeui-2.0.0/cascadeui/views/base.py +0 -1041
  132. pycascadeui-2.0.0/cascadeui/views/layout.py +0 -155
  133. pycascadeui-2.0.0/cascadeui/views/layout_patterns.py +0 -342
  134. pycascadeui-2.0.0/cascadeui/views/layout_specialized.py +0 -487
  135. pycascadeui-2.0.0/cascadeui/views/patterns.py +0 -254
  136. pycascadeui-2.0.0/cascadeui/views/persistent.py +0 -486
  137. pycascadeui-2.0.0/cascadeui/views/specialized.py +0 -527
  138. pycascadeui-2.0.0/pycascadeui.egg-info/PKG-INFO +0 -562
  139. pycascadeui-2.0.0/pycascadeui.egg-info/SOURCES.txt +0 -80
  140. pycascadeui-2.0.0/tests/test_auto_defer.py +0 -206
  141. pycascadeui-2.0.0/tests/test_backends.py +0 -122
  142. pycascadeui-2.0.0/tests/test_batching.py +0 -154
  143. pycascadeui-2.0.0/tests/test_components.py +0 -54
  144. pycascadeui-2.0.0/tests/test_devtools.py +0 -279
  145. pycascadeui-2.0.0/tests/test_layout_view.py +0 -224
  146. pycascadeui-2.0.0/tests/test_middleware.py +0 -129
  147. pycascadeui-2.0.0/tests/test_nav_stack.py +0 -285
  148. pycascadeui-2.0.0/tests/test_owner_only.py +0 -146
  149. pycascadeui-2.0.0/tests/test_reducers.py +0 -192
  150. pycascadeui-2.0.0/tests/test_scoping.py +0 -79
  151. pycascadeui-2.0.0/tests/test_session_limit.py +0 -439
  152. pycascadeui-2.0.0/tests/test_state_store.py +0 -261
  153. pycascadeui-2.0.0/tests/test_theming.py +0 -64
  154. pycascadeui-2.0.0/tests/test_undo.py +0 -212
  155. pycascadeui-2.0.0/tests/test_v2_helpers.py +0 -288
  156. pycascadeui-2.0.0/tests/test_view_init.py +0 -168
  157. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/LICENSE +0 -0
  158. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/components/selects.py +0 -0
  159. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/components/v1_composition.py +0 -0
  160. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/py.typed +0 -0
  161. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/utils/helpers.py +0 -0
  162. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/cascadeui/validation.py +0 -0
  163. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/pycascadeui.egg-info/dependency_links.txt +0 -0
  164. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/pycascadeui.egg-info/requires.txt +0 -0
  165. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/pycascadeui.egg-info/top_level.txt +0 -0
  166. {pycascadeui-2.0.0 → pycascadeui-3.0.0}/setup.cfg +0 -0
@@ -0,0 +1,774 @@
1
+ Metadata-Version: 2.4
2
+ Name: pycascadeui
3
+ Version: 3.0.0
4
+ Summary: Redux-inspired UI framework for discord.py
5
+ Author-email: HollowTheSilver <hollow@users.noreply.github.com>
6
+ License-Expression: MIT
7
+ Project-URL: Homepage, https://github.com/HollowTheSilver/CascadeUI
8
+ Project-URL: Repository, https://github.com/HollowTheSilver/CascadeUI
9
+ Project-URL: Issues, https://github.com/HollowTheSilver/CascadeUI/issues
10
+ Project-URL: Documentation, https://hollowthesilver.github.io/CascadeUI/
11
+ Keywords: discord,discord.py,ui,components,state-management
12
+ Classifier: Development Status :: 5 - Production/Stable
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Programming Language :: Python :: 3
15
+ Classifier: Programming Language :: Python :: 3.10
16
+ Classifier: Programming Language :: Python :: 3.11
17
+ Classifier: Programming Language :: Python :: 3.12
18
+ Classifier: Programming Language :: Python :: 3.13
19
+ Classifier: Programming Language :: Python :: 3.14
20
+ Classifier: Topic :: Communications :: Chat
21
+ Classifier: Topic :: Software Development :: Libraries :: Python Modules
22
+ Classifier: Typing :: Typed
23
+ Classifier: Framework :: AsyncIO
24
+ Requires-Python: >=3.10
25
+ Description-Content-Type: text/markdown
26
+ License-File: LICENSE
27
+ Requires-Dist: discord.py>=2.7
28
+ Provides-Extra: dev
29
+ Requires-Dist: pytest>=7.0; extra == "dev"
30
+ Requires-Dist: pytest-asyncio>=0.21; extra == "dev"
31
+ Requires-Dist: black>=26.3; extra == "dev"
32
+ Requires-Dist: isort>=8.0; extra == "dev"
33
+ Provides-Extra: docs
34
+ Requires-Dist: mkdocs>=1.5; extra == "docs"
35
+ Requires-Dist: mkdocs-material>=9.0; extra == "docs"
36
+ Requires-Dist: pymdown-extensions>=10.0; extra == "docs"
37
+ Provides-Extra: sqlite
38
+ Requires-Dist: aiosqlite>=0.19; extra == "sqlite"
39
+ Provides-Extra: redis
40
+ Requires-Dist: redis>=5.0; extra == "redis"
41
+ Dynamic: license-file
42
+
43
+ <p align="center">
44
+ <img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/docs/assets/banner.png" alt="CascadeUI - A Redux-Inspired Framework for Discord.py" width="100%">
45
+ </p>
46
+
47
+ <p align="center">
48
+ <a href="https://github.com/HollowTheSilver/CascadeUI/stargazers"><img src="https://img.shields.io/github/stars/HollowTheSilver/CascadeUI?style=flat&logo=github&label=stars" alt="Stars"></a>
49
+ <a href="https://github.com/sponsors/HollowTheSilver"><img src="https://img.shields.io/badge/Sponsor-%E2%9D%A4-ea4aaa?logo=githubsponsors&logoColor=white" alt="Sponsor"></a>
50
+ <a href="https://pypi.org/project/pycascadeui/"><img src="https://img.shields.io/pypi/dm/pycascadeui?logo=pypi&logoColor=white&label=downloads" alt="Downloads"></a>
51
+ <a href="https://pypi.org/project/pycascadeui/"><img src="https://img.shields.io/pypi/v/pycascadeui?logo=pypi&logoColor=white" alt="PyPI"></a>
52
+ <a href="https://github.com/Rapptz/discord.py"><img src="https://img.shields.io/badge/discord.py-2.7+-738adb.svg?logo=discord&logoColor=white" alt="discord.py 2.7+"></a>
53
+ <a href="https://www.python.org/downloads/"><img src="https://img.shields.io/badge/python-3.10%20|%203.11%20|%203.12%20|%203.13%20|%203.14-blue.svg?logo=python&logoColor=white" alt="Python 3.10-3.14"></a>
54
+ <a href="https://discord.com/invite/9Xj68BpKRb"><img src="https://img.shields.io/discord/1405822635920855040?logo=discord&logoColor=white&label=Discord&color=5865F2" alt="Discord"></a>
55
+ <a href="https://hollowthesilver.github.io/CascadeUI/"><img src="https://img.shields.io/badge/docs-GitHub%20Pages-8A2BE2?logo=readthedocs" alt="Docs"></a>
56
+ <a href="https://github.com/HollowTheSilver/CascadeUI/actions/workflows/ci.yml"><img src="https://img.shields.io/github/actions/workflow/status/HollowTheSilver/CascadeUI/ci.yml?logo=github&label=CI" alt="CI"></a>
57
+ <a href="https://opensource.org/licenses/MIT"><img src="https://img.shields.io/badge/License-MIT-yellow.svg" alt="License: MIT"></a>
58
+ </p>
59
+
60
+ <p align="center">
61
+ <strong>Build predictable, state-driven interfaces with <a href="https://github.com/Rapptz/discord.py">discord.py</a>.</strong><br>
62
+ A flexible, Redux-inspired UI framework that introduces centralized state, composable components, ownership control, and predictable data flow to Discord applications.<br>
63
+ </p>
64
+
65
+ <div align="center">
66
+ <img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-hero.gif" alt="CascadeUI Hero Demo" width="600">
67
+ </div>
68
+
69
+ <p align="center">
70
+ <a href="https://hollowthesilver.github.io/CascadeUI/"><strong>Read the Docs</strong></a>
71
+ </p>
72
+
73
+ ---
74
+
75
+ ## Why CascadeUI
76
+
77
+ > Interactive Discord UIs become difficult to manage as they grow. State accumulates across `View` subclass attributes, components stop responding after bot restarts, multi-step forms lose data between pages, and sharing data between views requires manual `message.edit()` plumbing in every callback.
78
+
79
+ CascadeUI introduces structure built on a Redux-inspired core:
80
+
81
+ - **Centralized state** instead of scattered view attributes, so every view reads from a single source of truth and stays in sync automatically.
82
+ - **Predictable updates** through dispatched actions, with one way to change state and one way to read it. No callback spaghetti.
83
+ - **Clear separation** between logic and presentation. Reducers handle data, views render it, and neither knows about the other.
84
+ - **Reusable UI patterns** instead of one-off implementations. Menus, pagination, forms, wizards, tabs, and persistent panels are first-class library primitives.
85
+ - **Built-in interaction control** for ownership, instance limits, and navigation. Restrict who can click what, cap how many concurrent instances a user or guild can hold, and push, pop, or replace views without tracking message history by hand.
86
+ - **Persistence, undo/redo, and lifecycle handling** without the boilerplate. Components survive bot restarts, state history is one method call away, and session cleanup happens automatically.
87
+
88
+ The pattern scales from simple panels to full application-style interfaces.
89
+
90
+ ---
91
+
92
+ ## Architecture
93
+
94
+ CascadeUI follows a unidirectional data flow model:
95
+
96
+ ```
97
+ User interaction -> dispatch(action)
98
+ -> middleware
99
+ -> reducer (state update)
100
+ -> subscribers notified
101
+ -> views re-render
102
+ ```
103
+
104
+ All state lives in a single store. Actions describe what happened. Reducers define how state changes. Views subscribe to relevant state and update automatically.
105
+
106
+ <details>
107
+ <summary><b>Coming from Redux or React?</b></summary>
108
+
109
+ <br>
110
+
111
+ CascadeUI ports Redux's mental model onto Discord. Most core primitives have a closest analogue in frameworks you already know:
112
+
113
+ | CascadeUI | Closest Redux / React analogue |
114
+ |-----------|-------------------------------|
115
+ | `StateStore` | Redux store |
116
+ | `@cascade_reducer` | Redux reducer |
117
+ | `@computed` | Reselect / `useMemo` |
118
+ | `build_ui()` | React component `render()` |
119
+ | `on_state_changed` | `componentDidUpdate` + auto re-render |
120
+ | `push()` / `pop()` / `replace()` | React Router navigation |
121
+ | Middleware chain | `applyMiddleware` |
122
+ | `PersistenceMiddleware` | `redux-persist` (opt-in per slot) |
123
+
124
+ **Full treatment:** [`guide/concepts.md`](https://hollowthesilver.github.io/CascadeUI/guide/concepts/) walks through each mapping in depth, including where the two diverge - middleware is async, state persists across bot restarts (Discord messages outlive your code), and Discord's platform layer (ephemeral 15-minute wall, webhook tokens, rate limits) has no React/Redux equivalent.
125
+
126
+ </details>
127
+
128
+ ---
129
+
130
+ ## When to Use
131
+
132
+ > Every discord.py view needs ownership control, session cleanup, and interaction safety. CascadeUI handles all of that out of the box with class-level declarations - no boilerplate, no manual checks.
133
+
134
+ Even a single-view panel benefits from `owner_only = True` and `instance_limit = 1`. As your interface grows, the same framework scales to:
135
+
136
+ - Shared state across multiple views via `StateStore`
137
+ - Real data and message persistence via `PersistenceMiddleware`
138
+ - Cross-view reactivity with `dispatch()` and `subscribed_actions`
139
+ - Multi-step flows and validation via `WizardLayoutView` and `FormLayoutView`
140
+ - Navigation stacks (`push()` / `pop()` / `replace()`), session policies, and `participant_limit`
141
+ - Grid-based game boards with `emoji_grid()` and `button_grid()`
142
+
143
+ ---
144
+
145
+ ## Getting Started
146
+
147
+ ```bash
148
+ pip install pycascadeui
149
+ ```
150
+
151
+ Optional dependencies:
152
+
153
+ ```bash
154
+ pip install pycascadeui[sqlite]
155
+ ```
156
+
157
+ Requirements:
158
+ - Python 3.10+
159
+ - discord.py 2.7+
160
+
161
+ ### Hello World
162
+
163
+ A minimal CascadeUI view: per-user counter with ownership, instance replacement, and state-driven rebuilds - in about 20 lines.
164
+
165
+ ```python
166
+ import discord
167
+ from discord.ui import ActionRow
168
+ from cascadeui import StatefulButton, StatefulLayoutView, card
169
+
170
+ class CounterView(StatefulLayoutView):
171
+ # Class-level policy -- ownership and instance control in three lines.
172
+ owner_only = True # Only the opener can click
173
+ instance_limit = 1 # One live counter per user
174
+ instance_policy = "replace" # Second open replaces the first
175
+
176
+ # Reactivity -- build_ui() re-runs whenever scoped state changes.
177
+ subscribed_actions = {"SCOPED_UPDATE"}
178
+ state_scope = "user"
179
+
180
+ def build_ui(self):
181
+ self.clear_items()
182
+ count = self.scoped_state.get("count", 0)
183
+ self.add_item(card(f"Count: **{count}**"))
184
+ self.add_item(ActionRow(StatefulButton(
185
+ label="+1",
186
+ style=discord.ButtonStyle.primary,
187
+ callback=self._increment,
188
+ )))
189
+
190
+ async def _increment(self, interaction):
191
+ count = self.scoped_state.get("count", 0)
192
+ await self.dispatch_scoped({"count": count + 1})
193
+
194
+ # In a cog command:
195
+ # view = CounterView(context=ctx)
196
+ # await view.send()
197
+ ```
198
+
199
+ See the [Quickstart](https://hollowthesilver.github.io/CascadeUI/guide/quickstart/) for the detailed walkthrough and [examples/v2_hello_world.py](examples/v2_hello_world.py) for the full runnable cog.
200
+
201
+ ---
202
+
203
+ ## Features
204
+
205
+ > For full details, see the official <a href="https://hollowthesilver.github.io/CascadeUI/"><strong>documentation</strong></a>.
206
+
207
+ ### State and Data Flow
208
+ - Centralized store with dispatch and reducer cycle
209
+ - Custom reducers via `@cascade_reducer` decorator with automatic deep copy and built-in collision guards
210
+ - Action batching for grouped, atomic updates; nested batches collapse into one commit and fire a single notification cycle
211
+ - Computed state via `@computed` decorator with selector-based cache invalidation and per-store instances that survive singleton resets (≈ Reselect / React's `useMemo`)
212
+ - Selector-based subscriptions for targeted re-renders (similar to React's selective re-render optimization)
213
+ - Built-in profiler with exportable markdown + JSON reports for dispatch, subscriber, and refresh timings -- measure before you optimize, attach snapshots to PRs and bug reports
214
+ - `access_slot()` / `read_slot()` / `slot_property` helpers for auto-vivifying application buckets without hand-rolling the read/write plumbing
215
+ - Scoped state family: `get_scoped()`, `get_scoped_from()`, `iter_scoped()`, `set_scoped()`, `merge_scoped()` -- one call from inside a reducer, no private key-building required
216
+ - Cross-view reactivity: dispatch from any view, all subscribers update instantly with automatic coalescing under concurrent access
217
+ - Middleware pipeline for logging, persistence, and transformation (Redux-style, async)
218
+ - Event hooks for lifecycle observation and side effects
219
+
220
+ ### Views and Patterns
221
+ - Layout-based V2 system for structured, container-driven interfaces
222
+ - Full support for traditional discord.py Views (V1)
223
+ - Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards, persistent leaderboards
224
+ - `PaginatedView.from_cursor()` for lazy cursor-driven pagination with an LRU page cache
225
+ - `DisplayLayoutView` for one-shot V2 sends from a pre-built container, no subclass required
226
+ - Automatic state-driven rebuilds: define `build_ui()` and the library wires it into `on_state_changed()` and `refresh()` for you (declarative render, like React components)
227
+ - One hook for V1 and V2: `build_ui()` returns `None` (V2 mutates the tree) or a dict of edit kwargs like `{"embed": ...}` (V1), and the library splats it into `message.edit()`
228
+ - Theming with per-view overrides, V2 accent colors, and a `ContextVar` that propagates the active theme through `build_ui()` so builders like `card()` and `stats_card()` inherit automatically (like `React.Context`)
229
+
230
+ ### Interaction Control and Sessions
231
+ - Interaction ownership control, owner-only by default and configurable via `allowed_users`
232
+ - Instance limiting per user, guild, user+guild, or globally with replace or reject policies
233
+ - Participant-aware views for multi-user scenarios like challenge flows, lobbies, and games
234
+ - `participant_limit` with `on_participant_limit` hook and `auto_register_participants` for automatic slot claiming during `send()`
235
+ - Navigation stack with `push()`, `pop()`, and `replace()`, sharing one Discord message across the chain (akin to React Router's history API)
236
+ - `check_instance_available()` for fail-fast pre-checks before constructing expensive views
237
+ - Five-pillar architecture: Access Control, Instance Constraints, View Lifecycle, Session Membership, and Navigation -- each attribute belongs to exactly one pillar
238
+ - `session_continuity` opt-in for repeat-open state coalescing; the default isolates every send as its own session
239
+ - Parent and child view lifecycle via `attach_child()` (or `parent=` kwarg) with automatic cleanup
240
+ - Automatic interaction acknowledgement via auto-defer (tunable per view), with `respond()` / `open_modal()` / `_safe_defer()` helpers that transparently route through response or followup
241
+ - Interaction serialization via an `asyncio.Lock` so rapid clicks process sequentially without racing `message.edit()` calls
242
+ - Refresh throttling: opt-in `refresh_cooldown_ms` proactively batches edits, and reactive 429 backoff honors Discord's `retry_after` automatically. Both share a single monotonic cooldown and coalesce on the latest store state at fire time
243
+ - `auto_refresh_ephemeral` flag: bypass Discord's 15-minute ephemeral editability wall with a user-driven token handoff; armed views freeze state-driven rebuilds so the refresh button cannot be clobbered between T+810s and T+900s
244
+ - Automatic message re-fetch after `send()` so long-lived views are not bound to the interaction webhook's 15-minute token window, with a `_webhook_message` dual-reference so embed edits still route through the webhook when the channel endpoint would drop them silently
245
+
246
+ ### Components and Composition
247
+ - Stateful buttons, selects, and modals with state integration
248
+ - Select callbacks can opt into a `values` second parameter, no more `interaction.data["values"][0]`
249
+ - V2 layout builders: `card()`, `stats_card()`, `action_section()`, `toggle_section()`, `image_section()`, `link_section()`, `confirm_section()`, `button_row()`, `cycle_button()`, `toggle_button()`, `tab_nav()`, `key_value()`, `alert()`, `progress_bar()`, `divider()`, `gap()`, `gallery()`
250
+ - Grid helpers: `emoji_grid()` for text-rendered boards with axis labels and mutation API, `button_grid()` for interactive cell grids with Discord's 5x5 limit enforced
251
+ - Built-in form system with typed modal fields (`text`, `integer`, `float`, `date`), inline selects, per-field validation, and declarative `FormSchema` / `WizardSchema` base classes
252
+ - Component wrappers: loading states, confirmation dialogs, cooldowns
253
+
254
+ ### Persistence and Infrastructure
255
+ - Persistent views that survive bot restarts with automatic message re-attachment
256
+ - Two-namespace persistence model (`registry` and `application`) with per-namespace windows, max-age ceiling, and retry backoff on backend failure
257
+ - State persistence backends: built-in SQLite (via `aiosqlite`) and an in-memory backend for tests; custom backends plug in through a capability-flag `Protocol` with documented copy-on-store and NULL-safe TTL contracts
258
+ - Opt-in application slots via `persistent_slots = ("scoped",)`. Only the slots a view declares ride to disk, the rest stay volatile (≈ `redux-persist`, opt-in per slot)
259
+ - Named scoped buckets via `scoped_slot` so each subsystem (e.g. `"battleship_stats"`, `"tictactoe_stats"`) persists into its own flat bucket instead of one monolithic `scoped` tree
260
+ - Debounced `PersistenceMiddleware` installed via `setup_middleware`, with smart filtering so bookkeeping actions do not hit disk and an identity-diff scan that skips no-op writes
261
+ - Undo and redo via snapshot-based state history (opt in with `enable_undo`); batched dispatches collapse to one undo entry per participating view
262
+ - Scoped state isolation (`user`, `guild`, `user_guild`, `global`) with automatic key derivation and a reducer-side `merge_scoped()` writer
263
+ - `DevToolsCog` with a tabbed state inspector and owner-only `/cascadeui` command group for live debugging
264
+ - Silent snowflake coercion at every public boundary (`Member` where `int` is expected just works)
265
+ - Class-attribute validation at subclass-definition time. Typos in `instance_policy`, `participant_limit`, and friends fail at import with a clear error
266
+
267
+ ---
268
+
269
+ ## Showcase
270
+
271
+ ### Cross-View Reactivity
272
+
273
+ > Dispatch actions from any view and update all subscribers instantly across the interface.
274
+
275
+ ```python
276
+ # Any view can dispatch a named action.
277
+ await self.dispatch("SETTINGS_UPDATED", {"theme": "dark"})
278
+
279
+ # Any other open view that subscribes wakes up automatically --
280
+ # no manual message.edit(), no cross-view wiring.
281
+ class NotificationPanel(StatefulLayoutView):
282
+ subscribed_actions = {"SETTINGS_UPDATED"}
283
+ # build_ui() re-runs whenever SETTINGS_UPDATED fires anywhere.
284
+ ```
285
+
286
+ ![Cross-View](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-cross-view-reactivity.gif)
287
+
288
+ ---
289
+
290
+ ### Dynamic Rendering
291
+
292
+ > Define `build_ui()` once. The library calls it on every relevant state change and ships the edit for you. No `on_state_changed()` override, no manual `refresh()`, no `message.edit()` plumbing.
293
+
294
+ ```python
295
+ class SettingsHub(StatefulLayoutView):
296
+ state_scope = "user"
297
+ subscribed_actions = {"SETTINGS_UPDATED"}
298
+
299
+ def build_ui(self):
300
+ self.clear_items()
301
+ settings = self.user_scoped_state()
302
+ self.add_item(card(
303
+ "## Settings",
304
+ key_value({
305
+ "Theme": settings.get("theme", "default").title(),
306
+ "Notifications": "On" if settings.get("notify") else "Off",
307
+ }),
308
+ ))
309
+
310
+ # build_ui() is called automatically on state changes --
311
+ # no manual refresh() or on_state_changed() override needed.
312
+ ```
313
+
314
+ ![Dynamic Rendering](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-dynamic-rendering.gif)
315
+
316
+ ---
317
+
318
+ ### Navigation and Flow
319
+
320
+ > Push, pop, and replace views on a shared navigation stack. `MenuLayoutView` handles the wiring for category-based hubs -- declare your categories and target views, the pattern generates the push callbacks and `action_section()` items automatically.
321
+
322
+ ```python
323
+ from cascadeui import MenuLayoutView
324
+
325
+ class SettingsMenu(MenuLayoutView):
326
+ instance_limit = 1
327
+ instance_scope = "user_guild"
328
+ instance_policy = "replace"
329
+
330
+ def __init__(self, *args, **kwargs):
331
+ categories = [
332
+ {"label": "Appearance", "emoji": "\N{ARTIST PALETTE}",
333
+ "description": "Theme and accent colors", "view": AppearanceView},
334
+ {"label": "Notifications", "emoji": "\N{BELL}",
335
+ "description": "Alert preferences", "view": NotificationsView},
336
+ {"label": "Locale", "emoji": "\N{GLOBE WITH MERIDIANS}",
337
+ "description": "Language and timezone", "view": LocaleView},
338
+ ]
339
+ super().__init__(*args, categories=categories, **kwargs)
340
+ ```
341
+
342
+ ![Navigation](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-settings.gif)
343
+
344
+ ---
345
+
346
+ ### Ownership Control
347
+
348
+ > Views are owner-only by default - only the user who opened it can interact. For multi-user scenarios, `allowed_users` and `participant_limit` extend that control.
349
+
350
+ ```python
351
+ class BattleshipView(StatefulLayoutView):
352
+ unauthorized_message = "You're not part of this game."
353
+ instance_limit = 1
354
+ instance_policy = "reject"
355
+ participant_limit = 2
356
+ auto_register_participants = True
357
+
358
+ def __init__(self, *args, opponent_id: int, **kwargs):
359
+ super().__init__(*args, **kwargs)
360
+ self.allowed_users = {self.user_id, opponent_id}
361
+ ```
362
+
363
+ ![Ownership Control](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-ownership-control.gif)
364
+
365
+ ---
366
+
367
+ ### Lifecycle Control
368
+
369
+ > Control active sessions per user, guild, or globally with automatic cleanup, replacement policies, and view-capacity caps.
370
+
371
+ ```python
372
+ class DashboardView(TabLayoutView):
373
+ instance_limit = 1 # Only one open at a time
374
+ instance_scope = "user_guild" # Per user per guild
375
+ instance_policy = "replace" # Exit the old one, open the new one
376
+
377
+
378
+ class GameView(StatefulLayoutView):
379
+ participant_limit = 8 # Owner + 7 joiners maximum
380
+ auto_register_participants = True # Claim slots from allowed_users on send()
381
+ ```
382
+
383
+ ![V2 Instance Limiting](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-session-limiting.gif)
384
+
385
+ ---
386
+
387
+ ### Persistence and Continuity
388
+
389
+ > Persist views and state across restarts with automatic restoration.
390
+
391
+ ```python
392
+ from cascadeui import PersistenceMiddleware, SQLiteBackend, setup_middleware
393
+
394
+ # Install PersistenceMiddleware once in your bot's setup_hook:
395
+ async def setup_hook(self):
396
+ await setup_middleware(
397
+ PersistenceMiddleware(backend=SQLiteBackend("cascadeui.db"), bot=self),
398
+ )
399
+ ```
400
+
401
+ Declare `persistent_slots` on any view that should carry application state to disk. The rest stays volatile:
402
+
403
+ ```python
404
+ class BattleshipView(StatefulLayoutView):
405
+ scoped_slot = "battleship_stats" # per-subsystem bucket
406
+ persistent_slots = ("battleship_stats",) # opt this slot into persistence
407
+ ```
408
+
409
+ ![Persistence](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-persistence-restart.gif)
410
+
411
+ ---
412
+
413
+ ### State History (Undo/Redo)
414
+
415
+ > Snapshot-based state history per session with built-in undo and redo support.
416
+
417
+ ```python
418
+ class SettingsHub(StatefulLayoutView):
419
+ enable_undo = True # Every dispatch captures a snapshot
420
+
421
+ async def _undo(self, interaction):
422
+ await self.undo() # Restore previous snapshot
423
+
424
+ async def _redo(self, interaction):
425
+ await self.redo() # Reapply the reverted snapshot
426
+ ```
427
+
428
+ ![Undo/Redo](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-undo-redo.gif)
429
+
430
+ ---
431
+
432
+ ### Ephemeral Refresh
433
+
434
+ > Discord ephemeral messages become uneditable after 15 minutes. CascadeUI handles the token handoff automatically.
435
+
436
+ ```python
437
+ class FleetView(StatefulLayoutView):
438
+ timeout = 3600 # Handoff auto-engages for timeout > 900s
439
+ refresh_button_label = "Refresh" # Default: "Continue Session"
440
+ ```
441
+
442
+ ![Ephemeral Refresh](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-refresh.gif)
443
+
444
+ ---
445
+
446
+ ### Developer Tools
447
+
448
+ > Inspect live state, session activity, and performance timings without leaving your Discord client.
449
+
450
+ ```python
451
+ from cascadeui import DevToolsCog
452
+
453
+ # In your bot's setup_hook:
454
+ await bot.add_cog(DevToolsCog(bot))
455
+ ```
456
+
457
+ ![DevTools](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-devtools.gif)
458
+
459
+ ---
460
+
461
+ ## View Patterns
462
+
463
+ ### Category Menu
464
+
465
+ > Category-based navigation hubs with automatic drill-down, themed cards, and declarative per-category styling.
466
+
467
+ ```python
468
+ from cascadeui import MenuLayoutView, card, key_value
469
+
470
+ class ConfigHub(MenuLayoutView):
471
+ menu_style = discord.ButtonStyle.primary
472
+ auto_exit_button = True
473
+
474
+ def __init__(self, *args, **kwargs):
475
+ categories = [
476
+ {"label": "General", "emoji": "\u2699\ufe0f",
477
+ "description": "Core settings", "view": GeneralView},
478
+ {"label": "Moderation", "emoji": "\U0001f6e1\ufe0f",
479
+ "description": "AutoMod and logging", "view": ModerationView},
480
+ ]
481
+ super().__init__(*args, categories=categories, **kwargs)
482
+
483
+ def _build_header(self):
484
+ return [card("## Server Config", key_value(self._summary()))]
485
+ ```
486
+
487
+ ---
488
+
489
+ ### Tabbed Dashboard
490
+
491
+ > Structured, multi-section interfaces with tab-based navigation and composable layouts.
492
+
493
+ ![Dashboard](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-dashboard.gif)
494
+
495
+ ---
496
+
497
+ ### Dynamic Pagination
498
+
499
+ > Generate paginated interfaces from raw data with built-in navigation and formatting helpers.
500
+
501
+ ```python
502
+ import discord
503
+ from cascadeui import PaginatedLayoutView, card, divider
504
+
505
+ def format_page(items):
506
+ lines = [f"**{item['name']}** | {item['rarity']} | {item['value']}g" for item in items]
507
+ return [card(
508
+ "## Inventory",
509
+ divider(),
510
+ "\n".join(lines),
511
+ color=discord.Color.blue(),
512
+ )]
513
+
514
+ view = await PaginatedLayoutView.from_data(
515
+ items=all_items,
516
+ per_page=4,
517
+ formatter=format_page,
518
+ context=ctx,
519
+ )
520
+ await view.send()
521
+ ```
522
+
523
+ ![Pagination](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-pagination.gif)
524
+
525
+ ---
526
+
527
+ ### Leaderboards
528
+
529
+ > Paginated ranked displays with cross-page numbering, optional summary stats, and a persistent variant for admin-posted panels that refresh on live data without a bot restart.
530
+
531
+ ```python
532
+ from cascadeui import LeaderboardLayoutView, PersistentLeaderboardLayoutView, get_store
533
+
534
+ class BattleshipLeaderboard(LeaderboardLayoutView):
535
+ leaderboard_top_n = 25
536
+ leaderboard_per_page = 10
537
+ title = "Battleship Rankings"
538
+
539
+ def format_stats(self, user_id, stats):
540
+ wins = stats.get("wins", 0)
541
+ games = stats.get("games", 0)
542
+ return f"**{wins}W** / {games - wins}L"
543
+
544
+ def build_summary(self, entries):
545
+ # Each game contributes to two player rows, so halve for unique games.
546
+ unique_games = sum(s.get("games", 0) for _, s in entries) // 2
547
+ return {"Players": str(len(entries)), "Games Played": str(unique_games)}
548
+
549
+
550
+ # One-shot usage: fetch live entries and pass them in.
551
+ entries = get_store().computed["battleship_leaderboards"].get(guild_id, [])
552
+ view = BattleshipLeaderboard(context=ctx, entries=entries)
553
+ await view.send()
554
+
555
+
556
+ # Persistent variant -- admin-posted panel that survives bot restarts
557
+ # and re-fetches live data on every restore.
558
+ class PersistentBoard(PersistentLeaderboardLayoutView):
559
+ persistence_key = "battleship-leaderboard-main"
560
+ ```
561
+
562
+ ![Leaderboards](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-leaderboard.gif)
563
+
564
+ ---
565
+
566
+ ### Forms and Validation
567
+
568
+ > Define structured input flows with declarative fields, native text inputs, and per-field validation.
569
+
570
+ ```python
571
+ from cascadeui import FormLayoutView, min_length, regex
572
+
573
+ class RegistrationForm(FormLayoutView):
574
+ instance_limit = 1
575
+ instance_policy = "reject"
576
+ exit_policy = "delete"
577
+
578
+ def __init__(self, *args, **kwargs):
579
+ fields = [
580
+ {
581
+ "id": "username", "label": "Username", "type": "text",
582
+ "required": True, "min_length": 3, "max_length": 20,
583
+ "validators": [
584
+ min_length(3),
585
+ regex(r"^[a-zA-Z0-9_]+$", "Alphanumeric and underscores only"),
586
+ ],
587
+ },
588
+ {
589
+ "id": "role", "label": "Role", "type": "select",
590
+ "required": True,
591
+ "options": [
592
+ {"label": "Developer", "value": "dev"},
593
+ {"label": "Designer", "value": "design"},
594
+ ],
595
+ },
596
+ ]
597
+ super().__init__(*args, title="Registration", fields=fields, **kwargs)
598
+
599
+ async def on_submit(self, interaction, values):
600
+ await self.respond(
601
+ interaction, f"Welcome, {values['username']}!", ephemeral=True,
602
+ )
603
+ await self.exit()
604
+ ```
605
+
606
+ ![Forms](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-form.gif)
607
+
608
+ ---
609
+
610
+ ### Multi-Step Wizard
611
+
612
+ > Multi-step flows with back/next/finish navigation, per-step builders and validators, and fully customizable button styling.
613
+
614
+ ```python
615
+ from cascadeui import WizardLayoutView
616
+
617
+ class CharacterCreator(WizardLayoutView):
618
+ instance_limit = 1
619
+ instance_policy = "replace"
620
+ exit_policy = "delete"
621
+
622
+ back_button_label = "Previous"
623
+ next_button_label = "Continue"
624
+ finish_button_label = "Create Character"
625
+ finish_button_style = discord.ButtonStyle.success
626
+
627
+ def __init__(self, *args, **kwargs):
628
+ steps = [
629
+ {"name": "Identity", "builder": self.build_identity},
630
+ {"name": "Class", "builder": self.build_class},
631
+ {"name": "Stats", "builder": self.build_stats},
632
+ {"name": "Review", "builder": self.build_review},
633
+ ]
634
+ super().__init__(*args, steps=steps, **kwargs)
635
+ ```
636
+
637
+ ![Wizard](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-wizard.gif)
638
+
639
+ ---
640
+
641
+ ## Component Patterns
642
+
643
+ ### Emoji Grid
644
+
645
+ > Text-rendered grids with optional axis labels and a mutation API. Plugs directly into `card()` and `Container`.
646
+
647
+ ```python
648
+ from cascadeui import emoji_grid, card
649
+
650
+ grid = emoji_grid(4, 4, fill="\u2b1c", col_labels="numeric")
651
+ grid.fill_rect((1, 0), (1, 3), "\U0001f7e6")
652
+ grid[(2, 2)] = "\u2764\ufe0f"
653
+
654
+ view.add_item(card(grid))
655
+ ```
656
+
657
+ ![Emoji Grid](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-emoji-grid.PNG)
658
+
659
+ ---
660
+
661
+ ### Button Grid
662
+
663
+ > Interactive cell grids packed into `ActionRow` components. Discord's 5x5 limit is enforced automatically.
664
+
665
+ ```python
666
+ from cascadeui import button_grid, StatefulButton
667
+
668
+ rows = button_grid(3, 3, lambda r, c: StatefulButton(
669
+ label=f"{chr(65 + r)}{c + 1}",
670
+ style=discord.ButtonStyle.secondary,
671
+ callback=on_cell_click,
672
+ ))
673
+ for row in rows:
674
+ view.add_item(row)
675
+ ```
676
+
677
+ ![Button Grid](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-button-grid.PNG)
678
+
679
+ ---
680
+
681
+ ## V1 Components
682
+
683
+ > CascadeUI supports traditional discord.py Views and embeds.
684
+
685
+ Use V1 when you need:
686
+ - Embed-specific features such as fields or timestamps
687
+ - Simpler layouts without containers
688
+
689
+ All core features such as navigation, persistence, and undo/redo are supported.
690
+
691
+ ```python
692
+ from cascadeui import PersistentView, SuccessButton
693
+ import discord
694
+
695
+ class TicketPanel(PersistentView):
696
+ persistence_key = "support-ticket-panel"
697
+ owner_only = False # Public panel -- anyone can open a ticket
698
+
699
+ def build_embed(self):
700
+ return discord.Embed(
701
+ title="Support Tickets",
702
+ description="Click below to open a private support thread.",
703
+ color=discord.Color.blurple(),
704
+ )
705
+
706
+ def __init__(self, **kwargs):
707
+ super().__init__(**kwargs)
708
+ self.add_item(SuccessButton(
709
+ label="Open Ticket",
710
+ custom_id="ticket-panel:open",
711
+ callback=self._open_ticket,
712
+ ))
713
+
714
+ async def _open_ticket(self, interaction):
715
+ # ... create private thread, send confirmation ...
716
+ await self.respond(interaction, "Ticket created!", ephemeral=True)
717
+ ```
718
+
719
+ ![Ticket System](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v1-ticket-system.gif)
720
+
721
+ ---
722
+
723
+ ## Examples
724
+
725
+ > The <a href="https://hollowthesilver.github.io/CascadeUI/examples/"><strong>documentation</strong></a> includes full implementations demonstrating practical usage:
726
+
727
+ - Dashboards and control panels
728
+ - Settings systems
729
+ - Pagination
730
+ - Forms and wizards
731
+ - Persistent views
732
+ - Multi-user games with shared state, hidden information, and challenge flows (TicTacToe, Battleship)
733
+ - Open-join lobbies with capacity caps and host-vs-participant authority (Werewolf-style)
734
+
735
+ ---
736
+
737
+ ## Documentation
738
+
739
+ - https://hollowthesilver.github.io/CascadeUI/
740
+
741
+ ---
742
+
743
+ ## Support
744
+
745
+ - Discord: https://discord.com/invite/9Xj68BpKRb
746
+ - Issues: https://github.com/HollowTheSilver/CascadeUI/issues
747
+
748
+ ---
749
+
750
+ ## Development
751
+
752
+ ```bash
753
+ git clone https://github.com/HollowTheSilver/CascadeUI.git
754
+ cd CascadeUI
755
+ pip install -e ".[dev]"
756
+
757
+ pytest tests/ -v
758
+ black cascadeui/
759
+ isort cascadeui/
760
+ ```
761
+
762
+ ---
763
+
764
+ ## Developer's Note
765
+
766
+ > I built CascadeUI with over **ten years** of Python experience behind it. All documentation, docstrings, and the entire testing module were written and designed using my custom **Anthropic Opus 4.6** sub-agents built on **Claude Code**. I don't try to hide that. I'm a proponent of efficient and responsible agent application in software design. That experience is what makes these tools effective. They're amplifiers, not substitutes.
767
+ >
768
+ > *-- Hollow*
769
+
770
+ ---
771
+
772
+ <p align="center">
773
+ MIT License
774
+ </p>