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.
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/PKG-INFO +118 -137
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/README.md +117 -134
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/__init__.py +18 -3
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/__init__.py +3 -0
- pycascadeui-3.2.0/cascadeui/components/base.py +402 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/inputs.py +114 -57
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/patterns/v2.py +10 -9
- pycascadeui-3.2.0/cascadeui/components/selects.py +270 -0
- pycascadeui-3.2.0/cascadeui/components/types.py +25 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/wrappers.py +104 -49
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/devtools.py +325 -47
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/manager.py +20 -6
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/persistence.py +31 -2
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/undo.py +38 -2
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/store.py +42 -14
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/coercion.py +41 -1
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/_navigation.py +199 -93
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/base.py +21 -5
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/layout.py +16 -16
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/__init__.py +3 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/form.py +4 -1
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/leaderboard.py +49 -10
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/menu.py +6 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/paginated.py +106 -25
- pycascadeui-3.2.0/cascadeui/views/patterns/roles.py +538 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/tabs.py +3 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/types.py +85 -5
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/patterns/wizard.py +9 -7
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/persistent.py +27 -36
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/PKG-INFO +118 -137
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/SOURCES.txt +6 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/requires.txt +0 -3
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pyproject.toml +1 -4
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_batching.py +7 -6
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_components.py +116 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_devtools.py +430 -2
- pycascadeui-3.2.0/tests/test_dynamic_persistent_button.py +330 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_form_patterns.py +164 -112
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_input_coercion.py +70 -1
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_inputs.py +109 -16
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_instance_limit.py +28 -14
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_layout_view.py +54 -36
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_leaderboard_patterns.py +164 -10
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_middleware.py +3 -3
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_navigation.py +171 -3
- pycascadeui-3.2.0/tests/test_navigation_artifact_restore.py +232 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_owner_only.py +12 -6
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_paginated_patterns.py +215 -3
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_persistence.py +101 -6
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_persistence_middleware.py +2 -2
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_persistent_views.py +46 -4
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_rebuild_callback.py +29 -14
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_reducers.py +11 -3
- pycascadeui-3.2.0/tests/test_roles_patterns.py +505 -0
- pycascadeui-3.2.0/tests/test_selects.py +227 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_state_store.py +61 -1
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_tab_patterns.py +5 -2
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_undo.py +106 -7
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_view_init.py +11 -10
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_wizard_patterns.py +9 -4
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_wrappers.py +135 -29
- pycascadeui-3.0.0/cascadeui/components/base.py +0 -199
- pycascadeui-3.0.0/cascadeui/components/selects.py +0 -106
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/LICENSE +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/__main__.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/buttons.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/patterns/__init__.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/patterns/v1.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/components/v1_composition.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/exceptions.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/__init__.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/backends/__init__.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/backends/memory.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/backends/sqlite.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/config.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/migrations.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/protocols.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/persistence/schema.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/py.typed +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/setup.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/__init__.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/actions.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/computed.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/__init__.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/middleware/logging.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/reducers.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/singleton.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/slots.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/state/types.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/theming/__init__.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/theming/context.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/theming/core.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/theming/themes.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/tracing.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/__init__.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/decorators.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/errors.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/helpers.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/logging.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/utils/tasks.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/validation.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/__init__.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/_interaction.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/cascadeui/views/view.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/dependency_links.txt +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/pycascadeui.egg-info/top_level.txt +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/setup.cfg +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_auto_defer.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_backends.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_button_grid.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_computed.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_emoji_grid.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_ephemeral_refresh.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_five_pillars.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_hooks.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_layout_pagination.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_layout_patterns.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_layout_persistent.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_menu_patterns.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_message_cleanup.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_nav_version.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_pagination.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_pillar_isolation.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_scoping.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_state_slots.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_theming.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_update_coalescing.py +0 -0
- {pycascadeui-3.0.0 → pycascadeui-3.2.0}/tests/test_v2_helpers.py +0 -0
- {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.
|
|
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,
|
|
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-
|
|
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
|
|
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
|
-
##
|
|
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
|
|
227
|
+
class MyFleetView(StatefulLayoutView):
|
|
296
228
|
state_scope = "user"
|
|
297
|
-
subscribed_actions = {"
|
|
229
|
+
subscribed_actions = {"FLEET_REROLLED"}
|
|
298
230
|
|
|
299
231
|
def build_ui(self):
|
|
300
232
|
self.clear_items()
|
|
301
|
-
|
|
302
|
-
self.
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
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
|
-
|
|
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
|
-
>
|
|
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
|
|
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
|

|
|
@@ -398,12 +329,18 @@ async def setup_hook(self):
|
|
|
398
329
|
)
|
|
399
330
|
```
|
|
400
331
|
|
|
401
|
-
|
|
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
|
|
405
|
-
|
|
406
|
-
|
|
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
|

|
|
@@ -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
|
|
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
|
-
|
|
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
|

|
|
@@ -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
|
-

|
|
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
|
-
|
|
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
|
-
##
|
|
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
|
-
|
|
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
|
-
|
|
696
|
-
|
|
697
|
-
|
|
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
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
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
|
-
|
|
707
|
-
|
|
708
|
-
|
|
709
|
-
|
|
710
|
-
|
|
711
|
-
|
|
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
|
-
|
|
715
|
-
|
|
716
|
-
|
|
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
|
-
|
|
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
|
+

|
|
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
|
+

|
|
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
|
|
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
|
|