Skip to content

fix: refresh generated tooltips when the accessible name changes - #92

Open
FutuRiata wants to merge 1 commit into
Formsmith746:mainfrom
FutuRiata:upstream-fix/tooltip-refresh
Open

fix: refresh generated tooltips when the accessible name changes#92
FutuRiata wants to merge 1 commit into
Formsmith746:mainfrom
FutuRiata:upstream-fix/tooltip-refresh

Conversation

@FutuRiata

Copy link
Copy Markdown

The effect in SketchForgeEditor.tsx that copies a button's accessible name into its title returns early on every button that already has one:

if (button.title) {
  return;
}

The MutationObserver right below it watches aria-label on the whole subtree, so the intent to keep the tooltip in step with the name is clearly there. But the guard makes the observer a no-op: the first pass gives each unlabelled button a title, and every later pass hits the guard and leaves. A generated tooltip is written once and never again.

Where it shows. Toggle buttons, whose accessible name is the thing that changes. ShapeInspector.tsx:480,483:

aria-label={locked ? "Unlock shape" : "Lock shape"}
aria-label={shape.hidden ? "Show shape" : "Hide shape"}

After one click the tooltip states the opposite of what the button will do: hovering the lock on an already-locked shape reads "Lock shape", and clicking it unlocks. The sketch image lock in SketchWorkspace.tsx:1241 behaves the same way. Found in a production build, by hovering, not by reading code.

Why the guard cannot simply go. I walked the JSX with the TypeScript parser rather than trusting grep. Of 152 <button> elements in apps/web/src, 53 set title in the markup, and on 23 of those the title deliberately differs from the accessible name:

Site title accessible name
WorkplaneViewport.tsx:5048-5053 (view cube, 6 buttons) Top view (5) Top view
WorkplaneViewport.tsx:5077 Place workplane (W) Place workplane
WorkplaneViewport.tsx:5059,5064 Show controls Show camera controls
SketchForgeEditor.tsx:9983 Add box at the sketch origin, or drag it onto the sketch Box (from the span)
SketchForgeEditor.tsx:10032,10035 Undo Sketch undo
ShapeInspector.tsx:528 #D41721 Set color #d41721
SketchWorkspace.tsx:1238 Lock image (L) Lock sketch image
SketchWorkspace.tsx:1247 Unlock image before deleting Delete sketch image
TransformOverlay.tsx:215,251 Rotate none at all
page.tsx:1771,1873 Project options Project options for <name>

Overwriting those would lose every keyboard shortcut hint in the viewport, so the fix has to tell "this tooltip was generated" from "this tooltip was authored".

What this does. The helper remembers what it wrote in a WeakMap keyed by the element and refreshes only a title that still matches its own last value. A title that came from the markup, or one that changed behind its back, is left alone. Buttons with a markup title take the same cheap early exit as before, so the observer's hot path is unchanged, and nothing is added to the DOM.

The logic moves into src/lib/buttonTooltips.ts so it can be covered by a unit test in the existing style; the effect in SketchForgeEditor.tsx is three lines now. If you would rather keep it inline, say so and I will fold it back — the WeakMap is the only part that matters.

Transcribing the shipped applyTitles() body verbatim into a scratch test and asserting the refresh makes it fail on Lock shape where Unlock shape is expected, which is the regression the new test pins down.

269 tests pass, the 264 existing ones plus five new; tsc --noEmit is clean.

🤖 Generated with Claude Code

https://claude.ai/code/session_01TAoEuBEJyJtkEGapsp6G22

The effect that copies a button's accessible name into its title returns
early on every button that already has one:

    if (button.title) {
      return;
    }

That leaves the MutationObserver watching aria-label with nothing to do.
The first pass gives each unlabelled button a title; every later pass hits
the guard and returns. A generated tooltip is written once and never again.

Toggle buttons are where it shows. ShapeInspector.tsx:480,483:

    aria-label={locked ? "Unlock shape" : "Lock shape"}
    aria-label={shape.hidden ? "Show shape" : "Hide shape"}

After one click the tooltip says the opposite of what the button does:
hovering a locked shape reads "Lock shape" while clicking unlocks. The
sketch image lock in SketchWorkspace.tsx:1241 behaves the same way.

Dropping the guard is not an option. Measured by walking the JSX with the
TypeScript parser: of 152 <button> elements, 53 set title in the markup,
and on 23 of those the title deliberately differs from the accessible name.
The six view cube buttons and the workplane button append the keyboard
shortcut ("Top view (5)" against aria-label "Top view", "Place workplane
(W)"); the shape menu entry explains the drag gesture in a full sentence;
the colour swatches show the hex code while the accessible name is
"Set color #d41721"; the transform and rotate handles carry a title and no
accessible name at all. Overwriting those would be a regression.

So the helper remembers what it wrote, in a WeakMap keyed by the element,
and only refreshes a title that still matches its own last value. A title
that came from the markup, or one that changed behind its back, is left
alone. Buttons with a markup title take the same early exit as before, so
the observer's hot path is unchanged.

The logic moves to src/lib/buttonTooltips.ts to make it testable; the effect
in SketchForgeEditor is three lines now. 269 tests pass, the 264 existing
ones plus five new.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01TAoEuBEJyJtkEGapsp6G22
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