nuspace 0.7.2__tar.gz → 0.7.3__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 (191) hide show
  1. {nuspace-0.7.2 → nuspace-0.7.3}/CHANGELOG.md +28 -2
  2. {nuspace-0.7.2 → nuspace-0.7.3}/PKG-INFO +6 -6
  3. {nuspace-0.7.2 → nuspace-0.7.3}/pyproject.toml +6 -6
  4. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/host/presets.py +4 -1
  5. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/ops/cell.py +6 -4
  6. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/ops/plane.py +41 -18
  7. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/plane.py +6 -6
  8. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/specs/ops.py +1 -2
  9. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/specs/specs.py +6 -6
  10. nuspace-0.7.3/src/nuspace/system/init/__init__.py +12 -0
  11. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/init/plane.py +9 -11
  12. nuspace-0.7.3/src/nuspace/system/init/shapes.py +33 -0
  13. nuspace-0.7.3/src/nuspace/system/planes.py +94 -0
  14. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/reactions/__init__.py +4 -5
  15. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/reactions/ops.py +3 -3
  16. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/reactions/plane.py +4 -7
  17. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/reactions/shapes.py +7 -7
  18. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/reload/__init__.py +3 -3
  19. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/reload/plane.py +6 -9
  20. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/supervisor/__init__.py +4 -5
  21. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/supervisor/plane.py +9 -13
  22. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/supervisor/shapes.py +11 -8
  23. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/utils/checks.py +20 -0
  24. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/__init__.py +5 -0
  25. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/desks/pruner.py +18 -17
  26. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/desks/ref.py +26 -5
  27. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/device.py +13 -5
  28. nuspace-0.7.3/src/nuspace/web/here.py +40 -0
  29. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/home/indexer.py +36 -27
  30. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/home/now.py +13 -7
  31. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/home/runs.py +13 -6
  32. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/interactions.py +2 -2
  33. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/ops.py +55 -18
  34. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/planes/__init__.py +25 -50
  35. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/planes/search.py +21 -11
  36. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/ref.py +10 -2
  37. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/shapes.py +14 -5
  38. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/sidebar/feed.py +4 -0
  39. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/sidebar/ref.py +4 -0
  40. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/utils.py +6 -0
  41. nuspace-0.7.3/src/nuspace/web/viewer/__init__.py +112 -0
  42. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/viewer/feed.py +238 -121
  43. nuspace-0.7.3/src/nuspace/web/viewer/interactions.py +278 -0
  44. nuspace-0.7.3/src/nuspace/web/viewer/ref.py +229 -0
  45. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_lib/_support/specs.py +20 -0
  46. nuspace-0.7.3/tests/_lib/_support/system.py +35 -0
  47. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_host.py +12 -7
  48. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_ops_cell.py +11 -0
  49. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_ops_plane.py +11 -0
  50. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_shapes.py +2 -2
  51. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_specs.py +3 -4
  52. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_system_ops.py +68 -47
  53. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_system_planes.py +20 -6
  54. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_web.py +263 -90
  55. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_web_home.py +43 -12
  56. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_web_indexer.py +28 -22
  57. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_web_ops.py +54 -15
  58. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_web_planes.py +11 -8
  59. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_web_pruner.py +12 -9
  60. nuspace-0.7.3/tests/nuspace/test_web_scroll.py +124 -0
  61. nuspace-0.7.2/src/nuspace/system/init/__init__.py +0 -12
  62. nuspace-0.7.2/src/nuspace/system/init/shapes.py +0 -30
  63. nuspace-0.7.2/src/nuspace/system/planes.py +0 -103
  64. nuspace-0.7.2/src/nuspace/web/viewer/__init__.py +0 -72
  65. nuspace-0.7.2/src/nuspace/web/viewer/interactions.py +0 -186
  66. nuspace-0.7.2/src/nuspace/web/viewer/ref.py +0 -123
  67. {nuspace-0.7.2 → nuspace-0.7.3}/.gitignore +0 -0
  68. {nuspace-0.7.2 → nuspace-0.7.3}/.pre-commit-config.yaml +0 -0
  69. {nuspace-0.7.2 → nuspace-0.7.3}/LICENSE.md +0 -0
  70. {nuspace-0.7.2 → nuspace-0.7.3}/Makefile +0 -0
  71. {nuspace-0.7.2 → nuspace-0.7.3}/README.md +0 -0
  72. {nuspace-0.7.2 → nuspace-0.7.3}/examples/space.py +0 -0
  73. {nuspace-0.7.2 → nuspace-0.7.3}/pkgs/ts/nuspace-ui/python/nuspace_ui/build +0 -0
  74. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/__init__.py +0 -0
  75. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_branding/__init__.py +0 -0
  76. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_branding/header.py +0 -0
  77. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_branding/logo.py +0 -0
  78. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_branding/palette.py +0 -0
  79. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/__init__.py +0 -0
  80. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/__main__.py +0 -0
  81. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/_installed.py +0 -0
  82. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/_meta.py +0 -0
  83. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/commands/__init__.py +0 -0
  84. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/commands/init.py +0 -0
  85. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/commands/run.py +0 -0
  86. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/commands/telemetry.py +0 -0
  87. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/commands/verse.py +0 -0
  88. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_cli/main.py +0 -0
  89. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_telemetry/__init__.py +0 -0
  90. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_telemetry/config.py +0 -0
  91. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_telemetry/send.py +0 -0
  92. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/_telemetry/usage.py +0 -0
  93. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/errors.py +0 -0
  94. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/host/__init__.py +0 -0
  95. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/host/devices.py +0 -0
  96. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/host/interactions.py +0 -0
  97. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/host/layout.py +0 -0
  98. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/host/lock.py +0 -0
  99. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/host/resources.py +0 -0
  100. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/host/space.py +0 -0
  101. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/host/stores.py +0 -0
  102. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/__init__.py +0 -0
  103. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/backends/__init__.py +0 -0
  104. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/backends/async_.py +0 -0
  105. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/backends/base.py +0 -0
  106. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/backends/interactions.py +0 -0
  107. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/backends/mp.py +0 -0
  108. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/backends/pool.py +0 -0
  109. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/body.py +0 -0
  110. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/close.py +0 -0
  111. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/envs.py +0 -0
  112. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/interactions.py +0 -0
  113. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/kernel.py +0 -0
  114. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/out.py +0 -0
  115. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/reap.py +0 -0
  116. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/kernel/records.py +0 -0
  117. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/ops/__init__.py +0 -0
  118. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/ops/brackets.py +0 -0
  119. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/ops/cell_run.py +0 -0
  120. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/ops/interactions.py +0 -0
  121. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/ops/plane_run.py +0 -0
  122. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/ops/state.py +0 -0
  123. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/ops/worker.py +0 -0
  124. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/places.py +0 -0
  125. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/__init__.py +0 -0
  126. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/cell.py +0 -0
  127. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/cell_run.py +0 -0
  128. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/content.py +0 -0
  129. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/exec.py +0 -0
  130. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/plane_run.py +0 -0
  131. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/space.py +0 -0
  132. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/state.py +0 -0
  133. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/store.py +0 -0
  134. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/shapes/worker.py +0 -0
  135. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/specs/__init__.py +0 -0
  136. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/specs/registry.py +0 -0
  137. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/__init__.py +0 -0
  138. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/init/ops.py +0 -0
  139. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/system/supervisor/ops.py +0 -0
  140. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/utils/__init__.py +0 -0
  141. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/utils/interactions.py +0 -0
  142. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/utils/placement.py +0 -0
  143. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/utils/policies.py +0 -0
  144. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/utils/reads.py +0 -0
  145. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/utils/tree.py +0 -0
  146. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/utils/typed.py +0 -0
  147. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/desks/__init__.py +0 -0
  148. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/desks/feed.py +0 -0
  149. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/desks/interactions.py +0 -0
  150. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/desks/model.py +0 -0
  151. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/desks/ops.py +0 -0
  152. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/env.py +0 -0
  153. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/home/__init__.py +0 -0
  154. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/home/common.py +0 -0
  155. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/home/history.py +0 -0
  156. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/home/space.py +0 -0
  157. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/nav.py +0 -0
  158. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/planes/settings.py +0 -0
  159. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/session.py +0 -0
  160. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/shell.py +0 -0
  161. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/sidebar/__init__.py +0 -0
  162. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/sidebar/interactions.py +0 -0
  163. {nuspace-0.7.2 → nuspace-0.7.3}/src/nuspace/web/stores.py +0 -0
  164. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_ext/nuspace_testing/__init__.py +0 -0
  165. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_ext/pyproject.toml +0 -0
  166. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_lib/_support/__init__.py +0 -0
  167. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_lib/_support/actions.py +0 -0
  168. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_lib/_support/contents.py +0 -0
  169. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_lib/_support/crash_host.py +0 -0
  170. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_lib/_support/kernel.py +0 -0
  171. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_lib/_support/kernel_envs.py +0 -0
  172. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_lib/_support/made.py +0 -0
  173. {nuspace-0.7.2 → nuspace-0.7.3}/tests/_lib/_support/probe.py +0 -0
  174. {nuspace-0.7.2 → nuspace-0.7.3}/tests/conftest.py +0 -0
  175. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_brackets.py +0 -0
  176. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_cli.py +0 -0
  177. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_content.py +0 -0
  178. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_errors.py +0 -0
  179. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_exports.py +0 -0
  180. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_kernel.py +0 -0
  181. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_nav.py +0 -0
  182. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_ops_cell_run.py +0 -0
  183. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_ops_plane_run.py +0 -0
  184. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_ops_state.py +0 -0
  185. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_ops_worker.py +0 -0
  186. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_packaging.py +0 -0
  187. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_places.py +0 -0
  188. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_session_pipe.py +0 -0
  189. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_system_utils.py +0 -0
  190. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_telemetry.py +0 -0
  191. {nuspace-0.7.2 → nuspace-0.7.3}/tests/nuspace/test_web_desks.py +0 -0
