fix: require advanced widget focus opt-in

This commit is contained in:
Thomas Camlong authored and ajnart committed 2026-08-14 11:07:08 +02:00
1 parent ae515584cc
commit 38d30acb87
25 files changed
+56 -29

No files matched your search

@@ -3,41 +3,44 @@ import { describe, expect, test } from "vitest";
import { supportsAdvancedFocus } from "./definition";
import { widgetImports } from ".";
const compactOnlyWidgetKinds = [
"app",
"bazarr",
"beszelAlerts",
"beszelSystemGrid",
"beszelSystemStats",
"beszelSystemTable",
"coolify",
"dnsHoleControls",
"dnsHoleSummary",
"iframe",
"indexerManager",
"mediaRequests-requestList",
"mediaRequests-requestStats",
"minecraftServerStatus",
"networkControllerSummary",
"notebook",
"notifications",
"paperlessNgx",
"patchmon",
"releases",
"rssFeed",
"stockPrice",
"systemDisks",
"video",
"vpn",
const advancedFocusWidgetKinds = [
"anchorNote",
"archiveTeamWarrior",
"audioStats",
"bookmarks",
"calendar",
"clock",
"dockerContainers",
"downloads",
"firewall",
"healthMonitoring",
"immich-albumCarousel",
"immich-serverStats",
"mediaMissing",
"mediaReleases",
"mediaServer",
"mediaTranscoding",
"networkControllerStatus",
"smartHome-entityState",
"smartHome-executeAutomation",
"speedtestTracker",
"systemResources",
"timetable",
"tracearr",
"traefik",
"umami",
"ups",
"uptimeKuma",
"weather",
] as const;
describe("advanced focus support", () => {
test("is reserved for widgets with a distinct advanced experience", () => {
const actualCompactOnlyKinds = Object.entries(widgetImports)
.filter(([, widgetImport]) => !supportsAdvancedFocus(widgetImport.definition))
const actualAdvancedFocusKinds = Object.entries(widgetImports)
.filter(([, widgetImport]) => supportsAdvancedFocus(widgetImport.definition))
.map(([kind]) => kind)
.toSorted();
expect(actualCompactOnlyKinds).toEqual([...compactOnlyWidgetKinds].toSorted());
expect(actualAdvancedFocusKinds).toEqual([...advancedFocusWidgetKinds].toSorted());
});
});
@@ -5,6 +5,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("archiveTeamWarrior", {
icon: IconArchive,
supportsAdvancedFocus: true,
maxIntegrations: 1,
createOptions() {
return optionsBuilder.from((factory) => ({
@@ -13,6 +13,7 @@ const hideUnlessAudiobookshelf = {
export const { definition, componentLoader } = createWidgetDefinition("audioStats", {
icon: IconHeadphones,
supportsAdvancedFocus: true,
queryKeys: [[["widget", "audioStats", "getStats"]], [["widget", "mediaServer", "getCurrentStreams"]]],
queryMatcher: matchesWidgetRuntimeQuery,
supportedIntegrations: ["navidrome", "audiobookshelf"],
+1
View File
@@ -9,6 +9,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("calendar", {
icon: IconCalendar,
supportsAdvancedFocus: true,
queryKey: [["widget", "calendar", "findAllEvents"]],
queryMatcher: matchesWidgetRuntimeQuery,
createOptions() {
+1
View File
@@ -6,6 +6,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("clock", {
icon: IconClock,
supportsAdvancedFocus: true,
queryKey: [["widget", "weather", "atLocation"]],
queryMatcher: ({ input }, scope) => {
const location = scope.options.weatherLocation;
+1
View File
@@ -25,6 +25,7 @@ const columnTranslationKeyMap = {
export const { definition, componentLoader } = createWidgetDefinition("dockerContainers", {
icon: IconBrandDocker,
supportsAdvancedFocus: true,
queryKey: [["docker", "getContainers"]],
refetchInterval: 30,
createOptions() {
+1
View File
@@ -42,6 +42,7 @@ const sortColumns = [
export const { definition, componentLoader } = createWidgetDefinition("downloads", {
icon: IconDownload,
supportsAdvancedFocus: true,
queryKey: [["widget", "downloads", "getJobsAndStatuses"]],
queryMatcher: ({ input }, scope) =>
widgetQueryInputMatches(input, {
+1
View File
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("firewall", {
icon: IconWall,
supportsAdvancedFocus: true,
refetchInterval: 5,
createOptions() {
return optionsBuilder.from(() => ({}));
@@ -51,6 +51,7 @@ const createOptions = () =>
export const { definition, componentLoader } = createWidgetDefinition("immich-albumCarousel", {
icon: IconPhoto,
supportsAdvancedFocus: true,
queryKey: [["widget", "immich", "getAlbum"]],
queryMatcher: ({ input }, scope) =>
widgetQueryInputMatches(input, {
@@ -5,6 +5,7 @@ import { optionsBuilder } from "../../options";
export const { definition, componentLoader } = createWidgetDefinition("immich-serverStats", {
icon: IconGraphFilled,
supportsAdvancedFocus: true,
queryKeys: [[["widget", "immich", "getServerStats"]], [["widget", "immich", "getAlbums"]]],
queryMatcher: matchesWidgetRuntimeQuery,
refetchInterval: null,
@@ -9,6 +9,7 @@ const pageSizeOptions = ["10", "20", "30", "50"] as const;
export const { componentLoader, definition } = createWidgetDefinition("mediaMissing", {
icon: IconMovie,
supportsAdvancedFocus: true,
queryKey: [["widget", "mediaOrganizer", "getData"]],
queryMatcher: matchesWidgetRuntimeQuery,
createOptions() {
@@ -5,6 +5,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("mediaReleases", {
icon: IconTicket,
supportsAdvancedFocus: true,
queryKey: [["widget", "mediaRelease"]],
refetchInterval: null,
createOptions() {
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../options";
export const { componentLoader, definition } = createWidgetDefinition("mediaServer", {
icon: IconVideo,
supportsAdvancedFocus: true,
queryKey: [["widget", "mediaServer", "getCurrentStreams"]],
queryMatcher: ({ input }, scope) =>
widgetQueryInputMatches(input, {
@@ -11,6 +11,7 @@ export const views = ["workers", "queue", "statistics"] as const;
export const { componentLoader, definition } = createWidgetDefinition("mediaTranscoding", {
icon: IconTransform,
supportsAdvancedFocus: true,
queryKey: [["widget", "mediaTranscoding", "getDataAsync"]],
queryMatcher: matchesWidgetRuntimeQuery,
maxIntegrations: 1,
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../../options";
export const { definition, componentLoader } = createWidgetDefinition("networkControllerStatus", {
icon: IconTopologyFull,
supportsAdvancedFocus: true,
queryKey: [["widget", "networkController"]],
refetchInterval: null,
createOptions() {
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../../options";
export const { definition, componentLoader } = createWidgetDefinition("smartHome-executeAutomation", {
icon: IconBinaryTree,
supportsAdvancedFocus: true,
maxIntegrations: 1,
createOptions() {
return optionsBuilder.from((factory) => ({
@@ -5,6 +5,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("speedtestTracker", {
icon: IconSpeedboat,
supportsAdvancedFocus: true,
refetchInterval: null,
createOptions() {
return optionsBuilder.from(
@@ -14,6 +14,7 @@ const labelDisplayModeOptions = {
export const { definition, componentLoader } = createWidgetDefinition("systemResources", {
icon: IconGraphFilled,
supportsAdvancedFocus: true,
queryKey: [["widget", "healthMonitoring"]],
refetchInterval: 5,
supportedIntegrations: ["dashDot", "openmediavault", "truenas", "unraid", "glances", "synology"],
+1
View File
@@ -9,6 +9,7 @@ import { optionsBuilder } from "../options";
export const { componentLoader, definition } = createWidgetDefinition("timetable", {
icon: IconBusStop,
supportsAdvancedFocus: true,
errors: {
BAD_REQUEST: {
icon: IconWorldOff,
+1
View File
@@ -5,6 +5,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("tracearr", {
icon: IconActivityHeartbeat,
supportsAdvancedFocus: true,
refetchInterval: 5,
createOptions() {
return optionsBuilder.from((factory) => ({
+1
View File
@@ -5,6 +5,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("traefik", {
icon: IconRoute,
supportsAdvancedFocus: true,
createOptions() {
return optionsBuilder.from((factory) => ({
showTcp: factory.switch({
+1
View File
@@ -8,6 +8,7 @@ export const timeFrameValues = ["today", "24h", "7d", "30d", "month", "lastMonth
export const { definition, componentLoader } = createWidgetDefinition("umami", {
icon: IconChartBar,
supportsAdvancedFocus: true,
queryKeys: [
[["widget", "umami", "getVisitorStats"]],
[["widget", "umami", "getActiveVisitors"]],
+1
View File
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("ups", {
icon: IconBatteryCharging,
supportsAdvancedFocus: true,
createOptions() {
return optionsBuilder.from((factory) => ({
showBattery: factory.switch({
@@ -5,6 +5,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("uptimeKuma", {
icon: IconHeartbeat,
supportsAdvancedFocus: true,
createOptions() {
return optionsBuilder.from((factory) => ({
showAverageUptime: factory.switch({
+1
View File
@@ -7,6 +7,7 @@ import { optionsBuilder } from "../options";
export const { definition, componentLoader } = createWidgetDefinition("weather", {
icon: IconCloud,
supportsAdvancedFocus: true,
queryKey: [["widget", "weather", "atLocation"]],
queryMatcher: ({ input }, scope) => {
const location = scope.options.location;