rgl with compactType=vertical doesn't move items horizontally, so
layoutItem.x in onDragStop stays near its origin, making horizontal
slot detection wrong.
Switch to tracking event.clientX/Y from onDrag (fitDragRef), then in
onDragStop translate the final mouse position against the .grid-layout
element's bounding rect to get pixel-accurate targetCol/targetRow.
This makes horizontal, vertical, and long-distance swaps all reliable.
https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
Previous approach sorted react-grid-layout's post-drag positions to infer
order, which broke for non-adjacent and horizontal moves because rgl pushes
items down instead of swapping them.
New approach:
- onDrag records which item is being dragged (draggedItemRef)
- onDragStop uses the dragged item's final x/y to compute the target slot
in our own grid, then performs a clean swap in the ordered name array
- Layout is always fully regenerated from our order array, ignoring rgl's
position arithmetic entirely
https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
handleFitDragStop now sorts dragged items by position to determine new
order, then recalculates all x/y coords into a strict dense grid instead
of spreading react-grid-layout's arbitrary y values — prevents cards from
being pushed off-screen after a drag.
Also replaces LuMaximize with LuScanBarcode for the fit-to-screen button.
https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
In fitToScreen mode, drag is now enabled so users can reorder cameras
while in edit mode. A fitLayoutOverride state captures the new order
after each drag, normalizing w/h back to gridUnitsPerCam to prevent
size changes. The override resets automatically when the camera list or
grid parameters change.
https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
Adds a toggle button to the Live view toolbar that automatically arranges
all cameras to fit within the viewport without scrolling. Uses a brute-force
algorithm to find the optimal number of columns that maximizes camera size
while keeping all cameras visible. State persists via IndexedDB.
https://claude.ai/code/session_01Cu7YDRKZrYX3sBs6g9w2dy
TransformComponent's contentStyle had height: undefined on desktop,
so the rotate container's ResizeObserver measured height=0, causing
the inner div to get width=0 and the video to be invisible.
Adding || rotate to the height condition ensures the height chain is
intact when rotation is active, matching the isMobile path that already
set height: "100%".
https://claude.ai/code/session_01CDLHQPGpf8w44jpsG8g8nM
- HlsVideoPlayer: add rotate prop; when true, wraps <video> in a
ResizeObserver-tracked container that swaps width/height and applies
rotate(90deg) transform, mirroring the MsePlayer grid-rotation logic
- DynamicVideoPlayer: thread rotate prop through to HlsVideoPlayer
- RecordingView: invert getCameraAspect ratio (1/ratio) for cameras
with ui.rotate so the outer container gets portrait proportions;
pass rotate={camera.ui?.rotate} to DynamicVideoPlayer
https://claude.ai/code/session_01CDLHQPGpf8w44jpsG8g8nM
Root cause: LivePlayer's outer div has no explicit height (only w-full),
so when MsePlayer reads containerSize.height via ResizeObserver it gets 0.
With isRotatedGrid=true, MsePlayer sets the inner div width:
containerSize.height → width: 0 → video invisible.
Fix:
- Add size-full to LivePlayer className when camera.ui?.rotate, ensuring
height: 100% propagates through the chain so MsePlayer gets real dims
- Re-add cameraAspectRatio inversion (1/ratio) for portrait container
layout; now that the height chain is intact this works correctly:
portrait container → LivePlayer size-full → MsePlayer real dims → swap+rotate
https://claude.ai/code/session_01CDLHQPGpf8w44jpsG8g8nM
The previous commit caused a double dimension swap for rotated cameras:
- LiveCameraView was inverting the aspect ratio (1/ratio) → portrait container
- MsePlayer was then swapping width/height again internally when
isRotatedGrid=true → video got zero/invalid dimensions, nothing visible
The MsePlayer already handles the full rotation internally via CSS variables
(transform + width/height swap). The container in LiveCameraView should
keep the original (landscape) aspect ratio, matching the grid cell behavior
in DraggableGridLayout where this works correctly.
https://claude.ai/code/session_01CDLHQPGpf8w44jpsG8g8nM
- Invert cameraAspectRatio when camera.ui?.rotate is true so the
container dimensions match the rotated video (width↔height swap)
- Pass CSS variables --frigate-mse-grid-rotated and
--frigate-mse-grid-rotation to LivePlayer, enabling the existing
MsePlayer rotation/swap logic for single-camera view
- Fullscreen orientation lock works automatically: an inverted ratio
< 1 causes portrait lock for a normally-landscape camera
https://claude.ai/code/session_01CDLHQPGpf8w44jpsG8g8nM
When clicking the History button on a specific camera's Live view,
append `?cameras=<camera_name>` to the review URL so the camera
filter is pre-set to that camera instead of showing "All Cameras".
The Events (Review) page already supports reading the `cameras` URL
parameter via useSearchEffect - no changes needed there.
Fixes: #12776, #16987https://claude.ai/code/session_01PnMA1HcuKsEXcvVLaXRgF1
When scrubbing in RecordingView, tall cameras passed size-full (width:100% + height:100%)
to PreviewPlayer, causing the browser to ignore aspect-ratio. Replacing size-full with
w-full lets height be computed from width + aspect-ratio, preserving correct proportions.
https://claude.ai/code/session_019sUH2h6HoVswdtD7EbhAJa
Replace size-full (100%×100%) on img/video elements with max-h-full max-w-full
so portrait (9:16) and 4:3 cameras maintain their natural proportions during
scrubbing in RecordingView. Add items-center to flex containers so content
stays vertically centered within the available space.
https://claude.ai/code/session_01H1uowWMpsNm1U8HdcSP8AA
* fix genai settings ui
- add roles widget to select roles for genai providers
- add dropdown in semantic search to allow selection of embeddings genai provider
* tweak grouping to prioritize fieldOrder before groups
previously, groups were always rendered first. now fieldOrder is respected, and any fields in a group will cause the group and all the fields in that group to be rendered in order. this allows moving the enabled switches to the top of the section
* mobile tweaks
stack buttons, add more space on profiles pane, and move the overridden badge beneath the description
* language consistency
* prevent camera config sections from being regenerated for profiles
* conditionally import axengine module
to match other detectors
* i18n
* update vscode launch.json for new integrated browser
* formatting
- MsePlayer: change default object-fit fallback from fill to contain
(grid layout keeps fill via --frigate-mse-object-fit:fill CSS variable)
- PreviewPlayer: add object-contain class to video element
- HlsVideoPlayer: add object-contain class to video element
Recordings view and timeline preview now preserve aspect ratio,
while live grid continues to stretch corridor cameras as before.
Add optional `roles` field to camera groups config to control which user
roles can see each group. Groups without roles are visible only to admins.
Admin users always see all groups. Backend filters groups in GET /config
based on remote-role header. Frontend adds roles multiselect in group
editor (admin only).
https://claude.ai/code/session_011sp9kHQfM39JvVxKHFh1Xq
* Use different association method
* Clarify
* Remove extra details from ollama schema
* Fix Gemini Chat
* Fix incorrect instructions
* Improve name handling
* Change order of information for llama.cpp
* Simplify prompt
* Fix formatting
* Add go2rtc settings section
- create separate settings section for all go2rtc streams
- extract credentials mask code into util
- create ffmpeg module utility
- i18n
* add camera config updater topic for live section
to support adding go2rtc streams after configuring a new one via the UI
* clean up
* tweak delete button color for consistency
* tweaks