Skip to content

Make Shark Dive say less, and say it once - #2968

Merged
pyricau merged 11 commits into
mainfrom
make-things-easier-for-beginne
Aug 30, 2026
Merged

Make Shark Dive say less, and say it once#2968
pyricau merged 11 commits into
mainfrom
make-things-easier-for-beginne

Conversation

@pyricau

@pyricau pyricau commented Aug 28, 2026

Copy link
Copy Markdown
Member

The window had verbose plain-language explanations in it — unusual for an expert tool, and a lot of content to go through before you get to the heap dump. A fair diagnosis of where they came from: the chatty UI is the model leaking into the product; half those labels are for the agent that wrote them, not the human reading them.

Seven commits, each one a single move.

1. Delete the paragraphs saying what the picture already said. A rectangle drawn inside another already says it is dominated by it.

2. Put a ? beside the labels that take a paragraph to explain. Hovering it gives one sentence; clicking it opens the reference as a tab of the window. The pages ship with the app rather than linking out, so a release explains itself with the text it was built with, and the same markdown files are the website's via pymdownx.snippets. Each page's opening sentence is the tooltip, so the short answer can't drift from the long one. Nothing fades and nothing decays: no first-run tour, no hint that stops appearing.

3. Say what to do about a library leak. The screen used to say there was "usually nothing to do about them but wait for a fix upstream", which is wrong twice — a leak in a library is an issue to file and often a small fix to send, and a leak in the framework has a workaround to look for. The ? there leads to the page saying which to do, including AndroidLeakFixes. And the description Shark carries for the leak is now drawn in full with its links live: 51 of the 85 end in the AOSP change that introduced the leak, which is where the way round it usually is, and three ellipsized lines of plain text was throwing exactly that away.

4. Give each reachability strength one name. The panel spelled out a sentence per strength where the legend said Strong and the leaks screen said Softly reachable for the same nine things. One label each, and one reference page for what they mean.

5. Say how many, once, for a rectangle that isn't one object. Three nouns — 300 smaller objects, 400 objects of one class, Unreachable — where the line under each already said which was which.

6. Make starred a list of objects, kept on disk, and name a size once. Starred was a screen of its own, holding a copy of each object's sizes taken when it was starred — which went stale the moment a verdict changed what something retains. It is now the same row the object list draws, read out of the heap dump, with the addresses kept in ~/.shark-dive/starred: one file per dump, one address per line, in the order they were starred, hand editable like the leak statuses beside it. That left one vocabulary to settle — the same two numbers were Retained/Shallow in the panel, Retains 1.2 MB in 57 objects/88 B of its own on the card, and Retaining … on a chain. Now Retained and Shallow everywhere. Dominates means one thing, on the chain.

7. Write down what the window is allowed to say. Nothing in the repo said why any of the above, so the next change was free to put it all back — and likely to, since every guide and KDoc here is written at length on purpose, which reads as a house style that applies to labels too. Two sections in shark/shark-dive/AGENTS.md, plus the line in the root guide separating repo prose from screen prose.

./gradlew :shark:shark-dive:*:check green across all five modules.

🤖 Generated with Claude Code

pyricau and others added 11 commits August 28, 2026 17:13
Five paragraphs, 159 words, drawn always-visible next to the thing they
described. Every one of them restated something already on screen:

- The two lines on the card at the pointer both opened "Not one object",
  above a title that is a count. `400 of one class` and `37 smaller
  objects` say it in the words that were already there.
- The details panel's class-group and unreachable paragraphs did the
  same, then spent the rest of themselves on what a click does — which
  the click does.
- The smaller-objects paragraph explained the layout to the reader
  ("too small or too many to draw one by one"), which is a note about
  the treemap's implementation, not about their objects.

Where a paragraph was carrying something real, it moves to the KDoc of
the composable it was describing: what a click on each kind of pile does
and why they differ is worth knowing before editing `GroupLines`, and
worth nothing on screen.

