
@@ -34,4 +31,5 @@ import piHoleApp from './img/pi-hole-ping-icon.png';
### Configuration
+
diff --git a/apps/docs/docs/widgets/bookmarks/index.mdx b/apps/docs/docs/widgets/bookmarks/index.mdx
index c3104ad5b..c79629113 100644
--- a/apps/docs/docs/widgets/bookmarks/index.mdx
+++ b/apps/docs/docs/widgets/bookmarks/index.mdx
@@ -1,29 +1,26 @@
---
-title: 'Bookmarks'
-description: 'Displays multiple app links'
+title: "Bookmarks"
+description: "Displays multiple app links"
hide_title: true
---
-import { AddingWidget } from '@site/src/components/widgets/adding';
-import { WidgetConfig } from '@site/src/components/widgets/configuration';
-import { WidgetHeader } from '@site/src/components/widgets/header';
-import { bookmarksWidget } from '.';
+import { AddingWidget } from "@site/src/components/widgets/adding";
+import { WidgetConfig } from "@site/src/components/widgets/configuration";
+import { WidgetHeader } from "@site/src/components/widgets/header";
+import { bookmarksWidget } from ".";
-
-
+
The bookmarks widget allows you to display multiple app links in a single widget.
You can add links to your favorite applications, websites, or any other resources you want quick access to.
+In view mode, right-clicking uses the browser's link menu. Homarr's widget menu remains available in edit mode.
### Screenshots
-import gridIconsOnly from './img/grid-icons-only.png';
-import gridWithTitle from './img/grid-with-title.png';
-import verticalListAll from './img/vertical-list-all.png';
-import vericalListSimple from './img/vertical-list-simple.png';
+import gridIconsOnly from "./img/grid-icons-only.png";
+import gridWithTitle from "./img/grid-with-title.png";
+import verticalListAll from "./img/vertical-list-all.png";
+import vericalListSimple from "./img/vertical-list-simple.png";
@@ -33,12 +30,13 @@ import vericalListSimple from './img/vertical-list-simple.png';

-
+
### Adding the widget
### Configuration
+
diff --git a/apps/nextjs/src/components/board/items/widget-context-menu.tsx b/apps/nextjs/src/components/board/items/widget-context-menu.tsx
index 3b844874f..9f3e19da7 100644
--- a/apps/nextjs/src/components/board/items/widget-context-menu.tsx
+++ b/apps/nextjs/src/components/board/items/widget-context-menu.tsx
@@ -44,9 +44,13 @@ interface WidgetContextMenuProps {
export const WidgetContextMenu = ({ item, definition, widgetStateRef, children }: WidgetContextMenuProps) => {
const { data: session } = useSession();
+ const [isEditMode] = useEditMode();
const settings = useSettings();
if (!session || !settings.enableRightClickOnWidgets) return <>{children}>;
+ const isLinkWidget = item.kind === "app" || item.kind === "bookmarks";
+ // Keep the Homarr menu in edit mode so these widgets can still be edited or removed.
+ if (isLinkWidget && !isEditMode) return <>{children}>;
return (
diff --git a/apps/nextjs/src/components/board/sections/gridstack/use-gridstack.ts b/apps/nextjs/src/components/board/sections/gridstack/use-gridstack.ts
index b5e468de6..38f5fd596 100644
--- a/apps/nextjs/src/components/board/sections/gridstack/use-gridstack.ts
+++ b/apps/nextjs/src/components/board/sections/gridstack/use-gridstack.ts
@@ -301,6 +301,8 @@ interface UseCssVariableConfiguration {
isDynamic: boolean;
}
+const minimumResizeWidthDelta = 20;
+
/**
* This hook is used to configure the css variables for the gridstack
* Those css variables are used to define the size of the gridstack items
@@ -319,9 +321,30 @@ const useCssVariableConfiguration = ({
columnCount,
isDynamic,
}: UseCssVariableConfiguration) => {
+ const prevSizeRef = useRef<{ width: number; height: number } | null>(null);
+
const onResize = useCallback(() => {
if (!wrapperRef.current) return;
if (!gridRef.current) return;
+
+ const currentWidth = wrapperRef.current.clientWidth;
+ const currentHeight = wrapperRef.current.clientHeight;
+ const prevSize = prevSizeRef.current;
+
+ // Only skip recalculation when the width change is insignificant
+ // and, for dynamic sections, the height is unchanged. This prevents infinite
+ // feedback loops when the vertical scrollbar appears/disappears, causing
+ // clientWidth to oscillate by ~15px, while still reacting to height changes.
+ if (
+ prevSize !== null &&
+ Math.abs(currentWidth - prevSize.width) < minimumResizeWidthDelta &&
+ (!isDynamic || currentHeight === prevSize.height)
+ ) {
+ return;
+ }
+
+ prevSizeRef.current = { width: currentWidth, height: currentHeight };
+
handleResizeChange(
wrapperRef.current,
gridRef.current,
diff --git a/packages/integrations/src/unraid/unraid-integration.ts b/packages/integrations/src/unraid/unraid-integration.ts
index c10aad89e..006282f3b 100644
--- a/packages/integrations/src/unraid/unraid-integration.ts
+++ b/packages/integrations/src/unraid/unraid-integration.ts
@@ -37,9 +37,16 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
const systemInfo = await this.getSystemInformationAsync();
const cpuUtilization = systemInfo.metrics.cpu.cpus.reduce((acc, val) => acc + val.percentTotal, 0);
- const cpuCount = systemInfo.info.cpu.cores;
+ const cpuCount = systemInfo.metrics.cpu.cpus.length;
+
+ // Each entry in cpus[] is one logical CPU (thread). Dividing the summed per-thread
+ // utilization by the thread count (not physical cores) gives the correct average and
+ // avoids over-reporting on SMT systems. Guard against zero to avoid Infinity/NaN.
+ let cpuUtilizationNormalized = 0;
+ if (cpuCount > 0) {
+ cpuUtilizationNormalized = cpuUtilization / cpuCount;
+ }
- // We use "info" object instead of the stats since this is the exact amount the kernel sees, which is what Unraid displays.
const totalMemory = systemInfo.info.memory.layout.reduce((acc, layout) => layout.size + acc, 0);
const usedMemory = totalMemory * (systemInfo.metrics.memory.percentTotal / 100);
const uptime = dayjs(systemInfo.info.os.uptime);
@@ -47,7 +54,7 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
return {
version: systemInfo.info.os.release,
cpuModelName: systemInfo.info.cpu.brand,
- cpuUtilization: cpuUtilization / cpuCount,
+ cpuUtilization: cpuUtilizationNormalized,
memUsedInBytes: usedMemory,
memAvailableInBytes: totalMemory - usedMemory,
uptime: dayjs().diff(uptime, "seconds"),
@@ -139,7 +146,7 @@ export class UnraidIntegration extends Integration implements ISystemHealthMonit
},
memory {
layout {
- size
+ size
}
}
}