pycascadeui 3.12.2__tar.gz → 3.13.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 (147) hide show
  1. {pycascadeui-3.12.2/pycascadeui.egg-info → pycascadeui-3.13.0}/PKG-INFO +3 -3
  2. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/__init__.py +1 -1
  3. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/base.py +10 -8
  4. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/inputs.py +8 -11
  5. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/devtools.py +20 -1
  6. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/manager.py +359 -20
  7. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/actions.py +9 -2
  8. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/middleware/persistence.py +43 -18
  9. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/reducers.py +7 -1
  10. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/store.py +161 -9
  11. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/responses.py +22 -0
  12. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/_interaction.py +10 -8
  13. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/_navigation.py +27 -4
  14. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/base.py +283 -68
  15. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/patterns/form.py +4 -4
  16. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/persistent.py +128 -37
  17. {pycascadeui-3.12.2 → pycascadeui-3.13.0/pycascadeui.egg-info}/PKG-INFO +3 -3
  18. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/pycascadeui.egg-info/requires.txt +2 -2
  19. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/pyproject.toml +3 -3
  20. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_devtools.py +59 -0
  21. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_dynamic_persistent_button.py +12 -2
  22. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_ephemeral_refresh.py +58 -0
  23. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_form_patterns.py +13 -4
  24. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_inputs.py +14 -0
  25. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_layout_view.py +191 -1
  26. pycascadeui-3.13.0/tests/test_message_cleanup.py +521 -0
  27. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_navigation.py +57 -2
  28. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_persistence.py +1350 -2
  29. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_persistence_middleware.py +3 -0
  30. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_persistent_views.py +421 -0
  31. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_public_api.py +10 -0
  32. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_state_store.py +217 -0
  33. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_view_init.py +25 -0
  34. pycascadeui-3.12.2/tests/test_message_cleanup.py +0 -239
  35. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/LICENSE +0 -0
  36. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/README.md +0 -0
  37. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/__main__.py +0 -0
  38. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/__init__.py +0 -0
  39. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/buttons.py +0 -0
  40. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/patterns/__init__.py +0 -0
  41. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/patterns/v1.py +0 -0
  42. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/patterns/v2.py +0 -0
  43. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/selects.py +0 -0
  44. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/types.py +0 -0
  45. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/v1_composition.py +0 -0
  46. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/components/wrappers.py +0 -0
  47. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/exceptions.py +0 -0
  48. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/__init__.py +0 -0
  49. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/backends/__init__.py +0 -0
  50. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/backends/memory.py +0 -0
  51. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/backends/postgres.py +0 -0
  52. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/backends/sqlite.py +0 -0
  53. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/config.py +0 -0
  54. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/migrations.py +0 -0
  55. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/protocols.py +0 -0
  56. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/schema.py +0 -0
  57. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/persistence/schema_postgres.py +0 -0
  58. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/py.typed +0 -0
  59. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/setup.py +0 -0
  60. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/__init__.py +0 -0
  61. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/_batching.py +0 -0
  62. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/computed.py +0 -0
  63. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/middleware/__init__.py +0 -0
  64. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/middleware/logging.py +0 -0
  65. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/middleware/undo.py +0 -0
  66. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/singleton.py +0 -0
  67. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/slots.py +0 -0
  68. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/state/types.py +0 -0
  69. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/testing.py +0 -0
  70. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/theming/__init__.py +0 -0
  71. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/theming/context.py +0 -0
  72. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/theming/core.py +0 -0
  73. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/theming/themes.py +0 -0
  74. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/tracing.py +0 -0
  75. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/__init__.py +0 -0
  76. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/coercion.py +0 -0
  77. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/decorators.py +0 -0
  78. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/errors.py +0 -0
  79. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/fetch.py +0 -0
  80. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/guards.py +0 -0
  81. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/hooks.py +0 -0
  82. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/logging.py +0 -0
  83. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/strings.py +0 -0
  84. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/utils/tasks.py +0 -0
  85. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/validation.py +0 -0
  86. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/__init__.py +0 -0
  87. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/_placement.py +0 -0
  88. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/layout.py +0 -0
  89. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/patterns/__init__.py +0 -0
  90. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/patterns/leaderboard.py +0 -0
  91. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/patterns/menu.py +0 -0
  92. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/patterns/paginated.py +0 -0
  93. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/patterns/roles.py +0 -0
  94. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/patterns/tabs.py +0 -0
  95. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/patterns/types.py +0 -0
  96. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/patterns/wizard.py +0 -0
  97. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/cascadeui/views/view.py +0 -0
  98. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/pycascadeui.egg-info/SOURCES.txt +0 -0
  99. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/pycascadeui.egg-info/dependency_links.txt +0 -0
  100. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/pycascadeui.egg-info/top_level.txt +0 -0
  101. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/setup.cfg +0 -0
  102. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_auto_defer.py +0 -0
  103. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_backends.py +0 -0
  104. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_backends_postgres.py +0 -0
  105. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_backends_raw_sql.py +0 -0
  106. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_batching.py +0 -0
  107. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_button_grid.py +0 -0
  108. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_components.py +0 -0
  109. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_computed.py +0 -0
  110. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_emoji_grid.py +0 -0
  111. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_examples.py +0 -0
  112. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_fetch.py +0 -0
  113. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_five_pillars.py +0 -0
  114. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_hooks.py +0 -0
  115. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_input_coercion.py +0 -0
  116. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_instance_limit.py +0 -0
  117. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_invariants.py +0 -0
  118. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_layout_pagination.py +0 -0
  119. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_layout_patterns.py +0 -0
  120. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_layout_persistent.py +0 -0
  121. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_leaderboard_patterns.py +0 -0
  122. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_logging_middleware.py +0 -0
  123. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_menu_patterns.py +0 -0
  124. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_middleware.py +0 -0
  125. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_nav_version.py +0 -0
  126. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_navigation_artifact_restore.py +0 -0
  127. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_navigation_matrix.py +0 -0
  128. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_owner_only.py +0 -0
  129. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_paginated_patterns.py +0 -0
  130. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_pagination.py +0 -0
  131. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_pillar_isolation.py +0 -0
  132. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_placement.py +0 -0
  133. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_rebuild_callback.py +0 -0
  134. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_reducers.py +0 -0
  135. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_roles_patterns.py +0 -0
  136. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_scoping.py +0 -0
  137. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_selects.py +0 -0
  138. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_state_slots.py +0 -0
  139. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_tab_patterns.py +0 -0
  140. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_task_manager.py +0 -0
  141. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_theming.py +0 -0
  142. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_undo.py +0 -0
  143. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_update_coalescing.py +0 -0
  144. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_v2_helpers.py +0 -0
  145. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_validation.py +0 -0
  146. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_wizard_patterns.py +0 -0
  147. {pycascadeui-3.12.2 → pycascadeui-3.13.0}/tests/test_wrappers.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: pycascadeui
3
- Version: 3.12.2
3
+ Version: 3.13.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
@@ -28,8 +28,8 @@ Requires-Dist: discord.py>=2.7
28
28
  Provides-Extra: dev
29
29
  Requires-Dist: pytest>=7.0; extra == "dev"
30
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"
31
+ Requires-Dist: black<27.0,>=26.3; extra == "dev"
32
+ Requires-Dist: isort<9.0,>=8.0; extra == "dev"
33
33
  Requires-Dist: testcontainers[postgres]>=4.0; extra == "dev"
34
34
  Provides-Extra: docs
35
35
  Requires-Dist: mkdocs>=1.5; extra == "docs"
@@ -184,7 +184,7 @@ if "PostgresBackend" in _persistence_all:
184
184
  # // ========================================( Script )======================================== // #
185
185
 
186
186
 
187
- __version__ = "3.12.2"
187
+ __version__ = "3.13.0"
188
188
 
189
189
  # Export public API
190
190
  __all__ = [
@@ -13,7 +13,13 @@ from ..state.actions import ActionCreators
13
13
  from ..state.store import _CURRENT_INTERACTION
14
14
  from ..utils.coercion import coerce_snowflake_match
15
15
  from ..utils.hooks import accepts_second_positional, await_maybe, can_accept_positional
16
- from ..utils.responses import ack_backstop, open_modal_safe, respond_safe, trailing_ack
16
+ from ..utils.responses import (
17
+ ack_backstop,
18
+ open_modal_safe,
19
+ respond_safe,
20
+ trailing_ack,
21
+ validate_ack_delay,
22
+ )
17
23
  from .types import MAX_SELECT_OPTIONS
18
24
 
19
25
  logger = logging.getLogger(__name__)
@@ -552,15 +558,11 @@ class DynamicPersistentButton(
552
558
  ``DynamicPersistentButton`` itself, because a class's
553
559
  ``__init_subclass__`` runs on its subclasses, not on itself.
554
560
 
555
- Also validates ``auto_defer_delay`` (must be a positive number) at
556
- definition time, mirroring ``Modal``.
561
+ Also validates ``auto_defer_delay`` (a positive number under Discord's
562
+ 3s ack deadline) at definition time, sharing the check ``Modal`` uses.
557
563
  """
558
564
  super().__init_subclass__(**kwargs)
559
- delay = cls.auto_defer_delay
560
- if not isinstance(delay, (int, float)) or isinstance(delay, bool) or delay <= 0:
561
- raise ValueError(
562
- f"{cls.__name__}.auto_defer_delay must be a positive number, got {delay!r}"
563
- )
565
+ validate_ack_delay(cls.__name__, "auto_defer_delay", cls.auto_defer_delay)
564
566
  _dynamic_button_classes[f"{cls.__module__}.{cls.__qualname__}"] = cls
565
567
 
566
568
  @classmethod
@@ -11,7 +11,7 @@ from discord import CheckboxGroupOption, Interaction, RadioGroupOption, TextStyl
11
11
  from discord.ui.select import BaseSelect
12
12
 
13
13
  from ..utils.hooks import await_maybe
14
- from ..utils.responses import ack_backstop, respond_safe, trailing_ack
14
+ from ..utils.responses import ack_backstop, respond_safe, trailing_ack, validate_ack_delay
15
15
  from ..utils.strings import slugify
16
16
  from ..validation import validate_fields
17
17
  from .base import StatefulComponent, require_value_callback
@@ -593,19 +593,16 @@ class Modal(discord.ui.Modal, StatefulComponent):
593
593
  auto_defer_delay: float = 2.5
594
594
 
595
595
  def __init_subclass__(cls, **kwargs):
596
- """Reject a non-positive ``auto_defer_delay`` at subclass-definition time.
596
+ """Reject an unusable ``auto_defer_delay`` at subclass-definition time.
597
597
 
598
- A value at or below zero (or the wrong type) silently defeats the ack
599
- backstop, so it fails when the subclass is defined rather than as a
600
- dropped interaction at runtime. Mirrors the view side's
601
- ``_POSITIVE_NUMBER_ATTRS`` check.
598
+ A value at or below zero, the wrong type, or one at or past Discord's
599
+ 3s ack deadline silently defeats the ack backstop, so it fails when the
600
+ subclass is defined rather than as a dropped interaction at runtime.
601
+ Shares :func:`~cascadeui.utils.responses.validate_ack_delay` with the
602
+ view side's ``_ACK_DELAY_ATTRS`` check.
602
603
  """
603
604
  super().__init_subclass__(**kwargs)
604
- delay = cls.auto_defer_delay
605
- if not isinstance(delay, (int, float)) or isinstance(delay, bool) or delay <= 0:
606
- raise ValueError(
607
- f"{cls.__name__}.auto_defer_delay must be a positive number, got {delay!r}"
608
- )
605
+ validate_ack_delay(cls.__name__, "auto_defer_delay", cls.auto_defer_delay)
609
606
 
610
607
  def __init__(
611
608
  self,
@@ -563,6 +563,11 @@ class InspectorView(TabLayoutView):
563
563
 
564
564
  # Exit live instances first
565
565
  for view_id, view in list(active.items()):
566
+ # One view's exit cascades to the children attached to it, so a
567
+ # snapshot taken before the first exit can name a view that is
568
+ # already gone by the time the loop reaches it.
569
+ if view._torn_down():
570
+ continue
566
571
  try:
567
572
  await view.exit()
568
573
  exited += 1
@@ -1429,6 +1434,9 @@ class DevToolsCog(commands.Cog, name="cascadeui_devtools"):
1429
1434
  for view_id, view in list(active.items()):
1430
1435
  if gid is not None and getattr(view, "guild_id", None) != gid:
1431
1436
  continue
1437
+ # A view an earlier exit cascaded to is already gone.
1438
+ if view._torn_down():
1439
+ continue
1432
1440
  try:
1433
1441
  await view.exit()
1434
1442
  exited += 1
@@ -1525,6 +1533,9 @@ class DevToolsCog(commands.Cog, name="cascadeui_devtools"):
1525
1533
  # state half-torn-down.
1526
1534
  failed = 0
1527
1535
  for view_id, view in list(store.get_active_views().items()):
1536
+ # A view an earlier exit cascaded to is already gone.
1537
+ if view._torn_down():
1538
+ continue
1528
1539
  try:
1529
1540
  await view.exit()
1530
1541
  except Exception as exc:
@@ -1535,6 +1546,13 @@ class DevToolsCog(commands.Cog, name="cascadeui_devtools"):
1535
1546
  # added to ``StateStore.__init__`` flow through here automatically.
1536
1547
  store.state = StateStore._build_initial_state()
1537
1548
 
1549
+ # The registry mirror is rebuilt rather than left empty: rows the exit
1550
+ # loop above did not delete are still on disk, and a mirror that has
1551
+ # forgotten them retires nothing when their key is exited later.
1552
+ manager = getattr(store, "persistence_manager", None)
1553
+ if manager is not None:
1554
+ manager.reseed_registry_mirror()
1555
+
1538
1556
  # Observability: invalidate every @computed so cached aggregates
1539
1557
  # recompute against the empty state on next access, and clear the
1540
1558
  # selector memo so subscriber selectors do not short-circuit on
@@ -1669,7 +1687,8 @@ class DevToolsCog(commands.Cog, name="cascadeui_devtools"):
1669
1687
  live panel. The cost is that a channel the bot will never see
1670
1688
  again is re-fetched every startup. This surfaces how long each
1671
1689
  one has been that way, and hands the delete decision to whoever
1672
- runs the command.
1690
+ runs the command, or to the daily sweep when the middleware sets
1691
+ ``prune_unreachable_after_days``.
1673
1692
  """
1674
1693
  store = get_store()
1675
1694
  manager = getattr(store, "persistence_manager", None)