arrayview 0.29.1__tar.gz → 0.29.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 (216) hide show
  1. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/context/lifecycle.md +1 -1
  2. {arrayview-0.29.1 → arrayview-0.29.2}/PKG-INFO +1 -1
  3. {arrayview-0.29.1 → arrayview-0.29.2}/pyproject.toml +1 -1
  4. {arrayview-0.29.1 → arrayview-0.29.2}/scripts/release.sh +25 -16
  5. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_launcher.py +6 -4
  6. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_viewer.html +24 -88
  7. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_vscode_extension.py +1 -1
  8. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_vscode_signal.py +111 -43
  9. arrayview-0.29.2/src/arrayview/arrayview-opener.vsix +0 -0
  10. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_api.py +67 -4
  11. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_lifecycle_contract.py +49 -5
  12. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_view_component_integration.py +22 -0
  13. {arrayview-0.29.1 → arrayview-0.29.2}/uv.lock +1 -1
  14. {arrayview-0.29.1 → arrayview-0.29.2}/vscode-extension/extension.js +13 -1
  15. {arrayview-0.29.1 → arrayview-0.29.2}/vscode-extension/lifecycle_helpers.js +5 -0
  16. {arrayview-0.29.1 → arrayview-0.29.2}/vscode-extension/package.json +1 -1
  17. {arrayview-0.29.1 → arrayview-0.29.2}/vscode-extension/test_lifecycle_helpers.js +6 -0
  18. arrayview-0.29.1/src/arrayview/arrayview-opener.vsix +0 -0
  19. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/frontend-designer/SKILL.md +0 -0
  20. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/invocation-consistency/SKILL.md +0 -0
  21. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/modes-consistency/SKILL.md +0 -0
  22. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/SKILL.md +0 -0
  23. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/references/element-attributes.md +0 -0
  24. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/references/playwright-tests.md +0 -0
  25. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/references/request-mocking.md +0 -0
  26. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/references/running-code.md +0 -0
  27. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/references/session-management.md +0 -0
  28. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/references/storage-state.md +0 -0
  29. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/references/test-generation.md +0 -0
  30. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/references/tracing.md +0 -0
  31. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/playwright-cli/references/video-recording.md +0 -0
  32. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/todo-workflow/SKILL.md +0 -0
  33. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/ui-consistency-audit/SKILL.md +0 -0
  34. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/viewer-ui-checklist/SKILL.md +0 -0
  35. {arrayview-0.29.1 → arrayview-0.29.2}/.agents/skills/visual-bug-fixing/SKILL.md +0 -0
  36. {arrayview-0.29.1 → arrayview-0.29.2}/.github/copilot-instructions.md +0 -0
  37. {arrayview-0.29.1 → arrayview-0.29.2}/.github/workflows/docs.yml +0 -0
  38. {arrayview-0.29.1 → arrayview-0.29.2}/.github/workflows/python-publish.yml +0 -0
  39. {arrayview-0.29.1 → arrayview-0.29.2}/.gitignore +0 -0
  40. {arrayview-0.29.1 → arrayview-0.29.2}/.ignore +0 -0
  41. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/AGENTS.md +0 -0
  42. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/ROUTER.md +0 -0
  43. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/SETUP.md +0 -0
  44. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/SYNC.md +0 -0
  45. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/context/architecture.md +0 -0
  46. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/context/conventions.md +0 -0
  47. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/context/decisions.md +0 -0
  48. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/context/frontend.md +0 -0
  49. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/context/project-state.md +0 -0
  50. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/context/render-pipeline.md +0 -0
  51. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/context/setup.md +0 -0
  52. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/context/stack.md +0 -0
  53. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/INDEX.md +0 -0
  54. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/README.md +0 -0
  55. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/add-file-format.md +0 -0
  56. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/add-server-endpoint.md +0 -0
  57. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/animation-verify.md +0 -0
  58. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/debug-render.md +0 -0
  59. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/debug-vscode-extension-python.md +0 -0
  60. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/extend-compare-mode.md +0 -0
  61. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/extract-server-route-module.md +0 -0
  62. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/patterns/frontend-change.md +0 -0
  63. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/setup.sh +0 -0
  64. {arrayview-0.29.1 → arrayview-0.29.2}/.mex/sync.sh +0 -0
  65. {arrayview-0.29.1 → arrayview-0.29.2}/.opencode/opencode.json +0 -0
  66. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T18-46-49-737Z.yml +0 -0
  67. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T18-48-21-979Z.yml +0 -0
  68. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T18-51-35-665Z.yml +0 -0
  69. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T19-07-01-393Z.yml +0 -0
  70. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T19-14-37-969Z.yml +0 -0
  71. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T19-21-30-940Z.yml +0 -0
  72. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T19-23-08-126Z.yml +0 -0
  73. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T19-29-33-155Z.yml +0 -0
  74. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T19-31-25-336Z.yml +0 -0
  75. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T19-31-53-789Z.yml +0 -0
  76. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T19-39-12-257Z.yml +0 -0
  77. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T19-39-16-449Z.yml +0 -0
  78. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T20-15-25-513Z.yml +0 -0
  79. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T20-25-13-179Z.yml +0 -0
  80. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T20-39-01-435Z.yml +0 -0
  81. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-01-27-659Z.yml +0 -0
  82. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-01-41-283Z.yml +0 -0
  83. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-03-00-625Z.yml +0 -0
  84. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-04-12-887Z.yml +0 -0
  85. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-33-39-044Z.yml +0 -0
  86. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-38-01-530Z.yml +0 -0
  87. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-45-20-383Z.yml +0 -0
  88. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-55-11-545Z.yml +0 -0
  89. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-56-03-307Z.yml +0 -0
  90. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-56-35-733Z.yml +0 -0
  91. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-57-12-181Z.yml +0 -0
  92. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-57-37-748Z.yml +0 -0
  93. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T21-58-13-679Z.yml +0 -0
  94. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-06T22-37-23-895Z.yml +0 -0
  95. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T00-39-18-637Z.yml +0 -0
  96. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T01-41-46-243Z.yml +0 -0
  97. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T04-31-48-472Z.yml +0 -0
  98. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-14-15-632Z.yml +0 -0
  99. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-14-47-582Z.yml +0 -0
  100. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-16-23-471Z.yml +0 -0
  101. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-17-10-247Z.yml +0 -0
  102. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-18-24-707Z.yml +0 -0
  103. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-20-06-164Z.yml +0 -0
  104. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-20-28-342Z.yml +0 -0
  105. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-21-54-962Z.yml +0 -0
  106. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-22-34-666Z.yml +0 -0
  107. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-23-11-336Z.yml +0 -0
  108. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-23-36-260Z.yml +0 -0
  109. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-24-09-267Z.yml +0 -0
  110. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-24-35-434Z.yml +0 -0
  111. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-25-57-010Z.yml +0 -0
  112. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-34-48-823Z.yml +0 -0
  113. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-46-46-468Z.yml +0 -0
  114. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-48-17-930Z.yml +0 -0
  115. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-49-26-400Z.yml +0 -0
  116. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-50-31-563Z.yml +0 -0
  117. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/page-2026-05-07T12-56-45-568Z.yml +0 -0
  118. {arrayview-0.29.1 → arrayview-0.29.2}/.playwright-cli/theme-dark.yml +0 -0
  119. {arrayview-0.29.1 → arrayview-0.29.2}/.python-version +0 -0
  120. {arrayview-0.29.1 → arrayview-0.29.2}/.vscode/settings.json +0 -0
  121. {arrayview-0.29.1 → arrayview-0.29.2}/AGENTS.md +0 -0
  122. {arrayview-0.29.1 → arrayview-0.29.2}/CONTRIBUTING.md +0 -0
  123. {arrayview-0.29.1 → arrayview-0.29.2}/DESIGN.md +0 -0
  124. {arrayview-0.29.1 → arrayview-0.29.2}/LICENSE +0 -0
  125. {arrayview-0.29.1 → arrayview-0.29.2}/README.md +0 -0
  126. {arrayview-0.29.1 → arrayview-0.29.2}/docs/comparing.md +0 -0
  127. {arrayview-0.29.1 → arrayview-0.29.2}/docs/configuration.md +0 -0
  128. {arrayview-0.29.1 → arrayview-0.29.2}/docs/display.md +0 -0
  129. {arrayview-0.29.1 → arrayview-0.29.2}/docs/index.md +0 -0
  130. {arrayview-0.29.1 → arrayview-0.29.2}/docs/launch-routing-refactor.md +0 -0
  131. {arrayview-0.29.1 → arrayview-0.29.2}/docs/lifecycle.md +0 -0
  132. {arrayview-0.29.1 → arrayview-0.29.2}/docs/loading.md +0 -0
  133. {arrayview-0.29.1 → arrayview-0.29.2}/docs/logo.png +0 -0
  134. {arrayview-0.29.1 → arrayview-0.29.2}/docs/measurement.md +0 -0
  135. {arrayview-0.29.1 → arrayview-0.29.2}/docs/remote.md +0 -0
  136. {arrayview-0.29.1 → arrayview-0.29.2}/docs/stylesheets/extra.css +0 -0
  137. {arrayview-0.29.1 → arrayview-0.29.2}/docs/viewing.md +0 -0
  138. {arrayview-0.29.1 → arrayview-0.29.2}/docs/vscode-window-routing-plan.md +0 -0
  139. {arrayview-0.29.1 → arrayview-0.29.2}/experiments/ws_tunnel_diag/ws_tunnel_diag.py +0 -0
  140. {arrayview-0.29.1 → arrayview-0.29.2}/matlab/arrayview.m +0 -0
  141. {arrayview-0.29.1 → arrayview-0.29.2}/mkdocs.yml +0 -0
  142. {arrayview-0.29.1 → arrayview-0.29.2}/plans/2026-04-14-immersive-animation.md +0 -0
  143. {arrayview-0.29.1 → arrayview-0.29.2}/plans/2026-05-07-unified-colormap-picker.md +0 -0
  144. {arrayview-0.29.1 → arrayview-0.29.2}/plans/arrayview_tool_menu_fix_prompt.md +0 -0
  145. {arrayview-0.29.1 → arrayview-0.29.2}/plans/arrayview_tool_menu_implementation_plan.md +0 -0
  146. {arrayview-0.29.1 → arrayview-0.29.2}/plans/egg-placement-mockup.html +0 -0
  147. {arrayview-0.29.1 → arrayview-0.29.2}/plans/refactoring_plan.md +0 -0
  148. {arrayview-0.29.1 → arrayview-0.29.2}/plans/roi-analysis-requirements.md +0 -0
  149. {arrayview-0.29.1 → arrayview-0.29.2}/plans/webview/LOG.md +0 -0
  150. {arrayview-0.29.1 → arrayview-0.29.2}/scripts/demo.py +0 -0
  151. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/ARCHITECTURE.md +0 -0
  152. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/__init__.py +0 -0
  153. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/__main__.py +0 -0
  154. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_analysis.py +0 -0
  155. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_app.py +0 -0
  156. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_codex_open.py +0 -0
  157. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_config.py +0 -0
  158. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_diff.py +0 -0
  159. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_icon.png +0 -0
  160. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_imaging.py +0 -0
  161. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_io.py +0 -0
  162. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_launch_plan.py +0 -0
  163. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_lifecycle.py +0 -0
  164. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_overlays.py +0 -0
  165. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_platform.py +0 -0
  166. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_render.py +0 -0
  167. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_analysis.py +0 -0
  168. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_export.py +0 -0
  169. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_loading.py +0 -0
  170. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_persistence.py +0 -0
  171. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_preload.py +0 -0
  172. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_query.py +0 -0
  173. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_rendering.py +0 -0
  174. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_segmentation.py +0 -0
  175. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_state.py +0 -0
  176. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_vectorfield.py +0 -0
  177. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_routes_websocket.py +0 -0
  178. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_segmentation.py +0 -0
  179. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_server.py +0 -0
  180. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_session.py +0 -0
  181. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_shell.html +0 -0
  182. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_synthetic_mri.py +0 -0
  183. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_torch.py +0 -0
  184. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_vectorfield.py +0 -0
  185. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_vscode.py +0 -0
  186. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/_vscode_browser.py +0 -0
  187. {arrayview-0.29.1 → arrayview-0.29.2}/src/arrayview/gsap.min.js +0 -0
  188. {arrayview-0.29.1 → arrayview-0.29.2}/tests/capture_v_animation.py +0 -0
  189. {arrayview-0.29.1 → arrayview-0.29.2}/tests/conftest.py +0 -0
  190. {arrayview-0.29.1 → arrayview-0.29.2}/tests/lifecycle_matrix.py +0 -0
  191. {arrayview-0.29.1 → arrayview-0.29.2}/tests/make_vectorfield_test_arrays.py +0 -0
  192. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_backend_shared.py +0 -0
  193. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_browser.py +0 -0
  194. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_cli.py +0 -0
  195. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_colorbar_hover_highlight.py +0 -0
  196. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_command_reachability.py +0 -0
  197. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_config.py +0 -0
  198. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_cross_mode_parametrized.py +0 -0
  199. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_interactions.py +0 -0
  200. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_io.py +0 -0
  201. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_large_arrays.py +0 -0
  202. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_launch_plan.py +0 -0
  203. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_loading_server.py +0 -0
  204. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_mode_consistency.py +0 -0
  205. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_mode_matrix.py +0 -0
  206. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_mode_roundtrip.py +0 -0
  207. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_nifti_meta.py +0 -0
  208. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_nifti_series.py +0 -0
  209. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_rgb_pixel_art.py +0 -0
  210. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_torch.py +0 -0
  211. {arrayview-0.29.1 → arrayview-0.29.2}/tests/test_view_component_unit.py +0 -0
  212. {arrayview-0.29.1 → arrayview-0.29.2}/tests/ui_audit.py +0 -0
  213. {arrayview-0.29.1 → arrayview-0.29.2}/tests/v_anim_frames/.gitkeep +0 -0
  214. {arrayview-0.29.1 → arrayview-0.29.2}/tests/visual_smoke.py +0 -0
  215. {arrayview-0.29.1 → arrayview-0.29.2}/vscode-extension/AGENTS.md +0 -0
  216. {arrayview-0.29.1 → arrayview-0.29.2}/vscode-extension/LICENSE +0 -0
@@ -60,7 +60,7 @@ This contract describes who owns the backend, when it starts, and what closes it
60
60
 
61
61
  - Remote or tunnel launches may persist when `--serve` or tunnel display ownership requires it.
62
62
  - VS Code tunnel display uses forwarded localhost URLs; the extension should configure the port, promote privacy when available, and resolve the URL with `asExternalUri`.
63
- - With multiple registered tunnel windows, a missing or stale `ARRAYVIEW_WINDOW_ID` must fail closed with a diagnostic rather than broadcasting to whichever window is focused.
63
+ - With multiple registered tunnel windows, a missing or stale `ARRAYVIEW_WINDOW_ID` uses the shared broadcast signal; only the focused VS Code window may claim it.
64
64
  - An exact registered `ARRAYVIEW_WINDOW_ID` wins; do not redirect it to a newer same-parent registration because live tunnel windows can share ancestry.
65
65
  - Plain SSH should use `localhost` forwarding guidance and stay transient unless a shared server was explicitly requested.
66
66
 
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: arrayview
3
- Version: 0.29.1
3
+ Version: 0.29.2
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.29.1"
7
+ version = "0.29.2"
8
8
  description = "Fast multi-dimensional array viewer"
9
9
  readme = { file = "README.md", content-type = "text/markdown" }
10
10
  requires-python = ">=3.12"
@@ -8,7 +8,7 @@ set -euo pipefail
8
8
  BUMP="minor"
9
9
  DRY_RUN=true
10
10
  NO_AI=false
11
- AI_TOOL="claude"
11
+ AI_TOOL="codex"
12
12
 
13
13
  usage() {
14
14
  cat <<EOF
@@ -16,7 +16,7 @@ Usage: $(basename "$0") [OPTIONS]
16
16
 
17
17
  Options:
18
18
  --bump {major,minor,patch} Version bump type (default: minor)
19
- --ai {claude,codex} AI tool for release notes (default: claude)
19
+ --ai {claude,codex} AI tool for release notes (default: codex)
20
20
  --execute Actually run (default is dry-run)
21
21
  --no-ai Skip AI release notes, use GitHub's --generate-notes
22
22
  -h, --help Show this help
@@ -116,9 +116,13 @@ Rules:
116
116
  if command -v "$CODEX_BIN" &>/dev/null; then
117
117
  echo "Generating release notes with Codex..."
118
118
  tmpfile=$(mktemp)
119
- trap 'rm -f "$tmpfile"' EXIT
120
- if printf '%s\n' "$PROMPT" | "$CODEX_BIN" exec --output-last-message "$tmpfile" - >/dev/null 2>/dev/null; then
119
+ errfile=$(mktemp)
120
+ trap 'rm -f "$tmpfile" "$errfile"' EXIT
121
+ if printf '%s\n' "$PROMPT" | "$CODEX_BIN" exec --output-last-message "$tmpfile" - >/dev/null 2>"$errfile"; then
121
122
  NOTES=$(<"$tmpfile")
123
+ elif [[ -s "$errfile" ]]; then
124
+ echo "Codex release notes failed:"
125
+ sed 's/^/ /' "$errfile"
122
126
  fi
123
127
  fi
124
128
  ;;
@@ -127,7 +131,19 @@ fi
127
131
 
128
132
  if [[ -z "$NOTES" ]]; then
129
133
  if [[ "$NO_AI" == false ]]; then
130
- echo "AI notes unavailable, falling back to --generate-notes"
134
+ echo "AI notes unavailable, falling back to commit-based notes"
135
+ fi
136
+ NOTES="## What's new in $TAG"
137
+ if [[ -n "$COMMITS" ]]; then
138
+ while IFS= read -r commit; do
139
+ [[ -z "$commit" ]] && continue
140
+ message="${commit#* }"
141
+ NOTES+=$'\n'
142
+ NOTES+="- ${message}"
143
+ done <<< "$COMMITS"
144
+ else
145
+ NOTES+=$'\n'
146
+ NOTES+="- Maintenance release."
131
147
  fi
132
148
  fi
133
149
 
@@ -146,17 +162,10 @@ run git push origin main
146
162
  run git tag "$TAG"
147
163
  run git push origin "$TAG"
148
164
 
149
- if [[ -n "$NOTES" ]]; then
150
- run gh release create "$TAG" \
151
- --title "$TAG" \
152
- --notes "$NOTES" \
153
- --prerelease
154
- else
155
- run gh release create "$TAG" \
156
- --title "$TAG" \
157
- --generate-notes \
158
- --prerelease
159
- fi
165
+ run gh release create "$TAG" \
166
+ --title "$TAG" \
167
+ --notes "$NOTES" \
168
+ --prerelease
160
169
 
161
170
  if [[ "$DRY_RUN" == true ]]; then
162
171
  echo ""
@@ -677,9 +677,7 @@ def _activate_early_cli_native_shell(
677
677
  notified = bool(notify_result.get("notified"))
678
678
  except Exception:
679
679
  notified = False
680
- if notified and _wait_for_native_shell_or_viewer_connection(
681
- port, viewer_before=viewer_before
682
- ):
680
+ if notified and _wait_for_viewer_connection(port, before=viewer_before):
683
681
  return True
684
682
  _vprint(
685
683
  "[ArrayView] Native window did not connect to the backend; falling back to browser",
@@ -1216,8 +1214,12 @@ def _handle_cli_spawned_daemon(
1216
1214
  and not is_remote
1217
1215
  and not overlay_files
1218
1216
  and not compare_files
1217
+ and not sys.platform.startswith("linux")
1219
1218
  ):
1220
- url_shell_early = _shell_url(port, sid, name)
1219
+ # Linux QtWebEngine denies sessionStorage to the viewer iframe when its
1220
+ # parent shell was created from inline data. Let Linux open the normal
1221
+ # HTTP shell after the daemon is listening instead.
1222
+ url_shell_early = f"http://{_LOOPBACK_HOST}:{port}/shell"
1221
1223
  early_native_shell_opened, early_native_shell_proc = _open_webview_cli_tracked(
1222
1224
  url_shell_early, 1400, 900, shell_port=port
1223
1225
  )
@@ -439,7 +439,6 @@
439
439
  #mv-cb { border: none; }
440
440
  .highlight { color: var(--highlight); font-weight: bold; }
441
441
  .dim-size { opacity: 0.28; font-size: 0.68em; font-weight: normal; }
442
- .dim-hover-idx { font-size: 0.68em; font-weight: normal; color: var(--dim-hover, #777); opacity: 1; }
443
442
  .active-dim { font-weight: bold; }
444
443
  .playing-dim { font-weight: bold; }
445
444
  .vfield-t-dim { color: var(--vfield-t-dim); }
@@ -3338,6 +3337,7 @@
3338
3337
  #mv-panes.mv-crosshair-active .mv-pane::after {
3339
3338
  content: ''; position: absolute; inset: 0; z-index: 4;
3340
3339
  box-shadow: inset 0 0 0 var(--mv-plane-border-width, 1px) var(--mv-plane-border, var(--active-dim));
3340
+ opacity: 0.45;
3341
3341
  border-radius: inherit; box-sizing: border-box;
3342
3342
  pointer-events: none;
3343
3343
  }
@@ -13885,7 +13885,6 @@
13885
13885
  if (!['circle', 'rect', 'freehand', 'floodfill'].includes(_roiShape)) _roiShape = 'circle';
13886
13886
  _roiVisible = true;
13887
13887
  hideAllPixelInfo();
13888
- _resetDimbarHover();
13889
13888
  canvas.style.cursor = 'crosshair';
13890
13889
  if (qmriActive) qmriViews.forEach(v => { v.canvas.style.cursor = 'crosshair'; });
13891
13890
  if (multiViewActive) mvViews.forEach(v => { v.canvas.style.cursor = 'crosshair'; });
@@ -16481,38 +16480,6 @@
16481
16480
  _updateMvPaneFill();
16482
16481
  }
16483
16482
 
16484
- function _dimHoverPadded(val, maxVal) {
16485
- const maxStr = String(maxVal);
16486
- const valStr = String(val);
16487
- const padLen = maxStr.length - valStr.length;
16488
- if (padLen <= 0) return '/' + valStr;
16489
- return '/<span style="opacity:0.28">' + '0'.repeat(padLen) + '</span>' + valStr;
16490
- }
16491
- function _updateDimbarHover(px, py, xLabel, yLabel, pz) {
16492
- // xLabel/yLabel: 'x','y','z' — which dimbar label to update (default x,y)
16493
- // pz: optional z index (for mosaic mode)
16494
- const xl = xLabel || 'x', yl = yLabel || 'y';
16495
- const xEl = document.getElementById('dim-' + xl + '-size');
16496
- const yEl = document.getElementById('dim-' + yl + '-size');
16497
- const zEl = document.getElementById('dim-z-size');
16498
- // Resolve label to shape dim index for padding width
16499
- const labelToDim = (multiViewActive || compareMvActive)
16500
- ? { x: (multiViewActive ? mvDims : compareMvDims)[0], y: (multiViewActive ? mvDims : compareMvDims)[1], z: (multiViewActive ? mvDims : compareMvDims)[2] }
16501
- : { x: dim_x, y: dim_y, z: dim_z };
16502
- const xMax = labelToDim[xl] >= 0 ? shape[labelToDim[xl]] : 1;
16503
- const yMax = labelToDim[yl] >= 0 ? shape[labelToDim[yl]] : 1;
16504
- if (xEl) { xEl.innerHTML = _dimHoverPadded(px, xMax); xEl.className = 'dim-hover-idx'; }
16505
- if (yEl) { yEl.innerHTML = _dimHoverPadded(py, yMax); yEl.className = 'dim-hover-idx'; }
16506
- if (zEl && pz != null) {
16507
- const zMax = dim_z >= 0 ? shape[dim_z] : 1;
16508
- zEl.innerHTML = _dimHoverPadded(pz + 1, zMax); zEl.className = 'dim-hover-idx';
16509
- }
16510
- }
16511
- function _resetDimbarHover() {
16512
- // Re-render dimbar to restore correct max sizes for all modes
16513
- renderInfo();
16514
- }
16515
-
16516
16483
  // Gnomon visibility: shown while a dim-bar token is hovered, or flashed
16517
16484
  // briefly when activeDim changes via h/l/←/→. Hidden otherwise.
16518
16485
  // In oblique mode the x/y labels no longer name axis-aligned directions,
@@ -17711,13 +17678,18 @@
17711
17678
 
17712
17679
  function saveState() {
17713
17680
  if (!sid) return;
17714
- sessionStorage.setItem('av_' + sid, JSON.stringify(collectStateSnapshot()));
17681
+ try {
17682
+ sessionStorage.setItem('av_' + sid, JSON.stringify(collectStateSnapshot()));
17683
+ } catch (e) {
17684
+ // Some embedded webviews deny storage access. Viewing must keep
17685
+ // working even when this optional convenience is unavailable.
17686
+ }
17715
17687
  }
17716
17688
 
17717
17689
  function restoreStateFromSessionStorage() {
17718
- const raw = sessionStorage.getItem('av_' + sid);
17719
- if (!raw) return false;
17720
17690
  try {
17691
+ const raw = sessionStorage.getItem('av_' + sid);
17692
+ if (!raw) return false;
17721
17693
  return applyStateSnapshot(JSON.parse(raw));
17722
17694
  } catch (e) {
17723
17695
  return false;
@@ -20117,7 +20089,6 @@
20117
20089
  showPixelInfo(document.getElementById('main-pixel-info'), { valueText: _pixelFmt(d.value, _activeNumericContext()), rawX, rawY }, e.clientX, e.clientY);
20118
20090
  }).catch(() => {});
20119
20091
  }
20120
- _updateDimbarHover(localX, localY, null, null, zIdx);
20121
20092
  return;
20122
20093
  }
20123
20094
 
@@ -20131,11 +20102,6 @@
20131
20102
  imgW: canvas.width,
20132
20103
  imgH: canvas.height,
20133
20104
  displayEl: document.getElementById('main-pixel-info'),
20134
- onResult: (d, px, py) => {
20135
- if (_pixelInfoActive() && !multiViewActive && !compareActive) {
20136
- _updateDimbarHover(px, py);
20137
- }
20138
- },
20139
20105
  });
20140
20106
  });
20141
20107
  canvas.addEventListener('mouseleave', () => {
@@ -20154,8 +20120,7 @@
20154
20120
  if (_pixelInfoHeld) {
20155
20121
  _pixelInfoHeld = false;
20156
20122
  if (!_pixelInfoVisible) {
20157
- const piEl = document.getElementById('main-pixel-info');
20158
- if (piEl) piEl.style.display = 'none';
20123
+ hideAllPixelInfo();
20159
20124
  }
20160
20125
  }
20161
20126
  });
@@ -20924,7 +20889,6 @@
20924
20889
  if (!_pixelInfoVisible) {
20925
20890
  _clearHoverPins();
20926
20891
  hideAllPixelInfo();
20927
- _resetDimbarHover();
20928
20892
  const axEl = document.getElementById('main-axes-svg');
20929
20893
  if (axEl) {
20930
20894
  axEl.querySelector('.axes-lbl-x').textContent = 'x';
@@ -26047,21 +26011,6 @@
26047
26011
  imgH: view.lastH,
26048
26012
  view,
26049
26013
  displayEl: view.pixelInfoEl,
26050
- onResult: (d, px, py) => {
26051
- if (_pixelInfoActive()) {
26052
- // Map pane dims to dimbar x/y/z labels
26053
- const labels = ['x','y','z'];
26054
- const activeDims = multiViewActive ? mvDims : (compareMvActive ? compareMvDims : []);
26055
- const posX = activeDims.indexOf(view.dimX);
26056
- const posY = activeDims.indexOf(view.dimY);
26057
- const lblX = labels[posX] || 'x';
26058
- const lblY = labels[posY] || 'y';
26059
- const elX = document.getElementById('dim-' + lblX + '-size');
26060
- const elY = document.getElementById('dim-' + lblY + '-size');
26061
- if (elX) { elX.textContent = '/' + px; elX.className = 'dim-hover-idx'; }
26062
- if (elY) { elY.textContent = '/' + py; elY.className = 'dim-hover-idx'; }
26063
- }
26064
- },
26065
26014
  });
26066
26015
  });
26067
26016
  cv.addEventListener('mouseleave', () => {
@@ -26072,7 +26021,6 @@
26072
26021
  if (tq) tq.classList.remove('visible');
26073
26022
  _roiSuppressPixelInfo = false;
26074
26023
  if (!mvDraggingView && view.pixelInfoEl) view.pixelInfoEl.style.display = 'none';
26075
- if (_pixelInfoActive()) _resetDimbarHover();
26076
26024
  });
26077
26025
  // Click-to-copy: multiview canvases
26078
26026
  let _mvClickDown = null;
@@ -26935,7 +26883,6 @@
26935
26883
  view,
26936
26884
  onResult: (d, px, py) => {
26937
26885
  if (_pixelInfoActive()) {
26938
- _updateDimbarHover(px, py);
26939
26886
  return; // hover mode: only show hovered pane's value
26940
26887
  }
26941
26888
  // Non-hover: fetch for all other qmri views at same pixel
@@ -26962,10 +26909,9 @@
26962
26909
  });
26963
26910
  });
26964
26911
  cv.addEventListener('mouseleave', () => {
26965
- if (_qmriLoupeDrag && _qmriLoupeDrag.canvas === cv) _endQmriLoupe();
26966
- qmriViews.forEach(v => { if (v.pixelInfoEl) v.pixelInfoEl.style.display = 'none'; });
26967
- if (_pixelInfoActive()) _resetDimbarHover();
26968
- });
26912
+ if (_qmriLoupeDrag && _qmriLoupeDrag.canvas === cv) _endQmriLoupe();
26913
+ qmriViews.forEach(v => { if (v.pixelInfoEl) v.pixelInfoEl.style.display = 'none'; });
26914
+ });
26969
26915
  // ROI drawing on qMRI canvases
26970
26916
  if (rectRoiMode) cv.style.cursor = 'crosshair';
26971
26917
  cv.addEventListener('mousemove', (e) => {
@@ -27029,6 +26975,11 @@
27029
26975
  }
27030
26976
  });
27031
26977
  cv.addEventListener('mousedown', (e) => {
26978
+ if (!rectRoiMode && e.button === 0 && !e.shiftKey && !e.metaKey && !e.ctrlKey && !e.altKey
26979
+ && !_cropActive && !_segMode && !_rulerMode) {
26980
+ _pixelInfoHeld = true;
26981
+ cv.dispatchEvent(new MouseEvent('mousemove', { clientX: e.clientX, clientY: e.clientY, bubbles: true }));
26982
+ }
27032
26983
  if (!rectRoiMode || e.button !== 0 || e.shiftKey || e.metaKey) return;
27033
26984
  e.preventDefault(); sink.focus();
27034
26985
  const rect = cv.getBoundingClientRect();
@@ -27449,15 +27400,17 @@
27449
27400
  view,
27450
27401
  displayEl: view.pixelInfoEl,
27451
27402
  qmriRole: view.qmriRole,
27452
- onResult: (d, px, py) => {
27453
- if (_pixelInfoActive()) _updateDimbarHover(px, py);
27454
- },
27455
27403
  });
27456
27404
  });
27405
+ cv.addEventListener('mousedown', (e) => {
27406
+ if (e.button !== 0 || e.shiftKey || e.metaKey || e.ctrlKey || e.altKey
27407
+ || _cropActive || _segMode || _rulerMode) return;
27408
+ _pixelInfoHeld = true;
27409
+ cv.dispatchEvent(new MouseEvent('mousemove', { clientX: e.clientX, clientY: e.clientY, bubbles: true }));
27410
+ });
27457
27411
  cv.addEventListener('mouseleave', () => {
27458
27412
  if (_qmriLoupeDrag && _qmriLoupeDrag.canvas === cv) _endQmriLoupe();
27459
27413
  compareQmriViews.forEach(v => { if (v.pixelInfoEl) v.pixelInfoEl.style.display = 'none'; });
27460
- if (_pixelInfoActive()) _resetDimbarHover();
27461
27414
  });
27462
27415
  if (!httpTransport) {
27463
27416
  view.ws = createTransport(sidVal,
@@ -27936,20 +27889,6 @@
27936
27889
  imgH: view.lastH,
27937
27890
  view,
27938
27891
  displayEl: view.pixelInfoEl,
27939
- onResult: (d, px, py) => {
27940
- if (_pixelInfoActive()) {
27941
- const labels = ['x','y','z'];
27942
- const activeDims = compareMvDims || [];
27943
- const posX = activeDims.indexOf(view.dimX);
27944
- const posY = activeDims.indexOf(view.dimY);
27945
- const lblX = labels[posX] || 'x';
27946
- const lblY = labels[posY] || 'y';
27947
- const elX = document.getElementById('dim-' + lblX + '-size');
27948
- const elY = document.getElementById('dim-' + lblY + '-size');
27949
- if (elX) { elX.textContent = '/' + px; elX.className = 'dim-hover-idx'; }
27950
- if (elY) { elY.textContent = '/' + py; elY.className = 'dim-hover-idx'; }
27951
- }
27952
- },
27953
27892
  });
27954
27893
  });
27955
27894
  cv.addEventListener('mouseleave', () => {
@@ -27957,7 +27896,6 @@
27957
27896
  if (!cmvDraggingView) {
27958
27897
  compareMvViews.forEach(v => { if (v.pixelInfoEl) v.pixelInfoEl.style.display = 'none'; });
27959
27898
  }
27960
- if (_pixelInfoActive()) _resetDimbarHover();
27961
27899
  });
27962
27900
  // Click-to-copy: compare-multiview canvases
27963
27901
  let _cmvClickDown = null;
@@ -30116,7 +30054,6 @@
30116
30054
  displayEl: piEl,
30117
30055
  onResult: (d, px, py) => {
30118
30056
  if (!_pixelInfoActive()) return;
30119
- _updateDimbarHover(px, py);
30120
30057
  // Show value on other compare panes at the same pixel
30121
30058
  const ver = indices.join(',') + ':' + dim_x + ':' + dim_y + ':' + complexMode + ':' + logScale;
30122
30059
  compareCanvases.forEach((ocv, j) => {
@@ -30145,7 +30082,6 @@
30145
30082
  cv.addEventListener('mouseleave', () => {
30146
30083
  _comparePiEls.forEach(p => { if (p) p.style.display = 'none'; });
30147
30084
  hideCompareCrosshairs();
30148
- if (_pixelInfoActive()) _resetDimbarHover();
30149
30085
  });
30150
30086
  });
30151
30087
  // Click-to-copy: compare canvases
@@ -44,7 +44,7 @@ def _vscode_app_bundle() -> str | None:
44
44
 
45
45
  _VSCODE_EXT_INSTALLED = False # cached so we only check once per process
46
46
  _VSCODE_EXT_FRESH_INSTALL = False # True if we just installed it this session
47
- _VSCODE_EXT_VERSION = "0.14.34" # current bundled extension version
47
+ _VSCODE_EXT_VERSION = "0.14.35" # current bundled extension version
48
48
 
49
49
  def _bundled_vscode_vsix_version(vsix_path: str) -> str | None:
50
50
  """Return the bundled opener extension version recorded inside the VSIX."""
@@ -323,6 +323,38 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
323
323
  data.setdefault("maxAgeMs", _VSCODE_SIGNAL_MAX_AGE_MS)
324
324
  data.setdefault("requestId", uuid.uuid4().hex)
325
325
 
326
+ def _focused_window_fallback() -> tuple[str, ...]:
327
+ """Let the focused extension window claim an untargeted request."""
328
+ data["broadcast"] = True
329
+ return (
330
+ (_VSCODE_SIGNAL_FILENAME,)
331
+ if skip_compat
332
+ else (_VSCODE_SIGNAL_FILENAME, *_VSCODE_COMPAT_SIGNAL_FILENAMES)
333
+ )
334
+
335
+ def _registrations_are_remote(window_files: list[str]) -> bool:
336
+ """Return whether every registration belongs to a remote extension host."""
337
+ if not window_files:
338
+ return False
339
+ for window_file in window_files:
340
+ try:
341
+ with open(os.path.join(signal_dir, window_file)) as registration:
342
+ registration_data = json.load(registration)
343
+ remote_name = registration_data.get("remoteName")
344
+ if remote_name:
345
+ continue
346
+ pid = int(registration_data["pid"])
347
+ with open(f"/proc/{pid}/cmdline", "rb") as command_file:
348
+ command = command_file.read().replace(b"\0", b" ").decode()
349
+ if (
350
+ "/.vscode/cli/servers/" not in command
351
+ and "/.vscode-server/" not in command
352
+ ):
353
+ return False
354
+ except Exception:
355
+ return False
356
+ return True
357
+
326
358
  # --- Primary: ARRAYVIEW_WINDOW_ID (all platforms) ---
327
359
  # The extension injects this env var into every terminal via
328
360
  # EnvironmentVariableCollection. It uniquely identifies the VS Code
@@ -381,15 +413,25 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
381
413
  flush=True,
382
414
  )
383
415
  elif len(_all_windows) > 1:
384
- print(
385
- "[ArrayView] VS Code window is ambiguous: "
386
- f"ARRAYVIEW_WINDOW_ID={env_wid!r} is not registered "
387
- f"and {len(_all_windows)} VS Code windows are active. "
388
- "Open a fresh terminal in the target VS Code window "
389
- "and run ArrayView again.",
390
- flush=True,
391
- )
392
- return False
416
+ if _registrations_are_remote(_all_windows):
417
+ filenames = _focused_window_fallback()
418
+ targeted_via_env = True
419
+ _vprint(
420
+ "[ArrayView] signal: stale ARRAYVIEW_WINDOW_ID with "
421
+ f"{len(_all_windows)} remote windows; "
422
+ "using focused-window fallback",
423
+ flush=True,
424
+ )
425
+ else:
426
+ print(
427
+ "[ArrayView] VS Code window is ambiguous: "
428
+ f"ARRAYVIEW_WINDOW_ID={env_wid!r} is not registered "
429
+ f"and {len(_all_windows)} VS Code windows are active. "
430
+ "Open a fresh terminal in the target VS Code window "
431
+ "and run ArrayView again.",
432
+ flush=True,
433
+ )
434
+ return False
393
435
  # else: 0 windows — fall through to subsequent targeting
394
436
 
395
437
  if targeted_via_env:
@@ -444,16 +486,30 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
444
486
  except Exception:
445
487
  pass
446
488
  if len(_wfiles) > 1:
447
- print(
448
- "[ArrayView] VS Code window is ambiguous: "
449
- "no exact ARRAYVIEW_WINDOW_ID or PID match is available "
450
- f"and {len(_wfiles)} VS Code windows are active. "
451
- "Open a fresh terminal in the target VS Code window "
452
- "and run ArrayView again.",
453
- flush=True,
454
- )
455
- return False
456
- filenames = tuple(_wfiles) if _wfiles else (_VSCODE_SIGNAL_FILENAME,)
489
+ _window_files = [
490
+ f"window-{name.split('-', 3)[-1][:-5]}.json"
491
+ for name in _wfiles
492
+ ]
493
+ if _registrations_are_remote(_window_files):
494
+ filenames = _focused_window_fallback()
495
+ _vprint(
496
+ "[ArrayView] signal: no exact match for local "
497
+ f"terminal with {len(_wfiles)} remote windows; "
498
+ "using focused-window fallback",
499
+ flush=True,
500
+ )
501
+ else:
502
+ print(
503
+ "[ArrayView] VS Code window is ambiguous: "
504
+ "no exact ARRAYVIEW_WINDOW_ID or PID match is available "
505
+ f"and {len(_wfiles)} VS Code windows are active. "
506
+ "Open a fresh terminal in the target VS Code window "
507
+ "and run ArrayView again.",
508
+ flush=True,
509
+ )
510
+ return False
511
+ else:
512
+ filenames = tuple(_wfiles) if _wfiles else (_VSCODE_SIGNAL_FILENAME,)
457
513
  else:
458
514
  filenames = (
459
515
  _VSCODE_SIGNAL_FILENAME,
@@ -494,18 +550,33 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
494
550
  except Exception:
495
551
  pass
496
552
  if len(window_files) > 1:
497
- print(
498
- "[ArrayView] VS Code window is ambiguous: "
499
- "no exact ARRAYVIEW_WINDOW_ID or PID match is available "
500
- f"and {len(window_files)} VS Code windows are active. "
501
- "Open a fresh terminal in the target VS Code window "
502
- "and run ArrayView again.",
503
- flush=True,
553
+ _registration_files = [
554
+ fname
555
+ for fname in os.listdir(signal_dir_temp)
556
+ if fname.startswith("window-") and fname.endswith(".json")
557
+ ]
558
+ if _registrations_are_remote(_registration_files):
559
+ filenames = _focused_window_fallback()
560
+ _vprint(
561
+ "[ArrayView] signal: local terminal has no exact "
562
+ f"match among {len(window_files)} remote windows; "
563
+ "using focused-window fallback",
564
+ flush=True,
565
+ )
566
+ else:
567
+ print(
568
+ "[ArrayView] VS Code window is ambiguous: "
569
+ "no exact ARRAYVIEW_WINDOW_ID or PID match is available "
570
+ f"and {len(window_files)} VS Code windows are active. "
571
+ "Open a fresh terminal in the target VS Code window "
572
+ "and run ArrayView again.",
573
+ flush=True,
574
+ )
575
+ return False
576
+ else:
577
+ filenames = (
578
+ tuple(window_files) if window_files else (_VSCODE_SIGNAL_FILENAME,)
504
579
  )
505
- return False
506
- filenames = (
507
- tuple(window_files) if window_files else (_VSCODE_SIGNAL_FILENAME,)
508
- )
509
580
  else:
510
581
  # Not in VS Code terminal: use shared file
511
582
  filenames = (
@@ -568,15 +639,13 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
568
639
  flush=True,
569
640
  )
570
641
  elif len(_all_windows_r) > 1:
571
- print(
572
- "[ArrayView] VS Code tunnel window is ambiguous: "
573
- f"ARRAYVIEW_WINDOW_ID={env_wid!r} is not registered "
574
- f"and {len(_all_windows_r)} VS Code windows are active. "
575
- "Open a fresh terminal in the target VS Code window "
576
- "and run ArrayView again.",
642
+ filenames = _focused_window_fallback()
643
+ _vprint(
644
+ "[ArrayView] signal: stale remote ARRAYVIEW_WINDOW_ID with "
645
+ f"{len(_all_windows_r)} registered windows; "
646
+ "using focused-window fallback",
577
647
  flush=True,
578
648
  )
579
- return False
580
649
  else:
581
650
  env_wid = None # 0 windows: keep existing fallback
582
651
 
@@ -603,14 +672,13 @@ def _write_vscode_signal(payload: dict, delay: float = 0.0, skip_compat: bool =
603
672
  flush=True,
604
673
  )
605
674
  elif len(_all_windows_r) > 1:
606
- print(
607
- "[ArrayView] VS Code tunnel window is ambiguous: "
608
- "ARRAYVIEW_WINDOW_ID is not available and multiple "
609
- "VS Code windows are active. Open a fresh terminal in "
610
- "the target VS Code window and run ArrayView again.",
675
+ filenames = _focused_window_fallback()
676
+ _vprint(
677
+ "[ArrayView] signal: remote window ID unavailable with "
678
+ f"{len(_all_windows_r)} registered windows; "
679
+ "using focused-window fallback",
611
680
  flush=True,
612
681
  )
613
- return False
614
682
  else:
615
683
  _vprint(f"[ArrayView] signal: no registered remote window, using shared fallback", flush=True)
616
684
  filenames = (