Skip to content

fix(css): unify compact touch target sizes - #85

Merged
adminopenclaw8-sketch merged 3 commits into
masterfrom
codex/fix-2052-touch-target-css
Sep 25, 2026
Merged

adminopenclaw8-sketch merged 3 commits into
masterfrom
codex/fix-2052-touch-target-css

Conversation

@adminopenclaw8-sketch

@adminopenclaw8-sketch adminopenclaw8-sketch commented Sep 23, 2026 •

Copy link
Copy Markdown
Collaborator

Summary

  • Remove the earlier conflicting 48px declarations for .nav-btn and .ch-icon-btn. Keep the shipped 44×44 minimum and touch-action: manipulation. Clarify the WCAG/Apple versus Material guidance in the stylesheet comment.
  • Review fix: remove a dead #chList .ch-icon-btn { min-width: 32px; min-height: 32px } override. Replace the falsely green static test with a browser test of the rendered touch targets, plus a corrected static policy test.

Upstream context: issue 2052 in Kpa-clawbot/CoreScope.

Review finding and what it turned out to be

The first version's policy test matched only the exact selector text .ch-icon-btn. It missed a more specific rule inside @media (max-width: 640px) that set the channel share/remove actions to 32×32.

Measuring the real app showed that rule never applies. Below 768 px, channels.js renders the flat .ch-row channel list (#1367), which has no inline share/remove actions. It also re-renders when the viewport crosses 767 px, so desktop rows never survive a resize to a narrow width.

So the rule contradicted the 44×44 policy without ever sizing a control a user could touch. It is removed, and the comment now states the responsive contract.

Responsive contract (as tested)

Viewport Channel list Share / remove
1440×900 desktop sectioned .ch-item rows 44×44 each (share 52.9×44 with its label), visible, not clipped, hit at the centre, no overlap, no page overflow
390×844 and 360×740 touch flat .ch-row list, rows 80 px high not rendered

On mobile, the visible tap targets in the channel sidebar are all at least 44×44: region pills 44×44, Add 60×48, and the rows.

Known limitation, not addressed here

Between 768 px and about 1330 px (≈1336 px counting the resize handle over remove's right edge), share/remove in the desktop rows are clipped by the channel sidebar. The sidebar is width: clamp(220px, 22vw, 320px); overflow: hidden, and a row with both actions needs about 293 px.

Viewport Share clipped Remove clipped Reachable
768×1024 and 844×390 35.9 px 81.9 px neither
1024 20.6 px 66.6 px neither
1180 0 32.3 px share only
1320 0 1.5 px both
≥1330 0 0 both

This behaves identically on master and is independent of this PR. It is kept out of scope and documented as a separate follow-up. There is no layout change here. The new E2E test logs the 768×1024 measurement as a non-gating characterization step, so the limitation stays visible in CI output.

Tests

  • test-issue-2052-touch-target-e2e.js (new, Playwright step). It uses a user-added channel from a saved key in localStorage; no DOM is injected.
    • Desktop 1440×900: share and remove are ≥44×44 and visible. They are not clipped by the viewport or any overflow ancestor. elementFromPoint at the centre hits the control. They don't overlap each other or the row's parts, and the page has no horizontal overflow.
    • Desktop ARIA and keyboard: the ARIA/title contract is unchanged. Tab from the row reaches share and then remove, with :focus-visible and the existing focused opacity (waited for, because of its 0.15s transition). No focus ring is asserted: .ch-icon-btn:focus keeps outline: none, unchanged and as on master. Enter on share opens the share modal; Escape closes it. Enter on remove shows the confirmation (waited for with a timeout, so a missing dialog fails instead of hanging), which is dismissed, and the channel and key stay.
    • Mobile 390×844 touch: the channel renders as a .ch-row with no share/remove and no .ch-item. Every visible sidebar tap target is ≥44×44, there is no overflow, and tapping the row opens the channel.
    • Tablet 768×1024: characterization only, as above.
    • Errors: it fails on page errors, same-origin console errors and unhandled rejections.
    • It uses no sleeps or retries; every wait is for a concrete DOM condition.
  • test-issue-2052-touch-target-css.js (unit step, now a supplement). Every selector anywhere in the stylesheet, including @media and more specific selectors, whose last compound targets .nav-btn, .ch-icon-btn, .ch-share-btn or .ch-remove-btn must declare min-width/min-height ≥44px (10 declarations checked). .nav-btn and .ch-icon-btn must keep touch-action: manipulation.

Mutation evidence (scratch copies, never committed)

Mutant E2E test Static test
Rendered desktop action forced to 32×32 red: "share renders 58.9x32.0, below 44x44" —
Remove pushed 160 px right at 1440 red: "remove is clipped by chList by 144.0px" —
Desktop action rows forced into the mobile view, plus the 32px rule red: "the channel is not rendered as a mobile .ch-row" red
The original 32px mobile rule alone green (the rule never reaches a rendered control) red: #chList .ch-icon-btn { min-width: 32px }
Remove shows no confirmation red after 12 s: waitForEvent('dialog') timeout (before the follow-up commit this hung) —

The first four mutants were run twice each, with the same result.

Fresh verification

All runs were local only, with no staging or production contact.

  • Integration: the merge with current master 4c5efa7b in an isolated export is conflict-free. The diff against master is exactly this PR's five files, and the workflow change is two registration lines. git diff --check is clean.
  • New E2E test: 5/5 plain and 3/3 on the instrumented frontend, plus 5 runs during development, and 3/3 again after the follow-up commit.
  • Related tests: all 46 channel, navigation, mobile, touch and gesture tests registered in CI pass on the instrumented frontend.
  • Viewports: measured at 1440, 1340, 1330, 1320 and 1280×900; 768×1024; 844×390; 390×844; and 360×740. There was no page overflow and no errors on any of them.
  • Static and CI checks:
    • CI's unit block passes (CSS-variable lint and 73 unit tests).
    • node --check passes, and the YAML parses.
    • The fork-guard/workflow Go tests pass (5).

Independent review

A separate reviewer re-measured this and found no blockers. They confirmed:

  • the cascade: the computed minimum is 44×44 wherever share/remove render;
  • no actions below 768 px, including after resizing a single page from 1440 down to 390;
  • hitboxes and keyboard behaviour;
  • all four mutations;
  • the E2E test at 3/3;
  • the scope: one workflow line, and fork guards unchanged.

Their one test weakness was fixed in a follow-up commit: the remove confirmation could hang instead of fail. It is now red within the timeout. The other findings are out of scope, and all behave the same on master:

  • no focus ring (outline: none);
  • the resize handle covering the right ~2 px of remove near 1330 px (added to the separate layout write-up);
  • a dead 48px .ch-remove-btn, .ch-share-btn rule;
  • the :active scale shrinking the hitbox;
  • test-touch-targets.js, which is unregistered and stale, still expecting 48px.

Scope

  • public/style.css: one dead rule removed, and comments updated.
  • Tests: the two test-issue-2052-* files, plus registration in test-all.sh and one line each in the unit and Playwright steps.
  • No layout change, and no change to triggers, permissions or fork guards.

Known unrelated baseline

A direct run of test-frontend-helpers.js on the base still has its two existing stale favStar expectation failures. This PR does not touch that code or relax those tests.

🤖 Generated with Claude Code

Openclaw and others added 3 commits September 23, 2026 16:22
…argets

Review found the Kpa-clawbot#2052 policy test was falsely green. It matched only the
exact selector ".ch-icon-btn". Inside @media (max-width: 640px), the more
specific "#chList .ch-icon-btn { min-width: 32px; min-height: 32px }" set
the channel share/remove actions to 32x32.

Measured in the real app, that rule never applies. Below 768px
channels.js renders the flat .ch-row list (Kpa-clawbot#1367), which has no inline
share/remove actions, and it re-renders when the viewport crosses that
width. So the rule contradicted the 44x44 policy without ever affecting
a control. It is removed, and the comment now states the responsive
contract.

test-issue-2052-touch-target-css.js now checks every selector anywhere in
the stylesheet whose last compound targets .nav-btn, .ch-icon-btn,
.ch-share-btn or .ch-remove-btn, including more specific selectors and
@media rules. Each min-width/min-height must be at least 44px. It stays
a supplement to the browser test.

test-issue-2052-touch-target-e2e.js measures the real responsive DOM for
a user-added channel (saved key; no injected DOM):
- desktop 1440x900: share and remove are at least 44x44, visible, not
  clipped by any overflow container, hit at their centre, not overlapping
  each other or the row, and the page has no horizontal overflow. They
  keep their ARIA/title contract and are reached by Tab with focus-visible.
  Enter opens the share modal; remove asks for confirmation, which is
  dismissed, so nothing is removed.
- mobile 390x844 touch: the channel renders as a .ch-row with no
  share/remove actions, every visible tap target is at least 44x44, and
  tapping the row opens the channel.
- tablet 768x1024: characterization only. The actions are clipped by the
  narrow sidebar there, a separate layout issue also present on master;
  the step logs the measurement and does not gate.

The E2E test is registered once in the Playwright step. This change
makes no layout change.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The remove step waited for the confirm dialog with a bare page.once
promise, so a missing dialog stalled the Playwright step until the job
timeout. Use page.waitForEvent('dialog') with a timeout so it fails the
step. Also state precisely what the focus check covers: the
:focus-visible state and the existing opacity cue, not a focus ring
(.ch-icon-btn:focus keeps outline: none, unchanged, as on master).

Co-Authored-By: Claude Opus 5.5 <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