#300: route icon-only controls through IconButton/IconLink #308

Merged
opus-5 merged 1 commits from issue-300-icon-buttons into main 2026-08-01 07:14:28 +02:00
Contributor

Closes #300.

The bell was misaligned because .notifications-bell__button had neither flex centring nor an icon size rule, so its svg sat inline on the text baseline and rendered at lucide's 24px default instead of the 1.15rem .icon-button enforces. It was bigger as well as higher.

Changes

  • IconLink added next to IconButton, sharing one class helper. Three controls navigate (pond settings, graph, trash) and are links, not buttons — without a link twin they would have remained the one group gluing the class on by hand.
  • 17 hand-applied className="icon-button …" usages across 9 files routed through the components, which is what enforces an accessible name on a control showing only an icon.
  • .notifications-bell__button removed; the badge re-fitted to the 2rem box.
  • The bell now announces its unread count (new key titleUnread, de+en). The badge sits inside the control, so aria-label masked it and a screen reader heard "Notifications" without ever learning how many.
  • ESLint rule rejecting icon-button on a raw button/a/Link, in both string and template-literal form.

Deliberately unchanged

  • .toolbar-button (editor) stays its own visual system.
  • topbar__search keeps its raw button — it carries visible text, so it is not an icon-only control.
  • The plugin uninstall button keeps a title that differs from its accessible name (it explains why a required plugin is locked). IconButton spreads rest last, so the explicit title still wins.

Verification

The ESLint rule was tested against a scratch file containing all three violating forms plus two legitimate ones: 3 errors raised, other-button and IconButton className="icon-button--favorite" passed.

pnpm typecheck, full pnpm lint, pnpm i18n:check and the web unit suite (131 tests) are green. No e2e locator is affected: social.spec.ts works through the API, and export.spec.ts/favorites.spec.ts hang on CSS classes the component still emits.

Not verified here: the optical result in a running browser. All three controls now resolve to the same CSS rule, so the alignment is structural rather than tuned by eye — but the visual check at default zoom and 200% remains open per the issue's acceptance criteria.

Also drops the graphify block from CLAUDE.md; it duplicates the workspace-level instructions.

Closes #300. The bell was misaligned because `.notifications-bell__button` had neither flex centring nor an icon size rule, so its svg sat inline on the text baseline and rendered at lucide's 24px default instead of the 1.15rem `.icon-button` enforces. It was bigger as well as higher. ## Changes - `IconLink` added next to `IconButton`, sharing one class helper. Three controls navigate (pond settings, graph, trash) and are links, not buttons — without a link twin they would have remained the one group gluing the class on by hand. - 17 hand-applied `className="icon-button …"` usages across 9 files routed through the components, which is what enforces an accessible name on a control showing only an icon. - `.notifications-bell__button` removed; the badge re-fitted to the 2rem box. - The bell now announces its unread count (new key `titleUnread`, de+en). The badge sits inside the control, so `aria-label` masked it and a screen reader heard "Notifications" without ever learning how many. - ESLint rule rejecting `icon-button` on a raw `button`/`a`/`Link`, in both string and template-literal form. ## Deliberately unchanged - `.toolbar-button` (editor) stays its own visual system. - `topbar__search` keeps its raw button — it carries visible text, so it is not an icon-only control. - The plugin uninstall button keeps a `title` that differs from its accessible name (it explains why a required plugin is locked). `IconButton` spreads rest last, so the explicit title still wins. ## Verification The ESLint rule was tested against a scratch file containing all three violating forms plus two legitimate ones: 3 errors raised, `other-button` and `IconButton className="icon-button--favorite"` passed. `pnpm typecheck`, full `pnpm lint`, `pnpm i18n:check` and the web unit suite (131 tests) are green. No e2e locator is affected: `social.spec.ts` works through the API, and `export.spec.ts`/`favorites.spec.ts` hang on CSS classes the component still emits. **Not verified here**: the optical result in a running browser. All three controls now resolve to the same CSS rule, so the alignment is structural rather than tuned by eye — but the visual check at default zoom and 200% remains open per the issue's acceptance criteria. Also drops the graphify block from `CLAUDE.md`; it duplicates the workspace-level instructions.
opus-5 added this to the M33 — Tweaks & Feinschliff milestone 2026-08-01 06:56:53 +02:00
opus-5 added 1 commit 2026-08-01 06:56:53 +02:00
#300: route icon-only controls through IconButton/IconLink
All checks were successful
CI / Auth e2e pack (pull_request) Successful in 8m36s
CI / Import/export fidelity gate (pull_request) Successful in 58s
CI / Lint, typecheck, test (pull_request) Successful in 6m22s
CI / Build container images (pull_request) Successful in 3m51s
CD / Build and push images (push) Successful in 15s
CD / Deploy to Test (push) Successful in 16s
CD / Smoke tests against Test (push) Successful in 1m16s
CD / Promote to Int (push) Successful in 13s
CI / Lint, typecheck, test (push) Successful in 6m32s
CI / Build container images (push) Has been skipped
CI / Auth e2e pack (push) Successful in 8m25s
CI / Import/export fidelity gate (push) Successful in 58s
5a4a99196e
The notification bell sat higher and larger than search and the theme
toggle next to it. The cause was not the glyph: `.notifications-bell__button`
carried its own rules with neither flex centring nor an icon size, so the
svg was laid out inline on the text baseline and rendered at lucide's
24px default instead of the 1.15rem the shared `.icon-button` enforces.

Route every icon-only control through the shared components instead:

- `IconLink` joins `IconButton`, sharing one class helper. Three controls
  navigate (pond settings, graph, trash) and are links, not buttons —
  without a link twin they would have stayed the one group gluing the
  class on by hand.
- 17 hand-applied `className="icon-button …"` usages across nine files
  now go through the components, which is what enforces the accessible
  name on a control that shows only an icon.
- The bell's unread count reaches assistive technology. The badge sits
  inside the control, so `aria-label` hid it and a screen reader
  announced "Notifications" without ever saying how many.

An ESLint rule keeps it that way: `icon-button` on a raw button, anchor
or Link is now an error, in both string and template-literal form.

The plugin uninstall button keeps a title that differs from its name (it
explains why a required plugin is locked); IconButton spreads rest last,
so the explicit title still wins.

Also drops the graphify block from CLAUDE.md — it duplicates the
workspace-level instructions.
opus-5 merged commit 5a4a99196e into main 2026-08-01 07:14:28 +02:00
Sign in to join this conversation.
No description provided.