arrayview 0.5.1__tar.gz → 0.6.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.
- arrayview-0.6.0/.claude/skills/invocation-consistency/SKILL.md +118 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/.claude/skills/task-workflow/SKILL.md +7 -3
- arrayview-0.6.0/.claude/skills/ui-consistency-audit/SKILL.md +336 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/.gitignore +6 -3
- arrayview-0.6.0/AGENTS.md +137 -0
- arrayview-0.6.0/PKG-INFO +68 -0
- arrayview-0.6.0/README.md +33 -0
- arrayview-0.6.0/docs/comparing.md +44 -0
- arrayview-0.6.0/docs/configuration.md +40 -0
- arrayview-0.6.0/docs/display.md +31 -0
- arrayview-0.6.0/docs/index.md +37 -0
- arrayview-0.6.0/docs/loading.md +108 -0
- arrayview-0.6.0/docs/measurement.md +34 -0
- arrayview-0.6.0/docs/remote.md +71 -0
- arrayview-0.6.0/docs/stylesheets/extra.css +25 -0
- arrayview-0.6.0/docs/superpowers/plans/2026-03-27-colorbar-class-refactor.md +1642 -0
- arrayview-0.6.0/docs/viewing.md +36 -0
- arrayview-0.6.0/mkdocs.yml +37 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/pyproject.toml +5 -1
- arrayview-0.6.0/src/arrayview/__init__.py +4 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_app.py +3 -0
- arrayview-0.6.0/src/arrayview/_config.py +92 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_io.py +12 -1
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_launcher.py +418 -78
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_platform.py +22 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_render.py +122 -2
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_server.py +415 -40
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_session.py +4 -1
- arrayview-0.6.0/src/arrayview/_viewer.html +11991 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_vscode.py +174 -2
- arrayview-0.6.0/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/tests/test_api.py +393 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/tests/test_browser.py +188 -13
- {arrayview-0.5.1 → arrayview-0.6.0}/tests/test_cli.py +62 -0
- arrayview-0.6.0/tests/test_config.py +146 -0
- arrayview-0.6.0/tests/test_interactions.py +1850 -0
- arrayview-0.6.0/tests/test_mode_consistency.py +560 -0
- arrayview-0.6.0/tests/test_mode_matrix.py +585 -0
- arrayview-0.6.0/tests/ui_audit.py +1179 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/tests/visual_smoke.py +454 -71
- {arrayview-0.5.1 → arrayview-0.6.0}/uv.lock +273 -1
- {arrayview-0.5.1 → arrayview-0.6.0}/vscode-extension/extension.js +42 -15
- {arrayview-0.5.1 → arrayview-0.6.0}/vscode-extension/package.json +1 -1
- arrayview-0.5.1/.claude/skills/invocation-consistency/SKILL.md +0 -235
- arrayview-0.5.1/AGENTS.md +0 -219
- arrayview-0.5.1/CHUNK_PLAN.md +0 -179
- arrayview-0.5.1/LOG_LARGE_ARRAYS.md +0 -214
- arrayview-0.5.1/PKG-INFO +0 -411
- arrayview-0.5.1/README.md +0 -376
- arrayview-0.5.1/TODO.md +0 -41
- arrayview-0.5.1/VSCODE_DETECTION.md +0 -82
- arrayview-0.5.1/docs/large-arrays.md +0 -197
- arrayview-0.5.1/docs/superpowers/specs/2026-03-14-todo-batch-design.md +0 -77
- arrayview-0.5.1/plans/speed/LOG.md +0 -219
- arrayview-0.5.1/plans/speed/PLAN.md +0 -266
- arrayview-0.5.1/plans/tunnel-fix/LOG.md +0 -284
- arrayview-0.5.1/plans/tunnel-fix/PLAN.md +0 -148
- arrayview-0.5.1/plans/tunnel-fix-local/LOG.md +0 -65
- arrayview-0.5.1/plans/tunnel-fix-local/PLAN.md +0 -148
- arrayview-0.5.1/src/arrayview/__init__.py +0 -5
- arrayview-0.5.1/src/arrayview/_viewer.html +0 -6837
- arrayview-0.5.1/src/arrayview/arrayview-opener.vsix +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/.claude/skills/modes-consistency/SKILL.md +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/.claude/skills/viewer-ui-checklist/SKILL.md +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/.github/workflows/python-publish.yml +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/.python-version +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/.tmp-vsix/extension/extension.js +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/.tmp-vsix/extension/package.json +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/LICENSE +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/scripts/demo.py +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_icon.png +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/src/arrayview/_shell.html +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/tests/conftest.py +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/tests/make_vectorfield_test_arrays.py +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/tests/test_large_arrays.py +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/tests/test_rgb_pixel_art.py +0 -0
- {arrayview-0.5.1 → arrayview-0.6.0}/vscode-extension/LICENSE +0 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: invocation-consistency
|
|
3
|
+
description: Use when implementing any server-side, startup, display-opening, or environment-detection feature in arrayview. Ensures the feature works correctly across all six ways arrayview can be launched — CLI, Python script, Jupyter, Julia, VS Code tunnel, and SSH.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ArrayView Invocation Consistency Checklist
|
|
7
|
+
|
|
8
|
+
## Rule
|
|
9
|
+
|
|
10
|
+
Every behavior that depends on *how* arrayview is started (server lifecycle, browser opening, display routing, port forwarding) must be verified across all six invocation paths before it is considered done.
|
|
11
|
+
|
|
12
|
+
## The Six Invocation Paths
|
|
13
|
+
|
|
14
|
+
| Path | Entry point | Display | Server model |
|
|
15
|
+
|------|------------|---------|-------------|
|
|
16
|
+
| **CLI** | `arrayview()` | native window → browser fallback | background thread, blocks until Ctrl-C |
|
|
17
|
+
| **Python script** | `view(arr)` | native window → browser fallback | daemon thread (dies with caller) |
|
|
18
|
+
| **Jupyter** | `view(arr)` | inline IFrame | daemon thread (persists across cells) |
|
|
19
|
+
| **Julia (PythonCall)** | `view(arr)` | system browser | always subprocess (`_view_julia()`) — GIL |
|
|
20
|
+
| **VS Code terminal** | `arrayview()` or `view()` | VS Code Simple Browser | in-process or subprocess |
|
|
21
|
+
| **Plain SSH (no VS Code)** | `arrayview()` or `view()` | prints port-forward hint | in-process or subprocess |
|
|
22
|
+
|
|
23
|
+
## Key Detection Functions (all in `_platform.py`)
|
|
24
|
+
|
|
25
|
+
```python
|
|
26
|
+
_in_jupyter() # ipykernel present → display inline IFrame
|
|
27
|
+
_in_vscode_terminal() # TERM_PROGRAM=vscode OR VSCODE_IPC_HOOK_CLI → use Simple Browser
|
|
28
|
+
_is_vscode_remote() # tunnel/SSH remote with VS Code server → shared signal fallback
|
|
29
|
+
_is_julia_env() # juliacall in sys.modules or julia in sys.executable → subprocess
|
|
30
|
+
_can_native_window() # pywebview available + not remote + display present → native window
|
|
31
|
+
_find_vscode_ipc_hook() # walk process tree for VSCODE_IPC_HOOK_CLI (stripped by uv run)
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Detection Order in `view()`
|
|
35
|
+
|
|
36
|
+
Julia → Jupyter → VS Code remote → VS Code terminal → local (native/browser)
|
|
37
|
+
|
|
38
|
+
Any new detection must not break the fallback chain for paths it shouldn't match.
|
|
39
|
+
|
|
40
|
+
## Julia-Specific Constraints
|
|
41
|
+
|
|
42
|
+
- Never run server in-process when `_is_julia_env()` is True (GIL conflicts)
|
|
43
|
+
- `_view_julia()` starts a detached subprocess; array is serialized to a temp `.npy` file
|
|
44
|
+
- No interactive stdin in subprocesses; all params encoded in CLI flags or files
|
|
45
|
+
|
|
46
|
+
## Jupyter-Specific Constraints
|
|
47
|
+
|
|
48
|
+
- `_in_jupyter()` returns True for ipykernel (VS Code notebook, JupyterLab, classic Notebook)
|
|
49
|
+
- Julia's IJulia kernel is NOT ipykernel → `_in_jupyter()` returns False in Julia notebooks
|
|
50
|
+
- `inline=True` → returns `IPython.display.IFrame`; caller must return it from the cell
|
|
51
|
+
- Port reuse: repeated `view()` calls reuse same port/server if already running
|
|
52
|
+
|
|
53
|
+
## Port & URL Rules
|
|
54
|
+
|
|
55
|
+
- Always use `localhost` (not `127.0.0.1`) so VS Code port-forwarding works
|
|
56
|
+
- Default port: `8123`; CLI: `--port` flag; `view()`: `port=` kwarg
|
|
57
|
+
|
|
58
|
+
## tmux and VS Code Terminal Detection
|
|
59
|
+
|
|
60
|
+
tmux breaks the ancestor-walk for `VSCODE_IPC_HOOK_CLI` because the process's parent chain goes through `tmux-server` (an independent daemon with no VS Code env), not through the VS Code terminal shell.
|
|
61
|
+
|
|
62
|
+
**Why `tmux show-environment` fails:** `VSCODE_IPC_HOOK_CLI` is not in tmux's default `update-environment` list.
|
|
63
|
+
|
|
64
|
+
**Why `#{client_pid}` alone fails:** returns only one client; breaks with multiple attached clients.
|
|
65
|
+
|
|
66
|
+
**Correct approach — enumerate all clients for the current session:**
|
|
67
|
+
|
|
68
|
+
```python
|
|
69
|
+
session_id = subprocess.run(["tmux", "display-message", "-p", "#{session_id}"], ...).stdout.strip()
|
|
70
|
+
client_pids = subprocess.run(
|
|
71
|
+
["tmux", "list-clients", "-t", session_id, "-F", "#{client_pid}"], ...
|
|
72
|
+
).stdout.strip().splitlines()
|
|
73
|
+
for pid_str in client_pids:
|
|
74
|
+
val = _ipc_from_pid(int(pid_str))
|
|
75
|
+
if val and os.path.exists(val):
|
|
76
|
+
return val
|
|
77
|
+
```
|
|
78
|
+
|
|
79
|
+
`list-clients -t <session_id>` scopes to current session only — prevents false-positive from a different VS Code window in another tmux session.
|
|
80
|
+
|
|
81
|
+
## Step-by-Step Checklist
|
|
82
|
+
|
|
83
|
+
1. **Server startup/teardown?**
|
|
84
|
+
- CLI: starts and terminates cleanly
|
|
85
|
+
- Python script: daemon thread doesn't orphan
|
|
86
|
+
- Jupyter: repeated calls don't fail on port-already-in-use
|
|
87
|
+
- Julia: `_view_julia()` passes new params via CLI flag or file
|
|
88
|
+
|
|
89
|
+
2. **Browser/display opening?**
|
|
90
|
+
- Local native window (macOS): `pywebview` opens correctly
|
|
91
|
+
- VS Code terminal: Simple Browser opens via extension signal file
|
|
92
|
+
- VS Code tunnel: reaches client-side Simple Browser, not remote host
|
|
93
|
+
- Verify `_ensure_vscode_extension()` installs/updates if VSIX changed
|
|
94
|
+
|
|
95
|
+
3. **Environment detection?**
|
|
96
|
+
- Check detection order: Julia → Jupyter → VS Code remote → VS Code terminal → local
|
|
97
|
+
- `_find_vscode_ipc_hook()` walks up to 12 parent processes; new subprocess wrappers may need the same
|
|
98
|
+
|
|
99
|
+
4. **VS Code extension changed?**
|
|
100
|
+
- Rebuild VSIX: `cd vscode-extension && vsce package -o ../src/arrayview/arrayview-opener.vsix`
|
|
101
|
+
- Bump `_VSCODE_EXT_VERSION` in `_vscode.py` and `vscode-extension/package.json` together
|
|
102
|
+
|
|
103
|
+
## Quick Automated Checks
|
|
104
|
+
|
|
105
|
+
```bash
|
|
106
|
+
uv run pytest tests/test_api.py -x
|
|
107
|
+
uv run pytest tests/test_cli.py -x
|
|
108
|
+
uv run python -c "from arrayview import view; import numpy as np; view(np.zeros((10,10)))"
|
|
109
|
+
```
|
|
110
|
+
|
|
111
|
+
## Red Flags — STOP
|
|
112
|
+
|
|
113
|
+
- "I changed server startup but only tested CLI" → test all paths
|
|
114
|
+
- "Works locally but not in tunnel" → check `_is_vscode_remote()` path and port exposure
|
|
115
|
+
- "I used `127.0.0.1` in the URL" → use `localhost`
|
|
116
|
+
- "I fixed tmux with `#{client_pid}`" → only works with one client; use `list-clients`
|
|
117
|
+
- "I bumped `package.json` but not `_VSCODE_EXT_VERSION`" → must stay in sync
|
|
118
|
+
- "Server starts but leaves an orphan after Ctrl-C" → check `_shutdown_event` and subprocess reaping
|
|
@@ -43,9 +43,13 @@ For each TODO item, ensure the following before marking the task done:
|
|
|
43
43
|
- [ ] README or in-app help overlay (`#help-overlay` content in `src/arrayview/_viewer.html`) updated if usage changed.
|
|
44
44
|
- [ ] `CHANGELOG.md` or `AGENTS.md` updated with a one-line summary.
|
|
45
45
|
- [ ] Commit message follows the format above and lists affected files.
|
|
46
|
-
- [ ]
|
|
47
|
-
-
|
|
48
|
-
|
|
46
|
+
- [ ] **Tests MUST be run by the agent — NEVER ask the user to run them.**
|
|
47
|
+
- After every code change, run the relevant test suite immediately:
|
|
48
|
+
- Backend/API changes → `uv run pytest tests/test_api.py -q`
|
|
49
|
+
- Viewer UI changes → `uv run pytest tests/test_interactions.py tests/test_mode_consistency.py -q`
|
|
50
|
+
- CLI changes → `uv run pytest tests/test_cli.py -q`
|
|
51
|
+
- If any test fails, fix it before considering the task done.
|
|
52
|
+
- Do NOT end a task with "run this to verify" — run it yourself and report results.
|
|
49
53
|
|
|
50
54
|
## How to use
|
|
51
55
|
|
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: ui-consistency-audit
|
|
3
|
+
description: Use when implementing or modifying any visual feature, layout, keyboard shortcut, or mode-specific behavior in arrayview. Proactively identifies all affected mode combinations before coding, prescribes per-mode behavior, checks for UI clashes, and runs a Playwright-based visual audit after implementation. Absorbs and replaces the modes-consistency skill.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# ArrayView UI Consistency Audit
|
|
7
|
+
|
|
8
|
+
## Rule
|
|
9
|
+
|
|
10
|
+
Every visual change must be verified across **all applicable mode × array-count combinations** before it ships. This skill has two phases: a **proactive phase** (before coding) that plans the cross-mode behavior, and a **reactive phase** (after coding) that runs an automated visual audit.
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## Phase 1: Proactive (Before Coding)
|
|
15
|
+
|
|
16
|
+
### Step 1 — Classify the Change
|
|
17
|
+
|
|
18
|
+
What area of the UI is being touched?
|
|
19
|
+
|
|
20
|
+
| Category | Examples |
|
|
21
|
+
|----------|---------|
|
|
22
|
+
| **Canvas rendering / sizing / zoom** | scaleCanvas, zoom cap, compact mode, miniview |
|
|
23
|
+
| **Chrome element** | colorbar, dimbar, eggs, array name, logo, miniview, colormap previewer |
|
|
24
|
+
| **Keyboard shortcut** | new or modified shortcut |
|
|
25
|
+
| **Mode entry/exit** | toggling multiview, qMRI, compare, diff, ROI, zen, compact |
|
|
26
|
+
| **Overlay feature** | ROI, vector field, histogram/Lebesgue, hover tooltip |
|
|
27
|
+
| **Multi-array layout** | compare pane alignment, diff center pane, grid layout |
|
|
28
|
+
|
|
29
|
+
### Step 2 — Enumerate Affected Modes
|
|
30
|
+
|
|
31
|
+
Use the code-area-to-mode mapping (Section 7) to list every mode combination that could be affected. Do not skip modes — if in doubt, include them.
|
|
32
|
+
|
|
33
|
+
### Step 3 — Prescribe Behavior Per Mode
|
|
34
|
+
|
|
35
|
+
For **each** affected mode, explicitly state:
|
|
36
|
+
|
|
37
|
+
1. How the feature behaves in this mode
|
|
38
|
+
2. Whether it's the same as normal mode or different (and why)
|
|
39
|
+
3. Which UI elements it could clash with (use the design rules in Section 5)
|
|
40
|
+
4. What happens at fit-to-window zoom AND zoomed-in (compact/miniview) zoom
|
|
41
|
+
|
|
42
|
+
### Step 4 — Check Against Design Rules
|
|
43
|
+
|
|
44
|
+
Walk through the principles (Section 5a) and specific rules (Section 5b). Flag any potential violations.
|
|
45
|
+
|
|
46
|
+
### Step 5 — Get Approval
|
|
47
|
+
|
|
48
|
+
Present the mode-by-mode behavior plan to the user before implementing.
|
|
49
|
+
|
|
50
|
+
---
|
|
51
|
+
|
|
52
|
+
## Phase 2: Reactive (After Coding)
|
|
53
|
+
|
|
54
|
+
After implementation, run the visual audit:
|
|
55
|
+
|
|
56
|
+
```bash
|
|
57
|
+
# Tier 1 only (quick, ~15 screenshots × 2 zoom levels)
|
|
58
|
+
uv run python tests/ui_audit.py --tier 1
|
|
59
|
+
|
|
60
|
+
# Full audit (tier 1 + tier 2, ~40 screenshots × 2 zoom levels)
|
|
61
|
+
uv run python tests/ui_audit.py
|
|
62
|
+
|
|
63
|
+
# Specific subset (when you know which modes are affected)
|
|
64
|
+
uv run python tests/ui_audit.py --tier 2 --subset zoom,compact,compare
|
|
65
|
+
|
|
66
|
+
# Update baselines after intentional changes
|
|
67
|
+
uv run python tests/ui_audit.py --update-baselines
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
**Output:** Terminal summary (pass/fail per combination) + screenshots in `tests/ui_audit/screenshots/`. Visual diffs in `tests/ui_audit/diffs/` when baselines exist.
|
|
71
|
+
|
|
72
|
+
### Step 3 — Visual Design Inspection (MANDATORY)
|
|
73
|
+
|
|
74
|
+
DOM assertions catch mechanical violations. They **cannot** catch:
|
|
75
|
+
- Bad layout decisions (elements in wrong position, wasted screen space)
|
|
76
|
+
- Overlapping elements not covered by a specific rule
|
|
77
|
+
- Aesthetically wrong choices (miniview floating in a weird spot, panes misaligned)
|
|
78
|
+
- Features that technically work but look broken to a human
|
|
79
|
+
|
|
80
|
+
After the audit script runs, you MUST:
|
|
81
|
+
|
|
82
|
+
1. **Read every screenshot** from the affected tier using the Read tool (it renders PNGs visually)
|
|
83
|
+
2. **For each screenshot, evaluate as a UI designer:**
|
|
84
|
+
- Are elements logically positioned? Would a user know where to look?
|
|
85
|
+
- Is screen real estate maximized? Are canvases as large as they can be?
|
|
86
|
+
- Do overlapping or adjacent elements clash? (colorbars, minimaps, eggs, labels)
|
|
87
|
+
- Is the layout consistent with the same feature in other modes?
|
|
88
|
+
- Would this look good on a 13" laptop screen?
|
|
89
|
+
3. **Report findings** — list every issue found, with the screenshot name and a concrete description
|
|
90
|
+
4. **Do NOT suppress assertions to make the audit pass.** If an assertion fails, the UI has a bug. Fix the UI, not the assertion. If the assertion is genuinely wrong (testing the wrong thing), fix the assertion AND explain why.
|
|
91
|
+
|
|
92
|
+
**Red flags for this step:**
|
|
93
|
+
- "The assertion failed so I marked it as zoomed=True" → NO. That's suppressing a real bug.
|
|
94
|
+
- "All assertions passed so the UI is fine" → NO. Assertions are a minimum bar, not a complete check.
|
|
95
|
+
- "I didn't look at the screenshots because the report said PASS" → NO. Always look.
|
|
96
|
+
|
|
97
|
+
**This is the verification step.** The `superpowers:verification-before-completion` skill requires running this before claiming work is done.
|
|
98
|
+
|
|
99
|
+
---
|
|
100
|
+
|
|
101
|
+
## Section 3: Mode Matrix & Tiers
|
|
102
|
+
|
|
103
|
+
### Tier 1 — Always Check
|
|
104
|
+
|
|
105
|
+
| Array config | Mode | Screenshot name |
|
|
106
|
+
|---|---|---|
|
|
107
|
+
| 1× 3D | normal (fit) | `t1_single_normal_fit` |
|
|
108
|
+
| 1× 3D | normal (zoomed) | `t1_single_normal_zoom` |
|
|
109
|
+
| 1× 3D | compact | `t1_single_compact` |
|
|
110
|
+
| 1× 3D | multiview | `t1_single_multiview_fit` |
|
|
111
|
+
| 1× 3D | multiview (zoomed) | `t1_single_multiview_zoom` |
|
|
112
|
+
| 1× 4D (dim0=5) | qMRI full | `t1_single_qmri_full` |
|
|
113
|
+
| 1× 4D (dim0=5) | qMRI compact | `t1_single_qmri_compact` |
|
|
114
|
+
| 1× 3D | zen | `t1_single_zen` |
|
|
115
|
+
| 2× 3D | compare | `t1_compare_fit` |
|
|
116
|
+
| 2× 3D | compare (zoomed) | `t1_compare_zoom` |
|
|
117
|
+
| 2× 3D | diff (A-B) | `t1_compare_diff` |
|
|
118
|
+
| 2× 3D | compare + multiview | `t1_compare_multiview` |
|
|
119
|
+
| 1× 3D | normal + ROI | `t1_single_roi` |
|
|
120
|
+
| 1× 3D | compact + ROI | `t1_compact_roi` |
|
|
121
|
+
|
|
122
|
+
### Tier 2 — Check When Relevant
|
|
123
|
+
|
|
124
|
+
| Array config | Mode | Trigger keywords |
|
|
125
|
+
|---|---|---|
|
|
126
|
+
| 1× 2D | normal | `2d, basic` |
|
|
127
|
+
| 1× 4D | normal | `4d, ndim` |
|
|
128
|
+
| 1× 4D | mosaic (z) | `mosaic, z-grid` |
|
|
129
|
+
| 1× complex | normal | `complex, dtype` |
|
|
130
|
+
| 2× 3D | diff |A-B| | `diff` |
|
|
131
|
+
| 2× 3D | diff |A-B|/|A| | `diff` |
|
|
132
|
+
| 2× 3D | overlay | `overlay, wipe, diff` |
|
|
133
|
+
| 2× 3D | wipe | `wipe, diff` |
|
|
134
|
+
| 2× 3D | registration | `registration` |
|
|
135
|
+
| 2× 4D | compare + qMRI | `qmri, compare` |
|
|
136
|
+
| 2× 3D | diff + ROI | `roi, diff` |
|
|
137
|
+
| 3× 3D | compare grid | `compare, grid, multi-array` |
|
|
138
|
+
| 3× 4D | compare + qMRI | `qmri, compare, multi-array` |
|
|
139
|
+
| 3× 3D | compare + multiview | `multiview, compare, multi-array` |
|
|
140
|
+
| 4× 3D | compare grid | `compare, grid, multi-array` |
|
|
141
|
+
| 3-4× 4D | compare + compact qMRI | `qmri, compare, compact, multi-array` |
|
|
142
|
+
| 1× 3D + vfield | vector field normal | `vector, vfield` |
|
|
143
|
+
| 1× 3D + vfield | vector field + ROI | `vector, roi` |
|
|
144
|
+
| 2× 3D (1+vfield) | compare with vector field | `vector, compare` |
|
|
145
|
+
|
|
146
|
+
---
|
|
147
|
+
|
|
148
|
+
## Section 4: The Six+ Modes (Reference)
|
|
149
|
+
|
|
150
|
+
| Mode | State flag | Scale function | Entry | Canvas elements |
|
|
151
|
+
|------|-----------|---------------|-------|-----------------|
|
|
152
|
+
| **Normal** | (default) | `scaleCanvas()` | — | `#canvas` |
|
|
153
|
+
| **Compact** | compact mode active | `scaleCanvas()` | K / auto on zoom | `#canvas` + miniview |
|
|
154
|
+
| **Multi-view** | `multiViewActive` | `mvScaleAllCanvases()` | v | `.mv-canvas` × 3 |
|
|
155
|
+
| **Compare** | `compareActive` | `compareScaleCanvases()` | B / P | `.compare-canvas` × 2–6 |
|
|
156
|
+
| **Diff** | `diffMode > 0` | `compareScaleCanvases()` | X (in compare) | `#compare-diff-canvas` |
|
|
157
|
+
| **Registration** | `registrationMode` | `compareScaleCanvases()` | R (in compare) | 3rd `.compare-canvas` |
|
|
158
|
+
| **qMRI** | `qmriActive` | `qvScaleAllCanvases()` | q | `.qv-canvas` × 3–6 |
|
|
159
|
+
| **Compact qMRI** | qMRI compact toggle | `qvScaleAllCanvases()` | q (second press) | `.qv-canvas` × 3 (T1,T2,|PD|) |
|
|
160
|
+
| **Zen** | zen mode | — | F | fullscreen, no chrome |
|
|
161
|
+
| **Vector field** | vectorfield attached | — | U toggle arrows | arrows on canvas |
|
|
162
|
+
|
|
163
|
+
**Orthogonal overlays** (can be active in any mode):
|
|
164
|
+
- **ROI** (A key): rect → circle → freehand → off
|
|
165
|
+
- **Histogram/Lebesgue** (w key): expanded colorbar with distribution
|
|
166
|
+
- **Hover tooltip** (H key): pixel value follows cursor
|
|
167
|
+
- **Overlay mask** (`overlay_sid` URL param): composited server-side
|
|
168
|
+
|
|
169
|
+
---
|
|
170
|
+
|
|
171
|
+
## Section 5a: Design Principles
|
|
172
|
+
|
|
173
|
+
1. **No overlapping chrome** — UI elements must never visually overlap each other or the canvas. If two elements compete for the same space, one must hide (e.g. colorbar hides when histogram is open).
|
|
174
|
+
|
|
175
|
+
2. **Consistent positioning** — an element appearing in mode A should appear in the same logical position in mode B. The miniview overlay and the mini 3D pane previewer must share a position (top-right).
|
|
176
|
+
|
|
177
|
+
3. **Zoom never clips** — canvases must stay within viewport bounds at all zoom levels. In multi-canvas modes, canvases grow until constrained, then zoom together with a single shared miniview (top-right, showing one representative slice).
|
|
178
|
+
|
|
179
|
+
4. **Maximize canvas real estate** — chrome is minimal and collapses when not needed. On a laptop screen, the array data is always the primary focus. No persistent toolbars or buttons.
|
|
180
|
+
|
|
181
|
+
5. **Mode transitions are reversible** — toggling a mode on then off returns to the exact prior state. No leftover artifacts, no shifted elements, no orphaned DOM.
|
|
182
|
+
|
|
183
|
+
6. **Orthogonal features compose cleanly** — ROI, vector field, histogram, colorbar, hover tooltip work independently. Enabling one must not break or interfere with another. ROI mode should be possible in any mode combination.
|
|
184
|
+
|
|
185
|
+
7. **Multi-array consistency** — when multiple arrays are loaded, all canvases zoom/scroll/navigate together. Colormaps on side panes match; diff center pane uses an independent colormap and dynamic range.
|
|
186
|
+
|
|
187
|
+
8. **Compare panes stay close** — pane slots shrink-wrap their canvas viewport when zoomed out so that panes remain adjacent for easy visual comparison. Never use fixed pane widths that create large gaps between canvases.
|
|
188
|
+
|
|
189
|
+
---
|
|
190
|
+
|
|
191
|
+
## Section 5b: Specific Rules (Growing Catalog)
|
|
192
|
+
|
|
193
|
+
These rules are codified as DOM assertions in `tests/ui_audit.py`. When a new inconsistency is found and fixed, add a rule here AND a corresponding assertion in the audit script.
|
|
194
|
+
|
|
195
|
+
| # | Rule | DOM assertion |
|
|
196
|
+
|---|------|---------------|
|
|
197
|
+
| R1 | Colorbar hides when histogram/Lebesgue is open | `#slim-cb-wrap` has `display:none` or `opacity:0` when Lebesgue mode active |
|
|
198
|
+
| R2 | Miniview position matches mini 3D pane previewer | Miniview overlay bounding box is anchored top-right, same position as multiview pane previewer |
|
|
199
|
+
| R3 | All canvases within viewport | Every `.compare-canvas`, `.mv-canvas`, `.qv-canvas`, `#canvas` bounding box fits within `window.innerWidth × innerHeight` |
|
|
200
|
+
| R4 | Diff colormaps: sides same, center different | Colormap name on diff center pane differs from side panes |
|
|
201
|
+
| R5 | Diff dynamic range is independent | vmin/vmax on diff center canvas differs from side panes |
|
|
202
|
+
| R6 | Eggs don't overlap colorbar | Bounding boxes of `#mode-eggs` children don't intersect `#slim-cb-wrap` |
|
|
203
|
+
| R7 | Fullscreen mode overlays chrome on canvas | `#info`, `#slim-cb-wrap` have `position:fixed` and glassmorphic styling when `body.fullscreen-mode` is active |
|
|
204
|
+
| R8 | ROI hover info within viewport | `.cv-pixel-info` bounding box is within viewport |
|
|
205
|
+
| R9 | No element jump on mode toggle | Pressing a mode toggle key then pressing it again returns bounding boxes to ±2px of original |
|
|
206
|
+
| R10 | Compare panes aligned | All `.compare-canvas` elements share the same `y` coordinate (horizontal layout) or same `x` (vertical layout) |
|
|
207
|
+
| R11 | Vector field arrows hidden when U toggled off | No arrow SVG/canvas elements visible after U press |
|
|
208
|
+
| R12 | Histogram not visible when colorbar is in slim mode | Lebesgue expanded state is mutually exclusive with slim colorbar |
|
|
209
|
+
| R13 | Colorbars don't overlap each other | No pair of colorbar elements (slim, compare-pane, mv, qv) have intersecting bounding boxes |
|
|
210
|
+
| R14 | Minimap within viewport and not overlapping colorbars | `#minimap-canvas` bounding box is within viewport and doesn't intersect any colorbar |
|
|
211
|
+
| R15 | Colorbar labels flank horizontally in all modes | Every `.cb-island`, `.compare-pane-cb-island`, `.qv-cb-island` uses `flex-direction: row` with vmin/vmax spans flanking the gradient canvas — never column layout with labels below |
|
|
212
|
+
| R16 | Per-pane colorbar width matches canvas viewport width (diff mode only) | Each `.compare-pane-cb` and `.qv-cb` canvas CSS width equals its data canvas viewport width. Per-pane colorbars only visible in diff mode. |
|
|
213
|
+
| R17 | Fullscreen mode eggs are visible over image | `#mode-eggs .mode-badge` elements have sufficient background opacity (>= 0.5) when `body.fullscreen-mode` is active, since they overlay the canvas |
|
|
214
|
+
| R18 | Histogram has visible background distinct from page | When `_cbExpanded`, the histogram area has an explicit background fill matching the dynamic island aesthetic, not transparent over the page background |
|
|
215
|
+
| R19 | Shared colorbar visible in all non-diff compare modes | `#slim-cb-wrap` is visible when `compareActive && !diffMode`. Per-pane colorbars hidden. |
|
|
216
|
+
| R20 | Compare panes shrink-wrap when zoomed out | Pane slot width ≤ viewport canvas width + padding, not fixed to max available. Panes stay close for visual comparison |
|
|
217
|
+
| R21 | Per-pane colorbars visible only in diff mode | `.compare-pane-cb-island` elements visible only when `diffMode > 0`. Three colorbars (left, center, right) at same vertical position. |
|
|
218
|
+
| R22 | Array names include logo in compare mode | Each `.compare-title` contains SVG logo + name text, styled like `#array-name` |
|
|
219
|
+
| R23 | Fullscreen mode overlays are glassmorphic islands | When `body.fullscreen-mode`, `#info` and `#slim-cb-wrap` have glassmorphic styling with `backdrop-filter: blur(12px)` |
|
|
220
|
+
| R24 | Diff c/d keys are mouse-aware | When `diffMode > 0`, `c` and `d` keys affect center pane when `_mouseOverCenterPane`, side panes otherwise |
|
|
221
|
+
| R25 | No duplicate colorbars in multiview | `#slim-cb-wrap` hidden when `multiViewActive` or `compareMvActive` (multiview has its own per-pane colorbars) |
|
|
222
|
+
| R26 | Shared colorbar width capped in compare mode | `#slim-cb-wrap` width ≤ 500px in compare mode |
|
|
223
|
+
| R27 | Mode eggs hidden during loading | `#mode-eggs` empty/hidden until at least one frame has been received |
|
|
224
|
+
| R28 | Compare+multiview crosshair fade works | Crosshair lines in compare+multiview fade in/out on hover, scoped to the hovered array's panes |
|
|
225
|
+
|
|
226
|
+
---
|
|
227
|
+
|
|
228
|
+
## Section 5c: Colorbar Layout Invariant
|
|
229
|
+
|
|
230
|
+
All colorbars across ALL modes follow the same structure: `[vmin span] [gradient canvas] [vmax span]` in a horizontal flex row inside a `.cb-island` container. This applies to:
|
|
231
|
+
- Normal mode: `#slim-cb-wrap` with `#slim-cb-vmin`, `#slim-cb`, `#slim-cb-vmax`
|
|
232
|
+
- Compare/diff: `.compare-pane-cb-island` with `.compare-pane-cb-val` spans + `.compare-pane-cb` canvas
|
|
233
|
+
- qMRI: `.qv-cb-island` with `.qv-cb-val` spans + `.qv-cb` canvas
|
|
234
|
+
|
|
235
|
+
**Never** use column layout with labels below. **Never** use `flex: 1` on colorbar canvases inside inline-flex islands (causes sizing loops). Always set explicit island width = viewport canvas width, with `box-sizing: border-box` so `.cb-island` padding doesn't inflate the width beyond the canvas. Always test with non-square arrays — square arrays hide width mismatches.
|
|
236
|
+
|
|
237
|
+
**Colorbar-to-canvas gap**: The gap between canvas and colorbar must be visually consistent across modes. Both single and compare mode use the shared `#slim-cb-wrap` colorbar positioned via `chromeGap` (measured from `#info` bottom to canvas top). Per-pane colorbars (`.compare-pane-cb-island`) are only visible in diff mode.
|
|
238
|
+
|
|
239
|
+
**Compare mode colorbar**: A single shared `#slim-cb-wrap` replaces per-pane colorbars. All arrays share colormap and dynamic range (union of all vmins/vmaxes). Histogram aggregates bins from all sessions. Per-pane colorbars only appear in diff mode (3 colorbars: left, center, right).
|
|
240
|
+
|
|
241
|
+
---
|
|
242
|
+
|
|
243
|
+
## Section 6: Common Feature Categories
|
|
244
|
+
|
|
245
|
+
### Zoom / Canvas Sizing
|
|
246
|
+
|
|
247
|
+
Four dedicated scale functions. When changing zoom behavior, check **all four**:
|
|
248
|
+
|
|
249
|
+
- `scaleCanvas(w, h)` — Normal/Compact
|
|
250
|
+
- `mvScaleAllCanvases()` — Multi-view
|
|
251
|
+
- `compareScaleCanvases()` — Compare / Diff / Registration
|
|
252
|
+
- `qvScaleAllCanvases()` — qMRI
|
|
253
|
+
|
|
254
|
+
### Eggs (Mode Badges)
|
|
255
|
+
|
|
256
|
+
Positioned by `positionEggs()` which branches by mode:
|
|
257
|
+
- Normal: uses `#slim-cb-wrap` bounding box if visible, else canvas bottom
|
|
258
|
+
- Multi-view: uses `#mv-cb-wrap` or estimates 36px below panes
|
|
259
|
+
- Compare: walks `.compare-canvas-wrap` rects for tallest pane bottom
|
|
260
|
+
- qMRI: uses `.qv-canvas-wrap` rects + 36px
|
|
261
|
+
|
|
262
|
+
### Colorbar
|
|
263
|
+
|
|
264
|
+
Separate per-mode drawing functions — no shared abstraction:
|
|
265
|
+
|
|
266
|
+
| Mode | Function |
|
|
267
|
+
|------|----------|
|
|
268
|
+
| Normal | `drawSlimColorbar(markerFrac)` |
|
|
269
|
+
| Compare | `drawComparePaneCb(idx)` + `drawAllComparePaneCbs()` |
|
|
270
|
+
| Diff | `drawDiffPaneCb(vmin, vmax)` |
|
|
271
|
+
| Registration | `drawRegBlendCb()` |
|
|
272
|
+
| Multi-view | `drawMvCbs()` |
|
|
273
|
+
| qMRI | Inline per view in `qvRender()` |
|
|
274
|
+
|
|
275
|
+
### Keyboard Shortcuts
|
|
276
|
+
|
|
277
|
+
The `keydown` handler on `#keyboard-sink` dispatches by active mode. New shortcuts must:
|
|
278
|
+
1. Not conflict with mode-specific keys
|
|
279
|
+
2. Have explicit guards when mode-specific (e.g., `if (!compareActive) return;`)
|
|
280
|
+
3. Fall through correctly when multiple modes are active
|
|
281
|
+
|
|
282
|
+
---
|
|
283
|
+
|
|
284
|
+
## Section 7: Code-Area-to-Mode Mapping
|
|
285
|
+
|
|
286
|
+
Use this to determine which tier 2 subsets to audit based on what code you're touching.
|
|
287
|
+
|
|
288
|
+
| Code area | Tier 2 subsets to include |
|
|
289
|
+
|-----------|--------------------------|
|
|
290
|
+
| `scaleCanvas` / zoom / `userZoom` | `zoom, compact` — all zoom-related modes |
|
|
291
|
+
| `compareScaleCanvases` / `.compare-` | `compare, diff, registration, grid, multi-array` |
|
|
292
|
+
| `mvScaleAllCanvases` / `.mv-` | `multiview` — single + compare multiview |
|
|
293
|
+
| `qvScaleAllCanvases` / `.qv-` / `qmri` | `qmri, compare` — single + compare qMRI, compact qMRI |
|
|
294
|
+
| `positionEggs` / `renderEggs` / `#mode-eggs` | all modes (eggs appear everywhere) |
|
|
295
|
+
| `drawSlimColorbar` / `#slim-cb` | `zoom, compact, diff` — colorbar visibility rules |
|
|
296
|
+
| Lebesgue / histogram / `w` key | `zoom, compact, diff` — mutual exclusivity with colorbar |
|
|
297
|
+
| ROI / `A` key / `.roi-` | `roi` — ROI across all modes |
|
|
298
|
+
| `diffMode` / `X` key / `#compare-diff` | `diff` — all diff variants |
|
|
299
|
+
| Vector field / `U` key / arrows | `vector` — normal, ROI, compare |
|
|
300
|
+
| `#minimap` / miniview | `zoom, compact, multiview` — position consistency |
|
|
301
|
+
| Overlay / `overlay_sid` | `overlay` — server-side compositing |
|
|
302
|
+
| Layout / `G` key / grid | `grid, multi-array` — 3-4 array layouts |
|
|
303
|
+
|
|
304
|
+
---
|
|
305
|
+
|
|
306
|
+
## Section 8: Test Arrays
|
|
307
|
+
|
|
308
|
+
The audit script creates these arrays to exercise different code paths:
|
|
309
|
+
|
|
310
|
+
| Array | Shape | Purpose |
|
|
311
|
+
|-------|-------|---------|
|
|
312
|
+
| `arr_3d` | (20, 64, 64) float32 | Primary test array, 3D navigation |
|
|
313
|
+
| `arr_3d_b` | (20, 64, 64) float32 | Second 3D for compare |
|
|
314
|
+
| `arr_2d` | (100, 80) float32 | 2D baseline |
|
|
315
|
+
| `arr_4d` | (5, 20, 32, 32) float32 | qMRI eligible (dim0=5) |
|
|
316
|
+
| `arr_4d_b` | (5, 20, 32, 32) float32 | Second 4D for compare qMRI |
|
|
317
|
+
| `arr_complex` | (20, 32, 32) complex64 | Complex dtype, triggers mag/phase/real/imag |
|
|
318
|
+
| `arr_3d_qmri3` | (3, 20, 32, 32) float32 | 3-panel qMRI (dim0=3) |
|
|
319
|
+
| `vf_3d` | (20, 64, 64, 3) float32 | Vector field (N+1 dims, last dim=3) |
|
|
320
|
+
| `arr_3d_c` | (20, 64, 64) float32 | Third array for 3-pane compare |
|
|
321
|
+
| `arr_3d_d` | (20, 64, 64) float32 | Fourth array for 4-pane grid |
|
|
322
|
+
|
|
323
|
+
---
|
|
324
|
+
|
|
325
|
+
## Red Flags — STOP
|
|
326
|
+
|
|
327
|
+
- "I only implemented it for normal mode" → enumerate all affected modes now
|
|
328
|
+
- "The colorbar is visible but the histogram is also showing" → R1 violation
|
|
329
|
+
- "It works but I didn't check zoomed-in" → run both zoom levels
|
|
330
|
+
- "ROI mode isn't relevant here" → ROI is always relevant, it's orthogonal
|
|
331
|
+
- "Compare mode doesn't need this" → if it affects the canvas or chrome, compare needs it
|
|
332
|
+
- "I'll update the audit script later" → update it in the same task as the rule
|
|
333
|
+
- "The screenshots look fine to me" → also check the DOM assertions passed
|
|
334
|
+
- "The assertion failed so I set zoomed=True to skip it" → that's suppressing a real bug, fix the UI
|
|
335
|
+
- "All assertions passed so no need to look at screenshots" → assertions are a minimum bar, always visually inspect
|
|
336
|
+
- "Vector field is separate" → still check it doesn't clash with the new feature
|
|
@@ -16,12 +16,15 @@ wheels/
|
|
|
16
16
|
CLAUDE.md
|
|
17
17
|
debug/
|
|
18
18
|
tests/snapshots/
|
|
19
|
+
tests/ui_audit/
|
|
20
|
+
tests/smoke_output/
|
|
19
21
|
|
|
20
22
|
*.jl
|
|
21
23
|
*.ipynb
|
|
22
|
-
|
|
24
|
+
dev/
|
|
23
25
|
*.png
|
|
24
26
|
!src/arrayview/_icon.png
|
|
25
|
-
.worktrees/
|
|
26
27
|
|
|
27
|
-
benchmarks/data
|
|
28
|
+
benchmarks/data
|
|
29
|
+
.superpowers/brainstorm
|
|
30
|
+
site/
|
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
# ArrayView Agent Guide
|
|
2
|
+
|
|
3
|
+
## Mission
|
|
4
|
+
|
|
5
|
+
Interactive viewer for multi-dimensional arrays and medical/scientific volumes. Runs a FastAPI server + HTML/JS frontend displayed in a native window, browser, VS Code Simple Browser, or Jupyter inline iframe.
|
|
6
|
+
|
|
7
|
+
**Display routing defaults (sane defaults per environment):**
|
|
8
|
+
|
|
9
|
+
| Where you are | Default display |
|
|
10
|
+
|---|---|
|
|
11
|
+
| Jupyter kernel | inline iframe |
|
|
12
|
+
| VS Code terminal (local or tunnel) | VS Code Simple Browser tab |
|
|
13
|
+
| Julia | system browser |
|
|
14
|
+
| otherwise (CLI, Python script) | native pywebview window |
|
|
15
|
+
|
|
16
|
+
VS Code Simple Browser requires the bundled `arrayview-opener.vsix` extension. VS Code provides no public API for auto-opening Simple Browser tabs — the extension uses signal-file IPC. This is fragile; load the `vscode-simplebrowser` skill before touching it.
|
|
17
|
+
|
|
18
|
+
## Architecture
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
CLI / Python API
|
|
22
|
+
|
|
|
23
|
+
+- view() Python entry (_launcher.py)
|
|
24
|
+
+- arrayview() CLI entry (_launcher.py)
|
|
25
|
+
|
|
|
26
|
+
+- FastAPI server (_server.py)
|
|
27
|
+
+- / viewer HTML
|
|
28
|
+
+- /shell pywebview shell HTML
|
|
29
|
+
+- /ws/{sid} WebSocket render updates
|
|
30
|
+
+- /load register arrays
|
|
31
|
+
+- /ping health check
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
## Core Files
|
|
35
|
+
|
|
36
|
+
### Backend
|
|
37
|
+
|
|
38
|
+
| File | Responsibility |
|
|
39
|
+
|------|---------------|
|
|
40
|
+
| `_launcher.py` | Entry points, process management, window/browser opening |
|
|
41
|
+
| `_server.py` | FastAPI app, REST routes, WebSocket handlers, HTML templates |
|
|
42
|
+
| `_session.py` | Sessions, global state, caches, render thread, constants |
|
|
43
|
+
| `_render.py` | Rendering: colormaps, LUTs, slice extraction, RGBA/mosaic/RGB |
|
|
44
|
+
| `_vscode.py` | Extension management, signal-file IPC, browser opening |
|
|
45
|
+
| `_platform.py` | Platform/environment detection |
|
|
46
|
+
| `_io.py` | Array I/O, format detection |
|
|
47
|
+
| `_app.py` | **Compat shim only** — re-exports; add no logic here |
|
|
48
|
+
|
|
49
|
+
### Frontend
|
|
50
|
+
|
|
51
|
+
| File | Responsibility |
|
|
52
|
+
|------|---------------|
|
|
53
|
+
| `_viewer.html` | Viewer UI (single file, all JS/CSS embedded) |
|
|
54
|
+
| `_shell.html` | Shell page for native tab/window management |
|
|
55
|
+
|
|
56
|
+
### VS Code Extension
|
|
57
|
+
|
|
58
|
+
| File | Responsibility |
|
|
59
|
+
|------|---------------|
|
|
60
|
+
| `vscode-extension/extension.js` | Polls signal file, opens `createWebviewPanel` |
|
|
61
|
+
| `vscode-extension/package.json` | Extension metadata and version |
|
|
62
|
+
| `arrayview-opener.vsix` | Packaged extension (auto-installed by Python) |
|
|
63
|
+
|
|
64
|
+
## Skills
|
|
65
|
+
|
|
66
|
+
Load the relevant skill before touching the corresponding area.
|
|
67
|
+
|
|
68
|
+
| Skill | Load when... |
|
|
69
|
+
|-------|-------------|
|
|
70
|
+
| `vscode-simplebrowser` | Touching extension install, signal-file IPC, `_ensure_vscode_extension()`, `_VSCODE_EXT_VERSION`, or `vscode-extension/` |
|
|
71
|
+
| `invocation-consistency` | Any server startup, display-opening, or environment-detection change |
|
|
72
|
+
| `ui-consistency-audit` | Any visual feature: zoom, colorbars, canvas events, rendering modes. Or UI changes: keyboard shortcuts, layout, new panels |
|
|
73
|
+
| `frontend-designer` | Any styling/layout change to `_viewer.html` |
|
|
74
|
+
| `docs-style` | Updating README, in-app help overlay, or docstrings |
|
|
75
|
+
| `task-workflow` | Feature or fix tasks |
|
|
76
|
+
|
|
77
|
+
## Non-Negotiables
|
|
78
|
+
|
|
79
|
+
- Always use `localhost` (not `127.0.0.1`) — required for VS Code port forwarding
|
|
80
|
+
- Never `--force` reinstall the extension if the correct version is already on disk
|
|
81
|
+
- Do not add logic to `_app.py` — compat shim only
|
|
82
|
+
- Avoid orphan processes; shutdown must be automatic
|
|
83
|
+
- Do not regress working display paths when fixing another
|
|
84
|
+
|
|
85
|
+
## Testing
|
|
86
|
+
|
|
87
|
+
```bash
|
|
88
|
+
uv sync --group test && uv run playwright install chromium
|
|
89
|
+
|
|
90
|
+
uv run pytest tests/test_api.py -v # HTTP API (~2s)
|
|
91
|
+
uv run pytest tests/test_cli.py -v # CLI entry points
|
|
92
|
+
uv run pytest tests/test_browser.py -v # Playwright (~100s)
|
|
93
|
+
uv run pytest tests/ # all
|
|
94
|
+
|
|
95
|
+
uv run python tests/visual_smoke.py # screenshots → tests/smoke_output/
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Visual baselines: `tests/snapshots/` — delete a file to reset.
|
|
99
|
+
|
|
100
|
+
| What changed | Run |
|
|
101
|
+
|---|---|
|
|
102
|
+
| Server / API | `test_api.py` |
|
|
103
|
+
| CLI | `test_cli.py` |
|
|
104
|
+
| Viewer UI | `test_browser.py` + `visual_smoke.py` |
|
|
105
|
+
| VS Code / platform | manual: VS Code local terminal, VS Code tunnel |
|
|
106
|
+
|
|
107
|
+
## VS Code Integration
|
|
108
|
+
|
|
109
|
+
All key functions live in `_vscode.py`:
|
|
110
|
+
- `_ensure_vscode_extension()` — installs VSIX only if version not already on disk
|
|
111
|
+
- `_open_via_signal_file()` — writes signal; extension polls and opens panel
|
|
112
|
+
- `_schedule_remote_open_retries()` — retry writes for tunnel/first-install latency
|
|
113
|
+
- `_configure_vscode_port_preview()` — port forwarding settings
|
|
114
|
+
|
|
115
|
+
`_VSCODE_EXT_VERSION` in `_vscode.py` must match `vscode-extension/package.json`.
|
|
116
|
+
Rebuild: `cd vscode-extension && vsce package -o ../src/arrayview/arrayview-opener.vsix`
|
|
117
|
+
|
|
118
|
+
## High-Risk Areas
|
|
119
|
+
|
|
120
|
+
- Extension install: skip `--force` if correct version on disk; clean stale dirs first
|
|
121
|
+
- IPC hook recovery when env vars are stripped by `uv run` or subprocess wrappers
|
|
122
|
+
- tmux: `VSCODE_IPC_HOOK_CLI` not inherited through tmux-server; must walk client PIDs
|
|
123
|
+
- Signal routing: local → per-window targeted file; remote/tunnel → shared fallback
|
|
124
|
+
- Julia: always subprocess (GIL); never run server in-process
|
|
125
|
+
- Port forwarding in tunnel environments; shutdown lifecycle and orphan process prevention
|
|
126
|
+
|
|
127
|
+
## Documentation
|
|
128
|
+
|
|
129
|
+
Docs live in `docs/` (MkDocs Material site) and `README.md` (minimal quickstart).
|
|
130
|
+
|
|
131
|
+
**Style:** quiet confidence — short sentences, lead with code, imply depth rather than explain everything. No verbose prose, no "Note:", no emojis. Load the `docs-style` skill before editing.
|
|
132
|
+
|
|
133
|
+
**Keep docs in sync:** when a commit adds, changes, or removes user-facing behavior (new shortcut, changed flag, removed feature), update the relevant docs page and README before committing. Skip this for purely internal changes.
|
|
134
|
+
|
|
135
|
+
## Source of Truth
|
|
136
|
+
|
|
137
|
+
End-user usage: `README.md` and `docs/`
|