Make Shark Dive say less, and say it once - #2968
Merged
Merged
Conversation
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
force-pushed
the
make-things-easier-for-beginne
branch
from
August 29, 2026 13:18
4e7e8ad to
eb8b231
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 viapymdownx.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, includingAndroidLeakFixes. 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
Strongand the leaks screen saidSoftly reachablefor the same nine things. Onelabeleach, 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 wereRetained/Shallowin the panel,Retains 1.2 MB in 57 objects/88 B of its ownon the card, andRetaining …on a chain. NowRetainedandShalloweverywhere.Dominatesmeans 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:*:checkgreen across all five modules.🤖 Generated with Claude Code