From d11ca0d70e91075c52bb0113418a16e90327bc5b Mon Sep 17 00:00:00 2001
From: shamoon <4887959+shamoon@users.noreply.github.com>
Date: Fri, 21 Aug 2026 12:29:01 -0700
Subject: [PATCH] Yay drop all these max fields
---
docs/widgets/authoring/getting-started.md | 1 +
src/widgets/arcane/component.jsx | 3 +--
src/widgets/backrest/component.jsx | 3 +--
src/widgets/dockhand/component.jsx | 3 +--
src/widgets/fritzbox/component.jsx | 3 +--
src/widgets/karakeep/component.jsx | 3 +--
src/widgets/komodo/component.jsx | 3 +--
src/widgets/maintainerr/component.jsx | 3 +--
src/widgets/pangolin/component.jsx | 3 +--
src/widgets/romm/component.jsx | 3 +--
src/widgets/seerr/component.jsx | 3 +--
src/widgets/slskd/component.jsx | 3 +--
src/widgets/tailscale/component.jsx | 3 +--
src/widgets/technitium/component.jsx | 4 +---
src/widgets/unraid/component.jsx | 11 +++--------
src/widgets/unraid/component.test.jsx | 9 ++++++---
src/widgets/wallos/component.jsx | 3 +--
17 files changed, 24 insertions(+), 40 deletions(-)
diff --git a/docs/widgets/authoring/getting-started.md b/docs/widgets/authoring/getting-started.md
index d93b01862..27fbb0570 100644
--- a/docs/widgets/authoring/getting-started.md
+++ b/docs/widgets/authoring/getting-started.md
@@ -85,6 +85,7 @@ To ensure cohesiveness of various widgets, the following should be used as a gui
- Note that we reserve the right to decline widgets for projects that are very young (eg < ~1y) or those with a small reach (eg low GitHub stars). Again, this is in an effort to keep overall widget maintenance under control.
- Widgets should be only one row of blocks
- Widgets should be no more than 4 blocks wide and generally conform to the styling / design choices of other widgets
+- Use `withWidgetFields(service, defaultFields)` from `utils/widget-fields` to apply default fields and cap configured fields without mutating the service. The helper limits widgets to 4 fields.
- Minimize the number of API calls
- Avoid the use of custom proxy unless absolutely necessary
- Widgets should be 'read-only', as in they should not make write changes using the relevant tool's API. Homepage widgets are designed to surface information, not to be a (usually worse) replacement for the tool itself.
diff --git a/src/widgets/arcane/component.jsx b/src/widgets/arcane/component.jsx
index 9f6d416e2..cdd14e300 100644
--- a/src/widgets/arcane/component.jsx
+++ b/src/widgets/arcane/component.jsx
@@ -5,12 +5,11 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
-const MAX_FIELDS = 4;
const DEFAULT_FIELDS = ["running", "stopped", "total", "image_updates"];
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_FIELDS);
+ const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const envNotSet = widget.env == null || widget.env === "";
diff --git a/src/widgets/backrest/component.jsx b/src/widgets/backrest/component.jsx
index 20e6745fd..0ab2ca483 100644
--- a/src/widgets/backrest/component.jsx
+++ b/src/widgets/backrest/component.jsx
@@ -6,12 +6,11 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const BACKREST_DEFAULT_FIELDS = ["num_success_latest", "num_failure_latest", "num_failure_30", "bytes_added_30"];
-const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, BACKREST_DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, BACKREST_DEFAULT_FIELDS);
const { widget } = service;
const { data, error } = useWidgetAPI(widget, "summary");
diff --git a/src/widgets/dockhand/component.jsx b/src/widgets/dockhand/component.jsx
index 6a5bdcc0e..e72132f5d 100644
--- a/src/widgets/dockhand/component.jsx
+++ b/src/widgets/dockhand/component.jsx
@@ -5,12 +5,11 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
-const MAX_FIELDS = 4;
const DEFAULT_FIELDS = ["running", "total", "cpu", "memory"];
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_FIELDS);
+ const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const { data: stats, error: statsError } = useWidgetAPI(widget, "dashboard/stats");
diff --git a/src/widgets/fritzbox/component.jsx b/src/widgets/fritzbox/component.jsx
index 3eda567e2..1f7f6c576 100644
--- a/src/widgets/fritzbox/component.jsx
+++ b/src/widgets/fritzbox/component.jsx
@@ -6,11 +6,10 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
export const fritzboxDefaultFields = ["connectionStatus", "uptime", "maxDown", "maxUp"];
-const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, fritzboxDefaultFields, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, fritzboxDefaultFields);
const { widget } = service;
const { data: fritzboxData, error: fritzboxError } = useWidgetAPI(widget, "status");
diff --git a/src/widgets/karakeep/component.jsx b/src/widgets/karakeep/component.jsx
index 3453e8275..00cc90d04 100644
--- a/src/widgets/karakeep/component.jsx
+++ b/src/widgets/karakeep/component.jsx
@@ -6,11 +6,10 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
export const karakeepDefaultFields = ["bookmarks", "favorites", "archived", "highlights"];
-const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, karakeepDefaultFields, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, karakeepDefaultFields);
const { widget } = service;
const { data: statsData, error: statsError } = useWidgetAPI(widget, "stats");
diff --git a/src/widgets/komodo/component.jsx b/src/widgets/komodo/component.jsx
index 509ab6fb1..a1e4a6f07 100644
--- a/src/widgets/komodo/component.jsx
+++ b/src/widgets/komodo/component.jsx
@@ -5,7 +5,6 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
-const MAX_ALLOWED_FIELDS = 4;
const SUMMARY_FIELDS = ["servers", "stacks", "containers"];
const STACK_FIELDS = ["total", "running", "down", "unhealthy"];
const CONTAINER_FIELDS = ["total", "running", "stopped", "unhealthy"];
@@ -18,7 +17,7 @@ export default function Component({ service: configuredService }) {
: configuredWidget.showStacks
? STACK_FIELDS
: CONTAINER_FIELDS;
- const service = withWidgetFields(configuredService, defaultFields, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, defaultFields);
const { widget } = service;
const containersEndpoint = !(!widget.showSummary && widget.showStacks) ? "containers" : "";
const { data: containersData, error: containersError } = useWidgetAPI(widget, containersEndpoint);
diff --git a/src/widgets/maintainerr/component.jsx b/src/widgets/maintainerr/component.jsx
index 2e0e175ee..77ee89ec0 100644
--- a/src/widgets/maintainerr/component.jsx
+++ b/src/widgets/maintainerr/component.jsx
@@ -6,11 +6,10 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const DEFAULT_FIELDS = ["itemsHandled", "episodesHandled", "moviesHandled", "reclaimable"];
-const MAX_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_FIELDS);
+ const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const { data, error } = useWidgetAPI(widget);
diff --git a/src/widgets/pangolin/component.jsx b/src/widgets/pangolin/component.jsx
index 1473487c6..e7ad38c7e 100644
--- a/src/widgets/pangolin/component.jsx
+++ b/src/widgets/pangolin/component.jsx
@@ -5,12 +5,11 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
-const MAX_ALLOWED_FIELDS = 4;
const DEFAULT_FIELDS = ["sites", "resources", "targets", "traffic"];
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const { data: sitesData, error: sitesError } = useWidgetAPI(widget, "sites");
diff --git a/src/widgets/romm/component.jsx b/src/widgets/romm/component.jsx
index 97a0afb9d..00fb25d5a 100644
--- a/src/widgets/romm/component.jsx
+++ b/src/widgets/romm/component.jsx
@@ -6,10 +6,9 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const ROMM_DEFAULT_FIELDS = ["platforms", "totalRoms", "saves", "states"];
-const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
- const service = withWidgetFields(configuredService, ROMM_DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, ROMM_DEFAULT_FIELDS);
const { widget } = service;
const { t } = useTranslation();
const { data: response, error: responseError } = useWidgetAPI(widget, "statistics");
diff --git a/src/widgets/seerr/component.jsx b/src/widgets/seerr/component.jsx
index 9415b256e..c1a7cb83d 100644
--- a/src/widgets/seerr/component.jsx
+++ b/src/widgets/seerr/component.jsx
@@ -5,10 +5,9 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
export const seerrDefaultFields = ["pending", "approved", "completed"];
-const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
- const service = withWidgetFields(configuredService, seerrDefaultFields, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, seerrDefaultFields);
const { widget } = service;
const isIssueEnabled = widget.fields.includes("issues");
diff --git a/src/widgets/slskd/component.jsx b/src/widgets/slskd/component.jsx
index 7c068ac14..7c3a56001 100644
--- a/src/widgets/slskd/component.jsx
+++ b/src/widgets/slskd/component.jsx
@@ -6,11 +6,10 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const slskdDefaultFields = ["slskStatus", "downloads", "uploads", "sharedFiles"];
-const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, slskdDefaultFields, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, slskdDefaultFields);
const { widget } = service;
const { data: appData, error: appError } = useWidgetAPI(widget, "application");
diff --git a/src/widgets/tailscale/component.jsx b/src/widgets/tailscale/component.jsx
index 77b80d348..4c05c5d30 100644
--- a/src/widgets/tailscale/component.jsx
+++ b/src/widgets/tailscale/component.jsx
@@ -6,12 +6,11 @@ import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
const DEFAULT_FIELDS = ["address", "last_seen", "expires"];
-const MAX_ALLOWED_FIELDS = 4;
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const { data: tailscaleData, error: tailscaleError } = useWidgetAPI(widget, "device");
diff --git a/src/widgets/technitium/component.jsx b/src/widgets/technitium/component.jsx
index 84d28226e..6d676c82a 100644
--- a/src/widgets/technitium/component.jsx
+++ b/src/widgets/technitium/component.jsx
@@ -5,14 +5,12 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
-const MAX_ALLOWED_FIELDS = 4;
-
export const technitiumDefaultFields = ["totalQueries", "totalAuthoritative", "totalCached", "totalServerFailure"];
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, technitiumDefaultFields, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, technitiumDefaultFields);
const { widget } = service;
const params = {
diff --git a/src/widgets/unraid/component.jsx b/src/widgets/unraid/component.jsx
index 99ec6244c..2b7ed6ea1 100644
--- a/src/widgets/unraid/component.jsx
+++ b/src/widgets/unraid/component.jsx
@@ -3,9 +3,9 @@ import Container from "components/services/widget/container";
import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
+import withWidgetFields from "utils/widget-fields";
const UNRAID_DEFAULT_FIELDS = ["status", "cpu", "memoryPercent", "notifications"];
-const MAX_ALLOWED_FIELDS = 4;
const POOLS = ["pool1", "pool2", "pool3", "pool4"];
const POOL_FIELDS = [
@@ -14,8 +14,9 @@ const POOL_FIELDS = [
{ param: "UsedPercent", label: "poolUsed", valueKey: "fsUsedPercent", valueType: "common.percent" },
];
-export default function Component({ service }) {
+export default function Component({ service: configuredService }) {
const { t } = useTranslation();
+ const service = withWidgetFields(configuredService, UNRAID_DEFAULT_FIELDS);
const { widget } = service;
const { data, error } = useWidgetAPI(widget);
@@ -24,12 +25,6 @@ export default function Component({ service }) {
return ;
}
- if (!widget.fields?.length) {
- widget.fields = UNRAID_DEFAULT_FIELDS;
- } else if (widget.fields.length > MAX_ALLOWED_FIELDS) {
- widget.fields = widget.fields.slice(0, MAX_ALLOWED_FIELDS);
- }
-
if (!data) {
return (
diff --git a/src/widgets/unraid/component.test.jsx b/src/widgets/unraid/component.test.jsx
index e6b82743d..e5d80b279 100644
--- a/src/widgets/unraid/component.test.jsx
+++ b/src/widgets/unraid/component.test.jsx
@@ -20,14 +20,17 @@ describe("widgets/unraid/component", () => {
vi.clearAllMocks();
});
- it("defaults widget.fields and filters down to 4 visible blocks while loading", () => {
+ it("defaults fields and filters down to 4 visible blocks while loading", () => {
useWidgetAPI.mockReturnValue({ data: undefined, error: undefined });
const service = { widget: { type: "unraid" } };
const { container } = renderWithProviders(, { settings: { hideErrors: false } });
- // Component sets default fields
- expect(service.widget.fields).toEqual(["status", "cpu", "memoryPercent", "notifications"]);
+ expect(service.widget.fields).toBeUndefined();
+ expect(useWidgetAPI).toHaveBeenCalledWith({
+ type: "unraid",
+ fields: ["status", "cpu", "memoryPercent", "notifications"],
+ });
// Container filters the many placeholder Blocks down to the selected fields.
expect(container.querySelectorAll(".service-block")).toHaveLength(4);
diff --git a/src/widgets/wallos/component.jsx b/src/widgets/wallos/component.jsx
index 16d5c659b..60606e9a4 100644
--- a/src/widgets/wallos/component.jsx
+++ b/src/widgets/wallos/component.jsx
@@ -5,7 +5,6 @@ import { useTranslation } from "next-i18next/pages";
import useWidgetAPI from "utils/proxy/use-widget-api";
import withWidgetFields from "utils/widget-fields";
-const MAX_ALLOWED_FIELDS = 4;
const DEFAULT_FIELDS = ["activeSubscriptions", "nextRenewingSubscription", "thisMonthlyCost", "nextMonthlyCost"];
const todayDate = new Date();
@@ -20,7 +19,7 @@ function toApiMonthYear(offset = 0) {
export default function Component({ service: configuredService }) {
const { t } = useTranslation();
- const service = withWidgetFields(configuredService, DEFAULT_FIELDS, MAX_ALLOWED_FIELDS);
+ const service = withWidgetFields(configuredService, DEFAULT_FIELDS);
const { widget } = service;
const subscriptionsEndPoint =