nuspace 0.7.1__tar.gz → 0.7.2__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.1 → nuspace-0.7.2}/CHANGELOG.md +14 -2
  2. {nuspace-0.7.1 → nuspace-0.7.2}/PKG-INFO +6 -6
  3. {nuspace-0.7.1 → nuspace-0.7.2}/pyproject.toml +6 -6
  4. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/__init__.py +1 -1
  5. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/host/devices.py +3 -3
  6. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/host/layout.py +1 -1
  7. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/host/resources.py +0 -3
  8. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/host/stores.py +1 -1
  9. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/ops/plane.py +5 -2
  10. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/__init__.py +1 -1
  11. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/content.py +2 -2
  12. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/plane.py +1 -1
  13. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/store.py +1 -1
  14. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/specs/ops.py +2 -2
  15. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/utils/placement.py +8 -6
  16. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/__init__.py +16 -20
  17. nuspace-0.7.2/src/nuspace/web/desks/__init__.py +58 -0
  18. nuspace-0.7.2/src/nuspace/web/desks/feed.py +152 -0
  19. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/desks/interactions.py +18 -7
  20. nuspace-0.7.2/src/nuspace/web/desks/model.py +542 -0
  21. nuspace-0.7.2/src/nuspace/web/desks/ops.py +418 -0
  22. {nuspace-0.7.1/src/nuspace/devices/web → nuspace-0.7.2/src/nuspace/web/desks}/pruner.py +28 -28
  23. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/desks/ref.py +2 -2
  24. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/device.py +7 -6
  25. nuspace-0.7.2/src/nuspace/web/home/__init__.py +56 -0
  26. nuspace-0.7.2/src/nuspace/web/home/common.py +103 -0
  27. nuspace-0.7.2/src/nuspace/web/home/history.py +141 -0
  28. {nuspace-0.7.1/src/nuspace/devices/web → nuspace-0.7.2/src/nuspace/web/home}/indexer.py +2 -2
  29. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/home/now.py +129 -136
  30. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/home/runs.py +42 -70
  31. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/home/space.py +58 -90
  32. nuspace-0.7.2/src/nuspace/web/interactions.py +261 -0
  33. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/ops.py +26 -128
  34. nuspace-0.7.1/src/nuspace/devices/web/planes.py → nuspace-0.7.2/src/nuspace/web/planes/__init__.py +20 -28
  35. {nuspace-0.7.1/src/nuspace/devices/web → nuspace-0.7.2/src/nuspace/web/planes}/search.py +189 -137
  36. nuspace-0.7.2/src/nuspace/web/planes/settings.py +211 -0
  37. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/ref.py +3 -3
  38. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/shapes.py +25 -12
  39. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/shell.py +4 -4
  40. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/sidebar/__init__.py +3 -3
  41. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/sidebar/feed.py +6 -6
  42. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/sidebar/interactions.py +1 -1
  43. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/sidebar/ref.py +2 -2
  44. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/stores.py +1 -2
  45. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/utils.py +7 -1
  46. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/viewer/__init__.py +3 -3
  47. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/viewer/feed.py +12 -12
  48. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/viewer/interactions.py +1 -1
  49. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/viewer/ref.py +2 -2
  50. {nuspace-0.7.1 → nuspace-0.7.2}/tests/conftest.py +1 -1
  51. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_cli.py +1 -1
  52. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_host.py +3 -3
  53. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_nav.py +3 -3
  54. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_session_pipe.py +1 -1
  55. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_shapes.py +8 -7
  56. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_specs.py +1 -1
  57. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_system_utils.py +1 -1
  58. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_telemetry.py +1 -1
  59. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_web.py +18 -17
  60. nuspace-0.7.2/tests/nuspace/test_web_desks.py +711 -0
  61. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_web_home.py +202 -134
  62. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_web_indexer.py +4 -3
  63. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_web_ops.py +22 -2
  64. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_web_planes.py +76 -33
  65. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_web_pruner.py +63 -35
  66. nuspace-0.7.1/src/nuspace/devices/__init__.py +0 -10
  67. nuspace-0.7.1/src/nuspace/devices/web/desks/__init__.py +0 -39
  68. nuspace-0.7.1/src/nuspace/devices/web/desks/feed.py +0 -132
  69. nuspace-0.7.1/src/nuspace/devices/web/home/__init__.py +0 -106
  70. nuspace-0.7.1/src/nuspace/devices/web/home/common.py +0 -72
  71. nuspace-0.7.1/src/nuspace/devices/web/home/history.py +0 -153
  72. nuspace-0.7.1/src/nuspace/devices/web/interactions.py +0 -630
  73. nuspace-0.7.1/src/nuspace/devices/web/settings.py +0 -27
  74. nuspace-0.7.1/tests/nuspace/test_web_desks.py +0 -543
  75. {nuspace-0.7.1 → nuspace-0.7.2}/.gitignore +0 -0
  76. {nuspace-0.7.1 → nuspace-0.7.2}/.pre-commit-config.yaml +0 -0
  77. {nuspace-0.7.1 → nuspace-0.7.2}/LICENSE.md +0 -0
  78. {nuspace-0.7.1 → nuspace-0.7.2}/Makefile +0 -0
  79. {nuspace-0.7.1 → nuspace-0.7.2}/README.md +0 -0
  80. {nuspace-0.7.1 → nuspace-0.7.2}/examples/space.py +0 -0
  81. {nuspace-0.7.1 → nuspace-0.7.2}/pkgs/ts/nuspace-ui/python/nuspace_ui/build +0 -0
  82. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_branding/__init__.py +0 -0
  83. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_branding/header.py +0 -0
  84. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_branding/logo.py +0 -0
  85. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_branding/palette.py +0 -0
  86. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/__init__.py +0 -0
  87. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/__main__.py +0 -0
  88. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/_installed.py +0 -0
  89. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/_meta.py +0 -0
  90. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/commands/__init__.py +0 -0
  91. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/commands/init.py +0 -0
  92. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/commands/run.py +0 -0
  93. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/commands/telemetry.py +0 -0
  94. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/commands/verse.py +0 -0
  95. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_cli/main.py +0 -0
  96. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_telemetry/__init__.py +0 -0
  97. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_telemetry/config.py +0 -0
  98. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_telemetry/send.py +0 -0
  99. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/_telemetry/usage.py +0 -0
  100. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/errors.py +0 -0
  101. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/host/__init__.py +0 -0
  102. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/host/interactions.py +0 -0
  103. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/host/lock.py +0 -0
  104. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/host/presets.py +0 -0
  105. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/host/space.py +0 -0
  106. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/__init__.py +0 -0
  107. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/backends/__init__.py +0 -0
  108. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/backends/async_.py +0 -0
  109. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/backends/base.py +0 -0
  110. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/backends/interactions.py +0 -0
  111. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/backends/mp.py +0 -0
  112. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/backends/pool.py +0 -0
  113. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/body.py +0 -0
  114. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/close.py +0 -0
  115. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/envs.py +0 -0
  116. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/interactions.py +0 -0
  117. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/kernel.py +0 -0
  118. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/out.py +0 -0
  119. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/reap.py +0 -0
  120. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/kernel/records.py +0 -0
  121. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/ops/__init__.py +0 -0
  122. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/ops/brackets.py +0 -0
  123. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/ops/cell.py +0 -0
  124. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/ops/cell_run.py +0 -0
  125. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/ops/interactions.py +0 -0
  126. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/ops/plane_run.py +0 -0
  127. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/ops/state.py +0 -0
  128. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/ops/worker.py +0 -0
  129. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/places.py +0 -0
  130. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/cell.py +0 -0
  131. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/cell_run.py +0 -0
  132. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/exec.py +0 -0
  133. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/plane_run.py +0 -0
  134. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/space.py +0 -0
  135. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/state.py +0 -0
  136. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/shapes/worker.py +0 -0
  137. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/specs/__init__.py +0 -0
  138. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/specs/registry.py +0 -0
  139. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/specs/specs.py +0 -0
  140. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/__init__.py +0 -0
  141. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/init/__init__.py +0 -0
  142. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/init/ops.py +0 -0
  143. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/init/plane.py +0 -0
  144. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/init/shapes.py +0 -0
  145. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/planes.py +0 -0
  146. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/reactions/__init__.py +0 -0
  147. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/reactions/ops.py +0 -0
  148. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/reactions/plane.py +0 -0
  149. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/reactions/shapes.py +0 -0
  150. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/reload/__init__.py +0 -0
  151. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/reload/plane.py +0 -0
  152. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/supervisor/__init__.py +0 -0
  153. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/supervisor/ops.py +0 -0
  154. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/supervisor/plane.py +0 -0
  155. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/system/supervisor/shapes.py +0 -0
  156. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/utils/__init__.py +0 -0
  157. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/utils/checks.py +0 -0
  158. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/utils/interactions.py +0 -0
  159. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/utils/policies.py +0 -0
  160. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/utils/reads.py +0 -0
  161. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/utils/tree.py +0 -0
  162. {nuspace-0.7.1 → nuspace-0.7.2}/src/nuspace/utils/typed.py +0 -0
  163. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/env.py +0 -0
  164. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/nav.py +0 -0
  165. {nuspace-0.7.1/src/nuspace/devices → nuspace-0.7.2/src/nuspace}/web/session.py +0 -0
  166. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_ext/nuspace_testing/__init__.py +0 -0
  167. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_ext/pyproject.toml +0 -0
  168. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_lib/_support/__init__.py +0 -0
  169. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_lib/_support/actions.py +0 -0
  170. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_lib/_support/contents.py +0 -0
  171. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_lib/_support/crash_host.py +0 -0
  172. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_lib/_support/kernel.py +0 -0
  173. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_lib/_support/kernel_envs.py +0 -0
  174. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_lib/_support/made.py +0 -0
  175. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_lib/_support/probe.py +0 -0
  176. {nuspace-0.7.1 → nuspace-0.7.2}/tests/_lib/_support/specs.py +0 -0
  177. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_brackets.py +0 -0
  178. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_content.py +0 -0
  179. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_errors.py +0 -0
  180. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_exports.py +0 -0
  181. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_kernel.py +0 -0
  182. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_ops_cell.py +0 -0
  183. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_ops_cell_run.py +0 -0
  184. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_ops_plane.py +0 -0
  185. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_ops_plane_run.py +0 -0
  186. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_ops_state.py +0 -0
  187. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_ops_worker.py +0 -0
  188. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_packaging.py +0 -0
  189. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_places.py +0 -0
  190. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_system_ops.py +0 -0
  191. {nuspace-0.7.1 → nuspace-0.7.2}/tests/nuspace/test_system_planes.py +0 -0
@@ -2,13 +2,25 @@
2
2
 
3
3
  ## Packages shipped from this repo
4
4
 
5
- - **nuspace** (the runtime, the `nuspace` command) — 0.7.1
6
- - **nuspace-ui** (the compiled web bundle) — 0.7.1
5
+ - **nuspace** (the runtime, the `nuspace` command) — 0.7.2
6
+ - **nuspace-ui** (the compiled web bundle) — 0.7.2
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.2 - 2026-11-13
13
+
14
+ - Release nuspace and nuspace-ui 0.7.2
15
+ - Home, settings and search follow the new layout model; plane links share one helper
16
+ - New planes get a default icon when created without one
17
+ - Home, settings and search planes get clean sectioned layouts with real tables
18
+ - Drops show the region they make, and single planes stack anywhere
19
+ - Sidebar states use the kit hover, press and selected washes in both themes
20
+ - Desks keep each item on its own, programs get item ops, cross-desk drags commit once
21
+ - The web device moves to nuspace.web, the devices package is gone
22
+ - Group the web device by area: desk ops under desks, shipped planes together
23
+
12
24
  ## 0.7.1 - 2026-11-12
13
25
 
14
26
  - Release nuspace and nuspace-ui 0.7.1
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nuspace
3
- Version: 0.7.1
3
+ Version: 0.7.2
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.3
21
- Requires-Dist: nuspace-ui>=0.7.1
22
- Requires-Dist: nustd[kv,mp-pool,proxy,ui,valkey]>=0.7.3
23
- Requires-Dist: nuverse>=0.3.9
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
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.3; extra == 'all'
29
+ Requires-Dist: nustd[all]>=0.7.4; 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.1"
7
+ version = "0.7.2"
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.3",
18
- "nustd[kv,ui,valkey,mp_pool,proxy]>=0.7.3",
17
+ "nucore>=0.7.4",
18
+ "nustd[kv,ui,valkey,mp_pool,proxy]>=0.7.4",
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.1",
24
+ "nuspace-ui>=0.7.2",
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.9",
27
+ "nuverse>=0.3.10",
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.3",
58
+ "nustd[all]>=0.7.4",
59
59
  ]
60
60
 
61
61
  [project.urls]
@@ -15,7 +15,7 @@ The top level holds the everyday names only:
15
15
  - ``nuspace.specs`` (PlaneSpec, CellSpec, the registry, add_plane,
16
16
  add_cell) and ``nuspace.errors`` (what an op raises).
17
17
 
18
- ``nuspace.kernel``, ``nuspace.system.*``, ``nuspace.devices.web``,
18
+ ``nuspace.kernel``, ``nuspace.system.*``, ``nuspace.web``,
19
19
  ``nuspace.host`` and ``nuspace.utils`` stay namespaced.
20
20
 
21
21
  Workers import this package: nothing here may pull in a server.
@@ -56,11 +56,11 @@ class Device:
56
56
 
57
57
  def _web() -> Device:
58
58
  """The web device. Its stores and planes import no server; :func:`serve` does, when called."""
59
- from nuspace.devices.web.planes import ensure
60
- from nuspace.devices.web.stores import CONN_STORE, WEB_STORE
59
+ from nuspace.web.planes import ensure
60
+ from nuspace.web.stores import CONN_STORE, WEB_STORE
61
61
 
62
62
  def serve(*, registry: Registry, **options: object) -> tuple[nu.Nu, Mapping[str, EnvFactory]]:
63
- from nuspace.devices.web.device import serve_web
63
+ from nuspace.web.device import serve_web
64
64
 
65
65
  return serve_web(registry=registry, **options)
66
66
 
@@ -13,7 +13,7 @@ repository keeps ``.git/``::
13
13
  infra/valkey/ the notification server's dir, per open
14
14
 
15
15
  The store files are the store records' (:mod:`.stores`,
16
- :mod:`nuspace.devices.web.stores`). The Space store's file is the mark of a
16
+ :mod:`nuspace.web.stores`). The Space store's file is the mark of a
17
17
  space: ``~/.nuspace`` holds the user's config whether or not home is a space.
18
18
  """
19
19
 
@@ -126,9 +126,6 @@ class Warmed:
126
126
  Never the web device's server.
127
127
  """
128
128
  import nu.prog # noqa: F401 -- imported for the module cache
129
- import nuspace.devices.web.env
130
- import nuspace.kernel.body
131
- import nuspace.kernel.out # noqa: F401
132
129
  import nustd.ui # noqa: F401
133
130
 
134
131
  async def asetup(self, ctx: Context) -> None:
@@ -10,7 +10,7 @@ so stores never wake each other's subscribers. Each is declared as a
10
10
  store/state.db State nuspace.state what programs keep
11
11
 
12
12
  A device keeps its own stores beside these (the web device:
13
- :mod:`nuspace.devices.web.stores`).
13
+ :mod:`nuspace.web.stores`).
14
14
 
15
15
  A container never written is absent, and reading one (its length, its keys,
16
16
  whether it holds an id) raises. So each store's fixed containers are made
