Skip to content

Fix blank Data Explorer grid for matrices with row names - #1357

Merged
juliasilge merged 3 commits into
mainfrom
bugfix/blank-data-explorer-grid-row-names
Jul 23, 2026
Merged

Fix blank Data Explorer grid for matrices with row names#1357
juliasilge merged 3 commits into
mainfrom
bugfix/blank-data-explorer-grid-row-names

Conversation

@juliasilge

Copy link
Copy Markdown
Member

Positron Release Notes

New Features

  • N/A

Bug Fixes


Description

View() on an R matrix with row names opened the Data Explorer but showed a blank grid. Data frames were fine, and matrices without row names were fine.

The root cause is in get_row_labels. When the Data Explorer opens, Positron's first cache update runs before the viewport rows are laid out, so it sends get_row_labels with an empty row selection. Ark implemented that request by subsetting the table to the selected rows and calling row.names() on the result. A zero-row subset of a matrix drops its row names to NULL (unlike a data.frame, which returns character(0)), and the strict match on the result then errored with "row.names should be strings, got 0". That single failed RPC was enough to leave the grid blank.

This PR fixes the Ark side:

  • get_row_labels now early-returns an empty result when the resolved row selection is empty, skipping the R subset call entirely.
  • As defense in depth, a NILSXP result from row.names() is treated as "no labels" rather than an error. The error is kept for other unexpected kinds.

Manual verification

Build a dev Ark (cargo build), point Positron at it with the positron.r.kernel.path setting (target/debug/ark), run Developer: Reload Window, and start a fresh R session.

  1. Minimal repro renders with row labels A and B, no row.names error in the console:

    m <- matrix(1:4, nrow = 2)
    rownames(m) <- c("A", "B")
    View(m)
  2. Scrolling after open still repaints correctly with the right row labels:

    big <- matrix(1:2000, ncol = 2)
    rownames(big) <- paste0("r", 1:1000)
    View(big)
  3. The as.matrix(data.frame) variant from the issue renders with its row names:

    View(as.matrix(mtcars[1:10, 1:8]))
Screenshot 2026-07-21 at 6 05 11 PM
  1. Regressions are unaffected: View(matrix(1:4, nrow = 2)) (no row names), View(mtcars), and View(iris) all render normally.

Automated: cargo test covers the new test_row_names_matrix_empty_selection integration test alongside the existing matrix row-label tests.

A follow-up as well

This Ark fix removes the failing RPC, which is enough to unblank the viewer on its own. I'm going to do a separate Positron PR to harden TableDataCache.update() so that any future failing backend request degrades a single paint rather than permanently wedging the viewer. We'll wrap the update body in try/finally so _updating is always cleared and the pending-descriptor drain still runs, and skip the row-labels RPC entirely when the row selection is empty. That change is independent of this one and will ship with its own vitest coverage.

@juliasilge
juliasilge requested a review from thomasp85 July 22, 2026 00:25
juliasilge added a commit to posit-dev/positron that referenced this pull request Jul 22, 2026
…5050)

This is the Positron-side hardening half of the fix for #12547. The Ark
root-cause fix is posit-dev/ark#1357.

### Summary

When the Data Explorer opens, the first cache update can run before the
viewport rows are laid out, so it carries an empty row selection. For a
backend that reports row labels (an R matrix with row names does), that
produced a malformed `get_row_labels` request that the backend rejected.
`TableDataCache.update()` set its in-progress flag with no
`try/finally`, so the rejected request threw out of the method before
the flag was cleared. Every subsequent update (scrolling, resizing,
sorting) then parked itself behind the in-progress guard and returned,
so the grid never repainted: it stayed blank even though the next
request would have succeeded.

This PR hardens `TableDataCache.update()` so a single failed backend
request degrades one paint instead of permanently wedging the viewer:

- Wrap the update body in `try/catch/finally` so the in-progress flag is
always cleared and the pending-update descriptor still drains, even when
a backend request rejects. This mirrors the same hardening already
applied to `TableSummaryCache.update()`. This makes the git diff look
bigger than the substance of the change here!
- Skip the `get_row_labels` request entirely when the row selection is
empty, so the malformed request is never sent.
- Decouple row-label loading from cell-data loading. On an invalidating
refresh (sort or filter), row labels are now fetched after the data
cache is cleared, refetched, and repopulated, and a row-labels failure
is isolated so it degrades to missing labels instead of aborting the
data refresh and leaving stale cells on screen.

Either side alone unblanks the viewer. The two changes are independent
and can land in either order.

### Release Notes

#### New Features
- N/A

#### Bug Fixes
- Fixed the Data Explorer grid remaining blank after a failed backend
request (#12547).

### Validation Steps

@:data-explorer

Automated: `npx vitest run
src/vs/workbench/services/positronDataExplorer/test/common/tableDataCache.vitest.ts`
covers flag-clear on rejection, pending-descriptor drain on failure, the
empty-selection skip, and data-refresh isolation from row-label
failures.

Manual (the un-blanking is verifiable against the current vendored Ark):

1. In an R session, confirm the grid renders instead of showing blank:

   ```r
   m <- matrix(1:4, nrow = 2)
   rownames(m) <- c("A", "B")
   View(m)
   ```

<img width="1402" height="1003" alt="Screenshot 2026-07-21 at 7 09
26 PM"
src="https://github.com/user-attachments/assets/d01af619-5780-461c-bbe6-fd959d833252"
/>


2. Scroll after opening a larger matrix and confirm it keeps repainting:

   ```r
   big <- matrix(1:2000, ncol = 2)
   rownames(big) <- paste0("r", 1:1000)
   View(big)
   ```

3. Confirm regressions are unaffected: `View(matrix(1:4, nrow = 2))` (no
row names), `View(mtcars)`, and `View(iris)` all render normally.

@thomasp85 thomasp85 left a comment

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM - a small non-blocking comment

},
// Defense in depth: `row.names()` returns `NULL` for a table with no
// row names, so treat that as "no labels" rather than an error.
NILSXP => Ok(vec![]),

Copy link
Copy Markdown
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I get the wish for added defensive coding but I don't think this path is ever possible to reach. We test for whether row names are null (line 1017) before asking for them so it should never be able to resolve to null with the new fix.

Not blocking, but maybe worth a mention as you could otherwise assume this was an actual possibility

@juliasilge
juliasilge merged commit efe072f into main Jul 23, 2026
17 checks passed
@juliasilge
juliasilge deleted the bugfix/blank-data-explorer-grid-row-names branch July 23, 2026 20:15
@github-actions github-actions Bot locked and limited conversation to collaborators Jul 23, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants