Compare commits

..
Author SHA1 Message Date
Thomas Camlong 6d204c9aaa fix: remove auto-placement of widgets for all integrations on new boards
The seed pipeline and onboarding flow auto-placed a widget for every
integration that existed in the database. This is overly aggressive
and places obscure widgets (like ArchiveTeam Warrior) on every new
dashboard. Now only explicitly listed default widgets (clock, weather,
bookmarks) and apps are placed automatically.
2026-07-20 17:04:30 +02:00
Thomas Camlong 070e186b57 fix: stop auto-seeding ArchiveTeam Warrior integration and widget on default dashboards 2026-07-20 17:00:11 +02:00
7 changed files with 75 additions and 70 deletions

No files matched your search

+2 -2
View File
@@ -6,7 +6,7 @@
},
"engines": {
"node": ">=24.18.0",
"pnpm": ">=11.13.1"
"pnpm": ">=11.11.0"
},
"packageManager": "pnpm@11.13.1"
"packageManager": "pnpm@11.11.0"
}
+2 -2
View File
@@ -69,7 +69,7 @@
},
"engines": {
"node": ">=24.18.0",
"pnpm": ">=11.13.1"
"pnpm": ">=11.11.0"
},
"packageManager": "pnpm@11.13.1"
"packageManager": "pnpm@11.11.0"
}
@@ -271,7 +271,6 @@ export const onboardRouter = createTRPCRouter({
setupIntegrations: onboardingProcedure.requiresStep("integrations").mutation(async ({ ctx }) => {
const db = ctx.db;
const allIntegrations = await db.query.integrations.findMany();
const allApps = await db.query.apps.findMany();
const board = await db.query.boards.findFirst({
@@ -316,7 +315,6 @@ export const onboardRouter = createTRPCRouter({
layoutId: layout.id,
columnCount: layout.columnCount,
},
allIntegrations,
allApps,
);
}
-2
View File
@@ -698,13 +698,11 @@ const seedBoardWidgetsAsync = async (db: Database) => {
const layout = board.layouts[0];
if (!section || !layout) return;
const allIntegrations = await db.query.integrations.findMany();
const allApps = await db.query.apps.findMany();
const count = await placeAllWidgetsAsync(
db,
{ boardId: board.id, sectionId: section.id, layoutId: layout.id, columnCount: layout.columnCount },
allIntegrations,
allApps,
);
+8 -48
View File
@@ -1,19 +1,11 @@
import superjson from "superjson";
import { stringify } from "superjson";
import { createId } from "@homarr/common";
import {
defaultBookmarkApps,
defaultWidgetConfigs,
emptySuperJSON,
getWidgetKindsForIntegration,
} from "@homarr/definitions";
import { defaultBookmarkApps, defaultWidgetConfigs, emptySuperJSON } from "@homarr/definitions";
import type { WidgetKind } from "@homarr/definitions";
import type { Database } from "..";
import { integrationItems, itemLayouts, items } from "../schema";
import type { Integration } from "../schema";
const widgetConfigMap = new Map(defaultWidgetConfigs.map((config) => [config.kind, config]));
interface BoardTarget {
boardId: string;
@@ -77,12 +69,7 @@ interface App {
name: string;
}
export const placeAllWidgetsAsync = async (
db: Database,
target: BoardTarget,
allIntegrations: Integration[],
allApps: App[],
) => {
export const placeAllWidgetsAsync = async (db: Database, target: BoardTarget, allApps: App[]) => {
const ctx: PlacementContext = {
...target,
xOffset: 0,
@@ -92,38 +79,11 @@ export const placeAllWidgetsAsync = async (
const placedWidgets = new Set<WidgetKind>();
for (const integration of allIntegrations) {
for (const widgetKind of getWidgetKindsForIntegration(integration.kind)) {
if (placedWidgets.has(widgetKind)) continue;
const config = widgetConfigMap.get(widgetKind);
if (config?.skip) continue;
placedWidgets.add(widgetKind);
const matchingIds = allIntegrations
.filter((row) => getWidgetKindsForIntegration(row.kind).includes(widgetKind))
.map((row) => row.id);
const options = config?.options ? superjson.stringify(config.options) : undefined;
await placeWidgetAsync(
db,
ctx,
widgetKind,
matchingIds,
options,
config && { width: config.width, height: config.height },
);
}
}
for (const app of allApps) {
await placeWidgetAsync(
db,
ctx,
"app",
[],
superjson.stringify({ appId: app.id, openInNewTab: true, showTitle: true }),
{ width: 1, height: 1 },
);
await placeWidgetAsync(db, ctx, "app", [], stringify({ appId: app.id, openInNewTab: true, showTitle: true }), {
width: 1,
height: 1,
});
}
const bookmarkAppNames = new Set(defaultBookmarkApps.map((bookmark) => bookmark.name));
@@ -138,7 +98,7 @@ export const placeAllWidgetsAsync = async (
options = { ...options, items: bookmarkAppIds };
}
await placeWidgetAsync(db, ctx, config.kind, [], options ? superjson.stringify(options) : undefined, {
await placeWidgetAsync(db, ctx, config.kind, [], options ? stringify(options) : undefined, {
width: config.width,
height: config.height,
});
+56 -13
View File
@@ -1,4 +1,6 @@
import { Box, Container, Flex, HoverCard, Text, useMantineTheme } from "@mantine/core";
import { useCallback, useState } from "react";
import { Box, Container, Flex, Popover, Text, useMantineTheme } from "@mantine/core";
import { useElementSize } from "@mantine/hooks";
import { useRequiredBoard } from "@homarr/boards/context";
import type { CalendarEvent } from "@homarr/integrations/types";
@@ -11,9 +13,14 @@ interface CalendarDayProps {
disabled: boolean;
rootWidth: number;
rootHeight: number;
onOpen: (close: () => void) => void;
}
export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: CalendarDayProps) => {
export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth, onOpen }: CalendarDayProps) => {
const [opened, setOpened] = useState(false);
const [pinned, setPinned] = useState(false);
const { primaryColor } = useMantineTheme();
const { ref, height } = useElementSize();
const board = useRequiredBoard();
const mantineTheme = useMantineTheme();
const actualItemRadius = mantineTheme.radius[board.itemRadius];
@@ -22,19 +29,39 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: C
const shouldScaleDown = minAxisSize < 350;
const isSmall = rootHeight < 256;
const isTooSmallForIndicators = height < 30;
const handleMouseEnter = useCallback(() => {
if (disabled) return;
onOpen(() => {
setOpened(false);
setPinned(false);
});
setOpened(true);
}, [disabled, onOpen]);
const handleMouseLeave = useCallback(() => {
if (!pinned) setOpened(false);
}, [pinned]);
return (
<HoverCard
<Popover
position="bottom"
withArrow
withinPortal
radius="lg"
shadow="sm"
transitionProps={{ transition: "pop" }}
openDelay={350}
closeDelay={400}
transitionProps={{
transition: "pop",
}}
onChange={(value) => {
setOpened(value);
if (!value) setPinned(false);
}}
opened={opened}
disabled={disabled}
>
<HoverCard.Target>
<Popover.Target>
<Container
h="100%"
w="100%"
@@ -42,23 +69,38 @@ export const CalendarDay = ({ date, events, disabled, rootHeight, rootWidth }: C
pt={isSmall ? 0 : 10}
pb={isSmall ? 0 : 10}
m={0}
pos="relative"
ref={ref}
bd={`2px solid ${opened && !disabled ? primaryColor : "transparent"}`}
style={{
alignContent: "center",
borderRadius: actualItemRadius,
cursor: disabled ? "default" : "pointer",
}}
onClick={() => {
if (!disabled) setPinned((prev) => !prev);
}}
onMouseEnter={handleMouseEnter}
onMouseLeave={handleMouseLeave}
>
<Text ta={"center"} size={shouldScaleDown ? "xs" : "md"} lh={1}>
{date.getDate()}
</Text>
<NotificationIndicator events={events} isSmall={isSmall} />
{!isTooSmallForIndicators && <NotificationIndicator events={events} isSmall={isSmall} />}
</Container>
</HoverCard.Target>
<HoverCard.Dropdown maw="calc(100vw - 24px)" w={512} pe={4} pb={0} style={{ overflow: "hidden" }}>
</Popover.Target>
{/* Popover has some offset on the left side, padding is removed because of scrollarea paddings */}
<Popover.Dropdown
maw="calc(100vw - 24px)"
w={512}
pe={4}
pb={0}
style={{ overflow: "hidden" }}
onMouseEnter={() => setOpened(true)}
onMouseLeave={handleMouseLeave}
>
<CalendarEventList events={events} />
</HoverCard.Dropdown>
</HoverCard>
</Popover.Dropdown>
</Popover>
);
};
@@ -69,6 +111,7 @@ interface NotificationIndicatorProps {
const NotificationIndicator = ({ events, isSmall }: NotificationIndicatorProps) => {
const notificationEvents = [...new Set(events.map((event) => event.indicatorColor))].filter(String);
/* position bottom is lower when small to not be on top of number*/
return (
<Flex
w="75%"
+7 -1
View File
@@ -1,6 +1,6 @@
"use client";
import { useMemo, useState } from "react";
import { useCallback, useMemo, useRef, useState } from "react";
import { useParams } from "next/navigation";
import { useMantineTheme } from "@mantine/core";
import { Calendar } from "@mantine/dates";
@@ -65,6 +65,11 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar
const isSmall = width < 256;
const normalizedEvents = useMemo(() => splitEvents(events), [events]);
const activeCloseRef = useRef<(() => void) | null>(null);
const onDayOpen = useCallback((close: () => void) => {
activeCloseRef.current?.();
activeCloseRef.current = close;
}, []);
return (
<Calendar
@@ -141,6 +146,7 @@ const CalendarBase = ({ isEditMode, events, month, setMonth, options }: Calendar
disabled={isEditMode || eventsForDate.length === 0}
rootWidth={width}
rootHeight={height}
onOpen={onDayOpen}
/>
);
}}