Skip to content

RC #1484#1485

Merged
jason-capsule42 merged 381 commits into
mainfrom
rc/1484
Jul 10, 2026
Merged

RC #1484#1485
jason-capsule42 merged 381 commits into
mainfrom
rc/1484

Conversation

@rmenner

@rmenner rmenner commented May 27, 2026

Copy link
Copy Markdown
Collaborator

Alaska Airlines Pull Request

Release candidate pull request. See issue #1484 for details.

Review Checklist:

  • My update follows the CONTRIBUTING guidelines of this project
  • I have performed a self-review of my own update
RC Checklist

Testing Checklist:

Browsers

Browsers Support Guide

Dev demo link

Android

  • Chrome
  • Firefox

iOS

  • Chrome
  • Firefox
  • Safari

Desktop

  • Chrome
  • Firefox
  • Safari
  • Edge

Scenarios

  • Validated linked issues with issue reporting team
  • Test coverage report review

Framework playground

  • Next React
  • SvelteKit

**By submitting this Pull Request, I confirm that my contribution is made under the terms of the Apache 2.0 license.**

Pull Requests will be evaluated by their quality of update and whether it is consistent with the goals and values of this project. Any submission is to be considered a conversation between the submitter and the maintainers of this project and may require changes to your submission.

Thank you for your submission!

-- Auro Design System Team

@rmenner
rmenner requested a review from a team as a code owner May 27, 2026 17:53

@sourcery-ai sourcery-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry @rmenner, you have reached your weekly rate limit of 500000 diff characters.

Please try again later or upgrade to continue using Sourcery

@chrisfalaska

chrisfalaska commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

auro-datepicker

P0 — Confirmed critical defects

RESOLVED P0-1 — Listener leak in auro-datepicker.js

RESOLUTION: event listeners now removed when disconnected.

  • Verified at: auro-datepicker.js:1248-1316, 1325-1340, 1853-1857
  • connectedCallback exists but does not call super.disconnectedCallback; no disconnectedCallback is defined at all.
  • Every listener in configureInput, configureCalendar, configureDatepicker is an anonymous arrow (input.addEventListener('input', (event) => { … }), etc.), so even a future removeEventListener wouldn't work.
  • auro-calendar.js:1369 does implement disconnectedCallback (cleans up live region + RAF + timer) — so the gap is specifically on the datepicker host.

RESOLVED P0-2 — ARIA grid wiring is non-functional

RESOLUTION: This feedback was based on stale comments in the code the defined behavior that had been changed since the comments were written. The code comments now correctly reflect the execution.

  • Verified at: auro-calendar.js:1591role="group", not role="grid".
  • ariaActiveDescendantElement is never assigned anywhere in source. The string appears only in five JSDoc comments (auro-calendar.js:490, 826, 1119; auro-calendar-cell.js:591, 608, 622). setActiveCell (line 495) only toggles cell classes.
  • The comment at auro-calendar-cell.js:649 literally says "buttons stay tabindex='-1' because the grid uses aria-activedescendant" — but the grid does not.
  • Effective behavior: live region (auro-calendar.js:1334-1362) is doing all the SR announcement work; the activedescendant pattern is dead code/docs.

RESOLVED P0-3 — centralDate type contract violation

RESOLUTION: Single line code change to enforce date type.

  • Verified at: auro-datepicker.js:1111-1113, 1632, 1693
  • Lines 1111, 1632, 1693 assign ISO strings. Line 1113 assigns new Date() (Date object) when valueObject is falsy and no centralDate/calendarStartDate/minDate exist. Mixed types into the same property after the ISO-only breaking change.

OUT OF SCOPE P0-4 — Home/End/PageUp/PageDown missing from grid keyboard

Resolution: These keyboard commands were intentionally excluded during the design phase. ADO Ticket

  • Verified at: auro-calendar.js:834-841
  • actionKeys array contains only ArrowRight/Left/Down/Up/Enter/Space. No Home (start of row/grid), End (end of row/grid), PageUp/PageDown (month/year nav).
  • WAI-ARIA grid pattern explicitly requires these. datepickerKeyboardStrategy.js confirms this is "intentional placeholder" (file header, lines 7, 13) but it's still a spec gap on a feature billed as "full keyboard navigation."

RESOLVED P0-5 — Inconsistent date-normalization strategy

RESOLUTION: all startOfDay instances changed to new Date().

  • Verified at: auro-calendar.js:2 imports startOfDay from date-fns, but uses it only at lines 1190, 1193. The other 10 occurrences (605, 659, 677, 679, 712, 714, 882, 914, 993, 1019, 1048) all use .setHours(0,0,0,0). auro-calendar-cell.js:18-22 reimplements its own startOfDay locally instead of importing date-fns.
  • Functionally equivalent today, but a refactor that switches one without the other will silently desync across DST.

P1 — Confirmed, fix soon

RESOLVED P1-1 — Range preview doesn't announce to AT during selection

RESOLUTION: New rangeLabelEndPreview attribute added to handle focus state with AT.

  • Verified at: auro-calendar-cell.js:686-711 (imperative class toggling, no live-region call).
  • Keyboard+SR user gets no preview-range feedback until they commit.

RESOLVED P1-2 — Live-region creation is racy

RESOLUTION: Code updated to account for racy behavior.

  • Verified at: auro-calendar.js:1340-1343 — returns null if dropdown.bibContent.shadowRoot.querySelector('dialog') is null at first call. No retry, no fallback container. Every subsequent announceFocusDebounced silently does nothing.

RESOLVED P1-3 — Month-boundary nav can land on wrong date

RESOLUTION: new pickNearestCell function added to handle this behavior.

  • Verified at: auro-calendar.js:939-948 (also 899-902 for Left/Right).
  • When ArrowDown/Up crosses an unrendered month, after requestAnimationFrame double-tick, if cells.find(targetDate) returns nothing it falls back to cells[0] / cells[cells.length-1]. Possible silent off-by-one.

OUT OF SCOPE P1-4 — Breaking ISO-only change has no consumer safety net

RESOLVED: This would be a deprecation breaking change behavior we currently intentionally do not support.

  • Verified at: auro-datepicker.js:73 (slot renamed date_MM_DD_YYYYdate_YYYY_MM_DD), 347 (format prop), 1556 (format mutated inside willUpdate).
  • Slot rename: a consumer still using date_MM_DD_YYYY will get nothing rendered, with no warning. No console.warn on legacy ISO/format input anywhere.

OUT OF SCOPE P1-5 — Shift+Arrow for keyboard range selection not implemented

RESOLUTION: Design intentionally decided to not support this key combination at this time.

  • Verified: grep shiftKey across auro-datepicker.js / auro-calendar.js / auro-calendar-cell.js returns zero hits. Keyboard users in range mode can only commit endpoints via Enter; there's no "extend selection" affordance.

RESOLVED P1-6 — computeActiveDate not memoized

RESOLUTION: New function handled so blackout dates are recalculated when changing cell dates across months rapidly.

  • Verified at: auro-calendar.js:617new Set(this.disabledDays.map(...)) constructed on every call. Called from setActiveCell path and month-change path. With 100+ blackout dates this becomes user-visible.

RESOLVED P1-7 — Tests: no timezone coverage

RESOLUTION: Add TZ specific tests and run all tests across three different timezones. Similar changes made for auro-input since it also has date support.

  • Verified: grep TZ=|Pacific/|Asia/|process\.env\.TZ on auro-datepicker.test.js returns nothing. Only mitigation is a toLocalISODate test helper (test file:312) — a workaround for the assertion side, not a TZ matrix.
  • Given the explicit "fix: not to directly convert value to Date to avoid date shifting from timezone diff" commit (e407410cd), the absence of a regression test in a non-UTC TZ is a real gap.

P1-8 — RESOLVED Tests: a11y depth is shallow

RESOLUTION: stale activedescendant code removed in earlier change, no need to add a11y tests for it.

  • Verified: Single expect(el).to.be.accessible() axe-core call. No semantic assertions for the activedescendant pattern (which would have caught P0-2), no live-region announcement assertions tied to specific actions, no focus-restoration assertions beyond trigger.inert.

P2 — Confirmed edge cases

RESOLVED P2-1 — Disabled cell semantics are over-specified

RESOLUTION: Remove aria-hidden from cells.

TESTING: Confirm that iOS voiceover can not swipe navigate over out of range date cells.

  • Verified at: auro-calendar-cell.js:733, 747-752
  • Out-of-range cells get disabled attr and aria-hidden="true" and tabindex="-1" and event handlers nulled out. Belt-and-suspenders, but aria-hidden + disabled together is redundant; just disabled (or aria-disabled) is the spec-compliant pick.
  • Blackout cells (in-range but disabled in business logic) get aria-disabled="true" only (line 628) and remain focusable — that's actually correct.

RESOLVED P2-2 — aria-current="date" not set on today

RESOLUTION: Added aria-current to the today cell.

  • Verified: grep aria-current|ariaCurrent returns zero hits in calendar/cell source. Today is styled visually but not announced as "current date".

RESOLVED P2-3 — Single-day range hover shows no preview

RESOLUTION: Minor code change so that functionality applies to today as well as future dates.

  • Verified at: auro-calendar-cell.js:692, 694
    • isInRange = dayDate > departTimestamp && dayDate < hoveredDate (strict)
    • isDepartWithPreview = dayDate === departTimestamp && hoveredDate > departTimestamp (strict)
  • When user hovers on the start cell after picking it, both are false → no visual cue.

RESOLVED P2-4 — convertWcTimeToDate TZ assumption undocumented

RESOLUTION: Code is better documented.

  • Verified at: auro-datepicker.js:940-942 — does new Date(time * 1000) then dateFormatter.toISOFormatString. The library helper (node_modules/.../dateFormatter.mjs:~233) formats local YYYY-MM-DD per toLocalISODate philosophy. Correct for vendor-supplies-local-time; surprising if vendor ever emits UTC. JSDoc doesn't say.

RESOLVED P2-5 — isBlackout runs O(N) per render per cell

RESOLUTION: Code changed to memoize the data.

  • Verified at: auro-calendar-cell.js:195-221 and called from renderCellButton template at 713-762. With 100 blackouts × 42 cells × 2 months = ~8.4k array scans per re-render. Memoize per-month.

NO CHANGE P2-6 — Live region uses aria-live="assertive"

RESOLUTION: assertive is required for correct AT performance and deemed accessibility during review with organization A11Y representatives.

  • Verified at: auro-calendar.js:1355assertive interrupts the user mid-utterance. For arrow-key cell focus changes, polite is the standard pattern. assertive should be reserved for selection commits / errors.

RESOLVED P2-7 — Calendar grid lacks aria-roledescription="calendar"

RESOLUTION: aria attribute added to DOM.

  • Verified at: auro-calendar.js:1591 — no aria-roledescription. Low impact but a common pattern with role="grid".

P3 — Nits / debatable

RESOLVED P3-1 — Listener-leak applies to addEventListener('focusin'/'focusout') on host too

RESOLUTION: Fixed in earlier update above, documentation in code improved.

  • auro-datepicker.js:1325, 1330 add listeners to this (the host). When the host is GC'd these go with it, so the practical leak risk is from the listeners on children (inputList, calendar) which can outlive the host if reparented.

RESOLVED P3-2 — _validLocale fallback is silent

RESOLUTION: added console.debug when invalid locale is used.

  • auro-datepicker.js:1551 — invalid locale string silently falls back to en-US. Consider a one-time console.warn in dev builds.

RESOLVED P3-3 — New i18n label props lack @default in JSDoc

RESOLUTION: Add missing JSDoc content.

  • auro-datepicker.js:123-132, 292, 540-572calendarGridLabel, navLabelPrevMonth/Next, rangeLabelStart/End/Before/In/After, blackoutLabel have constructor defaults but no @default in JSDoc.

RESOLVED P3-4 — Repeated double-rAF without explanatory comment

RESOLUTION: improve double-rAF and documentation.

  • auro-calendar.js:347-356, 892-907, 932-952, 1067-1080. Pattern is correct (wait for re-render + paint) but undocumented; future-you may "simplify" and break things.

RESOLVED P3-5 — disabledDays (legacy timestamps) still supported alongside blackoutDates

RESOLUTION: Document the deprecation and add console message when used.

  • auro-calendar-cell.js:200-203. Backward-compat path; deprecate with warning eventually.

Triage recommendation

Ship-blocking: P0-1, P0-2, P0-3 (data correctness + a11y promise vs reality). P0-4 and P0-5 are real but the team has signaled (datepickerKeyboardStrategy.js header) that full keyboard nav is staged work — fine to bug-track instead of block.

@chrisfalaska

chrisfalaska commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

auro-select

P0 — Fix before merge

❌ P0-1. Enter handler swallows native form submission when bib is closed

File: components/select/src/selectKeyboardStrategy.js:66-74

Verified: preventDefault() and stopPropagation() run on line 67-68 before the !ctx.isExpanded branch. Inside a <form>, pressing Enter on a closed combobox no longer triggers form submit — the key is consumed unconditionally to open the bib.

Fix: Move evt.preventDefault(); evt.stopPropagation(); into each branch (only call them when expanded, or when opening from closed state if you want to suppress form submit only then).

❌ Why the feedback is wrong:
  - context/keyboard-spec.md:188-196 explicitly specifies: when closed, Enter opens the listbox — sourced to the APG select-only combobox pattern.
  - The WAI-ARIA APG reference implementation also intercepts Enter to open the listbox; it does not submit. 
  - Native <select> similarly does not submit the form on Enter — it opens the popup.
  - auro-combobox does exactly the same thing on purpose, with an explicit comment at comboboxKeyboardStrategy.js:121-124 justifying it.
  - auro-select is not a form-associated custom element (no ElementInternals/formAssociated), so the only way Enter could submit a form is via auro-form's keydown
  listener (auro-form.js:733-748) — and stopPropagation() is exactly what stops it. That's the intended interaction.
  
What I changed:
  - Added a comment to selectKeyboardStrategy.js:66-74 mirroring combobox's, so the next reviewer doesn't file the same finding.
  - Added a test

P1 — Real a11y gaps; address this sprint

✅ P1-1. Missing aria-haspopup="listbox" on the combobox trigger

File: components/dropdown/src/auro-dropdown.js:1199-1210

Verified: grep confirms aria-haspopup does not appear anywhere in auro-dropdown.js. The trigger sets role, aria-expanded, aria-controls, aria-labelledby, aria-disabled — but the WAI-ARIA APG select-only combobox pattern requires aria-haspopup="listbox" so VoiceOver announces "pop-up listbox" on focus.

Fix: Add aria-haspopup="${ifDefined(this.a11yRole === 'combobox' ? 'listbox' : undefined)}" to the trigger in renderBasicHtml.

Scope note: This is a dropdown-level gap, not introduced by this PR — but since the select PR locks in the combobox pattern, fixing it here is appropriate.

✅ Three-agent consensus:
  1. Code agent — confirmed aria-haspopup is absent from auro-dropdown.js, and auro-select sets a11yRole='combobox' on the dropdown.
  2. Spec agent — flagged that role="combobox" has an implicit aria-haspopup="listbox" per ARIA 1.2, so the missing attribute is not a true conformance gap.
  3. Tiebreaker agent — confirmed the canonical W3C APG select-only example explicitly sets aria-haspopup="listbox" despite the implicit default, to avoid relying on UA
   implicit-value mapping.
   
  Verdict: Fix is appropriate, though the feedback's "VoiceOver requires it" framing overstates the case. The real justification is matching the W3C reference
  implementation.

  Changes:
  - components/dropdown/src/auro-dropdown.js:1206 — added aria-haspopup="${ifDefined(this.a11yRole === 'combobox' ? 'listbox' : undefined)}" to the trigger.
  - components/dropdown/test/auro-dropdown.test.js — added two tests asserting the attribute is set when a11yRole='combobox' and absent on the default button role.

  Dropdown: 348/348 passing, 100% coverage. Select: 454/454 passing, 100% coverage.

❌ P1-2. Tab handler does not preventDefault; relies on async trigger refocus

File: components/select/src/selectKeyboardStrategy.js:88-99

Verified: Tab → menu.makeSelection()dropdown.hide(). No preventDefault. Native Tab bubbles, browser advances focus to the next tabbable. Meanwhile restoreTriggerAfterClose (auro-select.js:584) only runs for fullscreen-bib mode, and dropdown-toggled listener (auro-select.js:621-624) only refocuses trigger inside a setTimeout(0) in non-fullscreen.

Risk: Real risk that focus jumps past the trigger on Tab+select.

Fix: Call evt.preventDefault() and synchronously refocus trigger, or explicitly advance focus to the next tabbable.

❌ Why the feedback is wrong
  
  The reviewer's premise — that focus should stay on the trigger — contradicts the spec.

  APG Select-Only Combobox (spec) defines Tab on an open listbox as three explicit actions:

  1. Set value to the focused option
  2. Close the listbox
  3. Perform the default action, moving focus to the next focusable element

  The APG reference implementation also does NOT call preventDefault() on Tab. Calling it would violate the spec.

  Verified by code trace

  In popover (non-fullscreen) mode:
  - Tab handler runs synchronously: makeSelection() → dropdown.hide()
  - dropdown.hide() does not move focus (popover never moved focus into the bib; aria-activedescendant pattern keeps focus on trigger)
  - restoreTriggerAfterClose is fullscreen-only (correctly skipped)
  - setTimeout(() => trigger.focus()) is gated on isPopoverVisible === true (open path only, doesn't fire on close)
  - Browser's native Tab default then advances focus past the trigger to the next tabbable — exactly as APG requires

  The "race" the feedback worries about doesn't exist: nothing schedules an async refocus on close in popover mode.

  Confirmed by existing tests + docs

  - components/select/test/auro-select.test.js:3351-3483 covers Tab: closes bib, selects active option, works in multiSelect
  - components/select/demo/keyboardBehavior.md:158-178 explicitly documents: "the page will also navigate to the next focusable element in the tabindex sequence"
  - All 454 tests pass; the no-preventDefault behavior is intentional

  Recommendation

  Reject the P1-2 finding. The current implementation matches APG spec, matches native <select> behavior, matches the APG reference implementation, and matches the
  documented design intent. Adding preventDefault() + sync trigger refocus would be a regression.

  What I did

  - Added a test to assert behavior

✅ P1-3. aria-setsize/aria-posinset only set once at init

File: components/select/src/auro-select.js:831 (updateOptionPositions() called once in configureMenu)

Verified: No slotchange or auroMenu-loadingChange re-trigger of updateOptionPositions. Dynamic option lists (lazy loading, filtering, async fetches) leave stale set-size announcements.

Fix: Listen on the default slot's slotchange or hook into handleMenuLoadingChange (line 1021) to re-run updateOptionPositions().

✅ Bug is real: updateOptionPositions() was only called once at init (line 831). No re-trigger on slotchange, async load, or any dynamic option mutation.
  aria-setsize/aria-posinset go stale.
  - Reviewer's suggested hooks are suboptimal:
    - slotchange on default slot: that slot lives in auro-menu's shadow DOM, not auro-select's — auro-select can't observe it directly.
    - handleMenuLoadingChange: only fires when a loading state transition happens; misses sync option swaps and filter-based mutations.
  - Better hook: auroMenu-optionsChange is dispatched by initItems() (auro-menu.js:642) on every path that rebuilds the items list. Single subscription covers all
  cases; no feedback loop (attribute mutations on slotted children don't refire slotchange).
  
Also refreshed this.options = this.menu.options in the same handler since that cached reference was going stale alongside.

Added new tests

P2 — Code hygiene; fix while you're in here

✅ P2-1. this.options is dead state after the pre-5.9 revert

File: components/select/src/auro-select.js:830, 362-365

Verified: this.options = this.menu.options is set once in configureMenu. updateOptionPositions reads this.menu.options directly (line 770); renderNativeSelect reads this.menu.options directly (line 1321). No code reads this.options.

Fix: Delete the property and the assignment, or wire it to slotchange if a reactive snapshot is genuinely needed.

✅ The original feedback was partially wrong. Both subagents independently confirmed that:
  - this.options is never read anywhere (true)
  - It's marked @private and not part of the public API (true)
  - There were actually two assignments, not one — line 830 in configureMenu and line 838 in the auroMenu-optionsChange listener

  But it was not dead state. The reactive state: true property declaration meant each assignment triggered a Lit re-render — and renderNativeSelect reads
  this.menu.options directly to build the hidden <select>'s <option> list. Without that implicit re-render trigger, the native select rendered with empty options on
  first paint, breaking native↔component value sync (caught by auro-select.test.js:954).

  Fix applied (components/select/src/auro-select.js):
  - Deleted the options property declaration (lines 362-365)
  - Replaced both this.options = this.menu.options assignments with explicit this.requestUpdate() calls
  - Updated the comments to explain that the update is for the native <option> list, not just aria attributes
  - Added new test assertion

  This makes the re-render intent explicit instead of hidden behind a phantom property assignment.

❌ P2-2. Menu event listeners are never detached

File: components/select/src/auro-select.js:832, 834, 843, 862

Verified: configureMenu attaches four addEventListener calls with anonymous arrow functions. disconnectedCallback (line 1117-1120) only clears the type-ahead buffer — no listener removal. Confirmed by grep -n removeEventListener auro-select.js returning no matches.

Risk: Leak in SPA scenarios where the auro-menu instance outlives the host or is reparented. Not regressed by this PR — but the revert reintroduces the direct-listener pattern, so worth closing now.

Fix: Store bound handler references, remove them in disconnectedCallback.

❌ The finding is a false positive (or at most EDGE_CASE_ONLY) — and the proposed fix would silently break the component
   on reparent.

  Why it's not a real leak

  - this.menu is a light-DOM child queried via querySelector('auro-menu, [auro-menu]') (line 790). When <auro-select> is removed, the child <auro-menu> goes with it.
  The listener closure forms a cycle (menu → closure → host → menu), but mark-and-sweep GC collects unreachable cycles fine.
  - firstUpdated is the only caller of configureMenu (the setTimeout retry at 794-797 early-returns before any addEventListener). Lit runs firstUpdated exactly once per
   instance — no double-attach race.
  - Same pattern is used in auro-combobox.js and auro-calendar.js without cleanup — it's the codebase convention.

  Why the proposed fix is actively unsafe

  The reviewer reading lifecycle semantics caught this: if we add removeEventListener calls to disconnectedCallback, then on a DOM move (e.g., <auro-select> lives
  inside a dialog that gets reparented), the listeners get stripped — but firstUpdated/configureMenu never re-run on reconnect, so the component becomes silently
  broken: no activated-option announcements, stale aria-setsize, no selection handling.

  A correct fix would need to also re-attach in connectedCallback (gated on hasUpdated) using an AbortController per connection. That's a bigger change than P2-2
  describes, and there's no evidence anyone has hit the edge case (an external consumer pulling the <auro-menu> out of the <auro-select> and keeping it alive).

  Recommendation

  Don't apply this fix. Push back on P2-2 in the review — it's a false positive, and the naive fix (the exact one described: "store bound handler references, remove
  them in disconnectedCallback") would regress accessibility on reparent. If you want to defensively guard the exotic reparent-the-slotted-menu case, that's a separate,
   larger change (connect/disconnect lifecycle with AbortController), and probably wants to be applied to auro-combobox at the same time for consistency.

What I did

  - Added a test to assert expected behavior

P3 — Nits / spec deviations to document, not fix

✅ P3-1. Duplicate "selected" announcement on Enter against already-selected option

File: components/select/src/auro-select.js:843-855, 862-889

Verified: Confirmed narrow case. On bib open, auroMenu-activatedOption fires for the pre-selected option → announces "X, selected". If user then presses Enter to confirm, makeSelection (auro-menu.js:819-822) takes the re-select branch → notifySelectionChangeauroMenu-selectedOption → 300ms later announces "X, selected" again. Outside this narrow flow, the two paths don't collide because handleSelectState does not call updateActiveOption. Lower-priority than my first pass suggested.

Fix (optional): Skip the activatedOption announce when the activated option is already selected, since the selection announce will cover it.

✅ Verified the bug — the duplicate is real.

  Flow:
  1. Bib opens → auroMenu-activatedOption fires for pre-selected option → announces "X, selected" (auro-select.js:858-859)
  2. User presses Enter → makeSelection re-select branch in auro-menu.js:822 calls notifySelectionChange → auroMenu-selectedOption → 300ms later announces "X, selected"
   again (auro-select.js:891-892)
   
  Fix — components/select/src/auro-select.js:855-862. Guard the activatedOption announcement when the option is already selected. aria-activedescendant + aria-selected
  continue to convey state for navigation; the Enter path's selection announce is the authoritative "selected" utterance.

  Test — components/select/test/auro-select.test.js: new regression test opens a presetValueFixture and asserts the live region stays empty after the bib opens onto the
   pre-selected option.

✅ P3-2. Type-ahead repeated-char cycling conflicts with multi-letter prefix

File: components/select/src/auro-select.js:986

Verified: new Set(this.typeaheadBuffer).size === 1 correctly detects all-same-char buffer. Edge: typing "aa" when both "Aardvark" and "Anchor" exist as options cycles by single-char instead of treating "aa" as a prefix matching only "Aardvark". APG says cycle only when no longer prefix matches.

✅ Bug confirmed:
  - APG spec agent: Spec language ("focus moves to the next item with a name that starts with the string of characters typed") and the APG reference implementation
  treat the buffer as a literal prefix. Cycle-on-repeated-char is a Windows convention, not APG.
  - Code verification agent: Walked the buffer state — typing 'a' then 'a' with [Aardvark, Anchor] does land on Anchor, not Aardvark. No existing test covers the case.

  Fix at components/select/src/auro-select.js:993: prefix match runs unconditionally first; the repeated-char cycle only fires when the buffer has no longer prefix
  match. The existing Apple/Apricot/Avocado cycle test still passes because no option starts with aa/aaa/aaaa, so the cycle fallback engages as before.

  New test at components/select/test/auro-select.test.js: Aardvark + Anchor fixture asserts that "aa" stays on Aardvark instead of cycling to Anchor.

✅ P3-3. _handleNativeSelectChange multiselect branch operates on string menu.value

File: components/select/src/auro-select.js:1261-1282

Verified: Post-revert, this.menu.value for multiselect is a JSON string (per auro-menu.js:662). currentArray = this.menu.value || [] then .includes() does substring matching on a JSON string; the spread [...currentArray, selectedValue] would spread JSON characters. However: renderNativeSelect (line 1311-1331) does not set the multiple attribute on the native <select>, so the multiselect branch is effectively unreachable through the native element. Dead-code bug.

Fix (optional): Remove the multiselect branch, or hold it correct against the string-valued menu.value.

✅ Bug Confirmed

  Both subagents confirmed the finding independently:
  - menu.value for multiselect is a JSON string (per auro-menu.js:662) — .includes() would substring-match, spread would explode JSON chars
  - renderNativeSelect (now lines 1306-1331) never sets multiple on the <select>, so the multiselect branch in _handleNativeSelectChange is unreachable through any real
   code path
  - The existing test only passed because menu.value was undefined, never exercising the broken logic

  Fix applied (components/select/src/auro-select.js:1271-1279):
  - Removed the dead multiselect branch entirely
  - Collapsed the single-select path — _handleNativeSelectChange now just syncs the single selected value
  - Removed the stale test that exercised the dead branch

  Chose removal over "fix it correctly" because adding real multiselect support to the hidden native fallback (the alternative agent 2 proposed) is a feature expansion,
   not a bug fix — and the native <select> is just an a11y/form-submission fallback.

✅ P3-4. Type-ahead doesn't open bib on no-match

File: components/select/src/auro-select.js:999-1003

Verified: dropdown.show() only runs when match is truthy. Documented behavior at docs/partials/keyboard-behavior/keyEvents.md:212. Native <select> opens regardless; this is an intentional deviation.

Action: Add to spec-deviation list in keyboard-spec.md memory if not already there.

✅ Confirmed, intentional

- Added a one line code comment
- Added a test to assert

✅ P3-5. Home/End collapsed → no-op

File: components/select/src/selectKeyboardStrategy.js:54-56, 77-79

Verified: Both return early when !ctx.isExpanded. APG suggests they should open + move to first/last.

Action: Spec-deviation entry.

✅ Verified: Two subagents (APG spec lookup + native <select> behavior research) converged — APG's select-only combobox example explicitly defines Home/End on a
  collapsed combobox as "Opens the listbox and moves visual focus to the first/last option." Native macOS <select> does nothing, but auro-select tracks APG. The
  early-return no-op was a real spec deviation with no UX justification (unlike the type-ahead no-match case, where opening an empty bib would be useless — here there
  are options to navigate to).

  Fix applied:
  - components/select/src/selectKeyboardStrategy.js:53-77 — Home() and End() now call dropdown.show() when collapsed, then set optionActive to first/last enabled
  option.
  - Tests updated (auro-select.test.js:3001-3017, 3363-3380) — two should do nothing while bib is collapsed tests rewritten to assert the new APG behavior. All 466
  select tests pass.
  - Docs updated: components/select/docs/partials/keyboard-behavior/keyEvents.md (added Collapsed rows for Home/End), context/keyboard-spec.md (added Home/End to
  "Desktop — Popup Closed" table), components/select/test/MANUAL_TESTING.md.

Testing

On Mac laptops with shorter keyboards, testing can be done using:

  - Home = Fn + ←
  - End = Fn + →

❌ P3-6. No PageUp/PageDown handlers

File: components/select/src/selectKeyboardStrategy.js (no PageUp/PageDown keys)

Verified: Strategy map has no entries; falls to default which only handles Space + printable; non-printable PageUp/PageDown are filtered out by updateActiveOptionBasedOnKey's length-1 guard (auro-select.js:959).

Action: Spec-deviation entry.

❌ Verified the claim: components/select/src/selectKeyboardStrategy.js has no entries for PageUp/PageDown. The default branch only special-cases Space; everything else
  forwards to updateActiveOptionBasedOnKey(), where the _key.length !== 1 guard at components/select/src/auro-select.js:966 drops any multi-character key name. Net
  effect: keystroke is swallowed — no announcement, no movement.

  Decision: leave as-is. Per the WAI-ARIA APG combobox pattern, PageUp/PageDown are not listed in the Listbox Popup keyboard interactions at all (they only appear in
  the Grid Popup variant, where they're optional). Omitting them in a listbox-backed select is fully spec-compliant — this is not a gap, it's a non-feature. Already
  documented as unsupported in components/select/demo/keyboardBehavior.md (lines 14–18) and absent from the docs/partials/keyboard-behavior/keyEvents.md table, so
  end-user docs are accurate.

✅ P3-7. Race when type-ahead opens bib from closed state

File: components/select/src/auro-select.js:592-602 vs 999-1003

Verified: When type-ahead triggers dropdown.show() (line 1001), the auroDropdown-toggled handler (line 592-602) sets the first-enabled or selected option as active before line 1003 overrides with the type-ahead match. Net result is correct (match wins), but a brief AT misfire is possible.

Fix (optional): Pre-stash desired active before show(), or have show() accept an initial-active hint.

✅ Verified — race is real. Sub-agent consensus (after resolving disagreement by reading floatingUI.mjs:783-801):

  - dropdown.show() → floater.showBib() → dispatchEventDropdownToggle() calls element.dispatchEvent(event) synchronously
  - So at auro-select.js:1012 the auroDropdown-toggled handler runs inside show(), synchronously writes aria-activedescendant to firstActive/selected, then line 1014
  overwrites with the type-ahead match
  - Live-region announceToScreenReader rAF-coalesces, but aria-activedescendant is set twice and some ATs (NVDA) can pick up the transient

  Fix applied at components/select/src/auro-select.js:1010-1018: moved updateActiveOption(match) before dropdown.show(). The guard at line 585 (if 
  (!this.menu.optionActive)) now short-circuits and the toggled handler skips its fallback. One write, one event.

✅ P3-8. Label slot discovery is non-reactive

File: components/select/src/auro-select.js:655-658, 815-818

Verified: bibDialogLabel and menu aria-label are read once at configureDropdown/configureMenu. Runtime swaps of label content don't propagate.

Fix (optional): MutationObserver on the label slot.

✅ Verified and fixed.

  - configureDropdown (line 647) and configureMenu (line 807) read the label slot exactly once during firstUpdated. No slotchange listener, MutationObserver, or other
  reactive mechanism exists for the label.
  - The downstream chain IS reactive: auro-dropdown.bibDialogLabel is a Lit property bound to the bib's <span id="dialogLabel">, and aria-label on the menu is a plain
  attribute. So reassigning works — only the upstream source wasn't.
  - Impact is real but narrow: i18n locale swaps or dynamic relabeling leave VoiceOver/dialog and listbox aria-label stale.

  Fix applied to components/select/src/auro-select.js:
  1. Extracted a _syncLabelText() helper used by both configureDropdown and configureMenu. Also fixed the pre-existing selector mismatch (span[slot="label"] →
  [slot="label"]) and removes aria-label cleanly when the label is empty (ARIA-correct).
  2. Added _observeLabelChanges() — a MutationObserver on the host with childList + subtree + characterData. Chose MutationObserver over slotchange because slotchange
  doesn't fire on textContent mutation of an already-assigned slotted node — the common i18n case.
  3. Wired observer setup in firstUpdated and teardown in disconnectedCallback.

  Test added at auro-select.test.js:2218 — mutates label textContent at runtime and asserts both menu aria-label and dropdown.bibDialogLabel update without manually
  re-calling configure methods. Passes.

  Test run: 462/464 pass on the branch with the fix. The 2 failures (announceToScreenReader populate/clear live region) are pre-existing — confirmed by stashing my
  changes and reproducing 460/462 with the same 2 failures. Unrelated to this change.

Recommended merge gate

  • Must fix: P0-1 (Enter swallows form submit).
  • Should fix this sprint: P1-1 through P1-3 (real a11y gaps that VoiceOver/keyboard users will hit).
  • Worth fixing while in this file: P2-1, P2-2.
  • Document only: all P3 items go on the spec-deviation list in keyboard-spec.md.

No critical/blocking bugs in the core revert (optionSelected clone, selectByValue routing, selectUtils helpers) — those are correctly implemented and tested.

@chrisfalaska

chrisfalaska commented Jun 24, 2026

Copy link
Copy Markdown
Contributor

auro-combobox

P0 — Blocker (ship-stoppers)

P0-1. Enter on a collapsed combobox blocks form submission

components/combobox/src/comboboxKeyboardStrategy.js:120-128 — the else branch calls evt.preventDefault() + evt.stopPropagation() + showBib() even when nothing is going to happen visibly. APG: Enter on a collapsed editable combobox should let the form submit. The inline comment ("safe because showBib() opens the dialog programmatically") misses the point — the bug is the swallowed default.

P0-2. aria-autocomplete is missing on the combobox input

Searched components/combobox/src/auro-combobox.js, components/input/src/auro-input.js, components/input/src/base-input.js — no a11yAutocomplete property, no aria-autocomplete attribute. APG requires this on role="combobox". Set "list" for filter/suggestion, "none" for select-only.

P0-3. Dual aria-activedescendant writes will mis-resolve

  • components/combobox/src/auro-combobox.js:1855 and :1891 set .a11yActivedescendant=${this.optionActive.id} (string id)
  • components/input/src/auro-input.js:475 renders that as aria-activedescendant=${ifDefined(this.a11yActivedescendant)} on the native input
  • components/combobox/src/auro-combobox.js:1227 separately calls this.input.setActiveDescendant(this.optionActive), which sets ariaActiveDescendantElement IDL prop at base-input.js:1203-1205

The id-string attribute cannot resolve across shadow boundaries; only the element-ref IDL prop works. Drop the .a11yActivedescendant binding (both call sites) and rely on setActiveDescendant().

P1 — High (visible regressions / spec violations)

P1-1. persistInput regression in setInputFocus()

components/combobox/src/auro-combobox.js:1040-1042 is now a bare this.input.focus() else branch. On main, the equivalent code routed focus to the clear button when persistInput was set and a menu-option had been clicked. The test that covered this is commented out at components/combobox/test/auro-combobox.test.js:2391-2425.

Correction to prior review: setClearBtnFocus() is NOT orphaned — comboboxKeyboardStrategy.js:168 still calls it from the modal-mode Tab path. The regression is just the persistInput menu-option-click path.

P1-2. Alt+ArrowUp does not close the popup (APG violation)

components/combobox/src/comboboxKeyboardStrategy.js:84-87 — with altKey and bib open, it jumps to first option instead of closing without selecting. Docs at components/combobox/docs/partials/keyboard-behavior/keyEvents.md document the deviation but keyboard-spec.md memory does not justify it.

P1-3. Shift+Tab doc/code drift

Docs at components/combobox/docs/partials/keyboard-behavior/keyEvents.md:291-305 (Tab row has rowspan=2; second row is Shift+Tab): "Advances focused option to first enabled option. Note: the bib will NOT close."

Code at components/combobox/src/comboboxKeyboardStrategy.js:154-170 treats Shift+Tab the same as Tab (makeSelection + hideBib, only skips the clear-btn focus).

Status: VERIFIED — pick one.

P1-4. No "X results / no results" filter announcement

components/combobox/src/auro-combobox.js:1831 declares <span id="srAnnouncement" ... aria-live="polite" role="status">. handleMenuOptions() at :757-769 sets aria-setsize/aria-posinset on each option but never announces the count. APG recommends an announcement on every filter change.

P1-5. auroMenu-selectValueFailure leaves combobox trigger label stale

components/combobox/src/auro-combobox.js:1218-1221 wipes value and optionSelected but never calls updateTriggerTextDisplay. Compare with components/select/src/auro-select.js:834-841 which calls updateDisplayedValue() after the wipe. Combobox should match. Status: VERIFIED — asymmetric.

P2 — Medium (smells, partial regressions, fragility)

P2-1. getOptionLabel() can return empty when only displayValue slot is set

components/combobox/src/auro-combobox.js:54-64 strips the [slot="displayValue"] subtree before reading textContent. Two consumers:

  • :677 — falls back to menu.currentLabel. Mitigated.
  • :1160 — falls back to this.menu.value (could be a raw code like "JFK" rather than "New York"). Partial regression.

Status: VERIFIED, partial. Either change :1160 to also fall back to menu.currentLabel, or make getOptionLabel return the displayValue text when no other text exists.

P2-2. _pendingMenuValueSync 0 ms reset can swallow a real selection

components/combobox/src/auro-combobox.js:1521-1527 — the inline comment acknowledges the hazard ("if menu.value === menu.optionSelected.value already, the listener won't fire and the flag would swallow the next user click") but the workaround (a 0 ms timer) is the same scheduling tier as the bug. Use a counter, or signal echo-vs-user explicitly.

P2-3. setMenuValue divergence between select and combobox

  • components/select/src/auro-select.js:1132-1135: this.menu.selectByValue(value) (uses menu's empty-string normalization)
  • components/combobox/src/auro-combobox.js:1515-1528: direct this.menu.value = value assignment

Passing '' to combobox bypasses the clear-selection path. Align the two.

P2-4. ArrowDown/ArrowUp guard on availableOptions.length, not ctx.isExpanded

components/combobox/src/comboboxKeyboardStrategy.js:56,79. When the user has typed a non-match, arrow keys become no-ops and don't preventDefault(), so the caret jumps within the input.

P2-5. Select multiselect auroMenu-selectValueFailure wipes the whole array

components/select/src/auro-select.js:836: this.optionSelected = this.multiSelect ? [] : undefined;. If menu fires failure for one bad value out of several valid ones (depends on menu's failure semantics), the entire selection is reset. Status: VERIFIED — needs menu-side audit to confirm severity.

P2-6. VoiceOver position double-announcement risk

components/combobox/src/auro-combobox.js:767-768 sets aria-setsize/aria-posinset on every visible option and :1238 announces "${optionText}${selectedState}, ${optionIndex} of ${optionCount}" via live region. AT that resolves the ARIA attributes will hear position twice. Pick one path.

P3 — Low (cleanup / docs)

P3-1. PageUp / PageDown not implemented

No handlers in either strategy; not documented as a deviation. APG recommends but doesn't require for combobox.

P3-2. docs/auro-select-revert-tests.md is untracked

File exists (4.3 KB, modified 2026-06-24) but not in git. Either commit it (it's likely valuable context for reviewers) or delete it.

P3-3. Re-entrancy flag stew

_pendingMenuValueSync, _clearing, _syncingDisplayValue, _programmaticFilterRefresh, _syncingBibValue all confirmed present (auro-combobox.js:1142, 1329, 1352, 1560, 700, 1190). None reset in disconnectedCallback. Worth a state-machine refactor before more behavior accretes here.

P3-4. Duplicated imask-resync block

components/combobox/src/auro-combobox.js:714-723 and :1640-1655 are byte-identical. Extract.

P3-5. Suggestion mode: auroMenu-selectValueFailure listener has no behavior guard

components/combobox/src/auro-combobox.js:1218. Current code paths only push unmatched values through setMenuValue in filter mode (:1607), so the listener is effectively scoped today — but the listener itself doesn't enforce it. Add a guard or document the assumption.


Test Gaps (Verified by Absence)

Test Priority Why
Enter on collapsed empty combobox does not call preventDefault() P0 Prevents regression of P0-1
Alt+ArrowUp closes popup without selection P1 APG contract
Filter result count is announced via srAnnouncement P1 Covers P1-4
auroMenu-selectValueFailure refreshes trigger label P1 Covers P1-5
Shift+Tab matches documented behavior P1 Covers P1-3
persistInput + menu-option click → clear-button focus P1 Existing test currently commented out
Esc from modal close button restores trigger focus P2 Native cancel path bypasses strategy
Ctrl+Arrow support in combobox P2 Feature exists but lacks coverage
disconnectedCallback cleans up timeouts and sync flags P3 Hygiene

Suggested Merge Gate

Must Fix Before Merge

  • P0-1 — Enter blocks form submission
  • P0-2 — Missing aria-autocomplete
  • P0-3 — Dual aria-activedescendant wiring

Should Fix Before Merge

  • P1-1persistInput regression
  • P1-2 — Alt+ArrowUp APG violation
  • P1-3 — Shift+Tab doc/code drift
  • P1-4 — Missing result-count announcements
  • P1-5 — Stale trigger label after selection failure

Follow-Up Issues

  • All P2 findings

Nice-to-Haves

  • All P3 findings

Final Assessment

The major regressions are concentrated around keyboard accessibility, combobox ARIA semantics, and focus management. The broader combobox/select synchronization work appears structurally sound, but the P0 and P1 findings should be addressed before merging dev into main.

@chrisfalaska

Copy link
Copy Markdown
Contributor

auro-menu

P0 (ship-blocking)

P0-1 · Doc + manual-test drift for removed features

Verified. allowDeselect, auroMenu-deselectPrevented, and selectAllMatchingOptions are fully gone from src/ but documented as live features in:

  • components/menu/docs/pages/css-only.md:97, 105, 109, 182
  • components/menu/docs/pages/why-menu.md:40-41, 128
  • components/menu/test/MANUAL_TESTING.md:10-11, 98-99

Users following these docs will write code against APIs that don't exist. Mechanical cleanup, must be done before release.

P0-2 · handleDeselectState throws on undefined optionSelected

Verified. auro-menu.js:698this.optionSelected.filter(...) has no guard. Reachable when external callers (form reset, programmatic state manipulation) clear optionSelected then trigger deselection. Internal call path via toggleOption is safe (gated by isOptionSelected), but the method is effectively part of the public-ish API. Add (this.optionSelected || []) guard.

P1 (regressions that affect consumers)

P1-1 · auroMenu-customEventFired re-fires for already-selected options on multiselect

Verified. auro-menu.js:449-456 — the loop fires handleCustomEvent for every currently-selected option on every value change, not just newly-added ones. Multiselect consumers using event attrs will see duplicate dispatches as the user accumulates selections.

P1-2 · auroMenu-selectedOption fires on clear with source: undefined

Verified. auro-menu.js:372-374 + notifySelectionChange at :1014-1016. When value is cleared, optionSelected transitions to undefined, the changedProperties.has('optionSelected') branch fires auroMenu-selectedOption with no detail. Consumers can't distinguish "user picked nothing" from "user picked something." Add a reason or skip the event when both old and new are empty.

P1-3 · handleNestedMenus overwrites option innerHTML on every slotchange

Verified. auro-menu.js:780-783option.innerHTML = ... runs unconditionally on slotchange. Now that handleNestedMenus runs on every slotchange (the deliberate a11y fix), any event listeners or non-Auro children inside an option's light DOM are destroyed on every menu mutation. Replace with prepended span nodes instead of innerHTML round-trip.

P1-4 · Keyboard Enter does not fire auroMenuOption-click

Verified. auro-menuoption.js:226-239 only dispatches on click. auro-menu.js:862-864 Enter → makeSelection() never produces auroMenuOption-click. Consumers listening for that event for side effects (e.g. close on activate) silently skip keyboard activation. Either dispatch it from makeSelection, or document that the event is mouse-only and use auroMenu-selectedOption as the canonical activation signal.

P2 (correctness / perf, not user-visible regressions)

P2-1 · updateItemsState runs twice per value change

Verified. auro-menu.js:441 and :468 both execute on every value change with overlapping changedProperties — the expensive matchWord DOM-rebuild path runs twice for nothing. Drop the explicit call at 441.

P2-2 · initItems dispatches auroMenu-optionsChange on every re-init

Verified. auro-menu.js:642-646 fires the event regardless of whether options actually changed. Called from firstUpdated and every handleSlotChange. Consumers debouncing on this event get spurious wakeups.

P2-3 · Tab double-commit risk in standalone usage

Partially verified. auro-menu.js:858-861 calls makeSelection on Tab, and selectKeyboardStrategy.js:88-99 also calls component.menu.makeSelection() on Tab. In the select/combobox-hosted pattern these do not collide because focus stays on the trigger and menu's keydown listener never sees the bubbled event (options have tabindex=-1, menu is inside the bib not an ancestor of the trigger). Standalone usage of auro-menu does have an unintended Tab→commit path. Acceptable if standalone usage isn't a supported configuration; otherwise gate on rootMenu && document.activeElement-inside-menu.

P2-4 · reset() duplicates clearSelection() logic

Verified. auro-menu.js:726-753. They can drift. reset() should call clearSelection() and then dispatch the event.

P3 (gaps, pre-existing, non-blocking)

P3-1 · No aria-activedescendant written by auro-menu itself

Verified. updateActiveOption at auro-menu.js:977-996 only toggles a CSS class. Works only because consumers (auro-select/combobox) wire ariaActiveDescendantElement from the trigger. Pre-existing — not a regression. Worth tracking for any future standalone consumer.

P3-2 · Missing keyboard handlers (Home / End / PageUp / PageDown / Space / Escape / typeahead / Arrow-Left-Right)

Verified. auro-menu.js:849-868 handles only ArrowUp/Down/Tab/Enter. Parent strategies in selectKeyboardStrategy.js cover Home/End/Escape/Space/typeahead at the host layer. Standalone auro-menu consumers have incomplete listbox semantics. Pre-existing.

P3-3 · aria-multiselectable="true" but no Space toggle

Verified. auro-menu.js:462 advertises multiselect; the menu's own handler offers no Space-to-toggle in multiselect mode. Parent (selectKeyboardStrategy.js) handles it for hosted usage. Standalone gap.

P3-4 · role="group" + aria-label="submenu" is misleading

Verified. auro-menu.js:771-777. Pre-existing; not menu-semantics — visual indent grouping.

P3-5 · Loading placeholder remains in the a11y tree as role="option"

Verified. auro-menu.js:1086-1101. aria-busy="true" is set on the listbox at :479. SR users hear "busy" but the placeholder is still a reachable disabled option. Pre-existing.

P3-6 · Test coverage gaps

Verified. No framework-tests/ directory exists on dev (the diff stat referenced files that aren't tracked — confirm they weren't accidentally dropped). Specific gaps:

  • Nested-menu re-init on slotchange — untested
  • Multiselect deferred-match path (auro-menu.js:920-927) — only single-select late-bind covered (test line 781)
  • disabled propagation to options — not asserted
  • handleCustomEvent empty/null event name — untested
  • Thin assertions: resetShapeClasses/resetLayoutClasses (1619-1644) only .to.not.throw(); hasLoadingPlaceholder (940) doesn't check shadow DOM; noCheckmark (1046) doesn't verify rendering; mouseover/mousedown (1936) has no assertions

Recommended merge order

  1. P0-1 doc cleanup (mechanical, no risk)
  2. P0-2 undefined guard (one-line fix)
  3. P1-1, P1-2, P1-3, P1-4 — design call needed on event semantics; P1-3 needs implementation change
  4. P2-1, P2-2 — performance, safe to defer if release pressure
  5. P3 — file follow-up issues; do not block this revert

@chrisfalaska

chrisfalaska commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

auro-input

P0 — Ship blockers

  1. RESOLVED Safari cursor-jump fix lost

RESOLUTION: Restore bug fix

components/input/src/base-input.js:1118-1135 (dev) vs main base-input.js:848-881. The setSelectionRange retry block is gone; setSelectionInputTypes array at line 144 is now dead code. Restore the post-updateComplete setSelectionRange for text/password/email, or remove the array and document why it's no longer needed.

  1. RESOLVED Four shipped examples use legacy non-ISO min/max — confirmed via cat:

RESOLUTION: Updated examples to use ISO date formats

  • apiExamples/min-date.html:1min="03/25/2023"
  • apiExamples/max-date.html:1max="03/25/2023"
  • apiExamples/set-custom-validity-range-underflow.html:1min="01/01/2024"
  • apiExamples/set-custom-validity-range-overflow.html:1max="12/31/2025"

Under the new contract these silently invalidate. Convert to ISO.

  1. RESOLVED Migration guide silent on the ISO breaking change

RESOLUTION: This specific guide is not supposed to cover it. The v6 guide will.

docs/releases/05.12.00.md:5-15 (migration) and :190-198 (AURO-INPUT section) make no mention of the ISO value/min/max requirement or new locale/format semantics. Add a migration entry + example diff.

P1 — Fix before release

  1. RESOLVED AuroInputUtil.toFormattedValue(date, undefined) throws

RESOLUTION: Now handles invalid format gracefully.

components/input/src/utilities.js:267 calls format.toLowerCase() with no guard. Public API exposed in auro-input-util.js:42. Add early return when format is falsy.

  1. RESOLVED formatISODate returns garbage on invalid ISO string

RESOLUTION: Code updated to better handle invalid isoStr values.

components/input/src/auro-input-util.js:21-32. dateFormatter.stringToDateInstance("not-a-date") returns Invalid Date; getFullYear() etc. yield NaN; String(NaN).padStart(4,'0') = "0NaN". Output becomes "0NaN/0NaN/0NaN" rather than undefined. Add if (Number.isNaN(date.getTime())) return undefined; after line 22. The try/catch comment at line 33 is misleading — stringToDateInstance doesn't throw on bad input, it just returns Invalid Date.

  1. OUT OF SCOPE Date inputs lost their format hint helptext

RESOLUTION: Intended behavior is that the helpText should contain formatting guidance.

auro-input.js:366-379 removed the type === 'date' branch that existed on base-input.js:1035-1037 of main. Commit ad4a91c8e did this intentionally, but SR users now only have the placeholder for format guidance (placeholder is unreliable on iOS VoiceOver and disappears once a value exists). Either restore the i18n hint or add sr-only text wired via aria-describedby.

  1. RESOLVED format case-sensitivity regression

RESOLUTION: normalized format to lowercase.

main's firstUpdated had if (this.format) { this.format = this.format.toLowerCase(); }; that line is gone on dev. Confirmed via git show main:components/input/src/base-input.js. Uppercase format (used in the new apiExamples/localization-locale-formatted.html:3) misses the dateFormatMap lookup at base-input.js:795, leaving setCustomValidityForType unset. Restore the lowercase normalization or normalize at lookup site.

  1. RESOLVED Show-password toggle has no aria-pressed

RESOLUTION: aria attribute added

auro-input.js:540-548. The slot-text swap ("Show Password" / "Hide Password") is not a toggle pattern; SR users miss the pressed state. Add aria-pressed="${this.showPassword}" to the button.

P2 — Polish

  1. RESOLVED Constructor double-initialization in BaseInput

RESOLUTION: Alphabetized and made initialization singular.

base-input.js:36-58 writes defaults, then _initializePrivateDefaults at lines 65-158 overwrites many of them, including instantiating new AuroFormValidation() twice (lines 104, 156) and new AuroInputUtilities(...) twice with different locale (lines 100, 152) — the first instance of each is discarded. icon, dvInputOnly, hasValue, label, touched, showPassword, _rawMaskValue are written twice within _initializePrivateDefaults itself. Consolidate.

  1. RESOLVED Error message live region remounts on validity flip

RESOLUTION: Improved DOM code.

auro-input.js:617-634 swaps between two distinct <p id="${this.uniqueId}"> nodes when validity transitions. VoiceOver may not re-announce because the live region element identity changes. Keep one <p> and toggle text/error attribute on the parent helpTextTag.

  1. RESOLVED New apiExamples not surfaced as named stories

RESOLUTION: Add examples to storybook testing

stories/individual-api-examples.stories.ts:50-92 doesn't export FormatIsoValue, LocalizationLocale, LocalizationLocaleFormatted, LocalizationLocaleInherited, LocalizationScreenreaderStrings. They're picked up by the glob but not promoted as autodoc entries. (Correcting prior subagent claim: the .js examples ARE globbed at line 17 — that part was wrong.)

  1. RESOLVED Test description bugtest/auro-input-util.test.js:103-105 claims "structurally valid ISO string that is not a real date" but actually passes undefined. Rename or add the real test case.

RESOLUTION: ISO tests improved.

P3 — Watch / verify

  1. OUT OF SCOPE* aria-invalid="true" on every untouched inputauro-input.js:479. Confirmed validity defaults to undefined (node_modules/@aurodesignsystem/form-validation/src/validation.js:23), so the expression is always true until first validation. Not a regression — main has the identical line. Pre-existing; fix opportunistically.

  2. RESOLVED Missing leap-year / DST / timezone / toFormattedValue invalid-Date test coverage in test/auro-input-util.test.js. None of these blow up production but they're cheap to add.

RESOLUTION: Testing added

  1. RETEST DURING REVIEW Manual VoiceOver/keyboard verification for credit-card and date IMask under delegatesFocus after locale switch — recent commit d1857401c patched cursor-throw; worth one round of hands-on testing before release.

@chrisfalaska chrisfalaska mentioned this pull request Jun 25, 2026
15 tasks
jason-capsule42 and others added 22 commits June 25, 2026 16:35
…t intent

Adds two tests inside the existing Enter describe block
verifying that pressing Enter on auro-select — whether
closed (opens bib) or open (selects option) — does not
propagate to a parent <form>.

Also adds an explanatory comment to the Enter handler in
selectKeyboardStrategy.js noting that the unconditional
preventDefault/stopPropagation matches APG and native
<select> behavior.
combobox trigger

Match the WAI-ARIA APG select-only combobox
reference markup. The implicit default of
aria-haspopup on role="combobox" is "listbox",
but setting it explicitly avoids relying on UA
implicit-value mapping fidelity.
option changes

Subscribe to auroMenu-optionsChange so screen
readers announce accurate positions after
dynamic option mutations (slotchange,
async/lazy loads, value-triggered re-init).
Previously these attributes were set once at
init and went stale.
Locks in the WAI-ARIA APG select-only combobox
contract that Tab must perform its default
action so focus advances to the next tabbable
element. Prevents future regressions that would
trap focus on the trigger by adding
preventDefault to the Tab handler.
The `options` reactive state property
(@Private) was assigned in configureMenu and
the auroMenu-optionsChange listener but never
read. Its implicit Lit re-render trigger was
masking the real dependency:
renderNativeSelect reads this.menu.options
directly. Replace both assignments with
explicit this.requestUpdate() calls so the
intent — re-stamping the hidden native
<option> list after menu mutations — is
obvious at the call site.

Add a regression test asserting the hidden
native <select> re-renders when menu options
are added at runtime.
jason-capsule42 and others added 15 commits July 9, 2026 09:27
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Selection listener's setClearBtnFocus was clobbering
Shift+Tab focus. Added a per-selection suppress flag
the listener uses, then move focus to the previous
page selectable option.
Skip preventDefault when no previous tab stop is found,
use checkVisibility() with a fallback so position:fixed
elements aren't dropped, and clear the suppress flag
via microtask so a no-op makeSelection can't leak it.
…278416

Adds a docs example, customize.md section,
unit test, and storybook play-function test.
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
@jason-capsule42
jason-capsule42 merged commit d56c0dd into main Jul 10, 2026
46 of 48 checks passed
@github-actions

Copy link
Copy Markdown

🎉 This PR is included in version 6.0.0 🎉

The release is available on:

Your semantic-release bot 📦🚀

@github-actions github-actions Bot added the released Completed work has been released label Jul 10, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

released Completed work has been released

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants