Skip to content

Make an object a place a tab goes to - #2947

Merged
pyricau merged 1 commit into
mainfrom
i-want-to-rethink-navigation-i
Aug 6, 2026
Merged

Make an object a place a tab goes to#2947
pyricau merged 1 commit into
mainfrom
i-want-to-rethink-navigation-i

Conversation

@pyricau

@pyricau pyricau commented Aug 6, 2026

Copy link
Copy Markdown
Member

Reworks navigation and information hierarchy in Shark Explorer: an object is a first class thing you go to, and everything the window draws follows from which object a tab is on.

The problem

The window kept where it was in two coordinates — which screen it was on, and a path the treemap had zoomed along — set from a dozen places. A click on a rectangle, a row of the object list, a field in the details panel and a step of the chain each set them slightly differently, so which panes agreed with each other depended on which of the four you had used.

A tab is one Place

Place (shark-explorer-core) is the whole of where a tab is: an object, the pile of smaller objects left out of one, the object list with its filter, the leaks, the starred. Every pane is a function of it — the map is laid out at place.viewRootObjectId, the chain walks up to a GC root from that object, the details panel summarises it, the tab is named after it. Place.of(cell) for a rectangle and Place.Object(id) everywhere else, through one open, so every way to an object is the same move.

The stored path could go because it was never independent: in a dominator tree the way down to an object is unique, so the map root is a function of the object. TreemapNavigation and ExplorerScreen are gone with it. The map is rooted at the object clicked rather than zoomed along a chain to it, so an object that dominates nothing draws as one rectangle of its own bytes — the honest answer to what it holds. How it is held is the chain pane's answer.

Tabs

Which tab a click means is the click's own answer, decided in OpenIn and nowhere else:

Gesture What it does
Click Goes there in the tab being read, like a link in a browser tab
Middle click, ⌘/Ctrl click Opens a tab behind the one being read
Right click → Open in a new tab Same, and it is what makes the other two findable
A button on the bar Always opens a tab, in front

Two object lists filtered differently are two useful tabs, which is why the bar never reuses the tab of the same name. Every tab closes, the last one included — a window with no tab still holds the heap dump it spent seconds reading, and the bar is one click from a tab again. A tab's history is its own, so the back arrow never walks out of the tab you are reading. A tab is named class-plus-address, since a strip of a dozen instances of one class is only one you can pick out of if each tab says which instance it is.

The three panes are resizable against each other and each folds away to the button that unfolds it.

All objects is now Object list.

Roles, which is also what makes this testable

Three things in the window can say Whole heap dump at once — the button on the bar, the tab, and the top row of every chain — and they are three different moves. Role.Button on the bar, Role.Tab on the strip, and no role at all on a row that navigates, which is a link rather than a button. Correct for a screen reader, and what lets an assertion say which of the three it means.

Tests

Place and Tabs are plain immutable state in shark-explorer-core, unit tested (TabsTest, 14 cases). New TabStripTest covers the tab rules end to end in the UI; ObjectsScreenTest holds the object list tests moved out of ExplorerAppTest. 175 UI tests, check and detekt green.

Notes and docs updated in the same change: notes/decisions.md, notes/treemap-rendering.md, shark/shark-explorer/AGENTS.md and docs/shark-explorer.md.

No change log entry — Shark Explorer's Unreleased is still ✨ Initial release.

The window kept where it was in two coordinates — which screen it was on,
and a path the treemap had zoomed along — set from a dozen places. A click
on a rectangle, a row of a list, a field in the details panel and a step of
the chain each set them slightly differently, so which panes agreed with
each other depended on which of the four had been used.

Now a tab is one `Place`: an object, the pile of smaller objects left out of
one, the object list with its filter, the leaks, the starred. The map is laid
out at the object, the chain pane walks up to a GC root from it, the details
panel summarises it, the tab is named after it. `Place.of(cell)` for a
rectangle and `Place.Object(id)` everywhere else, through one `open`, so
every way to an object is the same move.

The stored path could go because it was never independent: in a dominator
tree the way down to an object is unique, so the map root is a function of
the object. `TreemapNavigation` is gone with it, and the map is rooted at
the object clicked rather than zoomed along a chain to it — an object that
dominates nothing is one rectangle of its own bytes, which is the honest
answer to what it holds.

Which tab a click means is the click's own answer, in `OpenIn`: a plain
click moves the tab being read, a middle click, a ⌘/Ctrl click or "Open in
a new tab" opens one behind it, and the buttons on the bar always open one
in front. Every tab closes, the last one included, which leaves the heap
dump read and the bar one click from a tab again. Each of the three panes
folds away and the outer two drag wider.
@pyricau
pyricau merged commit 5e47d97 into main Aug 6, 2026
16 checks passed
@pyricau
pyricau deleted the i-want-to-rethink-navigation-i branch August 6, 2026 04:52
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant