The group-heading pencil, as it ships and as three proposals would change it. Light and dark sit side by side in every bench. All four interaction states are rendered simultaneously so they can be compared without a pointer; a live, genuinely-hoverable copy sits at the end.
Four states. Note that rest and heading hover are
pixel-identical — there is no ancestor-hover rule for .edit-pencil
anywhere in the theme.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Hidden at rest, revealed at --bn-colour-muted on heading hover,
accent on its own hover. No new tokens, no new elements — this is the pattern
.hide-btn and .bn-name-pencil already ship.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Rest is now empty — the heading reads as prose, with no permanent 13.5px speck beside every group title. That is a real gain beyond contrast: on a dashboard with a dozen groups, the current design draws twelve near-invisible marks that serve nobody until hovered.
On hover the pencil arrives at --bn-colour-muted —
4.83:1 light / 7.44:1 dark, comfortably legible without
competing with the heading. The appearance itself is the discoverability
signal; motion is a stronger cue than any static contrast bump, which is the point
you made about hovering being the moment that matters.
Cost: zero discoverability for a user who never hovers. That is the trade,
and it is the one .hide-btn already takes on every quote card.
Proposal A, with U+270E replaced by a 1.5-stroke SVG on the 16px grid — matching the seven SVGs already in the theme.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
The three panes deliberately separate the two variables — asset and colour — because proposal B changes both and they should be judged apart. Left is today. Middle changes only the asset. Right is B as it would ship.
Each pane is rasterised at the true 13.5px and upscaled 6× with
image-rendering:pixelated, so the fuzz you see is genuine — what the
rasteriser produced at that size, not a vector redraw. That is why peak and median
diverged: U+270E is drawn by whichever font the OS resolves it to, and at 13.5px it is
a hairline that is mostly antialiasing. A 1.5px stroke lands on the full token colour,
so median ink ≈ peak ink — and it renders identically on macOS, Windows and Linux
instead of varying by font fallback.
The icon catalog already asked for this: docs/design-system/icon-catalog.html
lists pencil as migration priority 3, explicitly because “rendering varies across OS”.
That plan is dated 9 Feb 2026 and names render_html.py as its target — a file
that no longer exists — so it needs re-homing at the SPA regardless of what we decide here.
Scope note: adopting B for one icon does not require the full icon-set migration.
It does mean choosing the stroke grid now so the remaining glyphs
(★ star, ▶ play ×2) can follow consistently.
The heading itself takes a hover tint and cursor: text; clicking it
starts the edit. The pencil stays as a secondary hint. This is the web-native convention —
and it is where the macOS convention disagrees.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
Users interact with search results and sorting controls to find what they need.
This one is a product call, not a defect fix — which is why it is separated.
EditableText already supports trigger="click" and already applies
cursor: text in that mode
(frontend/src/components/EditableText.tsx:178). All seven call sites pass
trigger="external", so the path is written, tested-adjacent, and dead.
cursor: text is the promise.The hover tint shown is --bn-hover-bg (the shipped token,
rgba(0,0,0,.04) / rgba(255,255,255,.06)) — no new value.
Judge whether that tint on a heading reads as “editable” or as “selected”; if it reads
as selected, C is actively misleading on the Mac and A/B is the safer answer.
Real pointer behaviour, real transition timings
(--bn-transition-fast on opacity, --bn-transition-normal on colour).
Feel the ramp rather than reading it.
Four controls of the same class ship three different disclosure behaviours. Whatever is chosen above should settle all four.
| control | rest | ancestor hover | self hover |
|---|---|---|---|
.edit-pencil — group heading | icon-idle, always on | — nothing — | accent |
.star-btn — quote card | icon-idle, always on | — nothing — | accent |
.hide-btn — quote card | opacity 0 | opacity 1 | accent |
.bn-name-pencil — sessions | opacity 0 | opacity 0.4 | opacity 0.8 |
.star-btn is deliberately excluded from the fix in the proposals above:
a star is a state display as well as a control — a starred quote must read as
starred without hovering — so it cannot simply adopt the hide/ramp pattern. It needs
its own answer (likely: idle→muted for the unstarred affordance, with the
starred state keeping --bn-colour-starred). Flagging rather than
assuming.
.bn-name-pencil’s 0.4 row-hover step composites to
1.70:1 light / 2.16:1 dark — fainter than it looks in the code,
and worth aligning to whatever A/B settles on.
Rows top to bottom: rest, heading hover, pencil hover, editing. Rows 1 and 2 are the same pixels. The only state change in the whole control requires the pointer to already be on a ~13.5px glyph rendering at 1.24:1 (light) / 1.44:1 (dark) median ink.
Measured from the two supplied screenshots by pixel sampling, not derived from tokens. Peak and median diverge because U+270E is a hairline glyph — most of its pixels are antialiasing, so the token value (
1.61 / 2.70) flatters what the eye receives.Root cause is semantic, not just numeric.
--bn-colour-icon-idleis defined incolors/_contract.css:66as “Disabled/idle icon colour”, and is being used as the resting colour of a live control.