Mockup · design-system-true

Inline-edit affordance — states & proposals

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.

Artefact — real pixels, real tokens Commentary — why, not what ships
Palette

1 · As it shipsdefect

Four states. Note that rest and heading hover are pixel-identical — there is no ancestor-hover rule for .edit-pencil anywhere in the theme.

current · light

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

current · dark

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

Commentary

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.

measurementlightdark
Heading text — median ink17.40:113.44:1
Pencil — peak ink1.61:12.03:1
Pencil — median ink1.24:11.44:1
Pencil on hover (accent)4.02:15.18:1

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-idle is defined in colors/_contract.css:66 as “Disabled/idle icon colour”, and is being used as the resting colour of a live control.

2 · Proposal A — the house disclosure ramprecommended base

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.

proposal A · light

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

proposal A · dark

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

Commentary

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.

3 · Proposal B — ramp + stroke SVGfixes the hairline

Proposal A, with U+270E replaced by a 1.5-stroke SVG on the 16px grid — matching the seven SVGs already in the theme.

proposal B · light

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

proposal B · dark

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

glyph · rasterised at 13.5px, shown 6× · light
todayU+270E · idle
asset onlySVG · idle
proposal BSVG · muted
glyph · rasterised at 13.5px, shown 6× · dark
todayU+270E · idle
asset onlySVG · idle
proposal BSVG · muted
Commentary

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.

4 · Proposal C — the text is the targetopen question

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.

proposal C · light

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

proposal C · dark

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

this is the best label i can think of

Users interact with search results and sorting controls to find what they need.

Commentary

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.

  • Web expectation: click the text, get a caret. Notion, Linear, Figma layer names. cursor: text is the promise.
  • macOS expectation: a single click selects; renaming is a deliberate second act (Return, or slow double-click) precisely so a stray click cannot start an edit. Finder, and the sidebar.
  • Today Bristlenose satisfies neither — there is no selection model on a heading, and no click-to-edit either.

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.

5 · Live — actually hover these

Real pointer behaviour, real transition timings (--bn-transition-fast on opacity, --bn-transition-normal on colour). Feel the ramp rather than reading it.

live · light

current — hover me, nothing happens

proposal A — glyph, ramp

proposal B — SVG, ramp

proposal C — the text is the target

live · dark

current — hover me, nothing happens

proposal A — glyph, ramp

proposal B — SVG, ramp

proposal C — the text is the target

6 · The pattern this sits in

Four controls of the same class ship three different disclosure behaviours. Whatever is chosen above should settle all four.

Commentary
controlrestancestor hoverself hover
.edit-pencil — group headingicon-idle, always on— nothing —accent
.star-btn — quote cardicon-idle, always on— nothing —accent
.hide-btn — quote cardopacity 0opacity 1accent
.bn-name-pencil — sessionsopacity 0opacity 0.4opacity 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.