fix: Restore iPadOS 15 compatibility by removing unsupported regex (#2756)

## Summary

Removes an unsupported negative regex lookbehind from input-helper
entity ID validation.

Advanced Camera Card v8 fails to load on Safari/iPadOS 15 because the
browser cannot parse `(?<!_)`. This prevents the `advanced-camera-card`
custom element from registering.

The replacement preserves the existing validation behavior without
lookbehind:

- Accepts valid `input_*` helper entity IDs.
- Rejects object IDs beginning or ending with `_`.
- Rejects object IDs containing `__`.
- Restores compatibility with Safari versions before 16.4.

This closes #2753 

## Testing

- Added regression coverage for valid and invalid input-helper entity
IDs.
- TypeScript typecheck passes.
- ESLint passes for the changed files.
- Build succeeds.
- Confirmed the generated distribution contains no negative lookbehind
syntax.
- Direct validation of the replacement regex passes.

## Reproduction

- Device: iPad mini 4
- OS: iPadOS 15.8.8
- Last working release: v7.27.4
- Affected releases: v8.0.0 and v8.0.1
- Browser error:

```text
SyntaxError: Invalid regular expression: invalid group specifier name
Safari and Safari on iOS did not support regex lookbehind until version 16.4.

---------

Co-authored-by: dermotduffy <dermot.duffy@gmail.com>
This commit is contained in:
Shane Sewell
2026-09-01 16:53:34 -07:00
committed by GitHub
co-authored by dermotduffy
parent 95b85bf608
commit 54e261fd54
2 changed files with 33 additions and 2 deletions
@@ -734,6 +734,34 @@ describe('state condition', () => {
).toBeFalsy();
});
it.each([
['input_text.expected', true],
['input_text.a_1', true],
['input_text.', false],
['input_text._expected', false],
['input_text.__expected', false],
['input_text.expected_', false],
['input_text.expected__name', false],
])('should resolve only a valid input helper ID: %s', (entityID, shouldResolve) => {
const evaluator = createConditionEvaluator(
{
condition: 'state' as const,
entity_id: 'binary_sensor.foo',
state: entityID,
},
createEvaluatorContext(),
);
expect(
evaluator.evaluate({
hass: createHASS({
'binary_sensor.foo': createStateEntity({ state: 'armed' }),
[entityID]: createStateEntity({ state: 'armed' }),
}),
}).result,
).toBe(shouldResolve);
});
it('should not resolve a non-input entity name', () => {
// Only `input_*` helpers are resolved; other entity names compare literally.
const evaluator = createConditionEvaluator(