pycascadeui 3.0.0__tar.gz → 3.2.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 (129) hide show
  1. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/PKG-INFO +118 -137
  2. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/README.md +117 -134
  3. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/__init__.py +18 -3
  4. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/__init__.py +3 -0
  5. pycascadeui-3.2.0/cascadeui/components/base.py +402 -0
  6. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/inputs.py +114 -57
  7. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/patterns/v2.py +10 -9
  8. pycascadeui-3.2.0/cascadeui/components/selects.py +270 -0
  9. pycascadeui-3.2.0/cascadeui/components/types.py +25 -0
  10. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/wrappers.py +104 -49
  11. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/devtools.py +325 -47
  12. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/manager.py +20 -6
  13. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/persistence.py +31 -2
  14. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/undo.py +38 -2
  15. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/store.py +42 -14
  16. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/coercion.py +41 -1
  17. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/_navigation.py +199 -93
  18. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/base.py +21 -5
  19. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/layout.py +16 -16
  20. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/__init__.py +3 -0
  21. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/form.py +4 -1
  22. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/leaderboard.py +49 -10
  23. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/menu.py +6 -0
  24. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/paginated.py +106 -25
  25. pycascadeui-3.2.0/cascadeui/views/patterns/roles.py +538 -0
  26. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/tabs.py +3 -0
  27. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/types.py +85 -5
  28. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/wizard.py +9 -7
  29. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/persistent.py +27 -36
  30. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/PKG-INFO +118 -137
  31. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/SOURCES.txt +6 -0
  32. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/requires.txt +0 -3
  33. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pyproject.toml +1 -4
  34. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_batching.py +7 -6
  35. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_components.py +116 -0
  36. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_devtools.py +430 -2
  37. pycascadeui-3.2.0/tests/test_dynamic_persistent_button.py +330 -0
  38. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_form_patterns.py +164 -112
  39. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_input_coercion.py +70 -1
  40. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_inputs.py +109 -16
  41. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_instance_limit.py +28 -14
  42. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_layout_view.py +54 -36
  43. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_leaderboard_patterns.py +164 -10
  44. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_middleware.py +3 -3
  45. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_navigation.py +171 -3
  46. pycascadeui-3.2.0/tests/test_navigation_artifact_restore.py +232 -0
  47. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_owner_only.py +12 -6
  48. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_paginated_patterns.py +215 -3
  49. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_persistence.py +101 -6
  50. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_persistence_middleware.py +2 -2
  51. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_persistent_views.py +46 -4
  52. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_rebuild_callback.py +29 -14
  53. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_reducers.py +11 -3
  54. pycascadeui-3.2.0/tests/test_roles_patterns.py +505 -0
  55. pycascadeui-3.2.0/tests/test_selects.py +227 -0
  56. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_state_store.py +61 -1
  57. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_tab_patterns.py +5 -2
  58. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_undo.py +106 -7
  59. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_view_init.py +11 -10
  60. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_wizard_patterns.py +9 -4
  61. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_wrappers.py +135 -29
  62. pycascadeui-3.0.0/cascadeui/components/base.py +0 -199
  63. pycascadeui-3.0.0/cascadeui/components/selects.py +0 -106
  64. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/LICENSE +0 -0
  65. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/__main__.py +0 -0
  66. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/buttons.py +0 -0
  67. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/patterns/__init__.py +0 -0
  68. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/patterns/v1.py +0 -0
  69. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/v1_composition.py +0 -0
  70. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/exceptions.py +0 -0
  71. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/__init__.py +0 -0
  72. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/backends/__init__.py +0 -0
  73. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/backends/memory.py +0 -0
  74. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/backends/sqlite.py +0 -0
  75. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/config.py +0 -0
  76. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/migrations.py +0 -0
  77. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/protocols.py +0 -0
  78. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/schema.py +0 -0
  79. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/py.typed +0 -0
  80. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/setup.py +0 -0
  81. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/__init__.py +0 -0
  82. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/actions.py +0 -0
  83. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/computed.py +0 -0
  84. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/__init__.py +0 -0
  85. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/logging.py +0 -0
  86. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/reducers.py +0 -0
  87. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/singleton.py +0 -0
  88. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/slots.py +0 -0
  89. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/types.py +0 -0
  90. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/theming/__init__.py +0 -0
  91. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/theming/context.py +0 -0
  92. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/theming/core.py +0 -0
  93. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/theming/themes.py +0 -0
  94. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/tracing.py +0 -0
  95. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/__init__.py +0 -0
  96. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/decorators.py +0 -0
  97. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/errors.py +0 -0
  98. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/helpers.py +0 -0
  99. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/logging.py +0 -0
  100. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/tasks.py +0 -0
  101. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/validation.py +0 -0
  102. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/__init__.py +0 -0
  103. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/_interaction.py +0 -0
  104. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/view.py +0 -0
  105. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/dependency_links.txt +0 -0
  106. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/top_level.txt +0 -0
  107. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/setup.cfg +0 -0
  108. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_auto_defer.py +0 -0
  109. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_backends.py +0 -0
  110. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_button_grid.py +0 -0
  111. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_computed.py +0 -0
  112. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_emoji_grid.py +0 -0
  113. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_ephemeral_refresh.py +0 -0
  114. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_five_pillars.py +0 -0
  115. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_hooks.py +0 -0
  116. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_layout_pagination.py +0 -0
  117. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_layout_patterns.py +0 -0
  118. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_layout_persistent.py +0 -0
  119. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_menu_patterns.py +0 -0
  120. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_message_cleanup.py +0 -0
  121. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_nav_version.py +0 -0
  122. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_pagination.py +0 -0
  123. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_pillar_isolation.py +0 -0
  124. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_scoping.py +0 -0
  125. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_state_slots.py +0 -0
  126. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_theming.py +0 -0
  127. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_update_coalescing.py +0 -0
  128. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_v2_helpers.py +0 -0
  129. {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_validation.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycascadeui
3
- Version: 3.0.0
3
+ Version: 3.2.0
4
4
  Summary: Redux-inspired UI framework for discord.py
5
5
  Author-email: HollowTheSilver <hollow@users.noreply.github.com>
6
6
  License-Expression: MIT
@@ -36,8 +36,6 @@ Requires-Dist: mkdocs-material>=9.0; extra == "docs"
36
36
  Requires-Dist: pymdown-extensions>=10.0; extra == "docs"
37
37
  Provides-Extra: sqlite
38
38
  Requires-Dist: aiosqlite>=0.19; extra == "sqlite"
39
- Provides-Extra: redis
40
- Requires-Dist: redis>=5.0; extra == "redis"
41
39
  Dynamic: license-file
42
40
 
43
41
  <p align="center">
@@ -59,11 +57,11 @@ Dynamic: license-file
59
57
 
60
58
  <p align="center">
61
59
  <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>
60
+ A flexible, Redux-inspired UI framework that introduces centralized state, access control, lifecycle control, and predictable data flow to Discord applications.<br>
63
61
  </p>
64
62
 
65
63
  <div align="center">
66
- <img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-hero.gif" alt="CascadeUI Hero Demo" width="600">
64
+ <img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-devtools.gif" alt="CascadeUI Hero Demo" width="600">
67
65
  </div>
68
66
 
69
67
  <p align="center">
@@ -129,7 +127,7 @@ CascadeUI ports Redux's mental model onto Discord. Most core primitives have a c
129
127
 
130
128
  ## When to Use
131
129
 
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.
130
+ > Every discord.py view requires access 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
131
 
134
132
  Even a single-view panel benefits from `owner_only = True` and `instance_limit = 1`. As your interface grows, the same framework scales to:
135
133
 
@@ -200,73 +198,7 @@ See the [Quickstart](https://hollowthesilver.github.io/CascadeUI/guide/quickstar
200
198
 
201
199
  ---
202
200
 
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
201
+ ## Feature Showcase
270
202
 
271
203
  ### Cross-View Reactivity
272
204
 
@@ -292,22 +224,25 @@ class NotificationPanel(StatefulLayoutView):
292
224
  > 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
225
 
294
226
  ```python
295
- class SettingsHub(StatefulLayoutView):
227
+ class MyFleetView(StatefulLayoutView):
296
228
  state_scope = "user"
297
- subscribed_actions = {"SETTINGS_UPDATED"}
229
+ subscribed_actions = {"FLEET_REROLLED"}
298
230
 
299
231
  def build_ui(self):
300
232
  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
- }),
233
+ grid = emoji_grid(10, 10, fill="\U0001f7e6", row_labels="alpha", col_labels="numeric")
234
+ for row, col in self.ship_cells():
235
+ grid[row, col] = "\U0001f6a2"
236
+
237
+ self.add_item(card("## My Fleet", divider(), grid, color=discord.Color.blue()))
238
+ self.add_item(ActionRow(
239
+ StatefulButton(label="Regenerate", emoji="\U0001f3b2", callback=self._reroll),
308
240
  ))
309
241
 
310
- # build_ui() is called automatically on state changes --
242
+ async def _reroll(self, interaction):
243
+ await self.dispatch("FLEET_REROLLED", {"cells": random_placement()})
244
+
245
+ # build_ui() runs automatically on every FLEET_REROLLED dispatch --
311
246
  # no manual refresh() or on_state_changed() override needed.
312
247
  ```
313
248
 
@@ -366,18 +301,14 @@ class BattleshipView(StatefulLayoutView):
366
301
 
367
302
  ### Lifecycle Control
368
303
 
369
- > Control active sessions per user, guild, or globally with automatic cleanup, replacement policies, and view-capacity caps.
304
+ > Cap active sessions per user, guild, or globally. Pick how a collision resolves and how the old view cleans up when a new one opens.
370
305
 
371
306
  ```python
372
- class DashboardView(TabLayoutView):
307
+ class SettingsHubView(MenuLayoutView):
373
308
  instance_limit = 1 # Only one open at a time
374
309
  instance_scope = "user_guild" # Per user per guild
375
310
  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()
311
+ exit_policy = "disable" # Old view's buttons grey out, message stays
381
312
  ```
382
313
 
383
314
  ![V2 Instance Limiting](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-session-limiting.gif)
@@ -398,12 +329,18 @@ async def setup_hook(self):
398
329
  )
399
330
  ```
400
331
 
401
- Declare `persistent_slots` on any view that should carry application state to disk. The rest stays volatile:
332
+ Subclass `PersistentRolesLayoutView` and declare your categories. The pattern handles button rendering, cardinality enforcement, and restart re-attachment. A stable `persistence_key` is the match identity the middleware uses to find this panel after restart:
402
333
 
403
334
  ```python
404
- class BattleshipView(StatefulLayoutView):
405
- scoped_slot = "battleship_stats" # per-subsystem bucket
406
- persistent_slots = ("battleship_stats",) # opt this slot into persistence
335
+ class GuildRoles(PersistentRolesLayoutView):
336
+ categories = [
337
+ RoleCategory(name="Color Roles", exclusive=True, roles={"Red": 123, "Blue": 456}),
338
+ RoleCategory(name="Pronouns", required=True, roles={"He/Him": 789, "She/Her": 12}),
339
+ ]
340
+
341
+ # Same key in, same panel out -- across bot restarts.
342
+ panel = GuildRoles(context=ctx, persistence_key=f"roles:{ctx.guild.id}")
343
+ await panel.send()
407
344
  ```
408
345
 
409
346
  ![Persistence](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-persistence-restart.gif)
@@ -415,8 +352,9 @@ class BattleshipView(StatefulLayoutView):
415
352
  > Snapshot-based state history per session with built-in undo and redo support.
416
353
 
417
354
  ```python
418
- class SettingsHub(StatefulLayoutView):
355
+ class NotificationsView(StatefulLayoutView):
419
356
  enable_undo = True # Every dispatch captures a snapshot
357
+ undo_limit = 10 # Stack depth cap (self.undo_depth / self.redo_depth read live)
420
358
 
421
359
  async def _undo(self, interaction):
422
360
  await self.undo() # Restore previous snapshot
@@ -556,7 +494,13 @@ await view.send()
556
494
  # Persistent variant -- admin-posted panel that survives bot restarts
557
495
  # and re-fetches live data on every restore.
558
496
  class PersistentBoard(PersistentLeaderboardLayoutView):
559
- persistence_key = "battleship-leaderboard-main"
497
+ pass
498
+
499
+ panel = PersistentBoard(
500
+ context=ctx,
501
+ persistence_key="battleship-leaderboard-main",
502
+ )
503
+ await panel.send()
560
504
  ```
561
505
 
562
506
  ![Leaderboards](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-leaderboard.gif)
@@ -638,8 +582,6 @@ class CharacterCreator(WizardLayoutView):
638
582
 
639
583
  ---
640
584
 
641
- ## Component Patterns
642
-
643
585
  ### Emoji Grid
644
586
 
645
587
  > Text-rendered grids with optional axis labels and a mutation API. Plugs directly into `card()` and `Container`.
@@ -654,10 +596,6 @@ grid[(2, 2)] = "\u2764\ufe0f"
654
596
  view.add_item(card(grid))
655
597
  ```
656
598
 
657
- ![Emoji Grid](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-emoji-grid.PNG)
658
-
659
- ---
660
-
661
599
  ### Button Grid
662
600
 
663
601
  > Interactive cell grids packed into `ActionRow` components. Discord's 5x5 limit is enforced automatically.
@@ -674,49 +612,76 @@ for row in rows:
674
612
  view.add_item(row)
675
613
  ```
676
614
 
677
- ![Button Grid](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-button-grid.PNG)
615
+ <div align="center">
616
+ <img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-emoji-grid.PNG" width="30%" alt="Emoji Grid" style="border-radius: 8px; margin: 5px;" />
617
+ <img src="https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/pngs/v2-button-grid.PNG" width="30%" alt="Button Grid" style="border-radius: 8px; margin: 5px;" />
618
+ </div>
678
619
 
679
620
  ---
680
621
 
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.
622
+ ## Features
690
623
 
691
- ```python
692
- from cascadeui import PersistentView, SuccessButton
693
- import discord
624
+ > For full details, see the official <a href="https://hollowthesilver.github.io/CascadeUI/"><strong>documentation</strong></a>.
694
625
 
695
- class TicketPanel(PersistentView):
696
- persistence_key = "support-ticket-panel"
697
- owner_only = False # Public panel -- anyone can open a ticket
626
+ ### State
627
+ - Centralized store with dispatch and reducer cycle
628
+ - Custom reducers via `@cascade_reducer` with automatic deep copy and collision guards
629
+ - Action batching with nested-batch collapse and a single notification per commit
630
+ - `@computed` values with selector-based cache invalidation (≈ Reselect / `useMemo`)
631
+ - Selector-based subscriptions for targeted re-renders
632
+ - Scoped state family: `get_scoped()`, `set_scoped()`, `merge_scoped()`, `iter_scoped()`
633
+ - Slot helpers: `access_slot()`, `read_slot()`, `slot_property`
634
+ - Middleware pipeline for logging, persistence, and transformation (Redux-style, async)
635
+ - Event hooks for lifecycle observation
636
+ - Cross-view reactivity: dispatch from any view, all subscribers update instantly
698
637
 
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
- )
638
+ ### Views
639
+ - V2 layout-based system for structured, container-driven interfaces
640
+ - Full support for traditional discord.py Views (V1)
641
+ - Pre-built patterns: menus, tabs, wizards, forms, pagination, leaderboards, roles
642
+ - `PaginatedView.from_cursor()` for lazy cursor-driven pagination with LRU page cache
643
+ - `DisplayLayoutView` for one-shot V2 sends from a pre-built container
644
+ - Automatic state-driven rebuilds: define `build_ui()`, the library handles the edit (≈ React `render()`)
645
+ - Theming with per-view overrides and a `ContextVar` that propagates through builders (≈ `React.Context`)
705
646
 
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
- ))
647
+ ### Components
648
+ - Stateful buttons, selects, and modals with state integration
649
+ - Select callbacks can opt into a `values` second parameter
650
+ - V2 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()`
651
+ - Grid helpers: `emoji_grid()` and `button_grid()`
652
+ - Typed modal fields (`text`, `integer`, `float`, `date`) with per-field validation
653
+ - Declarative `FormSchema` and `WizardSchema` base classes
654
+ - Component wrappers: loading states, confirmation dialogs, cooldowns
713
655
 
714
- async def _open_ticket(self, interaction):
715
- # ... create private thread, send confirmation ...
716
- await self.respond(interaction, "Ticket created!", ephemeral=True)
717
- ```
656
+ ### Interaction Control
657
+ - Owner-only views by default; `allowed_users` opens access to specific users
658
+ - Instance limits per user, guild, user+guild, or globally with replace or reject policies
659
+ - `participant_limit` with `on_participant_limit` hook and `auto_register_participants`
660
+ - `check_instance_available()` for fail-fast pre-checks before constructing expensive views
661
+ - Auto-defer with `respond()`, `open_modal()`, and `_safe_defer()` helpers
662
+ - Interaction serialization so rapid clicks process sequentially
663
+ - Refresh throttling via `refresh_cooldown_ms` and reactive 429 backoff
664
+ - Silent snowflake coercion at every public boundary
665
+ - Class-attribute validation at subclass-definition time
666
+
667
+ ### Navigation and Lifecycle
668
+ - Navigation stack: `push()`, `pop()`, `replace()` on one shared message (≈ React Router)
669
+ - Parent/child view lifecycle via `attach_child()` or `parent=` with automatic cleanup
670
+ - `session_continuity` opt-in for repeat-open state coalescing
671
+ - `auto_refresh_ephemeral` for user-driven token handoff past the 15-minute ephemeral wall
672
+ - Automatic message re-fetch so long-lived views survive the interaction token's 15-minute window
673
+
674
+ ### Persistence
675
+ - Persistent views that survive bot restarts with automatic message re-attachment
676
+ - Opt-in per slot via `persistent_slots = (...)` (≈ `redux-persist`)
677
+ - Built-in SQLite and in-memory backends; custom backends via capability-flag `Protocol`
678
+ - Named scoped buckets via `scoped_slot` for per-subsystem persistence
679
+ - Two-namespace model (`registry` and `application`) with per-namespace debounce and retry backoff
680
+ - Undo and redo via snapshot-based state history (opt in with `enable_undo`)
718
681
 
719
- ![Ticket System](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v1-ticket-system.gif)
682
+ ### Developer Tools
683
+ - Built-in profiler with markdown and JSON exports
684
+ - `DevToolsCog` with a tabbed state inspector and owner-only `/cascadeui` command group
720
685
 
721
686
  ---
722
687
 
@@ -732,6 +697,22 @@ class TicketPanel(PersistentView):
732
697
  - Multi-user games with shared state, hidden information, and challenge flows (TicTacToe, Battleship)
733
698
  - Open-join lobbies with capacity caps and host-vs-participant authority (Werewolf-style)
734
699
 
700
+ ![Examples](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v2-hero.gif)
701
+
702
+ ---
703
+
704
+ ## V1 Components
705
+
706
+ > CascadeUI supports traditional discord.py Views and embeds.
707
+
708
+ Use V1 when you need:
709
+ - Embed-specific features such as fields or timestamps
710
+ - Simpler layouts without containers
711
+
712
+ All core features such as navigation, persistence, and undo/redo are supported.
713
+
714
+ ![Ticket System](https://raw.githubusercontent.com/HollowTheSilver/CascadeUI/main/assets/gifs/v1-ticket-system.gif)
715
+
735
716
  ---
736
717
 
737
718
  ## Documentation
@@ -763,7 +744,7 @@ isort cascadeui/
763
744
 
764
745
  ## Developer's Note
765
746
 
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.
747
+ > I built CascadeUI with over **ten years** of Python, and roughly fifteen years of development experience. All documentation, docstrings and test modules are written and designed using custom **Anthropic Opus** sub-agents. I do not attempt to conceal this fact. 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
748
  >
768
749
  > *-- Hollow*
769
750