Draw a section's icon on rows and beside its crumb - #17
Merged
Conversation
ariofrio
force-pushed
the
ariofrio/icons-breadcrumbs-improvements
branch
from
August 22, 2026 04:39
1b14153 to
bfac510
Compare
A row showed its project's icon and nothing else, so two sections of the same project were told apart by their headings alone. It now shows the section's icon where the section has one, and the project's where it does not. "Has one" is the presence of a row in the Icons plugin's store: it writes on the first pick and deletes on Remove, so a sidebar nobody has touched draws exactly what it drew before, and clearing a section icon puts every row in it back to the project's. A child thread follows its root's section, which is where bb keeps it and what the section crumb already does. Verified on real rows in an isolated bb: filing a thread under a section with a pink rocket-01 drew rocket-01 in oklch(0.72 0.21 345.5) on that row while the rest kept their teal api icon, and clearing that icon put the row back to store-01. Screenshots are left stale on purpose. Recapturing is blocked until PR #11 lands: the harness no longer false-passes the crumb wait, so a run that meets bb's refused insert now aborts rather than shipping a bare header. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The header drew one icon, always the project's, so a thread's section was named by a crumb but never pictured. Now each crumb gets the icon of what it names: the section's before the section, the project's before the project, and neither where that crumb is turned off. bb's SDK lets no plugin render another's component, and these icons have to sit between the crumbs rather than ahead of them, so Breadcrumbs draws an empty marked span before each crumb and Icons fills it. An anchor React owns is also one bb's foreign-DOM guard admits a fresh node into even while a plugin is attributed — `e.parentNode === null && b(t)` is an allowed insert — which is a better container than bb's own header. React never puts children in the span, so this tree's reconciliation leaves the neighbour's alone. Unfilled it occupies nothing, so either plugin without the other reads exactly as it did. With no crumbs to sit beside, the header keeps a single icon and picks its owner the way a sidebar row does, so the two cannot disagree: the section's where the section has one, the project's where it does not. Icons asks bb for the thread's section rather than the sidebar's live view, which can still be empty while a header is already up. Verified on real headers in an isolated bb, all four cases: both crumbs drew rocket-01 before "Release actions" and book-open-01 before "Docs site actions"; the project crumb alone drew one project anchor; with every crumb off the lone icon was the section's rocket-01, and became the project's book-open-01 once that section's icon was cleared. Screenshots stay stale, still blocked on PR #11. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Every shot frames the thread header, and the project's icon moved a few pixels into the crumb run it now belongs to: it used to place itself, and now sits in the anchor before the project crumb, spaced by that row's own gap. Measured, the change is a 34x26 region in each window shot; the Icons shot moves more because its picker follows the trigger it hangs from. The three READMEs described the old behavior: one icon, always the project's, ahead of the crumbs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rule was the wrong way round. A row and the header's lone icon asked the section first, so giving one section an icon quietly took over every project inside it — including projects someone had already chosen an icon for. The project is what a row has always drawn, so it is asked first now: its own icon, then the section's where the project has none, then the project's default glyph where neither was picked. The picker no longer offers the two glyphs bb already draws by default, folder-01 and bubble-chat. Choosing one wrote a row identical to having chosen nothing, and under the rule above that row still outranks the section's icon — a pick that looks like a no-op while quietly changing what a sidebar draws. The section's default is composed rather than drawn from the catalog, so it was never offered. 2532 icons, now 2530. Verified on real rows and headers in an isolated bb, keyed by thread id rather than by title: a thread whose project keeps book-open-01 draws it even inside a section carrying a pink rocket; a thread whose project icon was cleared draws that rocket; and one in the same cleared project but no section falls back to folder-01. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Two pictures move. The hero regains the header icon's place in the crumb row, which the rebase had resolved back to main's copy. The Icons shot reflows one band of its picker: dropping folder-01 and bubble-chat from the catalog shifts every icon after them by two, and 696x48 of the grid is where that lands. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ariofrio
force-pushed
the
ariofrio/icons-breadcrumbs-improvements
branch
3 times, most recently
from
August 22, 2026 19:09
bab4806 to
0b16349
Compare
The anchor observer watched childList only. A thread moving between sections does not add or remove an anchor: React reuses the element and rewrites the owner on it, so the header kept the old section's icon beside the new section's name until an unrelated insertion happened to trigger a re-read. The picker inferred "nothing chosen" from the icon's value, which disabled Remove for a row holding the owner's default glyph. Those rows now outrank the section's icon, and dropping folder-01 from the catalog closed the way back, so the picker takes the fact explicitly. Also: setIcon no longer runs inside a setIcons updater, which React may call more than once per update; sectionId is cleared when the thread changes, so the fallback shows this thread's project rather than the last thread's section; the catalog test pins 2530 rather than a lower bound; and three comments still claiming 2,532 icons are corrected. A test now pins the anchors Breadcrumbs emits — kind, owner, and order. Deleting them from Crumbs previously left all tests passing. Prose pass over what this branch added: comments, changesets, READMEs and the PR body. The rule docstrings no longer claim the header and the sidebar cannot disagree, which is true of their shared precedence and false of the section each resolves separately. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The picker's bottom fade settles at a different opacity between runs, so this shot is not byte-stable. Same icons, same rows; only the gradient over the partially visible row differs. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Main restructured this plugin around a controller and an IconControl, and replaced the screenshot lock with a check that asks git what moved. The resolutions: - Screenshots and the lock: main's. Captures now run in a container, so the images this branch took on a laptop are obsolete and the lock file no longer exists. - SidebarIcons: main's. It delegates to IconControl now, so the fix that keeps a default icon removable moved into IconControl. - app.tsx: main's file with this branch's header action spliced in. The header slot cannot use the content script's controller, which is why main left that half alone too. Adopted main's IconGlyph shape. - The Icons README: main's, with the section fallback, the anchor contract, and the corrected icon count applied on top. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Three plugins each answered "which section is this thread in" differently: the root's then the thread's own, the nearest walking up, or the thread's own then the root's. They agree only while no non-root thread carries a section, and bb accepts one on any thread — verified against a running bb by filing a child thread and reading it back. All three now take the nearest walking up. A crumb, a row, and the header's icon name one section, and a thread filed somewhere of its own answers for itself and for anything under it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
The rule was stated three times in three plugins, argument and all. It keeps its reasoning in section-of.ts; the other two state what they apply and point at each other. The Thread stages README sentence carried the fallback order and the section rule at once, joined by "and", and the fallback order was already in the sentence before it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Only images conflicted. #32 recaptured every shot under the ChatGPT theme, so main's are the current ones and this branch's predate it. CI recaptures on the pull request, which is where this branch's own icon changes reach them. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Twenty images conflicted and nothing else. #26 changed how a recapture runs, so main's are the current ones; CI recaptures this branch's own change on the pull request. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
ariofrio
added a commit
that referenced
this pull request
Aug 25, 2026
Section icons (#17) landed on the same functions this branch moved onto bb's public APIs, so the two had to be reconciled rather than picked between. `fetchIcons` keeps main's three maps and takes this branch's loader, so the sidebar still asks bb to call the Icons plugin. `buildProjectIconMap` keeps the personal project as an argument. The Icons server had grown its own read of bb's projects for the names beside each row. Which project is personal now rides along with that list instead of costing a second call: one read, one cache, and the failure handling main already wrote. A write that arrives before the first read waits for it, so refusing the personal project's icon still needs bb's answer rather than a guess. `sidebar-dom.ts` keeps the literal id. It collects anchors from the DOM before any state exists, so there is nothing there to ask; the comment says so. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
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.
Summary
Draws a thread's section icon alongside its project's, on sidebar rows and in the thread header, and makes all three plugins agree on which section a thread is in.
Why
Sections have had icons since Icons learned about them, but nothing drew one. A thread's section was named by a crumb and never pictured, and two sections of the same project were told apart by their headings alone.
The three plugins also each answered "which section is this thread in" differently. They agree only while no non-root thread carries a section, and bb accepts one on any thread.
What changed
folder-01orbubble-chat. A row holding the glyph bb already draws looks like no choice at all, and still outranks the section's icon.An owner has an icon only once someone picks one, and Remove deletes the row, so an untouched install is unchanged.
How it works
bb's SDK gives one plugin no way to render another's component, and these icons belong between the crumbs. Breadcrumbs draws an empty marked span before each crumb; Icons fills it. An unfilled anchor draws nothing and takes no space, so either plugin without the other is unchanged.
An anchor React owns is a container bb's foreign-DOM guard admits a fresh node into, which bb's header is not, so these portals avoid the refusal #11 works around.
Screenshots
Two threads in one section, one whose project has an icon and one whose does not.
The same two with every crumb off, where the header keeps a single icon.
Testing
427 tests across the three plugins, 74 at the root,
release:checkclean on each. Computed on real elements in an isolated bb:book-open-01book-open-01rocketfolder-01, the defaultAlternatives considered
Breadcrumbs draws the icons itself, reading them over Icons' RPC as Thread stages does. One owner and no DOM contract, but the icons stop opening the picker, since that UI cannot cross the bundle boundary.
Extract the icon and picker into a shared package. The right long-term shape, and much larger; it blocks this behind a packaging change.
Known gaps