@@ -2,13 +2,39 @@
2
2
 
3
3
  ## Packages shipped from this repo
4
4
 
5
- - **nuspace** (the runtime, the `nuspace` command) — 0.7.2
6
- - **nuspace-ui** (the compiled web bundle) — 0.7.2
5
+ - **nuspace** (the runtime, the `nuspace` command) — 0.7.3
6
+ - **nuspace-ui** (the compiled web bundle) — 0.7.3
7
7
 
8
8
  Below is the changelog for **nuspace** - the full commit stream. Newest first.
9
9
  The `nuspace-ui` wheel is the same source tree's vite output and is not tracked
10
10
  separately here.
11
11
 
12
+ ## 0.7.3 - 2026-11-16
13
+
14
+ - Release nuspace and nuspace-ui 0.7.3
15
+ - Home's Recent timeline shows the last hour, so long runs never hide short ones
16
+ - System planes get minted ids, reserved keys and readable titles, found by key
17
+ - Home execution rows and timeline lanes name their plane without linking to it
18
+ - Sidebar hover buttons shrink to the fold chevron's size, rows and titles alike
19
+ - Boot skeleton gets its top gap back above the title
20
+
21
+ - Drop unused cell and status wire fields; ref classes cover every feed op
22
+ - Cells can make their pane follow the bottom or scroll, for chat style output
23
+ - Opening a deep plane opens its sidebar folders, and only the sidebar reads its tree
24
+ - Each open pane is its own node, so a rename no longer resends its cells
25
+ - Pane menu Width choice now reaches the server and sticks
26
+ - Urls are /p/<plane> and /d/<desk>; old plane links now read page not found
27
+ - Plane and cell ids given from outside must be slugs, so they read cleanly in urls
28
+ - Add plane and / menus fold packages by focus, with flat rows that wrap
29
+ - Pane breadcrumbs come from the server, so they show under collapsed sidebar branches
30
+ - Desk header shows only the desk name and close; items stay in the sidebar
31
+ - Search runs on Enter in any filter, on the new nu input contract
32
+ - Option Cmd click opens a plane to the side; panes get a close button again
33
+ - Desks get a header to switch items and leave; panes close from their menu
34
+ - Sidebar drops say what they do, refuse with a reason, and never shift rows
35
+ - Flat desk shell, one plane frame, and a page, board or full width setting
36
+ - Plane width replaces the compact and full width flags; update specs that set them
37
+
12
38
  ## 0.7.2 - 2026-11-13