The class-group and unreachable piles lose their only line under the
colour swatch that way, so they get the one an object already gets,
`reachabilityText`. A pile and an object now show the same rows in the
same order — how firmly it is held, then what it costs — which is a
better answer to "is this one object?" than a sentence saying it isn't.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Five paragraphs lived in the window: what names a leak, what a leak
fingerprint is, what shading by leak status does, why nothing is weakly
reachable, and what the arrows either side of a stretch of the chain do.
Together they were 420 words of prose sitting on a work surface, and an
expert tool that explains itself out loud is a tool with less room for
the heap dump.

They are now a page each, under `docs/shark-dive-reference/`, and the
window says one sentence with a `?` after it. Hovering the `?` gives that
sentence; clicking it opens the page as a tab, with every other page
listed under it.

The sentence on screen is the page's own opening sentence, read out of
the same file, so the two cannot drift apart. `ReferenceTest` holds every
opening sentence to one sentence of at most 25 words, since a tooltip is
read while deciding whether to look further.

The pages ship on the classpath, copied there by `copyReference`, rather
than being a link to the website: a release then explains itself with the
pages it was built with, instead of following a link to a page written
about a newer one. `docs/shark-dive-reference.md` includes the same files
for the site, and `exclude_docs` keeps them from being published twice —
so there is one copy of every sentence, and a test that says the site
publishes each of them.

The `?` never fades and is identical for everyone. A hint that decays is
a hint whoever built this stops seeing, and it is gone by the time
somebody wonders what a column meant — which is after the work, not
during it.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The Library leaks section said "there is usually nothing to do about them
but wait for a fix upstream". That is wrong twice over. A leak in a
library is an issue to file, usually a small fix to send, pressure to put
on a maintainer, and a version to bump once it lands. A leak in the
framework can't be fixed from here, but it can often be worked around,
and it can always be reported so the next Android version doesn't ship
it.

So the section now says only what the section *is* — someone else's code,
recognized by the reference holding it — and adds that this is about who
fixes it rather than whether it needs fixing. What to do is a page of the
reference behind the `?`, since it is four cases and not a line. It names
AndroidLeakFixes, the ten framework workarounds plumber-android already
applies, which nothing in the window mentioned.

And the description Shark carries for the leak is drawn in full with its
links live, through the same markdown the notes and the reference use.
51 of the 85 descriptions in AndroidReferenceMatchers end in a URL — the
AOSP change that introduced the leak, or the file it is in — which is
where the way round it usually is. The row showed three lines of plain
text with an ellipsis, so for the long ones that URL was the part cut
off, and for the rest it was there but dead.

Reading them needed one thing the note markdown didn't have:
Note.ofDocument, where a line break inside a paragraph is the wrapping
somebody did to fit a column rather than a line break. A `"""` block out
of Shark read as a note is four blocks, three ending mid-sentence.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Nine strengths, and the window had three vocabularies for them. The
legend above the map said "Strong". The details panel beside it said
"Strongly reachable". The leaks screen said "Softly reachable" over a
section, and "Held only by a cache that evicts" was a colour on the map.
Same nine things, three ways of saying them, so a reader who had learned
the legend had to learn the panel and then the list.

ReachabilityStrength.label is the one name now — Strong, Cache, Thread
local, Local, Soft, Weak, Finalizer, Phantom, Unreachable — and every
surface reads it: the legend, the details panel, the card at the pointer,
the chain, the headings of the leaks screen, and the log line saying how
a dump splits up. HeapDominatorTreemap.UNREACHABLE_LABEL was a fourth
copy of one of those words and is gone.

What a strength *means* is one page,
docs/shark-dive-reference/reachability-strength.md, behind the `?` on the
legend's "Colour" head, on the strength row of the details panel, and on
each of the five leaks sections a strength names. One `?` for the nine
rows rather than one each: nine question marks in a column is nine times
the ink for one answer.

So the five sections lost their explanation. Each was a paragraph saying
what the strength means, which is now what the page says, and two copies
of that is one that goes stale. The agent's JSON drops the key for them
rather than sending null.

The test about uncollected garbage counts rather than finds: the pile,
the tab it opens, the chain and the panel all say "Unreachable" now,
which is what one name means. What the panel says about a strength is
pinned by the tests using one that isn't also a pile.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Three kinds of rectangle stand for several objects: the children one had
no room to draw, every instance of a class the root owns directly, and
the uncollected garbage. Each had its own noun — "300 smaller objects",
"400 objects of one class", "Unreachable" — and under every one of them
was a line already saying which kind it was: the rectangle they were left
out of, the class name, the reachability strength.

So the noun was the line below said again in a different vocabulary. All
three now say the count and stop, and the panel drops the "Objects" row,
which was that same count a third time.

The one difference a reader can act on is whether clicking goes into the
pile or roots the map at what it was left out of. That is what the click
does; it never needed a noun.

"$nodeCount smaller objects" was also built in five places and rebuilt in
three view tests. It is formatObjectCount now, which is one place and
which puts the thousands separator on a count that never had one.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Two lists of objects were two tables. The object list drew a row per
object with Shallow and Retained columns; the starred screen drew a
block per object, labelled in words nothing else used, out of a copy of
each object's sizes taken at the moment it was starred. That copy is
what made it a screen of its own, and it went stale as soon as a verdict
set by hand changed what something retains, with nothing saying which of
the two numbers was current.

So `ObjectRow` is now the row both lists draw, `StarredFile` keeps the
addresses and nothing else — one file per heap dump under
`~/.shark-dive/starred`, in the order they were starred, hand editable
like the leak statuses beside it — and what a starred object is, is read
out of the heap dump again like everything else.

Which left one vocabulary to settle. The same two numbers were
`Retained` and `Shallow` in the details panel, `Retains 1.2 MB in 57
objects` and `88 B of its own` on the card at the pointer, `Retaining …`
on a chain, and `Retained` over a column. They are now `RETAINED` and
`SHALLOW` everywhere, with how many objects that is on the `Retained`
line rather than in a row of its own. The count of objects a rectangle
*immediately* holds is gone from the panel and the card: it is the
number of rectangles drawn inside this one. `Dominates` therefore means
one thing now, on the chain. An agent still gets `dominatedObjects`,
having no picture to read it off.

`PlaceDetails.dominator` went with it — the starred snapshot was its
only reader.
Six commits of this branch deleted on-screen prose, collapsed four
vocabularies into one, and moved the paragraphs behind a `?`. Nothing in
the repo says why, so the next change is free to put them back — and
likely to, because every guide, KDoc and note here is written at length
on purpose, which reads as a house style that applies to labels too.

Two sections in `shark/shark-dive/AGENTS.md`, one for each rule, with the
mechanism and the worked examples already in the tree. Both name the
failure mode as an agent's: having reasoned out why a label means what it
means, write the reasoning into the label; having learnt a concept, name
it afresh where it is already named.

The root guide gets the line that separates the two audiences, since that
is the one an agent reading this repo would otherwise get backwards.
`ObjectIdentity` — the class, the class in full greyed under it, then
the address — was already what a step of a chain, the card at the
pointer and the bar above the map draw. Two other surfaces named an
object with a `buildAnnotatedString` of their own instead: a row of the
object list said the package, the class and the kind on one line and no
address at all, and a leaking object on the Leaks screen said the class,
the kind and the address and no package.

Neither was a decision. Each was written beside the list it was for, and
each read as consistent from where it sat — the two lists of objects
agreed with each other, and the leaks row agreed with what a leak's
address gets copied for. Which is how one concept ended up with three
spellings while every commit that touched it looked fine.

So both call `ObjectIdentity` now, `nameStyle` being the only thing a
surface picks. The object list and the starred list gain an address,
which is what a `shark://` link, a note and an agent's answer all name an
object by and what a reader arriving with one has to be able to find
again; the leaks screen gains the package, so it says which `Handler` it
is.

The tests that matched a row by the old one-line concatenation now match
it by the simple class name and kind, which is `ObjectIdentity`'s first
line and already how the chain steps were found.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The line under the strength legend still spelled out "reachable only
through a java.lang.ref.Reference" — the phrasing that naming every
strength in one or two words was meant to retire, left behind because it
lives above the map rather than in the details panel where the rest of
it was.

It sits directly under rows reading `Soft 0 B`, `Weak 0 B`, `Phantom 0 B`
and `Finalizer 0 B`, so it was a second vocabulary for four names the
reader already has in front of them. Which class they have in common,
and why a dump with none of them is normal, is the page the `?` beside
the line already opens.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
An empty **Starred** list named the panel the star is in, the empty **Agent
logs** screen explained what an agent is and how to point one at the window,
and a window whose last tab was closed reassured the reader that the heap dump
was still open. Each of those is read every time the state comes up, by
somebody who has read it before, and only the first clause of any of them is
news.

The dialogs were the same in the other direction: `Take heap dump…` and
`Bitmaps from the live process` each spent a paragraph on the mechanism above
a list of devices — which Android version puts bitmap pixels in native memory,
what `am dumpheap -b png` needs, what a debugger does to a process while it
reads them, what `ro.debuggable` is. None of that changes which device gets
clicked. What does is the cost, so that is what is left.

The paragraph is not deleted, it moved: `docs/shark-dive.md` now carries the
bitmap story under **Open a heap dump**, which is where a reader who wants it
goes looking, and `notes/bitmaps.md` still has it for whoever is changing this
code.

And the question a `shark://` link asks when this machine has two heap dumps of
that name, or none, now leaves out what the dialog's own title already says:
the title names the file, the rows are the directories, so the question is the
count and where to look.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Setting one was a material3 `AlertDialog`, which draws over the whole window,
and the step that lists the verdicts a new one contradicts is what that got
wrong. Two things a reader wants there are behind the scrim. Why two verdicts
can disagree at all is a paragraph — every other label in this window answers
that with a `?` that opens the reference *in a tab*, and a tab opened behind a
window wide scrim is a tab nobody can reach, so the only way to say it was to
draw all 265 characters above the list, every time, for somebody who read it
once. And the objects themselves: each verdict being overruled is an object,
and the reason somebody typed for it is the case for the other reading, so
weighing it against yours is sometimes going and looking — which a dialog over
the window can only offer by being dismissed, and dismissing it throws away the
half typed reason it was holding.

It looks exactly as it did. `LeakStatusSetter` draws the same centred card over
the same 32% scrim, inside the `Box` that holds the tab's panes rather than
over the window, so the screen bar and the tab strip above it still take a
click. And `SettingVerdict` is state `HeapDumpDive` keeps per tab id rather
than `remember`ed inside the composable, so switching tabs leaves it where it
is and coming back finds the reason half typed. Tab ids are never reused, so an
entry can only be about the tab it was made for, and closing that tab drops it.

The paragraph is now the `CONFLICTING_VERDICTS` reference page, and each
conflicting verdict is a link to its object.

The three surviving `AlertDialog`s are all about getting a heap dump rather
than reading one — `Take heap dump…`, `Bitmaps from the live process`, and the
one a `shark://` link asks — so there is nothing behind them worth reaching.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@pyricau
pyricau force-pushed the make-things-easier-for-beginne branch from 4e7e8ad to eb8b231 Compare August 29, 2026 13:18
@pyricau
pyricau merged commit 9cb24da into main Aug 30, 2026
16 checks passed
@pyricau
pyricau deleted the make-things-easier-for-beginne branch August 30, 2026 09:41
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