From 040d154744e80aced70d78682b86cd66bb5947e9 Mon Sep 17 00:00:00 2001 From: ajnart Date: Sun, 6 Sep 2026 19:14:35 +0200 Subject: [PATCH] feat: announce Homarr v2 beta --- .github/ISSUE_TEMPLATE/v2_beta_feedback.yml | 61 ++++ apps/docs/docs/getting-started/index.mdx | 6 + .../app/[locale]/boards/_layout-creator.tsx | 38 +- apps/nextjs/src/components/layout/header.tsx | 44 ++- apps/nextjs/src/components/layout/shell.tsx | 4 +- .../layout/v2-beta-announcement/constants.ts | 5 + .../v2-beta-announcement.module.css | 110 ++++++ .../v2-beta-announcement.tsx | 342 ++++++++++++++++++ .../v2-beta-dashboard-shell.tsx | 39 ++ packages/definitions/src/cookie.ts | 1 + 10 files changed, 618 insertions(+), 32 deletions(-) create mode 100644 .github/ISSUE_TEMPLATE/v2_beta_feedback.yml create mode 100644 apps/nextjs/src/components/layout/v2-beta-announcement/constants.ts create mode 100644 apps/nextjs/src/components/layout/v2-beta-announcement/v2-beta-announcement.module.css create mode 100644 apps/nextjs/src/components/layout/v2-beta-announcement/v2-beta-announcement.tsx create mode 100644 apps/nextjs/src/components/layout/v2-beta-announcement/v2-beta-dashboard-shell.tsx diff --git a/.github/ISSUE_TEMPLATE/v2_beta_feedback.yml b/.github/ISSUE_TEMPLATE/v2_beta_feedback.yml new file mode 100644 index 000000000..384c1a920 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/v2_beta_feedback.yml @@ -0,0 +1,61 @@ +name: Homarr v2 beta feedback +description: Tell us what works, what breaks, or what could be clearer in the Homarr v2 beta +title: "feedback(v2): " +labels: ["needs triage"] +body: + - type: markdown + attributes: + value: | + Thanks for testing Homarr v2. It will become the default when it is ready, and every report helps us get there. + + - type: dropdown + id: feedback-type + attributes: + label: Feedback type + options: + - Bug + - Upgrade or migration + - Usability or design + - Performance + - Other + validations: + required: true + + - type: input + id: version + attributes: + label: Homarr v2 version or image tag + description: You can find the version in the About page or provide the Docker image tag you tested. + placeholder: ghcr.io/homarr-labs/homarr-test:v2 + validations: + required: true + + - type: textarea + id: feedback + attributes: + label: What did you find? + description: Describe what happened, what you expected, or what you think could be improved. + validations: + required: true + + - type: textarea + id: reproduction + attributes: + label: Steps to reproduce + description: For bugs, list the shortest steps that consistently reproduce the problem. + + - type: textarea + id: additional-information + attributes: + label: Additional information + description: Add screenshots, logs, browser details, or anything else that may help. Remove secrets and personal information first. + + - type: checkboxes + id: checks + attributes: + label: Before submitting + options: + - label: I searched the existing issues for the same feedback. + required: true + - label: I removed passwords, API keys, tokens, and other private information. + required: true diff --git a/apps/docs/docs/getting-started/index.mdx b/apps/docs/docs/getting-started/index.mdx index 4dc986658..1a4722ff3 100644 --- a/apps/docs/docs/getting-started/index.mdx +++ b/apps/docs/docs/getting-started/index.mdx @@ -11,6 +11,12 @@ tags: import Link from '@docusaurus/Link'; import { IconArrowRight } from '@tabler/icons-react'; +:::info Homarr v2 beta is available + +There is no release date yet: Homarr v2 will become the default when it is ready. Read [what is new](https://v2.preview.homarr.dev/blog), try the [hosted preview](https://app-v2.preview.homarr.dev), [join us on Discord](https://discord.com/invite/aCsmEV5RgA), and [share your feedback](https://github.com/homarr-labs/homarr/issues/new?template=v2_beta_feedback.yml). Everyone uses Homarr differently, so every report helps us find what breaks or does not feel right before release. + +::: + Before you try to install Homarr, please review if your device meets the requirements. Installing on an unsupported device may lead to unstable behaviour and no support will be provided. diff --git a/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx b/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx index 24ebe3d03..897563072 100644 --- a/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/_layout-creator.tsx @@ -1,6 +1,8 @@ +import { createHash } from "node:crypto"; + import type { JSX, PropsWithChildren } from "react"; import { notFound, redirect } from "next/navigation"; -import { AppShellMain } from "@mantine/core"; +import { cookies } from "next/headers"; import { TRPCError } from "@trpc/server"; import { getRscUserSettingsAsync } from "@homarr/api/user-server"; @@ -8,10 +10,11 @@ import { auth } from "@homarr/auth/next"; import { BoardProvider } from "@homarr/boards/context"; import { EditModeProvider } from "@homarr/boards/edit-mode"; import { createLogger } from "@homarr/core/infrastructure/logs"; +import { v2BetaAnnouncementCookieKey } from "@homarr/definitions"; -import { MainHeader } from "~/components/layout/header"; +import { MainHeaderContent } from "~/components/layout/header"; import { BoardLogoWithTitle } from "~/components/layout/logo/board-logo"; -import { ClientShell } from "~/components/layout/shell"; +import { V2BetaDashboardShell } from "~/components/layout/v2-beta-announcement/v2-beta-dashboard-shell"; import { BoardTourGate } from "~/components/onboarding/board-tour-gate"; import { env } from "~/env"; import { getCurrentColorSchemeAsync } from "~/theme/color-scheme"; @@ -42,6 +45,7 @@ export const createBoardLayout = ({ }>) => { const resolvedParams = await params; const sessionPromise = auth(); + const cookiesPromise = cookies(); const initialBoardPromise = getInitialBoard(resolvedParams).then( (board) => ({ status: "fulfilled", board }) as const, (error: unknown) => ({ status: "rejected", error }) as const, @@ -58,11 +62,12 @@ export const createBoardLayout = ({ return false; } }); - const [session, initialBoardResult, colorScheme, shouldRunBoardTour] = await Promise.all([ + const [session, initialBoardResult, colorScheme, shouldRunBoardTour, cookieStore] = await Promise.all([ sessionPromise, initialBoardPromise, colorSchemePromise, shouldRunBoardTourPromise, + cookiesPromise, ]); if (initialBoardResult.status === "rejected") { const { error } = initialBoardResult; @@ -83,6 +88,10 @@ export const createBoardLayout = ({ throw error; } const initialBoard = initialBoardResult.board; + const viewerIdentity = session?.user.id ?? "anonymous"; + const viewerHash = createHash("sha256").update(viewerIdentity).digest("hex").slice(0, 16); + const dismissalCookieName = `${v2BetaAnnouncementCookieKey}.${viewerHash}`; + const isAnnouncementDismissed = cookieStore.get(dismissalCookieName)?.value === "dismissed"; return ( @@ -91,14 +100,19 @@ export const createBoardLayout = ({ - - } - actions={headerActions} - hasNavigation={false} - /> - {children} - + } + actions={headerActions} + hasNavigation={false} + /> + } + > + {children} + diff --git a/apps/nextjs/src/components/layout/header.tsx b/apps/nextjs/src/components/layout/header.tsx index 2c46608c0..a512229d0 100644 --- a/apps/nextjs/src/components/layout/header.tsx +++ b/apps/nextjs/src/components/layout/header.tsx @@ -19,25 +19,31 @@ interface Props { export const MainHeader = ({ logo, actions, hasNavigation = true }: Props) => { return ( - - - {hasNavigation && } - - {logo ?? } - - - - - - - {actions} - - - - - - - + ); }; + +export const MainHeaderContent = ({ logo, actions, hasNavigation = true }: Props) => ( + <> + + + {hasNavigation && } + + {logo ?? } + + + + + + + {actions} + + + + + + + + +); diff --git a/apps/nextjs/src/components/layout/shell.tsx b/apps/nextjs/src/components/layout/shell.tsx index b51cf3220..01ef8b7e0 100644 --- a/apps/nextjs/src/components/layout/shell.tsx +++ b/apps/nextjs/src/components/layout/shell.tsx @@ -10,11 +10,13 @@ import { navigationCollapsedAtom } from "./header/burger"; interface ClientShellProps { hasHeader?: boolean; hasNavigation?: boolean; + headerHeight?: number; } export const ClientShell = ({ hasHeader = true, hasNavigation = true, + headerHeight = 60, children, }: PropsWithChildren) => { const collapsed = useAtomValue(navigationCollapsedAtom); @@ -23,7 +25,7 @@ export const ClientShell = ({ return ( " }, + { kind: "context", value: " WORKSHOP_API_URL: https://v2.preview.homarr.dev/ # Only during the beta" }, + { kind: "context", value: "" }, + { kind: "context", value: " volumes:" }, + { kind: "context", value: " homarr-v2:" }, +] as const; + +const v2ComposeFile = `services: + homarr: + image: ghcr.io/homarr-labs/homarr-test:v2 + ports: + - "7575:7575" + volumes: + - homarr-v2:/appdata + environment: + SECRET_ENCRYPTION_KEY: + WORKSHOP_API_URL: https://v2.preview.homarr.dev/ # Only during the beta + +volumes: + homarr-v2: +`; + +interface V2BetaAnnouncementProps { + dismissalCookieName: string; + onDismiss: () => void; +} + +export const V2BetaAnnouncement = ({ dismissalCookieName, onDismiss }: V2BetaAnnouncementProps) => { + const { openModal } = useModalAction(V2BetaAnnouncementModal); + + const dismiss = () => { + setClientCookie(dismissalCookieName, "dismissed", { + maxAge: 60 * 60 * 24 * 365, + path: "/", + sameSite: "lax", + secure: window.location.protocol === "https:", + }); + onDismiss(); + }; + + return ( + + + + + + + + + Homarr v2 beta is here + + + Test it with us and help make v2 ready for everyone. + + + + + + + + + + + + + ); +}; + +const V2BetaAnnouncementModal = createModal(() => { + const { openModal: openInstallModal } = useModalAction(V2BetaInstallModal); + + return ( + + + + + Public beta + + Help us get Homarr v2 ready + + There is no release date yet. Homarr v2 will become the default when it is ready—and we need you to test it + and share feedback before we can be confident enough to make that switch. + + + + + + } title="Homarr Workshop"> + Share and discover Custom Widgets and CSS made by the Homarr community. + + } title="Complete drag-and-drop rewrite"> + Move, resize, multi-select, and arrange your dashboard with a board editor rebuilt from the ground up. + + } title="Custom Widgets v2"> + We think you can make almost any widget with live previews, API requests, options, actions, and validation. + + } title="Homarr Assistant"> + Ask questions, find what you need, and manage Homarr through permission-aware tools. + + + + + + And so much more + + Hundreds of quality-of-life improvements, including: + + + Brand-new onboarding + Visual board switcher + Keyboard shortcuts + Instance branding + Custom header buttons + Custom header order + + + + + } title="Your testing is essential"> + Everyone uses Homarr in a different way. We’re counting on you to tell us anything that breaks or doesn’t feel + right with this new version. Every report matters. + + + + + + + + + + + + + + Please use a separate volume for the beta and keep a backup of your current Homarr data. + + + + + ); +}).withOptions({ + defaultTitle: "Homarr v2 beta", + size: 1000, + centered: true, +}); + +const V2BetaInstallModal = createModal(() => ( + + + Run the beta with the separate homarr-v2 volume shown below. Keep a backup, and do not point this + prerelease image at the only copy of your production data. + + + + Update your Docker Compose file + + Replace the current image and add the beta-only Workshop URL. The complete example below can be copied as a + starting point. + + + + + + + + + docker-compose.yml + + + + {({ copied, copy }) => ( + + )} + + + + {composeDiffLines.map((line, index) => ( + + {line.value || " "} + + ))} + + + + + Replace <SECRET_KEY> with your existing encryption key. The Workshop URL is only required while + the beta uses the preview Workshop. + + +)).withOptions({ + defaultTitle: "Test Homarr v2 on your server", + size: 900, + centered: true, +}); + +interface FeatureCardProps { + icon: ReactNode; + title: string; + children: ReactNode; +} + +const FeatureCard = ({ icon, title, children }: FeatureCardProps) => ( + + + + {icon} + + + + {title} + + + {children} + + + + +); diff --git a/apps/nextjs/src/components/layout/v2-beta-announcement/v2-beta-dashboard-shell.tsx b/apps/nextjs/src/components/layout/v2-beta-announcement/v2-beta-dashboard-shell.tsx new file mode 100644 index 000000000..dd4ac079e --- /dev/null +++ b/apps/nextjs/src/components/layout/v2-beta-announcement/v2-beta-dashboard-shell.tsx @@ -0,0 +1,39 @@ +"use client"; + +import type { PropsWithChildren, ReactNode } from "react"; +import { useState } from "react"; +import { AppShellHeader, AppShellMain } from "@mantine/core"; + +import { ClientShell } from "../shell"; +import { V2BetaAnnouncement } from "./v2-beta-announcement"; + +interface V2BetaDashboardShellProps { + dismissalCookieName: string; + initiallyDismissed: boolean; + header: ReactNode; +} + +export const V2BetaDashboardShell = ({ + dismissalCookieName, + initiallyDismissed, + header, + children, +}: PropsWithChildren) => { + const [isAnnouncementVisible, setIsAnnouncementVisible] = useState(!initiallyDismissed); + const headerHeight = isAnnouncementVisible ? 112 : 60; + + return ( + + + {isAnnouncementVisible && ( + setIsAnnouncementVisible(false)} + /> + )} + {header} + + {children} + + ); +}; diff --git a/packages/definitions/src/cookie.ts b/packages/definitions/src/cookie.ts index 69cb24930..67ba5a36d 100644 --- a/packages/definitions/src/cookie.ts +++ b/packages/definitions/src/cookie.ts @@ -1,2 +1,3 @@ export const colorSchemeCookieKey = "homarr.color-scheme"; export const localeCookieKey = "homarr.locale"; +export const v2BetaAnnouncementCookieKey = "homarr.v2-beta-announcement";