@@ -56,6 +56,9 @@ __all__ = [
56
56
  #: The prefixes an icon is spelled with, a lucide name or an emoji after it.
57
57
  ICON_PREFIXES = ("lucide:", "emoji:")
58
58
 
59
+ #: The icon a plane is made with when none is given.
60
+ DEFAULT_ICON = "lucide:file-text"
61
+
59
62
 
60
63
  def _icon_spelled(icon: nu.StrArg) -> None:
61
64
  """A literal icon is ``""`` or a prefix with something after it. A term is stored as it is.
@@ -138,7 +141,7 @@ def add(
138
141
  name: Display text only.
139
142
  key: An optional handle in key chars (letters, digits, ``_``, ``-``),
140
143
  case-sensitive, ``""`` for none.
141
- icon: ``lucide:<name>``, ``emoji:<char>`` or ``""``.
144
+ icon: ``lucide:<name>`` or ``emoji:<char>``. ``""``: :data:`DEFAULT_ICON`.
142
145
  backend: What its runs execute on. ``""``: it does not run.
143
146
  system: Protected: ``remove`` refuses it. Only a system plane takes
144
147
  a :data:`~nuspace.shapes.plane.RESERVED` id.
@@ -169,7 +172,7 @@ def add(
169
172
  return (
170
173
  row.name.set(name)
171
174
  >> row.key.set(key)
172
- >> row.icon.set(icon)
175
+ >> row.icon.set(icon or DEFAULT_ICON if isinstance(icon, str) else icon)
173
176
  >> row.parent.set(parent)
174
177
  >> row.version.set(0)
175
178
  >> row.cells.set(nu.Dict.create())
@@ -28,7 +28,7 @@ for a cell run's output.
28
28
 
29
29
  Each store is a :class:`Store` record (root, file, channel, init), declared
30
30
  by the host for the core stores. The web device keeps its own stores and
31
- shapes, in :mod:`nuspace.devices.web.shapes`. Core never reads them.
31
+ shapes, in :mod:`nuspace.web.shapes`. Core never reads them.
32
32
  """
33
33
 
34
34
  from .cell import Cell
@@ -13,7 +13,7 @@ Finding reads one kind's subtree, scope by scope, all bare reads:
13
13
  the items ``match`` holds for in one scope, :meth:`Content.find` both
14
14
  together. A search reads per kind, so it never touches another kind's
15
15
  items; the web device's search drives it a place at a time
16
- (:mod:`nuspace.devices.web.search`).
16
+ (:mod:`nuspace.web.planes.search`).
17
17
 
18
18
  Workers import this module: nothing here may pull in a server.
19
19
  """
@@ -219,7 +219,7 @@ class Content(Generic[S]):
219
219
  only the checks asked for.
220
220
  - :meth:`render`, :meth:`render_hit`: how an item draws in full and as a
221
221
  search hit. Bare ui chains, landed under wherever the drawing goes
222
- (:class:`~nuspace.devices.web.interactions.Drawing`).
222
+ (:class:`~nuspace.web.interactions.Drawing`).
223
223
  - :meth:`form`: the search form, generated from ``match``'s keywords.
224
224
  - :meth:`find`: every item ``match`` holds for, from every place.
225
225
 
@@ -16,7 +16,7 @@ ROOT = "root"
16
16
 
17
17
  #: Plane ids only nuspace's own planes take: ``plane.add`` refuses them
18
18
  #: unless ``system=True``. The system planes' (``nuspace.system``), then the
19
- #: web device's (``nuspace.devices.web.planes``): the searches folder, the
19
+ #: web device's (``nuspace.web.planes``): the searches folder, the
20
20
  #: home indexer and the desk pruner. Home, search and settings have minted
21
21
  #: ids, found through the web device's roles. Root's id and ``""`` are
22
22
  #: refused for every add.
@@ -1,7 +1,7 @@
1
1
  """Store: one store of a space, as a record: its root shape, its file, its channel, its init.
2
2
 
3
3
  The core stores are declared in :mod:`nuspace.host.stores`, a device's
4
- beside its shapes (the web device: :mod:`nuspace.devices.web.stores`). The
4
+ beside its shapes (the web device: :mod:`nuspace.web.stores`). The
5
5
  record sits here, with the roots, so the host and the devices both read it
6
6
  and neither imports the other for it. A store has no ops of its own: its
7
7
  init term is the one thing it runs, and the store's declaration builds it.
@@ -119,11 +119,11 @@ class _Tree:
119
119
 
120
120
 
121
121
  def _web_row(pid: nu.Str, spec: PlaneSpec) -> nu.Nu:
122
- """The plane's web row, from ``spec.web``: ``nuspace.devices.web.add``, the plane shows on the web.
122
+ """The plane's web row, from ``spec.web``: ``nuspace.web.add``, the plane shows on the web.
123
123
 
124
124
  Imported here, not at the top, so core loads without the web device.
125
125
  """
126
- from nuspace.devices.web import ops as web
126
+ from nuspace.web import ops as web
127
127
 
128
128
  flags = spec.web
129
129
  return web.add(
@@ -1,8 +1,8 @@
1
1
  """Placing an id in an ordered list: before an anchor, after one, or at the end.
2
2
 
3
3
  The one spelling of "placement is ``before=`` / ``after=`` an id, neither
4
- means the end" that ops share. Generic: it works on any ``OrderedDictRef``
5
- of bool. The checks that make an anchor valid (it is there, it is a
4
+ means the end" that ops share. Generic: it works on any ``OrderedDictRef``,
5
+ of bool (membership, the default) or of values. The checks that make an anchor valid (it is there, it is a
6
6
  sibling) are the caller's, made before this runs.
7
7
  """
8
8
 
@@ -37,17 +37,19 @@ def place(
37
37
  *,
38
38
  before: nu.StrArg | None = None,
39
39
  after: nu.StrArg | None = None,
40
+ value: object = True,
40
41
  ) -> nu.Nu:
41
42
  """Put ``id`` into ``listed`` before or after an anchor, or at the end. One view call.
42
43
 
43
- ``id`` must not be listed yet: to move one, take it out first. No bracket.
44
+ ``value`` is what ``id`` holds, ``True`` for a membership list. ``id``
45
+ must not be listed yet: to move one, take it out first. No bracket.
44
46
 
45
47
  Raises:
46
48
  TypeError: Both ``before`` and ``after`` are given, at build.
47
49
  """
48
50
  anchor_of(before, after)
49
51
  if before is not None:
50
- return listed.insert_before(before, id)
52
+ return listed.insert_before(before, id, value)
51
53
  if after is not None:
52
- return listed.insert_after(after, id)
53
- return listed.set_item(id, True)
54
+ return listed.insert_after(after, id, value)
55
+ return listed.set_item(id, value)
@@ -1,49 +1,49 @@
1
- """``nuspace.devices.web``: the web device, the browser's way into the space, with its own stores.
1
+ """``nuspace.web``: the web device, the browser's way into the space, with its own stores.
2
2
 
3
3
  The sdk, what a program calls, is the ops (:mod:`.ops`), flat here::
4
4
 
5
- import nuspace.devices.web as web
5
+ import nuspace.web as web
6
6
 
7
7
  nuspace.atomic(web.add(plane, editable=True)) # put a plane on the web
8
8
  web.params(), web.on_params() # a cell's own pane params
9
9
  web.restart_cell(sid, cell) # run one cell again, brackets itself
10
10
  nuspace.atomic(web.link(row.link, plane)) # draw a link to a plane
11
- nuspace.atomic(web.set_desk(desk_id, desk)) # replace a desk whole
11
+ nuspace.atomic(web.page(plane)) # {id, title, icon, href}: a PageLink table cell
12
+ web.url(plane, {"id": 3}) # its URL, plain python, for rows built outside a term
12
13
  nuspace.atomic(web.set_role("home", plane)) # make a plane the home
13
14
 
14
15
  - :mod:`.shapes`: :class:`Web` (web planes, recents, desks, roles) and :class:`Conn`
15
16
  (browser tabs, their panes and sidebar folds); :mod:`.stores`: their
16
17
  files, channels and init terms.
17
- - :mod:`.ops`: the ops over them.
18
+ - :mod:`.ops`: the ops over them, all but the desks'.
19
+ - :mod:`.desks`: the desks: their ops (``web.desks.add_plane``, ...), the
20
+ browser node and feed, the desk pruner.
21
+ - :mod:`.home`: Home and the home indexer; :mod:`.planes`: every plane
22
+ the device ships with, made at open (search and settings live there).
18
23
  - :mod:`.nav`: the driver running what the panes show.
19
24
  - :mod:`.device`: the server and one arm per tab, the sidebar, viewer and
20
- desks feeds under it, and :mod:`.planes` (the home, settings and search
21
- role planes) it ships with.
25
+ desks feeds under it.
22
26
  Imported by the host only: it pulls in the web server.
23
27
 
24
28
  Safe for a worker to import: nothing imported here pulls in a server.
25
29
  """
26
30
 
31
+ from . import desks
32
+ from .interactions import url
27
33
  from .ops import (
28
34
  add,
29
35
  close_tab,
30
36
  collapse,
31
- create_desk,
32
- delete_desk,
33
- desk_order,
34
37
  expand,
35
- get_desk,
36
38
  href,
37
39
  link,
38
- list_desks,
39
- move_desk,
40
40
  on_params,
41
41
  open_tab,
42
+ page,
42
43
  params,
43
44
  restart_cell,
44
45
  restart_pane,
45
46
  set,
46
- set_desk,
47
47
  set_pane_params,
48
48
  set_panes,
49
49
  set_role,
@@ -66,24 +66,20 @@ __all__ = [
66
66
  "add",
67
67
  "close_tab",
68
68
  "collapse",
69
- "create_desk",
70
- "delete_desk",
71
- "desk_order",
69
+ "desks",
72
70
  "expand",
73
- "get_desk",
74
71
  "href",
75
72
  "link",
76
- "list_desks",
77
- "move_desk",
78
73
  "on_params",
79
74
  "open_tab",
75
+ "page",
80
76
  "params",
81
77
  "restart_cell",
82
78
  "restart_pane",
83
79
  "set",
84
- "set_desk",
85
80
  "set_pane_params",
86
81
  "set_panes",
87
82
  "set_role",
83
+ "url",
88
84
  "visit",
89
85
  ]
@@ -0,0 +1,58 @@
1
+ """The desks: their ops, the tab's copy of the desks and the roles, and the pruner.
2
+
3
+ ``ops``
4
+ The desk ops a program calls, re-exported here::
5
+
6
+ import nuspace.web.desks as desks
7
+
8
+ nuspace.atomic(desks.add_plane(desk_id, plane))
9
+
10
+ ``model``
11
+ The desk shape in plain python, and the hosts running it.
12
+ ``ref``
13
+ The node the browser keeps, write only: it draws nothing.
14
+ ``interactions``
15
+ Everything that node says and hears, in the browser's words.
16
+ ``feed``
17
+ One arm per interaction. An event runs one op, a store change ships
18
+ the desks, the roles or the desk planes' titles again.
19
+ ``pruner``
20
+ The system plane taking removed planes off every desk.
21
+
22
+ The wire contract is ``task-167/contract.md`` in Go.
23
+ """
24
+
25
+ from nuspace.web.desks.ops import (
26
+ add_item,
27
+ add_plane,
28
+ create_desk,
29
+ delete_desk,
30
+ get_desk,
31
+ list_desks,
32
+ move_desk,
33
+ move_item,
34
+ remove_item,
35
+ rename_desk,
36
+ reset_item,
37
+ save_desk,
38
+ set_item,
39
+ write_items,
40
+ )
41
+
42
+
43
+ __all__ = [
44
+ "add_item",
45
+ "add_plane",
46
+ "create_desk",
47
+ "delete_desk",
48
+ "get_desk",
49
+ "list_desks",
50
+ "move_desk",
51
+ "move_item",
52
+ "remove_item",
53
+ "rename_desk",
54
+ "reset_item",
55
+ "save_desk",
56
+ "set_item",
57
+ "write_items",
58
+ ]
@@ -0,0 +1,152 @@
1
+ """The desks feed: one arm per interaction, all in parallel, for one tab. Host only.
2
+
3
+ - **browser to store.** A desk event runs its ops in one Web commit: a
4
+ create, a rename, a delete, a move, or ``items.write``, item writes on
5
+ one desk or more (a move across desks is one of these). A failure toasts
6
+ on the tab (:meth:`~nuspace.web.utils.Arms.event`) and writes nothing.
7
+ - **store to browser.** Any desk write (a desk created, renamed, moved or
8
+ deleted, an item set, moved or taken off, by any tab or program): every
9
+ desk ships again, whole, read back as ``{name, items}``, in sidebar
10
+ order, with that order as a list. A desk is a few hundred bytes, so
11
+ shipping all of them is cheaper than tracking which one moved. The roles
12
+ changed: the roles ship again.
13
+ - **titles.** The title and icon of every plane a desk links to, original
14
+ or recent, ship as their own write, so a tab names an item it neither
15
+ has open nor sees in its lazy tree. They ship again when the desks
16
+ change and when a plane's name or icon changes, the watches the
17
+ sidebar's tree uses. A plane with no row, or removed, is left out.
18
+
19
+ Level, with no re-checks, as the sidebar: a missed notification leaves
20
+ the tab stale until the next change.
21
+ """
22
+
23
+ from __future__ import annotations
24
+
25
+ from typing import TYPE_CHECKING
26
+
27
+ import nu
28
+ import nuspace
29
+ from nuspace.ops.brackets import atomic
30
+ from nuspace.shapes.space import Space
31
+ from nuspace.web import ops as web
32
+ from nuspace.web.desks import interactions
33
+ from nuspace.web.desks import ops as desk_ops
34
+ from nuspace.web.desks.model import DeskPlanes, DesksView, Items
35
+ from nuspace.web.shapes import Web
36
+ from nuspace.web.utils import Arms, field_dict, field_dicts, field_str
37
+
38
+
39
+ if TYPE_CHECKING:
40
+ from collections.abc import Callable
41
+
42
+ from nustd.ui.core import Ref
43
+
44
+
45
+ __all__ = ["desks_feed", "titles"]
46
+
47
+
48
+ def _ship(read: nu.Nu, send: Callable[[nu.Dict], nu.Nu]) -> nu.Nu:
49
+ """``read`` in one snapshot, then ``send(value)`` to the browser."""
50
+ return nu.let(atomic(read), lambda held: send(nu.Dict(held)))
51
+
52
+
53
+ def titles() -> nu.Dict:
54
+ """``{plane_id: {title, icon}}`` for every plane a desk links to, original or recent. Bare read.
55
+
56
+ A title or icon the plane lacks is ``""``. A plane with no row, or
57
+ removed, is left out. Cost: one read of the desks, four reads a plane
58
+ on them.
59
+ """
60
+
61
+ def look(at: nu.Attr) -> nu.List:
62
+ plane = nu.Str(at)
63
+ row = Space.planes[plane]
64
+ return nu.List.of(
65
+ plane, nu.Dict.of(title=row.name.fallback(""), icon=row.icon.fallback(""))
66
+ )
67
+
68
+ planes = nu.List(DeskPlanes(Web.desks.extract()))
69
+ shown = planes.iter().filter(lambda p: nuspace.plane.exists(nu.Str(p)))
70
+ return nu.dict(shown.map(look).to_list())
71
+
72
+
73
+ def _create(ev: nu.Attr) -> nu.Nu:
74
+ """``desk.create``: the desk, last, then its items in their order."""
75
+ desk_id = field_str(ev, "desk_id")
76
+ desk = field_dict(ev, "desk")
77
+ made = desk_ops.create_desk(nu.Str(desk["name"]), id=desk_id)
78
+ return made >> desk_ops.write_items(desk_id, Items(desk["items"]))
79
+
80
+
81
+ def _move(ev: nu.Attr) -> nu.Nu:
82
+ """``desk.move``: in front of ``before``, last for ``""``."""
83
+ desk_id, before = field_str(ev, "desk_id"), field_str(ev, "before")
84
+ return nu.IfDo(
85
+ before == "", desk_ops.move_desk(desk_id), desk_ops.move_desk(desk_id, before=before)
86
+ )
87
+
88
+
89
+ def _write(ev: nu.Attr) -> nu.Nu:
90
+ """``items.write``: each desk's writes in turn, the caller's one commit around them all."""
91
+
92
+ def one(held: nu.Attr) -> nu.Nu:
93
+ w = nu.Dict(held)
94
+ return desk_ops.write_items(nu.Str(w["desk_id"]), w["sets"], w["deletes"], w["order"])
95
+
96
+ return nu.ForEachDo(field_dicts(ev, "writes"), one)
97
+
98
+
99
+ def desks_feed(desks: Ref, toasts: Ref) -> nu.Nu:
100
+ """The desks, the roles and the desk planes' titles of one tab, live, as one term. Built per tab, never ends.
101
+
102
+ Args:
103
+ desks: The shell's desks ref.
104
+ toasts: The tab's toast ref, where a failed event shows.
105
+ """
106
+ arms = Arms("desks", toasts)
107
+
108
+ def op(name: str, change: nu.Nu, run: Callable[[nu.Attr], nu.Nu]) -> nu.Nu:
109
+ return arms.event(name, change, lambda ev: atomic(run(ev)))
110
+
111
+ def send_desks(held: nu.Dict) -> nu.Nu:
112
+ return interactions.set_desks(desks, held, nu.list(held.keys()))
113
+
114
+ shipped_desks = _ship(DesksView(Web.desks.extract()), send_desks)
115
+ shipped_roles = _ship(Web.roles.extract(), lambda r: interactions.set_roles(desks, r))
116
+ shipped_titles = _ship(titles(), lambda t: interactions.set_titles(desks, t))
117
+ on_desks = atomic(Web.desks.on_change())
118
+ return nu.ParallelAsync(
119
+ arms.states("desks", [on_desks], shipped_desks),
120
+ arms.states(
121
+ "roles",
122
+ [atomic(Web.roles.on_children_change()), atomic(Web.roles.on_descendants_change("*"))],
123
+ shipped_roles,
124
+ ),
125
+ arms.states(
126
+ "titles",
127
+ [
128
+ atomic(Web.desks.on_change()),
129
+ atomic(Space.planes.on_descendants_change("*", "name")),
130
+ atomic(Space.planes.on_descendants_change("*", "icon")),
131
+ ],
132
+ shipped_titles,
133
+ ),
134
+ op("create", interactions.on_create_desk(desks), _create),
135
+ op(
136
+ "rename",
137
+ interactions.on_rename_desk(desks),
138
+ lambda ev: desk_ops.rename_desk(field_str(ev, "desk_id"), field_str(ev, "name")),
139
+ ),
140
+ op(
141
+ "delete",
142
+ interactions.on_delete_desk(desks),
143
+ lambda ev: desk_ops.delete_desk(field_str(ev, "desk_id")),
144
+ ),
145
+ op("move", interactions.on_move_desk(desks), _move),
146
+ op("write", interactions.on_write_items(desks), _write),
147
+ op(
148
+ "role",
149
+ interactions.on_set_role(desks),
150
+ lambda ev: web.set_role(field_str(ev, "role"), field_str(ev, "plane_id")),
151
+ ),
152
+ )
@@ -13,7 +13,7 @@ from __future__ import annotations
13
13
 
14
14
  from typing import TYPE_CHECKING
15
15
 
16
- from nuspace.devices.web.utils import event, write
16
+ from nuspace.web.utils import event, write
17
17
 
18
18
 
19
19
  if TYPE_CHECKING:
@@ -25,8 +25,9 @@ __all__ = [
25
25
  "on_create_desk",
26
26
  "on_delete_desk",
27
27
  "on_move_desk",
28
- "on_set_desk",
28
+ "on_rename_desk",
29
29
  "on_set_role",
30
+ "on_write_items",
30
31
  "set_desks",
31
32
  "set_roles",
32
33
  "set_titles",
@@ -37,7 +38,7 @@ __all__ = [
37
38
 
38
39
 
39
40
  def set_desks(desks: Ref, held: DictArg, order: ListArg[str]) -> Nu:
40
- """Replace every desk: ``{desk_id: desk}``, each desk the whole dict, and ``order``, their ids in creation order."""
41
+ """Replace every desk: ``{desk_id: {name, items}}``, each desk whole, and ``order``, their ids in sidebar order."""
41
42
  return write(desks, "set_desks", desks=held, order=order)
42
43
 
43
44
 
@@ -55,13 +56,23 @@ def set_titles(desks: Ref, titles: DictArg) -> Nu:
55
56
 
56
57
 
57
58
  def on_create_desk(desks: Ref) -> Changed:
58
- """``{desk_id, desk}``. The browser minted ``desk_id``."""
59
+ """``{desk_id, desk}``, ``desk`` as ``{name, items}``, last among the desks. The browser minted ``desk_id``."""
59
60
  return event(desks, "desk.create")
60
61
 
61
62
 
62
- def on_set_desk(desks: Ref) -> Changed:
63
- """``{desk_id, desk}``. The desk whole, last write wins."""
64
- return event(desks, "desk.set")
63
+ def on_rename_desk(desks: Ref) -> Changed:
64
+ """``{desk_id, name}``."""
65
+ return event(desks, "desk.rename")
66
+
67
+
68
+ def on_write_items(desks: Ref) -> Changed:
69
+ """``{writes: [{desk_id, sets, deletes, order}]}``: item writes on one desk or more, as one commit.
70
+
71
+ ``sets`` item id to item (no id in it), ``deletes`` and ``order`` item
72
+ ids. A move across desks is one event: a delete on one desk, a set on
73
+ the other.
74
+ """
75
+ return event(desks, "items.write")
65
76
 
66
77
 
67
78
  def on_delete_desk(desks: Ref) -> Changed: