arrayview 0.25.0__tar.gz → 0.25.1__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 (211) hide show
  1. {arrayview-0.25.0 → arrayview-0.25.1}/PKG-INFO +1 -1
  2. {arrayview-0.25.0 → arrayview-0.25.1}/pyproject.toml +1 -1
  3. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/__init__.py +1 -1
  4. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_io.py +2 -2
  5. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_launcher.py +181 -26
  6. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_server.py +1 -0
  7. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_api.py +130 -10
  8. {arrayview-0.25.0 → arrayview-0.25.1}/uv.lock +1 -1
  9. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/frontend-designer/SKILL.md +0 -0
  10. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/invocation-consistency/SKILL.md +0 -0
  11. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/modes-consistency/SKILL.md +0 -0
  12. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/SKILL.md +0 -0
  13. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/references/element-attributes.md +0 -0
  14. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/references/playwright-tests.md +0 -0
  15. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/references/request-mocking.md +0 -0
  16. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/references/running-code.md +0 -0
  17. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/references/session-management.md +0 -0
  18. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/references/storage-state.md +0 -0
  19. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/references/test-generation.md +0 -0
  20. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/references/tracing.md +0 -0
  21. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/playwright-cli/references/video-recording.md +0 -0
  22. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/todo-workflow/SKILL.md +0 -0
  23. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/ui-consistency-audit/SKILL.md +0 -0
  24. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/viewer-ui-checklist/SKILL.md +0 -0
  25. {arrayview-0.25.0 → arrayview-0.25.1}/.agents/skills/visual-bug-fixing/SKILL.md +0 -0
  26. {arrayview-0.25.0 → arrayview-0.25.1}/.github/copilot-instructions.md +0 -0
  27. {arrayview-0.25.0 → arrayview-0.25.1}/.github/workflows/docs.yml +0 -0
  28. {arrayview-0.25.0 → arrayview-0.25.1}/.github/workflows/python-publish.yml +0 -0
  29. {arrayview-0.25.0 → arrayview-0.25.1}/.gitignore +0 -0
  30. {arrayview-0.25.0 → arrayview-0.25.1}/.ignore +0 -0
  31. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/AGENTS.md +0 -0
  32. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/ROUTER.md +0 -0
  33. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/SETUP.md +0 -0
  34. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/SYNC.md +0 -0
  35. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/context/architecture.md +0 -0
  36. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/context/conventions.md +0 -0
  37. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/context/decisions.md +0 -0
  38. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/context/frontend.md +0 -0
  39. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/context/lifecycle.md +0 -0
  40. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/context/project-state.md +0 -0
  41. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/context/render-pipeline.md +0 -0
  42. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/context/setup.md +0 -0
  43. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/context/stack.md +0 -0
  44. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/INDEX.md +0 -0
  45. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/README.md +0 -0
  46. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/add-file-format.md +0 -0
  47. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/add-server-endpoint.md +0 -0
  48. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/animation-verify.md +0 -0
  49. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/debug-render.md +0 -0
  50. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/debug-vscode-extension-python.md +0 -0
  51. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/extend-compare-mode.md +0 -0
  52. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/extract-server-route-module.md +0 -0
  53. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/patterns/frontend-change.md +0 -0
  54. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/setup.sh +0 -0
  55. {arrayview-0.25.0 → arrayview-0.25.1}/.mex/sync.sh +0 -0
  56. {arrayview-0.25.0 → arrayview-0.25.1}/.opencode/opencode.json +0 -0
  57. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T18-46-49-737Z.yml +0 -0
  58. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T18-48-21-979Z.yml +0 -0
  59. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T18-51-35-665Z.yml +0 -0
  60. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T19-07-01-393Z.yml +0 -0
  61. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T19-14-37-969Z.yml +0 -0
  62. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T19-21-30-940Z.yml +0 -0
  63. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T19-23-08-126Z.yml +0 -0
  64. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T19-29-33-155Z.yml +0 -0
  65. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T19-31-25-336Z.yml +0 -0
  66. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T19-31-53-789Z.yml +0 -0
  67. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T19-39-12-257Z.yml +0 -0
  68. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T19-39-16-449Z.yml +0 -0
  69. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T20-15-25-513Z.yml +0 -0
  70. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T20-25-13-179Z.yml +0 -0
  71. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T20-39-01-435Z.yml +0 -0
  72. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-01-27-659Z.yml +0 -0
  73. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-01-41-283Z.yml +0 -0
  74. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-03-00-625Z.yml +0 -0
  75. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-04-12-887Z.yml +0 -0
  76. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-33-39-044Z.yml +0 -0
  77. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-38-01-530Z.yml +0 -0
  78. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-45-20-383Z.yml +0 -0
  79. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-55-11-545Z.yml +0 -0
  80. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-56-03-307Z.yml +0 -0
  81. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-56-35-733Z.yml +0 -0
  82. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-57-12-181Z.yml +0 -0
  83. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-57-37-748Z.yml +0 -0
  84. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T21-58-13-679Z.yml +0 -0
  85. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-06T22-37-23-895Z.yml +0 -0
  86. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T00-39-18-637Z.yml +0 -0
  87. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T01-41-46-243Z.yml +0 -0
  88. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T04-31-48-472Z.yml +0 -0
  89. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-14-15-632Z.yml +0 -0
  90. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-14-47-582Z.yml +0 -0
  91. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-16-23-471Z.yml +0 -0
  92. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-17-10-247Z.yml +0 -0
  93. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-18-24-707Z.yml +0 -0
  94. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-20-06-164Z.yml +0 -0
  95. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-20-28-342Z.yml +0 -0
  96. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-21-54-962Z.yml +0 -0
  97. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-22-34-666Z.yml +0 -0
  98. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-23-11-336Z.yml +0 -0
  99. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-23-36-260Z.yml +0 -0
  100. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-24-09-267Z.yml +0 -0
  101. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-24-35-434Z.yml +0 -0
  102. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-25-57-010Z.yml +0 -0
  103. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-34-48-823Z.yml +0 -0
  104. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-46-46-468Z.yml +0 -0
  105. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-48-17-930Z.yml +0 -0
  106. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-49-26-400Z.yml +0 -0
  107. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-50-31-563Z.yml +0 -0
  108. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/page-2026-05-07T12-56-45-568Z.yml +0 -0
  109. {arrayview-0.25.0 → arrayview-0.25.1}/.playwright-cli/theme-dark.yml +0 -0
  110. {arrayview-0.25.0 → arrayview-0.25.1}/.python-version +0 -0
  111. {arrayview-0.25.0 → arrayview-0.25.1}/.vscode/settings.json +0 -0
  112. {arrayview-0.25.0 → arrayview-0.25.1}/AGENTS.md +0 -0
  113. {arrayview-0.25.0 → arrayview-0.25.1}/CONTRIBUTING.md +0 -0
  114. {arrayview-0.25.0 → arrayview-0.25.1}/DESIGN.md +0 -0
  115. {arrayview-0.25.0 → arrayview-0.25.1}/LICENSE +0 -0
  116. {arrayview-0.25.0 → arrayview-0.25.1}/README.md +0 -0
  117. {arrayview-0.25.0 → arrayview-0.25.1}/docs/comparing.md +0 -0
  118. {arrayview-0.25.0 → arrayview-0.25.1}/docs/configuration.md +0 -0
  119. {arrayview-0.25.0 → arrayview-0.25.1}/docs/display.md +0 -0
  120. {arrayview-0.25.0 → arrayview-0.25.1}/docs/index.md +0 -0
  121. {arrayview-0.25.0 → arrayview-0.25.1}/docs/lifecycle.md +0 -0
  122. {arrayview-0.25.0 → arrayview-0.25.1}/docs/loading.md +0 -0
  123. {arrayview-0.25.0 → arrayview-0.25.1}/docs/logo.png +0 -0
  124. {arrayview-0.25.0 → arrayview-0.25.1}/docs/measurement.md +0 -0
  125. {arrayview-0.25.0 → arrayview-0.25.1}/docs/remote.md +0 -0
  126. {arrayview-0.25.0 → arrayview-0.25.1}/docs/stylesheets/extra.css +0 -0
  127. {arrayview-0.25.0 → arrayview-0.25.1}/docs/viewing.md +0 -0
  128. {arrayview-0.25.0 → arrayview-0.25.1}/matlab/arrayview.m +0 -0
  129. {arrayview-0.25.0 → arrayview-0.25.1}/mkdocs.yml +0 -0
  130. {arrayview-0.25.0 → arrayview-0.25.1}/plans/2026-04-14-immersive-animation.md +0 -0
  131. {arrayview-0.25.0 → arrayview-0.25.1}/plans/2026-05-07-unified-colormap-picker.md +0 -0
  132. {arrayview-0.25.0 → arrayview-0.25.1}/plans/arrayview_tool_menu_fix_prompt.md +0 -0
  133. {arrayview-0.25.0 → arrayview-0.25.1}/plans/arrayview_tool_menu_implementation_plan.md +0 -0
  134. {arrayview-0.25.0 → arrayview-0.25.1}/plans/egg-placement-mockup.html +0 -0
  135. {arrayview-0.25.0 → arrayview-0.25.1}/plans/refactoring_plan.md +0 -0
  136. {arrayview-0.25.0 → arrayview-0.25.1}/plans/webview/LOG.md +0 -0
  137. {arrayview-0.25.0 → arrayview-0.25.1}/scripts/demo.py +0 -0
  138. {arrayview-0.25.0 → arrayview-0.25.1}/scripts/release.sh +0 -0
  139. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/ARCHITECTURE.md +0 -0
  140. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/__main__.py +0 -0
  141. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_analysis.py +0 -0
  142. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_app.py +0 -0
  143. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_codex_open.py +0 -0
  144. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_config.py +0 -0
  145. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_diff.py +0 -0
  146. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_icon.png +0 -0
  147. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_imaging.py +0 -0
  148. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_lifecycle.py +0 -0
  149. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_overlays.py +0 -0
  150. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_platform.py +0 -0
  151. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_render.py +0 -0
  152. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_analysis.py +0 -0
  153. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_export.py +0 -0
  154. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_loading.py +0 -0
  155. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_persistence.py +0 -0
  156. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_preload.py +0 -0
  157. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_query.py +0 -0
  158. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_rendering.py +0 -0
  159. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_segmentation.py +0 -0
  160. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_state.py +0 -0
  161. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_vectorfield.py +0 -0
  162. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_routes_websocket.py +0 -0
  163. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_segmentation.py +0 -0
  164. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_session.py +0 -0
  165. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_shell.html +0 -0
  166. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_stdio_server.py +0 -0
  167. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_synthetic_mri.py +0 -0
  168. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_torch.py +0 -0
  169. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_vectorfield.py +0 -0
  170. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_viewer.html +0 -0
  171. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_vscode.py +0 -0
  172. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_vscode_browser.py +0 -0
  173. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_vscode_extension.py +0 -0
  174. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_vscode_shm.py +0 -0
  175. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/_vscode_signal.py +0 -0
  176. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/arrayview-opener.vsix +0 -0
  177. {arrayview-0.25.0 → arrayview-0.25.1}/src/arrayview/gsap.min.js +0 -0
  178. {arrayview-0.25.0 → arrayview-0.25.1}/tests/capture_v_animation.py +0 -0
  179. {arrayview-0.25.0 → arrayview-0.25.1}/tests/conftest.py +0 -0
  180. {arrayview-0.25.0 → arrayview-0.25.1}/tests/lifecycle_matrix.py +0 -0
  181. {arrayview-0.25.0 → arrayview-0.25.1}/tests/make_vectorfield_test_arrays.py +0 -0
  182. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_backend_shared.py +0 -0
  183. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_browser.py +0 -0
  184. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_cli.py +0 -0
  185. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_colorbar_hover_highlight.py +0 -0
  186. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_command_reachability.py +0 -0
  187. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_config.py +0 -0
  188. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_cross_mode_parametrized.py +0 -0
  189. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_interactions.py +0 -0
  190. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_large_arrays.py +0 -0
  191. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_lifecycle_contract.py +0 -0
  192. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_loading_server.py +0 -0
  193. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_mode_consistency.py +0 -0
  194. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_mode_entry_batching.py +0 -0
  195. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_mode_matrix.py +0 -0
  196. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_mode_roundtrip.py +0 -0
  197. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_nifti_meta.py +0 -0
  198. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_rgb_pixel_art.py +0 -0
  199. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_stdio_server.py +0 -0
  200. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_torch.py +0 -0
  201. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_view_component_integration.py +0 -0
  202. {arrayview-0.25.0 → arrayview-0.25.1}/tests/test_view_component_unit.py +0 -0
  203. {arrayview-0.25.0 → arrayview-0.25.1}/tests/ui_audit.py +0 -0
  204. {arrayview-0.25.0 → arrayview-0.25.1}/tests/v_anim_frames/.gitkeep +0 -0
  205. {arrayview-0.25.0 → arrayview-0.25.1}/tests/visual_smoke.py +0 -0
  206. {arrayview-0.25.0 → arrayview-0.25.1}/vscode-extension/AGENTS.md +0 -0
  207. {arrayview-0.25.0 → arrayview-0.25.1}/vscode-extension/LICENSE +0 -0
  208. {arrayview-0.25.0 → arrayview-0.25.1}/vscode-extension/extension.js +0 -0
  209. {arrayview-0.25.0 → arrayview-0.25.1}/vscode-extension/lifecycle_helpers.js +0 -0
  210. {arrayview-0.25.0 → arrayview-0.25.1}/vscode-extension/package.json +0 -0
  211. {arrayview-0.25.0 → arrayview-0.25.1}/vscode-extension/test_lifecycle_helpers.js +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arrayview
3
- Version: 0.25.0
3
+ Version: 0.25.1
4
4
  Summary: Fast multi-dimensional array viewer
5
5
  Project-URL: Home, https://github.com/oscarvanderheide/arrayview
6
6
  Project-URL: Source, https://github.com/oscarvanderheide/arrayview
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "arrayview"
7
- version = "0.25.0"
7
+ version = "0.25.1"
8
8
  description = "Fast multi-dimensional array viewer"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.12"
@@ -1,4 +1,4 @@
1
- __version__ = "0.20.0"
1
+ __version__ = "0.25.1"
2
2
 
3
3
  __all__ = [
4
4
  "TrainingMonitor",
@@ -67,7 +67,7 @@ def list_mat_keys(filepath):
67
67
  for k, v in mat.items():
68
68
  if k.startswith("_"):
69
69
  continue
70
- if isinstance(v, np.ndarray) and v.ndim >= 1:
70
+ if isinstance(v, np.ndarray) and v.ndim >= 1 and v.dtype.kind in ("b", "i", "u", "f", "c"):
71
71
  keys.append({"key": k, "shape": list(v.shape), "dtype": str(v.dtype)})
72
72
  return keys
73
73
  except NotImplementedError:
@@ -77,7 +77,7 @@ def list_mat_keys(filepath):
77
77
  keys = []
78
78
  for k in f.keys():
79
79
  ds = f[k]
80
- if isinstance(ds, h5py.Dataset) and len(ds.shape) >= 1:
80
+ if isinstance(ds, h5py.Dataset) and len(ds.shape) >= 1 and ds.dtype.kind in ("b", "i", "u", "f", "c"):
81
81
  keys.append({"key": k, "shape": list(ds.shape), "dtype": str(ds.dtype)})
82
82
  f.close()
83
83
  return keys
@@ -448,6 +448,17 @@ def _open_webview_cli(
448
448
  Returns True once the child has imported pywebview and created the window.
449
449
  Returns False if it crashed; in that case the caller should fall back to browser.
450
450
  """
451
+ opened, _proc = _open_webview_cli_tracked(url, win_w, win_h, shell_port=shell_port)
452
+ return opened
453
+
454
+
455
+ def _open_webview_cli_tracked(
456
+ url: str,
457
+ win_w: int,
458
+ win_h: int,
459
+ shell_port: int | None = None,
460
+ ) -> tuple[bool, subprocess.Popen | None]:
461
+ """Launch pywebview and return the child process when startup succeeds."""
451
462
  import tempfile
452
463
 
453
464
  _vprint("[ArrayView] Launching native window (PyWebView)...", flush=True)
@@ -483,21 +494,64 @@ def _open_webview_cli(
483
494
  )
484
495
  if stderr_out:
485
496
  _vprint(f"[ArrayView] webview stderr: {stderr_out}", flush=True)
486
- return False
497
+ return False, None
487
498
  if os.path.exists(ready_file):
488
499
  try:
489
500
  os.unlink(ready_file)
490
501
  except OSError:
491
502
  pass
492
503
  _vprint("[ArrayView] Native window started successfully", flush=True)
493
- return True
504
+ return True, proc
494
505
  time.sleep(0.02)
495
506
  try:
496
507
  os.unlink(ready_file)
497
508
  except OSError:
498
509
  pass
499
510
  _vprint("[ArrayView] Native window started successfully", flush=True)
500
- return True
511
+ return True, proc
512
+
513
+
514
+ def _server_viewer_connections_seen(port: int, timeout: float = 0.5) -> int:
515
+ """Return the daemon's monotonic viewer WebSocket connection count."""
516
+ url = f"http://{_LOOPBACK_HOST}:{port}/ping"
517
+ try:
518
+ with urllib.request.urlopen(url, timeout=timeout) as resp:
519
+ if resp.status != 200:
520
+ return 0
521
+ payload = json.loads(resp.read().decode("utf-8"))
522
+ if payload.get("ok") is True and payload.get("service") == "arrayview":
523
+ return int(
524
+ payload.get("viewer_connections_seen")
525
+ or payload.get("viewer_sockets")
526
+ or 0
527
+ )
528
+ except Exception:
529
+ pass
530
+ return 0
531
+
532
+
533
+ def _wait_for_viewer_connection(
534
+ port: int,
535
+ *,
536
+ before: int,
537
+ timeout: float = 8.0,
538
+ ) -> bool:
539
+ """Wait until the backend has accepted a new viewer WebSocket connection."""
540
+ deadline = time.monotonic() + timeout
541
+ while time.monotonic() < deadline:
542
+ if _server_viewer_connections_seen(port, timeout=0.3) > before:
543
+ return True
544
+ time.sleep(0.1)
545
+ return False
546
+
547
+
548
+ def _terminate_native_process(proc: subprocess.Popen | None) -> None:
549
+ if proc is None or proc.poll() is not None:
550
+ return
551
+ try:
552
+ proc.terminate()
553
+ except Exception:
554
+ return
501
555
 
502
556
 
503
557
  # ── Server Port Utilities ─────────────────────────────────────────
@@ -809,7 +863,15 @@ def _open_cli_existing_server_view(
809
863
  return
810
864
  if notify_webview and not notified:
811
865
  url_shell = _shell_url(port, sid, name, compare_sids=compare_sids)
812
- if not _open_webview_cli(url_shell, 1200, 800):
866
+ seen_before = _server_viewer_connections_seen(port)
867
+ opened, proc = _open_webview_cli_tracked(url_shell, 1200, 800)
868
+ if not opened or not _wait_for_viewer_connection(port, before=seen_before):
869
+ if opened:
870
+ _vprint(
871
+ "[ArrayView] Native window did not connect to the backend; falling back to browser",
872
+ flush=True,
873
+ )
874
+ _terminate_native_process(proc)
813
875
  _vprint("[ArrayView] Falling back to browser", flush=True)
814
876
  _print_viewer_location(url)
815
877
  _open_browser(
@@ -993,7 +1055,9 @@ def _handle_cli_spawned_daemon(
993
1055
  )
994
1056
 
995
1057
  early_webview_opened = False
1058
+ early_webview_proc = None
996
1059
  early_webview_notified = False
1060
+ early_webview_connected = False
997
1061
  if (
998
1062
  use_webview
999
1063
  and not is_remote
@@ -1001,7 +1065,7 @@ def _handle_cli_spawned_daemon(
1001
1065
  and not compare_files
1002
1066
  ):
1003
1067
  url_shell_early = _shell_url(port, sid, name)
1004
- early_webview_opened = _open_webview_cli(
1068
+ early_webview_opened, early_webview_proc = _open_webview_cli_tracked(
1005
1069
  url_shell_early, 1400, 900, shell_port=port
1006
1070
  )
1007
1071
 
@@ -1019,6 +1083,7 @@ def _handle_cli_spawned_daemon(
1019
1083
  sys.exit(1)
1020
1084
 
1021
1085
  if early_webview_opened:
1086
+ seen_before = _server_viewer_connections_seen(port)
1022
1087
  try:
1023
1088
  notify_result = _notify_existing_session(
1024
1089
  port,
@@ -1030,6 +1095,21 @@ def _handle_cli_spawned_daemon(
1030
1095
  early_webview_notified = bool(notify_result.get("notified"))
1031
1096
  except Exception:
1032
1097
  early_webview_notified = False
1098
+ if early_webview_notified:
1099
+ early_webview_connected = _wait_for_viewer_connection(
1100
+ port,
1101
+ before=seen_before,
1102
+ )
1103
+ if not early_webview_connected:
1104
+ _vprint(
1105
+ "[ArrayView] Native window did not connect to the backend; falling back to browser",
1106
+ flush=True,
1107
+ )
1108
+ _terminate_native_process(early_webview_proc)
1109
+
1110
+ should_retry_webview = use_webview and not (
1111
+ early_webview_opened and not early_webview_connected
1112
+ )
1033
1113
 
1034
1114
  _open_cli_spawned_view(
1035
1115
  port=port,
@@ -1037,14 +1117,14 @@ def _handle_cli_spawned_daemon(
1037
1117
  compare_sids=compare_sids,
1038
1118
  overlay_sid=overlay_sid,
1039
1119
  dims_override=dims_override,
1040
- use_webview=use_webview,
1120
+ use_webview=should_retry_webview,
1041
1121
  name=name,
1042
1122
  base_file=base_file,
1043
1123
  watch=watch,
1044
1124
  window_mode=window_mode,
1045
1125
  floating=floating,
1046
1126
  is_remote=is_remote,
1047
- webview_already_opened=early_webview_opened,
1127
+ webview_already_opened=early_webview_connected,
1048
1128
  )
1049
1129
 
1050
1130
 
@@ -1075,7 +1155,15 @@ def _open_cli_spawned_view(
1075
1155
  if webview_already_opened:
1076
1156
  return
1077
1157
  url_shell = _shell_url(port, sid, name, compare_sids=compare_sids)
1078
- if not _open_webview_cli(url_shell, 1400, 900):
1158
+ seen_before = _server_viewer_connections_seen(port)
1159
+ opened, proc = _open_webview_cli_tracked(url_shell, 1400, 900)
1160
+ if not opened or not _wait_for_viewer_connection(port, before=seen_before):
1161
+ if opened:
1162
+ _vprint(
1163
+ "[ArrayView] Native window did not connect to the backend; falling back to browser",
1164
+ flush=True,
1165
+ )
1166
+ _terminate_native_process(proc)
1079
1167
  _vprint("[ArrayView] Falling back to browser", flush=True)
1080
1168
  _print_viewer_location(url)
1081
1169
  _open_browser(
@@ -2621,7 +2709,16 @@ def _serve_daemon(
2621
2709
  from arrayview._io import load_data, load_data_with_meta, list_array_keys
2622
2710
 
2623
2711
  try:
2624
- data, spatial_meta = load_data_with_meta(filepath)
2712
+ # Multi-array .npz/.mat: load the first array so the session is
2713
+ # created, then store keys so the viewer can show a picker.
2714
+ _array_keys = None
2715
+ if (filepath.endswith(".npz") or filepath.endswith(".mat")) and not cleanup:
2716
+ try:
2717
+ _array_keys = list_array_keys(filepath)
2718
+ except Exception:
2719
+ pass
2720
+ _load_key = _array_keys[0]["key"] if _array_keys and len(_array_keys) > 1 else None
2721
+ data, spatial_meta = load_data_with_meta(filepath, key=_load_key)
2625
2722
  if cleanup:
2626
2723
  try:
2627
2724
  os.unlink(filepath)
@@ -2632,15 +2729,9 @@ def _serve_daemon(
2632
2729
  )
2633
2730
  session.sid = sid
2634
2731
  session.spatial_meta = spatial_meta
2635
- # Multi-array .npz/.mat: store keys so the viewer can show a picker
2636
- if (filepath.endswith(".npz") or filepath.endswith(".mat")) and not cleanup:
2637
- try:
2638
- keys = list_array_keys(filepath)
2639
- if len(keys) > 1:
2640
- session.array_keys = keys
2641
- session.array_filepath = filepath
2642
- except Exception:
2643
- pass
2732
+ if _array_keys and len(_array_keys) > 1:
2733
+ session.array_keys = _array_keys
2734
+ session.array_filepath = filepath
2644
2735
  if spatial_meta is not None:
2645
2736
  session.original_volume = data
2646
2737
  if rgb:
@@ -3165,10 +3256,62 @@ def arrayview():
3165
3256
  # --diagnose: print detection results and exit
3166
3257
  if getattr(args, "diagnose", False):
3167
3258
  import json as _json
3259
+ import importlib.util as _importlib_util
3168
3260
  from ._platform import (
3169
3261
  _find_vscode_ipc_hook,
3170
3262
  _in_jupyter,
3171
3263
  )
3264
+ from arrayview._config import CONFIG_PATH, get_window_default, load_config
3265
+ from arrayview._platform import detect_environment
3266
+
3267
+ _det_env = detect_environment()
3268
+ _config_window = get_window_default(_det_env)
3269
+ _in_vs = _in_vscode_terminal()
3270
+ _is_remote = _is_vscode_remote()
3271
+ _can_native = _can_native_window()
3272
+ _window_plan = _resolve_cli_window_mode(
3273
+ explicit_window=args.window,
3274
+ browser_flag=args.browser,
3275
+ config_window=_config_window,
3276
+ in_vscode_terminal=_in_vs,
3277
+ is_vscode_remote=_is_remote,
3278
+ can_native_window=_can_native,
3279
+ )
3280
+
3281
+ def _localhost_candidates() -> list[dict[str, object]]:
3282
+ out = []
3283
+ try:
3284
+ infos = socket.getaddrinfo(
3285
+ _LOOPBACK_HOST,
3286
+ args.port,
3287
+ type=socket.SOCK_STREAM,
3288
+ )
3289
+ except Exception as exc:
3290
+ return [{"error": str(exc)}]
3291
+ for family, socktype, proto, _canon, sockaddr in infos:
3292
+ out.append(
3293
+ {
3294
+ "family": getattr(socket.AddressFamily(family), "name", str(family)),
3295
+ "socktype": socktype,
3296
+ "proto": proto,
3297
+ "sockaddr": str(sockaddr),
3298
+ }
3299
+ )
3300
+ return out
3301
+
3302
+ def _bind_probe(port: int) -> dict[str, object]:
3303
+ try:
3304
+ sock = _make_loopback_socket(port)
3305
+ except Exception as exc:
3306
+ return {"ok": False, "error": str(exc)}
3307
+ try:
3308
+ return {
3309
+ "ok": True,
3310
+ "family": getattr(socket.AddressFamily(sock.family), "name", str(sock.family)),
3311
+ "sockname": str(sock.getsockname()),
3312
+ }
3313
+ finally:
3314
+ sock.close()
3172
3315
 
3173
3316
  diag: dict = {
3174
3317
  "env": {
@@ -3182,28 +3325,40 @@ def arrayview():
3182
3325
  "WAYLAND_DISPLAY": os.environ.get("WAYLAND_DISPLAY"),
3183
3326
  },
3184
3327
  "detection": {
3185
- "in_vscode_terminal": _in_vscode_terminal(),
3186
- "is_vscode_remote": _is_vscode_remote(),
3328
+ "in_vscode_terminal": _in_vs,
3329
+ "is_vscode_remote": _is_remote,
3187
3330
  "in_vscode_tunnel": _in_vscode_tunnel(),
3188
- "can_native_window": _can_native_window(),
3331
+ "can_native_window": _can_native,
3189
3332
  "in_jupyter": _in_jupyter(),
3190
3333
  "vscode_ipc_hook_recovered": _find_vscode_ipc_hook(),
3191
3334
  },
3335
+ "launch_plan": {
3336
+ **_window_plan,
3337
+ "port": args.port,
3338
+ "port_in_use": _port_in_use(args.port),
3339
+ "arrayview_server_alive": _server_alive(args.port),
3340
+ },
3341
+ "loopback": {
3342
+ "host": _LOOPBACK_HOST,
3343
+ "getaddrinfo": _localhost_candidates(),
3344
+ "bind_probe": _bind_probe(0),
3345
+ },
3346
+ "native_dependencies": {
3347
+ "qtpy": _importlib_util.find_spec("qtpy") is not None,
3348
+ "gi": _importlib_util.find_spec("gi") is not None,
3349
+ "webview": _importlib_util.find_spec("webview") is not None,
3350
+ },
3192
3351
  "pid": os.getpid(),
3193
3352
  "ppid": os.getppid(),
3194
3353
  "platform": sys.platform,
3195
3354
  "python": sys.executable,
3196
3355
  }
3197
- from arrayview._config import CONFIG_PATH, get_window_default, load_config
3198
- from arrayview._platform import detect_environment
3199
-
3200
- _det_env = detect_environment()
3201
3356
  diag["config"] = {
3202
3357
  "detected_environment": _det_env,
3203
3358
  "config_file": CONFIG_PATH,
3204
3359
  "config_contents": load_config() or None,
3205
3360
  "ARRAYVIEW_WINDOW": os.environ.get("ARRAYVIEW_WINDOW") or None,
3206
- "resolved_window_pref": get_window_default(_det_env),
3361
+ "resolved_window_pref": _config_window,
3207
3362
  }
3208
3363
  print(_json.dumps(diag, indent=2))
3209
3364
  return
@@ -152,6 +152,7 @@ def ping():
152
152
  "pid": os.getpid(),
153
153
  "hostname": socket.gethostname(),
154
154
  "viewer_sockets": _session_mod.VIEWER_SOCKETS,
155
+ "viewer_connections_seen": _session_mod.VIEWER_CONNECTIONS_SEEN,
155
156
  }
156
157
 
157
158
 
@@ -2186,7 +2186,7 @@ class TestCliOpenHelpers:
2186
2186
  monkeypatch.setattr(launcher, "_vprint", lambda *args, **kwargs: calls.append(args))
2187
2187
  monkeypatch.setattr(
2188
2188
  launcher,
2189
- "_open_webview_cli",
2189
+ "_open_webview_cli_tracked",
2190
2190
  lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("unexpected")),
2191
2191
  )
2192
2192
  monkeypatch.setattr(
@@ -2217,7 +2217,9 @@ class TestCliOpenHelpers:
2217
2217
 
2218
2218
  opened = []
2219
2219
  printed = []
2220
- monkeypatch.setattr(launcher, "_open_webview_cli", lambda *args, **kwargs: False)
2220
+ monkeypatch.setattr(
2221
+ launcher, "_open_webview_cli_tracked", lambda *args, **kwargs: (False, None)
2222
+ )
2221
2223
  monkeypatch.setattr(
2222
2224
  launcher, "_print_viewer_location", lambda url: printed.append(url)
2223
2225
  )
@@ -2256,6 +2258,104 @@ class TestCliOpenHelpers:
2256
2258
  }
2257
2259
  ]
2258
2260
 
2261
+ def test_open_cli_existing_server_view_falls_back_when_native_never_connects(
2262
+ self, monkeypatch
2263
+ ):
2264
+ import arrayview._launcher as launcher
2265
+
2266
+ opened = []
2267
+ terminated = []
2268
+ proc = type(
2269
+ "P",
2270
+ (),
2271
+ {
2272
+ "poll": lambda self: None,
2273
+ "terminate": lambda self: terminated.append(True),
2274
+ },
2275
+ )()
2276
+ monkeypatch.setattr(
2277
+ launcher, "_open_webview_cli_tracked", lambda *args, **kwargs: (True, proc)
2278
+ )
2279
+ monkeypatch.setattr(launcher, "_server_viewer_connections_seen", lambda port: 4)
2280
+ monkeypatch.setattr(
2281
+ launcher, "_wait_for_viewer_connection", lambda *args, **kwargs: False
2282
+ )
2283
+ monkeypatch.setattr(launcher, "_print_viewer_location", lambda url: None)
2284
+ monkeypatch.setattr(
2285
+ launcher,
2286
+ "_open_browser",
2287
+ lambda url, **kwargs: opened.append({"url": url, **kwargs}),
2288
+ )
2289
+ monkeypatch.setattr(launcher, "_vprint", lambda *args, **kwargs: None)
2290
+
2291
+ launcher._open_cli_existing_server_view(
2292
+ port=8000,
2293
+ sid="sid_base",
2294
+ compare_sids=[],
2295
+ overlay_sid=None,
2296
+ dims_override=None,
2297
+ notify_webview=True,
2298
+ notified=False,
2299
+ name="base.npy",
2300
+ base_file="/tmp/base.npy",
2301
+ watch=False,
2302
+ window_mode="native",
2303
+ floating=False,
2304
+ )
2305
+
2306
+ assert terminated == [True]
2307
+ assert opened[0]["url"] == "http://localhost:8000/?sid=sid_base"
2308
+ assert opened[0]["blocking"] is True
2309
+
2310
+ def test_open_cli_spawned_view_falls_back_when_native_never_connects(
2311
+ self, monkeypatch
2312
+ ):
2313
+ import arrayview._launcher as launcher
2314
+
2315
+ opened = []
2316
+ terminated = []
2317
+ proc = type(
2318
+ "P",
2319
+ (),
2320
+ {
2321
+ "poll": lambda self: None,
2322
+ "terminate": lambda self: terminated.append(True),
2323
+ },
2324
+ )()
2325
+ monkeypatch.setattr(
2326
+ launcher, "_open_webview_cli_tracked", lambda *args, **kwargs: (True, proc)
2327
+ )
2328
+ monkeypatch.setattr(launcher, "_server_viewer_connections_seen", lambda port: 0)
2329
+ monkeypatch.setattr(
2330
+ launcher, "_wait_for_viewer_connection", lambda *args, **kwargs: False
2331
+ )
2332
+ monkeypatch.setattr(launcher, "_print_viewer_location", lambda url: None)
2333
+ monkeypatch.setattr(
2334
+ launcher,
2335
+ "_open_browser",
2336
+ lambda url, **kwargs: opened.append({"url": url, **kwargs}),
2337
+ )
2338
+ monkeypatch.setattr(launcher, "_vprint", lambda *args, **kwargs: None)
2339
+
2340
+ launcher._open_cli_spawned_view(
2341
+ port=8000,
2342
+ sid="sid_base",
2343
+ compare_sids=[],
2344
+ overlay_sid=None,
2345
+ dims_override=None,
2346
+ use_webview=True,
2347
+ name="base.npy",
2348
+ watch=False,
2349
+ window_mode="native",
2350
+ floating=False,
2351
+ is_remote=False,
2352
+ base_file="/tmp/base.npy",
2353
+ )
2354
+
2355
+ assert terminated == [True]
2356
+ assert opened[0]["url"] == "http://localhost:8000/?sid=sid_base"
2357
+ assert opened[0]["blocking"] is False
2358
+
2259
2359
  def test_open_cli_spawned_view_overlay_browser_path_starts_watch(self, monkeypatch):
2260
2360
  import arrayview._launcher as launcher
2261
2361
 
@@ -2264,7 +2364,7 @@ class TestCliOpenHelpers:
2264
2364
  watched = []
2265
2365
  monkeypatch.setattr(
2266
2366
  launcher,
2267
- "_open_webview_cli",
2367
+ "_open_webview_cli_tracked",
2268
2368
  lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("unexpected")),
2269
2369
  )
2270
2370
  monkeypatch.setattr(
@@ -2485,6 +2585,7 @@ class TestCliOpenHelpers:
2485
2585
 
2486
2586
  events = []
2487
2587
  opened = []
2588
+ proc = type("P", (), {"poll": lambda self: None, "terminate": lambda self: None})()
2488
2589
 
2489
2590
  monkeypatch.setattr(
2490
2591
  launcher.subprocess,
@@ -2493,14 +2594,16 @@ class TestCliOpenHelpers:
2493
2594
  )
2494
2595
  monkeypatch.setattr(
2495
2596
  launcher,
2496
- "_open_webview_cli",
2497
- lambda *args, **kwargs: events.append(("webview", args, kwargs)) or True,
2597
+ "_open_webview_cli_tracked",
2598
+ lambda *args, **kwargs: events.append(("webview", args, kwargs)) or (True, proc),
2498
2599
  )
2499
2600
  monkeypatch.setattr(
2500
2601
  launcher,
2501
2602
  "_wait_for_port",
2502
2603
  lambda *args, **kwargs: events.append(("wait", args, kwargs)) or True,
2503
2604
  )
2605
+ monkeypatch.setattr(launcher, "_server_viewer_connections_seen", lambda port: 0)
2606
+ monkeypatch.setattr(launcher, "_wait_for_viewer_connection", lambda *args, **kwargs: True)
2504
2607
  monkeypatch.setattr(launcher, "_load_compare_sids", lambda port, files: [])
2505
2608
  monkeypatch.setattr(
2506
2609
  launcher,
@@ -2540,25 +2643,40 @@ class TestCliOpenHelpers:
2540
2643
  assert events[1][2]["shell_port"] == 8000
2541
2644
  assert opened[0]["webview_already_opened"] is True
2542
2645
 
2543
- def test_handle_cli_spawned_daemon_keeps_first_native_shell_when_notify_misses(
2646
+ def test_handle_cli_spawned_daemon_falls_back_when_early_native_never_connects(
2544
2647
  self, monkeypatch
2545
2648
  ):
2546
2649
  import arrayview._launcher as launcher
2547
2650
 
2548
2651
  opened = []
2652
+ terminated = []
2653
+ proc = type(
2654
+ "P",
2655
+ (),
2656
+ {
2657
+ "poll": lambda self: None,
2658
+ "terminate": lambda self: terminated.append(True),
2659
+ },
2660
+ )()
2549
2661
 
2550
2662
  monkeypatch.setattr(
2551
2663
  launcher.subprocess,
2552
2664
  "Popen",
2553
2665
  lambda cmd, *args, **kwargs: object(),
2554
2666
  )
2555
- monkeypatch.setattr(launcher, "_open_webview_cli", lambda *args, **kwargs: True)
2667
+ monkeypatch.setattr(
2668
+ launcher, "_open_webview_cli_tracked", lambda *args, **kwargs: (True, proc)
2669
+ )
2556
2670
  monkeypatch.setattr(launcher, "_wait_for_port", lambda *args, **kwargs: True)
2557
2671
  monkeypatch.setattr(launcher, "_load_compare_sids", lambda port, files: [])
2672
+ monkeypatch.setattr(launcher, "_server_viewer_connections_seen", lambda port: 0)
2673
+ monkeypatch.setattr(
2674
+ launcher, "_wait_for_viewer_connection", lambda *args, **kwargs: False
2675
+ )
2558
2676
  monkeypatch.setattr(
2559
2677
  launcher,
2560
2678
  "_notify_existing_session",
2561
- lambda *args, **kwargs: {"notified": False},
2679
+ lambda *args, **kwargs: {"notified": True},
2562
2680
  )
2563
2681
  monkeypatch.setattr(
2564
2682
  launcher,
@@ -2586,7 +2704,9 @@ class TestCliOpenHelpers:
2586
2704
  demo_cleanup=False,
2587
2705
  )
2588
2706
 
2589
- assert opened[0]["webview_already_opened"] is True
2707
+ assert terminated == [True]
2708
+ assert opened[0]["use_webview"] is False
2709
+ assert opened[0]["webview_already_opened"] is False
2590
2710
 
2591
2711
  def test_handle_cli_spawned_daemon_does_not_early_open_remote_webview(
2592
2712
  self, monkeypatch
@@ -2602,7 +2722,7 @@ class TestCliOpenHelpers:
2602
2722
  )
2603
2723
  monkeypatch.setattr(
2604
2724
  launcher,
2605
- "_open_webview_cli",
2725
+ "_open_webview_cli_tracked",
2606
2726
  lambda *args, **kwargs: (_ for _ in ()).throw(AssertionError("unexpected")),
2607
2727
  )
2608
2728
  monkeypatch.setattr(launcher, "_wait_for_port", lambda *args, **kwargs: True)
@@ -198,7 +198,7 @@ wheels = [
198
198
 
199
199
  [[package]]
200
200
  name = "arrayview"
201
- version = "0.25.0"
201
+ version = "0.25.1"
202
202
  source = { editable = "." }
203
203
  dependencies = [
204
204
  { name = "fastapi" },
File without changes
File without changes