Review queries were issued without a severity filter, so the thumbnail
bar, gallery and timeline showed every Frigate review item -- alerts and
detections alike -- regardless of what a camera is configured to alert
on. Severity could only be narrowed interactively, in the media filter
drawer, and that choice did not persist.
`media.severities` resolves into the default query the same way
`media.reviewed` does, and is overridden by an explicit severity filter
so the media filter drawer keeps working. Unset means all severities, so
existing behaviour is unchanged.
cameras:
- camera_entity: camera.office
media:
severities:
- high
## 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>
Selecting a camera in `live.display.mode: grid` lands in two visible
passes: the cell gets its 2-column width immediately but keeps its 1-row
height for ~400 ms, and the rest of the grid only settles ~700 ms after
the click (measured per-frame on `main`; v7.27.4 has the same two
passes, v8's early position resolution just leaves the late height
correction standing out as a lone vertical jump).
**Cause:** a grid cell's height follows its content in the same style
recalculation as its width (intrinsic media ratio,
`dimensions.aspect_ratio`, or the unsized 16:9 reservation), but the
slotted carousel carries an inline `max-height` from
`MediaHeightController` (debounced 0.3 s + 0.1 s transition; it sizes
the card outside grids). In a grid that cap can only ever delay growth:
it pins the freshly widened cell at its old height until the debounce
fires, and the correction then also rides the 300 ms `_throttledLayout`.
`selectCell()`'s existing `forceReflow()` + `layout()` was already
positioned to do this in one pass; the cap starves it of the final
height.
**Fix:** neutralize the cap on grid cells (`max-height: none
!important`; important is needed to beat the inline style). Covers the
live and viewer grids; non-grid behaviour is untouched. This also stops
the cap clipping cells by their border width (it was measured on the
slide's content box but applied to the cell's border box).
**Trade-off worth flagging:** in the *viewer* grid a cell's carousel
holds all of one camera's media. If those slides do not share one ratio,
the cell now sizes to its tallest slide (letterboxing shorter ones, no
re-layout per swipe) instead of tracking the selected one after a
debounce. If you would rather leave the viewer untouched, the rule can
be scoped to `::slotted(advanced-camera-card-live-carousel)` instead
(live grid cells always hold exactly one slide); say so and I will
rework the PR that way.
**Verification:**
- New browser test `tests/components/live/grid.browser.test.ts`: fails
on any frame where the newly selected cell is selected-wide but still
unselected-high. On `main` it fails with 22 such frames; with this
change there are none: the click settles in a single frame (~28 ms),
every cell at its final size and position.
- `yarn run test`, `yarn run test:browser` (chromium, firefox and webkit
for the new test), `yarn run lint`, `yarn run typecheck` pass.
- No resize oscillation in a cramped viewport with an
appearing/disappearing ancestor scrollbar (the #2306 scenario), on
window resizes, or under a narrow-screen `grid_columns: 2` override;
verified against a live HA 2026.8.3 dashboard (6 go2rtc cameras).
---------
Co-authored-by: dermotduffy <dermot.duffy@gmail.com>
Since f240646 (#2639, first released in v8.0.0) the card claims focus on
any `pointerdown` inside it, so that `key` triggers receive their
keyboard events (`keyboard-state-manager.ts`). The claim is a script
call, `element.focus({ preventScroll: true })`, and script-initiated
focus comes with the browser's focus indicator: after a pointer press
while focus was outside the card, the card matches `:focus-visible` and
Chromium draws its default ring around the entire card (measured:
`outline: auto 1px rgb(238, 238, 238)`, a bright line on a dark
dashboard). The ring then persists until focus leaves the card, which
users experience as a white border that appears intermittently when they
click or tap the card.
Ordinary dashboard cards are unaffected because they rely on the
browser's native pointer focus, which shows no indicator. Isolated in
the same browser, a plain `tabindex` element gains focus from a click
without matching `:focus-visible`, while `focus()` from script does
match it. v7 did not claim focus at all, so it never showed this.
**Change:** pass the intent along with the claim: `element.focus({
preventScroll: true, focusVisible: false })`. This code path only runs
for pointer interaction, where no indicator is wanted. Keyboard focus
does not pass through it: tabbing to the card keeps its ring, and the
`key` trigger support from #2639 is unchanged. Browsers without
`FocusOptions.focusVisible` ignore the option and simply keep today's
behaviour. (`focusVisible` is not yet in the bundled TypeScript DOM
types, hence the small global augmentation.)
**Verification:**
- Unit test asserts the focus claim carries `focusVisible: false`.
- Measured in Chromium 152 on a live dashboard: before, a pointer press
on the card leaves it `:focus-visible` with the UA default ring; after,
the same press focuses the card without one, and reaching the card with
Tab still shows the ring. In the same browser, `focus({ focusVisible:
false })` verifiably suppresses `:focus-visible` where a plain `focus()`
sets it.
- `yarn run test`, `yarn run test:browser` (chromium and firefox), `yarn
run lint` and `yarn run typecheck` pass. The webkit browser run fails
one focus test in this local environment, identically on unmodified
`main`, so it is unrelated to this change.
---------
Co-authored-by: dermotduffy <dermot.duffy@gmail.com>
Menu icons sit visibly high inside their round buttons in the iOS
companion app, and one pixel high everywhere else. The card's icon
wrapper (`advanced-camera-card-icon`) is slotted into `ha-icon-button`,
whose label renders slotted content inside a plain `<span>`. The wrapper
is an `inline-block`, so it rides the text baseline and leaves the
font's descender space underneath it: the span grows taller than the
icon (measured: 24.5px around a 22.5px icon under Roboto), the button's
flex layout centers the *span*, and the icon lands high by half the
descent.
That descent is a property of the active font, which is why the offset
differs per platform: about 1px under Roboto, and about 2px when the
font stack resolves to a system font, as on iOS, where it is clearly
visible inside the round menu button background. Slotted Home Assistant
icons do not show this because they are laid out block-level via
`--ha-icon-display`, which the card already sets; the card's own wrapper
element reintroduces the inline-block one level up.
**Change:** lay the wrapper out block-level wherever it is slotted into
`ha-icon-button` (`button.scss`, which also covers the submenu and
next/previous controls). Block layout has no baseline, so the label span
shrinks to exactly the icon and the icon centers.
**Verification:**
- The structure (flex button, label span, slotted icon) rebuilt
standalone in Playwright Chromium and WebKit: with an inline-block icon
the offset is 1px under Roboto and 1.75 to 2px under system fonts, in
both engines; with a block icon it is 0.0px in every engine, font, and
button size tried.
- Measured on a live HA 2026.8.3 dashboard (45px menu buttons): before,
every menu icon center sat 1px above its button center; after, 0.0px for
both a standard menu button and a conditional
`advanced-camera-card-menu-icon` element.
- `yarn run test`, `yarn run test:browser` (chromium), `yarn run lint`,
`yarn run typecheck` pass.
---------
Co-authored-by: dermotduffy <dermot.duffy@gmail.com>
## Summary
- add optional microphone constraints for echo cancellation, noise
suppression, automatic gain control, and channel count
- request configured values as non-mandatory `ideal` constraints
- expose privacy-safe microphone capabilities, requested constraints,
and applied settings in card diagnostics
- document the new configuration and add schema, microphone manager, and
diagnostics tests
## Motivation
The card currently calls `getUserMedia()` with `audio: true`. This
leaves echo cancellation, noise suppression, automatic gain control, and
channel count implicit.
Browser and device behavior differs. Explicit processing defaults can
regress microphone gain or amplify noise on some devices. This change
therefore keeps all processing constraints optional and configurable.
## Configuration
```yaml
live:
microphone:
constraints:
echo_cancellation: true
noise_suppression: true
auto_gain_control: false
channel_count: 1
```
Configured values use `ideal` constraints. A browser can ignore
unsupported values. Card diagnostics show the browser capabilities, the
requested constraints, and the reported applied settings.
## Backward compatibility
- existing configurations still use `audio: true`
- no audio-processing defaults are added
- explicit `false` values are preserved
- diagnostic output excludes device and group identifiers
## Validation
- focused microphone, schema, and diagnostics tests: 46 passed
- full test suite: 7,177 passed
- lint passed
- format check passed
- typecheck passed
- unused-code check passed
- production build passed
The optional constraints were also tested successfully with an iOS Home
Assistant Companion client and a go2rtc-based full-duplex intercom. This
is a client microphone-processing change only. It does not add backend
audio denoise.
---------
Co-authored-by: dermotduffy <dermot.duffy@gmail.com>
The card claimed a camera's ONVIF audio backchannel in two places that
had
nothing to do with a call: the capability probe at camera init
(`µphone`),
and a pre-armed `sendonly` audio transceiver on every live WebRTC offer.
Merely
looking at a dashboard occupied the camera's speaker line. Outbound
audio now
travels on its own audio-only WebRTC connection, opened when a call is
answered
and closed when it ends.
- The backchannel is claimed only for the duration of a call. Idle
viewing
claims nothing.
- Two-way audio now works in `mse`, `mp4` and `mjpeg` modes (note: the
outbound
audio still traverses WebRTC).
- No renegotiation and no video blink at call start or end.
- A call that cannot carry audio now reports it and ends, instead of
showing a
live microphone that goes nowhere.
- `live.microphone.always_connected` is now purely about the browser
microphone
permission prompt.
- Call setup measured at 66ms (LAN) and ~260ms (cellular) for ICE and
DTLS, plus
~300ms for `go2rtc` to open an RTSP backchannel.
Verified against a live Frigate + `go2rtc` instance, and by unit tests
at 100%
coverage.
- Closes#2691
- Closes#2039
- Closes#2178
Ref #2299 -- the probe no longer opens a backchannel, but it still runs
per
camera on every load and reconnect, and still dials the camera on the
direct-`go2rtc` path. Caching remains to be done.
Ref AlexxIT/go2rtc#1860 -- once a call has opened a backchannel,
`go2rtc` keeps
that media set up on the camera's RTSP session for the life of the
producer.
Diagnoses #2678
- Closes: #2679
BREAKING CHANGE: `selected` and `unselected` are removed from
`live.microphone.auto_unmute` / `auto_mute`. A camera change ends any
call and the microphone only carries audio during a call, so neither
were useful.
The microphone is connected when a call needs it and released the moment
that
call ends, so the browser stops reporting it as in use at hangup rather
than
`disconnect_seconds` later.
Existing configurations are migrated automatically by the visual editor.
- Closes: #2681
BREAKING CHANGE: `live.microphone.disconnect_seconds` is removed. The
microphone is released when a call ends, so there is no idle countdown
to
configure. Use `live.microphone.always_connected` to hold it open
instead.
BREAKING CHANGE: The `microphone_connect` and `microphone_disconnect`
actions
are removed. The card owns the microphone lifecycle; `microphone_mute`
and
`microphone_unmute` remain.
BREAKING CHANGE: `call` is removed from `live.microphone.auto_mute`,
whose
default is now `[]`. The microphone is muted when a call ends regardless
of
this option.
BREAKING CHANGE: `microphone_unmute` has no effect outside a call.
Nothing
carries the audio at any other time, so the request is ignored rather
than
opening the microphone.