From ce105c4ed1950914b227e67a679760239c026a95 Mon Sep 17 00:00:00 2001 From: Patrick Buckley Date: Fri, 12 Jun 2026 00:10:45 -0700 Subject: [PATCH] =?UTF-8?q?fix(ui):=20split=20separator=20ARIA=20range=20r?= =?UTF-8?q?eflects=20the=20real=20clamp,=20not=2010=E2=80=9390?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit _buildHandle hard-coded aria-valuemin/max at 10/90 (inherited from the old ui/static implementation) while the actual drag/keyboard clamp is _ratioBounds — the cell minimums against the split node's OWN px region (a 1200px host really clamps at ~17/83; nested splits sit tighter), so assistive tech was told a wider range than the separator allows. aria-valuenow/min/max are now all written in _applyLayout's handle loop from _ratioBounds(h.node) — one writer, refreshed on every drag, keyboard nudge, and structural change. A bare window resize can stale the advertised range until the next interaction (no resize listener by design — % insets make resizes free), still strictly truer than a constant. The max>=min guard covers a host shrunk below two cell minimums, where the bounds legitimately cross. --- tests/test_shell_js.py | 5 +++++ turnstone/shared_static/pane.js | 20 +++++++++++++++++--- 2 files changed, 22 insertions(+), 3 deletions(-) diff --git a/tests/test_shell_js.py b/tests/test_shell_js.py index 3ab963b9..84a1811f 100644 --- a/tests/test_shell_js.py +++ b/tests/test_shell_js.py @@ -736,6 +736,11 @@ def test_pane_manager_split_engine() -> None: assert 'setAttribute("role", "separator")' in pane assert "setPointerCapture" in pane and "_ratioBounds(node)" in pane assert '"aria-valuenow"' in pane and "ArrowRight" in pane + # the ARIA range mirrors the REAL clamp (_ratioBounds per handle in the + # _applyLayout loop) — never a hard-coded constant + assert "this._ratioBounds(h.node)" in pane + assert '"aria-valuemin"' in pane and '"aria-valuemax"' in pane + assert 'setAttribute("aria-valuemin", "10")' not in pane # limits: cell minimums + cap (the old ui/static ceiling, kept) assert "SPLIT_MAX_CELLS = 6" in pane assert "SPLIT_MIN_W = 200" in pane and "SPLIT_MIN_H = 150" in pane diff --git a/turnstone/shared_static/pane.js b/turnstone/shared_static/pane.js index 2619c7eb..9d301abc 100644 --- a/turnstone/shared_static/pane.js +++ b/turnstone/shared_static/pane.js @@ -806,6 +806,21 @@ export class PaneManager { el.style.top = h.y * 100 + "%"; if (h.node.dir === "row") el.style.height = h.span * 100 + "%"; else el.style.width = h.span * 100 + "%"; + // The ARIA range is the REAL clamp (_ratioBounds: the cell minimums + // against this split's OWN px region — nested splits sit tighter than + // any constant; the old hard-coded 10–90 misreported it to AT). It + // refreshes with every drag/keyboard/structure pass through here; a + // bare window resize can stale it until the next interaction (no + // resize listener by design — % insets make resizes free), which is + // still strictly truer than a constant. The max>=min guard covers a + // host shrunk below two minimums, where the bounds legitimately cross. + const b = this._ratioBounds(h.node); + const lo = Math.round(b.min * 100); + el.setAttribute("aria-valuemin", String(lo)); + el.setAttribute( + "aria-valuemax", + String(Math.max(lo, Math.round(b.max * 100))), + ); el.setAttribute("aria-valuenow", String(Math.round(h.node.ratio * 100))); } } @@ -916,9 +931,8 @@ export class PaneManager { "aria-orientation", node.dir === "row" ? "vertical" : "horizontal", ); - el.setAttribute("aria-valuemin", "10"); - el.setAttribute("aria-valuemax", "90"); - el.setAttribute("aria-valuenow", String(Math.round(node.ratio * 100))); + // aria-valuenow/min/max are written by _applyLayout's handle loop (the + // single writer) — the range comes from _ratioBounds, not a constant. el.setAttribute( "aria-label", node.dir === "row"