diff --git a/apps/docs/docs/widgets/app/index.mdx b/apps/docs/docs/widgets/app/index.mdx
index 9321c968c..3c7231fdc 100644
--- a/apps/docs/docs/widgets/app/index.mdx
+++ b/apps/docs/docs/widgets/app/index.mdx
@@ -11,7 +11,7 @@ import { appWidget } from ".";
-The app widget allows you to add a link to an application or a website that you want to access quickly from your Homarr board.
+The app widget allows you to add a link to an application or a website that you want to access quickly from your Homarr board. Compact tiles retain the familiar title, optional description, and centered app icon proportions.
It also supports a status check to indicate whether Homarr can reach the application or website.
### Screenshots
diff --git a/apps/docs/docs/widgets/audio-stats/index.mdx b/apps/docs/docs/widgets/audio-stats/index.mdx
index f79a588c3..2281e9116 100644
--- a/apps/docs/docs/widgets/audio-stats/index.mdx
+++ b/apps/docs/docs/widgets/audio-stats/index.mdx
@@ -23,6 +23,8 @@ This widget adapts its display based on the linked integration:
Only options relevant to your linked integration are shown in the configuration panel.
+Compact mode keeps every configured statistic visible in the familiar filled tile layout, including a vertical stack for narrow Navidrome cards. Advanced mode retains the complete expanded statistics and stream details.
+
### Supported Integrations
-This widget displays your Beszel alert configurations separated by status (triggered and ok). It also shows alert history with timestamps. Alerts are updated in real time via WebSocket subscriptions.
+This widget displays your Beszel alert configurations separated by status (triggered and ok). It also shows alert history with timestamps. Compact mode keeps configured OK alerts, descriptions, and history visible alongside triggered alerts. Alerts are updated in real time via WebSocket subscriptions.
### Screenshots
diff --git a/apps/docs/docs/widgets/coolify/index.mdx b/apps/docs/docs/widgets/coolify/index.mdx
index b843f4971..71b1081a0 100644
--- a/apps/docs/docs/widgets/coolify/index.mdx
+++ b/apps/docs/docs/widgets/coolify/index.mdx
@@ -15,7 +15,7 @@ import { coolifyIntegration } from "@site/docs/integrations/coolify";
-The Coolify widget displays a compact overview of your Coolify instance. It can show servers, applications, and services with status indicators, project and environment context, direct links to Coolify, and the installed Coolify version.
+The Coolify widget displays a compact overview of your Coolify instance. It can show servers, applications, and services with status indicators, project and environment context, direct links to Coolify, and the installed Coolify version. Compact resource badges and footer details remain visible without height-based hiding.
### Supported Integrations
diff --git a/apps/docs/docs/widgets/dns-hole-controls/index.mdx b/apps/docs/docs/widgets/dns-hole-controls/index.mdx
index 387431190..e9b9667cb 100644
--- a/apps/docs/docs/widgets/dns-hole-controls/index.mdx
+++ b/apps/docs/docs/widgets/dns-hole-controls/index.mdx
@@ -22,7 +22,7 @@ import { technitiumDnsIntegration } from "@site/docs/integrations/technitium-dns
When several DNS integrations are selected, Homarr keeps healthy controls available if one source fails and shows a warning identifying the unavailable integration.
-This widget lets permitted users enable, disable, or temporarily pause one or more DNS services. It shares the summary query and cache with DNS Hole Summary. Every selected source stays available in the normal scrollable view, while cards and controls adapt to the widget's width and height. Bulk actions keep successful results visible and report the number of failures.
+This widget lets permitted users enable, disable, or temporarily pause one or more DNS services. It shares the summary query and cache with DNS Hole Summary. Every selected source stays available in the normal scrollable view, with its familiar textual status control retained at compact heights. Bulk actions keep successful results visible and report the number of failures.
### Screenshots
diff --git a/apps/docs/docs/widgets/docker-containers/index.mdx b/apps/docs/docs/widgets/docker-containers/index.mdx
index e405f270d..65b3a9051 100644
--- a/apps/docs/docs/widgets/docker-containers/index.mdx
+++ b/apps/docs/docs/widgets/docker-containers/index.mdx
@@ -12,7 +12,7 @@ import { dockerContainersWidget } from ".";
-This widget displays the resource usage of your Docker containers and allows you to manage them.
+This widget displays the resource usage of your Docker containers and allows you to manage them. Compact mode keeps every configured table column and shows the complete CPU and memory totals whenever the footer is available.
:::info
This widget reads the Docker environments configured through environment variables. You do not need to create a Docker integration under **Management → Integrations**. The widget can only be used with administrator privileges.
diff --git a/apps/docs/docs/widgets/downloads/index.mdx b/apps/docs/docs/widgets/downloads/index.mdx
index fefd416c1..a01051f0c 100644
--- a/apps/docs/docs/widgets/downloads/index.mdx
+++ b/apps/docs/docs/widgets/downloads/index.mdx
@@ -58,7 +58,7 @@ import filterPanelImg from "./img/filter-panel.png";
### Features
-- **Responsive compact table** — in compact mode, selected columns adapt to both widget width and height; narrow or short tiles hide speed/state columns, and the shortest tiles also hide the ETA column
+- **Responsive compact table** — in compact mode, selected columns use the established width breakpoints; widget height does not remove configured columns or the footer
- **Sortable columns** — click any sortable column header to sort the current view; the configured default controls the initial column and direction
- **Column customization** — choose which columns to display, then hover a header to reveal its grab handle for reordering or drag its edge to resize it; layouts save automatically per widget
- **Download-row context menu** — copy a download name or ID from any row; pause, resume, and delete actions appear when you have Interact access to that integration
@@ -72,11 +72,10 @@ import filterPanelImg from "./img/filter-panel.png";
:::note
-Compact layouts use both dimensions: below 500px wide, speed columns are hidden; below 300px wide, the ETA and state
-columns are hidden too. A tile shorter than 180px hides speed and state columns, and a tile shorter than 120px also
-hides ETA. The footer is hidden below 96px in compact mode. Advanced view bypasses these compact size reductions,
-keeps the selected column set, and always includes the stats bar and footer controls, with the filter panel available
-from the footer.
+Compact layouts use their established width breakpoints: below 500px wide, speed columns are hidden; below 300px wide,
+the ETA and state columns are hidden too. Height does not remove configured columns or the footer. Advanced view bypasses
+the compact width reductions, keeps the selected column set, and always includes the stats bar and footer controls, with
+the filter panel available from the footer.
The filter panel changes the visible rows. Its client/status filters also change the stats bar's progress, size, and
state counts, while transfer-speed totals summarize the selected clients and the global torrent ratio summarizes the
diff --git a/apps/docs/docs/widgets/firewall/index.mdx b/apps/docs/docs/widgets/firewall/index.mdx
index aec2d89d9..856331f40 100644
--- a/apps/docs/docs/widgets/firewall/index.mdx
+++ b/apps/docs/docs/widgets/firewall/index.mdx
@@ -16,6 +16,8 @@ import { opnsenseIntegration } from "../../integrations/opnsense";
+Compact mode keeps the configured firewall summary and interface section visible without dropping interfaces based on widget height.
+
### Screenshots

