arrayview 0.27.0__tar.gz → 0.28.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (223) hide show
  1. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/invocation-consistency/SKILL.md +8 -8
  2. arrayview-0.28.0/.mex/ROUTER.md +62 -0
  3. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/context/architecture.md +8 -11
  4. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/context/conventions.md +4 -4
  5. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/context/decisions.md +5 -6
  6. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/context/frontend.md +3 -3
  7. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/context/lifecycle.md +3 -11
  8. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/context/project-state.md +6 -5
  9. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/context/render-pipeline.md +22 -17
  10. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/context/setup.md +3 -3
  11. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/patterns/INDEX.md +1 -1
  12. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/patterns/add-file-format.md +5 -7
  13. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/patterns/add-server-endpoint.md +1 -8
  14. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/patterns/animation-verify.md +4 -5
  15. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/patterns/debug-render.md +3 -3
  16. arrayview-0.28.0/.mex/patterns/debug-vscode-extension-python.md +75 -0
  17. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/patterns/extend-compare-mode.md +1 -1
  18. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/patterns/frontend-change.md +3 -3
  19. {arrayview-0.27.0 → arrayview-0.28.0}/PKG-INFO +1 -1
  20. {arrayview-0.27.0 → arrayview-0.28.0}/docs/launch-routing-refactor.md +20 -34
  21. {arrayview-0.27.0 → arrayview-0.28.0}/docs/loading.md +36 -0
  22. {arrayview-0.27.0 → arrayview-0.28.0}/docs/remote.md +12 -16
  23. {arrayview-0.27.0 → arrayview-0.28.0}/docs/viewing.md +4 -0
  24. arrayview-0.28.0/experiments/ws_tunnel_diag/ws_tunnel_diag.py +277 -0
  25. {arrayview-0.27.0 → arrayview-0.28.0}/plans/refactoring_plan.md +3 -0
  26. {arrayview-0.27.0 → arrayview-0.28.0}/plans/webview/LOG.md +4 -0
  27. {arrayview-0.27.0 → arrayview-0.28.0}/pyproject.toml +1 -1
  28. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/ARCHITECTURE.md +6 -12
  29. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/__init__.py +2 -1
  30. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_analysis.py +1 -1
  31. arrayview-0.28.0/src/arrayview/_io.py +763 -0
  32. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_launch_plan.py +0 -4
  33. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_launcher.py +230 -331
  34. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_loading.py +2 -0
  35. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_rendering.py +3 -0
  36. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_websocket.py +3 -0
  37. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_synthetic_mri.py +18 -1
  38. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_viewer.html +1141 -320
  39. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_vscode.py +0 -3
  40. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_vscode_browser.py +24 -27
  41. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_vscode_extension.py +8 -1
  42. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_vscode_signal.py +127 -72
  43. arrayview-0.28.0/src/arrayview/arrayview-opener.vsix +0 -0
  44. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_api.py +196 -52
  45. arrayview-0.28.0/tests/test_io.py +53 -0
  46. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_launch_plan.py +0 -4
  47. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_lifecycle_contract.py +2 -49
  48. arrayview-0.28.0/tests/test_nifti_series.py +263 -0
  49. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_view_component_integration.py +320 -0
  50. {arrayview-0.27.0 → arrayview-0.28.0}/tests/visual_smoke.py +11 -1
  51. {arrayview-0.27.0 → arrayview-0.28.0}/uv.lock +1 -1
  52. {arrayview-0.27.0 → arrayview-0.28.0}/vscode-extension/AGENTS.md +5 -8
  53. {arrayview-0.27.0 → arrayview-0.28.0}/vscode-extension/extension.js +348 -462
  54. {arrayview-0.27.0 → arrayview-0.28.0}/vscode-extension/package.json +1 -1
  55. arrayview-0.27.0/.mex/ROUTER.md +0 -113
  56. arrayview-0.27.0/.mex/patterns/debug-vscode-extension-python.md +0 -95
  57. arrayview-0.27.0/src/arrayview/_io.py +0 -336
  58. arrayview-0.27.0/src/arrayview/_stdio_server.py +0 -961
  59. arrayview-0.27.0/src/arrayview/_vscode_shm.py +0 -77
  60. arrayview-0.27.0/src/arrayview/arrayview-opener.vsix +0 -0
  61. arrayview-0.27.0/tests/test_io.py +0 -139
  62. arrayview-0.27.0/tests/test_mode_entry_batching.py +0 -63
  63. arrayview-0.27.0/tests/test_stdio_server.py +0 -13
  64. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/frontend-designer/SKILL.md +0 -0
  65. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/modes-consistency/SKILL.md +0 -0
  66. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/SKILL.md +0 -0
  67. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/references/element-attributes.md +0 -0
  68. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/references/playwright-tests.md +0 -0
  69. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/references/request-mocking.md +0 -0
  70. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/references/running-code.md +0 -0
  71. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/references/session-management.md +0 -0
  72. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/references/storage-state.md +0 -0
  73. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/references/test-generation.md +0 -0
  74. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/references/tracing.md +0 -0
  75. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/playwright-cli/references/video-recording.md +0 -0
  76. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/todo-workflow/SKILL.md +0 -0
  77. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/ui-consistency-audit/SKILL.md +0 -0
  78. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/viewer-ui-checklist/SKILL.md +0 -0
  79. {arrayview-0.27.0 → arrayview-0.28.0}/.agents/skills/visual-bug-fixing/SKILL.md +0 -0
  80. {arrayview-0.27.0 → arrayview-0.28.0}/.github/copilot-instructions.md +0 -0
  81. {arrayview-0.27.0 → arrayview-0.28.0}/.github/workflows/docs.yml +0 -0
  82. {arrayview-0.27.0 → arrayview-0.28.0}/.github/workflows/python-publish.yml +0 -0
  83. {arrayview-0.27.0 → arrayview-0.28.0}/.gitignore +0 -0
  84. {arrayview-0.27.0 → arrayview-0.28.0}/.ignore +0 -0
  85. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/AGENTS.md +0 -0
  86. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/SETUP.md +0 -0
  87. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/SYNC.md +0 -0
  88. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/context/stack.md +0 -0
  89. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/patterns/README.md +0 -0
  90. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/patterns/extract-server-route-module.md +0 -0
  91. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/setup.sh +0 -0
  92. {arrayview-0.27.0 → arrayview-0.28.0}/.mex/sync.sh +0 -0
  93. {arrayview-0.27.0 → arrayview-0.28.0}/.opencode/opencode.json +0 -0
  94. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T18-46-49-737Z.yml +0 -0
  95. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T18-48-21-979Z.yml +0 -0
  96. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T18-51-35-665Z.yml +0 -0
  97. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T19-07-01-393Z.yml +0 -0
  98. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T19-14-37-969Z.yml +0 -0
  99. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T19-21-30-940Z.yml +0 -0
  100. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T19-23-08-126Z.yml +0 -0
  101. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T19-29-33-155Z.yml +0 -0
  102. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T19-31-25-336Z.yml +0 -0
  103. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T19-31-53-789Z.yml +0 -0
  104. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T19-39-12-257Z.yml +0 -0
  105. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T19-39-16-449Z.yml +0 -0
  106. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T20-15-25-513Z.yml +0 -0
  107. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T20-25-13-179Z.yml +0 -0
  108. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T20-39-01-435Z.yml +0 -0
  109. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-01-27-659Z.yml +0 -0
  110. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-01-41-283Z.yml +0 -0
  111. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-03-00-625Z.yml +0 -0
  112. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-04-12-887Z.yml +0 -0
  113. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-33-39-044Z.yml +0 -0
  114. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-38-01-530Z.yml +0 -0
  115. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-45-20-383Z.yml +0 -0
  116. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-55-11-545Z.yml +0 -0
  117. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-56-03-307Z.yml +0 -0
  118. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-56-35-733Z.yml +0 -0
  119. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-57-12-181Z.yml +0 -0
  120. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-57-37-748Z.yml +0 -0
  121. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T21-58-13-679Z.yml +0 -0
  122. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-06T22-37-23-895Z.yml +0 -0
  123. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T00-39-18-637Z.yml +0 -0
  124. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T01-41-46-243Z.yml +0 -0
  125. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T04-31-48-472Z.yml +0 -0
  126. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-14-15-632Z.yml +0 -0
  127. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-14-47-582Z.yml +0 -0
  128. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-16-23-471Z.yml +0 -0
  129. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-17-10-247Z.yml +0 -0
  130. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-18-24-707Z.yml +0 -0
  131. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-20-06-164Z.yml +0 -0
  132. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-20-28-342Z.yml +0 -0
  133. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-21-54-962Z.yml +0 -0
  134. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-22-34-666Z.yml +0 -0
  135. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-23-11-336Z.yml +0 -0
  136. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-23-36-260Z.yml +0 -0
  137. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-24-09-267Z.yml +0 -0
  138. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-24-35-434Z.yml +0 -0
  139. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-25-57-010Z.yml +0 -0
  140. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-34-48-823Z.yml +0 -0
  141. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-46-46-468Z.yml +0 -0
  142. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-48-17-930Z.yml +0 -0
  143. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-49-26-400Z.yml +0 -0
  144. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-50-31-563Z.yml +0 -0
  145. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/page-2026-05-07T12-56-45-568Z.yml +0 -0
  146. {arrayview-0.27.0 → arrayview-0.28.0}/.playwright-cli/theme-dark.yml +0 -0
  147. {arrayview-0.27.0 → arrayview-0.28.0}/.python-version +0 -0
  148. {arrayview-0.27.0 → arrayview-0.28.0}/.vscode/settings.json +0 -0
  149. {arrayview-0.27.0 → arrayview-0.28.0}/AGENTS.md +0 -0
  150. {arrayview-0.27.0 → arrayview-0.28.0}/CONTRIBUTING.md +0 -0
  151. {arrayview-0.27.0 → arrayview-0.28.0}/DESIGN.md +0 -0
  152. {arrayview-0.27.0 → arrayview-0.28.0}/LICENSE +0 -0
  153. {arrayview-0.27.0 → arrayview-0.28.0}/README.md +0 -0
  154. {arrayview-0.27.0 → arrayview-0.28.0}/docs/comparing.md +0 -0
  155. {arrayview-0.27.0 → arrayview-0.28.0}/docs/configuration.md +0 -0
  156. {arrayview-0.27.0 → arrayview-0.28.0}/docs/display.md +0 -0
  157. {arrayview-0.27.0 → arrayview-0.28.0}/docs/index.md +0 -0
  158. {arrayview-0.27.0 → arrayview-0.28.0}/docs/lifecycle.md +0 -0
  159. {arrayview-0.27.0 → arrayview-0.28.0}/docs/logo.png +0 -0
  160. {arrayview-0.27.0 → arrayview-0.28.0}/docs/measurement.md +0 -0
  161. {arrayview-0.27.0 → arrayview-0.28.0}/docs/stylesheets/extra.css +0 -0
  162. {arrayview-0.27.0 → arrayview-0.28.0}/matlab/arrayview.m +0 -0
  163. {arrayview-0.27.0 → arrayview-0.28.0}/mkdocs.yml +0 -0
  164. {arrayview-0.27.0 → arrayview-0.28.0}/plans/2026-04-14-immersive-animation.md +0 -0
  165. {arrayview-0.27.0 → arrayview-0.28.0}/plans/2026-05-07-unified-colormap-picker.md +0 -0
  166. {arrayview-0.27.0 → arrayview-0.28.0}/plans/arrayview_tool_menu_fix_prompt.md +0 -0
  167. {arrayview-0.27.0 → arrayview-0.28.0}/plans/arrayview_tool_menu_implementation_plan.md +0 -0
  168. {arrayview-0.27.0 → arrayview-0.28.0}/plans/egg-placement-mockup.html +0 -0
  169. {arrayview-0.27.0 → arrayview-0.28.0}/plans/roi-analysis-requirements.md +0 -0
  170. {arrayview-0.27.0 → arrayview-0.28.0}/scripts/demo.py +0 -0
  171. {arrayview-0.27.0 → arrayview-0.28.0}/scripts/release.sh +0 -0
  172. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/__main__.py +0 -0
  173. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_app.py +0 -0
  174. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_codex_open.py +0 -0
  175. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_config.py +0 -0
  176. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_diff.py +0 -0
  177. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_icon.png +0 -0
  178. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_imaging.py +0 -0
  179. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_lifecycle.py +0 -0
  180. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_overlays.py +0 -0
  181. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_platform.py +0 -0
  182. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_render.py +0 -0
  183. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_analysis.py +0 -0
  184. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_export.py +0 -0
  185. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_persistence.py +0 -0
  186. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_preload.py +0 -0
  187. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_query.py +0 -0
  188. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_segmentation.py +0 -0
  189. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_state.py +0 -0
  190. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_routes_vectorfield.py +0 -0
  191. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_segmentation.py +0 -0
  192. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_server.py +0 -0
  193. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_session.py +0 -0
  194. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_shell.html +0 -0
  195. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_torch.py +0 -0
  196. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/_vectorfield.py +0 -0
  197. {arrayview-0.27.0 → arrayview-0.28.0}/src/arrayview/gsap.min.js +0 -0
  198. {arrayview-0.27.0 → arrayview-0.28.0}/tests/capture_v_animation.py +0 -0
  199. {arrayview-0.27.0 → arrayview-0.28.0}/tests/conftest.py +0 -0
  200. {arrayview-0.27.0 → arrayview-0.28.0}/tests/lifecycle_matrix.py +0 -0
  201. {arrayview-0.27.0 → arrayview-0.28.0}/tests/make_vectorfield_test_arrays.py +0 -0
  202. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_backend_shared.py +0 -0
  203. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_browser.py +0 -0
  204. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_cli.py +0 -0
  205. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_colorbar_hover_highlight.py +0 -0
  206. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_command_reachability.py +0 -0
  207. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_config.py +0 -0
  208. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_cross_mode_parametrized.py +0 -0
  209. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_interactions.py +0 -0
  210. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_large_arrays.py +0 -0
  211. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_loading_server.py +0 -0
  212. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_mode_consistency.py +0 -0
  213. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_mode_matrix.py +0 -0
  214. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_mode_roundtrip.py +0 -0
  215. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_nifti_meta.py +0 -0
  216. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_rgb_pixel_art.py +0 -0
  217. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_torch.py +0 -0
  218. {arrayview-0.27.0 → arrayview-0.28.0}/tests/test_view_component_unit.py +0 -0
  219. {arrayview-0.27.0 → arrayview-0.28.0}/tests/ui_audit.py +0 -0
  220. {arrayview-0.27.0 → arrayview-0.28.0}/tests/v_anim_frames/.gitkeep +0 -0
  221. {arrayview-0.27.0 → arrayview-0.28.0}/vscode-extension/LICENSE +0 -0
  222. {arrayview-0.27.0 → arrayview-0.28.0}/vscode-extension/lifecycle_helpers.js +0 -0
  223. {arrayview-0.27.0 → arrayview-0.28.0}/vscode-extension/test_lifecycle_helpers.js +0 -0
@@ -20,14 +20,16 @@ Every behavior that depends on *how* arrayview is started (server lifecycle, bro
20
20
  | **VS Code terminal** | `arrayview()` or `view()` | VS Code Simple Browser | in-process or subprocess |
21
21
  | **Plain SSH (no VS Code)** | `arrayview()` or `view()` | prints port-forward hint | in-process or subprocess |
22
22
 
23
- ## Transport Split
23
+ ## Transport
24
24
 
25
- ArrayView has two backend transports. Invocation-sensitive fixes must account for both:
25
+ ArrayView uses one viewer backend transport:
26
26
 
27
27
  - **Network mode**: FastAPI routes and WebSocket server. File registration goes through `/load` in `_routes_loading.py`; spawned CLI sessions load in `_launcher._serve_daemon()`.
28
- - **VS Code direct mode**: stdio subprocess. VS Code tunnel launches write a signal file; the extension runs `python -m arrayview --mode stdio <file>`, and file registration goes through `_stdio_server._handle_register()`.
29
28
 
30
- Do not assume a `/load` or daemon fix covers VS Code tunnel. Shared behavior such as file-format key selection belongs in `_io.py` helpers and must be used by both transports.
29
+ VS Code local and remote/tunnel sessions both use signal-file opening plus the
30
+ HTTP/WebSocket viewer. Shared behavior such as file-format key selection belongs
31
+ in `_io.py` helpers and must be used by `/load`, daemon startup, and in-process
32
+ session creation.
31
33
 
32
34
  ## Key Detection Functions (all in `_platform.py`)
33
35
 
@@ -98,8 +100,6 @@ for pid_str in client_pids:
98
100
  2. **File/session registration consistent?**
99
101
  - FastAPI `/load` path handles the feature
100
102
  - CLI spawned daemon path handles the feature
101
- - VS Code direct stdio `_handle_register()` path handles the feature
102
- - Direct `--mode stdio <file>` CLI path handles the feature
103
103
  - Multi-array/default-key behavior is centralized in `_io.py`
104
104
 
105
105
  3. **Browser/display opening?**
@@ -127,8 +127,8 @@ uv run python -c "from arrayview import view; import numpy as np; view(np.zeros(
127
127
  ## Red Flags — STOP
128
128
 
129
129
  - "I changed server startup but only tested CLI" → test all paths
130
- - "I fixed `/load` but not `_stdio_server.py`" → VS Code tunnel can still be broken
131
- - "I fixed `_serve_daemon()` but `--diagnose` says `is_vscode_remote: true`" → tunnel uses stdio direct mode, not the daemon
130
+ - "I fixed `/load` but not daemon startup" → spawned CLI sessions can still be broken
131
+ - "I fixed `_serve_daemon()` but `--diagnose` says `is_vscode_remote: true`" → verify the tunnel signal-file, port exposure, and `asExternalUri` path too
132
132
  - "Works locally but not in tunnel" → check `_is_vscode_remote()` path and port exposure
133
133
  - "I used `127.0.0.1` in the URL" → use `localhost`
134
134
  - "I fixed tmux with `#{client_pid}`" → only works with one client; use `list-clients`
@@ -0,0 +1,62 @@
1
+ ---
2
+ name: router
3
+ description: Entry point for task routing, project non-negotiables, and behavioral guardrails. Start here, then load only the minimum extra context a task needs.
4
+ last_updated: 2026-07-01
5
+ ---
6
+
7
+ # arrayview — Router
8
+
9
+ Python package for interactively viewing multi-dimensional arrays (numpy, NIfTI, zarr, …) with a FastAPI backend, single-file HTML/JS frontend, and multi-environment display routing (Jupyter, VS Code, SSH, native window).
10
+
11
+ ## Non-Negotiables
12
+
13
+ - Never split `_viewer.html` — entire frontend is one self-contained file, no build step.
14
+ - All heavy imports (numpy, matplotlib, nibabel, FastAPI, uvicorn) must be lazy — CLI fast path stays near-zero cost.
15
+ - New rendering features must be consistent across all six invocation environments.
16
+ - Global state lives in `_session.py` only — `SESSIONS`, `SERVER_LOOP`, `VIEWER_SOCKETS` never redefined elsewhere.
17
+ - Render thread must remain a raw `threading.Thread` + `SimpleQueue`, not `concurrent.futures`.
18
+
19
+ ## Context Budget
20
+
21
+ - Start with this file plus **one context file and/or one pattern file**, loaded only when the task actually needs them.
22
+ - Do **not** reopen this router or already-loaded docs on routine follow-ups in the same thread. Reuse what's loaded unless blocked.
23
+ - When blocked, load **one more** file — do not recursively fan out through second-hop references.
24
+ - For `_viewer.html` follow-ups with a known symbol: go straight to an exact `rg` search and a narrow code read. Do **not** auto-load `context/frontend.md`.
25
+ - For UI behavior, animation, or UX-expectation tasks: ask a plain-English clarification question **before** reading git history, large diffs, or broad source sweeps.
26
+ - Normal work never loads `.mex/SETUP.md`, `.mex/SYNC.md`, or `patterns/README.md` — those are scaffold-maintenance files.
27
+
28
+ ## Where to Look
29
+
30
+ Load only the file(s) matching the task. One is usually enough.
31
+
32
+ | Task | File |
33
+ |------|------|
34
+ | Current shipped / in-progress status | `context/project-state.md` |
35
+ | System architecture, component connections | `context/architecture.md` |
36
+ | Startup, shutdown, display ownership, orphans, VS Code tabs, session release | `context/lifecycle.md` |
37
+ | Specific technology / library choices | `context/stack.md` |
38
+ | Code patterns when writing or reviewing | `context/conventions.md` |
39
+ | Why something is built the way it is / a new architectural choice | `context/decisions.md` |
40
+ | First-time dev environment setup | `context/setup.md` |
41
+ | `_viewer.html` — modes, reconcilers, command registry, View Component System | `context/frontend.md` (+ `patterns/frontend-change.md` if changing code) |
42
+ | Rendering, colormaps, LUTs, caching, render thread | `context/render-pipeline.md` |
43
+ | Any task — scan for a matching pattern first | `patterns/INDEX.md` |
44
+ | Adding a file format | `patterns/add-file-format.md` |
45
+ | Adding a server route / WebSocket endpoint | `patterns/add-server-endpoint.md` |
46
+ | Visual bugs / render artifacts | `patterns/debug-render.md` |
47
+ | Verifying animation changes (frame captures) | `patterns/animation-verify.md` |
48
+ | Design philosophy, new features, mode additions, interaction model | `../DESIGN.md` |
49
+
50
+ ## Commands
51
+
52
+ - Test: run `uv run pytest` against `tests/`
53
+ - Visual smoke: run `uv run python` on `tests/visual_smoke.py`
54
+ - CLI: `uvx arrayview <file>`
55
+ - Build: `uv build`
56
+ - Drift check: `mex check --quiet`
57
+
58
+ ## After Completing a Task
59
+
60
+ - [ ] Update `context/project-state.md` if shipped or in-progress status changed.
61
+ - [ ] Update any stale `.mex/context/` or `.mex/patterns/` files touched by the task.
62
+ - [ ] If this revealed a repeatable workflow, add or update a pattern in `.mex/patterns/`.
@@ -30,13 +30,12 @@ last_updated: 2026-06-11
30
30
  ```
31
31
  CLI / Python API (view() or uvx arrayview <file>)
32
32
  └─ _launcher.py → FastAPI server (_server.py + _routes_*.py, uvicorn) [network mode]
33
- → _stdio_server.py [VS Code direct webview]
34
33
 
35
- Server (either mode)
34
+ Server
36
35
  ├─ _session.py Session objects, global state, render thread, caches
37
36
  ├─ _render.py extract_slice → apply_complex_mode → render_rgba → PNG pipeline
38
37
  ├─ _analysis.py / _diff.py / _overlays.py / _vectorfield.py
39
- │ Shared helpers used by FastAPI and stdio transports
38
+ │ Shared helpers used by route modules and WebSocket handlers
40
39
  └─ _io.py load_data() — npy/npz/nii/zarr/h5/mat/tif/pt routing
41
40
 
42
41
  Browser (_viewer.html — single self-contained HTML+JS+CSS file)
@@ -47,7 +46,7 @@ Browser (_viewer.html — single self-contained HTML+JS+CSS file)
47
46
 
48
47
  A `view()` call creates a `Session`, starts the FastAPI server if not running,
49
48
  registers the session via HTTP POST `/load`, then opens a display for the detected
50
- environment (Jupyter inline, VS Code webview panel or direct webview, native
49
+ environment (Jupyter inline, VS Code webview panel, native
51
50
  pywebview, or system browser).
52
51
 
53
52
  ## Key Components
@@ -57,12 +56,11 @@ pywebview, or system browser).
57
56
  - **`_routes_*.py`** — Feature-route modules grouped by domain (analysis, loading, persistence, segmentation, state, query, export, preload, vectorfield, rendering, websocket transport). Each module exposes `register_*_routes(app, ...)` and keeps `_server.py` focused on assembly and shared dependencies.
58
57
  - **`_session.py`** — Single source of global mutable state: `SESSIONS`, `SERVER_LOOP`, `VIEWER_SOCKETS`, `VIEWER_SIDS`, `SHELL_SOCKETS`. Owns the render thread (`_RENDER_QUEUE`, `_RENDER_THREAD`), prefetch pool, and the `Session` class with its three LRU caches.
59
58
  - **`_render.py`** — Stateless rendering functions: `extract_slice()`, `apply_complex_mode()`, `render_rgba()`, `render_rgb_rgba()`, `render_mosaic()`, `extract_projection()`. Owns colormap LUTs (`LUTS` dict, lazy-initialized by `_init_luts()`). Also provides `_build_mosaic_grid()` (shared grid builder) and `_evict_lru()` (shared cache eviction).
60
- - **`_imaging.py`** — Shared lazy PIL accessors (`ensure_image()`, `ensure_imageops()`) used by `_diff`, `_server`, `_routes_rendering`, `_routes_websocket`, `_stdio_server`.
61
- - **`_analysis.py`, `_diff.py`, `_overlays.py`, `_vectorfield.py`** — Shared backend helpers for metadata, analysis endpoints, compare/diff rendering, overlay compositing, vector field validation, and arrow sampling. Imported by both `_server.py` and `_stdio_server.py`.
59
+ - **`_imaging.py`** — Shared lazy PIL accessors (`ensure_image()`, `ensure_imageops()`) used by `_diff`, `_server`, `_routes_rendering`, and `_routes_websocket`.
60
+ - **`_analysis.py`, `_diff.py`, `_overlays.py`, `_vectorfield.py`** — Shared backend helpers for metadata, analysis endpoints, compare/diff rendering, overlay compositing, vector field validation, and arrow sampling.
62
61
  - **`_io.py`** — All file-format loading behind `load_data(filepath)`. Lazy nibabel import for NIfTI. Handles `.npy`, `.npz`, `.nii` and `.nii.gz`, `.zarr`, `.zarr.zip`, `.pt` and `.pth`, `.h5` and `.hdf5`, `.tif` and `.tiff`, `.mat`. Extensions registered in `_SUPPORTED_EXTS`.
63
62
  - **`_platform.py`** — Environment detection: checks jupyter → vscode → julia → ssh → terminal in priority order. Results cached. Never short-circuit this order.
64
- - **`_vscode.py`** — VS Code integration facade. Submodules: `_vscode_extension.py` (install), `_vscode_signal.py` (signal-file IPC), `_vscode_shm.py` (shared-memory transport), `_vscode_browser.py` (browser/SSH guidance).
65
- - **`_stdio_server.py`** — Alternative to FastAPI for VS Code tunnel (direct webview): JSON on stdin, length-prefixed binary on stdout.
63
+ - **`_vscode.py`** — VS Code integration facade. Submodules: `_vscode_extension.py` (install), `_vscode_signal.py` (signal-file IPC), `_vscode_browser.py` (browser/SSH guidance).
66
64
  - **`_viewer.html`** — The entire frontend (~24 100 lines). CSS + JS in one file, no build step. Canvas-based rendering, WebSocket binary protocol, all viewing modes, reconcilers, command registry. See `context/frontend.md`.
67
65
 
68
66
  ## Frontend Tool Lifecycle
@@ -83,7 +81,7 @@ Tool launch, tool activation, and drawer visibility are separate states.
83
81
  |---|---|---|
84
82
  | Jupyter | Inline iframe | network |
85
83
  | VS Code local | Webview panel | network |
86
- | VS Code tunnel | Direct webview (stdio) | stdio |
84
+ | VS Code tunnel | Webview panel (forwarded WebSocket) | network |
87
85
  | Julia | System browser | network |
88
86
  | CLI / Python script | Native pywebview | network |
89
87
  | SSH terminal | Prints URL — user forwards port with `ssh -L` | network |
@@ -96,8 +94,7 @@ File parsing belongs in `_io.py`, but session registration happens through diffe
96
94
 
97
95
  - **Network `/load`**: existing FastAPI server path in `_routes_loading.py`.
98
96
  - **Spawned CLI daemon**: `_launcher._serve_daemon()` loads before publishing the pending session.
99
- - **VS Code tunnel direct webview**: `_stdio_server._handle_register()` handles `python -m arrayview --mode stdio <file>` spawned by the extension.
100
- - **Direct stdio CLI mode**: `_launcher.arrayview()` preloads positional files before starting `run_stdio_server()`.
97
+ All file registration paths use either FastAPI `/load`, spawned daemon startup, or in-process `Session` creation. Keep format-specific key filtering/default selection in `_io.py` helpers so every launch path sees the same behavior.
101
98
 
102
99
  For multi-array formats or formats with non-displayable members, keep key filtering/default selection in `_io.py` helpers and call those helpers from every registration path. A fix in `/load` alone does not cover VS Code tunnel.
103
100
 
@@ -18,7 +18,7 @@ edges:
18
18
  condition: when applying frontend conventions (section separators, reconcilers, command registry)
19
19
  - target: context/render-pipeline.md
20
20
  condition: when applying render pipeline conventions (pipeline order, cache patterns, LUT usage)
21
- last_updated: 2026-06-19
21
+ last_updated: 2026-07-01
22
22
  ---
23
23
 
24
24
  # Conventions
@@ -34,11 +34,11 @@ last_updated: 2026-06-19
34
34
  ## Structure
35
35
 
36
36
  - **Lazy imports everywhere** — any import that costs >10 ms must be deferred. Pattern: module-level `_cache = None` + accessor function (see `_server_mod()`, `_uvicorn()`, `_nib()`, `_init_luts()`). The CLI fast path (server already running) must stay near-zero cost.
37
- - **Global state lives in `_session.py`** — `SESSIONS`, `SERVER_LOOP`, `VIEWER_SOCKETS`, `SHELL_SOCKETS`. Other modules import these by name, never redefine them.
37
+ - **Global state lives in `_session.py`** — `SESSIONS`, `SERVER_LOOP`, `SERVER_PORT`, `VIEWER_SOCKETS`, `VIEWER_SIDS`, `VIEWER_SID_COUNTS`, `VIEWER_CONNECTIONS_SEEN`, `SHELL_SOCKETS`, `PENDING_SESSIONS`, `PENDING_SESSION_EVENTS`, `_window_process`, plus the render pool (`_RENDER_QUEUE`, `_RENDER_THREADS`, `_RENDER_WORKERS`). Other modules import these by name, never redefine them.
38
38
  - **Render pipeline is pure functions** — `_render.py` exports stateless functions that take `session` + parameters. No class hierarchy.
39
39
  - **One HTML file for the entire frontend** — all CSS and JS live in `_viewer.html`. Do not split into separate files.
40
40
  - **Tests live in `tests/`** — not next to source files. Visual/browser tests use playwright and are marked `@pytest.mark.browser`.
41
- - **All file-format loading goes through `_io.load_data(filepath)`** no direct format imports in `_server.py` or `_launcher.py`.
41
+ - **All file-format loading goes through `_io.py`** — `load_data_with_meta(filepath, key=None, select=None)` is the canonical primary-load path (returns `(data, spatial_meta)` and is what `_launcher.py` uses). `load_data(filepath, key=None)` still exists for loads that don't need spatial metadata. No direct format imports in `_server.py` or `_launcher.py`.
42
42
  - **Server features usually live in `_routes_*.py`** — keep `_server.py` as the assembly layer unless the route is tiny infrastructure.
43
43
 
44
44
  ## Patterns
@@ -90,7 +90,7 @@ Always check in this priority order. Never short-circuit.
90
90
  Before presenting any code change:
91
91
  - [ ] New heavy imports are lazy (wrapped in a `_mod = None` / accessor function pattern)
92
92
  - [ ] Any new `Session` field is initialized in `Session.__init__` and cleared in `reset_caches()` if it's cache-related
93
- - [ ] New file format support goes through `_io.load_data()` and adds the extension to `_SUPPORTED_EXTS`
93
+ - [ ] New file format support goes through `_io.load_data_with_meta()` (and `load_data()`), and adds the extension to `_SUPPORTED_EXTS`
94
94
  - [ ] Frontend changes are in `_viewer.html` only — no new JS/CSS files
95
95
  - [ ] New rendering functions follow the `extract_slice → apply_complex_mode → apply_colormap_rgba` pipeline order
96
96
  - [ ] Environment detection changes go in `_platform.py`, not inline in `_launcher.py` or `_vscode.py`
@@ -53,13 +53,12 @@ last_updated: 2026-06-19
53
53
  **Alternatives considered:** `ThreadPoolExecutor` (rejected — shutdown race); `asyncio.run_in_executor` with default executor (same issue).
54
54
  **Consequences:** The prefetch pool *does* use `concurrent.futures.ThreadPoolExecutor` (it submits non-critical background work and gracefully handles `RuntimeError` on shutdown). Only the critical render path must stay on `SimpleQueue`.
55
55
 
56
- ### stdio transport for VS Code tunnel (direct webview)
56
+ ### VS Code tunnel uses forwarded WebSocket transport
57
57
  **Date:** 2024
58
- **Status:** Active
59
- **Decision:** When a VS Code tunnel is detected, `_stdio_server.py` replaces FastAPI+WebSocket. Messages are JSON on stdin, length-prefixed binary on stdout. The VS Code extension bridges `postMessage` subprocess stdio.
60
- **Reasoning:** VS Code tunnel environments block arbitrary TCP ports. The Direct WebView API bypasses the network entirely by running the viewer as a subprocess of the extension. This gives reliable display without requiring port forwarding.
61
- **Alternatives considered:** Port forwarding via VS Code tunnel (rejected unreliable, depends on tunnel configuration); WebSocket over stdio tunnel (rejected more complex than length-prefixed binary).
62
- **Consequences:** `_stdio_server.py` must mirror every route and feature of `_server.py`. New server features must be implemented in both. The `_vscode.py` signal-file and shared-memory IPC are part of this same display path.
58
+ **Status:** Superseded
59
+ **Decision:** Older tunnel builds used a now-removed stdio-based server module and direct webview `postMessage` IPC to avoid unreliable forwarded ports. Current builds use the same FastAPI/WebSocket transport as local VS Code and have the extension expose/promote the tunnel port before opening the tab.
60
+ **Reasoning:** The direct transport duplicated every server feature and created drift between FastAPI and stdio behavior. The VS Code port-forwarding path is now reliable enough to make one transport simpler and easier to test.
61
+ **Consequences:** New viewer features only need the FastAPI routes/WebSocket path; the VS Code extension remains responsible for signal-file routing, external URI resolution, and port privacy.
63
62
 
64
63
  ### ROI in qMRI uses per-pane overlay canvases, not a shared overlay
65
64
  **Date:** 2026-04-16
@@ -18,7 +18,7 @@ edges:
18
18
  condition: when writing new frontend code and need section separator conventions
19
19
  - target: patterns/frontend-change.md
20
20
  condition: when making a concrete change to _viewer.html
21
- last_updated: 2026-06-05
21
+ last_updated: 2026-07-01
22
22
  ---
23
23
 
24
24
  # Frontend (_viewer.html)
@@ -46,7 +46,7 @@ For a small follow-up tweak, prefer exact code search in `_viewer.html` and skip
46
46
  - Section separators: `/* ── Section Name ── */` in CSS, `// ── Section Name ──` in JS
47
47
  - Reconcilers: grep `UI Validation and Reconciliation`, `_reconcileUI`, `_reconcileLayout`
48
48
  - Keybinds/help: grep `commands`, `keybinds`, `GUIDE_TABS`, `dispatchCommand`
49
- - Mode system: grep `Mode Registry`, `modeManager`, `enterMultiView`, `enterCompare`, `enterQmri`
49
+ - Mode system: grep `Mode Registry`, `modeManager`, `enterMultiView`, `enterCompareModeBySid`, `enterQmri`
50
50
  - Layout/scale: grep `scaleCanvas`, `mvScaleAllCanvases`, `compareScaleCanvases`, `qvScaleAllCanvases`
51
51
  - Colorbars: grep `ColorBar class`, `drawSlimColorbar`, `drawMvColorbar`
52
52
 
@@ -73,7 +73,7 @@ For a small follow-up tweak, prefer exact code search in `_viewer.html` and skip
73
73
 
74
74
  - **Reconcilers** — UI visibility/state should converge here after mode changes
75
75
  - **Command registry** — `commands`, `keybinds`, `dispatchCommand`, `GUIDE_TABS`
76
- - **View component system** — `View`, `Slicer`, `Layer`, `LayoutStrategy`, `modeManager`; still coexists with legacy rendering
76
+ - **View component system** — `View`, `Slicer`, `LayoutStrategy`, `modeManager`, plus concrete layer classes `VectorFieldLayer`, `OverlayLayer`, `CrosshairLayer`; still coexists with legacy rendering
77
77
  - **Dual-write state** — when legacy globals are changed, matching `displayState` fields may need updating too
78
78
  - **Manual range state** — `manualVmin` / `manualVmax` and per-view locked ranges are real state, not derived UI
79
79
  - **Tool menu and dynamic island** — central owners for several multi-feature UI surfaces; touch deliberately
@@ -28,11 +28,10 @@ This contract describes who owns the backend, when it starts, and what closes it
28
28
  | Invocation | Display owner | Backend model | Shutdown/release |
29
29
  |---|---|---|---|
30
30
  | Local VS Code CLI `arrayview file.npy` | VS Code URL webview panel | Shared transient daemon | Panel close releases URL sessions; last viewer WebSocket close stops daemon |
31
- | VS Code file click/custom editor | VS Code extension | Extension-owned stdio subprocess | Panel close kills subprocess |
32
31
  | Plain Python script `view(arr)` | Browser/native/VS Code display | Non-daemon background server thread | Survives caller until viewer connects then closes |
33
32
  | Jupyter `view(arr)` | Notebook kernel inline iframe | Kernel-owned daemon server thread | Iframe disappearance must not hard-kill backend |
34
33
  | Julia/PythonCall | Browser/VS Code route from subprocess | Detached subprocess | Never in-process; avoid GIL deadlock |
35
- | Remote/tunnel | VS Code direct webview when possible | stdio direct webview or intentional persistent server | Persistence allowed only when transport requires it |
34
+ | Remote/tunnel | VS Code URL webview panel | Forwarded WebSocket server | Persistent only when `--serve` or tunnel ownership requires it |
36
35
  | Plain SSH | User-forwarded localhost URL | Transient server unless `--serve` requested | Viewer close ends transient session |
37
36
 
38
37
  ## Local VS Code CLI
@@ -43,13 +42,6 @@ This contract describes who owns the backend, when it starts, and what closes it
43
42
  - Closing the last viewer tab should stop the transient daemon.
44
43
  - The VS Code wrapper must not show "backend unavailable" based on a webview-side `fetch()`; backend health checks belong in the extension host.
45
44
 
46
- ## VS Code File Click And Custom Editor
47
-
48
- - Prefer direct extension-owned stdio subprocesses.
49
- - Do not require localhost or a shared port when direct mode is available.
50
- - Closing the tab should terminate the subprocess.
51
- - This path is transient and owned by the extension session, not by a long-lived server.
52
-
53
45
  ## Python Script
54
46
 
55
47
  - `view(arr)` from a script should survive the script exiting.
@@ -66,8 +58,8 @@ This contract describes who owns the backend, when it starts, and what closes it
66
58
 
67
59
  ## Remote, Tunnel, And SSH
68
60
 
69
- - Remote or tunnel launches may persist when `--serve` or direct-server constraints require it.
70
- - Direct VS Code tunnel display should prefer stdio direct webview to avoid forwarded-port auth and public-port setup.
61
+ - Remote or tunnel launches may persist when `--serve` or tunnel display ownership requires it.
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`.
71
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.
72
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.
73
65
  - Plain SSH should use `localhost` forwarding guidance and stay transient unless a shared server was explicitly requested.
@@ -7,7 +7,7 @@ triggers:
7
7
  - "recent work"
8
8
  - "active feature"
9
9
  - "shipped recently"
10
- last_updated: 2026-06-05
10
+ last_updated: 2026-06-30
11
11
  ---
12
12
 
13
13
  # Project State
@@ -15,12 +15,13 @@ last_updated: 2026-06-05
15
15
  ## Working
16
16
 
17
17
  - CLI (`uvx arrayview file.npy`) and Python API (`view(arr)`) — both stable
18
- - Display environments: Jupyter inline, VS Code local, VS Code tunnel (stdio), Julia, native pywebview, SSH URL print (user forwards port with `ssh -L`)
18
+ - Display environments: Jupyter inline, VS Code local, VS Code tunnel via forwarded WebSocket, Julia, native pywebview, SSH URL print (user forwards port with `ssh -L`)
19
19
  - File formats: `.npy`, `.npz`, `.nii`/`.nii.gz`, `.zarr`, `.h5`/`.hdf5`, `.mat`, `.tif`/`.tiff`, `.pt`/`.pth`
20
20
  - Rendering pipeline: colormaps, complex modes, mosaic, RGB/RGBA, projections, overlays
21
- - Backend transport parity: FastAPI and stdio now share metadata/analysis helpers, compare/diff helpers, overlay compositing, and vector field layout/arrow sampling via `_analysis.py`, `_diff.py`, `_overlays.py`, and `_vectorfield.py`
21
+ - Backend transport: FastAPI HTTP/WebSocket is the single viewer transport; shared helpers keep route modules small for metadata/analysis, compare/diff, overlay compositing, and vector field layout/arrow sampling.
22
22
  - NIfTI spatial metadata, RAS resampling
23
- - VS Code extension v0.14.4 stable window ID via `EnvironmentVariableCollection`; `arrayview.openInFloatingWindow` setting moves new tabs to a floating window; `view(arr, floating=True)` and `arrayview file.npy --floating` open in a floating window per-call regardless of global setting; `!vscode.env.remoteName` guard removed (remote VS Code supports floating windows); floating mode now uses a single persistent shell hub panel (`_shell.html`) so all arrays share one floating window as tabs instead of opening separate windows; fixed: second CLI call now injects tab via `new_tab` postMessage relay (extension -> hub wrapper -> shell iframe) instead of relying on WebSocket notify which wasn't sent in VS Code mode; custom-editor fallback now pins `uv` to Python 3.12 so tunnel workspaces with older project interpreters still open arrays
23
+ - NIfTI series: `--stack-nifti DIR` + `--select PATTERN` stacks a directory of same-shape NIfTI files into a lazy 4D/5D array; `view_dir()` Python helper; recursive patient-folder grouping, LRU volume cache, spatial meta from first file
24
+ - VS Code extension v0.14.27 — stable window ID via `EnvironmentVariableCollection`; `arrayview.openInFloatingWindow` setting moves new tabs to a floating window; `view(arr, floating=True)` and `arrayview file.npy --floating` open in a floating window per-call regardless of global setting; remote/tunnel display uses the same forwarded HTTP/WebSocket path as local VS Code; floating mode uses a single persistent shell hub panel (`_shell.html`) so all arrays share one floating window as tabs instead of opening separate windows; second CLI calls inject tabs via `new_tab` postMessage relay (extension -> hub wrapper -> shell iframe)
24
25
  - Colorbar refactor: `ColorBar` JS class partially migrated (in progress)
25
26
  - Colormap picker: `c` opens an expanded colorbar-island grid without changing the colormap; subsequent `c` taps cycle, hover/hjkl/arrows live-preview, Enter/click commits, Esc cancels, and auto-dismiss pauses while hovered
26
27
  - Cold-start loading spinner in VS Code and native shell
@@ -37,7 +38,7 @@ last_updated: 2026-06-05
37
38
  - Island collapse affordance: inline `~` at the island's top-right animates the panel into the bottom-left `~` hint circle; external `~` hint only visible while the island is actually collapsed. New `/` hint circle at bottom-right opens the tool menu
38
39
  - Compare center tool menu: `/` now re-opens the last-used compare center mode from the tool menu, while compare pane header buttons select diff / overlay / wipe directly. Eligible two-array compare layouts can switch into a big-left arrangement with a wider center pane, and the diff colorbar now matches that center-pane width.
39
40
  - Compare center + ortho auto-layout: layout choice is now auto-picked once on entry from a shared viewport-profile helper, stays stable across resize, and becomes sticky only after manual `G` / `g` override. Compare big-left also now supports in-pane A/B source badges and a shared source colorbar parked in the gap between the stacked source panes.
40
- - Detached compare-on-X: with a single array and a non-spatial active dimension, `X` now enters compare mode by treating two indices from that dimension as A/B sources. Pane titles switch to index-over-total labels, the dimbar marks the detached dimension with a purple `X`, `[` / `]` scrub the left pane index, `{` / `}` scrub the right pane index, and repeated `X` presses cycle the existing compare-center modes before exiting back to normal view. The split-index diff path now works in both FastAPI and stdio transports.
41
+ - Detached compare-on-X: with a single array and a non-spatial active dimension, `X` now enters compare mode by treating two indices from that dimension as A/B sources. Pane titles switch to index-over-total labels, the dimbar marks the detached dimension with a purple `X`, `[` / `]` scrub the left pane index, `{` / `}` scrub the right pane index, and repeated `X` presses cycle the existing compare-center modes before exiting back to normal view. The split-index diff path works through the FastAPI/WebSocket transport.
41
42
 
42
43
  ## Recently Completed
43
44
 
@@ -20,7 +20,7 @@ edges:
20
20
  condition: when writing new render functions or extending the pipeline
21
21
  - target: patterns/debug-render.md
22
22
  condition: when a render produces wrong output or the wrong cache is hit
23
- last_updated: 2026-06-19
23
+ last_updated: 2026-07-01
24
24
  ---
25
25
 
26
26
  # Render Pipeline
@@ -34,29 +34,34 @@ Every slice request follows this fixed order. Do not skip steps or reorder.
34
34
  → returns float32 2D numpy array (raw slice from ND array)
35
35
  → result cached in session.raw_cache (LRU, keyed by (dim_x, dim_y, key_idx))
36
36
 
37
- 2. apply_complex_mode(arr, mode)
38
- applies FFT, magnitude, phase, real, imag transforms to complex arrays
39
- skip if data is not complex
37
+ 2. _prepare_display(session, raw, complex_mode, dr, log_scale, vmin_override, vmax_override)
38
+ calls apply_complex_mode(raw, complex_mode) internally (mag/phase/real/imag)
39
+ returns (float32 data, vmin, vmax) — NOT uint8; normalization to [0,255]
40
+ happens later inside apply_colormap_rgba
40
41
 
41
- 3. _prepare_display(arr, vmin, vmax, log_scale)
42
- normalizes to [0, 255] uint8
43
-
44
- 4. render_rgba(arr_uint8, lut_name) / render_rgb_rgba(arr) / render_mosaic(...)
45
- → applies LUT (colormap) → returns RGBA uint8 2D array
42
+ 3. render_rgba(session, ...) / render_rgb_rgba(session, ...) / render_mosaic(session, ...)
43
+ each takes a session + dims/idx/colormap params (NOT a pre-extracted array),
44
+ calls extract_slice internally, then applies LUT → RGBA uint8 2D array
46
45
  → result cached in session.rgba_cache or session.mosaic_cache
47
46
 
48
- 5. PNG encode (Pillow)
49
- sent as binary WebSocket frame
47
+ 4. WebSocket binary frame
48
+ raw RGBA bytes prefixed by a binary header (seq, w, h as uint32 +
49
+ vmin, vmax as float32). Pillow PNG encoding is ONLY used by the
50
+ HTTP export endpoints (e.g. /grid/{sid}), not the live WS render path.
50
51
  ```
51
52
 
53
+ Note: FFT is NOT part of `apply_complex_mode`. It is a separate data-level
54
+ transform applied in the `/fft/{sid}` endpoint (`_routes_state.py`), which
55
+ mutates `session.data` and stashes the original in `session.fft_original_data`.
56
+
52
57
  ## Colormap LUTs
53
58
 
54
59
  `LUTS` dict in `_render.py` — maps colormap name → 256×4 uint8 numpy array (RGBA).
55
60
 
56
61
  - Initialized lazily by `_init_luts()` on first use. Not initialized at import time.
57
- - Available colormaps: `gray`, `lipari`, `navia`, `viridis`, `plasma`, `magma`, `inferno`, `cividis`, `rainbow`, `RdBu_r`, `twilight_shifted`, plus `RdBu_r_black` (diff mode).
62
+ - Available colormaps: `gray`, `lipari`, `navia`, `viridis`, `plasma`, `magma`, `inferno`, `cividis`, `rainbow`, `RdBu_r`, `twilight_shifted`, `turbo`, plus `RdBu_r_black` (diff mode).
58
63
  - `lipari` and `navia` require `qmricolors` to be imported first (done inside `_init_luts()`).
59
- - To add a colormap: add name to `COLORMAPS` list in `_session.py`, then add LUT construction in `_init_luts()` in `_render.py`. Restart required.
64
+ - To add a colormap: add the name to `COLORMAPS` in `_session.py`. `_init_luts()` iterates `COLORMAPS` and builds each LUT from `matplotlib.colormaps[name]` automatically, so standard matplotlib colormaps need no extra work. Only a custom (non-matplotlib) colormap like `RdBu_r_black` needs manual LUT construction in `_init_luts()`. Env vars are read once at import, so a restart is required.
60
65
 
61
66
  ## Session Caches
62
67
 
@@ -68,7 +73,7 @@ Each `Session` has three LRU caches, all `OrderedDict`:
68
73
  | `rgba_cache` | derived | RGBA uint8 tiles | `ARRAYVIEW_RGBA_CACHE_MB` (10% RAM) |
69
74
  | `mosaic_cache` | derived | mosaic RGBA | `ARRAYVIEW_MOSAIC_CACHE_MB` (2.5% RAM) |
70
75
 
71
- Cache budgets are computed once at import time from available RAM (via `psutil`). Override per-session via env vars.
76
+ Cache budgets are computed once at module import time from available RAM (via `psutil`), with a 64 MB floor. The env vars below are read once at import — there is no per-session override; changing a value requires a process restart.
72
77
 
73
78
  LRU eviction pattern:
74
79
  ```python
@@ -83,14 +88,14 @@ while session._raw_bytes > session.RAW_CACHE_BYTES and session.raw_cache:
83
88
 
84
89
  ## Render Thread
85
90
 
86
- `_RENDER_QUEUE: SimpleQueue` in `_session.py`. The render thread (`arrayview-render`, daemon) drains the queue and resolves `asyncio.Future`s on the server event loop.
91
+ `_RENDER_QUEUE: SimpleQueue` in `_session.py`. A pool of `_RENDER_WORKERS = max(2, min(4, os.cpu_count() or 2))` daemon threads (named `arrayview-render-{N}`, tracked in `_RENDER_THREADS`) drains the queue and resolves `asyncio.Future`s on the server event loop. `_ensure_render_thread()` keeps the pool at the configured worker count.
87
92
 
88
93
  ```python
89
94
  await _render(loop, lambda: extract_slice(session, dim_x, dim_y, idx))
90
95
  ```
91
96
 
92
97
  - Never use `asyncio.run_in_executor` for render work — the executor's `_global_shutdown` flag races with daemon thread cleanup.
93
- - The queue accepts `None` as a sentinel to stop the thread.
98
+ - The queue accepts `None` as a sentinel to stop a worker.
94
99
 
95
100
  ## Prefetch Pool
96
101
 
@@ -99,7 +104,7 @@ Separate 1-thread `ThreadPoolExecutor` (`arrayview-prefetch`). Warms `raw_cache`
99
104
  ## Special Array Types
100
105
 
101
106
  - **RGB arrays** — detected by `_setup_rgb()`. Skip colormap; go directly to `render_rgb_rgba()`. `session.rgb_axis` holds the axis index; `session.spatial_shape` is shape without that axis.
102
- - **Complex arrays** — `apply_complex_mode()` transforms to real before colormapping. Supported modes: magnitude, phase, real, imag, FFT (various).
107
+ - **Complex arrays** — `apply_complex_mode()` transforms to real before colormapping. Supported modes: magnitude, phase, real, imag. FFT is a separate data-level transform (see note above).
103
108
  - **NIfTI** — canonical-reoriented on load by `_load_nifti_with_meta()`. `session.spatial_meta` holds affine, voxel sizes, axis labels. RAS resample cached in `session.resampled_volume`.
104
109
  - **Large memmap arrays** — `_default_start_dims_for_data()` may override the default startup axes to avoid high-stride reads.
105
110
 
@@ -13,7 +13,7 @@ edges:
13
13
  condition: when specific technology versions or library details are needed
14
14
  - target: context/architecture.md
15
15
  condition: when understanding how components connect during setup
16
- last_updated: 2026-06-19
16
+ last_updated: 2026-07-01
17
17
  ---
18
18
 
19
19
  # Setup
@@ -29,7 +29,7 @@ last_updated: 2026-06-19
29
29
  1. Clone the repo
30
30
  2. `uv sync --all-groups` — installs all dependencies including dev and test groups
31
31
  3. Run `uv run arrayview` on `debug/cig_meeting_examples/BraTS2021_00009_t1.nii.gz` — smoke test: should open the viewer with a sample array
32
- 4. Run `uv run pytest` on `tests/test_mode_consistency.py` — verify core render consistency passes
32
+ 4. Run `uv run pytest` on `tests/test_mode_consistency.py` — verify core render consistency passes (this is a browser-marked suite: run `uv run playwright install chromium` first, see step below)
33
33
 
34
34
  For browser-based tests (playwright):
35
35
  - `uv run playwright install chromium` — one-time browser install
@@ -48,7 +48,7 @@ No `.env` file is needed. All env vars are optional overrides; the server runs w
48
48
  - `uv run arrayview <file>` — launch viewer on a file (CLI entry point)
49
49
  - `uvx arrayview <file>` — launch from anywhere without activating the venv
50
50
  - Run `uv run pytest` on `tests/<target>` — run a specific test file
51
- - Run `uv run pytest` on `tests/test_mode_consistency.py` — mode consistency suite after render changes
51
+ - Run `uv run pytest` on `tests/test_mode_consistency.py` — mode consistency suite after render changes (browser-marked; requires `uv run playwright install chromium`)
52
52
  - Run `uv run python` on `tests/visual_smoke.py` — browser smoke tests (requires playwright)
53
53
  - `uv run pytest -m "not browser"` — all non-browser tests
54
54
  - `uv build` — build wheel + sdist in the default build output directory
@@ -28,7 +28,7 @@ Lookup table for all pattern files in this directory. Check here before starting
28
28
  | [add-file-format.md](add-file-format.md) | Adding support for a new file format in `_io.py` |
29
29
  | [animation-verify.md](animation-verify.md) | Verifying animation quality after GSAP, rAF, or CSS transition changes |
30
30
  | [add-server-endpoint.md](add-server-endpoint.md) | Adding a new REST or WebSocket route to `_server.py` |
31
- | [debug-vscode-extension-python.md](debug-vscode-extension-python.md) | Diagnosing VS Code custom-editor failures caused by Python selection, workspace virtualenv drift, or uv fallback behavior |
31
+ | [debug-vscode-extension-python.md](debug-vscode-extension-python.md) | Diagnosing VS Code opener failures in the signal-file and forwarded WebSocket path |
32
32
  | [debug-render.md](debug-render.md) | Visual rendering failures — wrong colors, blank canvas, artifacts, colormap issues |
33
33
  | [extend-compare-mode.md](extend-compare-mode.md) | Extending compare mode in `_viewer.html`, including new compare entry paths, pane labels, or per-pane diff/render state |
34
34
  | [extract-server-route-module.md](extract-server-route-module.md) | Moving an existing route cluster out of `_server.py` into a dedicated `_routes_*.py` module |
@@ -31,8 +31,7 @@ All file parsing goes through `_io.load_data(filepath)` or `_io.load_data_with_m
31
31
  Transport/session creation is separate from parsing:
32
32
 
33
33
  - FastAPI `/load`: `src/arrayview/_routes_loading.py`
34
- - CLI spawned daemon and direct stdio CLI mode: `src/arrayview/_launcher.py`
35
- - VS Code tunnel direct webview: `src/arrayview/_stdio_server.py`
34
+ - CLI spawned daemon and in-process sessions: `src/arrayview/_launcher.py`
36
35
 
37
36
  The server and CLI must not import format libraries directly. They may call generic `_io` helpers such as `list_array_keys()` or `default_array_key()` when they need to choose a key before creating a session.
38
37
 
@@ -82,7 +81,7 @@ The server and CLI must not import format libraries directly. They may call gene
82
81
 
83
82
  7. **Keep key/default selection consistent across transports** for multi-array formats (`.npz`, `.mat`, HDF5-like containers):
84
83
  - Put filtering and default-key logic in `_io.py`, not in a route or launcher branch.
85
- - Use the same helper from FastAPI `/load`, CLI daemon/direct paths, and `_stdio_server.py`.
84
+ - Use the same helper from FastAPI `/load`, CLI daemon startup, and in-process session creation.
86
85
  - If `/load` returns picker keys for multiple arrays, define what non-picker transports do (usually first displayable key).
87
86
 
88
87
  ## Gotchas
@@ -90,8 +89,8 @@ The server and CLI must not import format libraries directly. They may call gene
90
89
  - **Do not import the format library at the top of `_io.py`** without checking import cost first. Nibabel is the reference: it is lazy because it costs ~200 ms on first import.
91
90
  - **`.nii.gz` is two extensions** — the dispatch checks the full suffix, not just the last dot. New formats with compound extensions (`.foo.bar`) need special suffix handling in `load_data()`.
92
91
  - **Zarr is not loaded via `load_data()`** in all paths — zarr arrays can also be passed directly to `view()` as array objects. Ensure zarr-specific logic lives in `_session.py` / `_render.py`, not only in the file-load path.
93
- - **No format-specific parsing code in `_server.py`, `_launcher.py`, or `_stdio_server.py`** — if you find yourself adding a format import or dtype/shape filter in those modules, move it to `_io.py`.
94
- - **VS Code tunnel is stdio, not HTTP** — `arrayview file.ext` in a VS Code tunnel writes a signal file; the extension then runs `python -m arrayview --mode stdio file.ext`. A `/load` fix alone does not cover this path.
92
+ - **No format-specific parsing code in `_server.py` or `_launcher.py`** — if you find yourself adding a format import or dtype/shape filter in those modules, move it to `_io.py`.
93
+ - **VS Code tunnel uses the WebSocket server path** — remote/tunnel launches still need `/load` and daemon startup behavior to stay consistent.
95
94
 
96
95
  ## Verify
97
96
 
@@ -100,8 +99,7 @@ The server and CLI must not import format libraries directly. They may call gene
100
99
  - [ ] `load_data("myfile.myext")` returns a numpy array with correct shape and dtype
101
100
  - [ ] If the format can contain multiple arrays or non-displayable members, add tests for `list_array_keys()` / `default_array_key()` and direct `load_data(..., key=...)`
102
101
  - [ ] FastAPI `/load` works for the file, including picker/default-key behavior
103
- - [ ] `_stdio_server._handle_register()` works for the file, because VS Code tunnel direct webview uses stdio instead of `/load`
104
- - [ ] CLI daemon/direct paths use generic `_io` helpers for key selection instead of duplicating format rules
102
+ - [ ] CLI daemon and in-process paths use generic `_io` helpers for key selection instead of duplicating format rules
105
103
  - [ ] `view(load_data("myfile.myext"))` opens the viewer without errors
106
104
  - [ ] If full-load format, added to `FULL_LOAD_EXTS`
107
105
  - [ ] `uv run pytest` on `tests/test_mode_consistency.py` still passes (no dtype regressions)
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  name: add-server-endpoint
3
- description: Adding a new REST or WebSocket route to the FastAPI server (_server.py) — includes session lookup, render dispatch, and stdio transport parity.
3
+ description: Adding a new REST or WebSocket route to the FastAPI server (_server.py) — includes session lookup and render dispatch.
4
4
  triggers:
5
5
  - "endpoint"
6
6
  - "route"
@@ -27,8 +27,6 @@ last_updated: 2026-06-19
27
27
 
28
28
  Heavy render work must go to the render thread via `_render()` from `_session.py` — never block the async event loop with CPU work.
29
29
 
30
- If the feature involves the VS Code direct webview (stdio transport), `_stdio_server.py` must also be updated to handle the new message type. The two servers must stay feature-equivalent.
31
-
32
30
  ## Steps
33
31
 
34
32
  1. **Pick the owning module first:** add the route to the matching `_routes_*.py` module if the domain already exists. Only add it inline to `_server.py` if it is tiny infrastructure or shared app plumbing.
@@ -61,8 +59,6 @@ If the feature involves the VS Code direct webview (stdio transport), `_stdio_se
61
59
 
62
60
  6. **Wire the frontend:** If the route is called by JavaScript, add the fetch/WebSocket call in the correct section of `_viewer.html`. Follow the dual-write pattern if it updates display state.
63
61
 
64
- 7. **Update `_stdio_server.py`** if the feature is needed in VS Code direct webview mode. The stdio server handles messages as JSON objects on stdin — add a new `elif msg_type == "my_type":` branch in the message dispatch loop.
65
-
66
62
  ## Gotchas
67
63
 
68
64
  - **Do not add logic to `_app.py`** — `_app.py` is a backward-compat shim only. New routes belong in `_routes_*.py` or `_server.py`.
@@ -70,15 +66,12 @@ If the feature involves the VS Code direct webview (stdio transport), `_stdio_se
70
66
  - **WebSocket binary protocol is tightly coupled** — the byte layout (offset, header fields, RGBA payload) is shared between `_server.py` and the WS handler in `_viewer.html`. Change one → change both. Mismatch causes the canvas to render garbage or stay blank.
71
67
  - **Never block the event loop** — even a small `np.array()` call on a large array can block for hundreds of milliseconds. Use `await _render(loop, func)` for all numpy work.
72
68
  - **`VIEWER_SOCKETS` is a module-level integer in `_session.py`** — increment/decrement via `_session_mod.VIEWER_SOCKETS` (the module reference), not the locally imported name. Same for `VIEWER_SIDS`.
73
- - **`_stdio_server.py` parity** — if your endpoint returns data that the viewer needs in any environment, it must work in the stdio transport too. VS Code tunnel users will silently get a broken feature otherwise.
74
-
75
69
  ## Verify
76
70
 
77
71
  - [ ] Route validates `sid` and returns 404 for unknown sessions
78
72
  - [ ] CPU/render work dispatched via `await _render(loop, ...)`, not called directly
79
73
  - [ ] WebSocket binary frame layout matches `_viewer.html` expectations (if applicable)
80
74
  - [ ] No new logic added to `_app.py`
81
- - [ ] `_stdio_server.py` updated if the feature is needed in VS Code direct webview
82
75
  - [ ] `uv run pytest` on `tests/test_view_component_integration.py` passes
83
76
  - [ ] Manual test: run `uv run arrayview` on `debug/test_array.npy` and confirm the new route is reachable
84
77
 
@@ -16,7 +16,7 @@ triggers:
16
16
  edges:
17
17
  - target: patterns/frontend-change.md
18
18
  condition: always — animation changes are always frontend changes
19
- last_updated: 2026-06-19
19
+ last_updated: 2026-07-01
20
20
  ---
21
21
 
22
22
  # Animation Verify
@@ -101,18 +101,17 @@ Open the frame directory and scan the sequence:
101
101
 
102
102
  ### 3. Scrub at key progress points
103
103
 
104
- For pinch immersive crossfade: exercise `rawP` at 0 → 0.25 → 0.5 → 0.75 → 1.0 via
105
- `window.__pinchDebug = true` and the Playwright console:
104
+ For pinch immersive crossfade: drive `immersiveTl.progress()` at 0 → 0.25 → 0.5 → 0.75 → 1.0 from the Playwright console (the pinch path writes `immersiveTl.progress()` directly; `_crossfadeP` mirrors the same value):
106
105
 
107
106
  - [ ] Canvas size progresses monotonically (grows without shrinking mid-transition)
108
107
  - [ ] Chrome elements stay at freeze position until their fade-out begins
109
- - [ ] No layout jump at the rawP=0 or rawP=1 boundary
108
+ - [ ] No layout jump at the progress=0 or progress=1 boundary
110
109
  - [ ] After `_settleImmersive()` fires: chrome visible at overlay positions, `_scrubDetached` is false
111
110
 
112
111
  ### 4. Verify reverse path
113
112
 
114
113
  - [ ] Exit animation (reverse scrub) shows the same freeze behavior as entry
115
- - [ ] Chrome elements rejoin document flow cleanly at rawP=0 (no leftover fixed positioning)
114
+ - [ ] Chrome elements rejoin document flow cleanly at progress=0 (no leftover fixed positioning)
116
115
  - [ ] `_resetImmersiveTransforms()` clears all detach inline styles
117
116
 
118
117
  ## Regression Table
@@ -22,7 +22,7 @@ edges:
22
22
  condition: only after Step 1 points to `_viewer.html` rather than the Python pipeline
23
23
  - target: context/conventions.md
24
24
  condition: for the Verify Checklist after fixing
25
- last_updated: 2026-06-19
25
+ last_updated: 2026-07-01
26
26
  ---
27
27
 
28
28
  # Debug Render
@@ -95,7 +95,7 @@ print(vmin, vmax) # should not be equal
95
95
 
96
96
  **Step 5 — Check WebSocket binary frame format**
97
97
  In the browser console: look for `ArrayBuffer` size. Expected: `H * W * 4` bytes (RGBA, row-major).
98
- If size is wrong → server is sending wrong shape. Add a print to the `/ws/{sid}` handler in `_server.py`.
98
+ If size is wrong → server is sending wrong shape. Add a print to the `/ws/{sid}` handler in `_routes_websocket.py` (registered into the app via `register_websocket_routes(app)`; the handler is no longer in `_server.py`).
99
99
 
100
100
  **Step 6 — Check mosaic grid layout**
101
101
  For mosaic artifacts: `mosaic_shape(n)` returns `(rows, cols)`. Verify `rows * cols >= n`.
@@ -107,7 +107,7 @@ Run `_composite_overlay_mask(rgba, ov_raw, is_label=True)` on a test RGBA and ma
107
107
  ## Gotchas
108
108
 
109
109
  - **Cache stale after data change** — if `session.data` is modified without calling `session.reset_caches()`, stale slices will be served. Always call `reset_caches()` after modifying session data.
110
- - **Complex dtype from `.mat`** — scipy structured dtypes with `real`/`imag` fields are converted in `extract_slice`, not in `_io.load_data`. If you bypass `extract_slice`, you must call `_fix_mat_complex()` yourself.
110
+ - **Complex dtype from `.mat`** — scipy structured dtypes with `real`/`imag` fields are converted in `_io.load_data()` (via `_fix_mat_complex()`), not in `extract_slice`. If you bypass `load_data()`, you must call `_fix_mat_complex()` yourself.
111
111
  - **RGB arrays skip colormap** — `render_rgb_rgba` does NOT call `apply_colormap_rgba`. If your code assumes all renders go through colormap, it breaks for RGB sessions.
112
112
  - **Mosaic gap pixels** — the grid is filled with `np.nan` first, then NaN pixels are colored `[22, 22, 22, 255]`. A NaN in actual data will look like a grid gap — use `extract_slice` which calls `nan_to_num`.
113
113
  - **Colormap not in LUTS** — custom colormaps added after `_init_luts()` go through `_ensure_lut()`. Never access `LUTS[name]` without calling `_ensure_lut(name)` first.