Skip to content

Draw a section's icon on rows and beside its crumb - #17

Merged
ariofrio merged 15 commits into
mainfrom
ariofrio/icons-breadcrumbs-improvements
Aug 25, 2026
Merged

Draw a section's icon on rows and beside its crumb#17
ariofrio merged 15 commits into
mainfrom
ariofrio/icons-breadcrumbs-improvements

Conversation

@ariofrio

@ariofrio ariofrio commented Aug 22, 2026

Copy link
Copy Markdown
Owner

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

  • Sidebar rows draw the project's icon, the section's where that project has none of its own, and the project's default glyph where neither was picked.
  • The thread header draws one icon before each crumb, and none where that crumb is off. With no crumbs it keeps a single icon and chooses its owner the way a row does.
  • All three plugins take the nearest section walking up, the thread's own included.
  • The picker no longer offers folder-01 or bubble-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.

after

The same two with every crumb off, where the header keeps a single icon.

fallback

Testing

427 tests across the three plugins, 74 at the root, release:check clean on each. Computed on real elements in an isolated bb:

thread project icon in a section draws
project keeps book-open-01 yes yes book-open-01
project cleared no yes the section's rocket
project cleared no no folder-01, the default

Alternatives 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

  • A section's icon has no accessible name. The picker and the button read "this section", because Icons holds section ids and not names.
  • The fixture seeds no sections, so no committed screenshot covers any of this and the two above were captured by hand.

@ariofrio
ariofrio force-pushed the ariofrio/icons-breadcrumbs-improvements branch from 1b14153 to bfac510 Compare August 22, 2026 04:39
@ariofrio ariofrio changed the title Show a section's icon beside the crumb and row it belongs to Fall back through project, then section, for a thread's icon Aug 22, 2026
ariofrio and others added 5 commits August 22, 2026 14:53
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
ariofrio force-pushed the ariofrio/icons-breadcrumbs-improvements branch 3 times, most recently from bab4806 to 0b16349 Compare August 22, 2026 19:09
@ariofrio ariofrio changed the title Fall back through project, then section, for a thread's icon Draw a section's icon on rows and beside its crumb Aug 25, 2026
ariofrio and others added 10 commits August 24, 2026 20:50
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
ariofrio merged commit 67b3d6b into main Aug 25, 2026
11 checks passed
@ariofrio
ariofrio deleted the ariofrio/icons-breadcrumbs-improvements branch August 25, 2026 04:33
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>
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