Conversation
… backward compatibility for unknown schema versions and what error views will be supported
First official technical design outline for the mrd-viz extension. Small questions regarding error handling and backwards compatibility remain open and will be answered with further real-world testing.
Add MRD Viz scaffold
Escape '<' before embedding to avoid injection/unexpected behavior for JSON payload handling Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Removed unused import of Counter from collections.
…ly break for extract_image command, clarified schema_version html_harness definitions, unified backend default values, added instructions for generating mrd test files
…ds, added test case for this functionality
Add MRD Viz backend CLI Test through `OFFICIAL_EXT_DEV_RUNBOOK.md` which has powershell and linux bash commands, guides users through generating test mrd files Test suite also added and will be expanded upon, will run on any PRs targetting carter-mrd-viz
viewer.js is the esbuild bundle output built by npm run build:webview and regenerated automatically on vsce package via the vscode:prepublish hook (CI runs npm ci + vsce package), so the committed copy is never consumed at package time and only produces noisy diffs. Add a .gitignore for the extension and untrack the file.
mrd-viz: stop tracking generated webview bundle (media/viewer.js)
mrd-viz CI: drop flaky darwin-x64 (Intel macos-13) build leg
Address PR #82 review (Yulia): - removeIncompleteVenv now retries rm a few times and, if the directory still can't be deleted (e.g. a Windows file lock), renames it aside so its interpreter path no longer exists and the resolver skips the candidate; best-effort deletes the quarantined dir. - Invalidate the backend cache on the provisioning-failure path so a torn-down venv can't stay cached. - Export classifyProvisioningFailure and removeIncompleteVenv; add unit tests for the failure hints (confirming the mrd-viz pip name) and the venv cleanup.
mrd-viz: clean up failed backend venv + clearer setup errors
…ementation plan Design docs backing the deterministic backend-resolution work: current-state analysis, the two-audience target architecture, levels-of-abstraction diagram, and the phased implementation plan. Base commit for the resolver rewrite (PR1).
Replace the 5-candidate search with a pure planBackendCandidates(): a configured override is the ONLY candidate (no silent fallback), otherwise the bundled binary, with the repo .venv tried first only in the F5 Development host. Adds BackendKind and getConfiguredBackendPath (backendPath, then legacy explicit pythonPath). Drops managed-venv and PATH candidates.
selectInterpreter and the guided setup now write the machine-scoped mrdViz.backendPath (no more workspace scope-clearing needed), so the managed venv becomes a first-class override rather than an implicit resolver candidate. Config invalidation watches backendPath and pythonPath.
Lead with the relevant explanation and primary action: a broken mrdViz.backendPath override vs. a bundled backend that won't run on this platform. Update references to backendPath.
Uses mrdViz.backendPath (applied as a container remote setting) instead of the deprecated pythonPath, so the container interpreter can't leak to the host.
…implemented Unit-test planBackendCandidates (override no-fallback, dev vs prod order, binary classification, empty) and the tailored override page. Update BACKEND_INSTALL_MODES (target -> implemented) and DEVCONTAINER (machine-scoped backendPath, leak now structurally prevented).
…et/toast/doc - Override candidate/logs and the backend-missing page now name the setting the value came from (backendPath vs legacy pythonPath) instead of always backendPath. - Setup snippet installs editable from a checkout (mrd-viz is not on PyPI). - Interpreter-selection toast is setting-neutral (path may be a binary). - Reconcile BACKEND_INSTALL_MODES intro (pre-refactor, not current). - Tests for the legacy-setting labeling.
- centralize magic timeouts/buffers/names in backendConstants.ts - add runProcess() execFile wrapper; use it in resolver, runner, provisioning - use PYPI package name mrd-viz and add --disable-pip-version-check --no-input to pip; TODO(publish-pypi) - drop unused getConfiguredBackendPath export - refresh stale mrdViz.pythonPath -> mrdViz.backendPath references in docs and postCreate - update backend-missing test fixture to realistic post-refactor sources
The un-scoped legacy setting could leak across machines (e.g. a Linux path synced into Windows User settings) and, treated as an exclusive override, suppressed the bundled binary and forced the backend-not-found page. Only the machine-scoped mrdViz.backendPath is honored now. - resolver reads only mrdViz.backendPath; remove legacy inspect() branch and settingKey plumbing - remove deprecated mrdViz.pythonPath declaration and config-change watch - simplify backend-missing intro to name mrdViz.backendPath; drop two legacy tests
…etworks Corporate networks block public PyPI (files.pythonhosted.org SSL handshake failure) and public npm. Add select-pkg-index.sh, a candidate-ladder helper sourced by postCreate and container-setup that probes the Microsoft-internal mirror first and falls back to the public registry, exporting PIP_INDEX_URL/npm_config_registry. Candidate lists live in devcontainer.json remoteEnv (discoverable, external devs still work). Prints the exact export line on total failure; documented in DEVCONTAINER.md.
- resolveBackend now returns an explicit 'no backend available' failure when there are no candidates (no override + no bundled binary), instead of an empty attempt list the webview can't explain (yuliadub). - Extract the hardcoded 'mrd-viz' binary name to BACKEND_BINARY_NAME so a rename touches one place (yuliadub). - setUpBackend confirms a Reinstall when a managed backend already exists, rather than silently rebuilding it (yuliadub).
mrd-viz: deterministic two-tier backend resolution
FeoluK
left a comment
There was a problem hiding this comment.
Code Review: Add mrd-viz extension and backend
Scope: new VS Code custom editor, webview viewer, Python analysis backend, tests, packaging, and CI/release workflows.
| Severity | Count | Status |
|---|---|---|
| CRITICAL | 0 | pass |
| HIGH | 3 | blocking |
| MEDIUM | 5 | needs follow-up |
| LOW | 0 | pass |
Verdict: REQUEST CHANGES — PR validation is not running for the target branch, release tooling is mutable, and initial inspection can fully scan large acquisition-only files. (Filed as a comment review rather than a blocking one; inline findings below.)
Highlights
- [HIGH] Both
mrd-vizworkflows filterpull_request.branchestocarter-mrd-viz, but that filter matches the base branch — this PR targetsmain, so neither workflow runs here. - [HIGH] The release job runs unversioned
npx --yes @vscode/vsceand uses the mutablesoftprops/action-gh-release@v2tag withcontents: write. - [HIGH] Initial-open termination is driven only by the image-thumbnail limit, so acquisition-only files are read through EOF.
- [MEDIUM] Classification retains per-image metadata; response guards only check
ok; superseded image requests are not cancelled; the image cache is entry-bounded but not byte-bounded; sparse--sliceaxes can allocate enormous tuples.
Architecture notes
Layering is clean: the host owns the custom editor, backend resolution, process execution and panel-lifetime cancellation; the webview owns selection, mosaic presentation, viewport state and the image cache; the backend streams and classifies MRD items. Trust boundaries are mostly well handled — file: URI and extension checks, machine-scoped executable override, execFile argv arrays, timeouts, max buffer, windowsHide, CSP/nonces and textContent rendering. The two gaps are incomplete schema validation of successful payloads, and correlation-without-cancellation (stale results are dropped but their processes keep running).
This design uses short-lived subprocesses that reopen and rescan the MRD for each image request. A persistent backend session would trade lifecycle complexity for lower repeated I/O and natural request cancellation — worth considering if large raw files become a common case.
Recommendations (shared standards)
- Define one versioned message-contract schema with complete runtime guards for host, webview and backend payloads.
- Add malicious-workspace coverage for executable settings and Workspace Trust behavior.
- Add adversarial and very large MRD fixtures with explicit time, item and memory budgets.
- Add cancellation/race tests that issue overlapping image and mosaic requests.
- Enforce a byte budget for webview caches and test eviction with large payloads.
- Add CI checks for CSP/nonce rules, VSIX contents, pinned release tooling and cross-platform subprocess behavior.
- Keep structured logs redacted so image data and file contents are not emitted.
Tests currently cover backend payload behavior, manifest contributions, escaping, request guards, resolver ordering and provisioning cleanup. The notable gaps are controller concurrency, webview response handling, cache budgets and very large raw files.
| try: | ||
| with mrd.BinaryMrdReader(str(path), skip_completed_check=not options.read_full_stream) as reader: | ||
| header = reader.read_header() | ||
| if header is None: | ||
| return _error_payload(path, "Missing MRD header") | ||
|
|
||
| state = _new_state(path, header) | ||
| for stream_index, item in enumerate(reader.read_data()): | ||
| should_stop = _consume_stream_item(state, item, stream_index, options) | ||
| if should_stop and not options.read_full_stream: | ||
| state["stream"]["partial"] = True | ||
| break | ||
|
|
There was a problem hiding this comment.
[HIGH] Initial inspection fully scans acquisition-only MRD streams
The loop's stop condition is driven by the image-thumbnail limit, but acquisition handling always continues and acquisitions never contribute to that limit. An acquisition-only (raw) file is therefore read through EOF on every open, with its arrays materialized.
Impact: opening a large raw MRD can perform multi-gigabyte I/O and repeatedly exceed the extension's 30-second timeout before any metadata is shown.
Fix: apply an independent item / byte / wall-clock budget to initial inspection and mark counts and classification as partial when the budget trips. Reserve complete scans for an explicit user-invoked operation.
Confidence: high
There was a problem hiding this comment.
Deferred until later - larger behavioral change. Thanks for the detailed writeup.
| renderSelectedTile(state.selectedTileThumb, 'Loading full-resolution image...'); | ||
| const requestId = nextRequestId(); | ||
| state.pendingRequestId = requestId; | ||
| state.pendingRequestIndex = state.selectedImageIndex; | ||
| state.pendingRequestCoords = state.selectedSliceCoords.slice(); | ||
| postMessage({ | ||
| type: 'loadImage', | ||
| requestId: requestId, | ||
| imageIndex: state.selectedImageIndex, | ||
| sliceCoords: state.selectedSliceCoords.slice() | ||
| }); |
There was a problem hiding this comment.
[MEDIUM] Superseded image requests keep expensive subprocesses running
A new requestId correctly prevents a stale response from updating the UI, but nothing cancels the in-flight backend process for the previous request — image requests share the panel-lifetime abort signal rather than a per-request controller.
Impact: rapid tile/slice navigation can leave several Python processes concurrently rescanning the same large MRD for up to 30 seconds each.
Fix: abort the previous selected-image request when it is superseded, or add an explicit cancellation message keyed by request ID.
Confidence: high
There was a problem hiding this comment.
Deferred until later - larger behavioral change.
| options: BackendRunnerOptions, | ||
| outputChannel: vscode.OutputChannel, | ||
| signal: AbortSignal, | ||
| message: Extract<ViewerToExtensionMessage, { type: 'loadImage' }>, | ||
| ): Promise<void> { | ||
| try { | ||
| const { payload, stderr } = await runImage(targetUri.fsPath, message.imageIndex, options, signal, message.sliceCoords); |
There was a problem hiding this comment.
[MEDIUM] Host side of the missing per-request cancellation
runImage() receives the shared panel-lifetime signal, so a superseded request has no way to be aborted independently of panel disposal.
Fix: create a per-request AbortController (linked to the panel signal), track it by request ID, and abort the prior one when a newer loadImage/mosaic request arrives.
Confidence: high
There was a problem hiding this comment.
Deferred until later - larger behavioral change.
| const MAX_IMAGE_CACHE_ENTRIES = Number(config.maxImageCacheEntries) || 32; | ||
|
|
||
| export const imageCache = new Map(); | ||
|
|
||
| export function cacheImage(key, image) { | ||
| if (imageCache.has(key)) { | ||
| imageCache.delete(key); | ||
| } else if (imageCache.size >= MAX_IMAGE_CACHE_ENTRIES) { | ||
| const oldestKey = imageCache.keys().next().value; | ||
| imageCache.delete(oldestKey); | ||
| } | ||
| imageCache.set(key, image); |
There was a problem hiding this comment.
[MEDIUM] The image cache is entry-bounded but not byte-bounded
Eviction counts entries only. With BACKEND_RESPONSE_MAX_BUFFER_BYTES at 64 MiB, 32 retained base64 images is roughly 2 GiB of encoded data, plus decoded renderer memory — and hidden editors retain their webview context.
Impact: browsing perfectly valid large images can destabilize the webview or extension host.
Fix: use a conservative byte-budgeted cache based on encoded length and decoded dimensions, and clear or shrink it when the editor is hidden or disposed.
Confidence: high
There was a problem hiding this comment.
Deferred until later - larger behavioral change.
| // in-memory LRU cache. Bounds memory when browsing many tiles: recently viewed images stay | ||
| // instant to revisit while the oldest entries are evicted. 32 comfortably covers typical | ||
| // back-and-forth navigation without letting the cache grow unbounded. | ||
| const MAX_IMAGE_CACHE_ENTRIES = 32; |
There was a problem hiding this comment.
[MEDIUM] Cache budget is expressed in entries, not bytes
This is the host-side source of the 32-entry cache limit consumed by media/viewer/state.ts. Paired with a 64 MiB max backend response, an entry count is not a meaningful memory bound.
Fix: express the budget in bytes (and keep the entry count only as a secondary cap). See the related comment on state.ts.
Confidence: high
There was a problem hiding this comment.
Deferred until later - larger behavioral change.
| export const PROVISIONING_STEP_TIMEOUT_MS = 10 * 60 * 1_000; | ||
|
|
||
| /** stdout/stderr buffer for a backend payload response (base64 PNGs can be large). */ | ||
| export const BACKEND_RESPONSE_MAX_BUFFER_BYTES = 64 * 1024 * 1024; |
There was a problem hiding this comment.
[MEDIUM] 64 MiB per response is the multiplier behind the unbounded cache
This limit is reasonable on its own, but combined with a 32-entry webview image cache it defines the worst-case retained footprint (~2 GiB encoded). Whatever byte budget the cache adopts should be derived from or cross-referenced with this constant so the two cannot drift apart.
Confidence: high
There was a problem hiding this comment.
Deferred until later - larger behavioral change.
MRD Viz: Marketplace release prep + publish job
- pull_request/push filters matched the PR base branch (carter-mrd-viz), so neither workflow ran for PRs into main; target main instead - pin @vscode/vsce to an exact version in the package/publish steps - pin softprops/action-gh-release to a commit SHA (was mutable v2 tag) Addresses review comments on PR #87.
- reject --slice AXIS above a small cap so a huge axis returns a diagnostic instead of attempting an enormous dense-tuple allocation - guard the webview mosaic against a non-array thumbnails value so a malformed backend payload cannot throw and blank the editor Addresses review comments on PR #87.
Summary
Adds the
mrd-vizVS Code extension and its Python backend for viewing MRD files, along with supporting CI workflows and devcontainer setup.This PR is opened primarily as an initial full-repo review pass — a chance to surface issues that only show up when the work is looked at as a whole, and to serve as a large-PR code-review exercise. The remaining marketplace-release changes are intentionally not included yet.
Included
mrd-vizextension (webview viewer, backend resolver/runner, editor provider)mrd-vizPython backend (CLI, rendering, tests) + packagingmrd_viz.yml,mrd_viz_release.yml) and devcontainer configTECHNICAL_DESIGN.mdandDEVCONTAINER.mdIntentionally excluded (kept untracked for now)
The following internal/WIP docs are excluded until they are ready for review:
BACKEND_INSTALL_MODES.mdBACKEND_RESOLUTION_IMPLEMENTATION_PLAN.mdEXTENSION_RELEASE_RUNBOOK.mdOFFICIAL_EXT_DEV_RUNBOOK.mdPACKAGING_AND_INSTALL_RUNBOOK.md