diff --git a/apps/docs/docs/widgets/immich-server-stats/index.mdx b/apps/docs/docs/widgets/immich-server-stats/index.mdx
index e2c967365..e80a2045d 100644
--- a/apps/docs/docs/widgets/immich-server-stats/index.mdx
+++ b/apps/docs/docs/widgets/immich-server-stats/index.mdx
@@ -15,6 +15,8 @@ import { immichServerStatsWidget } from ".";
+Compact mode presents every enabled statistic as a vertical icon, label, and value card.
+
### Screenshots
import screen from "./img/screen.png";
diff --git a/apps/docs/docs/widgets/indexer-manager/index.mdx b/apps/docs/docs/widgets/indexer-manager/index.mdx
index 2deeecd66..029eb2bf0 100644
--- a/apps/docs/docs/widgets/indexer-manager/index.mdx
+++ b/apps/docs/docs/widgets/indexer-manager/index.mdx
@@ -14,6 +14,8 @@ import { indexerManagerWidget } from ".";
+Compact mode keeps the titled indexer list, per-indexer status indicators, and full-width **Test all** action together.
+
### Screenshots
import bigItem from "./img/big.png";
diff --git a/apps/docs/docs/widgets/media-releases/index.mdx b/apps/docs/docs/widgets/media-releases/index.mdx
index 79dfb4f17..fea6f8a71 100644
--- a/apps/docs/docs/widgets/media-releases/index.mdx
+++ b/apps/docs/docs/widgets/media-releases/index.mdx
@@ -17,7 +17,7 @@ import { plexIntegration } from "@site/docs/integrations/plex";
-Provides a list of newly added media or upcoming releases from your media server.
+Provides a list of newly added media or upcoming releases from your media server. Compact rows retain their poster, full release date and time, and configured metadata.
Releases retain the name and icon of the integration that supplied them. If one integration is unavailable, releases from healthy integrations remain visible and a warning identifies the failed source without exposing connection details. The widget reports an error only when every selected integration fails.
diff --git a/apps/docs/docs/widgets/media-request-list/index.mdx b/apps/docs/docs/widgets/media-request-list/index.mdx
index 840315831..2ed31ea88 100644
--- a/apps/docs/docs/widgets/media-request-list/index.mdx
+++ b/apps/docs/docs/widgets/media-request-list/index.mdx
@@ -19,7 +19,7 @@ import { overseerrIntegration } from "@site/docs/integrations/overseerr";
-Provides a list of all open and recently closed requests. It also provides the functionality to approve or reject requests.
+Provides a list of all open and recently closed requests. It also provides the functionality to approve or reject requests. Compact request cards retain their full artwork, request metadata, title, status, and permitted actions.
You can limit the list to specific request statuses (for example, only show `pending`) and restrict it to requests created within the last N days.
diff --git a/apps/docs/docs/widgets/media-request-stats/index.mdx b/apps/docs/docs/widgets/media-request-stats/index.mdx
index 3adfcbffb..7376bb0e4 100644
--- a/apps/docs/docs/widgets/media-request-stats/index.mdx
+++ b/apps/docs/docs/widgets/media-request-stats/index.mdx
@@ -19,7 +19,7 @@ import { overseerrIntegration } from "@site/docs/integrations/overseerr";
-Provides an overview of media requests, including totals, availability, approvals, and rejections. Resize the widget to reveal denser stat tiles; the user ranking remains scrollable instead of being truncated.
+Provides an overview of media requests, including totals, availability, approvals, and rejections. Compact mode keeps every configured stat and section title visible; the user ranking remains scrollable instead of being truncated.
### Supported Integrations
diff --git a/apps/docs/docs/widgets/media-server/index.mdx b/apps/docs/docs/widgets/media-server/index.mdx
index a2ab91c9c..c881bc3c5 100644
--- a/apps/docs/docs/widgets/media-server/index.mdx
+++ b/apps/docs/docs/widgets/media-server/index.mdx
@@ -22,7 +22,7 @@ import { plexIntegration } from "@site/docs/integrations/plex";
The media server widget shows active sessions across your media servers.
-It shows playback progress, pause state, time remaining, the streaming resolution (e.g. `1080p`), and whether each session is direct play, direct stream, or transcoding audio or video. Location and bitrate can be toggled independently.
+It shows playback progress, pause state, time remaining, the streaming resolution (e.g. `1080p`), and whether each session is direct play, direct stream, or transcoding audio or video. Location and bitrate can be toggled independently. Compact mode keeps the user, currently playing, and status columns together with the stream summary and integration names.
Select a session to inspect its media metadata in an anchored popover. Advanced mode adds search, sorting, location, bitrate, and a summary of playing and transcoding sessions while keeping separate integration instances identifiable. Compact visibility switches do not hide location or bitrate from the complete advanced session view.
If one selected server is unavailable, a warning indicator identifies the degraded result while sessions from healthy servers remain usable.
diff --git a/apps/docs/docs/widgets/minecraft-server-status/index.mdx b/apps/docs/docs/widgets/minecraft-server-status/index.mdx
index 4df364116..c35135292 100644
--- a/apps/docs/docs/widgets/minecraft-server-status/index.mdx
+++ b/apps/docs/docs/widgets/minecraft-server-status/index.mdx
@@ -17,7 +17,7 @@ import { minecraftServerStatusWidget } from ".";
-Shows the status and amount of players on a Minecraft server. It supports both Java and Bedrock editions, however only public servers are supported which can be reached through the used api of [api.mcsrvstat.us](https://api.mcsrvstat.us).
+Shows the status and amount of players on a Minecraft server. Compact mode keeps the server icon and player count visible instead of hiding them based on tile size. It supports both Java and Bedrock editions, however only public servers are supported which can be reached through the used api of [api.mcsrvstat.us](https://api.mcsrvstat.us).
### Screenshots
diff --git a/apps/docs/docs/widgets/network-controller-status/index.mdx b/apps/docs/docs/widgets/network-controller-status/index.mdx
index 64ddb6ad0..a9cbfc87c 100644
--- a/apps/docs/docs/widgets/network-controller-status/index.mdx
+++ b/apps/docs/docs/widgets/network-controller-status/index.mdx
@@ -20,7 +20,7 @@ import { networkControllerStatusWidget } from ".";
Shows connected users and connected guest devices on wired or wireless networks.
-Compact view aggregates the configured controllers into the selected Wi-Fi or wired total. Advanced view shows both totals separately for every controller, preserving each integration's name, type, and exact update time while keeping partial-source and stale-data warnings visible.
+Compact view aggregates the configured controllers into one familiar card for the selected Wi-Fi or wired total. A failed refresh is shown as a small warning instead of replacing the totals card. Advanced view shows both totals separately for every controller, preserving each integration's name, type, and exact update time while keeping partial-source and stale-data warnings visible.
### Screenshots
diff --git a/apps/docs/docs/widgets/network-controller-summary/index.mdx b/apps/docs/docs/widgets/network-controller-summary/index.mdx
index 3850cc25c..f90189e5e 100644
--- a/apps/docs/docs/widgets/network-controller-summary/index.mdx
+++ b/apps/docs/docs/widgets/network-controller-summary/index.mdx
@@ -18,7 +18,7 @@ import { networkControllerSummaryWidget } from ".";
-Shows whether your WAN, internet, Wi-Fi, LAN, and VPN are available. Wider or taller layouts progressively reveal latency, ping, and connected-client counts without a separate view.
+Shows whether your WAN, internet, Wi-Fi, and VPN are available. The compact board view keeps the familiar status list, including internet latency and the connected VPN-user count.
### Screenshots
@@ -36,7 +36,7 @@ import summaryCard from "./img/summary.png";
]}
/>
-In advanced view, each controller gets its own information matrix showing source, last update, WAN and web status, latency, ping, web uptime, separate Wi-Fi and LAN user and guest counts, and connected VPN users. The compact board view keeps its progressive aggregate layout.
+In advanced view, each controller gets its own information matrix showing source, last update, WAN and web status, latency, ping, web uptime, separate Wi-Fi and LAN user and guest counts, and connected VPN users.
diff --git a/apps/docs/docs/widgets/notifications/index.mdx b/apps/docs/docs/widgets/notifications/index.mdx
index 5616f297b..f68e1baee 100644
--- a/apps/docs/docs/widgets/notifications/index.mdx
+++ b/apps/docs/docs/widgets/notifications/index.mdx
@@ -20,7 +20,7 @@ import { gotifyIntegration } from "@site/docs/integrations/gotify";
-The notifications widget allows you to display notifications from various integrations in a unified view.
+The notifications widget allows you to display notifications from various integrations in a unified view. Compact mode presents them as a vertical list of padded notification cards.
### Screenshots
diff --git a/apps/docs/docs/widgets/paperless-ngx/index.mdx b/apps/docs/docs/widgets/paperless-ngx/index.mdx
index a2ef00ebe..e11680f82 100644
--- a/apps/docs/docs/widgets/paperless-ngx/index.mdx
+++ b/apps/docs/docs/widgets/paperless-ngx/index.mdx
@@ -15,7 +15,7 @@ import { paperlessNgxIntegration } from "@site/docs/integrations/paperless-ngx";
-This widget displays statistics from your Paperless-ngx instance, including an inbox ratio ring showing how many documents are in your inbox versus total, along with counts for correspondents, tags, and document types.
+This widget displays statistics from your Paperless-ngx instance, including an inbox ratio ring showing how many documents are in your inbox versus total, along with counts for correspondents, tags, and document types. Compact mode keeps every configured statistic in the familiar filled tile layout and uses a vertical stack on narrow cards.
### Supported Integrations
diff --git a/apps/docs/docs/widgets/releases/index.mdx b/apps/docs/docs/widgets/releases/index.mdx
index 88f5bc9ee..e94de9027 100644
--- a/apps/docs/docs/widgets/releases/index.mdx
+++ b/apps/docs/docs/widgets/releases/index.mdx
@@ -13,7 +13,7 @@ import { releasesWidget } from ".";
-The releases widget enables you to track a list of repositories and see their respective latest release.
+The releases widget enables you to track a list of repositories and see their respective latest release. Compact mode keeps repositories in the familiar full-width stack.
It also supports importing releases from your local docker containers.
### Screenshots
@@ -35,7 +35,7 @@ GitHub, Docker Hub, GitLab, NPM, Codeberg, GitHub Container Registry, Quay, and
For GitHub Container Registry, use `owner/package` identifiers such as `homarr-labs/homarr`.
Full image references such as `ghcr.io/homarr-labs/homarr:latest` are also accepted.
-Advanced view shows every configured repository even when compact view is limited to highlighted or top releases. Repository names and details remain visible, and selecting a repository still expands its description and links.
+Advanced view shows every configured repository in a roomier responsive layout. Repository names and details remain visible, and selecting a repository still expands its description and links.
diff --git a/apps/docs/docs/widgets/rss-feed/index.mdx b/apps/docs/docs/widgets/rss-feed/index.mdx
index cec1fcedf..eef12286d 100644
--- a/apps/docs/docs/widgets/rss-feed/index.mdx
+++ b/apps/docs/docs/widgets/rss-feed/index.mdx
@@ -13,7 +13,7 @@ import { rssFeedWidget } from ".";
-Using the RSS standard, you can integrate most News websites, applications and more on your dashboard.
+Using the RSS standard, you can integrate most News websites, applications and more on your dashboard. Compact mode uses a vertical card list and keeps enclosure images as the familiar poster and blurred backdrop.
### Screenshots
diff --git a/apps/docs/docs/widgets/smart-home-entity-state/index.mdx b/apps/docs/docs/widgets/smart-home-entity-state/index.mdx
index 2d12f3d71..acf6e4e8d 100644
--- a/apps/docs/docs/widgets/smart-home-entity-state/index.mdx
+++ b/apps/docs/docs/widgets/smart-home-entity-state/index.mdx
@@ -18,7 +18,7 @@ import { homeAssistantIntegration } from "@site/docs/integrations/home-assistant
-The entity state enables you to display the current state of an entity.
+The entity state enables you to display the current state of an entity. Compact mode presents the friendly name first and its state beneath it.
This works with most entities and enables you display almost any data.
It is recommended that you transform data on the integration as much as possible since Homarr's capabilities in transformation are very limited.
diff --git a/apps/docs/docs/widgets/speedtest-tracker/index.mdx b/apps/docs/docs/widgets/speedtest-tracker/index.mdx
index 2308d2d95..2912946e5 100644
--- a/apps/docs/docs/widgets/speedtest-tracker/index.mdx
+++ b/apps/docs/docs/widgets/speedtest-tracker/index.mdx
@@ -18,7 +18,7 @@ import { speedtestTrackerIntegration } from "@site/docs/integrations/speedtest-t
This widget displays internet speed test results from your [Speedtest Tracker](https://docs.speedtest-tracker.dev/) instance,
including the latest result, historical averages, and a live chart of the last 12 hours.
-Advanced view renders a source-labeled panel for every healthy integration. Each panel exposes that source's latest test, statistics, speed and ping charts, plus 12-hour, 24-hour, and truthful **Last 30** result ranges regardless of compact visibility switches. Compact mode keeps the responsive aggregate overview and names its healthy sources when multiple integrations are selected.
+Advanced view renders a source-labeled panel for every healthy integration. Each panel exposes that source's latest test, statistics, speed and ping charts, plus 12-hour, 24-hour, and truthful **Last 30** result ranges regardless of compact visibility switches. Compact mode keeps every configured overview section in its familiar card layout and names its healthy sources when multiple integrations are selected.
When multiple Speedtest Tracker integrations are selected, results from healthy sources remain visible if another source fails. A warning indicator identifies each unavailable integration without exposing private request details. If every selected integration fails, the widget reports an error; if cached data remains visible after a later total failure, a stale-data warning is shown.
diff --git a/apps/docs/docs/widgets/system-disks/index.mdx b/apps/docs/docs/widgets/system-disks/index.mdx
index 33a2e7693..3d4e6c222 100644
--- a/apps/docs/docs/widgets/system-disks/index.mdx
+++ b/apps/docs/docs/widgets/system-disks/index.mdx
@@ -19,7 +19,7 @@ import { synologyIntegration } from "@site/docs/integrations/synology";
-This widget displays filesystem and SMART disk usage for supported server and NAS integrations.
+This widget displays filesystem and SMART disk usage for supported server and NAS integrations. Compact mode uses a one-column card list with the configured primary value, available temperature, and solid health bar.
### Supported Integrations
diff --git a/apps/docs/docs/widgets/system-resources/index.mdx b/apps/docs/docs/widgets/system-resources/index.mdx
index cdc258f4c..5c68695d6 100644
--- a/apps/docs/docs/widgets/system-resources/index.mdx
+++ b/apps/docs/docs/widgets/system-resources/index.mdx
@@ -18,7 +18,7 @@ import { proxmoxIntegration } from "@site/docs/integrations/proxmox";
-Displays CPU, RAM and network of your host in real time
+Displays CPU, RAM and network of your host in real time. Compact view keeps every configured chart in its familiar stacked layout, while advanced view adds the expanded per-source presentation.
### Screenshots
diff --git a/apps/docs/docs/widgets/timetable/index.mdx b/apps/docs/docs/widgets/timetable/index.mdx
index 1aede99cd..0855906ee 100644
--- a/apps/docs/docs/widgets/timetable/index.mdx
+++ b/apps/docs/docs/widgets/timetable/index.mdx
@@ -13,7 +13,7 @@ import { timetableWidget } from ".";
-The Timetable widget provides real-time departure times for a selected station.
+The Timetable widget provides real-time departure times for a selected station. Compact mode keeps configured titles, line names, and platform fields visible regardless of widget height or width.
### Screenshots
diff --git a/apps/docs/docs/widgets/tracearr/index.mdx b/apps/docs/docs/widgets/tracearr/index.mdx
index 8d31d9d5e..4ddb47acf 100644
--- a/apps/docs/docs/widgets/tracearr/index.mdx
+++ b/apps/docs/docs/widgets/tracearr/index.mdx
@@ -15,7 +15,7 @@ import { tracearrIntegration } from "@site/docs/integrations/tracearr";
-The Tracearr widget provides a real-time monitoring dashboard for your media server activity. It displays active streams, user statistics, recent watching history, and policy violations.
+The Tracearr widget provides a real-time monitoring dashboard for your media server activity. It displays active streams, user statistics, recent watching history, and policy violations. Compact mode renders every enabled section in that familiar order without hiding or truncating sections based on widget height.
When multiple Tracearr integrations are selected, every stream, violation, and activity row keeps its source identity and an integration-scoped key. Data from healthy sources remains visible if another source fails, and a warning identifies each unavailable integration without exposing private request details. If every selected integration fails, the widget reports an error; if cached data remains visible after a later total failure, a stale-data warning is shown.
diff --git a/apps/docs/docs/widgets/traefik/index.mdx b/apps/docs/docs/widgets/traefik/index.mdx
index edbbba8b8..bb8393226 100644
--- a/apps/docs/docs/widgets/traefik/index.mdx
+++ b/apps/docs/docs/widgets/traefik/index.mdx
@@ -17,7 +17,7 @@ import { traefikIntegration } from "@site/docs/integrations/traefik";
This widget displays a compact overview of your Traefik instance, including total routers, entry points, protocol-specific router/service/middleware counts, and warning or error totals when Traefik reports them.
-Advanced view shows every protocol and entry point, identifies every healthy Traefik source and version, and labels each resource or failed endpoint with its owning integration. Compact mode keeps the responsive aggregate overview and names each healthy source when multiple integrations are selected.
+Advanced view shows every protocol and entry point, identifies every healthy Traefik source and version, and labels each resource or failed endpoint with its owning integration. Compact mode keeps every configured protocol card and entry-point label visible and names each healthy source when multiple integrations are selected.
When multiple Traefik integrations are selected, data from healthy sources remains visible if another source fails. A warning indicator identifies each unavailable integration without exposing private request details. If every selected integration fails, the widget reports an error; if cached data remains visible after a later total failure, a stale-data warning is shown.
diff --git a/apps/docs/docs/widgets/umami/index.mdx b/apps/docs/docs/widgets/umami/index.mdx
index 6f2c2bb7f..eb5d5e0f9 100644
--- a/apps/docs/docs/widgets/umami/index.mdx
+++ b/apps/docs/docs/widgets/umami/index.mdx
@@ -15,7 +15,7 @@ import { umamiIntegration } from "@site/docs/integrations/umami";
-The Umami Analytics widget displays key visitor metrics from a website tracked by your [Umami](https://umami.is) instance - pageviews, unique visitors, visits, bounce rate, avg. visit duration, and real-time active visitor count. The widget body can be switched between a visitor/event chart, a multi-event chart, and ranked top pages or referrers lists.
+The Umami Analytics widget displays key visitor metrics from a website tracked by your [Umami](https://umami.is) instance - pageviews, unique visitors, visits, bounce rate, avg. visit duration, and real-time active visitor count. The widget body can be switched between a visitor/event chart, a multi-event chart, and ranked top pages or referrers lists. Compact mode keeps configured axes and secondary statistics visible regardless of widget height.
### Supported Integrations
diff --git a/apps/docs/docs/widgets/uptime-kuma/index.mdx b/apps/docs/docs/widgets/uptime-kuma/index.mdx
index e0150a94d..6b9188b88 100644
--- a/apps/docs/docs/widgets/uptime-kuma/index.mdx
+++ b/apps/docs/docs/widgets/uptime-kuma/index.mdx
@@ -15,7 +15,7 @@ import { uptimeKumaIntegration } from "@site/docs/integrations/uptime-kuma";
-This widget displays uptime statistics from your Uptime Kuma status page, including a visual uptime ring, average uptime percentage, and per-status monitor counts.
+This widget displays uptime statistics from your Uptime Kuma status page, including a visual uptime ring, average uptime percentage, and per-status monitor counts. Compact mode shows every configured ring and statistic without dropping them based on widget height.
Advanced view always shows the uptime ring, all status totals, and the complete monitor list with source identity, regardless of compact visibility switches.
diff --git a/apps/nextjs/src/components/board/items/item-content.tsx b/apps/nextjs/src/components/board/items/item-content.tsx
index caac4cd60..e2ef8c88d 100644
--- a/apps/nextjs/src/components/board/items/item-content.tsx
+++ b/apps/nextjs/src/components/board/items/item-content.tsx
@@ -334,6 +334,7 @@ const LoadedBoardItemContent = ({
item={item}
width={width}
height={height}
+ displayScale={boardCanvasScale}
widgetStateRef={widgetStateRef}
widgetRuntimeRef={widgetRuntimeRef}
displayMode={isAdvanced ? "advanced" : "compact"}
@@ -436,6 +437,7 @@ interface InnerContentProps {
item: SectionItem;
width: number;
height: number;
+ displayScale: number;
widgetStateRef: MutableRefObject | null>;
widgetRuntimeRef: WidgetRuntimeRef;
displayMode: "compact" | "advanced";
diff --git a/apps/nextjs/src/components/board/layout/scaled-board-canvas.module.css b/apps/nextjs/src/components/board/layout/scaled-board-canvas.module.css
index e0f3b6d65..91dfef8ca 100644
--- a/apps/nextjs/src/components/board/layout/scaled-board-canvas.module.css
+++ b/apps/nextjs/src/components/board/layout/scaled-board-canvas.module.css
@@ -30,11 +30,15 @@
--mantine-spacing-md: calc(1rem * var(--board-canvas-ui-scale, 1));
--mantine-spacing-lg: calc(1.25rem * var(--board-canvas-ui-scale, 1));
--mantine-spacing-xl: calc(2rem * var(--board-canvas-ui-scale, 1));
+ --mantine-font-size-xxs: calc(0.6875rem * var(--board-canvas-ui-scale, 1));
--mantine-font-size-xs: calc(0.75rem * var(--board-canvas-ui-scale, 1));
--mantine-font-size-sm: calc(0.875rem * var(--board-canvas-ui-scale, 1));
--mantine-font-size-md: calc(1rem * var(--board-canvas-ui-scale, 1));
--mantine-font-size-lg: calc(1.125rem * var(--board-canvas-ui-scale, 1));
--mantine-font-size-xl: calc(1.25rem * var(--board-canvas-ui-scale, 1));
+ --mantine-font-size-2xl: calc(1.5rem * var(--board-canvas-ui-scale, 1));
+ --mantine-font-size-3xl: calc(1.75rem * var(--board-canvas-ui-scale, 1));
+ --mantine-font-size-4xl: calc(2.25rem * var(--board-canvas-ui-scale, 1));
--mantine-h1-font-size: calc(2.125rem * var(--board-canvas-ui-scale, 1));
--mantine-h2-font-size: calc(1.625rem * var(--board-canvas-ui-scale, 1));
--mantine-h3-font-size: calc(1.375rem * var(--board-canvas-ui-scale, 1));
diff --git a/package.json b/package.json
index 347e76aa0..b4d256aaf 100644
--- a/package.json
+++ b/package.json
@@ -33,6 +33,7 @@
"dev:cli": "go -C tools/homarr-dev run -buildvcs=false .",
"dev:cli:build": "go -C tools/homarr-dev build -buildvcs=false .",
"dev:cli:install": "go -C tools/homarr-dev install -buildvcs=false .",
+ "dev:widget-regressions": "pnpm -F @homarr/db demo:widget-regressions",
"dev:docs": "pnpm -F @homarr/docs dev",
"docker:docs": "docker compose -f apps/workshop/docker-compose.yml up --build docs",
"docker:workshop": "docker compose -f apps/workshop/docker-compose.yml up --build workshop",
diff --git a/packages/db/package.json b/packages/db/package.json
index eab91d238..61bf669b9 100644
--- a/packages/db/package.json
+++ b/packages/db/package.json
@@ -23,6 +23,7 @@
"build:postgresql": "esbuild migrations/postgresql/migrate.ts --bundle --platform=node --outfile=migrations/postgresql/migrate.cjs",
"build:sqlite": "esbuild migrations/sqlite/migrate.ts --bundle --platform=node --outfile=migrations/sqlite/migrate.cjs",
"clean": "rm -rf .turbo node_modules",
+ "demo:widget-regressions": "pnpm with-env tsx ./scripts/seed-widget-regression-board.ts",
"format": "oxfmt .",
"lint": "oxlint",
"migration:custom": "pnpm with-env tsx ./migrations/custom/run-custom.ts",
diff --git a/packages/db/scripts/seed-widget-regression-board.ts b/packages/db/scripts/seed-widget-regression-board.ts
new file mode 100644
index 000000000..cb60918e9
--- /dev/null
+++ b/packages/db/scripts/seed-widget-regression-board.ts
@@ -0,0 +1,269 @@
+import { relative, resolve } from "node:path";
+
+import { stringify } from "superjson";
+
+import { createId } from "@homarr/common";
+import type { WidgetKind } from "@homarr/definitions";
+
+import { db, eq } from "..";
+import { apps, boards, integrationItems, integrations, itemLayouts, items, layouts, sections, users } from "../schema";
+
+const boardName = "widget-regressions";
+const truthyEnvironmentValues = new Set(["1", "true", "yes", "on"]);
+
+interface FixtureWidget {
+ kind: WidgetKind;
+ xOffset: number;
+ yOffset: number;
+ width: number;
+ height: number;
+ mobileHeight: number;
+ needsIntegration: boolean;
+ options?: Record;
+}
+
+if (process.env.DB_DRIVER !== "better-sqlite3") {
+ throw new Error("The widget regression fixture only supports an isolated better-sqlite3 demo database");
+}
+
+if (!truthyEnvironmentValues.has((process.env.DEMO_MODE ?? "").toLowerCase())) {
+ throw new Error("The widget regression fixture requires DEMO_MODE=true");
+}
+
+if (truthyEnvironmentValues.has((process.env.DEMO_READ_ONLY ?? "").toLowerCase())) {
+ throw new Error("The widget regression fixture requires writable demo mode");
+}
+
+const databaseUrl = process.env.DB_URL;
+if (!databaseUrl) {
+ throw new Error("The widget regression fixture requires DB_URL");
+}
+
+const resolvedDatabasePath = resolve(databaseUrl);
+const pathWithinTmp = relative("/tmp", resolvedDatabasePath);
+if (pathWithinTmp.length === 0 || pathWithinTmp.startsWith("..")) {
+ throw new Error("The widget regression fixture requires an isolated SQLite database under /tmp");
+}
+
+const demoUser = await db.query.users.findFirst({
+ where: eq(users.name, "demo"),
+});
+if (!demoUser) {
+ throw new Error("Demo user not found. Run the SQLite migrations and demo seed first");
+}
+
+const mockIntegration = await db.query.integrations.findFirst({
+ where: eq(integrations.kind, "mock"),
+});
+if (!mockIntegration) {
+ throw new Error("Mock integration not found. Start the seed with DEMO_MODE=true");
+}
+
+const sonarrApp = await db.query.apps.findFirst({
+ where: eq(apps.name, "Sonarr"),
+});
+if (!sonarrApp) {
+ throw new Error("Seeded Sonarr app not found");
+}
+
+const existingBoard = await db.query.boards.findFirst({
+ where: eq(boards.name, boardName),
+});
+if (existingBoard) {
+ await db.delete(boards).where(eq(boards.id, existingBoard.id));
+}
+
+const boardId = createId();
+const sectionId = createId();
+const mobileLayoutId = createId();
+const baseLayoutId = createId();
+
+await db.insert(boards).values({
+ id: boardId,
+ name: boardName,
+ isPublic: false,
+ creatorId: demoUser.id,
+ pageTitle: "Widget regression fixture",
+ primaryColor: "#fa5252",
+ secondaryColor: "#fd7e14",
+ itemRadius: "lg",
+});
+await db.insert(sections).values({
+ id: sectionId,
+ boardId,
+ kind: "empty",
+ xOffset: 0,
+ yOffset: 0,
+});
+await db.insert(layouts).values([
+ {
+ id: mobileLayoutId,
+ name: "Mobile",
+ boardId,
+ columnCount: 3,
+ breakpoint: 0,
+ role: "mobile",
+ },
+ {
+ id: baseLayoutId,
+ name: "Base",
+ boardId,
+ columnCount: 16,
+ breakpoint: 768,
+ role: "base",
+ },
+]);
+
+const fixtureWidgets: FixtureWidget[] = [
+ {
+ kind: "indexerManager",
+ xOffset: 0,
+ yOffset: 0,
+ width: 2,
+ height: 4,
+ mobileHeight: 4,
+ needsIntegration: true,
+ options: { openIndexerSiteInNewTab: true },
+ },
+ {
+ kind: "systemResources",
+ xOffset: 2,
+ yOffset: 0,
+ width: 4,
+ height: 4,
+ mobileHeight: 4,
+ needsIntegration: true,
+ options: {
+ hasShadow: true,
+ visibleCharts: ["cpu", "memory", "network"],
+ labelDisplayMode: "textWithIcon",
+ },
+ },
+ {
+ kind: "networkControllerStatus",
+ xOffset: 6,
+ yOffset: 0,
+ width: 2,
+ height: 2,
+ mobileHeight: 2,
+ needsIntegration: true,
+ options: { content: "wired" },
+ },
+ {
+ kind: "networkControllerStatus",
+ xOffset: 6,
+ yOffset: 2,
+ width: 2,
+ height: 2,
+ mobileHeight: 2,
+ needsIntegration: true,
+ options: { content: "wifi" },
+ },
+ {
+ kind: "audioStats",
+ xOffset: 8,
+ yOffset: 0,
+ width: 2,
+ height: 2,
+ mobileHeight: 3,
+ needsIntegration: true,
+ options: {
+ showArtists: true,
+ showAlbums: true,
+ showSongs: true,
+ compactMode: false,
+ },
+ },
+ {
+ kind: "paperlessNgx",
+ xOffset: 10,
+ yOffset: 0,
+ width: 2,
+ height: 2,
+ mobileHeight: 3,
+ needsIntegration: true,
+ options: {
+ showInboxRatio: false,
+ showInboxRing: false,
+ showDocumentsTotal: true,
+ showDocumentsInbox: true,
+ showCorrespondents: false,
+ showTags: false,
+ showDocumentTypes: true,
+ },
+ },
+ {
+ kind: "networkControllerSummary",
+ xOffset: 0,
+ yOffset: 4,
+ width: 4,
+ height: 2,
+ mobileHeight: 3,
+ needsIntegration: true,
+ },
+ {
+ kind: "app",
+ xOffset: 4,
+ yOffset: 4,
+ width: 2,
+ height: 2,
+ mobileHeight: 2,
+ needsIntegration: false,
+ options: {
+ appId: sonarrApp.id,
+ openInNewTab: true,
+ showTitle: true,
+ pingEnabled: false,
+ },
+ },
+];
+
+let mobileYOffset = 0;
+for (const widget of fixtureWidgets) {
+ const itemId = createId();
+ await db.insert(items).values({
+ id: itemId,
+ boardId,
+ kind: widget.kind,
+ options: stringify(widget.options ?? {}),
+ });
+ await db.insert(itemLayouts).values([
+ {
+ itemId,
+ sectionId,
+ layoutId: baseLayoutId,
+ xOffset: widget.xOffset,
+ yOffset: widget.yOffset,
+ width: widget.width,
+ height: widget.height,
+ },
+ {
+ itemId,
+ sectionId,
+ layoutId: mobileLayoutId,
+ xOffset: 0,
+ yOffset: mobileYOffset,
+ width: 3,
+ height: widget.mobileHeight,
+ },
+ ]);
+ mobileYOffset += widget.mobileHeight;
+
+ if (widget.needsIntegration) {
+ await db.insert(integrationItems).values({
+ itemId,
+ integrationId: mockIntegration.id,
+ });
+ }
+}
+
+await db.update(users).set({ homeBoardId: boardId }).where(eq(users.id, demoUser.id));
+
+const insertedItems = await db.query.items.findMany({
+ where: eq(items.boardId, boardId),
+});
+if (insertedItems.length !== fixtureWidgets.length) {
+ throw new Error(`Expected ${fixtureWidgets.length} fixture widgets, inserted ${insertedItems.length}`);
+}
+
+console.log(`Created /boards/${boardName} with ${insertedItems.length} focused regression widgets`);
diff --git a/packages/widgets/src/app/component.tsx b/packages/widgets/src/app/component.tsx
index be8b936e1..cb8c3cef6 100644
--- a/packages/widgets/src/app/component.tsx
+++ b/packages/widgets/src/app/component.tsx
@@ -2,7 +2,7 @@
import type { PropsWithChildren } from "react";
import { Fragment, Suspense } from "react";
-import { Box, Flex, Stack, Text, Tooltip, UnstyledButton } from "@mantine/core";
+import { Box, Flex, rem, Stack, Text, Tooltip, UnstyledButton } from "@mantine/core";
import { IconMinus } from "@tabler/icons-react";
import combineClasses from "clsx";
@@ -59,19 +59,19 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
{app.name}
)}
- {options.descriptionDisplayMode === "normal" && !isTiny && (
+ {options.descriptionDisplayMode === "normal" && (
{app.description?.split("\n").map((line, index) => (
@@ -91,7 +91,7 @@ export default function AppWidget({ options, isEditMode, height, width }: Widget
height: "100%",
width: "100%",
minWidth: "20%",
- maxWidth: isColumnLayout ? undefined : isTiny ? "38%" : "50%",
+ maxWidth: isColumnLayout ? undefined : "50%",
}}
/>
diff --git a/packages/widgets/src/audio-stats/audio-stats-content.tsx b/packages/widgets/src/audio-stats/audio-stats-content.tsx
index f239961f5..72d9c292f 100644
--- a/packages/widgets/src/audio-stats/audio-stats-content.tsx
+++ b/packages/widgets/src/audio-stats/audio-stats-content.tsx
@@ -9,14 +9,7 @@ import { zoomCompensatedSize } from "@homarr/ui";
import classes from "./component.module.css";
import type { AudioStatsBackend, AudioStatsDisplayOptions } from "./shared";
-import {
- AUDIO_STATS_TRANSLATION_SCOPE,
- getGridCols,
- getIconSize,
- prioritizeVisibleStats,
- getVisibleStatLimit,
- getVisibleStats,
-} from "./shared";
+import { AUDIO_STATS_TRANSLATION_SCOPE, getGridCols, getIconSize, getVisibleStats } from "./shared";
const rootClassByCompact = {
true: classes.rootCompact,
@@ -33,13 +26,20 @@ const statTileClassByCompact = {
false: "",
} as const;
+const statTileClassByAdvanced = {
+ true: classes.statTileAdvanced,
+ false: "",
+} as const;
+
+const COMPACT_SINGLE_COLUMN_BREAKPOINT = 220;
+
interface AudioStatsContentProps {
backend: AudioStatsBackend;
stats: NavidromeDashboardData | AudiobookshelfDashboardData;
options: AudioStatsDisplayOptions;
showAllStats?: boolean;
+ advanced?: boolean;
width: number;
- height: number;
}
export function AudioStatsContent({
@@ -47,15 +47,18 @@ export function AudioStatsContent({
stats,
options,
showAllStats = false,
+ advanced = false,
width,
- height,
}: AudioStatsContentProps) {
const t = useI18n(AUDIO_STATS_TRANSLATION_SCOPE);
const compact = options.compactMode ?? false;
- const enabledStats = prioritizeVisibleStats(getVisibleStats(backend, options, stats, showAllStats), compact);
- const visibleStats = enabledStats.slice(0, getVisibleStatLimit(width, height, enabledStats.length, compact));
+ const visibleStats = getVisibleStats(backend, options, stats, showAllStats);
const compactKey = String(compact) as keyof typeof rootClassByCompact;
- const gridCols = getGridCols(width, visibleStats.length, compact);
+ const advancedKey = String(advanced) as keyof typeof statTileClassByAdvanced;
+ let gridCols = getGridCols(width, visibleStats.length, compact);
+ if (!advanced && width < COMPACT_SINGLE_COLUMN_BREAKPOINT) {
+ gridCols = 1;
+ }
const iconSize = getIconSize(width, compact);
return (
@@ -66,7 +69,10 @@ export function AudioStatsContent({
style={{ "--stat-cols": gridCols } as CSSProperties}
>
{visibleStats.map(({ optionKey, statKey, value, Icon }) => (
-