13
39
 
14
40
  - Release nuspace and nuspace-ui 0.7.2
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nuspace
3
- Version: 0.7.2
3
+ Version: 0.7.3
4
4
  Summary: Computational space for your data and tools.
5
5
  Project-URL: Repository, https://github.com/nustackdev/nuspace
6
6
  Author-email: Gor Arakelyan <gorarkln@gmail.com>
@@ -17,16 +17,16 @@ Classifier: Programming Language :: Python :: 3.13
17
17
  Requires-Python: >=3.10
18
18
  Requires-Dist: certifi>=2023.7.22
19
19
  Requires-Dist: click>=8.0
20
- Requires-Dist: nucore>=0.7.4
21
- Requires-Dist: nuspace-ui>=0.7.2
22
- Requires-Dist: nustd[kv,mp-pool,proxy,ui,valkey]>=0.7.4
23
- Requires-Dist: nuverse>=0.3.10
20
+ Requires-Dist: nucore>=0.7.5
21
+ Requires-Dist: nuspace-ui>=0.7.3
22
+ Requires-Dist: nustd[kv,mp-pool,proxy,ui,valkey]>=0.7.5
23
+ Requires-Dist: nuverse>=0.3.11
24
24
  Requires-Dist: rich-click>=1.6
25
25
  Requires-Dist: rich>=10.7
26
26
  Requires-Dist: typing-extensions>=4.7.0
27
27
  Requires-Dist: virtuals-py[redis]>=0.2.2
28
28
  Provides-Extra: all
29
- Requires-Dist: nustd[all]>=0.7.4; extra == 'all'
29
+ Requires-Dist: nustd[all]>=0.7.5; extra == 'all'
30
30
  Description-Content-Type: text/markdown
31
31
 
32
32
  <div align="center">
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nuspace"
7
- version = "0.7.2"
7
+ version = "0.7.3"
8
8
  description = "Computational space for your data and tools."
9
9
  readme = "README.md"
10
10
  license = "AGPL-3.0-or-later"
@@ -14,17 +14,17 @@ dependencies = [
14
14
  # The fabrics a space runs on, nothing more: kv for the store, valkey for
15
15
  # its change notifications, ui for the browser, mp_pool for the workers,
16
16
  # proxy (invisibles) for the worker's session.
17
- "nucore>=0.7.4",
18
- "nustd[kv,ui,valkey,mp_pool,proxy]>=0.7.4",
17
+ "nucore>=0.7.5",
18
+ "nustd[kv,ui,valkey,mp_pool,proxy]>=0.7.5",
19
19
  # The store's change notifications run over virtuals' redis backend.
20
20
  "virtuals-py[redis]>=0.2.2",
21
21
  # The compiled web bundle. A separate distribution only because PyPI caps
22
22
  # project size and a SPA rebuilt on every UI tweak would eat the quota --
23
23
  # not optional. `pip install nuspace` gives you the browser.
24
- "nuspace-ui>=0.7.2",
24
+ "nuspace-ui>=0.7.3",
25
25
  # The everyday Planes and Cells. A plugin like any other, found through
26
26
  # its entry point, but every space ships with it.
27
- "nuverse>=0.3.10",
27
+ "nuverse>=0.3.11",
28
28
  "typing-extensions>=4.7.0",
29
29
  "click>=8.0",
30
30
  "rich>=10.7",
@@ -55,7 +55,7 @@ classifiers = [
55
55
  [project.optional-dependencies]
56
56
  # Every nustd fabric and kv backend, for cells that reach past the defaults.
57
57
  all = [
58
- "nustd[all]>=0.7.4",
58
+ "nustd[all]>=0.7.5",
59
59
  ]
60
60
 
61
61
  [project.urls]
@@ -102,7 +102,10 @@ def with_plane(
102
102
  def opened(plane: nu.ObjectRef) -> nu.Nu:
103
103
  steps = [_plane_of(found, plane)]
104
104
  if web and open_browser:
105
- url = nu.Str(f"http://{host}:{port}/") + nu.Str(plane)
105
+ # The web device's own url, so the tab opens on the same encoded path a link would.
106
+ from nuspace.web.interactions import Href
107
+
108
+ url = nu.Str(f"http://{host}:{port}") + nu.Str(Href(plane, {}))
106
109
  steps.append(Browse(url, host, port))
107
110
  return nu.Sequential(*steps, nu.Park())
108
111
 
@@ -21,6 +21,7 @@ from nuspace.shapes.space import Space
21
21
  from nuspace.utils import MintId, Require, anchor_of, place
22
22
  from nuspace.utils.checks import (
23
23
  anchored,
24
+ id_spelled,
24
25
  key_free,
25
26
  key_spelled,
26
27
  live,
@@ -91,7 +92,7 @@ def add(
91
92
  Args:
92
93
  plane: The plane it goes on.
93
94
  prog: Its source.
94
- id: Its id. Minted when the op evaluates when absent.
95
+ id: Its id, key chars. Minted when the op evaluates when absent.
95
96
  before: A cell of the plane to place it before.
96
97
  after: A cell of the plane to place it after. Neither: the end.
97
98
  name: Display text only.
@@ -109,8 +110,8 @@ def add(
109
110
  Exists: The id or the key is taken.
110
111
  Missing: No plane or anchor with that id.
111
112
  Removed: The plane or the anchor is removed.
112
- Refused: An anchor on another plane, a key that is not key chars
113
- (a literal at build).
113
+ Refused: An anchor on another plane, an id or a key that is not
114
+ key chars (a literal at build).
114
115
  TypeError: Both ``before`` and ``after``, at build.
115
116
  """
116
117
  anchor = anchor_of(before, after)
@@ -136,7 +137,8 @@ def add(
136
137
  )
137
138
 
138
139
  writes = (
139
- refuse(rows.contains(cid), Exists, "cell ", cid, " exists")
140
+ id_spelled(cid, "cell")
141
+ >> refuse(rows.contains(cid), Exists, "cell ", cid, " exists")
140
142
  >> live(Space.planes, plane, "plane")
141
143
  >> anchored(listed, rows, anchor, cid, "cell", text("plane ", plane))
142
144
  >> spelled
@@ -25,6 +25,7 @@ from nuspace.shapes.space import Space
25
25
  from nuspace.utils import MintId, Require, anchor_of, place
26
26
  from nuspace.utils.checks import (
27
27
  anchored,
28
+ id_spelled,
28
29
  key_free,
29
30
  key_spelled,
30
31
  live,
@@ -39,6 +40,7 @@ from nuspace.utils.tree import no_cycle, subtree
39
40
  __all__ = [
40
41
  "add",
41
42
  "exists",
43
+ "keyed",
42
44
  "move",
43
45
  "on_change",
44
46
  "on_children_change",
@@ -74,23 +76,34 @@ def _icon_spelled(icon: nu.StrArg) -> None:
74
76
  raise Refused(msg)
75
77
 
76
78
 
77
- def _reserved(id: nu.StrArg, system: nu.BoolArg) -> nu.Nu:
78
- """Refuse an id no new plane may take: root's, ``""``, or a :data:`RESERVED` one for a user plane.
79
+ def _reserved(id: nu.StrArg) -> nu.Nu:
80
+ """Refuse an id no plane may take: root's or ``""``.
79
81
 
80
82
  Raises:
81
83
  Refused: A literal at build, a term at run time.
82
84
  """
83
85
  if isinstance(id, str):
84
- user = system is False
85
- if id in (ROOT, "") or (user and id in RESERVED):
86
+ if id in (ROOT, ""):
86
87
  msg = f"plane id {id!r} is reserved"
87
88
  raise Refused(msg)
88
- if system is True or id not in RESERVED:
89
- return nu.Noop()
89
+ return nu.Noop()
90
90
  pid = nu.Str(id)
91
- always = (pid == ROOT).or_(pid == "")
92
- taken = nu.List.of(*sorted(RESERVED)).contains(pid).and_(nu.Bool(system).not_())
93
- return refuse(always.or_(taken), Refused, "plane id ", id, " is reserved")
91
+ return refuse((pid == ROOT).or_(pid == ""), Refused, "plane id ", id, " is reserved")
92
+
93
+
94
+ def _key_reserved(key: nu.StrArg, system: nu.BoolArg) -> nu.Nu:
95
+ """Refuse a :data:`RESERVED` key for a plane that is not system.
96
+
97
+ Raises:
98
+ Refused: A literal at build, a term at run time.
99
+ """
100
+ if isinstance(key, str) and isinstance(system, bool):
101
+ if key in RESERVED and not system:
102
+ msg = f"plane key {key!r} is reserved"
103
+ raise Refused(msg)
104
+ return nu.Noop()
105
+ taken = nu.List.of(*sorted(RESERVED)).contains(key).and_(nu.Bool(system).not_())
106
+ return refuse(taken, Refused, "plane key ", key, " is reserved")
94
107
 
95
108
 
96
109
  def _not_root(id: nu.StrArg, doing: str) -> nu.Nu:
@@ -134,7 +147,7 @@ def add(
134
147
  when given. Cost: O(1), about 20 keys.
135
148
 
136
149
  Args:
137
- id: Its id. Minted when the op evaluates when absent.
150
+ id: Its id, key chars. Minted when the op evaluates when absent.
138
151
  parent: The plane it hangs under.
139
152
  before: A sibling to place it before.
140
153
  after: A sibling to place it after. Neither: the end.
@@ -143,8 +156,8 @@ def add(
143
156
  case-sensitive, ``""`` for none.
144
157
  icon: ``lucide:<name>`` or ``emoji:<char>``. ``""``: :data:`DEFAULT_ICON`.
145
158
  backend: What its runs execute on. ``""``: it does not run.
146
- system: Protected: ``remove`` refuses it. Only a system plane takes
147
- a :data:`~nuspace.shapes.plane.RESERVED` id.
159
+ system: Protected: ``remove`` refuses it. Only a system plane holds
160
+ a :data:`~nuspace.shapes.plane.RESERVED` key.
148
161
  spec: The spec it was made from, ``""`` for none.
149
162
  meta: The free dict, for programs. nuspace never reads it.
150
163
  into: A ref set to the id in the same commit, for a minted one.
@@ -153,10 +166,10 @@ def add(
153
166
  Exists: The id or the key is taken.
154
167
  Missing: No parent or anchor with that id.
155
168
  Removed: The parent or the anchor is removed.
156
- Refused: A reserved id (root's, ``""``, or one of
157
- :data:`~nuspace.shapes.plane.RESERVED` unless ``system``), an
158
- anchor that is not a sibling, a key that is not key chars, an
159
- icon spelled wrong. A literal at build.
169
+ Refused: A reserved id (root's or ``""``), a
170
+ :data:`~nuspace.shapes.plane.RESERVED` key unless ``system``, an
171
+ id or a key that is not key chars, an anchor that is not a
172
+ sibling, an icon spelled wrong. A literal at build.
160
173
  TypeError: Both ``before`` and ``after``, at build.
161
174
  """
162
175
  anchor = anchor_of(before, after)
@@ -186,11 +199,13 @@ def add(
186
199
  )
187
200
 
188
201
  writes = (
189
- _reserved(pid, system)
202
+ _reserved(pid)
203
+ >> id_spelled(pid, "plane")
190
204
  >> refuse(rows.contains(pid), Exists, "plane ", pid, " exists")
191
205
  >> live(rows, parent, "plane")
192
206
  >> anchored(siblings, rows, anchor, pid, "plane", text("plane ", parent))
193
207
  >> spelled
208
+ >> _key_reserved(key, system)
194
209
  >> key_free(Space.indexes.plane_keys, key, None, "plane")
195
210
  >> nu.let(nustd.time.time(), made)
196
211
  >> place(siblings, pid, before=before, after=after)
@@ -308,7 +323,9 @@ def set_key(id: nu.StrArg, key: nu.StrArg) -> nu.Nu:
308
323
  Missing: No plane with that id.
309
324
  Removed: It is removed.
310
325
  Exists: Another plane holds the key.
311
- Refused: An empty key, or one not in key chars. A literal at build.
326
+ Refused: An empty key, or one not in key chars, a literal at build.
327
+ A :data:`~nuspace.shapes.plane.RESERVED` key on a plane that is
328
+ not system.
312
329
  """
313
330
  spelled = key_spelled(key, "plane", empty_ok=False)
314
331
  index = Space.indexes.plane_keys
@@ -317,6 +334,7 @@ def set_key(id: nu.StrArg, key: nu.StrArg) -> nu.Nu:
317
334
  return (
318
335
  spelled
319
336
  >> live(Space.planes, id, "plane")
337
+ >> _key_reserved(key, row.system.fallback(False))
320
338
  >> key_free(index, key, id, "plane")
321
339
  >> nu.IfDo((old != "").and_(old != key), index.del_item(old))
322
340
  >> index.set_item(key, id)
@@ -391,6 +409,11 @@ def exists(id: nu.StrArg, *, removed: bool = False) -> nu.Bool:
391
409
  return there if removed else there.and_(Space.planes[id].removed_at.exists().not_())
392
410
 
393
411
 
412
+ def keyed(key: nu.StrArg) -> nu.Str:
413
+ """The id of the live plane holding ``key``, ``""`` when none does. A read. Cost: O(1), 1 point read."""
414
+ return nu.Str(Space.indexes.plane_keys.get_item(key, ""))
415
+
416
+
394
417
  def title(id: nu.StrArg) -> nu.Str:
395
418
  """What a list or a header shows for a plane: its name, else its id. Cost: O(1), 2 reads.
396
419
 
@@ -14,12 +14,12 @@ __all__ = ["RESERVED", "ROOT", "Plane"]
14
14
  #: remove), has no backend (no run) and no parent of its own (``""``).
15
15
  ROOT = "root"
16
16
 
17
- #: Plane ids only nuspace's own planes take: ``plane.add`` refuses them
18
- #: unless ``system=True``. The system planes' (``nuspace.system``), then the
19
- #: web device's (``nuspace.web.planes``): the searches folder, the
20
- #: home indexer and the desk pruner. Home, search and settings have minted
21
- #: ids, found through the web device's roles. Root's id and ``""`` are
22
- #: refused for every add.
17
+ #: Plane keys only nuspace's own planes hold: ``plane.add`` and ``plane.set_key``
18
+ #: refuse them unless the plane is system. The system planes'
19
+ #: (``nuspace.system``), then the web device's (``nuspace.web.planes``): the
20
+ #: searches folder, the home indexer and the desk pruner. Their ids are
21
+ #: minted like any plane's; code finds them by key. Home, search and
22
+ #: settings are found through the web device's roles.
23
23
  RESERVED = frozenset(
24
24
  {
25
25
  "init",
@@ -129,8 +129,7 @@ def _web_row(pid: nu.Str, spec: PlaneSpec) -> nu.Nu:
129
129
  return web.add(
130
130
  pid,
131
131
  editable=flags.editable, # type: ignore[union-attr]
132
- full_width=flags.full_width, # type: ignore[union-attr]
133
- compact=flags.compact, # type: ignore[union-attr]
132
+ width=flags.width, # type: ignore[union-attr]
134
133
  )
135
134
 
136
135
 
@@ -16,7 +16,7 @@ spec says how a space presents it.
16
16
  from __future__ import annotations
17
17
 
18
18
  from dataclasses import dataclass, field
19
- from typing import TYPE_CHECKING, Any
19
+ from typing import TYPE_CHECKING, Any, Literal
20
20
 
21
21
  import nu
22
22
  import nustd.kv
@@ -38,13 +38,13 @@ class WebOptions:
38
38
 
39
39
  Args:
40
40
  editable: Its cells can be edited in the browser.
41
- full_width: It takes the whole width of a pane.
42
- compact: It draws its cells tight.
41
+ width: How wide its content runs in a pane, at most: ``page`` (the
42
+ reading measure), ``board`` (a dashboard's) or ``full`` (the
43
+ whole pane).
43
44
  """
44
45
 
45
46
  editable: bool = True
46
- full_width: bool = False
47
- compact: bool = False
47
+ width: Literal["page", "board", "full"] = "page"
48
48
 
49
49
 
50
50
  class SpecCells(nu.Shape):
@@ -123,7 +123,7 @@ EMPTY_PLANE = PlaneSpec(
123
123
  "",
124
124
  "Empty",
125
125
  description="An empty plane. Add cells with /, or nest planes under it.",
126
- web=WebOptions(editable=True, full_width=False),
126
+ web=WebOptions(editable=True),
127
127
  backend="async",
128
128
  )
129
129
 
@@ -0,0 +1,12 @@
1
+ """``nuspace.system.init``: the system plane that starts planes when the space opens.
2
+
3
+ - ops: :func:`boot`, :func:`unboot`, over the boot list (:class:`Boot`).
4
+ - :mod:`.plane`: the plane's cell and its driver.
5
+ """
6
+
7
+ from .ops import boot, unboot
8
+ from .plane import BOOTED, BY, driver
9
+ from .shapes import KEY, Boot, boot_list
10
+
11
+
12
+ __all__ = ["BOOTED", "BY", "KEY", "Boot", "boot", "boot_list", "driver", "unboot"]
@@ -1,6 +1,6 @@
1
1
  """The init plane: starts the planes on its boot list when the space opens.
2
2
 
3
- One cell, :data:`CELL`, runs :func:`driver`. The host asks for init's own
3
+ One cell, ``main``, runs :func:`driver`. The host asks for init's own
4
4
  plane run at open (``nuspace.system.start``); init asks for the rest. A
5
5
  plane on the boot list (:class:`~.shapes.Boot`) gets one plane run ``by``
6
6
  init in every open, unless it already ran in this open:
@@ -35,22 +35,20 @@ from nuspace.utils import keys_of
35
35
  from nuspace.utils.policies import report, restarting
36
36
 
37
37
  from .. import supervisor
38
- from .shapes import PLANE, boot_list
38
+ from .shapes import KEY, boot_list
39
39
 
40
40
 
41
- __all__ = ["BACKEND", "BOOTED", "BY", "CELL", "PROG", "driver"]
41
+ __all__ = ["BACKEND", "BOOTED", "BY", "PROG", "driver"]
42
42
 
43
43
 
44
- #: Its one cell's id, fixed.
45
- CELL = "init_main"
46
-
47
44
  #: What its runs execute on.
48
45
  BACKEND = "mp"
49
46
 
50
47
  #: What the plane runs init asks for are recorded as ``by``.
51
48
  BY = "init"
52
49
 
53
- #: What the boot list starts as, the first time the system planes are made.
50
+ #: What the boot list starts as, the first time the system planes are made: plane keys,
51
+ #: written as the ids they find.
54
52
  BOOTED = ("supervisor", "reload", "reactions")
55
53
 
56
54
  #: The cell's prog: the driver lives here, the store holds this.
@@ -69,7 +67,7 @@ def _supervised(plane: nu.Str) -> nu.Bool:
69
67
  The supervisor plane itself is never skipped: init is what brings it up.
70
68
  """
71
69
  policy = supervisor.supervised()[plane].policy
72
- return policy.exists().and_(plane != supervisor.PLANE)
70
+ return policy.exists().and_(plane != ops.plane.keyed(supervisor.KEY))
73
71
 
74
72
 
75
73
  def _due(plane: nu.Str) -> nu.Bool:
@@ -93,7 +91,7 @@ def _due(plane: nu.Str) -> nu.Bool:
93
91
  def _up(plane: nu.Str) -> nu.Nu:
94
92
  """One listed plane run if it is due, check and run in one commit. A raise is reported."""
95
93
  up = atomic(nu.IfDo(_due(plane), ops.plane_run.add(plane, by=BY)))
96
- return nu.TryCatch(up, catch=lambda err: report(PLANE, plane, err))
94
+ return nu.TryCatch(up, catch=lambda err: report(KEY, plane, err))
97
95
 
98
96
 
99
97
  def driver() -> nu.Nu:
@@ -103,7 +101,7 @@ def driver() -> nu.Nu:
103
101
  raise outside a plane's step (reading the list) is reported and the
104
102
  driver runs again (``restarting``).
105
103
  """
106
- planes = boot_list()
104
+ planes = boot_list(here=True)
107
105
  listed = keys_of(planes)
108
106
  one_pass = nu.ForEachDo(atomic(listed), lambda p: _up(nu.Str(p)))
109
107
  flow = nu.Reconcile(
@@ -112,4 +110,4 @@ def driver() -> nu.Nu:
112
110
  after=nu.RECHECK_AFTER,
113
111
  every=nu.RECHECK_EVERY,
114
112
  )
115
- return restarting(flow, PLANE)
113
+ return restarting(flow, KEY)
@@ -0,0 +1,33 @@
1
+ """init's own state: the plane's key and the boot list, kept in the init plane's state."""
2
+
3
+ from __future__ import annotations
4
+
5
+ import nu
6
+ import nustd.kv
7
+ from nuspace.places import at
8
+
9
+
10
+ __all__ = ["KEY", "Boot", "boot_list"]
11
+
12
+
13
+ #: The plane's key. Its id is minted when it is made; code finds it by this.
14
+ KEY = "init"
15
+
16
+
17
+ class Boot(nu.Shape):
18
+ """init's state, in the init plane's state.
19
+
20
+ ``planes`` is the boot list: the ids of the planes init runs at every
21
+ open, a set (dict of bool, point add and delete). Reach it through the
22
+ plane's place: ``nuspace.at.plane(init.KEY).state(Boot).planes``.
23
+ """
24
+
25
+ planes = nustd.kv.DictRef.slot(bool)
26
+
27
+
28
+ def boot_list(*, here: bool = False) -> nu.Nu:
29
+ """The boot list's ref, found by key: one index read. ``here``: the running plane's, no read.
30
+
31
+ ``here`` is for init's own cell; every other caller finds the plane by key.
32
+ """
33
+ return (at.plane if here else at.plane(KEY)).state(Boot).planes
@@ -0,0 +1,94 @@
1
+ """The system planes as a set: made where missing, and init started. What the host runs at open.
2
+
3
+ Each system plane has a minted id like any plane, a key code finds it by
4
+ (``nuspace.at.plane(KEY)``, ``ops.plane.keyed(KEY)``), a title, is
5
+ ``system`` (remove refuses it, only it holds a reserved key) and holds
6
+ cells whose progs are short shims: the code lives in
7
+ ``nuspace.system.<plane>``, the store holds the shim. init's boot list
8
+ starts as the other three, so init brings them up:
9
+
10
+ host ensure() >> start()
11
+ init boots supervisor, reload, reactions
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import nu
17
+ from nuspace import ops
18
+ from nuspace.ops.brackets import atomic
19
+ from nuspace.utils import MintId
20
+
21
+ from . import init, reactions, reload, supervisor
22
+ from .init.plane import BACKEND as INIT_BACKEND
23
+ from .init.plane import PROG as INIT_PROG
24
+ from .reactions.plane import BACKEND as REACTIONS_BACKEND
25
+ from .reactions.plane import PARK_PROG
26
+ from .reload.plane import BACKEND as RELOAD_BACKEND
27
+ from .reload.plane import PROG as RELOAD_PROG
28
+ from .supervisor.plane import BACKEND as SUPERVISOR_BACKEND
29
+ from .supervisor.plane import PROG as SUPERVISOR_PROG
30
+
31
+
32
+ __all__ = ["BY", "PLANES", "ensure", "start"]
33
+
34
+
35
+ #: Every system plane: ``(key, title, backend, ((cell name, prog), ...))``. init first.
36
+ PLANES = (
37
+ (init.KEY, "Init", INIT_BACKEND, (("main", INIT_PROG),)),
38
+ (supervisor.KEY, "Supervisor", SUPERVISOR_BACKEND, (("main", SUPERVISOR_PROG),)),
39
+ (reload.KEY, "Reload", RELOAD_BACKEND, (("main", RELOAD_PROG),)),
40
+ (reactions.KEY, "Reactions", REACTIONS_BACKEND, (("park", PARK_PROG),)),
41
+ )
42
+
43
+ #: What init's own plane run is recorded as ``by`` when :func:`start` asks for it.
44
+ BY = "host"
45
+
46
+
47
+ def _made(key: str, title: str, backend: str, cells: tuple[tuple[str, str], ...]) -> nu.Nu:
48
+ """One system plane and its cells, minted ids, when no plane holds its key. One commit."""
49
+
50
+ def made(minted: nu.ObjectRef) -> nu.Nu:
51
+ plane = nu.Str(minted)
52
+ adds = ops.plane.add(plane, key=key, name=title, backend=backend, system=True)
53
+ for name, prog in cells:
54
+ adds = adds >> ops.cell.add(plane, prog, name=name, fabrics=[])
55
+ return adds
56
+
57
+ return atomic(nu.IfDo(ops.plane.keyed(key) == "", nu.let(MintId("p"), made)))
58
+
59
+
60
+ def ensure() -> nu.Nu:
61
+ """Every system plane and its cells, then their state's containers, made where missing.
62
+
63
+ Idempotent: a plane is made only when no plane holds its key, and what
64
+ is there is left exactly as it is, init's boot list included (it is data
65
+ somebody may have edited). The boot list starts as the ids of
66
+ :data:`~nuspace.system.init.BOOTED`'s planes the first time. One commit
67
+ per plane, then one State commit.
68
+ """
69
+ made = nu.Sequential(*(_made(*spec) for spec in PLANES))
70
+ booted = init.boot_list()
71
+ first = nu.Sequential(
72
+ booted.init({}),
73
+ *(booted.set_item(ops.plane.keyed(key), True) for key in init.BOOTED),
74
+ )
75
+ state = (
76
+ nu.IfDo(booted.exists().not_(), first)
77
+ >> supervisor.supervised().init({})
78
+ >> reactions.registry().by_plane.init({})
79
+ )
80
+ return made >> atomic(state)
81
+
82
+
83
+ def start(*, by: nu.StrArg = BY) -> nu.Nu:
84
+ """A plane run of init asked for, unless it has a live one. One commit. The kernel runs it.
85
+
86
+ Args:
87
+ by: Who asks, what the run is recorded as.
88
+
89
+ Raises:
90
+ Missing: No init plane: :func:`ensure` first.
91
+ """
92
+ plane = ops.plane.keyed(init.KEY)
93
+ idle = ops.plane_run.any_live(plane).not_()
94
+ return atomic(nu.IfDo(idle, ops.plane_run.add(plane, by=by)))
@@ -1,18 +1,17 @@
1
1
  """``nuspace.system.reactions``: the system plane that runs a plane when something changes.
2
2
 
3
3
  - ops: :func:`add`, :func:`remove`, :func:`clear`; :func:`source`, a reaction's prog.
4
- - :mod:`.plane`: the plane's ids, its park cell, :func:`react` (a reaction's program) and :func:`up_plane`.
4
+ - :mod:`.plane`: its park cell, :func:`react` (a reaction's program) and :func:`up_plane`.
5
5
  - shapes: :class:`Registry` (by plane), :class:`Reaction` (a reaction's plane and run).
6
6
  """
7
7
 
8
8
  from .ops import add, clear, remove, source
9
- from .plane import PARK, react, up_plane
10
- from .shapes import PLANE, Reaction, ReactionIds, Registry, registry
9
+ from .plane import react, up_plane
10
+ from .shapes import KEY, Reaction, ReactionIds, Registry, registry
11
11
 
12
12
 
13
13
  __all__ = [
14
- "PARK",
15
- "PLANE",
14
+ "KEY",
16
15
  "Reaction",
17
16
  "ReactionIds",
18
17
  "Registry",
@@ -26,7 +26,7 @@ from nuspace.shapes import Space
26
26
  from nuspace.utils import MintId, keys_of
27
27
  from nuspace.utils.checks import live, refuse
28
28
 
29
- from .shapes import PLANE, Reaction, registry
29
+ from .shapes import KEY, Reaction, registry
30
30
 
31
31
 
32
32
  if TYPE_CHECKING:
@@ -127,7 +127,7 @@ def add(
127
127
  >> at.cell(id=reaction).state(Reaction).plane.set(plane)
128
128
  >> registry().by_plane[plane].ids.set_item(reaction, True)
129
129
  )
130
- cell = ops.cell.add(PLANE, src, id=reaction, name=plane, fabrics=[])
130
+ cell = ops.cell.add(ops.plane.keyed(KEY), src, id=reaction, name=plane, fabrics=[])
131
131
  steps = atomic(kept) >> atomic(cell)
132
132
  if into is not None:
133
133
  steps = steps >> atomic(into.set(reaction))
@@ -141,7 +141,7 @@ def add(
141
141
  def _known(reaction: nu.StrArg) -> nu.Bool:
142
142
  """Whether ``reaction`` is a live reaction: a cell on the reactions plane, not removed, with its plane kept."""
143
143
  row = Space.cells[reaction]
144
- on = row.plane.fallback("") == PLANE
144
+ on = row.plane.fallback("") == ops.plane.keyed(KEY)
145
145
  kept = at.cell(id=reaction).state(Reaction).plane.exists()
146
146
  return on.and_(row.removed_at.exists().not_()).and_(kept)
147
147
 
@@ -1,7 +1,7 @@
1
1
  """The reactions plane: runs a plane when something changes. No engine of its own.
2
2
 
3
3
  The plane runs its cells like any plane, on the ``async`` backend, and
4
- init boots it. Its first cell, :data:`PARK`, runs ``nu.Park()``: it never
4
+ init boots it. Its first cell, ``park``, runs ``nu.Park()``: it never
5
5
  ends, so the plane run stays live with no reactions at all, and reload
6
6
  gives each new reaction cell its first run in it. Every other cell is a
7
7
  reaction (see :mod:`.ops`), whose program is :func:`react`::
@@ -30,15 +30,12 @@ from nuspace.places import at
30
30
  from nuspace.shapes import Space
31
31
  from nuspace.utils.policies import restarting
32
32
 
33
- from .shapes import PLANE, Reaction
33
+ from .shapes import KEY, Reaction
34
34
 
35
35
 
36
- __all__ = ["BACKEND", "PARK", "PARK_PROG", "react", "up_plane"]
36
+ __all__ = ["BACKEND", "PARK_PROG", "react", "up_plane"]
37
37
 
38
38
 
39
- #: Its fixed first cell, the one that keeps its run live.
40
- PARK = "reactions_park"
41
-
42
39
  #: What its runs execute on.
43
40
  BACKEND = "async"
44
41
 
@@ -99,4 +96,4 @@ def react(change: nu.Nu, plane: nu.StrArg, reaction: nu.StrArg) -> nu.Nu:
99
96
  reaction: The reaction's id.
100
97
  """
101
98
  flow = nu.ReactEvery(atomic(change), up_plane(plane, reaction))
102
- return restarting(flow, PLANE, reaction)
99
+ return restarting(flow, KEY, reaction)