Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
fc0367a
chore(deps): update dependency hono@<4.6.5 to >=4.12.18 (#5745)
homarr-renovate[bot] May 18, 2026
b0cdb81
chore(deps): update tiptap monorepo to v3.23.4 (#5743)
homarr-renovate[bot] May 18, 2026
507ac51
chore(deps): update dependency @playwright/test to ^1.60.0 (#5746)
homarr-renovate[bot] May 19, 2026
58d8005
chore(deps): update dependency tsdav to ^2.2.2 (#5748)
homarr-renovate[bot] May 19, 2026
97b7c41
chore(deps): update dependency isomorphic-dompurify to ^3.13.0 (#5750)
homarr-renovate[bot] May 19, 2026
f773b4a
wip
ajnart May 19, 2026
48d157b
Merge branch 'renovate/major-mantine-monorepo' into feat/app-tour
ajnart May 19, 2026
fa73763
fix: exclude .env files from Docker build context (#5738)
ajnart May 20, 2026
2190436
feat(db): add user onboarding tour completion columns
ajnart May 20, 2026
acd248c
feat(api): add onboarding tour status tRPC endpoints
ajnart May 20, 2026
8e6f31e
chore(ui): import mantine onboarding tour styles
ajnart May 20, 2026
97e9c32
feat(onboarding): add shared tour shell with keyboard navigation
ajnart May 20, 2026
1279168
feat(onboarding): add management tour with multi-page navigation
ajnart May 20, 2026
60bf867
feat(onboarding): add board tour on dashboard pages
ajnart May 20, 2026
3da1042
feat(i18n): add onboarding tour copy for manage and board flows
ajnart May 20, 2026
ff3933b
chore(deps): update dependency rollup@>=4.0.0 <4.22.4 to >=4.60.4 (#5…
homarr-renovate[bot] May 20, 2026
1220fbf
fix(onboarding): resolve tour shell and manage tour type errors
ajnart May 20, 2026
2711f3d
chore(deps): update dependency turbo to v2.9.14 [security] (#5759)
homarr-renovate[bot] May 20, 2026
62a0bae
Merge branch 'dev' into feat/app-tour
ajnart May 20, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion .dockerignore
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,7 @@ README.md
.git
dev
.build
e2e
e2e
.env
.env.*
!.env.example
1 change: 1 addition & 0 deletions apps/nextjs/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
"@dnd-kit/modifiers": "catalog:",
"@dnd-kit/sortable": "catalog:",
"@dnd-kit/utilities": "catalog:",
"@gfazioli/mantine-onboarding-tour": "catalog:",
"@homarr/analytics": "workspace:^0.1.0",
"@homarr/api": "workspace:^0.1.0",
"@homarr/auth": "workspace:^0.1.0",
Expand Down
13 changes: 2 additions & 11 deletions apps/nextjs/src/app/[locale]/boards/(content)/_client.tsx
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
"use client";

import { useCallback, useRef } from "react";
import { Box, LoadingOverlay, Stack } from "@mantine/core";
import { Box, Stack } from "@mantine/core";

import type { RouterOutputs } from "@homarr/api";
import { clientApi } from "@homarr/api/client";
Expand All @@ -10,8 +10,6 @@ import { useCurrentLayout, useRequiredBoard } from "@homarr/boards/context";
import { BoardCategorySection } from "~/components/board/sections/category-section";
import { BoardEmptySection } from "~/components/board/sections/empty-section";
import { BoardBackgroundVideo } from "~/components/layout/background";
import { fullHeightWithoutHeaderAndFooter } from "~/constants";
import { useIsBoardReady } from "./_ready-context";

let boardName: string | null = null;

Expand Down Expand Up @@ -44,7 +42,6 @@ export const useUpdateBoard = () => {
export const ClientBoard = () => {
const board = useRequiredBoard();
const currentLayoutId = useCurrentLayout();
const isReady = useIsBoardReady();

const fullWidthSortedSections = board.sections
.filter((section) => section.kind === "empty" || section.kind === "category")
Expand All @@ -55,13 +52,7 @@ export const ClientBoard = () => {
return (
<Box h="100%" pos="relative">
<BoardBackgroundVideo />
<LoadingOverlay
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Was this accidental?

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, this was another overlay that blocked the tour from appearing, so since I made the other PR that also removes it (it's not needed) then I figured I'll remove it in this one too

visible={!isReady}
transitionProps={{ duration: 500 }}
loaderProps={{ size: "lg" }}
h={fullHeightWithoutHeaderAndFooter}
/>
<Stack ref={ref} h="100%" style={{ visibility: isReady ? "visible" : "hidden" }}>
<Stack ref={ref} h="100%">
{fullWidthSortedSections.map((section) =>
section.kind === "empty" ? (
// Unique keys per layout to always reinitialize the gridstack
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@ export const createBoardContentPage = <TParams extends Record<string, unknown>>(
layout: createBoardLayout({
headerActions: <BoardContentHeaderActions />,
getInitialBoardAsync: getInitialBoard,
withTour: true,
}),
// eslint-disable-next-line no-restricted-syntax
page: async ({ params }: { params: Promise<TParams> }) => {
Expand Down
65 changes: 37 additions & 28 deletions apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@

import { useCallback, useEffect } from "react";
import { useRouter } from "next/navigation";
import { Group, Menu, ScrollArea } from "@mantine/core";
import { OnboardingTour } from "@gfazioli/mantine-onboarding-tour";
import { Box, Group, Menu, ScrollArea } from "@mantine/core";
import { useHotkeys } from "@mantine/hooks";
import {
IconBox,
Expand Down Expand Up @@ -53,9 +54,11 @@ export const BoardContentHeaderActions = () => {

<EditModeMenu />

<HeaderButton href={`/boards/${board.name}/settings`}>
<IconSettings stroke={1.5} />
</HeaderButton>
<OnboardingTour.Target id="board-settings">
<HeaderButton href={`/boards/${board.name}/settings`}>
<IconSettings stroke={1.5} />
</HeaderButton>
</OnboardingTour.Target>

<SelectBoardsMenu />
</>
Expand Down Expand Up @@ -173,37 +176,43 @@ const EditModeMenu = () => {
usePreventLeaveWithDirty(isEditMode);

return (
<HeaderButton onClick={toggle} loading={isPending}>
{isEditMode ? <IconPencilOff stroke={1.5} /> : <IconPencil stroke={1.5} />}
</HeaderButton>
<OnboardingTour.Target id="board-edit-mode">
<HeaderButton onClick={toggle} loading={isPending}>
{isEditMode ? <IconPencilOff stroke={1.5} /> : <IconPencil stroke={1.5} />}
</HeaderButton>
</OnboardingTour.Target>
);
};

const SelectBoardsMenu = () => {
const { data: boards = [] } = clientApi.board.getAllBoards.useQuery();

return (
<Menu position="bottom-end" withArrow>
<Menu.Target>
<HeaderButton w="auto" px={4}>
<IconReplace stroke={1.5} />
</HeaderButton>
</Menu.Target>
<Menu.Dropdown style={{ transform: "translate(-7px, 0)" }}>
<ScrollArea.Autosize mah={300}>
{boards.map((board) => (
<Menu.Item
key={board.id}
component={Link}
href={`/boards/${board.name}`}
leftSection={<IconLayoutBoard size={20} />}
>
{board.name}
</Menu.Item>
))}
</ScrollArea.Autosize>
</Menu.Dropdown>
</Menu>
<OnboardingTour.Target id="board-switcher">
<Box>
<Menu position="bottom-end" withArrow>
<Menu.Target>
<HeaderButton w="auto" px={4}>
<IconReplace stroke={1.5} />
</HeaderButton>
</Menu.Target>
<Menu.Dropdown style={{ transform: "translate(-7px, 0)" }}>
<ScrollArea.Autosize mah={300}>
{boards.map((board) => (
<Menu.Item
key={board.id}
component={Link}
href={`/boards/${board.name}`}
leftSection={<IconLayoutBoard size={20} />}
>
{board.name}
</Menu.Item>
))}
</ScrollArea.Autosize>
</Menu.Dropdown>
</Menu>
</Box>
</OnboardingTour.Target>
);
};

Expand Down
26 changes: 18 additions & 8 deletions apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { createLogger } from "@homarr/core/infrastructure/logs";
import { MainHeader } from "~/components/layout/header";
import { BoardLogoWithTitle } from "~/components/layout/logo/board-logo";
import { ClientShell } from "~/components/layout/shell";
import { BoardTourProvider } from "~/components/onboarding/board-tour";
import { getCurrentColorSchemeAsync } from "~/theme/color-scheme";
import type { Board } from "./_types";
import type { Params } from "./(content)/_creator";
Expand All @@ -20,14 +21,21 @@ import { BoardMantineProvider } from "./(content)/_theme";

const logger = createLogger({ module: "createBoardLayout" });

const BoardTourWrapper = ({ hasSession, children }: PropsWithChildren<{ hasSession: boolean }>) => {
if (!hasSession) return <>{children}</>;
return <BoardTourProvider>{children}</BoardTourProvider>;
};

interface CreateBoardLayoutProps<TParams extends Params> {
headerActions: JSX.Element;
getInitialBoardAsync: (params: TParams) => Promise<Board>;
withTour?: boolean;
}

export const createBoardLayout = <TParams extends Params>({
headerActions,
getInitialBoardAsync: getInitialBoard,
withTour = false,
}: CreateBoardLayoutProps<TParams>) => {
const Layout = async ({
params,
Expand Down Expand Up @@ -61,14 +69,16 @@ export const createBoardLayout = <TParams extends Params>({
<EditModeProvider>
<BoardMantineProvider defaultColorScheme={colorScheme}>
<CustomCss />
<ClientShell hasNavigation={false}>
<MainHeader
logo={<BoardLogoWithTitle size="md" hideTitleOnMobile />}
actions={headerActions}
hasNavigation={false}
/>
<AppShellMain>{children}</AppShellMain>
</ClientShell>
<BoardTourWrapper hasSession={withTour && !!session}>
<ClientShell hasNavigation={false}>
<MainHeader
logo={<BoardLogoWithTitle size="md" hideTitleOnMobile />}
actions={headerActions}
hasNavigation={false}
/>
<AppShellMain>{children}</AppShellMain>
</ClientShell>
</BoardTourWrapper>
</BoardMantineProvider>
</EditModeProvider>
</BoardReadyProvider>
Expand Down
1 change: 1 addition & 0 deletions apps/nextjs/src/app/[locale]/layout.tsx
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
import type { Metadata, Viewport } from "next";
import { Inter } from "next/font/google";

import "@gfazioli/mantine-onboarding-tour/styles.css";
import "@homarr/notifications/styles.css";
import "@homarr/spotlight/styles.css";
import "@homarr/ui/styles.css";
Expand Down
15 changes: 9 additions & 6 deletions apps/nextjs/src/app/[locale]/manage/apps/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import type { inferSearchParamsFromSchema } from "@homarr/common/types";
import { getI18n, getScopedI18n } from "@homarr/translation/server";
import { Link, SearchInput, TablePagination } from "@homarr/ui";

import { TourTarget } from "~/components/layout/header/tour-target";
import { ManageContainer } from "~/components/manage/manage-container";
import { MobileAffixButton } from "~/components/manage/mobile-affix-button";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
Expand Down Expand Up @@ -47,19 +48,21 @@ export default async function AppsPage(props: AppsPageProps) {
<Group justify="space-between" align="center">
<SearchInput placeholder={`${t("search")}...`} defaultValue={searchParams.search} flexExpand />
{session.user.permissions.includes("app-create") && (
<MobileAffixButton component={Link} href="/manage/apps/new">
{t("page.create.title")}
</MobileAffixButton>
<TourTarget id="manage-apps-create">
<MobileAffixButton component={Link} href="/manage/apps/new">
{t("page.create.title")}
</MobileAffixButton>
</TourTarget>
)}
</Group>
{apps.length === 0 && <AppNoResults />}
{apps.length > 0 && (
<TourTarget id="manage-apps-list">
<Stack gap="sm">
{apps.length === 0 && <AppNoResults />}
{apps.map((app) => (
<AppCard key={app.id} app={app} />
))}
</Stack>
)}
</TourTarget>

{/* Added margin to not hide pagination behind affix-button */}
<Group justify="end" mb={48}>
Expand Down
23 changes: 15 additions & 8 deletions apps/nextjs/src/app/[locale]/manage/boards/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ import { getScopedI18n } from "@homarr/translation/server";
import { Link, UserAvatar } from "@homarr/ui";

import { getBoardPermissionsAsync } from "~/components/board/permissions/server";
import { TourTarget } from "~/components/layout/header/tour-target";
import { ManageContainer } from "~/components/manage/manage-container";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { BoardCardMenuDropdown } from "./_components/board-card-menu-dropdown";
Expand All @@ -40,16 +41,22 @@ export default async function ManageBoardsPage() {
<Stack>
<Group justify="space-between">
<Title mb="md">{t("title")}</Title>
{canCreateBoards && <CreateBoardButton />}
{canCreateBoards && (
<TourTarget id="manage-boards-create">
<CreateBoardButton />
</TourTarget>
)}
</Group>

<Grid mb={{ base: "xl", md: 0 }}>
{boards.map((board) => (
<GridCol span={{ base: 12, md: 6 }} key={board.id}>
<BoardCard board={board} />
</GridCol>
))}
</Grid>
<TourTarget id="manage-boards-list">
<Grid mb={{ base: "xl", md: 0 }}>
{boards.map((board) => (
<GridCol span={{ base: 12, md: 6 }} key={board.id}>
<BoardCard board={board} />
</GridCol>
))}
</Grid>
</TourTarget>
</Stack>
</ManageContainer>
);
Expand Down
21 changes: 13 additions & 8 deletions apps/nextjs/src/app/[locale]/manage/integrations/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ import { getIntegrationName } from "@homarr/definitions";
import { getScopedI18n } from "@homarr/translation/server";
import { CountBadge, IntegrationAvatar, Link } from "@homarr/ui";

import { TourTarget } from "~/components/layout/header/tour-target";
import { ManageContainer } from "~/components/manage/manage-container";
import { DynamicBreadcrumb } from "~/components/navigation/dynamic-breadcrumb";
import { NoResults } from "~/components/no-results";
Expand Down Expand Up @@ -84,18 +85,22 @@ export default async function IntegrationsPage(props: IntegrationsPageProps) {
</IntegrationSelectMenu>
</Box>

<Box visibleFrom="md">
<IntegrationSelectMenu>
<MenuTarget>
<Button rightSection={<IconChevronDown size={16} stroke={1.5} />}>{t("action.create")}</Button>
</MenuTarget>
</IntegrationSelectMenu>
</Box>
<TourTarget id="manage-integrations-create">
<Box visibleFrom="md">
<IntegrationSelectMenu>
<MenuTarget>
<Button rightSection={<IconChevronDown size={16} stroke={1.5} />}>{t("action.create")}</Button>
</MenuTarget>
</IntegrationSelectMenu>
</Box>
</TourTarget>
</>
)}
</Group>

<IntegrationList integrations={integrations} activeTab={searchParams.tab} />
<TourTarget id="manage-integrations-list">
<IntegrationList integrations={integrations} activeTab={searchParams.tab} />
</TourTarget>
</Stack>
</ManageContainer>
);
Expand Down
Loading
Loading