From d9c503d01b4f11eab87fda232ea3acb22fd51c0e Mon Sep 17 00:00:00 2001 From: "homarr-releases[bot]" <175486441+homarr-releases[bot]@users.noreply.github.com> Date: Fri, 10 Jul 2026 20:17:43 +0000 Subject: [PATCH 01/54] chore: update bug report template (#6281) Co-authored-by: Releases Homarr <175486441+homarr-releases[bot]@users.noreply.github.com> --- .github/ISSUE_TEMPLATE/bug_report.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.yml b/.github/ISSUE_TEMPLATE/bug_report.yml index c15f08874..42f647d87 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.yml +++ b/.github/ISSUE_TEMPLATE/bug_report.yml @@ -33,6 +33,7 @@ body: options: # The below comment is used to insert a new version with on-release.yml #NEXT_VERSION# + - 1.70.0 - 1.69.2 - 1.69.0 - 1.68.0 From 34bd57435499f588f44fcbf91ca49af9719a7c9c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" Date: Fri, 10 Jul 2026 20:32:11 +0000 Subject: [PATCH 02/54] docs(helm): Update Helm chart documentation --- apps/docs/docs/getting-started/installation/helm.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/apps/docs/docs/getting-started/installation/helm.md b/apps/docs/docs/getting-started/installation/helm.md index 1f6aa6e9e..039f76ed4 100644 --- a/apps/docs/docs/getting-started/installation/helm.md +++ b/apps/docs/docs/getting-started/installation/helm.md @@ -2,9 +2,9 @@ homarr logo -![Version: 8.21.0](https://img.shields.io/badge/Version-8.21.0-informational?style=flat) +![Version: 8.22.0](https://img.shields.io/badge/Version-8.22.0-informational?style=flat) ![Type: application](https://img.shields.io/badge/Type-application-informational?style=flat) -![AppVersion: v1.69.2](https://img.shields.io/badge/AppVersion-v1.69.2-informational?style=flat) +![AppVersion: v1.70.0](https://img.shields.io/badge/AppVersion-v1.70.0-informational?style=flat) A Helm chart to deploy homarr for Kubernetes @@ -429,7 +429,7 @@ All available values are listed on the [artifacthub](https://artifacthub.io/pack | httproute.rules[0].matches[0].path.value | string | `"/"` | Path value to match | | image.pullPolicy | string | `"IfNotPresent"` | Image pull policy | | image.repository | string | `"ghcr.io/homarr-labs/homarr"` | Image repository | -| image.tag | string | `"v1.69.2"` | Overrides the image tag whose default is the chart appVersion | +| image.tag | string | `"v1.70.0"` | Overrides the image tag whose default is the chart appVersion | | imagePullSecrets | list | `[]` | Secrets for Docker registry | | ingress.annotations | object | `{}` | Ingress annotations | | ingress.enabled | bool | `false` | Enable ingress | From def3ede3bc07ad40f6d2b5f3c948d15da70184dc Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 11 Jul 2026 15:40:06 +0200 Subject: [PATCH 03/54] fix(db): restore widget_secret migration journal entries accidentally removed by #6206 (#6288) --- packages/db/migrations/mysql/meta/_journal.json | 7 +++++++ packages/db/migrations/postgresql/meta/_journal.json | 7 +++++++ packages/db/migrations/sqlite/meta/_journal.json | 7 +++++++ 3 files changed, 21 insertions(+) diff --git a/packages/db/migrations/mysql/meta/_journal.json b/packages/db/migrations/mysql/meta/_journal.json index b2665bb91..4ce2bf038 100644 --- a/packages/db/migrations/mysql/meta/_journal.json +++ b/packages/db/migrations/mysql/meta/_journal.json @@ -295,6 +295,13 @@ "when": 1780764500000, "tag": "0041_merge_custom_widget_url", "breakpoints": true + }, + { + "idx": 42, + "version": "5", + "when": 1783176100870, + "tag": "0042_add_widget_secret", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/migrations/postgresql/meta/_journal.json b/packages/db/migrations/postgresql/meta/_journal.json index 495b5e2fb..829168b9e 100644 --- a/packages/db/migrations/postgresql/meta/_journal.json +++ b/packages/db/migrations/postgresql/meta/_journal.json @@ -57,6 +57,13 @@ "when": 1780764500000, "tag": "0007_merge_custom_widget_url", "breakpoints": true + }, + { + "idx": 8, + "version": "7", + "when": 1783176100870, + "tag": "0008_add_widget_secret", + "breakpoints": true } ] } \ No newline at end of file diff --git a/packages/db/migrations/sqlite/meta/_journal.json b/packages/db/migrations/sqlite/meta/_journal.json index 85ee5fc47..a99a995ce 100644 --- a/packages/db/migrations/sqlite/meta/_journal.json +++ b/packages/db/migrations/sqlite/meta/_journal.json @@ -281,6 +281,13 @@ "when": 1780764500000, "tag": "0039_merge_custom_widget_url", "breakpoints": true + }, + { + "idx": 40, + "version": "6", + "when": 1783176100870, + "tag": "0040_add_widget_secret", + "breakpoints": true } ] } \ No newline at end of file From 576856d634626dfd9affc8abe59407d8e6ad65af Mon Sep 17 00:00:00 2001 From: Thomas Date: Sat, 11 Jul 2026 17:55:04 +0200 Subject: [PATCH 04/54] fix(beszel): add reliable live stats and filesystem UI (#6289) --- .../docs/widgets/beszel-system-grid/index.mdx | 4 +- .../widgets/beszel-system-stats/index.mdx | 38 +- .../widgets/beszel-system-table/index.mdx | 4 +- .../app/[locale]/_client-providers/trpc.tsx | 15 +- .../[locale]/boards/(content)/_creator.tsx | 3 +- .../boards/(content)/_header-actions.tsx | 7 +- .../board/items/widget-context-menu.tsx | 13 +- apps/nextjs/src/env.ts | 1 + packages/api/src/query-cache.ts | 2 +- packages/api/src/router/widgets/beszel.ts | 40 +- packages/api/src/test/query-cache.spec.ts | 2 + .../core/src/infrastructure/http/request.ts | 27 +- .../src/beszel/beszel-integration.ts | 273 ++++++++----- .../integrations/src/beszel/beszel-types.ts | 26 +- .../src/beszel/pocketbase-realtime.ts | 65 +++ .../beszel/test/beszel-integration.spec.ts | 119 +++++- .../beszel/test/pocketbase-realtime.spec.ts | 31 ++ packages/integrations/src/mock/data/beszel.ts | 1 + packages/request-handler/src/beszel.ts | 1 + packages/translation/src/lang/en.json | 7 +- .../src/beszel-system-grid/component.tsx | 58 +-- .../widgets/src/beszel-system-grid/index.ts | 1 + .../src/beszel-system-stats/component.tsx | 380 ++---------------- .../widgets/src/beszel-system-stats/index.ts | 2 +- .../src/beszel-system-table/component.tsx | 31 +- .../widgets/src/beszel-system-table/index.ts | 1 + .../widgets/src/beszel/_shared/chart.spec.ts | 87 ++++ packages/widgets/src/beszel/_shared/chart.tsx | 76 +++- .../widgets/src/beszel/_shared/disk-usage.tsx | 105 +++++ .../widgets/src/beszel/_shared/format.spec.ts | 21 + packages/widgets/src/beszel/_shared/format.ts | 10 +- .../widgets/src/beszel/_shared/stats-view.tsx | 339 ++++++++++++++++ .../src/beszel/_shared/system-stats-modal.tsx | 158 ++------ .../src/beszel/_shared/use-live-stats.ts | 11 +- packages/widgets/src/definition.ts | 3 +- 35 files changed, 1272 insertions(+), 690 deletions(-) create mode 100644 packages/integrations/src/beszel/pocketbase-realtime.ts create mode 100644 packages/integrations/src/beszel/test/pocketbase-realtime.spec.ts create mode 100644 packages/widgets/src/beszel/_shared/chart.spec.ts create mode 100644 packages/widgets/src/beszel/_shared/disk-usage.tsx create mode 100644 packages/widgets/src/beszel/_shared/format.spec.ts create mode 100644 packages/widgets/src/beszel/_shared/stats-view.tsx diff --git a/apps/docs/docs/widgets/beszel-system-grid/index.mdx b/apps/docs/docs/widgets/beszel-system-grid/index.mdx index de3578aeb..e3b17623b 100644 --- a/apps/docs/docs/widgets/beszel-system-grid/index.mdx +++ b/apps/docs/docs/widgets/beszel-system-grid/index.mdx @@ -18,7 +18,9 @@ import beszelSystemGrid from './img/beszel-system-grid.png'; categories={['System Monitoring']} /> -This widget displays all your Beszel-monitored systems as a responsive card grid. Each card shows real-time metrics like CPU, memory, disk, network, temperature, and more. Cards adapt their layout based on available space. +This widget displays all your Beszel-monitored systems as a responsive card grid. Each card shows real-time metrics like CPU, memory, disk, network, temperature, and more. Cards adapt their layout based on available space. If Beszel reports multiple filesystems, the disk bar exposes a hover breakdown for the root disk and each extra mount. + +Select a system card to open the complete statistics view, including system, filesystem, Docker, historical, and realtime charts. ### Screenshots diff --git a/apps/docs/docs/widgets/beszel-system-stats/index.mdx b/apps/docs/docs/widgets/beszel-system-stats/index.mdx index 8efa3fb46..1e9c896b9 100644 --- a/apps/docs/docs/widgets/beszel-system-stats/index.mdx +++ b/apps/docs/docs/widgets/beszel-system-stats/index.mdx @@ -1,24 +1,21 @@ --- -title: 'Beszel System Stats' -description: 'Time-series charts for CPU, memory, disk, network, and Docker container metrics from Beszel.' +title: "Beszel System Stats" +description: "Time-series charts for CPU, memory, disk, network, and Docker container metrics from Beszel." hide_title: true --- -import { WidgetHeader } from '@site/src/components/widgets/header'; -import { AddingWidget } from '@site/src/components/widgets/adding'; -import { WidgetConfig } from '@site/src/components/widgets/configuration'; -import { WidgetIntegrations } from '@site/src/components/widgets/integrations'; -import { beszelSystemStatsWidget } from '.'; -import { beszelIntegration } from '@site/docs/integrations/beszel'; +import { WidgetHeader } from "@site/src/components/widgets/header"; +import { AddingWidget } from "@site/src/components/widgets/adding"; +import { WidgetConfig } from "@site/src/components/widgets/configuration"; +import { WidgetIntegrations } from "@site/src/components/widgets/integrations"; +import { beszelSystemStatsWidget } from "."; +import { beszelIntegration } from "@site/docs/integrations/beszel"; -import beszelSystemStats from './img/beszel-system-stats.png'; +import beszelSystemStats from "./img/beszel-system-stats.png"; - + -This widget displays time-series charts for a selected Beszel system, including CPU, memory, disk, network, and Docker container metrics. Data is cached and refreshed periodically. You can select a time period ranging from 1 hour to 30 days. +This widget displays time-series charts for a selected Beszel system, including CPU, memory, disk, network, and Docker container metrics. The Disk Usage chart combines the root disk and every extra filesystem reported by Beszel. You can use the realtime Live view or select a historical period ranging from 1 hour to 30 days. If the live stream drops, the widget now shows the connection failure state immediately and can switch back to historical data. ### Screenshots @@ -26,14 +23,19 @@ This widget displays time-series charts for a selected Beszel system, including ### Supported Integrations - + ### Adding the widget ### Configuration + diff --git a/apps/docs/docs/widgets/beszel-system-table/index.mdx b/apps/docs/docs/widgets/beszel-system-table/index.mdx index dfab7a977..df68fcc1b 100644 --- a/apps/docs/docs/widgets/beszel-system-table/index.mdx +++ b/apps/docs/docs/widgets/beszel-system-table/index.mdx @@ -17,7 +17,9 @@ import { beszelIntegration } from '@site/docs/integrations/beszel'; categories={['System Monitoring']} /> -This widget displays all your Beszel-monitored systems in a sortable data table. Each row shows system metrics like CPU, memory, disk, network, temperature, and more. Columns can be toggled on or off, and the table supports sorting by any column. +This widget displays all your Beszel-monitored systems in a sortable data table. Each row shows system metrics like CPU, memory, disk, network, temperature, and more. Columns can be toggled on or off, and the table supports sorting by any column. When Beszel reports extra filesystems, the disk column adds a hover breakdown for each mount alongside the root disk. + +Select a system row to open the complete statistics view, including system, filesystem, Docker, historical, and realtime charts. ### Supported Integrations diff --git a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx index fbe310781..7d06a8951 100644 --- a/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx +++ b/apps/nextjs/src/app/[locale]/_client-providers/trpc.tsx @@ -10,6 +10,7 @@ import { createWSClient, httpBatchStreamLink, httpLink, + httpSubscriptionLink, isNonJsonSerializable, loggerLink, splitLink, @@ -101,9 +102,17 @@ export function TRPCReactProvider(props: PropsWithChildren) { }), splitLink({ condition: ({ type }) => type === "subscription", - true: wsLink({ - client: wsClient, - transformer: superjson, + true: splitLink({ + condition: ({ path }) => path === "widget.beszel.subscribeSystemStats", + true: httpSubscriptionLink({ + url: getTrpcUrl(), + transformer: superjson, + eventSourceOptions: { withCredentials: true }, + }), + false: wsLink({ + client: wsClient, + transformer: superjson, + }), }), false: splitLink({ condition: ({ input }) => isNonJsonSerializable(input), diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx index d67002432..16be5c478 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_creator.tsx @@ -22,6 +22,7 @@ import { getI18n } from "@homarr/translation/server"; import { prefetchForKindAsync } from "@homarr/widgets/prefetch"; import { createMetaTitle } from "~/metadata"; +import { env } from "~/env"; import { createBoardLayout } from "../_layout-creator"; import type { Board, Item } from "../_types"; import { DynamicClientBoard } from "./_dynamic-client"; @@ -40,7 +41,7 @@ export const createBoardContentPage = >( }: Props) => { return { layout: createBoardLayout({ - headerActions: , + headerActions: , getInitialBoardAsync: getInitialBoard, withTour: true, }), diff --git a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx index 454256366..30c91de40 100644 --- a/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx +++ b/apps/nextjs/src/app/[locale]/boards/(content)/_header-actions.tsx @@ -41,15 +41,12 @@ import { useDynamicSectionActions } from "~/components/board/sections/dynamic/dy import { IntegrationSelectModal } from "~/components/integration/integration-select-modal"; import { HeaderButton } from "~/components/layout/header/button"; -export const BoardContentHeaderActions = () => { +export const BoardContentHeaderActions = ({ demoReadOnly }: { demoReadOnly: boolean }) => { const [isEditMode] = useEditMode(); const board = useRequiredBoard(); const { hasChangeAccess } = useBoardPermissions(board); - // Fall back to read-only if the query has no data (e.g. errored) so we never expose - // edit/save/settings UI that would then fail server-side. - const { data: demoReadOnly = true, isLoading } = clientApi.info.isDemoReadOnly.useQuery(); - if (!hasChangeAccess || isLoading) { + if (!hasChangeAccess) { return ; } diff --git a/apps/nextjs/src/components/board/items/widget-context-menu.tsx b/apps/nextjs/src/components/board/items/widget-context-menu.tsx index c3f3469cb..ff225f642 100644 --- a/apps/nextjs/src/components/board/items/widget-context-menu.tsx +++ b/apps/nextjs/src/components/board/items/widget-context-menu.tsx @@ -4,7 +4,7 @@ import type { MutableRefObject, ReactNode } from "react"; import { useCallback, useEffect, useMemo, useState } from "react"; import { Group, Loader, Menu, Switch, Text } from "@mantine/core"; import { IconCopy, IconLayoutKanban, IconRefresh, IconSettings, IconTrash } from "@tabler/icons-react"; -import type { QueryClient } from "@tanstack/react-query"; +import type { QueryClient, QueryKey } from "@tanstack/react-query"; import { useIsFetching, useQueryClient } from "@tanstack/react-query"; import { clientApi } from "@homarr/api/client"; @@ -49,7 +49,10 @@ export const WidgetContextMenu = ({ item, widgetStateRef, children }: WidgetCont const { gridstack } = useSectionContext().refs; const queryClient = useQueryClient(); - const widgetQueryKey = useMemo(() => [["widget", item.kind]], [item.kind]); + const widgetQueryKey = useMemo( + () => ("queryKey" in currentDefinition ? (currentDefinition.queryKey as QueryKey) : [["widget", item.kind]]), + [currentDefinition, item.kind], + ); const isWidgetFetching = useIsFetching({ queryKey: widgetQueryKey }) > 0; const handleRefetch = useCallback(() => { void queryClient.refetchQueries({ queryKey: widgetQueryKey, type: "all" }); @@ -276,7 +279,7 @@ const WidgetCacheAge = ({ isFetching, }: { queryClient: QueryClient; - queryKey: unknown[]; + queryKey: QueryKey; isFetching: boolean; }) => { // 1s tick for "just now" → "1s ago" label transition; isFetching prop handles refetch reactivity @@ -295,8 +298,8 @@ const WidgetCacheAge = ({ .filter(Boolean); if (timestamps.length === 0) return null; - const oldest = Math.min(...timestamps); - const seconds = Math.floor((Date.now() - oldest) / 1000); + const latest = Math.max(...timestamps); + const seconds = Math.floor((Date.now() - latest) / 1000); if (seconds < 5) return "just now"; if (seconds < 60) return `${seconds}s ago`; return `${Math.floor(seconds / 60)}m ago`; diff --git a/apps/nextjs/src/env.ts b/apps/nextjs/src/env.ts index 96f03e7b7..d285a8393 100644 --- a/apps/nextjs/src/env.ts +++ b/apps/nextjs/src/env.ts @@ -4,6 +4,7 @@ export const env = createEnv({ server: { UNSAFE_ENABLE_MOCK_INTEGRATION: createBooleanSchema(false), DEMO_MODE: createBooleanSchema(false), + DEMO_READ_ONLY: createBooleanSchema(true), }, experimental__runtimeEnv: process.env, }); diff --git a/packages/api/src/query-cache.ts b/packages/api/src/query-cache.ts index 9890483e2..a8cdb9641 100644 --- a/packages/api/src/query-cache.ts +++ b/packages/api/src/query-cache.ts @@ -16,7 +16,7 @@ export const setActiveQueryCacheBoardId = (boardId: string | null) => { export const getActiveQueryCacheBoardId = () => activeQueryCacheBoardId; const persistableQueryPaths = new Set(["app.byId", "app.byIds", "docker.getContainers", "integration.byIds"]); -const excludedWidgetPaths = new Set(["widget.app.ping"]); +const excludedWidgetPaths = new Set(["widget.app.ping", "widget.beszel.getSystemStats"]); const getTrpcPathFromQueryKey = (queryKey: QueryKey) => { const first = queryKey[0]; diff --git a/packages/api/src/router/widgets/beszel.ts b/packages/api/src/router/widgets/beszel.ts index 66516b372..81fae10a0 100644 --- a/packages/api/src/router/widgets/beszel.ts +++ b/packages/api/src/router/widgets/beszel.ts @@ -207,6 +207,13 @@ export const beszelRouter = createTRPCRouter({ return observable((emit) => { const controller = new AbortController(); let isActive = true; + let emittedEventCount = 0; + + logger.debug("Beszel realtime subscription started", { + userId: ctx.session?.user?.id, + integrationIds: ctx.integrations.map((integration) => integration.id), + systemId: input.systemId, + }); void (async () => { const integration = ctx.integrations[0]; @@ -223,7 +230,19 @@ export const beszelRouter = createTRPCRouter({ await instance.subscribeRealtimeMetrics( input.systemId, (event) => { - if (isActive) emit.next(event); + if (!isActive) return; + emittedEventCount += 1; + if (emittedEventCount <= 2 || emittedEventCount % 60 === 0) { + logger.debug("Forwarding Beszel realtime events", { + userId: ctx.session?.user?.id, + integrationId: integration.id, + systemId: input.systemId, + eventType: event.type, + emittedEventCount, + statsCount: Array.isArray(event.record.stats) ? event.record.stats.length : undefined, + }); + } + emit.next(event); }, controller.signal, ); @@ -237,6 +256,13 @@ export const beszelRouter = createTRPCRouter({ } } catch (error) { if (isActive) { + logger.warn("Beszel realtime subscription failed", { + userId: ctx.session?.user?.id, + integrationId: integration.id, + systemId: input.systemId, + emittedEventCount, + error: errorMessage(error), + }); emit.error( error instanceof TRPCError ? error @@ -249,12 +275,24 @@ export const beszelRouter = createTRPCRouter({ } if (isActive) { + logger.debug("Beszel realtime subscription completed", { + userId: ctx.session?.user?.id, + integrationId: integration.id, + systemId: input.systemId, + emittedEventCount, + }); emit.complete(); } })(); return () => { isActive = false; + logger.debug("Beszel realtime subscription cancelled", { + userId: ctx.session?.user?.id, + integrationIds: ctx.integrations.map((integration) => integration.id), + systemId: input.systemId, + emittedEventCount, + }); controller.abort(); }; }); diff --git a/packages/api/src/test/query-cache.spec.ts b/packages/api/src/test/query-cache.spec.ts index 090544cb2..68d28eb15 100644 --- a/packages/api/src/test/query-cache.spec.ts +++ b/packages/api/src/test/query-cache.spec.ts @@ -67,6 +67,8 @@ describe("isPersistableWidgetQueryKey", () => { [[["docker", "getContainers"], { type: "query" }], true], [[["integration", "byIds"], { type: "query" }], true], [[["widget", "app", "ping"], { type: "query", input: { id: "abc" } }], false], + [[["widget", "beszel", "getSystemStats"], { type: "query" }], false], + [[["widget", "beszel", "getSystems"], { type: "query" }], true], [[["app", "selectable"], { type: "query" }], false], [[["board", "getBoardByName"], { type: "query" }], false], [["widget"], false], diff --git a/packages/core/src/infrastructure/http/request.ts b/packages/core/src/infrastructure/http/request.ts index 9bfc376fa..b13eb0cc2 100644 --- a/packages/core/src/infrastructure/http/request.ts +++ b/packages/core/src/infrastructure/http/request.ts @@ -2,7 +2,7 @@ import type { AgentOptions } from "node:https"; import { Agent as HttpsAgent } from "node:https"; import { checkServerIdentity } from "node:tls"; import axios from "axios"; -import type { RequestInfo, RequestInit, Response } from "undici"; +import type { Agent as UndiciAgent, RequestInfo, RequestInit, Response } from "undici"; import { fetch } from "undici"; import { @@ -29,11 +29,15 @@ export const createCustomCheckServerIdentity = ( }; }; -export const createCertificateAgentAsync = async (override?: { - ca: string | string[]; - checkServerIdentity: typeof checkServerIdentity; -}) => { +export const createCertificateAgentAsync = async ( + override?: { + ca: string | string[]; + checkServerIdentity: typeof checkServerIdentity; + }, + agentOptions?: Pick, +) => { return new UndiciHttpAgent({ + ...agentOptions, connect: override ?? { ca: await getAllTrustedCertificatesAsync(), checkServerIdentity: createCustomCheckServerIdentity(await getTrustedCertificateHostnamesAsync()), @@ -61,14 +65,18 @@ export const createAxiosCertificateInstanceAsync = async ( export const fetchWithTrustedCertificatesAsync = async ( url: RequestInfo, - options?: RequestInit & { timeout?: number }, + options?: RequestInit & { timeout?: number; bodyTimeout?: number }, ): Promise => { - const agent = await createCertificateAgentAsync(undefined); + const agent = await createCertificateAgentAsync( + undefined, + options?.bodyTimeout !== undefined ? { bodyTimeout: options.bodyTimeout } : undefined, + ); if (options?.timeout) { + const { bodyTimeout: _bodyTimeout, ...fetchOptions } = options; return await withTimeoutAsync( async (signal) => fetch(url, { - ...options, + ...fetchOptions, signal, dispatcher: agent, }), @@ -76,8 +84,9 @@ export const fetchWithTrustedCertificatesAsync = async ( ); } + const { bodyTimeout: _bodyTimeout, ...fetchOptions } = options ?? {}; return fetch(url, { - ...options, + ...fetchOptions, dispatcher: agent, }); }; diff --git a/packages/integrations/src/beszel/beszel-integration.ts b/packages/integrations/src/beszel/beszel-integration.ts index e9c4b35b2..f8c681da3 100644 --- a/packages/integrations/src/beszel/beszel-integration.ts +++ b/packages/integrations/src/beszel/beszel-integration.ts @@ -19,15 +19,66 @@ import type { BeszelSystemDetails, BeszelSystemdService, BeszelSystemStatsRecord, + BeszelSystemStats, + BeszelContainerStats, CreateAlertInput, LiveStatsEvent, PocketBaseListResponse, UpdateAlertInput, } from "./beszel-types"; +import { createRealtimeMetricsTopic, PocketBaseSseParser } from "./pocketbase-realtime"; const logger = createLogger({ module: "beszel-integration" }); const escapeFilterValue = (value: string) => value.replace(/\\/g, "\\\\").replace(/'/g, "\\'"); +const realtimeRetryDelaysMs = [200, 300, 500, 1_000, 1_500, 2_000] as const; + +const supportsRealtimeMetrics = (version: string) => { + const [major = 0, minor = 0] = version.replace(/^v/, "").split(".").map(Number); + return major > 0 || minor >= 13; +}; + +const waitForRetryAsync = async (delayMs: number, signal: AbortSignal) => + await new Promise((resolve) => { + if (signal.aborted) return resolve(); + const timeout = setTimeout(done, delayMs); + signal.addEventListener("abort", done, { once: true }); + function done() { + clearTimeout(timeout); + signal.removeEventListener("abort", done); + resolve(); + } + }); + +interface BeszelRealtimeSnapshot { + stats?: BeszelSystemStats; + container?: BeszelContainerStats[]; +} + +export const normalizeRealtimeSnapshot = ( + payload: BeszelRealtimeSnapshot, + systemId: string, + receivedAt = new Date(), +): LiveStatsEvent[] => { + const created = receivedAt.toISOString(); + const id = `realtime-${receivedAt.getTime()}`; + const events: LiveStatsEvent[] = []; + + if (payload.stats && typeof payload.stats === "object" && !Array.isArray(payload.stats)) { + events.push({ + type: "system_stats", + record: { id, system: systemId, stats: payload.stats, type: "1m", created, updated: created }, + }); + } + if (Array.isArray(payload.container)) { + events.push({ + type: "container_stats", + record: { id, system: systemId, stats: payload.container, type: "1m", created, updated: created }, + }); + } + + return events; +}; interface BeszelSession { token: string; @@ -285,123 +336,163 @@ export class BeszelIntegration extends Integration { }); } - /** - * Subscribe to real-time system and container stats via PocketBase SSE. - * Connects to the PocketBase Realtime API and parses incoming SSE events, - * filtering by systemId. This provides live-updating metrics (≤1s latency - * from agent) instead of polling the REST API every 5s. - */ + /** Subscribe to Beszel's custom one-second `rt_metrics` PocketBase topic. */ public async subscribeRealtimeMetrics( systemId: string, onMessage: (event: LiveStatsEvent) => void, signal: AbortSignal, ): Promise { - const session = await this.authenticateAsync(); - const realtimeUrl = this.url("/api/realtime"); - - logger.debug("Opening Beszel SSE connection for realtime metrics", { - integrationId: this.integration.id, - systemId, - url: realtimeUrl.pathname, - }); - - const response = await fetchWithTrustedCertificatesAsync(realtimeUrl, { - headers: { Authorization: session.token }, - signal, - }); - - if (!response.ok) { - logger.warn("Beszel SSE connection failed", { - integrationId: this.integration.id, - systemId, - status: response.status, - }); - throw new ResponseError(response); + const systems = await this.getSystemsAsync(); + const system = systems.find((candidate) => candidate.id === systemId); + if (!system) throw new Error("Selected Beszel system was not found"); + if (system.status !== "up") + throw new Error(`Beszel system is ${system.status}; Live mode requires an online system`); + if (!supportsRealtimeMetrics(system.info.v)) { + throw new Error(`Beszel agent ${system.info.v} does not support Live mode; version 0.13.0 or newer is required`); } - const reader = response.body?.getReader(); - if (!reader) throw new Error("No response body in SSE stream"); - - const decoder = new TextDecoder(); - let buffer = ""; - let subscribed = false; - - const processLine = async (line: string) => { - if (!line.startsWith("data: ")) return; + const realtimeUrl = this.url("/api/realtime"); + const topic = createRealtimeMetricsTopic(systemId); + let retryAttempt = 0; + let emittedSnapshotCount = 0; + while (!signal.aborted) { try { - const parsed = JSON.parse(line.slice(6)) as Record; - - // PB_CONNECT delivers the clientId — use it to POST our subscriptions - if (!subscribed && typeof parsed.clientId === "string") { - subscribed = true; - const subscribeResponse = await fetchWithTrustedCertificatesAsync(realtimeUrl, { - method: "POST", + let session = await this.authenticateAsync(); + const openStream = async () => + await fetchWithTrustedCertificatesAsync(realtimeUrl, { headers: { + Accept: "text/event-stream", Authorization: session.token, - "Content-Type": "application/json", + "Cache-Control": "no-cache", + Pragma: "no-cache", }, - body: JSON.stringify({ - clientId: parsed.clientId, - subscriptions: ["system_stats", "container_stats"], - }), signal, + bodyTimeout: 0, }); - if (!subscribeResponse.ok) { - logger.warn("Beszel SSE subscription POST failed", { + logger.debug("Opening Beszel rt_metrics SSE connection", { + integrationId: this.integration.id, + systemId, + retryAttempt, + }); + let response = await openStream(); + if (response.status === 401) { + await this.sessionStore.clearAsync(); + session = await this.authenticateAsync(); + response = await openStream(); + } + if (!response.ok) throw new ResponseError(response); + + const reader = response.body?.getReader(); + if (!reader) throw new Error("Beszel realtime response has no body"); + const decoder = new TextDecoder(); + const parser = new PocketBaseSseParser(); + let clientId: string | undefined; + + const processFrameAsync = async (frame: ReturnType[number]) => { + if (frame.event === "PB_CONNECT") { + let fallbackClientId: string | undefined; + try { + const connectData = JSON.parse(frame.data) as { clientId?: string }; + fallbackClientId = connectData.clientId; + } catch { + // PocketBase normally provides the client ID in the SSE id field. + } + clientId = frame.id || fallbackClientId; + if (!clientId) throw new Error("PocketBase PB_CONNECT event did not include a client ID"); + + const subscribeResponse = await fetchWithTrustedCertificatesAsync(realtimeUrl, { + method: "POST", + headers: { Authorization: session.token, "Content-Type": "application/json" }, + body: JSON.stringify({ clientId, subscriptions: [topic] }), + signal, + }); + if (subscribeResponse.status === 401) await this.sessionStore.clearAsync(); + if (!subscribeResponse.ok) throw new ResponseError(subscribeResponse); + logger.debug("Subscribed to Beszel rt_metrics topic", { integrationId: this.integration.id, systemId, - status: subscribeResponse.status, + clientId, }); - throw new ResponseError(subscribeResponse); + return; } - logger.debug("Beszel SSE subscribed to collections", { + if (!frame.event.startsWith("rt_metrics")) return; + try { + const snapshot = JSON.parse(frame.data) as BeszelRealtimeSnapshot; + const events = normalizeRealtimeSnapshot(snapshot, systemId); + if (events.length === 0) return; + emittedSnapshotCount += 1; + retryAttempt = 0; + for (const event of events) onMessage(event); + if (emittedSnapshotCount === 1 || emittedSnapshotCount % 30 === 0) { + logger.debug("Forwarded Beszel rt_metrics snapshots", { + integrationId: this.integration.id, + systemId, + emittedSnapshotCount, + containerCount: snapshot.container?.length ?? 0, + }); + } + } catch (error) { + logger.warn("Failed to parse Beszel rt_metrics payload", { + integrationId: this.integration.id, + systemId, + error: error instanceof Error ? error.message : String(error), + }); + } + }; + + logger.debug("Beszel rt_metrics SSE connection established", { + integrationId: this.integration.id, + systemId, + contentType: response.headers.get("content-type"), + }); + try { + while (!signal.aborted) { + const { done, value } = await reader.read(); + if (done) break; + for (const frame of parser.push(decoder.decode(value, { stream: true }))) { + await processFrameAsync(frame); + } + } + for (const frame of parser.push(decoder.decode())) await processFrameAsync(frame); + for (const frame of parser.finish()) await processFrameAsync(frame); + } finally { + await reader.cancel().catch(() => {}); + reader.releaseLock(); + } + + if (!signal.aborted) throw new Error("Beszel rt_metrics SSE stream ended unexpectedly"); + } catch (error) { + if (signal.aborted) break; + if (error instanceof ResponseError && error.statusCode === 401 && retryAttempt === 0) { + retryAttempt += 1; + logger.debug("Retrying Beszel rt_metrics handshake after reauthentication", { integrationId: this.integration.id, systemId, - clientId: parsed.clientId, }); - return; + continue; } - - const record = parsed.record as Record | undefined; - if (!record || record.system !== systemId) return; - - if (Array.isArray(record.stats)) { - onMessage({ - type: "container_stats", - record: record as unknown as BeszelContainerStatsRecord, - }); - } else if (record.stats && typeof record.stats === "object") { - onMessage({ - type: "system_stats", - record: record as unknown as BeszelSystemStatsRecord, - }); - } - } catch (error) { - if (error instanceof ResponseError) throw error; - // skip malformed SSE data lines + if (error instanceof ResponseError && error.statusCode < 500 && error.statusCode !== 429) throw error; + const delayMs = realtimeRetryDelaysMs[Math.min(retryAttempt, realtimeRetryDelaysMs.length - 1)] ?? 2_000; + retryAttempt += 1; + logger.warn("Beszel rt_metrics connection lost; retrying", { + integrationId: this.integration.id, + systemId, + retryAttempt, + delayMs, + error: error instanceof Error ? error.message : String(error), + }); + await waitForRetryAsync(delayMs, signal); } - }; - - try { - while (true) { - const { done, value } = await reader.read(); - if (done) break; - - buffer += decoder.decode(value, { stream: true }); - const lines = buffer.split("\n"); - buffer = lines.pop() ?? ""; - - for (const line of lines) { - await processLine(line); - } - } - } finally { - reader.releaseLock(); - logger.debug("Beszel SSE connection closed", { integrationId: this.integration.id, systemId }); } + + logger.debug("Beszel rt_metrics subscription stopped", { + integrationId: this.integration.id, + systemId, + emittedSnapshotCount, + }); } protected async testingAsync(input: IntegrationTestingInput): Promise { diff --git a/packages/integrations/src/beszel/beszel-types.ts b/packages/integrations/src/beszel/beszel-types.ts index 1abbc6564..e8504bb87 100644 --- a/packages/integrations/src/beszel/beszel-types.ts +++ b/packages/integrations/src/beszel/beszel-types.ts @@ -133,9 +133,9 @@ export interface BeszelSystemStats { /** disk usage (%) */ dp: number; /** disk read (MB/s) */ - dr: number; + dr?: number; /** disk write (MB/s) */ - dw: number; + dw?: number; /** disk read max (MB/s) */ drm?: number; /** disk write max (MB/s) */ @@ -145,9 +145,9 @@ export interface BeszelSystemStats { /** disk IOPS max [read, write] */ diom?: [number, number]; /** network sent — all interfaces (bytes/s) */ - ns: number; + ns?: number; /** network received — all interfaces (bytes/s) */ - nr: number; + nr?: number; /** bandwidth — public interfaces only (bytes/s) [sent, recv]. Prefer over ns/nr */ b?: [number, number]; /** network sent max (bytes/s) */ @@ -169,26 +169,26 @@ export interface BeszelSystemStats { } export interface BeszelExtraFsStats { - /** total disk (bytes) */ + /** total disk (GiB) */ d: number; - /** disk used (bytes) */ + /** disk used (GiB) */ du: number; /** read (bytes/s) */ r: number; /** write (bytes/s) */ w: number; /** read max (bytes/s) */ - rm: number; + rm?: number; /** write max (bytes/s) */ - wm: number; + wm?: number; /** read (IOPS) */ - rb: number; + rb?: number; /** write (IOPS) */ - wb: number; + wb?: number; /** read max (IOPS) */ - rbm: number; + rbm?: number; /** write max (IOPS) */ - wbm: number; + wbm?: number; } export interface BeszelGPUData { @@ -382,6 +382,8 @@ export interface BeszelSystemRow { memory: number; /** disk usage (%) */ disk: number; + /** extra filesystem usage (%) keyed by mount path */ + extraFilesystems: Record; /** GPU usage (%) */ gpu: number; /** load average [1m, 5m, 15m] */ diff --git a/packages/integrations/src/beszel/pocketbase-realtime.ts b/packages/integrations/src/beszel/pocketbase-realtime.ts new file mode 100644 index 000000000..e3330834b --- /dev/null +++ b/packages/integrations/src/beszel/pocketbase-realtime.ts @@ -0,0 +1,65 @@ +export interface PocketBaseSseFrame { + event: string; + id?: string; + data: string; +} + +export const createRealtimeMetricsTopic = (systemId: string) => + `rt_metrics?options=${encodeURIComponent(JSON.stringify({ query: { system: systemId } }))}`; + +export class PocketBaseSseParser { + private buffer = ""; + private event = "message"; + private id: string | undefined; + private dataLines: string[] = []; + + public push(chunk: string): PocketBaseSseFrame[] { + this.buffer += chunk; + const frames: PocketBaseSseFrame[] = []; + const lines = this.buffer.split(/\r?\n/); + this.buffer = lines.pop() ?? ""; + + for (const line of lines) { + const frame = this.processLine(line); + if (frame) frames.push(frame); + } + + return frames; + } + + public finish(): PocketBaseSseFrame[] { + const frames: PocketBaseSseFrame[] = []; + if (this.buffer !== "") { + const frame = this.processLine(this.buffer); + if (frame) frames.push(frame); + this.buffer = ""; + } + const trailingFrame = this.dispatch(); + if (trailingFrame) frames.push(trailingFrame); + return frames; + } + + private processLine(line: string): PocketBaseSseFrame | null { + if (line === "") return this.dispatch(); + if (line.startsWith(":")) return null; + + const colonIndex = line.indexOf(":"); + const field = colonIndex === -1 ? line : line.slice(0, colonIndex); + let value = colonIndex === -1 ? "" : line.slice(colonIndex + 1); + if (value.startsWith(" ")) value = value.slice(1); + + if (field === "event") this.event = value; + else if (field === "id") this.id = value; + else if (field === "data") this.dataLines.push(value); + return null; + } + + private dispatch(): PocketBaseSseFrame | null { + if (this.dataLines.length === 0 && this.event === "message" && this.id === undefined) return null; + const frame = { event: this.event, id: this.id, data: this.dataLines.join("\n") }; + this.event = "message"; + this.id = undefined; + this.dataLines = []; + return frame; + } +} diff --git a/packages/integrations/src/beszel/test/beszel-integration.spec.ts b/packages/integrations/src/beszel/test/beszel-integration.spec.ts index a68e1dbc8..22465fd19 100644 --- a/packages/integrations/src/beszel/test/beszel-integration.spec.ts +++ b/packages/integrations/src/beszel/test/beszel-integration.spec.ts @@ -40,7 +40,124 @@ vi.mock("@homarr/core/infrastructure/certificates", () => ({ getAllTrustedCertificatesAsync: vi.fn().mockResolvedValue([]), })); -import { BeszelIntegration } from "../beszel-integration"; +import { BeszelIntegration, normalizeRealtimeSnapshot } from "../beszel-integration"; +import type { LiveStatsEvent } from "../beszel-types"; +import { createRealtimeMetricsTopic } from "../pocketbase-realtime"; + +describe("normalizeRealtimeSnapshot", () => { + test("normalizes current Beszel host and container snapshots", () => { + const receivedAt = new Date("2026-07-11T13:51:30.217Z"); + const events = normalizeRealtimeSnapshot( + { + stats: { + cpu: 18.07, + m: 31.09, + mu: 16.53, + mp: 53.18, + mb: 14.32, + s: 4, + su: 3.9, + d: 938.8, + du: 455.81, + dp: 51.15, + efs: { sda: { d: 1906.8, du: 1656.45, r: 25.63, w: 0 } }, + }, + container: [{ n: "homarr", c: 1.65, m: 650.56, b: [356066, 19702867] }], + }, + "system-1", + receivedAt, + ); + + expect(events).toHaveLength(2); + expect(events[0]).toMatchObject({ + type: "system_stats", + record: { system: "system-1", created: receivedAt.toISOString(), stats: { cpu: 18.07 } }, + }); + expect(events[1]).toMatchObject({ + type: "container_stats", + record: { system: "system-1", stats: [{ n: "homarr", m: 650.56 }] }, + }); + }); + + test("ignores snapshots without metric data", () => { + expect(normalizeRealtimeSnapshot({}, "system-1")).toEqual([]); + }); +}); + +describe("subscribeRealtimeMetrics", () => { + test("subscribes to Beszel rt_metrics and forwards one-second snapshots", async () => { + const originalFetch = globalThis.fetch; + const topic = createRealtimeMetricsTopic("system-1"); + const subscriptionBodies: unknown[] = []; + const encoder = new TextEncoder(); + + globalThis.fetch = vi.fn(async (input: RequestInfo | URL, init?: RequestInit) => { + const url = String(input); + if (url.endsWith("/api/collections/users/auth-with-password")) { + return Response.json({ token: "test-token", record: { id: "user-1" } }); + } + if (url.includes("/api/collections/systems/records")) { + return Response.json({ + page: 1, + perPage: 500, + totalItems: 1, + totalPages: 1, + items: [ + { + id: "system-1", + name: "Server", + host: "localhost", + port: "45876", + status: "up", + info: { cpu: 1, mp: 2, dp: 3, u: 4, v: "0.18.7" }, + created: "2026-07-11T00:00:00.000Z", + updated: "2026-07-11T00:00:00.000Z", + }, + ], + }); + } + if (url.endsWith("/api/realtime") && init?.method === "POST") { + subscriptionBodies.push(JSON.parse(String(init.body))); + expect(new Headers(init.headers).get("Authorization")).toBe("test-token"); + return new Response(null, { status: 204 }); + } + if (url.endsWith("/api/realtime")) { + const snapshot = JSON.stringify({ + stats: { cpu: 5, m: 10, mu: 4, mp: 40, mb: 1, s: 0, su: 0, d: 20, du: 8, dp: 40 }, + container: [{ n: "homarr", c: 1, m: 256 }], + }); + const stream = new ReadableStream({ + start(controller) { + controller.enqueue(encoder.encode("event: PB_CONNECT\nid: client-1\ndata: {}\n\n")); + controller.enqueue(encoder.encode(`event: ${topic}\ndata: ${snapshot}\n\n`)); + controller.close(); + }, + }); + return new Response(stream, { headers: { "Content-Type": "text/event-stream" } }); + } + throw new Error(`Unexpected request: ${init?.method ?? "GET"} ${url}`); + }) as typeof fetch; + + try { + const integration = createBeszelIntegration(); + const controller = new AbortController(); + const events: LiveStatsEvent[] = []; + await integration.subscribeRealtimeMetrics( + "system-1", + (event) => { + events.push(event); + if (events.length === 2) controller.abort(); + }, + controller.signal, + ); + + expect(subscriptionBodies).toEqual([{ clientId: "client-1", subscriptions: [topic] }]); + expect(events.map((event) => event.type)).toEqual(["system_stats", "container_stats"]); + } finally { + globalThis.fetch = originalFetch; + } + }); +}); function createBeszelIntegration() { return new BeszelIntegration({ diff --git a/packages/integrations/src/beszel/test/pocketbase-realtime.spec.ts b/packages/integrations/src/beszel/test/pocketbase-realtime.spec.ts new file mode 100644 index 000000000..938278d95 --- /dev/null +++ b/packages/integrations/src/beszel/test/pocketbase-realtime.spec.ts @@ -0,0 +1,31 @@ +import { describe, expect, test } from "vitest"; + +import { createRealtimeMetricsTopic, PocketBaseSseParser } from "../pocketbase-realtime"; + +describe("PocketBaseSseParser", () => { + test("parses PB_CONNECT client IDs and CRLF-delimited frames split across chunks", () => { + const parser = new PocketBaseSseParser(); + expect(parser.push("event: PB_CONNECT\r\nid: client-")).toEqual([]); + expect(parser.push("123\r\ndata: {}\r\n\r\n")).toEqual([{ event: "PB_CONNECT", id: "client-123", data: "{}" }]); + }); + + test("joins multiline data and ignores keepalive comments", () => { + const parser = new PocketBaseSseParser(); + expect(parser.push(': keepalive\n\nevent: rt_metrics\ndata: {"stats":\ndata: {}}\n\n')).toEqual([ + { event: "rt_metrics", id: undefined, data: '{"stats":\n{}}' }, + ]); + }); + + test("flushes a trailing frame when the stream ends without a blank line", () => { + const parser = new PocketBaseSseParser(); + expect(parser.push("event: rt_metrics\ndata: {}")).toEqual([]); + expect(parser.finish()).toEqual([{ event: "rt_metrics", id: undefined, data: "{}" }]); + }); +}); + +describe("createRealtimeMetricsTopic", () => { + test("matches the PocketBase custom-topic options encoding used by Beszel", () => { + const topic = createRealtimeMetricsTopic("system-1"); + expect(topic).toBe(`rt_metrics?options=${encodeURIComponent(JSON.stringify({ query: { system: "system-1" } }))}`); + }); +}); diff --git a/packages/integrations/src/mock/data/beszel.ts b/packages/integrations/src/mock/data/beszel.ts index a83347553..f79de9d99 100644 --- a/packages/integrations/src/mock/data/beszel.ts +++ b/packages/integrations/src/mock/data/beszel.ts @@ -226,6 +226,7 @@ export class BeszelMockService { u: randInt(86400, 2_000_000), mp: clamp(sys.baseMem + rand(-5, 5), 0, 100), dp: clamp(sys.baseDisk + rand(-2, 2), 0, 100), + efs: i === 0 ? { "/mnt/sda": 86.9, "/mnt/sdb": 70.5, "/mnt/sdc": 42.7 } : undefined, bb: rand(200, 3000) * KB, v: "0.8.2", g: i === 3 ? rand(15, 45) : 0, diff --git a/packages/request-handler/src/beszel.ts b/packages/request-handler/src/beszel.ts index a6619e6ef..dab263e3a 100644 --- a/packages/request-handler/src/beszel.ts +++ b/packages/request-handler/src/beszel.ts @@ -34,6 +34,7 @@ function mapToSystemRow(system: BeszelSystem, details: BeszelSystemDetails | nul cpu: info.cpu, memory: info.mp, disk: info.dp, + extraFilesystems: info.efs ?? {}, gpu: info.g ?? 0, loadAvg: info.la ?? null, // bb = bytes/s (newer), b = Mbps (legacy, multiply to get bytes/s) diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index c314dd1ef..7a2c27b24 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -4031,9 +4031,6 @@ }, "showDockerNetwork": { "label": "Show Docker network chart" - }, - "showStorage": { - "label": "Show storage chart" } }, "selectSystem": "Select system", @@ -4060,8 +4057,8 @@ }, "disk": { "title": "Disk Usage", - "subtitle": "Usage of root partition", - "series": "Used" + "subtitle": "Usage by filesystem", + "series": "Root" }, "diskIO": { "title": "Disk I/O", diff --git a/packages/widgets/src/beszel-system-grid/component.tsx b/packages/widgets/src/beszel-system-grid/component.tsx index 5d4671f5f..51e5d014c 100644 --- a/packages/widgets/src/beszel-system-grid/component.tsx +++ b/packages/widgets/src/beszel-system-grid/component.tsx @@ -27,10 +27,18 @@ import classes from "./component.module.css"; import type { WidgetComponentProps } from "../definition"; import type { BeszelSystemRow } from "../beszel/_shared/types"; import { statusColorMap, thresholdColor } from "../beszel/_shared/colors"; -import { formatByteRate, formatLoadAvg, formatPercent, formatTemp, formatUptime } from "../beszel/_shared/format"; +import { + formatByteRate, + formatLoadAvg, + formatPercent, + formatTemp, + formatUptime, + getProgressTrackSize, +} from "../beszel/_shared/format"; import { useBeszelFilteredSystems } from "../beszel/_shared/hooks"; import { BeszelIntegrationErrorIndicator } from "../beszel/_shared/error-indicator"; import { BeszelSystemStatsModal } from "../beszel/_shared/system-stats-modal"; +import { DiskUsage } from "../beszel/_shared/disk-usage"; interface SizeConfig { iconSize: number; @@ -106,22 +114,14 @@ const getMaxVisibleMetrics = (cellHeight: number, size: SizeConfig): number => { return Math.max(1, Math.floor(available / size.rowHeight)); }; -const MIN_CELL_WIDTH = 120; +const MIN_CELL_WIDTH = 180; const MIN_CELL_HEIGHT = 80; -const getColCount = (width: number, itemCount: number): number => { +const getColCount = (width: number, height: number, itemCount: number): number => { if (itemCount <= 1) return 1; const maxCols = Math.min(itemCount, Math.max(1, Math.floor(width / MIN_CELL_WIDTH))); - - let best = 1; - for (let c = 1; c <= maxCols; c++) { - const emptyCells = (c - (itemCount % c)) % c; - const bestEmpty = (best - (itemCount % best)) % best; - if (emptyCells < bestEmpty || (emptyCells === bestEmpty && c > best)) { - best = c; - } - } - return best; + const aspectRatio = width / Math.max(height, MIN_CELL_HEIGHT); + return Math.min(maxCols, Math.max(1, Math.ceil(Math.sqrt(itemCount * aspectRatio)))); }; interface MetricRowProps { @@ -133,20 +133,25 @@ interface MetricRowProps { } const MetricValue = ({ value, progress, size }: Pick) => ( - - + + {value} {progress && ( - + )} ); const MetricRow = ({ icon, label, value, progress, size }: MetricRowProps) => ( - + {icon} - + {label} @@ -195,14 +200,13 @@ const metricRenderers = [ { key: "showDisk", render: (s: BeszelSystemRow, t: SystemCardProps["t"], sz: SizeConfig) => ( - } - label={t("metric.disk")} - value={formatPercent(s.disk)} - progress={{ value: s.disk, color: thresholdColor(s.disk) }} - size={sz} - /> + + + + {t("metric.disk")} + + + ), visible: (s: BeszelSystemRow, o: SystemCardProps["options"]) => o.showDisk, }, @@ -399,7 +403,7 @@ export default function BeszelSystemGridWidget({ ); } - const cols = getColCount(width, filteredSystems.length); + const cols = getColCount(width, height, filteredSystems.length); const rows = Math.ceil(filteredSystems.length / cols) || 1; const rawCellHeight = height / rows; const scrollEnabled = rawCellHeight < MIN_CELL_HEIGHT; diff --git a/packages/widgets/src/beszel-system-grid/index.ts b/packages/widgets/src/beszel-system-grid/index.ts index 9551adfc6..69dc7e2ba 100644 --- a/packages/widgets/src/beszel-system-grid/index.ts +++ b/packages/widgets/src/beszel-system-grid/index.ts @@ -6,6 +6,7 @@ import { optionsBuilder } from "../options"; export const { definition, componentLoader } = createWidgetDefinition("beszelSystemGrid", { icon: IconLayoutGrid, + queryKey: [["widget", "beszel"]], supportedIntegrations: ["beszel", "mock"], integrationsRequired: true, createOptions() { diff --git a/packages/widgets/src/beszel-system-stats/component.tsx b/packages/widgets/src/beszel-system-stats/component.tsx index 21b21981f..91dcd7292 100644 --- a/packages/widgets/src/beszel-system-stats/component.tsx +++ b/packages/widgets/src/beszel-system-stats/component.tsx @@ -1,54 +1,21 @@ "use client"; import { useCallback, useMemo } from "react"; -import { Button, Box, Center, Loader, Menu, ScrollArea, Select, SimpleGrid, Stack, Text } from "@mantine/core"; -import { IconPlugConnectedX, IconServer, IconQuestionMark, IconServerOff } from "@tabler/icons-react"; +import { Box, Button, Center, Loader, Menu, ScrollArea, Select, Stack, Text } from "@mantine/core"; +import { IconQuestionMark, IconServer, IconServerOff } from "@tabler/icons-react"; import { clientApi } from "@homarr/api/client"; import { useSession } from "@homarr/auth/client"; import { constructBoardPermissions } from "@homarr/auth/shared"; import { useOptionalBoard } from "@homarr/boards/context"; -import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types"; import { useScopedI18n } from "@homarr/translation/client"; import classes from "./component.module.css"; import type { WidgetComponentProps } from "../definition"; -import { containerColors } from "../beszel/_shared/colors"; import type { BeszelTimePeriod } from "../beszel/_shared/chart"; -import { - BeszelChartPanel, - CPU_Y_AXIS_DOMAIN, - useContainerNames, - useDockerChartData, - useEfsChartData, - useSystemChartData, -} from "../beszel/_shared/chart"; -import { - chartAxisFormatters, - formatByteRate, - formatGB, - formatPercent, - formatStorageBytes, -} from "../beszel/_shared/format"; import { BeszelIntegrationErrorIndicator } from "../beszel/_shared/error-indicator"; -import { makeTooltipProps } from "../beszel/_shared/tooltip"; -import { useLiveStats } from "../beszel/_shared/use-live-stats"; - -const tooltipPercent = makeTooltipProps(formatPercent); -const tooltipGB = makeTooltipProps(formatGB, true); -const tooltipRate = makeTooltipProps(formatByteRate, true); -const tooltipPercentTotal = makeTooltipProps(formatPercent, true); -const tooltipBytesTotal = makeTooltipProps(formatStorageBytes, true); - -const CHART_HEIGHT = 180; -const MB = 1024 * 1024; -const gridColumns = [1, 2] as const; - -function statsWhenShown(show: boolean | undefined, data: T | undefined): T | undefined { - if (!show || !data) return undefined; - return data; -} +import { BeszelStatsView } from "../beszel/_shared/stats-view"; export default function BeszelSystemStatsWidget({ options, @@ -71,14 +38,12 @@ export default function BeszelSystemStatsWidget({ } = clientApi.widget.beszel.getSystems.useQuery({ integrationIds }); const systems = useMemo( - () => systemsResult.flatMap((r) => r.systems.map((s) => ({ value: s.id, label: s.name }))), + () => systemsResult.flatMap((result) => result.systems.map((system) => ({ value: system.id, label: system.name }))), [systemsResult], ); - const selectedSystem = options.systemId || systems[0]?.value || ""; - const selectedLabel = systems.find((s) => s.value === selectedSystem)?.label; - const systemExists = systems.some((s) => s.value === selectedSystem); - const systemReady = !systemsPending && (selectedSystem === "" || systemExists); + const selectedLabel = systems.find((system) => system.value === selectedSystem)?.label; + const systemExists = systems.some((system) => system.value === selectedSystem); const handleSelectSystem = useCallback( (value: string) => { @@ -90,139 +55,13 @@ export default function BeszelSystemStatsWidget({ [setOptions, hasChangeAccess, boardId, itemId, saveItemOptions], ); - const showDocker = options.showDockerCpu || options.showDockerMemory || options.showDockerNetwork; - const isLive = options.timePeriod === "1m"; - - const { data: statsResult, error: statsError } = clientApi.widget.beszel.getSystemStats.useQuery( - { - integrationIds, - systemId: selectedSystem, - timePeriod: options.timePeriod as "1m" | "1h" | "12h" | "24h" | "1w" | "30d", - includeDocker: showDocker, - }, - { - refetchInterval: isLive ? false : 5_000, - enabled: !isLive && systemReady && selectedSystem !== "", - }, - ); - - const { data: liveData, error: liveError } = useLiveStats(integrationIds, selectedSystem, isLive && systemReady); - - let activeStats: - | { systemStats: BeszelSystemStatsRecord[]; containerStats: BeszelContainerStatsRecord[]; error?: string } - | null - | undefined = statsResult; - if (isLive) { - activeStats = liveData; - } - - const mappers = useMemo( - () => ({ - cpu: (s: { cpu: number }) => ({ [t("chart.cpu.series")]: s.cpu }), - memory: (s: { mu: number; mb: number }) => ({ - [t("chart.memory.series")]: s.mu, - [t("chart.memory.cache")]: s.mb ?? 0, - }), - disk: (s: { du: number }) => ({ [t("chart.disk.series")]: s.du }), - diskIO: (s: { dr?: number; dw?: number }) => ({ - [t("chart.diskIO.read")]: (s.dr ?? 0) * MB, - [t("chart.diskIO.write")]: (s.dw ?? 0) * MB, - }), - network: (s: { ns: number; nr: number; b?: [number, number] }) => ({ - [t("chart.network.sent")]: s.b?.[0] ?? s.ns, - [t("chart.network.recv")]: s.b?.[1] ?? s.nr, - }), - }), - [t], - ); - - const series = useMemo( - () => ({ - cpu: [{ name: t("chart.cpu.series"), color: "teal.6" }], - memory: [ - { name: t("chart.memory.series"), color: "teal.5" }, - { name: t("chart.memory.cache"), color: "teal.8" }, - ], - disk: [{ name: t("chart.disk.series"), color: "grape.6" }], - diskIO: [ - { name: t("chart.diskIO.write"), color: "orange.6" }, - { name: t("chart.diskIO.read"), color: "blue.6" }, - ], - network: [ - { name: t("chart.network.sent"), color: "blue.6" }, - { name: t("chart.network.recv"), color: "teal.6" }, - ], - }), - [t], - ); - - const systemStats = activeStats?.systemStats; - const containerStatsRaw = activeStats?.containerStats; - - const timePeriod = options.timePeriod as BeszelTimePeriod; - const cpuData = useSystemChartData(statsWhenShown(options.showCpu, systemStats), mappers.cpu, timePeriod); - const memoryData = useSystemChartData(statsWhenShown(options.showMemory, systemStats), mappers.memory, timePeriod); - const diskData = useSystemChartData(statsWhenShown(options.showDisk, systemStats), mappers.disk, timePeriod); - const diskIOData = useSystemChartData(statsWhenShown(options.showDiskIO, systemStats), mappers.diskIO, timePeriod); - const networkData = useSystemChartData(statsWhenShown(options.showNetwork, systemStats), mappers.network, timePeriod); - - const containerNames = useContainerNames(statsWhenShown(showDocker, containerStatsRaw)); - const dockerCpuData = useDockerChartData( - statsWhenShown(options.showDockerCpu, containerStatsRaw), - containerNames, - "cpu", - timePeriod, - ); - const dockerMemData = useDockerChartData( - statsWhenShown(options.showDockerMemory, containerStatsRaw), - containerNames, - "memory", - timePeriod, - ); - const dockerNetData = useDockerChartData( - statsWhenShown(options.showDockerNetwork, containerStatsRaw), - containerNames, - "network", - timePeriod, - ); - - const efsPaths = useMemo(() => { - if (!systemStats) return []; - const paths = new Set(); - for (const record of systemStats) { - if (record.stats.efs) { - for (const path of Object.keys(record.stats.efs)) { - paths.add(path); - } - } - } - return [...paths]; - }, [systemStats]); - - const storageData = useEfsChartData(statsWhenShown(options.showStorage, systemStats), efsPaths, timePeriod); - - const containerSeries = useMemo( - () => containerNames.map((name, i) => ({ name, color: containerColors[i % containerColors.length] as string })), - [containerNames], - ); - - const storageSeries = useMemo( - () => efsPaths.map((path, i) => ({ name: path, color: containerColors[i % containerColors.length] as string })), - [efsPaths], - ); - - const cols = gridColumns[Number(width > 600)]; - if (systemsError) throw systemsError; - if (!isLive && statsError) throw statsError; - - if (systemsPending) { + if (systemsPending) return (
); - } if (systems.length === 0) { return ( @@ -240,7 +79,7 @@ export default function BeszelSystemStatsWidget({ ); } - if (!systemsPending && !systemExists && systems.length > 0) { + if (!systemExists) { return (
@@ -260,22 +99,6 @@ export default function BeszelSystemStatsWidget({ ); } - if (isLive && liveError && !liveData) { - return ( -
- - - - {t("error.liveConnectionFailed")} - - - -
- ); - } - return ( @@ -284,7 +107,7 @@ export default function BeszelSystemStatsWidget({ {!isEditMode && systems.length > 1 && ( @@ -300,176 +123,37 @@ export default function BeszelSystemStatsWidget({ - {systems.map((s) => ( + {systems.map((system) => ( handleSelectSystem(s.value)} + fw={system.value === selectedSystem ? 600 : 400} + c={system.value !== selectedSystem ? "dimmed" : undefined} + onClick={() => handleSelectSystem(system.value)} > - {s.label} + {system.label} ))} )} - - {!isLive && !activeStats && systemReady && selectedSystem !== "" && ( -
- -
- )} - - {!isLive && activeStats && "error" in activeStats && activeStats.error && ( -
- - - - {activeStats.error} - - -
- )} - - {activeStats && (!("error" in activeStats) || !activeStats.error) && ( - - {options.showCpu && cpuData.length > 0 && ( - - )} - - {options.showMemory && memoryData.length > 0 && ( - - )} - - {options.showDisk && diskData.length > 0 && ( - - )} - - {options.showDiskIO && diskIOData.length > 0 && ( - - )} - - {options.showNetwork && networkData.length > 0 && ( - - )} - - {options.showStorage && storageData.length > 0 && storageSeries.length > 0 && ( - - )} - - {showDocker && containerSeries.length > 0 && ( - <> - {options.showDockerCpu && dockerCpuData.length > 0 && ( - - )} - - {options.showDockerMemory && dockerMemData.length > 0 && ( - - )} - - {options.showDockerNetwork && dockerNetData.length > 0 && ( - - )} - - )} - - )} + 600 ? 2 : 1} + visibility={{ + cpu: options.showCpu, + memory: options.showMemory, + disk: options.showDisk, + diskIO: options.showDiskIO, + network: options.showNetwork, + dockerCpu: options.showDockerCpu, + dockerMemory: options.showDockerMemory, + dockerNetwork: options.showDockerNetwork, + }} + onSwitchToHistorical={() => setOptions({ newOptions: { timePeriod: "1h" } })} + />
diff --git a/packages/widgets/src/beszel-system-stats/index.ts b/packages/widgets/src/beszel-system-stats/index.ts index 3e48a1e4f..af2907b25 100644 --- a/packages/widgets/src/beszel-system-stats/index.ts +++ b/packages/widgets/src/beszel-system-stats/index.ts @@ -16,6 +16,7 @@ const timePeriodOptions = [ export const { definition, componentLoader } = createWidgetDefinition("beszelSystemStats", { icon: IconChartAreaLine, + queryKey: [["widget", "beszel"]], supportedIntegrations: ["beszel", "mock"], integrationsRequired: true, createOptions() { @@ -45,7 +46,6 @@ export const { definition, componentLoader } = createWidgetDefinition("beszelSys showDockerCpu: factory.switch({ defaultValue: true }), showDockerMemory: factory.switch({ defaultValue: true }), showDockerNetwork: factory.switch({ defaultValue: true }), - showStorage: factory.switch({ defaultValue: true }), })); }, errors: { diff --git a/packages/widgets/src/beszel-system-table/component.tsx b/packages/widgets/src/beszel-system-table/component.tsx index 49b3dca74..5352eb170 100644 --- a/packages/widgets/src/beszel-system-table/component.tsx +++ b/packages/widgets/src/beszel-system-table/component.tsx @@ -26,10 +26,18 @@ import { useScopedI18n } from "@homarr/translation/client"; import type { WidgetComponentProps } from "../definition"; import type { BeszelSystemRow } from "../beszel/_shared/types"; import { loadAvgColor, statusColorMap, thresholdColor } from "../beszel/_shared/colors"; -import { formatByteRate, formatLoadAvg, formatPercent, formatTemp, formatUptime } from "../beszel/_shared/format"; +import { + formatByteRate, + formatLoadAvg, + formatPercent, + formatTemp, + formatUptime, + getProgressTrackSize, +} from "../beszel/_shared/format"; import { useBeszelFilteredSystems } from "../beszel/_shared/hooks"; import { BeszelIntegrationErrorIndicator } from "../beszel/_shared/error-indicator"; import { BeszelSystemStatsModal } from "../beszel/_shared/system-stats-modal"; +import { DiskUsage } from "../beszel/_shared/disk-usage"; const directionMultiplier: Record = { asc: 1, desc: -1 }; @@ -97,11 +105,16 @@ export default function BeszelSystemTableWidget({ }, [filteredSystems, sortStatus]); const PercentCell = ({ value }: { value: number }) => ( - - + + {formatPercent(value)} - + ); @@ -156,7 +169,15 @@ export default function BeszelSystemTableWidget({ ), sortable: true, - render: (record) => , + render: (record) => ( + + ), }, options.showGpu && { accessor: "gpu", diff --git a/packages/widgets/src/beszel-system-table/index.ts b/packages/widgets/src/beszel-system-table/index.ts index 538649afd..ba90cb5e9 100644 --- a/packages/widgets/src/beszel-system-table/index.ts +++ b/packages/widgets/src/beszel-system-table/index.ts @@ -25,6 +25,7 @@ const sortDirectionOptions = [ export const { definition, componentLoader } = createWidgetDefinition("beszelSystemTable", { icon: IconTable, + queryKey: [["widget", "beszel"]], supportedIntegrations: ["beszel", "mock"], integrationsRequired: true, createOptions() { diff --git a/packages/widgets/src/beszel/_shared/chart.spec.ts b/packages/widgets/src/beszel/_shared/chart.spec.ts new file mode 100644 index 000000000..5a86bf2bb --- /dev/null +++ b/packages/widgets/src/beszel/_shared/chart.spec.ts @@ -0,0 +1,87 @@ +import { describe, expect, test } from "vitest"; + +import type { BeszelSystemStatsRecord } from "@homarr/integrations/types"; + +import { buildDiskChartData, padLiveTimeGrid } from "./chart"; + +const record = ( + created: string, + du: number, + efs: BeszelSystemStatsRecord["stats"]["efs"], +): BeszelSystemStatsRecord => ({ + id: created, + system: "system-1", + type: "1m", + created, + updated: created, + stats: { + cpu: 0, + m: 0, + mu: 0, + mp: 0, + mb: 0, + s: 0, + su: 0, + d: 938.8, + du, + dp: 51.15, + efs, + }, +}); + +describe("buildDiskChartData", () => { + test("keeps root and extra filesystem usage in GiB without stacking or byte conversion", () => { + const data = buildDiskChartData( + [ + record("2026-07-11T13:46:00.000Z", 455.81, { + sda: { d: 1906.8, du: 1656.45, r: 0, w: 0 }, + sdb: { d: 5587.04, du: 3936.86, r: 0, w: 0 }, + sdc: { d: 3724.18, du: 1589.84, r: 0, w: 0 }, + }), + ], + ["sda", "sdb", "sdc"], + "Root", + "1h", + ); + + expect(data).toHaveLength(1); + expect(data[0]).toMatchObject({ Root: 455.81, sda: 1656.45, sdb: 3936.86, sdc: 1589.84 }); + }); + + test("uses zero for a filesystem missing from an individual sample and orders historical records oldest first", () => { + const data = buildDiskChartData( + [ + record("2026-07-11T13:47:00.000Z", 2, { sda: { d: 10, du: 4, r: 0, w: 0 } }), + record("2026-07-11T13:46:00.000Z", 1, undefined), + ], + ["sda"], + "Root", + "1h", + ); + + expect(data.map((point) => ({ Root: point.Root, sda: point.sda }))).toEqual([ + { Root: 1, sda: 0 }, + { Root: 2, sda: 4 }, + ]); + }); +}); + +describe("padLiveTimeGrid", () => { + test("keeps a fixed 60-second window with the newest sample at the right edge", () => { + const data = padLiveTimeGrid([{ time: "13:51:30", rawTime: "2026-07-11T13:51:30.217Z", CPU: 18.07 }]); + + expect(data).toHaveLength(60); + expect(data[0]).not.toHaveProperty("CPU"); + expect(data[58]).not.toHaveProperty("CPU"); + expect(data[59]).toMatchObject({ CPU: 18.07 }); + }); + + test("leaves null gaps for missing seconds instead of stretching existing points", () => { + const data = padLiveTimeGrid([ + { time: "13:51:28", rawTime: "2026-07-11T13:51:28.000Z", CPU: 10 }, + { time: "13:51:30", rawTime: "2026-07-11T13:51:30.000Z", CPU: 20 }, + ]); + + expect(data.slice(-3).map((point) => point.CPU)).toEqual([10, undefined, 20]); + }); +}); diff --git a/packages/widgets/src/beszel/_shared/chart.tsx b/packages/widgets/src/beszel/_shared/chart.tsx index 0866dc9d8..7ba5b66fe 100644 --- a/packages/widgets/src/beszel/_shared/chart.tsx +++ b/packages/widgets/src/beszel/_shared/chart.tsx @@ -30,6 +30,7 @@ function prepareRecords(records: T[], timePeriod: BeszelTimePeriod) { } function padTimeGrid(data: Record[], timePeriod: BeszelTimePeriod) { + if (timePeriod === "1m") return padLiveTimeGrid(data); const days = periodDays[timePeriod]; if (!days) return data; @@ -50,6 +51,38 @@ function padTimeGrid(data: Record[], timePeriod: BeszelTimePeri return result.toSorted((a, b) => new Date(a.rawTime as string).getTime() - new Date(b.rawTime as string).getTime()); } +export function padLiveTimeGrid(data: Record[], pointCount = 60) { + if (data.length === 0) return data; + + const end = dayjs( + data.reduce((latest, point) => { + const timestamp = new Date(point.rawTime as string).getTime(); + return Number.isFinite(timestamp) ? Math.max(latest, timestamp) : latest; + }, 0), + ).startOf("second"); + const pointsBySecond = new Map( + data.map( + (point) => + [ + dayjs(point.rawTime as string) + .startOf("second") + .valueOf(), + point, + ] as const, + ), + ); + + return Array.from({ length: pointCount }, (_, index) => { + const timestamp = end.subtract(pointCount - index - 1, "second"); + return ( + pointsBySecond.get(timestamp.valueOf()) ?? { + time: timestamp.format(timeFormats["1m"]), + rawTime: timestamp.toISOString(), + } + ); + }); +} + const yAxisBase = { tickMargin: 0, tick: { fontSize: 10 } } as const; const chartStyle = { minWidth: 0, minHeight: 1 } as const; const panelStyle = { minWidth: 0, overflow: "hidden" } as const; @@ -174,24 +207,39 @@ const defaultContainerExtractors: Record = { network: (c) => (c?.b ? c.b[0] + c.b[1] : (c?.ns ?? 0) + (c?.nr ?? 0)), }; -export const useEfsChartData = ( +export const useDiskChartData = ( systemStats: BeszelSystemStatsRecord[] | undefined, efsPaths: string[], + rootSeriesName: string, timePeriod: BeszelTimePeriod = "1h", ) => - useMemo(() => { - if (!systemStats?.length) return []; - const { fmt, ordered } = prepareRecords(systemStats, timePeriod); - const mapped = ordered.map((record) => { - const point: Record = { time: fmt(record.created), rawTime: record.created }; - const efs = record.stats.efs ?? {}; - for (const path of efsPaths) { - point[path] = efs[path]?.du ?? 0; - } - return point; - }); - return padTimeGrid(mapped, timePeriod); - }, [systemStats, efsPaths, timePeriod]); + useMemo( + () => buildDiskChartData(systemStats, efsPaths, rootSeriesName, timePeriod), + [systemStats, efsPaths, rootSeriesName, timePeriod], + ); + +export const buildDiskChartData = ( + systemStats: BeszelSystemStatsRecord[] | undefined, + efsPaths: string[], + rootSeriesName: string, + timePeriod: BeszelTimePeriod = "1h", +) => { + if (!systemStats?.length) return []; + const { fmt, ordered } = prepareRecords(systemStats, timePeriod); + const mapped = ordered.map((record) => { + const point: Record = { + time: fmt(record.created), + rawTime: record.created, + [rootSeriesName]: record.stats.du, + }; + const efs = record.stats.efs ?? {}; + for (const path of efsPaths) { + point[path] = efs[path]?.du ?? 0; + } + return point; + }); + return padTimeGrid(mapped, timePeriod); +}; export const useDockerChartData = ( containerStats: BeszelContainerStatsRecord[] | undefined, diff --git a/packages/widgets/src/beszel/_shared/disk-usage.tsx b/packages/widgets/src/beszel/_shared/disk-usage.tsx new file mode 100644 index 000000000..9aa2a14ad --- /dev/null +++ b/packages/widgets/src/beszel/_shared/disk-usage.tsx @@ -0,0 +1,105 @@ +import { Box, Group, HoverCard, Progress, Stack, Text, UnstyledButton } from "@mantine/core"; + +import type { BeszelSystemRow } from "./types"; +import { thresholdColor } from "./colors"; +import { formatPercent, getProgressTrackSize } from "./format"; + +interface DiskUsageProps { + system: BeszelSystemRow; + fontSize: string; + progressSize: "xs" | "sm"; + valueMiw: number; + valueGap?: number; +} + +const mountLabel = (path: string) => + path === "/" ? "ROOT" : (path.split("/").filter(Boolean).at(-1) ?? path).toUpperCase(); +const severityColor = (value: number) => `var(--mantine-color-${thresholdColor(value)}-6)`; + +export const DiskUsage = ({ system, fontSize, progressSize, valueMiw, valueGap = 6 }: DiskUsageProps) => { + const filesystems = Object.entries(system.extraFilesystems).filter(([path]) => path !== "/"); + const trackSize = getProgressTrackSize(progressSize); + const dotSize = progressSize === "xs" ? 2 : 3; + const dotGap = 2; + + return ( + + + {formatPercent(system.disk)} + + + + {filesystems.length > 0 ? ( + + + + + {filesystems.map(([path, value]) => ( + + ))} + + + + ) : ( + + + + + + )} + + + + {[["/", system.disk] as const, ...filesystems].map(([path, value]) => ( + + + {mountLabel(path)} + + + + {formatPercent(value)} + + + + + ))} + + + + + ); +}; diff --git a/packages/widgets/src/beszel/_shared/format.spec.ts b/packages/widgets/src/beszel/_shared/format.spec.ts new file mode 100644 index 000000000..ec4545063 --- /dev/null +++ b/packages/widgets/src/beszel/_shared/format.spec.ts @@ -0,0 +1,21 @@ +import { describe, expect, test } from "vitest"; + +import { chartAxisFormatters, formatGB, getProgressTrackSize } from "./format"; + +describe("Beszel storage formatting", () => { + test("keeps smaller values in GB", () => { + expect(formatGB(455.81)).toBe("455.81 GB"); + expect(chartAxisFormatters.gb(455.81)).toBe("456G"); + }); + + test("promotes large GiB values to TB", () => { + expect(formatGB(3323)).toBe("3.25 TB"); + expect(formatGB(3936.86)).toBe("3.84 TB"); + expect(chartAxisFormatters.gb(3323)).toBe("3.2T"); + }); + + test("maps progress sizes consistently", () => { + expect(getProgressTrackSize("xs")).toBe(6); + expect(getProgressTrackSize("sm")).toBe(9); + }); +}); diff --git a/packages/widgets/src/beszel/_shared/format.ts b/packages/widgets/src/beszel/_shared/format.ts index 8e84c74e4..dc483daa6 100644 --- a/packages/widgets/src/beszel/_shared/format.ts +++ b/packages/widgets/src/beszel/_shared/format.ts @@ -29,14 +29,22 @@ export const formatStorageBytes = (bytes: number): string => formatScaled(bytes, export const formatGB = (value: number): string => { if (!value || !Number.isFinite(value)) return "0 GB"; + if (Math.abs(value) >= 1024) return `${(value / 1024).toFixed(2)} TB`; return `${value.toFixed(2)} GB`; }; export const formatPercent = (value: number): string => `${value.toFixed(1)}%`; +export const getProgressTrackSize = (size: "xs" | "sm"): number => (size === "xs" ? 6 : 9); + export const chartAxisFormatters = { percent: formatPercent, - gb: (value: number) => (!value ? "0" : `${Number(value).toFixed(0)}G`), + gb: (value: number) => { + const numericValue = Number(value); + if (!numericValue) return "0"; + if (Math.abs(numericValue) >= 1024) return `${(numericValue / 1024).toFixed(1)}T`; + return `${numericValue.toFixed(0)}G`; + }, mb: (value: number) => (!value ? "0" : `${Number(value).toFixed(0)}M`), bytes: (value: number) => formatScaledCompact(Number(value), byteUnits, "0"), rate: (value: number) => formatScaledCompact(Number(value), rateUnits, "0"), diff --git a/packages/widgets/src/beszel/_shared/stats-view.tsx b/packages/widgets/src/beszel/_shared/stats-view.tsx new file mode 100644 index 000000000..48706d4ad --- /dev/null +++ b/packages/widgets/src/beszel/_shared/stats-view.tsx @@ -0,0 +1,339 @@ +"use client"; + +import { useMemo } from "react"; +import { Button, Center, Group, SimpleGrid, Skeleton, Stack, Text } from "@mantine/core"; +import { IconPlugConnectedX, IconServerOff } from "@tabler/icons-react"; + +import { clientApi } from "@homarr/api/client"; +import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types"; +import { useScopedI18n } from "@homarr/translation/client"; + +import { containerColors } from "./colors"; +import type { BeszelTimePeriod } from "./chart"; +import { + BeszelChartPanel, + CPU_Y_AXIS_DOMAIN, + useContainerNames, + useDiskChartData, + useDockerChartData, + useSystemChartData, +} from "./chart"; +import { chartAxisFormatters, formatByteRate, formatGB, formatPercent, formatStorageBytes } from "./format"; +import { makeTooltipProps } from "./tooltip"; +import { useLiveStats } from "./use-live-stats"; + +const CHART_HEIGHT = 180; +const MB = 1024 * 1024; +const tooltipPercent = makeTooltipProps(formatPercent); +const tooltipGB = makeTooltipProps(formatGB, true); +const tooltipRate = makeTooltipProps(formatByteRate, true); +const tooltipPercentTotal = makeTooltipProps(formatPercent, true); +const tooltipBytesTotal = makeTooltipProps(formatStorageBytes, true); + +const ChartSkeleton = () => ( + + + + + + + +); + +export interface BeszelStatsVisibility { + cpu: boolean; + memory: boolean; + disk: boolean; + diskIO: boolean; + network: boolean; + dockerCpu: boolean; + dockerMemory: boolean; + dockerNetwork: boolean; +} + +interface BeszelStatsViewProps { + integrationIds: string[]; + systemId: string; + timePeriod: BeszelTimePeriod; + visibility: BeszelStatsVisibility; + columns: 1 | 2; + onSwitchToHistorical?: () => void; +} + +const whenVisible = (visible: boolean, data: T | undefined) => (visible ? data : undefined); + +export function BeszelStatsView({ + integrationIds, + systemId, + timePeriod, + visibility, + columns, + onSwitchToHistorical, +}: BeszelStatsViewProps) { + const t = useScopedI18n("widget.beszelSystemStats"); + const showDocker = visibility.dockerCpu || visibility.dockerMemory || visibility.dockerNetwork; + const isLive = timePeriod === "1m"; + + const { data: historicalData, error: historicalError } = clientApi.widget.beszel.getSystemStats.useQuery( + { integrationIds, systemId, timePeriod, includeDocker: showDocker }, + { refetchInterval: isLive ? false : 5_000, enabled: !isLive && systemId !== "" }, + ); + const { data: liveData, error: liveError } = useLiveStats(integrationIds, systemId, isLive && systemId !== ""); + + const activeStats: + | { systemStats: BeszelSystemStatsRecord[]; containerStats: BeszelContainerStatsRecord[]; error?: string } + | null + | undefined = isLive ? liveData : historicalData; + + const mappers = useMemo( + () => ({ + cpu: (s: { cpu: number }) => ({ [t("chart.cpu.series")]: s.cpu }), + memory: (s: { mu: number; mb: number }) => ({ + [t("chart.memory.series")]: s.mu, + [t("chart.memory.cache")]: s.mb ?? 0, + }), + diskIO: (s: { dr?: number; dw?: number }) => ({ + [t("chart.diskIO.read")]: (s.dr ?? 0) * MB, + [t("chart.diskIO.write")]: (s.dw ?? 0) * MB, + }), + network: (s: { ns?: number; nr?: number; b?: [number, number] }) => ({ + [t("chart.network.sent")]: s.b?.[0] ?? s.ns ?? 0, + [t("chart.network.recv")]: s.b?.[1] ?? s.nr ?? 0, + }), + }), + [t], + ); + + const series = useMemo( + () => ({ + cpu: [{ name: t("chart.cpu.series"), color: "teal.6" }], + memory: [ + { name: t("chart.memory.series"), color: "teal.5" }, + { name: t("chart.memory.cache"), color: "teal.8" }, + ], + diskIO: [ + { name: t("chart.diskIO.write"), color: "orange.6" }, + { name: t("chart.diskIO.read"), color: "blue.6" }, + ], + network: [ + { name: t("chart.network.sent"), color: "blue.6" }, + { name: t("chart.network.recv"), color: "teal.6" }, + ], + }), + [t], + ); + + const systemStats = activeStats?.systemStats; + const containerStats = activeStats?.containerStats; + const cpuData = useSystemChartData(whenVisible(visibility.cpu, systemStats), mappers.cpu, timePeriod); + const memoryData = useSystemChartData(whenVisible(visibility.memory, systemStats), mappers.memory, timePeriod); + const diskIOData = useSystemChartData(whenVisible(visibility.diskIO, systemStats), mappers.diskIO, timePeriod); + const networkData = useSystemChartData(whenVisible(visibility.network, systemStats), mappers.network, timePeriod); + + const efsPaths = useMemo(() => { + const paths = new Set(); + for (const record of systemStats ?? []) { + for (const path of Object.keys(record.stats.efs ?? {})) paths.add(path); + } + return [...paths]; + }, [systemStats]); + const rootSeriesName = t("chart.disk.series"); + const diskData = useDiskChartData(whenVisible(visibility.disk, systemStats), efsPaths, rootSeriesName, timePeriod); + const diskSeries = useMemo( + () => [ + { name: rootSeriesName, color: "grape.6" }, + ...efsPaths.map((path, index) => ({ + name: path, + color: containerColors[(index + 1) % containerColors.length] as string, + })), + ], + [efsPaths, rootSeriesName], + ); + + const containerNames = useContainerNames(whenVisible(showDocker, containerStats)); + const containerSeries = useMemo( + () => + containerNames.map((name, index) => ({ name, color: containerColors[index % containerColors.length] as string })), + [containerNames], + ); + const dockerCpuData = useDockerChartData( + whenVisible(visibility.dockerCpu, containerStats), + containerNames, + "cpu", + timePeriod, + ); + const dockerMemoryData = useDockerChartData( + whenVisible(visibility.dockerMemory, containerStats), + containerNames, + "memory", + timePeriod, + ); + const dockerNetworkData = useDockerChartData( + whenVisible(visibility.dockerNetwork, containerStats), + containerNames, + "network", + timePeriod, + ); + + if (historicalError) throw historicalError; + + if (isLive && liveError) { + return ( +
+ + + + {t("error.liveConnectionFailed")} + + {onSwitchToHistorical && ( + + )} + +
+ ); + } + + if (!activeStats) { + const visibleChartCount = Object.values(visibility).filter(Boolean).length; + return ( + + {Array.from({ length: visibleChartCount }, (_, index) => ( + + ))} + + ); + } + + if ("error" in activeStats && activeStats.error) { + return ( +
+ + + + {activeStats.error} + + +
+ ); + } + + return ( + + {visibility.cpu && cpuData.length > 0 && ( + + )} + {visibility.memory && memoryData.length > 0 && ( + + )} + {visibility.disk && diskData.length > 0 && ( + + )} + {visibility.diskIO && diskIOData.length > 0 && ( + + )} + {visibility.network && networkData.length > 0 && ( + + )} + {showDocker && containerSeries.length > 0 && ( + <> + {visibility.dockerCpu && dockerCpuData.length > 0 && ( + + )} + {visibility.dockerMemory && dockerMemoryData.length > 0 && ( + + )} + {visibility.dockerNetwork && dockerNetworkData.length > 0 && ( + + )} + + )} + + ); +} diff --git a/packages/widgets/src/beszel/_shared/system-stats-modal.tsx b/packages/widgets/src/beszel/_shared/system-stats-modal.tsx index 1071ccc53..fda3146d2 100644 --- a/packages/widgets/src/beszel/_shared/system-stats-modal.tsx +++ b/packages/widgets/src/beszel/_shared/system-stats-modal.tsx @@ -1,25 +1,15 @@ "use client"; -import { useMemo, useState } from "react"; -import { SegmentedControl, SimpleGrid, Stack } from "@mantine/core"; +import { useState } from "react"; +import { SegmentedControl, Stack } from "@mantine/core"; -import { clientApi } from "@homarr/api/client"; import { createModal } from "@homarr/modals"; -import { useScopedI18n } from "@homarr/translation/client"; import type { BeszelTimePeriod } from "./chart"; -import { BeszelChartPanel, CPU_Y_AXIS_DOMAIN, useSystemChartData } from "./chart"; -import { chartAxisFormatters, formatByteRate, formatGB, formatPercent } from "./format"; -import { makeTooltipProps } from "./tooltip"; - -const tooltipPercent = makeTooltipProps(formatPercent); -const tooltipGB = makeTooltipProps(formatGB, true); -const tooltipRate = makeTooltipProps(formatByteRate, true); - -const CHART_HEIGHT = 180; -const MB = 1024 * 1024; +import { BeszelStatsView } from "./stats-view"; const timePeriodOptions: { value: BeszelTimePeriod; label: string }[] = [ + { value: "1m", label: "Live" }, { value: "1h", label: "1H" }, { value: "12h", label: "12H" }, { value: "24h", label: "24H" }, @@ -27,141 +17,41 @@ const timePeriodOptions: { value: BeszelTimePeriod; label: string }[] = [ { value: "30d", label: "30D" }, ]; +const allCharts = { + cpu: true, + memory: true, + disk: true, + diskIO: true, + network: true, + dockerCpu: true, + dockerMemory: true, + dockerNetwork: true, +} as const; + interface BeszelSystemStatsModalProps { integrationId: string; systemId: string; } export const BeszelSystemStatsModal = createModal(({ innerProps }) => { - const t = useScopedI18n("widget.beszelSystemStats"); const [timePeriod, setTimePeriod] = useState("1h"); - const { data } = clientApi.widget.beszel.getSystemStats.useQuery({ - integrationIds: [innerProps.integrationId], - systemId: innerProps.systemId, - timePeriod, - includeDocker: false, - }); - - const mappers = useMemo( - () => ({ - cpu: (s: { cpu: number }) => ({ [t("chart.cpu.series")]: s.cpu }), - memory: (s: { mu: number; mb: number }) => ({ - [t("chart.memory.series")]: s.mu, - [t("chart.memory.cache")]: s.mb ?? 0, - }), - disk: (s: { du: number }) => ({ [t("chart.disk.series")]: s.du }), - diskIO: (s: { dr?: number; dw?: number }) => ({ - [t("chart.diskIO.read")]: (s.dr ?? 0) * MB, - [t("chart.diskIO.write")]: (s.dw ?? 0) * MB, - }), - network: (s: { ns: number; nr: number; b?: [number, number] }) => ({ - [t("chart.network.sent")]: s.b?.[0] ?? s.ns, - [t("chart.network.recv")]: s.b?.[1] ?? s.nr, - }), - }), - [t], - ); - - const series = useMemo( - () => ({ - cpu: [{ name: t("chart.cpu.series"), color: "teal.6" }], - memory: [ - { name: t("chart.memory.series"), color: "teal.5" }, - { name: t("chart.memory.cache"), color: "teal.8" }, - ], - disk: [{ name: t("chart.disk.series"), color: "grape.6" }], - diskIO: [ - { name: t("chart.diskIO.write"), color: "orange.6" }, - { name: t("chart.diskIO.read"), color: "blue.6" }, - ], - network: [ - { name: t("chart.network.sent"), color: "blue.6" }, - { name: t("chart.network.recv"), color: "teal.6" }, - ], - }), - [t], - ); - - const systemStats = data?.systemStats; - const cpuData = useSystemChartData(systemStats, mappers.cpu, timePeriod); - const memoryData = useSystemChartData(systemStats, mappers.memory, timePeriod); - const diskData = useSystemChartData(systemStats, mappers.disk, timePeriod); - const diskIOData = useSystemChartData(systemStats, mappers.diskIO, timePeriod); - const networkData = useSystemChartData(systemStats, mappers.network, timePeriod); - return ( setTimePeriod(v as BeszelTimePeriod)} + onChange={(value) => setTimePeriod(value as BeszelTimePeriod)} data={timePeriodOptions} /> - - {cpuData.length > 0 && ( - - )} - {memoryData.length > 0 && ( - - )} - {diskData.length > 0 && ( - - )} - {diskIOData.length > 0 && ( - - )} - {networkData.length > 0 && ( - - )} - + setTimePeriod("1h")} + /> ); }).withOptions({ diff --git a/packages/widgets/src/beszel/_shared/use-live-stats.ts b/packages/widgets/src/beszel/_shared/use-live-stats.ts index f8c351bf8..f2ab5df34 100644 --- a/packages/widgets/src/beszel/_shared/use-live-stats.ts +++ b/packages/widgets/src/beszel/_shared/use-live-stats.ts @@ -1,12 +1,14 @@ "use client"; import { useCallback, useEffect, useRef, useState } from "react"; +import { skipToken } from "@tanstack/react-query"; import { clientApi } from "@homarr/api/client"; import type { BeszelContainerStatsRecord, BeszelSystemStatsRecord } from "@homarr/integrations/types"; -// 120 records at ~1 per second = 2 minutes of rolling data for charts -const MAX_BUFFER = 120; +// Beszel emits roughly one record per second. Keep a sliding one-minute window +// without synthesizing points for missed updates. +const MAX_BUFFER = 60; export const useLiveStats = (integrationIds: string[], systemId: string, enabled: boolean) => { const [systemStats, setSystemStats] = useState([]); @@ -29,9 +31,8 @@ export const useLiveStats = (integrationIds: string[], systemId: string, enabled }, []); clientApi.widget.beszel.subscribeSystemStats.useSubscription( - { integrationIds, systemId }, + enabled && systemId !== "" ? { integrationIds, systemId } : skipToken, { - enabled: enabled && systemId !== "", onData(event) { setError(null); if (event.type === "system_stats") { @@ -48,7 +49,7 @@ export const useLiveStats = (integrationIds: string[], systemId: string, enabled // Reset buffers when system changes or integration changes const prevKeyRef = useRef(""); - const currentKey = `${integrationIds.join(",")}:${systemId}`; + const currentKey = `${integrationIds.join(",")}:${systemId}:${enabled}`; useEffect(() => { if (prevKeyRef.current !== currentKey) { prevKeyRef.current = currentKey; diff --git a/packages/widgets/src/definition.ts b/packages/widgets/src/definition.ts index a6e769d5d..aaf60269f 100644 --- a/packages/widgets/src/definition.ts +++ b/packages/widgets/src/definition.ts @@ -1,6 +1,6 @@ import type React from "react"; import type { LoaderComponent } from "next/dynamic"; -import type { QueryClient } from "@tanstack/react-query"; +import type { QueryClient, QueryKey } from "@tanstack/react-query"; import type { DefaultErrorData } from "@trpc/server/unstable-core-do-not-import"; import type { IntegrationKind, WidgetKind } from "@homarr/definitions"; @@ -69,6 +69,7 @@ export const createWidgetDefinition = Date: Sun, 12 Jul 2026 00:06:59 +0000 Subject: [PATCH 05/54] chore(deps): update dependency oxfmt to ^0.58.0 (#6305) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 164 ++++++++++++++++++++++---------------------- pnpm-workspace.yaml | 2 +- 2 files changed, 83 insertions(+), 83 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 574aad9dc..a2679caf4 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -430,8 +430,8 @@ catalogs: specifier: ^5.0.5 version: 5.0.5 oxfmt: - specifier: ^0.57.0 - version: 0.57.0 + specifier: ^0.58.0 + version: 0.58.0 oxlint: specifier: ^1.72.0 version: 1.72.0 @@ -642,7 +642,7 @@ importers: version: 2.2.3 oxfmt: specifier: 'catalog:' - version: 0.57.0 + version: 0.58.0 oxlint: specifier: 'catalog:' version: 1.72.0 @@ -6002,124 +6002,124 @@ packages: '@oxc-project/types@0.138.0': resolution: {integrity: sha512-1a7ZKmrRTCoN1XMZ4L0PyyqrMnrNlLyPuOkdSX2MZg7IiIGRUyurNhAm73ptDOraoBcIordsIGKNPKUzy3ZmfA==} - '@oxfmt/binding-android-arm-eabi@0.57.0': - resolution: {integrity: sha512-qVBsEO+KugOsCmUHcO8iqNnqc65p7PCKpCs8M66mPZ+Ri+CWbcpoQOEJBg2OTu03+0qu++NK1jj6IzvQVs0Sig==} + '@oxfmt/binding-android-arm-eabi@0.58.0': + resolution: {integrity: sha512-Uz62sHduGGPftXtILGyxdSW4PX82rUg+rfdNqhsgxe881g4rIoXlIqmZQ6HVKcF4f+F8qMhdD03Bx5u7gmeTdg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [android] - '@oxfmt/binding-android-arm64@0.57.0': - resolution: {integrity: sha512-mp6PibWbao3aizijcheOeHQaYEhcUAt8pwLniYbtLfHxL/psFF0BykAwCj+s3c6qIpa8yN8keZICWrqtZ70w8g==} + '@oxfmt/binding-android-arm64@0.58.0': + resolution: {integrity: sha512-rD0lRaJp1b+9vw6X4A2dJWKukd6X8yxiicN4JxXcXayolmUypRZxk+lKR+fVOu5q/iYc0fh5fR4bgmfOfVlbaA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [android] - '@oxfmt/binding-darwin-arm64@0.57.0': - resolution: {integrity: sha512-T+0stuCBqmUVY+aMIvrgXhzGhHO3sD5tNiiEcYqgSdPsnukskQqn2u5qOVD0sv1l7RLdFS5Z/f5Wi9Ktyjr3Eg==} + '@oxfmt/binding-darwin-arm64@0.58.0': + resolution: {integrity: sha512-uzbPPk7O6M+w2K65vcQ1woga3wgP8zghjL1KOG5b6qJ8dvYHZJ1VShaslg2KOK6yQIwCQtcMCXqLBM6sqXUNTg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [darwin] - '@oxfmt/binding-darwin-x64@0.57.0': - resolution: {integrity: sha512-O+3JbqWs/mCI2oi4xfhRO2IVPFJNDDEBV8Odo+ZpmsUOeKJfjXoNH7nDmBEQcDgK7NfjDIyE7kRgYSZcTLDO0A==} + '@oxfmt/binding-darwin-x64@0.58.0': + resolution: {integrity: sha512-L0nKYDxU32oxeQqJj21W9SlIMnf81VZEhyah6iDvFhf5q0oynq498Fopth7blErUJVBpVtxQ98RMCfMPqpJX6w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [darwin] - '@oxfmt/binding-freebsd-x64@0.57.0': - resolution: {integrity: sha512-pxwhxVC+JkLX9twOQ/8C/vbuOQcMZyKIDmiRDZfO7yITuVcIdZCiLRqqf4QOxb2+8FWrRXzQpm+1DBKcMpHSSQ==} + '@oxfmt/binding-freebsd-x64@0.58.0': + resolution: {integrity: sha512-woNwfD58dC5PGS9LSLSD5JYfo/EFK5iG9vhDWkcCg3q78ag7KC8bpDqgvPHrMoXpx83OLXxoSOhu6z8FsVTHlg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [freebsd] - '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': - resolution: {integrity: sha512-pxBU4zH2imB/MDBfth2rOMeVxXUMjRQLCazagwLARIFH3hVlxZJBlM4nSnHXaIHJK4/qezoFCIORN6AY8Mra4A==} + '@oxfmt/binding-linux-arm-gnueabihf@0.58.0': + resolution: {integrity: sha512-Sqs8nMLxuQpY21NKJ1u4stPDmO5hskBCNNh2E3AdCfI1QqWtf4m+Qn4mGEIUO4KGmuq3SWc/SZ80uy5IiwTCDw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm-musleabihf@0.57.0': - resolution: {integrity: sha512-JAprOzt8tycYou36ZgEw14DlRHTiN8qdtKANdV3VZIRIvTI/lh/cX13c9pJ/EnDk2GT3FASH7KvCgQ2AufAifQ==} + '@oxfmt/binding-linux-arm-musleabihf@0.58.0': + resolution: {integrity: sha512-Vd4exzBI5B5hB9m22JiTQzIL23WvHo/Pe+sNXPNeBLXSP9swCBPKCEBRwKpmpQzYhlgYaCgfPcGXPKAJBRIiZQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm] os: [linux] - '@oxfmt/binding-linux-arm64-gnu@0.57.0': - resolution: {integrity: sha512-ajtjaxSaj9xl4BW7REt+Cef/ttzbAq00Bq4z7JUDZEfgFXdwSjH8K9bF+IcIJzZB9lKqMfQ4eHuSFOvvlvtqOg==} + '@oxfmt/binding-linux-arm64-gnu@0.58.0': + resolution: {integrity: sha512-bUWi5mHV+4Vi56RLHE1h6q/HHfwAIT3XoB9vJAVeRzfu5NriXM8y6eeJu0vlKa0C9kq2rq1sOWRClhdLHPocrg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-arm64-musl@0.57.0': - resolution: {integrity: sha512-p4Y/+RYk9Bk5WO+zHSUXAClRmZ2fbJCejMuCAsU2HhyME4jqf6Ftt/mJYEwIah1wGCBDYOB7wEGV1x5bCEZ6hA==} + '@oxfmt/binding-linux-arm64-musl@0.58.0': + resolution: {integrity: sha512-2ZHxemzgHcjtktAuVUwSoyXmGo/t+aF5tS1ciPpPei4rhSyrz3JOqDosXXrmhN/yLUSzJjtuW7ToTWqfQpCj2w==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-ppc64-gnu@0.57.0': - resolution: {integrity: sha512-By6tRALAZsno0F4zedmtG+wdMvJiJmJoXM4d3+A9zHE4HRXLqXITwRH8mgrlcXc5yJM2g2W3riRPwTYdgemZLQ==} + '@oxfmt/binding-linux-ppc64-gnu@0.58.0': + resolution: {integrity: sha512-AwKkVwjVmFQ3bcO7j0McGYAqCKH2a326fswfofng/E8VewCT/raeeGQr4huVhY704deK8AWASSTlxzMj0eZc6Q==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ppc64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-gnu@0.57.0': - resolution: {integrity: sha512-skYeG+RgvyzspqVEBsEprL90OYYZfoVNqB3HcCNR6QDJyXKOzfDRT3zncnHmUaFluIlBHuY23mU1b5WGgR98hA==} + '@oxfmt/binding-linux-riscv64-gnu@0.58.0': + resolution: {integrity: sha512-xsRpTxfUnJF8D3AUKko/qyWdjw4GZVHlCVFuGlzSCTeewLmykKINW8em1+wx+axsDVtJJcMtvsiaXggXxrlHgw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-riscv64-musl@0.57.0': - resolution: {integrity: sha512-FFgACrZOXAXUh5KQh2mt1CDOVOZmn+QzHP71wM9QobNwyQvoFfyAeefVUltW83g3sm7LTiH3yfFqLLVUpA5ZFQ==} + '@oxfmt/binding-linux-riscv64-musl@0.58.0': + resolution: {integrity: sha512-Z4AYOTcy7nYEIiXwD62PlerimyYRcfJOgUbQAEBjXz098kxKuERBlRntofGy69HHhe9E0TLVNMl1yspVNu+efw==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [riscv64] os: [linux] libc: [musl] - '@oxfmt/binding-linux-s390x-gnu@0.57.0': - resolution: {integrity: sha512-Nm/BAOfQeFiiKd502mZn/GAVKJwtd0RdCg17G3Wz/WSOIQmDi3+7/SZH4BHn1Ye5KvTVH3ua8WvfwLLycNIuvA==} + '@oxfmt/binding-linux-s390x-gnu@0.58.0': + resolution: {integrity: sha512-A3nhhtZPC/TKVWOPj9q/H3p2znJDCcHWYlJBhWL8hGq/bFmBaNBHC8Np6E581yVq1w9Mi3rMDNzDalWvtUfJtQ==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [s390x] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-gnu@0.57.0': - resolution: {integrity: sha512-BiSy5Ku3mQqyxS6YIqAJgd403wEUWvI7kerfzPxc2l/txZVmZM0pSj7oDM+4bGBExowxOi7o73jEam1W0EDTZg==} + '@oxfmt/binding-linux-x64-gnu@0.58.0': + resolution: {integrity: sha512-2g+tVkgwqphw8R4hgo+kF4oz8+P5RwVOtr9+irsC7uwEp0e9j7Crw8kDGKL20uYlLPD7g02DqA61mC/UNYx98A==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [glibc] - '@oxfmt/binding-linux-x64-musl@0.57.0': - resolution: {integrity: sha512-BCRkJiotz5s9afLYD2LuMvzAoDYx9H17E/YbDyu4xK7l4zHDPeny9ErSXL//i/nJyaOwRk08x4b8cgJC00+JDg==} + '@oxfmt/binding-linux-x64-musl@0.58.0': + resolution: {integrity: sha512-rc15P6AbyyB7426aN8AakLd02Trb3a6ML/mmfAQeVHJEfVofWLcWIrBdy6zDEY+DIaL/s8E4GGPboVw+oP3+EA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [linux] libc: [musl] - '@oxfmt/binding-openharmony-arm64@0.57.0': - resolution: {integrity: sha512-4Oaxe1qrGgXfpCJ1C/ERJ2iCtV2rN1R79ga9fsfyVHfSQRu/hVW780u2KDqZWFZ/iGTHODJji0JemxqFZ63eIQ==} + '@oxfmt/binding-openharmony-arm64@0.58.0': + resolution: {integrity: sha512-ZWoTM27/HYPOh9iq86DAbhPu9nXb8qKvvGU/h8OfliyVUFAMMNTLDkGsWDKKnDqIkqvZ9+dXlgUOsH1LYO3O7g==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [openharmony] - '@oxfmt/binding-win32-arm64-msvc@0.57.0': - resolution: {integrity: sha512-MYLAsDnhdNsSGheLYhWgbk0vfIrlS84iQYun/y21fX6u0jj8iBtYtbpZMdiqYeuf8U12eVPUjVY2xE2NrCfJ0g==} + '@oxfmt/binding-win32-arm64-msvc@0.58.0': + resolution: {integrity: sha512-LHZnqFXe2dEfkRI4XdZS/57nEOT/I4UCRX5IyM9v4GYW9XwQCjGe1IUK59SuKw3POwvcgWQ4pme2cYXmNqTNPg==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [arm64] os: [win32] - '@oxfmt/binding-win32-ia32-msvc@0.57.0': - resolution: {integrity: sha512-PBwdzZALJY/jcCx2E6is0yu+cuVXeySTDmwuseD+9j0mHqlRNxwlKgsyRTBed/woPeqfVfuXfWjoq4Cx2Zt3Eg==} + '@oxfmt/binding-win32-ia32-msvc@0.58.0': + resolution: {integrity: sha512-mZKpg20TpheCJym1rarcZCUJeW1sSruw8zAAaCYWvuVfwIUDN1CXdrPU/JgCWReXTCTrEfCB8Wyo3hh9jSZ2EA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [ia32] os: [win32] - '@oxfmt/binding-win32-x64-msvc@0.57.0': - resolution: {integrity: sha512-bQJdH9i4RRfw55jm7+8/xS7GzHLLTbHx4huhrrDxQJaJtbSDbsyOnODvP1ftT7EG0KFKAYO2S+q6AcioXODx8w==} + '@oxfmt/binding-win32-x64-msvc@0.58.0': + resolution: {integrity: sha512-N/wUU4N5PZ2orBtI+Ko7MnMfYLfE7K91UrGMY/c/pYyHR3lA9kwst1XugkZx+92YcRh/Eo+iv2eTESSWXfiZPA==} engines: {node: ^20.19.0 || >=22.12.0} cpu: [x64] os: [win32] @@ -12352,8 +12352,8 @@ packages: outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} - oxfmt@0.57.0: - resolution: {integrity: sha512-ZB7Bi+rGDSqmVIo9jwcLyFgjxXvQhDdU+jx+ZrVy6VRiVXK2+CHc4hO3J4dUQjHe7V0ymHB+MDuv5z+NhK07HA==} + oxfmt@0.58.0: + resolution: {integrity: sha512-8feG/7NVEHDVwc1OUpP6Pks+TnaDFUw2jLLFIMi5bcmmwxAX2wBQvjSzj62RRTYBf2Op1Wt8xbkmagmPTR5ETg==} engines: {node: ^20.19.0 || >=22.12.0} hasBin: true peerDependencies: @@ -19591,61 +19591,61 @@ snapshots: '@oxc-project/types@0.138.0': {} - '@oxfmt/binding-android-arm-eabi@0.57.0': + '@oxfmt/binding-android-arm-eabi@0.58.0': optional: true - '@oxfmt/binding-android-arm64@0.57.0': + '@oxfmt/binding-android-arm64@0.58.0': optional: true - '@oxfmt/binding-darwin-arm64@0.57.0': + '@oxfmt/binding-darwin-arm64@0.58.0': optional: true - '@oxfmt/binding-darwin-x64@0.57.0': + '@oxfmt/binding-darwin-x64@0.58.0': optional: true - '@oxfmt/binding-freebsd-x64@0.57.0': + '@oxfmt/binding-freebsd-x64@0.58.0': optional: true - '@oxfmt/binding-linux-arm-gnueabihf@0.57.0': + '@oxfmt/binding-linux-arm-gnueabihf@0.58.0': optional: true - '@oxfmt/binding-linux-arm-musleabihf@0.57.0': + '@oxfmt/binding-linux-arm-musleabihf@0.58.0': optional: true - '@oxfmt/binding-linux-arm64-gnu@0.57.0': + '@oxfmt/binding-linux-arm64-gnu@0.58.0': optional: true - '@oxfmt/binding-linux-arm64-musl@0.57.0': + '@oxfmt/binding-linux-arm64-musl@0.58.0': optional: true - '@oxfmt/binding-linux-ppc64-gnu@0.57.0': + '@oxfmt/binding-linux-ppc64-gnu@0.58.0': optional: true - '@oxfmt/binding-linux-riscv64-gnu@0.57.0': + '@oxfmt/binding-linux-riscv64-gnu@0.58.0': optional: true - '@oxfmt/binding-linux-riscv64-musl@0.57.0': + '@oxfmt/binding-linux-riscv64-musl@0.58.0': optional: true - '@oxfmt/binding-linux-s390x-gnu@0.57.0': + '@oxfmt/binding-linux-s390x-gnu@0.58.0': optional: true - '@oxfmt/binding-linux-x64-gnu@0.57.0': + '@oxfmt/binding-linux-x64-gnu@0.58.0': optional: true - '@oxfmt/binding-linux-x64-musl@0.57.0': + '@oxfmt/binding-linux-x64-musl@0.58.0': optional: true - '@oxfmt/binding-openharmony-arm64@0.57.0': + '@oxfmt/binding-openharmony-arm64@0.58.0': optional: true - '@oxfmt/binding-win32-arm64-msvc@0.57.0': + '@oxfmt/binding-win32-arm64-msvc@0.58.0': optional: true - '@oxfmt/binding-win32-ia32-msvc@0.57.0': + '@oxfmt/binding-win32-ia32-msvc@0.58.0': optional: true - '@oxfmt/binding-win32-x64-msvc@0.57.0': + '@oxfmt/binding-win32-x64-msvc@0.58.0': optional: true '@oxlint/binding-android-arm-eabi@1.72.0': @@ -26615,29 +26615,29 @@ snapshots: outvariant@1.4.3: optional: true - oxfmt@0.57.0: + oxfmt@0.58.0: dependencies: tinypool: 2.1.0 optionalDependencies: - '@oxfmt/binding-android-arm-eabi': 0.57.0 - '@oxfmt/binding-android-arm64': 0.57.0 - '@oxfmt/binding-darwin-arm64': 0.57.0 - '@oxfmt/binding-darwin-x64': 0.57.0 - '@oxfmt/binding-freebsd-x64': 0.57.0 - '@oxfmt/binding-linux-arm-gnueabihf': 0.57.0 - '@oxfmt/binding-linux-arm-musleabihf': 0.57.0 - '@oxfmt/binding-linux-arm64-gnu': 0.57.0 - '@oxfmt/binding-linux-arm64-musl': 0.57.0 - '@oxfmt/binding-linux-ppc64-gnu': 0.57.0 - '@oxfmt/binding-linux-riscv64-gnu': 0.57.0 - '@oxfmt/binding-linux-riscv64-musl': 0.57.0 - '@oxfmt/binding-linux-s390x-gnu': 0.57.0 - '@oxfmt/binding-linux-x64-gnu': 0.57.0 - '@oxfmt/binding-linux-x64-musl': 0.57.0 - '@oxfmt/binding-openharmony-arm64': 0.57.0 - '@oxfmt/binding-win32-arm64-msvc': 0.57.0 - '@oxfmt/binding-win32-ia32-msvc': 0.57.0 - '@oxfmt/binding-win32-x64-msvc': 0.57.0 + '@oxfmt/binding-android-arm-eabi': 0.58.0 + '@oxfmt/binding-android-arm64': 0.58.0 + '@oxfmt/binding-darwin-arm64': 0.58.0 + '@oxfmt/binding-darwin-x64': 0.58.0 + '@oxfmt/binding-freebsd-x64': 0.58.0 + '@oxfmt/binding-linux-arm-gnueabihf': 0.58.0 + '@oxfmt/binding-linux-arm-musleabihf': 0.58.0 + '@oxfmt/binding-linux-arm64-gnu': 0.58.0 + '@oxfmt/binding-linux-arm64-musl': 0.58.0 + '@oxfmt/binding-linux-ppc64-gnu': 0.58.0 + '@oxfmt/binding-linux-riscv64-gnu': 0.58.0 + '@oxfmt/binding-linux-riscv64-musl': 0.58.0 + '@oxfmt/binding-linux-s390x-gnu': 0.58.0 + '@oxfmt/binding-linux-x64-gnu': 0.58.0 + '@oxfmt/binding-linux-x64-musl': 0.58.0 + '@oxfmt/binding-openharmony-arm64': 0.58.0 + '@oxfmt/binding-win32-arm64-msvc': 0.58.0 + '@oxfmt/binding-win32-ia32-msvc': 0.58.0 + '@oxfmt/binding-win32-x64-msvc': 0.58.0 oxlint@1.72.0: optionalDependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c33da635f..36f39e3cf 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -151,7 +151,7 @@ catalog: next-intl: 4.13.1 node-loader: ^2.1.0 octokit: ^5.0.5 - oxfmt: ^0.57.0 + oxfmt: ^0.58.0 oxlint: ^1.72.0 pg: ^8.22.0 postcss-preset-mantine: ^1.18.0 From d53c4e65134520dd14be237039fbc4a512c4f365 Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 02:58:51 +0000 Subject: [PATCH 06/54] chore(deps): update dependency hono@<4.6.5 to >=4.12.28 (#6291) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 4 ++-- pnpm-workspace.yaml | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index a2679caf4..2626a1cbb 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -567,7 +567,7 @@ overrides: brace-expansion@>=1.0.0 <=1.1.11: '>=5.0.7' esbuild@<=0.24.2: '>=0.28.1' form-data@>=4.0.0 <4.0.4: '>=4.0.6' - hono@<4.6.5: '>=4.12.27' + hono@<4.6.5: '>=4.12.28' linkifyjs@<4.3.2: '>=4.3.3' nanoid@>=4.0.0 <5.0.9: '>=5.1.16' prismjs@<1.30.0: '>=1.30.0' @@ -5017,7 +5017,7 @@ packages: resolution: {integrity: sha512-GwtvgtXxnWsucXvbQXkRgqksiH2Qed37H9xHZocE5sA3N8O8O8/8FA3uclQXxXVzc9XBZuEOMK7+r02FmSpHtw==} engines: {node: '>=18.14.1'} peerDependencies: - hono: '>=4.12.27' + hono: '>=4.12.28' '@iconify/types@2.0.0': resolution: {integrity: sha512-+wluvCrRhXrhyOmRDJ3q8mux9JkKy5SJ/v8ol2tu4FVjyYvtEzkc/3pK15ET6RKg4b4w4BmTk1+gsCUhf21Ykg==} diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 36f39e3cf..459d7cf0d 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -218,7 +218,7 @@ overrides: "brace-expansion@>=1.0.0 <=1.1.11": ">=5.0.7" "esbuild@<=0.24.2": ">=0.28.1" "form-data@>=4.0.0 <4.0.4": ">=4.0.6" - "hono@<4.6.5": ">=4.12.27" + "hono@<4.6.5": ">=4.12.28" "linkifyjs@<4.3.2": ">=4.3.3" "nanoid@>=4.0.0 <5.0.9": ">=5.1.16" "prismjs@<1.30.0": ">=1.30.0" From c56b91570bdcd6ece84d4aa0db268e92162ad25a Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 03:01:23 +0000 Subject: [PATCH 07/54] chore(deps): update tiptap monorepo to v3.27.2 (#6292) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 666 +++++++++++++++++++++++--------------------- pnpm-workspace.yaml | 32 +-- 2 files changed, 367 insertions(+), 331 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 2626a1cbb..389ce2dae 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,53 +151,53 @@ catalogs: specifier: ^12.0.4 version: 12.0.4 '@tiptap/extension-color': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-highlight': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-image': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-link': - specifier: ^3.27.1 - version: 3.27.1 + specifier: ^3.27.2 + version: 3.27.3 '@tiptap/extension-placeholder': - specifier: ^3.27.1 - version: 3.27.1 + specifier: ^3.27.2 + version: 3.27.3 '@tiptap/extension-table': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-table-cell': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-table-header': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-table-row': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-task-item': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-task-list': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-text-align': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-text-style': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/extension-underline': - specifier: 3.27.1 - version: 3.27.1 + specifier: 3.27.2 + version: 3.27.2 '@tiptap/react': - specifier: ^3.27.1 - version: 3.27.1 + specifier: ^3.27.2 + version: 3.27.3 '@tiptap/starter-kit': - specifier: ^3.27.1 - version: 3.27.1 + specifier: ^3.27.2 + version: 3.27.3 '@trpc/client': specifier: ^11.18.0 version: 11.18.0 @@ -907,7 +907,7 @@ importers: version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/tiptap': specifier: 'catalog:' - version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tiptap/extension-link@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/react@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tiptap/extension-link@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))(@tiptap/react@3.27.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@modelcontextprotocol/sdk': specifier: 'catalog:' version: 1.29.0(zod@4.4.3) @@ -2598,52 +2598,52 @@ importers: version: 3.44.0(react@19.2.7) '@tiptap/extension-color': specifier: 'catalog:' - version: 3.27.1(@tiptap/extension-text-style@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))) + version: 3.27.2(@tiptap/extension-text-style@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))) '@tiptap/extension-highlight': specifier: 'catalog:' - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/extension-image': specifier: 'catalog:' - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/extension-link': specifier: 'catalog:' - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + version: 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) '@tiptap/extension-placeholder': specifier: 'catalog:' - version: 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + version: 3.27.3(@tiptap/extensions@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-table': specifier: 'catalog:' - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) '@tiptap/extension-table-cell': specifier: 'catalog:' - version: 3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-table-header': specifier: 'catalog:' - version: 3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-table-row': specifier: 'catalog:' - version: 3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-task-item': specifier: 'catalog:' - version: 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-task-list': specifier: 'catalog:' - version: 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-text-align': specifier: 'catalog:' - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/extension-text-style': specifier: 'catalog:' - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/extension-underline': specifier: 'catalog:' - version: 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/react': specifier: 'catalog:' - version: 3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + version: 3.27.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@tiptap/starter-kit': specifier: 'catalog:' - version: 3.27.1 + version: 3.27.3 clsx: specifier: 'catalog:' version: 2.1.1 @@ -7349,215 +7349,226 @@ packages: '@testcontainers/redis@12.0.4': resolution: {integrity: sha512-44Ov3Lcptb3KqljcCy6xIJ2VxNsN+3Kw/YpBiotZpOcI4iLgLNmcOgvCFPVo+vDoh4rCfgtNUEbIpL5Mu7oRuw==} - '@tiptap/core@3.27.1': - resolution: {integrity: sha512-rV6Qn4wmC6BxfF+4mu6bqGWj9vA4oXXhsrpXaJL2uhjxeHAGofjwcHof2X84VYzeyXgdlsGmqKie4TAppVXZUQ==} + '@tiptap/core@3.27.3': + resolution: {integrity: sha512-TJj5929M96C1KlH796wS8MywfHDh49RhmakOyzyMMc9pFmRj9UXi1gj0TCXgsZtjEOG7B+m/DRvNOvnuvR9kmg==} peerDependencies: - '@tiptap/pm': 3.27.1 + '@tiptap/pm': 3.27.3 - '@tiptap/extension-blockquote@3.27.1': - resolution: {integrity: sha512-VMF7xJx6qEGiX6DTKNiL31NLqypOcd/4sNjFSe8rb41PwejBJh/nOqVIbBvWkiT6NMGFLxMhj7zJ8/zPo1hXeg==} + '@tiptap/extension-blockquote@3.27.3': + resolution: {integrity: sha512-VGoVMcqcGkkoduzEqQ70ZK5OOHBDn5iYeHJSgkvNoSHzYHl0CaKoWxoDKJjSWC5DAM4mBU+tXTlsiUp3evRMfA==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extension-bold@3.27.1': - resolution: {integrity: sha512-TlC5bsS+pqETTrlz4CZz9RO/cKBYtELGIxwtKeivUn3eNfnOxQbbu4WDsiwIfzRFyd0OMnKl6BPM2KnYEehoEQ==} + '@tiptap/extension-bold@3.27.3': + resolution: {integrity: sha512-pchbycFppBqBmvk+OxrQLIPZLNd3rNbcm7zOa+OTjluphpAcsJ6AmHBmiRhJUYWitV6/SA+0nujcaWxBp5hNcQ==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extension-bubble-menu@3.27.1': - resolution: {integrity: sha512-j/j8Qp9Z5nViade2m7zjrO/CYH/Ca80Qj7aqo0eUaei6FZQ5izlF9o4XQU5EFMAutV6mwynsPUp8FVo5sCuYfw==} + '@tiptap/extension-bubble-menu@3.27.3': + resolution: {integrity: sha512-PKSM9g8BXzO0Lxm2gOin7FAFvxO6T0QvlXTXVke/GJn/sR1tEz7w5ybsvweytUPmSliUc601pfIUOHKEhIVC2Q==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3 + '@tiptap/pm': 3.27.3 - '@tiptap/extension-bullet-list@3.27.1': - resolution: {integrity: sha512-faCUHnRP47o9Zh9VZZX6EX/569udw9Vopm2PgEKPWuKLE2qaS5WBuUVU0iItdJmKUqaWiOZkpoW4jvnDmj0dfg==} + '@tiptap/extension-bullet-list@3.27.3': + resolution: {integrity: sha512-owQW6mcgnYgQU1z5BqmslcjZFBUm1SGM/Ax++4R6XelIt/7ol6uSTjzjAzum+ASW7UlBbuJHuSgRjl8cA+RGkg==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/extension-list': 3.27.3 - '@tiptap/extension-code-block@3.27.1': - resolution: {integrity: sha512-pHlzmZx2OlHfyQ0yRlT5UL4mGokz947DthZuYefN1OleVqOkHpWBG+2JQwqoNq6bmzMne92zbH32rhcJUEYSjA==} + '@tiptap/extension-code-block@3.27.3': + resolution: {integrity: sha512-3CVnzkpGoqqI5KaXI9l9PMwLkx34SYY63tpeo0I0QjDk4LU+JTAQDTPJFSwB4zTsHZ7ZFMU0jjasFOZ1kZRVgw==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3 + '@tiptap/pm': 3.27.3 - '@tiptap/extension-code@3.27.1': - resolution: {integrity: sha512-epOUpFfEmBzjvnqvjv2qHX7NAuLo5dlOGV690lWu+sAYMjibuJBeVvAiKPyFCfRCCTUxdbDB3jbaOA1yEcEJ7w==} + '@tiptap/extension-code@3.27.3': + resolution: {integrity: sha512-T7JF10Y3k7fyVujXXLVubdUh7fctgBUoiuxbvjotpltg1U/mPbOcT0UW/uif9j3H8TFvAwNApVw3x8bwwFUakA==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extension-color@3.27.1': - resolution: {integrity: sha512-9R/vW4dDTce0E0HU+AyfSkKhz08Hlrx7noRRj3CKrTwsCTR86wReTKFHlzxhLKZiDrJuV2Y7CtNG7KGrAArf+A==} + '@tiptap/extension-color@3.27.2': + resolution: {integrity: sha512-X2Mo7y/49OFg/5I9B2APgXd1DsxseLXyvnvpFBGGj1AYqYGxvvJ9thvyLZr7SZVGjnAlbu3kwI2lhRQdyyIq8w==} peerDependencies: - '@tiptap/extension-text-style': 3.27.1 + '@tiptap/extension-text-style': 3.27.2 - '@tiptap/extension-document@3.27.1': - resolution: {integrity: sha512-8FbBTkfnRP4iVaoj+2h3iWa+H0eGDD3yTyVCwrmue/sQTkqUNUoSuAZa3GDG4Sd41xdPwTJxl9nUWGgM1qDCnw==} + '@tiptap/extension-document@3.27.3': + resolution: {integrity: sha512-PR25MRv56Nm4ETVLwA8nAh/0RrVdvD4D2nxnAC8kUkxz1WOYDaqTfPqqEeSWDoUUTUp1I1xzDkD4x0gEnAlmZA==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extension-dropcursor@3.27.1': - resolution: {integrity: sha512-blFf9x9RG0Qr7P3FoAH/033ffa+mMLZn34trVs8Vi0Ppk6FmJAg5HpYFOtmYoeREdNDJ5rHJKV7SoACbOHgskQ==} + '@tiptap/extension-dropcursor@3.27.3': + resolution: {integrity: sha512-XsL0Ziual+ynXyx5JgLb0KIOShlMN3MW7+GQvf9PGRb1vOB7IOTRFDxMPrxDWcFIZ6WWMWSrbkCLOKHStt1BWg==} peerDependencies: - '@tiptap/extensions': 3.27.1 + '@tiptap/extensions': 3.27.3 - '@tiptap/extension-floating-menu@3.27.1': - resolution: {integrity: sha512-BmJF1VqB7dSJkgAalrpVFj88WLhxKjcWPuWHOqf2ITrUU2832BhKLXKmxjWUy1gqV8PfNNVWtGfIERy7I0y0+Q==} + '@tiptap/extension-floating-menu@3.27.3': + resolution: {integrity: sha512-qL+g1Z6MqZvYG4mHTPwub9II2fWZY+2I5VFfV8fzqy1HchBOyY6Df0mRKDNb0bmjBdFabIXnswz3iqLx3mDTbw==} peerDependencies: '@floating-ui/dom': ^1.0.0 - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3 + '@tiptap/pm': 3.27.3 - '@tiptap/extension-gapcursor@3.27.1': - resolution: {integrity: sha512-QoezN0wdvXIwLQ4ee2ccWDaX3RG0lzgQpIMpMz55oPDhpUVax1+19ApsS53LkcktpS4EbnPL4xO4DaJk0Vp7PQ==} + '@tiptap/extension-gapcursor@3.27.3': + resolution: {integrity: sha512-MVBqzwYrDOttkn2GOrvvKTDCymfSS2VGSKpahcKKhge5mweSDiqko2X1vBDnMF8aVW3WxIFfDMmO4VMmh8HDZA==} peerDependencies: - '@tiptap/extensions': 3.27.1 + '@tiptap/extensions': 3.27.3 - '@tiptap/extension-hard-break@3.27.1': - resolution: {integrity: sha512-iv/m9hzl6jfSj9Q8UEjAxONvCoUDaP7M9SRCPx3PaLNxA230TTD6RE0Ye4zFJ8ze7ZVoJJMAqg9Qpq1iYg2JOQ==} + '@tiptap/extension-hard-break@3.27.3': + resolution: {integrity: sha512-cySObJITR2CRrWII87mI2LJ12wEETTnxt2p1vvC6ZtUmKnB50fOu+ux8ZEt6KYNocIOtrEZthNFzTOx7R7UtrQ==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extension-heading@3.27.1': - resolution: {integrity: sha512-SrC4l1kEIyv9ZXFaI/8LQqU2MyMmjczw7XXsWUQOTN4YXv0JyVgMNR3cI/wz0d2xsTfBdZ1N85Tdng+Ga1t0Sg==} + '@tiptap/extension-heading@3.27.3': + resolution: {integrity: sha512-QHXnsNic6iId8pnsFZ8z4PkX5L+HCHa/D7rAi3nNWtPlSIAOxo4nKrALcB5/tHmY+XL8kEXKH3nsNLNEDLCYPg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extension-highlight@3.27.1': - resolution: {integrity: sha512-IeMIUKbW4oyRkgn92BPYQ0fifkbTwVigKwa107nGDHMokz+pHQPFHy8xG3U7gtUOra/8OUo57bFgNGuP0TaH4A==} + '@tiptap/extension-highlight@3.27.2': + resolution: {integrity: sha512-+PhUkhh8g9a005FvwtDVnxRzadLO2tBWY4plUfJJDDpFIC0lTSxc0Q6ESBEV4x/BnuClH/RKH+tZAhkyEqeTeQ==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.2 - '@tiptap/extension-horizontal-rule@3.27.1': - resolution: {integrity: sha512-QlKE7qn5qMnIGVGhXQlvYedvLtNJ9z0dmit5w8vPb8tKzW4Spk6M7N2kruprrDA8GBwHfeR5wmF+njfUm34qxg==} + '@tiptap/extension-horizontal-rule@3.27.3': + resolution: {integrity: sha512-sNCuo0+Q001B5XTVWY+ULpWXQqtBNRmAqXxAexZl44bx3rDqHG8OzjwT4EM0LIj2+BYVbdqwaJ1vfQbZVEEbig==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3 + '@tiptap/pm': 3.27.3 - '@tiptap/extension-image@3.27.1': - resolution: {integrity: sha512-+JTahgQT+NxiGjduaB3qJVyhU/wh4m3pVkht1Earioku2bm/apj5Lb8rSowa/NJYP3B+oQgV/V4YLw5dtDgBoA==} + '@tiptap/extension-image@3.27.2': + resolution: {integrity: sha512-xnDCj+9oNwr4Zh9B+HHi+9lXyUVYv2KmCF3QuCdGxhZA4FDNlJT3gISwYnH4LNtKSD1XHh/gSCMTpZF+8oqe3g==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.2 - '@tiptap/extension-italic@3.27.1': - resolution: {integrity: sha512-jGGeyn9uRUnNjSTHpbqhiGsp6KaYTSbV09jDXPJI9cDwfV9hpugLvpaCZd0BMBbhU1B1W6kOfX0BE15qX/HQfA==} + '@tiptap/extension-italic@3.27.3': + resolution: {integrity: sha512-7VpVi2vn8kGFAc/HLrt96J/E6+LXOGKn8xLhJS863t150yLIG3EPQLA1h+G5O6eo+/w+9YokvJAAJoeDihj1Jg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extension-link@3.27.1': - resolution: {integrity: sha512-/2jBfsxBZUDGJmpZifqRQPz7f1E5qpS1BckTZ39TADzUJX+feKy7RJ3DtQ02+8y6SSMzvP9loGVjrk6zEMTk4g==} + '@tiptap/extension-link@3.27.3': + resolution: {integrity: sha512-iXmu1tJ/3vP60c9d+zfO3+X64vod2EyBrM1qeufecAtWA6t0bT9tYeka0Zq6qDRGsZK5zViO9F2lsY7eHXiYuA==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3 + '@tiptap/pm': 3.27.3 - '@tiptap/extension-list-item@3.27.1': - resolution: {integrity: sha512-zwRl01ETfCkWUvtvK5fw9bXtAajMPkvlkE3Cq6JvH3LF7XXJwDtNj5Tj7exacMpCaSZmlNc43vFb2rAYnrnwMA==} + '@tiptap/extension-list-item@3.27.3': + resolution: {integrity: sha512-UddhVaQ7+V9IfQVVE8AJLNzSmyaaNpZPQsoPQxFNlJyOFxD6RwSTT8L4rb/TwbUctxSFWpyd1J7exzf33Ony/g==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/extension-list': 3.27.3 - '@tiptap/extension-list-keymap@3.27.1': - resolution: {integrity: sha512-OIMZNlzPSO8WRd4ic73Fxckzl4N1tesjjLL2XApaNA/uMpO0LoF6WSRPAWv+Z24Wp92ARRJAnRP7iZoI5+Jxig==} + '@tiptap/extension-list-keymap@3.27.3': + resolution: {integrity: sha512-GZPVg4QCFm41yTMPEPeTlH2odNeQwRuWHY/pt3pI5LsfipQ4kDBv6h9we928vJQUnU2Gg9Y3cL5iRbmXIWWTUA==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/extension-list': 3.27.3 - '@tiptap/extension-list@3.27.1': - resolution: {integrity: sha512-c2Upru7lj0/ZV/Ibww6cNz6sUS8m6Dp/9uygFhYcZOd3X8M0xBIEk42c6m6SQehkPziVA8QOgNJz7sMqsbz1OQ==} + '@tiptap/extension-list@3.27.2': + resolution: {integrity: sha512-SsVnh5ruM9QjgOGbhJjegHAIADuoE+9Sb0UV+lyxAeuEwO98JpVmE926Yur9abzUAA9MWZZ/XBIzoA5q0a4dBQ==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.2 + '@tiptap/pm': 3.27.2 - '@tiptap/extension-ordered-list@3.27.1': - resolution: {integrity: sha512-GYrKqD//9nHJ2r80uXqbDMzRnFpGzbaEQRTSGaO/SH7DvXWFMow8evkOdjQ7PCQO07jNjJo75+A85Jwu3Ov3AA==} + '@tiptap/extension-list@3.27.3': + resolution: {integrity: sha512-52rcaSzYjtVGUrNkdB8k1Bw/rId2lBirrWMvmnBLsieoeFd8zo8ow5zkwV057BYSgWXfnMAV7GOpDyq2IoSD9A==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/core': 3.27.3 + '@tiptap/pm': 3.27.3 - '@tiptap/extension-paragraph@3.27.1': - resolution: {integrity: sha512-7K7eo1gruOgAsnbK+GCV23AUVUI0cL1bTig8HaPneoFMVbig7vddk8jNLKBWO8TXVbG7TuHdnDN4F98vdtwh5Q==} + '@tiptap/extension-ordered-list@3.27.3': + resolution: {integrity: sha512-oHYoE65WfRSM5tCcXi8MtQmzPxz83E2qw0pa2yHBKwr9xCCQUu8vJHJE+EJGduWdaNImv43+6/6kVMeYYbXRKg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/extension-list': 3.27.3 - '@tiptap/extension-placeholder@3.27.1': - resolution: {integrity: sha512-lhcNDcczQ75yJOSywCHb58Hmtg1aPL/2TdYmeLPxVrP048D7rRs133sfONcgyyw0AvhnfmPOkHLTv3QtKSowhw==} + '@tiptap/extension-paragraph@3.27.3': + resolution: {integrity: sha512-G5XPCN7lm0nULYPelJC2eUbKbt1q97j67e/HSWxwMYEXhceec5eNAtVfpDCRyXNO9osyBi2kXqLv/IQtxbch9A==} peerDependencies: - '@tiptap/extensions': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extension-strike@3.27.1': - resolution: {integrity: sha512-Y3DW1jlSlCNCyMGHP3+3qBNNPS83wuFz4RTYGjZtvRRTCRh7apZme9XRWMq1rN5mJ2Cr7fKocA2/5Bs13KgN6Q==} + '@tiptap/extension-placeholder@3.27.3': + resolution: {integrity: sha512-Y43dGaN3rA0oU4CWmXkZ2idjZjrxPX9oy0zZL92EgXOguhAUToBs0dIxRG5ZanYX9A8kKKWPlfeRFlBMtRhKvg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/extensions': 3.27.3 - '@tiptap/extension-table-cell@3.27.1': - resolution: {integrity: sha512-aopweoHugP+ZR5WTgb7mccXXWmjoNv7PBjtQDl8rvS4IWsJE1w23B+rkj7VuoncO8WK5bU3SS9O3l5aObql68w==} + '@tiptap/extension-strike@3.27.3': + resolution: {integrity: sha512-dp6Rs3I4zLVJPvAw2Q9yrNuYKcX5LTONH3/oyULvGsrqq5DKz7pqscfwhwnDSzrgm/3aCz6B9r1BVHlYI/DXlA==} peerDependencies: - '@tiptap/extension-table': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extension-table-header@3.27.1': - resolution: {integrity: sha512-+J9EyKP6RMInfMJbIoNemqWzHxZe9XZv/7OjpsvtVZO1cxMTd5W1xIg0Ntr7YMdG9eKBC8Vwx5jjFbRm2tbM3A==} + '@tiptap/extension-table-cell@3.27.2': + resolution: {integrity: sha512-OD0Zui+/3pT8XwkHWjOMrcyALrnImcOQ+Khg/4pJb+5KoKCkExBqYeH2Zq8Gz5yAO1r9ICrQvl/Kjil1JnOZuA==} peerDependencies: - '@tiptap/extension-table': 3.27.1 + '@tiptap/extension-table': 3.27.2 - '@tiptap/extension-table-row@3.27.1': - resolution: {integrity: sha512-peEcbNRrJJ9Qc/WNKQNFbkwBrJvBvq22Dp5WNqEygn/ZJez1qD9ctd10+D6f3b9GtSyx12sfDgn8yBufWa+Iug==} + '@tiptap/extension-table-header@3.27.2': + resolution: {integrity: sha512-HWbSsuYqxGT+trU9GP/EnX4tF4p+pq/YVrQKu4qPK+gFMJUG/FiL2OH/eNRh48r+EXp+VM6znYcIHrfUhNroNw==} peerDependencies: - '@tiptap/extension-table': 3.27.1 + '@tiptap/extension-table': 3.27.2 - '@tiptap/extension-table@3.27.1': - resolution: {integrity: sha512-tNB8kjxo0+XPremnWkd6NUikpeJ+JQrss7HntL8GgIxX4t0gkdnLn9yUWb0JzcaYP7Y8iTZZHdkPs0P154DG8Q==} + '@tiptap/extension-table-row@3.27.2': + resolution: {integrity: sha512-X+HxuA5aTLrGGUHKPVKrVRyZt+zst4fxG5r89ExFH96bWHs3hLQGZnIDEBpOVsMFl65FAUxE0ycw/Vh/xAT5NQ==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/extension-table': 3.27.2 - '@tiptap/extension-task-item@3.27.1': - resolution: {integrity: sha512-HxS85Xqlf9voWpqW6yJNExjnqy9AcQbvL7K41gzKUIaXZdNxAyEkUg7wve7WAd7AUPI9yrS9WONplhmq7pWuJw==} + '@tiptap/extension-table@3.27.2': + resolution: {integrity: sha512-lOuapZby/RgOmrAg5OlTIh/4li8qYhbbTJLKXGq+vCl2BlWLZBMXrhdzPnIqqCiD7mLhNoOPA4kQOohJUah+ww==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/core': 3.27.2 + '@tiptap/pm': 3.27.2 - '@tiptap/extension-task-list@3.27.1': - resolution: {integrity: sha512-5LaJU3q/O6S1aNe9uj/VbZ7uS8G9mJMvJGNm/wRGyR3HcZtHAcFsPh9+woylQvUThD0qO9OMeGXkgqRSE+ayuA==} + '@tiptap/extension-task-item@3.27.2': + resolution: {integrity: sha512-6JiIlSncQatTCSjy8ECJbmNJbNbbrDkLOI7tzVroo72fuHrjF6W87Fr9EmSOCbYeZF54cr6QksogeZhF9rUmfw==} peerDependencies: - '@tiptap/extension-list': 3.27.1 + '@tiptap/extension-list': 3.27.2 - '@tiptap/extension-text-align@3.27.1': - resolution: {integrity: sha512-EXawuJBO55wd8WcTbHTMoPhv0CGQxza4yCCPB5Hqz4ZPQwahIr3ej+8yp/kimIl0xokabwZ0/Fu8STQ4AkZv5g==} + '@tiptap/extension-task-list@3.27.2': + resolution: {integrity: sha512-T3EgjXeiFv4kxZV4TFgAFAbERS2IQ3KjXgWMsMUVM44H1E/VW/2c25Ut2EOZiZ8ZTcBbyJ7aWCx2rbpHa2r/VQ==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/extension-list': 3.27.2 - '@tiptap/extension-text-style@3.27.1': - resolution: {integrity: sha512-J48WIl+6YDYTFPhWXUBQk+u7+AKVUqTdvrZOQyPYCGuQMgHrYzgWrI5+HeEifUgXJ5rMIWWP3qytp7KhVVqpDQ==} + '@tiptap/extension-text-align@3.27.2': + resolution: {integrity: sha512-38MH2LOYzZPbJMFoL+M+NpIUpI2pMefTVRuqlHSNoAdn6wEyJoByEeceRab2vt7/S4w9lWcWxwzsXEa38G/5WQ==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.2 - '@tiptap/extension-text@3.27.1': - resolution: {integrity: sha512-6ZwaZwSrDh+KFFv6V1J79oO37yPs7y1bFxvk1/9Ih2rn3Xr5AWz+eMS+n8RpH3djBVVAQpdIAeYQgcn+VCSsTg==} + '@tiptap/extension-text-style@3.27.2': + resolution: {integrity: sha512-kgGg3LXYnEEttqM6CsiSLxRVU5055vON1T5xipskmbdDtV/xXTykzbISiX3ND5zYw+BG7hF8COEKLt1iLo6LbA==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.2 - '@tiptap/extension-underline@3.27.1': - resolution: {integrity: sha512-N889J4nXN/TPfVt8uF9N1A0SY82E90zwc1y26lqOcw6KWNLmQrlhMh/9OD4ikLDbekmFpOBq/UicpHf/6S8hbQ==} + '@tiptap/extension-text@3.27.3': + resolution: {integrity: sha512-wSatZ/bop0pleeC18nF90zvWWEoRe/ewZncvm8LjcMrXwq41kTEs3j7nZflMYAh7X8fZvu00UIMeEyCNJl+5Yg==} peerDependencies: - '@tiptap/core': 3.27.1 + '@tiptap/core': 3.27.3 - '@tiptap/extensions@3.27.1': - resolution: {integrity: sha512-1Tdx9faw8k0/83V6X+xCDVhV8yElGt95JxeW3YMkKQJI56QdlPz0xOdJPlMiSGJKinPyVier+x9LJD/YZUZIaw==} + '@tiptap/extension-underline@3.27.2': + resolution: {integrity: sha512-5/1XpKxBqAkiR6ND2P/jcPQ06Opcz/Nidd6dasMkjxwuRJPU5OSz3Qy1fsfLlduRU8TzSqjT8vpQJgaqsHqbLA==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.2 - '@tiptap/pm@3.27.1': - resolution: {integrity: sha512-Ffjx+vimmBU7zH/KrpXzJid3+pziCe/VL2aexSTP63cyQwKQ65LkFkCKaIsSpFdQQuakVZBGWjCA5RoBV852pw==} - - '@tiptap/react@3.27.1': - resolution: {integrity: sha512-/Wn2fc9zMtX08MXYScDFsm4wJ8lzfhfPEdbtls7WCDlbtrop48PWlkHDBBJrywARfAQTB2mFs9KiFy9yrQm5Lg==} + '@tiptap/extension-underline@3.27.3': + resolution: {integrity: sha512-YH7ka/Rp7ZwlzTuQCcs0OZ4CMSx/P/MzbseQKxKebEIZreWaKMZAaED97Fnu0dA66VlreD4/1C7xHJy84ovamg==} peerDependencies: - '@tiptap/core': 3.27.1 - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3 + + '@tiptap/extensions@3.27.3': + resolution: {integrity: sha512-IA2QKUVJgzUPEhhkUfr6P5u5GFVfhiApiEaaHXBz07saL2c4HNoVs2RC18QlZDCtLNKrPR1mUScr72u7uYs+YQ==} + peerDependencies: + '@tiptap/core': 3.27.3 + '@tiptap/pm': 3.27.3 + + '@tiptap/pm@3.27.3': + resolution: {integrity: sha512-ppiG57RxM3HSHHgcHT0hP6Ib4P56Sd5itxdV4w0hIGHKPGyLLKiAkYp+htIHa9T7IvjMdaqxIlsfyV3ZKsS1sw==} + + '@tiptap/react@3.27.3': + resolution: {integrity: sha512-lanXxMScw/LevbRFuFw5MoQJ1grmHUDszwph2VIk4l2U5e/EDIw3rr4CeDME6Z4isVReD6zccMFVShkHqsv+jg==} + peerDependencies: + '@tiptap/core': 3.27.3 + '@tiptap/pm': 3.27.3 '@types/react': ^17.0.0 || ^18.0.0 || ^19.0.0 '@types/react-dom': ^17.0.0 || ^18.0.0 || ^19.0.0 react: ^17.0.0 || ^18.0.0 || ^19.0.0 react-dom: ^17.0.0 || ^18.0.0 || ^19.0.0 - '@tiptap/starter-kit@3.27.1': - resolution: {integrity: sha512-vfxRsqW8rCc0k4pzo0ilU3wobVi2wqVj88VZI2SlgZlNnUAkrDGDIAph7CTa9k9fshV+O1ivpEgPC5yC046jow==} + '@tiptap/starter-kit@3.27.3': + resolution: {integrity: sha512-xX3baFqiC30skntdhxUUvyJo755ON9c1pE83+2Wiq2g+Qnffg9knUuLCZStHoZZ318yB0aBsKAMvHWLtYDo8AA==} '@trpc/client@11.18.0': resolution: {integrity: sha512-wOqeg3Fvl25V1ZisQhUD3K8G60ZJDlSGJNSyeXrLH24xAo5w6GSR2Kzb1cSNY9Y+IQ2YZvYGZstBU+V/ulo/ow==} @@ -13207,20 +13218,20 @@ packages: property-information@7.0.0: resolution: {integrity: sha512-7D/qOz/+Y4X/rzSB6jKxKUsQnphO046ei8qxG59mtM3RG3DHgTK81HrxrmoDVINJb8NKT5ZsRbwHvQ6B68Iyhg==} - prosemirror-changeset@2.3.0: - resolution: {integrity: sha512-8wRKhlEwEJ4I13Ju54q2NZR1pVKGTgJ/8XsQ8L5A5uUsQ/YQScQJuEAuh8Bn8i6IwAMjjLRABd9lVli+DlIiVw==} + prosemirror-changeset@2.4.1: + resolution: {integrity: sha512-96WBLhOaYhJ+kPhLg3uW359Tz6I/MfcrQfL4EGv4SrcqKEMC1gmoGrXHecPE8eOwTVCJ4IwgfzM8fFad25wNfw==} - prosemirror-commands@1.6.2: - resolution: {integrity: sha512-0nDHH++qcf/BuPLYvmqZTUUsPJUCPBUXt0J1ErTcDIS369CTp773itzLGIgIXG4LJXOlwYCr44+Mh4ii6MP1QA==} + prosemirror-commands@1.7.1: + resolution: {integrity: sha512-rT7qZnQtx5c0/y/KlYaGvtG411S97UaL6gdp6RIZ23DLHanMYLyfGBV5DtSnZdthQql7W+lEVbpSfwtO8T+L2w==} - prosemirror-dropcursor@1.8.1: - resolution: {integrity: sha512-M30WJdJZLyXHi3N8vxN6Zh5O8ZBbQCz0gURTfPmTIBNQ5pxrdU7A58QkNqfa98YEjSAL1HUyyU34f6Pm5xBSGw==} + prosemirror-dropcursor@1.8.3: + resolution: {integrity: sha512-FoYbsJR8gK+DGlqhNoE29Loa38eIZPzQRIb1VMaDNBoo4OLP6vVof/jR8qFY/6XvUd6Dhug8MDCHl2a/h8RTfQ==} - prosemirror-gapcursor@1.3.2: - resolution: {integrity: sha512-wtjswVBd2vaQRrnYZaBCbyDqr232Ed4p2QPtRIUK5FuqHYKGWkEwl08oQM4Tw7DOR0FsasARV5uJFvMZWxdNxQ==} + prosemirror-gapcursor@1.4.1: + resolution: {integrity: sha512-pMdYaEnjNMSwl11yjEGtgTmLkR08m/Vl+Jj443167p9eB3HVQKhYCc4gmHVDsLPODfZfjr/MmirsdyZziXbQKw==} - prosemirror-history@1.4.1: - resolution: {integrity: sha512-2JZD8z2JviJrboD9cPuX/Sv/1ChFng+xh2tChQ2X4bB2HeK+rra/bmJ3xGntCcjhOqIzSDG6Id7e8RJ9QPXLEQ==} + prosemirror-history@1.5.0: + resolution: {integrity: sha512-zlzTiH01eKA55UAf1MEjtssJeHnGxO0j4K4Dpx+gnmX9n+SHNlDqI2oO1Kv1iPN5B1dm5fsljCfqKF9nFL6HRg==} prosemirror-inputrules@1.5.1: resolution: {integrity: sha512-7wj4uMjKaXWAQ1CDgxNzNtR9AlsuwzHfdFH1ygEHA2KHF2DOEaXl1CJfNPAKCg9qNEh4rum975QLaCiQPyY6Fw==} @@ -13228,6 +13239,9 @@ packages: prosemirror-keymap@1.2.3: resolution: {integrity: sha512-4HucRlpiLd1IPQQXNqeo81BGtkY8Ai5smHhKW9jjPKRc2wQIxksg7Hl1tTI2IfT2B/LgX6bfYvXxEpJl7aKYKw==} + prosemirror-model@1.25.10: + resolution: {integrity: sha512-9n6rH4DbJU1eH4SxLt6Y0HhJIo6cZsb7DJ/30uob1hOKPeO6TAaMWI2tc7kwR92BjfPOU2fFHWbZLovLi3XQfA==} + prosemirror-model@1.25.7: resolution: {integrity: sha512-A79aN8QEFUwI6cax8Yq4Rpcx1TJZ3Kagn+ii7qLo4/V8H3mMiHrhFyhTyHHvpSnOgMPpWiDGSwM3etwrxE50ug==} @@ -13246,6 +13260,9 @@ packages: prosemirror-view@1.41.8: resolution: {integrity: sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==} + prosemirror-view@1.42.0: + resolution: {integrity: sha512-N54DF3OXNWDuP81G1kbfCys8ZzIjuL1VnvJ2mk5STSu/fNxWIcX/EutQLA3s9KR/2wVhgDi4hzBB/1fINVxk0A==} + proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -19090,12 +19107,12 @@ snapshots: dependencies: react: 19.2.7 - '@mantine/tiptap@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tiptap/extension-link@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))(@tiptap/react@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@mantine/tiptap@9.4.1(@mantine/core@9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(@mantine/hooks@9.4.1(react@19.2.7))(@tiptap/extension-link@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))(@tiptap/react@3.27.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7))(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: '@mantine/core': 9.4.1(@mantine/hooks@9.4.1(react@19.2.7))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) '@mantine/hooks': 9.4.1(react@19.2.7) - '@tiptap/extension-link': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/react': 3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) + '@tiptap/extension-link': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/react': 3.27.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) react: 19.2.7 react-dom: 19.2.7(react@19.2.7) @@ -20999,187 +21016,196 @@ snapshots: - bare-buffer - supports-color - '@tiptap/core@3.27.1(@tiptap/pm@3.27.1)': + '@tiptap/core@3.27.3(@tiptap/pm@3.27.3)': dependencies: - '@tiptap/pm': 3.27.1 + '@tiptap/pm': 3.27.3 - '@tiptap/extension-blockquote@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-blockquote@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-bold@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-bold@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-bubble-menu@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-bubble-menu@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: '@floating-ui/dom': 1.7.6 - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 optional: true - '@tiptap/extension-bullet-list@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-bullet-list@3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-code-block@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-code-block@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 - '@tiptap/extension-code@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-code@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-color@3.27.1(@tiptap/extension-text-style@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)))': + '@tiptap/extension-color@3.27.2(@tiptap/extension-text-style@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)))': dependencies: - '@tiptap/extension-text-style': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) + '@tiptap/extension-text-style': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) - '@tiptap/extension-document@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-document@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-dropcursor@3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-dropcursor@3.27.3(@tiptap/extensions@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extensions': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extensions': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-floating-menu@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-floating-menu@3.27.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: '@floating-ui/dom': 1.7.6 - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 optional: true - '@tiptap/extension-gapcursor@3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-gapcursor@3.27.3(@tiptap/extensions@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extensions': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extensions': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-hard-break@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-hard-break@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-heading@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-heading@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-highlight@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-highlight@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-horizontal-rule@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-horizontal-rule@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 - '@tiptap/extension-image@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-image@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-italic@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-italic@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-link@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-link@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 linkifyjs: 4.3.3 - '@tiptap/extension-list-item@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-list-item@3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-list-keymap@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-list-keymap@3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 - '@tiptap/extension-ordered-list@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 - '@tiptap/extension-paragraph@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-ordered-list@3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-placeholder@3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-paragraph@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extensions': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-strike@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-placeholder@3.27.3(@tiptap/extensions@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/extensions': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-table-cell@3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-strike@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-table': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-table-header@3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-table-cell@3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-table': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-table': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-table-row@3.27.1(@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-table-header@3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-table': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-table': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-table@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-table-row@3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/extension-table': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-task-item@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 - '@tiptap/extension-task-list@3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1))': + '@tiptap/extension-task-item@3.27.2(@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-text-align@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-task-list@3.27.2(@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/extension-list': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-text-style@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-text-align@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-text@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-text-style@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-underline@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))': + '@tiptap/extension-text@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)': + '@tiptap/extension-underline@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/pm@3.27.1': + '@tiptap/extension-underline@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: - prosemirror-changeset: 2.3.0 - prosemirror-commands: 1.6.2 - prosemirror-dropcursor: 1.8.1 - prosemirror-gapcursor: 1.3.2 - prosemirror-history: 1.4.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + + '@tiptap/extensions@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': + dependencies: + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 + + '@tiptap/pm@3.27.3': + dependencies: + prosemirror-changeset: 2.4.1 + prosemirror-commands: 1.7.1 + prosemirror-dropcursor: 1.8.3 + prosemirror-gapcursor: 1.4.1 + prosemirror-history: 1.5.0 prosemirror-inputrules: 1.5.1 prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.7 + prosemirror-model: 1.25.10 prosemirror-schema-list: 1.5.1 prosemirror-state: 1.4.4 prosemirror-tables: 1.8.5 prosemirror-transform: 1.12.0 - prosemirror-view: 1.41.8 + prosemirror-view: 1.42.0 - '@tiptap/react@3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': + '@tiptap/react@3.27.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7)': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 '@types/react': 19.2.17 '@types/react-dom': 19.2.3(@types/react@19.2.17) '@types/use-sync-external-store': 0.0.6 @@ -21188,37 +21214,37 @@ snapshots: react-dom: 19.2.7(react@19.2.7) use-sync-external-store: 1.6.0(react@19.2.7) optionalDependencies: - '@tiptap/extension-bubble-menu': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-floating-menu': 3.27.1(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) + '@tiptap/extension-bubble-menu': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-floating-menu': 3.27.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) transitivePeerDependencies: - '@floating-ui/dom' - '@tiptap/starter-kit@3.27.1': + '@tiptap/starter-kit@3.27.3': dependencies: - '@tiptap/core': 3.27.1(@tiptap/pm@3.27.1) - '@tiptap/extension-blockquote': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-bold': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-bullet-list': 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-code': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-code-block': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-document': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-dropcursor': 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-gapcursor': 3.27.1(@tiptap/extensions@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-hard-break': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-heading': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-horizontal-rule': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-italic': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-link': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-list': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/extension-list-item': 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-list-keymap': 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-ordered-list': 3.27.1(@tiptap/extension-list@3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1)) - '@tiptap/extension-paragraph': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-strike': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-text': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extension-underline': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1)) - '@tiptap/extensions': 3.27.1(@tiptap/core@3.27.1(@tiptap/pm@3.27.1))(@tiptap/pm@3.27.1) - '@tiptap/pm': 3.27.1 + '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) + '@tiptap/extension-blockquote': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-bold': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-bullet-list': 3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + '@tiptap/extension-code': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-code-block': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-document': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-dropcursor': 3.27.3(@tiptap/extensions@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + '@tiptap/extension-gapcursor': 3.27.3(@tiptap/extensions@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + '@tiptap/extension-hard-break': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-heading': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-horizontal-rule': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-italic': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-link': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-list': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-list-item': 3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + '@tiptap/extension-list-keymap': 3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + '@tiptap/extension-ordered-list': 3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + '@tiptap/extension-paragraph': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-strike': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-text': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-underline': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extensions': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/pm': 3.27.3 '@trpc/client@11.18.0(@trpc/server@11.18.0(typescript@6.0.3))(typescript@6.0.3)': dependencies: @@ -27543,30 +27569,30 @@ snapshots: property-information@7.0.0: {} - prosemirror-changeset@2.3.0: + prosemirror-changeset@2.4.1: dependencies: prosemirror-transform: 1.12.0 - prosemirror-commands@1.6.2: + prosemirror-commands@1.7.1: dependencies: prosemirror-model: 1.25.7 prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 - prosemirror-dropcursor@1.8.1: + prosemirror-dropcursor@1.8.3: dependencies: prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 prosemirror-view: 1.41.8 - prosemirror-gapcursor@1.3.2: + prosemirror-gapcursor@1.4.1: dependencies: prosemirror-keymap: 1.2.3 prosemirror-model: 1.25.7 prosemirror-state: 1.4.4 prosemirror-view: 1.41.8 - prosemirror-history@1.4.1: + prosemirror-history@1.5.0: dependencies: prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 @@ -27583,6 +27609,10 @@ snapshots: prosemirror-state: 1.4.4 w3c-keyname: 2.2.8 + prosemirror-model@1.25.10: + dependencies: + orderedmap: 2.1.1 + prosemirror-model@1.25.7: dependencies: orderedmap: 2.1.1 @@ -27617,6 +27647,12 @@ snapshots: prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 + prosemirror-view@1.42.0: + dependencies: + prosemirror-model: 1.25.10 + prosemirror-state: 1.4.4 + prosemirror-transform: 1.12.0 + proto-list@1.2.4: {} protobufjs@7.4.0: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 459d7cf0d..c3546c0c2 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -53,22 +53,22 @@ catalog: "@testcontainers/mysql": ^12.0.4 "@testcontainers/postgresql": ^12.0.4 "@testcontainers/redis": ^12.0.4 - "@tiptap/extension-color": 3.27.1 - "@tiptap/extension-highlight": 3.27.1 - "@tiptap/extension-image": 3.27.1 - "@tiptap/extension-link": ^3.27.1 - "@tiptap/extension-placeholder": ^3.27.1 - "@tiptap/extension-table": 3.27.1 - "@tiptap/extension-table-cell": 3.27.1 - "@tiptap/extension-table-header": 3.27.1 - "@tiptap/extension-table-row": 3.27.1 - "@tiptap/extension-task-item": 3.27.1 - "@tiptap/extension-task-list": 3.27.1 - "@tiptap/extension-text-align": 3.27.1 - "@tiptap/extension-text-style": 3.27.1 - "@tiptap/extension-underline": 3.27.1 - "@tiptap/react": ^3.27.1 - "@tiptap/starter-kit": ^3.27.1 + "@tiptap/extension-color": 3.27.2 + "@tiptap/extension-highlight": 3.27.2 + "@tiptap/extension-image": 3.27.2 + "@tiptap/extension-link": ^3.27.2 + "@tiptap/extension-placeholder": ^3.27.2 + "@tiptap/extension-table": 3.27.2 + "@tiptap/extension-table-cell": 3.27.2 + "@tiptap/extension-table-header": 3.27.2 + "@tiptap/extension-table-row": 3.27.2 + "@tiptap/extension-task-item": 3.27.2 + "@tiptap/extension-task-list": 3.27.2 + "@tiptap/extension-text-align": 3.27.2 + "@tiptap/extension-text-style": 3.27.2 + "@tiptap/extension-underline": 3.27.2 + "@tiptap/react": ^3.27.2 + "@tiptap/starter-kit": ^3.27.2 "@trpc/client": ^11.18.0 "@trpc/next": ^11.18.0 "@trpc/react-query": ^11.18.0 From d44bc06a93a72577d91bd5883f7a944661d0de95 Mon Sep 17 00:00:00 2001 From: Artur Date: Sun, 12 Jul 2026 07:58:02 +0200 Subject: [PATCH 08/54] feat(notebook): support text direction controls (#6293) --- apps/docs/docs/widgets/notebook/index.mdx | 60 ++++++++++++------- packages/translation/src/lang/en.json | 2 + packages/widgets/src/notebook/notebook.tsx | 20 +++++++ .../src/notebook/text-direction.spec.ts | 34 +++++++++++ .../widgets/src/notebook/text-direction.ts | 28 +++++++++ 5 files changed, 124 insertions(+), 20 deletions(-) create mode 100644 packages/widgets/src/notebook/text-direction.spec.ts create mode 100644 packages/widgets/src/notebook/text-direction.ts diff --git a/apps/docs/docs/widgets/notebook/index.mdx b/apps/docs/docs/widgets/notebook/index.mdx index 5728e0113..393137ed2 100644 --- a/apps/docs/docs/widgets/notebook/index.mdx +++ b/apps/docs/docs/widgets/notebook/index.mdx @@ -1,29 +1,25 @@ --- -title: 'Notebook' -description: 'A simple notebook widget that supports markdown.' +title: "Notebook" +description: "A simple notebook widget that supports markdown." hide_title: true --- -import { AddingWidget } from '@site/src/components/widgets/adding'; -import { WidgetConfig } from '@site/src/components/widgets/configuration'; -import { WidgetHeader } from '@site/src/components/widgets/header'; -import { notebookWidget } from '.'; +import { AddingWidget } from "@site/src/components/widgets/adding"; +import { WidgetConfig } from "@site/src/components/widgets/configuration"; +import { WidgetHeader } from "@site/src/components/widgets/header"; +import { notebookWidget } from "."; + - - -The notebook widget focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. +The notebook widget focuses on usability and is designed to be as simple as possible to bring a familiar editing experience to regular users. It is based on Tiptap.dev and supports most of its features. ### Screenshots -import editOff from './img/normal.png'; -import editOn from './img/edit.png'; +import editOff from "./img/normal.png"; +import editOn from "./img/edit.png"; -
+
notebook edit-off notebook edit-on
@@ -33,24 +29,27 @@ import editOn from './img/edit.png'; ### Configuration + ### Editing + When the permissions of the user allows it, you can enter the editing of the text by clicking the following button in the top right. ![](./img/notebook-enter-editing.webp) - You can then decide to save your changes or cancel them to exit editing, using the buttons placed in the same location. ![](./img/notebook-exit-editing.webp) ### Notebook toolbar functions -The notebook offers a variaty of functions to edit the text and more. + +The notebook offers a variety of functions to edit the text and more. ![](./img/notebook-toolbar.webp) #### Common text formatting + | Function | Description | | ---------------------- | ------------------------------------------------------------------- | | Bold | Makes the selected text bold | @@ -62,35 +61,51 @@ The notebook offers a variaty of functions to edit the text and more. | Clear formatting | Erases any of the preceding function's effects on the selected text | #### Heading + Choose a heading size for the line, making the text bigger. Working like titles and sub-titles. -#### Text Alignement -Select the alignement on the line. Left, center or right. +#### Text Alignment + +Select the alignment on the line. Left, center or right. + +#### Text direction + +Set the current paragraph or heading to left-to-right or right-to-left. This is useful when writing right-to-left text while Homarr is using a left-to-right language, or vice versa. #### Misc functions + ##### Blockquote + Puts the current line in a quoting block. ##### Horizontal line + Adds a line that goes from the far right to the far left as a separator. #### Lists + ##### Bullet lists + Simple list with a dot preceding each element. ##### Ordered lists + List with numbers preceding each element. ##### Check lists + List with a checkbox on each element. ##### Indentation changes + ![](./img/notebook-toolbar-lists.webp) When you are currently in a list of any sort, the last 2 buttons will appear and will allow you to change the indentation of the current list item. #### Insert functions + ##### Link controls + The first buttons is to insert a link, or edit an existing link. The link will be applied on the selected text. Pasting a link directly into the text will automatically put the link anchor on it too. @@ -102,16 +117,20 @@ The second button will remove the link while leaving the text intact. This is the modal for the link, The little button in the textbox can be clicked to make the link open in a new tab. ##### Image controls + ![](./img/notebook-image-modal.webp) ##### Source + You can use a normal link to an image or you can also use locally stored images. ##### Width + The aspect ratio will always be respected, so only the width field is necessary. You can provide a value in a plain number or you can also use percents to have your image size dynamically change on different screens. #### Table + This last control allows you to add a table, for which you can determine the number of columns and row before inserting it. ![](./img/notebook-table-modal.webp) @@ -119,6 +138,7 @@ This last control allows you to add a table, for which you can determine the num Clicking that button while in a table will delete it. ##### Table controls + ![](./img/notebook-toolbar-table.webp) While in a table, further controls will appear to help edit the table. @@ -134,4 +154,4 @@ All the following ones are for editing the wize of the table: - Delete Column - Add row on top - Add row below -- Delete Row \ No newline at end of file +- Delete Row diff --git a/packages/translation/src/lang/en.json b/packages/translation/src/lang/en.json index 7a2c27b24..ad97ab109 100644 --- a/packages/translation/src/lang/en.json +++ b/packages/translation/src/lang/en.json @@ -1956,6 +1956,8 @@ "clear": "Clear formatting", "heading": "Heading {level}", "align": "Align text: {position}", + "directionLtr": "Set text direction left to right", + "directionRtl": "Set text direction right to left", "blockquote": "Blockquote", "horizontalLine": "Horizontal line", "bulletList": "Bullet list", diff --git a/packages/widgets/src/notebook/notebook.tsx b/packages/widgets/src/notebook/notebook.tsx index 229b3730a..9fdd692fb 100644 --- a/packages/widgets/src/notebook/notebook.tsx +++ b/packages/widgets/src/notebook/notebook.tsx @@ -39,6 +39,8 @@ import { IconRowRemove, IconTableOff, IconTablePlus, + IconTextDirectionLtr, + IconTextDirectionRtl, IconX, } from "@tabler/icons-react"; import { Color } from "@tiptap/extension-color"; @@ -66,6 +68,7 @@ import { useI18n, useScopedI18n } from "@homarr/translation/client"; import type { TablerIcon } from "@homarr/ui"; import type { WidgetComponentProps } from "../definition"; +import { NotebookTextDirection, setTextDirection } from "./text-direction"; import "@mantine/tiptap/styles.css"; import "./notebook.css"; @@ -171,6 +174,7 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W }), TaskList.configure({ itemTypeName: "taskItem" }), TextAlign.configure({ types: ["heading", "paragraph"] }), + NotebookTextDirection, TextStyle, ], shouldRerenderOnTransaction: true, @@ -347,6 +351,22 @@ export function Notebook({ options, setOptions, isEditMode, boardId, itemId }: W position: t("widget.notebook.align.right"), })} /> + editor && setTextDirection(editor, "ltr").run()} + > + + + editor && setTextDirection(editor, "rtl").run()} + > + + diff --git a/packages/widgets/src/notebook/text-direction.spec.ts b/packages/widgets/src/notebook/text-direction.spec.ts new file mode 100644 index 000000000..62b0313df --- /dev/null +++ b/packages/widgets/src/notebook/text-direction.spec.ts @@ -0,0 +1,34 @@ +import { Editor } from "@tiptap/react"; +import { StarterKit } from "@tiptap/starter-kit"; +import { describe, expect, it } from "vitest"; + +import { NotebookTextDirection, setTextDirection } from "./text-direction"; + +describe("notebook text direction", () => { + it("persists right-to-left direction in the notebook HTML", () => { + const editor = new Editor({ + content: "

יש לי 16GB

", + extensions: [StarterKit, NotebookTextDirection], + }); + + setTextDirection(editor, "rtl").run(); + + expect(editor.getHTML()).toBe('

יש לי 16GB

'); + editor.destroy(); + }); + + it("reads and changes an existing direction attribute", () => { + const editor = new Editor({ + content: '

Heading

', + extensions: [StarterKit, NotebookTextDirection], + }); + + expect(editor.getHTML()).toBe('

Heading

'); + + editor.commands.setTextSelection(1); + setTextDirection(editor, "ltr").run(); + + expect(editor.getHTML()).toContain('

Heading

'); + editor.destroy(); + }); +}); diff --git a/packages/widgets/src/notebook/text-direction.ts b/packages/widgets/src/notebook/text-direction.ts new file mode 100644 index 000000000..7ab9ea022 --- /dev/null +++ b/packages/widgets/src/notebook/text-direction.ts @@ -0,0 +1,28 @@ +import { Extension } from "@tiptap/react"; +import type { Editor } from "@tiptap/react"; + +export type TextDirection = "ltr" | "rtl"; + +export const NotebookTextDirection = Extension.create({ + name: "notebookTextDirection", + + addGlobalAttributes() { + return [ + { + types: ["heading", "paragraph"], + attributes: { + dir: { + default: null, + parseHTML: (element) => element.getAttribute("dir"), + renderHTML: ({ dir }: { dir?: TextDirection }) => (dir ? { dir } : {}), + }, + }, + }, + ]; + }, +}); + +export function setTextDirection(editor: Editor, direction: TextDirection) { + const nodeType = editor.isActive("heading") ? "heading" : "paragraph"; + return editor.chain().focus().updateAttributes(nodeType, { dir: direction }); +} From c2508dffea88343af206745977f066e5512aa20b Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 06:18:17 +0000 Subject: [PATCH 09/54] chore(deps): update dependency posthog-js to ^1.396.7 (#6285) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 88 +++++++++++++++++++++++++-------------------- pnpm-workspace.yaml | 2 +- 2 files changed, 50 insertions(+), 40 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 389ce2dae..52a3e23a1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -442,8 +442,8 @@ catalogs: specifier: ^1.18.0 version: 1.18.0 posthog-js: - specifier: ^1.396.4 - version: 1.396.5 + specifier: ^1.398.0 + version: 1.399.0 posthog-node: specifier: ^5.39.2 version: 5.39.4 @@ -1012,7 +1012,7 @@ importers: version: 1.18.0(postcss@8.5.16) posthog-js: specifier: 'catalog:' - version: 1.396.5 + version: 1.399.0(preact-render-to-string@6.5.11) prismjs: specifier: 'catalog:' version: 1.30.0 @@ -6407,9 +6407,15 @@ packages: '@posthog/core@1.39.6': resolution: {integrity: sha512-o6ajIwN5zXoNP0D4H/QPmOyibNTUkSyOR6ya7AG5U2ywXx4awo72L2KnCoiZPQM5x/bXv6jPBdimH8M18Ax0aw==} + '@posthog/core@1.40.0': + resolution: {integrity: sha512-oGDbIwlTquNwdHbEL5ZLEkuW4UFkkEanfx3QAxDgyVbISv+OAA6YGQwrvo0JD3MUJEbJZvyh8XsX+WYDGw9XHw==} + '@posthog/types@1.392.0': resolution: {integrity: sha512-nctNujXL3FC1v99FktaTMSugSD9ZOZekEpahUSafkU2TSvW+XGKNkQZbokuJtiWvPBK208dwMJva8UfBkChqpw==} + '@posthog/types@1.393.0': + resolution: {integrity: sha512-vzWeEJZ7ERQhFRoQYaP5jzN1JvIu46UJyHXsuv+dTGW2r3sMgREOhNxXLZjmFHwZ8/FOHQoyqqQmXTCXZSfMSg==} + '@protobufjs/aspromise@1.1.2': resolution: {integrity: sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==} @@ -13139,8 +13145,8 @@ packages: resolution: {integrity: sha512-Ray65LYEJrMMqDtsBUBXunEVP/g4wtATvq/xz9rchUoLy/9mSkkFgUko/8DVtGxgiP3vivpFMgfb9HpCuDrBHg==} engines: {node: '>=10.15.1'} - posthog-js@1.396.5: - resolution: {integrity: sha512-huW/8R151I08i/tkOaAv3Q/yWVqzhqPj3SxyhpgNLB/xysaNeV1ibp6DHzbRWV5D7FRM48Zg6Icxsm4q0irEtg==} + posthog-js@1.399.0: + resolution: {integrity: sha512-8l+uZJZM3+OAc0D0+iLBMDRVfWF9s26Rt0jv8EC3kMJcA/9oyOets0zDgqIZk2TTfUs3H96ycLE6Lwj1wWG16Q==} posthog-node@5.39.4: resolution: {integrity: sha512-+fCQ7htBFRQQFbIzl1T0TA7bDwYyaB9XP308ZFMCUoB5LzTzOFxBa6TYVrxdH/VQl43WXTp6sf0QsG2Z4XlNBg==} @@ -13159,8 +13165,13 @@ packages: preact@10.24.3: resolution: {integrity: sha512-Z2dPnBnMUfyQfSQ+GBdsGa16hz35YmLmtTLhM169uW944hYL6xzTYkJjC07j+Wosz733pMWx0fgON3JNw1jJQA==} - preact@10.29.2: - resolution: {integrity: sha512-7tNmwg/7mzzAoB/8kSg6Hl37JraAZw3Z3A0JSY7VXlZwo82Xn0G7wKbNNs2qoF4ZEEsQGTwDAroNdqKs1ofJxQ==} + preact@10.29.7: + resolution: {integrity: sha512-DCHYrK/B10yUD3ZjLfhZ3WIE/9Vf9VFUODcRE2dRomTYDpJk6z6L9wecSfhfE6M9ZTHUdyQkoC46arIDhEV84Q==} + peerDependencies: + preact-render-to-string: '>=5' + peerDependenciesMeta: + preact-render-to-string: + optional: true prebuild-install@7.1.2: resolution: {integrity: sha512-UnNke3IQb6sgarcZIDU3gbMeTp/9SSU1DAIkil7PrqG1vZlBtY5msYccSKSHDqa3hNg436IXK+SNImReuA1wEQ==} @@ -13242,9 +13253,6 @@ packages: prosemirror-model@1.25.10: resolution: {integrity: sha512-9n6rH4DbJU1eH4SxLt6Y0HhJIo6cZsb7DJ/30uob1hOKPeO6TAaMWI2tc7kwR92BjfPOU2fFHWbZLovLi3XQfA==} - prosemirror-model@1.25.7: - resolution: {integrity: sha512-A79aN8QEFUwI6cax8Yq4Rpcx1TJZ3Kagn+ii7qLo4/V8H3mMiHrhFyhTyHHvpSnOgMPpWiDGSwM3etwrxE50ug==} - prosemirror-schema-list@1.5.1: resolution: {integrity: sha512-927lFx/uwyQaGwJxLWCZRkjXG0p48KpMj6ueoYiu4JX05GGuGcgzAy62dfiV8eFZftgyBUvLx76RsMe20fJl+Q==} @@ -13257,9 +13265,6 @@ packages: prosemirror-transform@1.12.0: resolution: {integrity: sha512-GxboyN4AMIsoHNtz5uf2r2Ru551i5hWeCMD6E2Ib4Eogqoub0NflniaBPVQ4MrGE5yZ8JV9tUHg9qcZTTrcN4w==} - prosemirror-view@1.41.8: - resolution: {integrity: sha512-TnKDdohEatgyZNGCDWIdccOHXhYloJwbwU+phw/a23KBvJIR9lWQWW7WHHK3vBdOLDNuF7TaX98GObUZOWkOnA==} - prosemirror-view@1.42.0: resolution: {integrity: sha512-N54DF3OXNWDuP81G1kbfCys8ZzIjuL1VnvJ2mk5STSu/fNxWIcX/EutQLA3s9KR/2wVhgDi4hzBB/1fINVxk0A==} @@ -19912,8 +19917,14 @@ snapshots: dependencies: '@posthog/types': 1.392.0 + '@posthog/core@1.40.0': + dependencies: + '@posthog/types': 1.393.0 + '@posthog/types@1.392.0': {} + '@posthog/types@1.393.0': {} + '@protobufjs/aspromise@1.1.2': {} '@protobufjs/base64@1.1.2': {} @@ -27474,16 +27485,18 @@ snapshots: posthog-docusaurus@2.0.5: {} - posthog-js@1.396.5: + posthog-js@1.399.0(preact-render-to-string@6.5.11): dependencies: - '@posthog/core': 1.39.6 - '@posthog/types': 1.392.0 + '@posthog/core': 1.40.0 + '@posthog/types': 1.393.0 core-js: 3.49.0 dompurify: 3.4.11 fflate: 0.4.8 - preact: 10.29.2 + preact: 10.29.7(preact-render-to-string@6.5.11) query-selector-shadow-dom: 1.0.1 web-vitals: 5.3.0 + transitivePeerDependencies: + - preact-render-to-string posthog-node@5.39.4(rxjs@7.8.2): dependencies: @@ -27495,9 +27508,16 @@ snapshots: dependencies: preact: 10.24.3 + preact-render-to-string@6.5.11(preact@10.29.7): + dependencies: + preact: 10.29.7(preact-render-to-string@6.5.11) + optional: true + preact@10.24.3: {} - preact@10.29.2: {} + preact@10.29.7(preact-render-to-string@6.5.11): + optionalDependencies: + preact-render-to-string: 6.5.11(preact@10.29.7) prebuild-install@7.1.2: dependencies: @@ -27575,7 +27595,7 @@ snapshots: prosemirror-commands@1.7.1: dependencies: - prosemirror-model: 1.25.7 + prosemirror-model: 1.25.10 prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 @@ -27583,20 +27603,20 @@ snapshots: dependencies: prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 - prosemirror-view: 1.41.8 + prosemirror-view: 1.42.0 prosemirror-gapcursor@1.4.1: dependencies: prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.7 + prosemirror-model: 1.25.10 prosemirror-state: 1.4.4 - prosemirror-view: 1.41.8 + prosemirror-view: 1.42.0 prosemirror-history@1.5.0: dependencies: prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 - prosemirror-view: 1.41.8 + prosemirror-view: 1.42.0 rope-sequence: 1.3.4 prosemirror-inputrules@1.5.1: @@ -27613,39 +27633,29 @@ snapshots: dependencies: orderedmap: 2.1.1 - prosemirror-model@1.25.7: - dependencies: - orderedmap: 2.1.1 - prosemirror-schema-list@1.5.1: dependencies: - prosemirror-model: 1.25.7 + prosemirror-model: 1.25.10 prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 prosemirror-state@1.4.4: dependencies: - prosemirror-model: 1.25.7 + prosemirror-model: 1.25.10 prosemirror-transform: 1.12.0 - prosemirror-view: 1.41.8 + prosemirror-view: 1.42.0 prosemirror-tables@1.8.5: dependencies: prosemirror-keymap: 1.2.3 - prosemirror-model: 1.25.7 + prosemirror-model: 1.25.10 prosemirror-state: 1.4.4 prosemirror-transform: 1.12.0 - prosemirror-view: 1.41.8 + prosemirror-view: 1.42.0 prosemirror-transform@1.12.0: dependencies: - prosemirror-model: 1.25.7 - - prosemirror-view@1.41.8: - dependencies: - prosemirror-model: 1.25.7 - prosemirror-state: 1.4.4 - prosemirror-transform: 1.12.0 + prosemirror-model: 1.25.10 prosemirror-view@1.42.0: dependencies: diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index c3546c0c2..0a7ba31ce 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -93,7 +93,7 @@ catalog: "@types/video.js": ^7.3.58 "@types/ws": ^8.18.1 "@types/xml2js": ^0.4.14 - posthog-js: ^1.396.4 + posthog-js: ^1.398.0 posthog-node: ^5.39.2 "@vitejs/plugin-react": ^6.0.3 "@vitest/coverage-v8": ^4.1.9 From 9f313440acdf85aea215b89f460138d3c44a896f Mon Sep 17 00:00:00 2001 From: Nerdy Technician Date: Sun, 12 Jul 2026 07:59:52 +0100 Subject: [PATCH 10/54] fix(integrations): update PatchMon API key settings link (#6302) --- packages/definitions/src/integration.ts | 2 +- packages/definitions/src/test/api-key-url.spec.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/definitions/src/integration.ts b/packages/definitions/src/integration.ts index 138a1c9f7..211f0807b 100644 --- a/packages/definitions/src/integration.ts +++ b/packages/definitions/src/integration.ts @@ -379,7 +379,7 @@ export const integrationDefs = { category: ["healthMonitoring"], documentationUrl: createDocumentationLink("/docs/integrations/patchmon"), defaultPort: 3413, - apiKeySettingsPath: "/settings/api-keys", + apiKeySettingsPath: "/settings/integrations", }, tracearr: { name: "Tracearr", diff --git a/packages/definitions/src/test/api-key-url.spec.ts b/packages/definitions/src/test/api-key-url.spec.ts index 21b7e0fe7..4e70c75ca 100644 --- a/packages/definitions/src/test/api-key-url.spec.ts +++ b/packages/definitions/src/test/api-key-url.spec.ts @@ -15,6 +15,12 @@ describe("getIntegrationApiKeyUrl", () => { ); }); + it("builds URL for patchmon", () => { + expect(getIntegrationApiKeyUrl("http://patchmon.local:3413", "patchmon")).toBe( + "http://patchmon.local:3413/settings/integrations", + ); + }); + it("returns null for integrations without apiKeySettingsPath", () => { expect(getIntegrationApiKeyUrl("http://localhost", "plex")).toBeNull(); }); From e9543dc30d36b06c9ca7eeb26667d7cca76e779a Mon Sep 17 00:00:00 2001 From: "homarr-renovate[bot]" <158783068+homarr-renovate[bot]@users.noreply.github.com> Date: Sun, 12 Jul 2026 09:12:55 +0000 Subject: [PATCH 11/54] chore(deps): update tiptap monorepo to v3.27.3 (#6308) Co-authored-by: homarr-renovate[bot] <158783068+homarr-renovate[bot]@users.noreply.github.com> --- pnpm-lock.yaml | 202 ++++++++++++++++++++------------------------ pnpm-workspace.yaml | 32 +++---- 2 files changed, 107 insertions(+), 127 deletions(-) diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 52a3e23a1..5f263521b 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -151,52 +151,52 @@ catalogs: specifier: ^12.0.4 version: 12.0.4 '@tiptap/extension-color': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-highlight': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-image': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-link': - specifier: ^3.27.2 + specifier: ^3.27.3 version: 3.27.3 '@tiptap/extension-placeholder': - specifier: ^3.27.2 + specifier: ^3.27.3 version: 3.27.3 '@tiptap/extension-table': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-table-cell': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-table-header': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-table-row': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-task-item': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-task-list': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-text-align': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-text-style': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/extension-underline': - specifier: 3.27.2 - version: 3.27.2 + specifier: 3.27.3 + version: 3.27.3 '@tiptap/react': - specifier: ^3.27.2 + specifier: ^3.27.3 version: 3.27.3 '@tiptap/starter-kit': - specifier: ^3.27.2 + specifier: ^3.27.3 version: 3.27.3 '@trpc/client': specifier: ^11.18.0 @@ -2598,13 +2598,13 @@ importers: version: 3.44.0(react@19.2.7) '@tiptap/extension-color': specifier: 'catalog:' - version: 3.27.2(@tiptap/extension-text-style@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))) + version: 3.27.3(@tiptap/extension-text-style@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))) '@tiptap/extension-highlight': specifier: 'catalog:' - version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/extension-image': specifier: 'catalog:' - version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/extension-link': specifier: 'catalog:' version: 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) @@ -2613,31 +2613,31 @@ importers: version: 3.27.3(@tiptap/extensions@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-table': specifier: 'catalog:' - version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + version: 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) '@tiptap/extension-table-cell': specifier: 'catalog:' - version: 3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/extension-table@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-table-header': specifier: 'catalog:' - version: 3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/extension-table@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-table-row': specifier: 'catalog:' - version: 3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/extension-table@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-task-item': specifier: 'catalog:' - version: 3.27.2(@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-task-list': specifier: 'catalog:' - version: 3.27.2(@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)) '@tiptap/extension-text-align': specifier: 'catalog:' - version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/extension-text-style': specifier: 'catalog:' - version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/extension-underline': specifier: 'catalog:' - version: 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + version: 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/react': specifier: 'catalog:' version: 3.27.3(@floating-ui/dom@1.7.6)(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)(@types/react-dom@19.2.3(@types/react@19.2.17))(@types/react@19.2.17)(react-dom@19.2.7(react@19.2.7))(react@19.2.7) @@ -7392,10 +7392,10 @@ packages: peerDependencies: '@tiptap/core': 3.27.3 - '@tiptap/extension-color@3.27.2': - resolution: {integrity: sha512-X2Mo7y/49OFg/5I9B2APgXd1DsxseLXyvnvpFBGGj1AYqYGxvvJ9thvyLZr7SZVGjnAlbu3kwI2lhRQdyyIq8w==} + '@tiptap/extension-color@3.27.3': + resolution: {integrity: sha512-tNXyK3VXPGygJP+vq1+YyPmrxLabx4vs+rYiVhGtmhIapTQeMVYcHbFH4s4v3h5m8JGVlTWnpI8n7cb5ExIhSQ==} peerDependencies: - '@tiptap/extension-text-style': 3.27.2 + '@tiptap/extension-text-style': 3.27.3 '@tiptap/extension-document@3.27.3': resolution: {integrity: sha512-PR25MRv56Nm4ETVLwA8nAh/0RrVdvD4D2nxnAC8kUkxz1WOYDaqTfPqqEeSWDoUUTUp1I1xzDkD4x0gEnAlmZA==} @@ -7429,10 +7429,10 @@ packages: peerDependencies: '@tiptap/core': 3.27.3 - '@tiptap/extension-highlight@3.27.2': - resolution: {integrity: sha512-+PhUkhh8g9a005FvwtDVnxRzadLO2tBWY4plUfJJDDpFIC0lTSxc0Q6ESBEV4x/BnuClH/RKH+tZAhkyEqeTeQ==} + '@tiptap/extension-highlight@3.27.3': + resolution: {integrity: sha512-99QYxMx8F/NhdvznM8/Zhm4XiCdMU0OEiLwPvLNObJiKV8UBqjdA68cJLX6FRuOi2nqD7xfJq2KuSsFKBldi2A==} peerDependencies: - '@tiptap/core': 3.27.2 + '@tiptap/core': 3.27.3 '@tiptap/extension-horizontal-rule@3.27.3': resolution: {integrity: sha512-sNCuo0+Q001B5XTVWY+ULpWXQqtBNRmAqXxAexZl44bx3rDqHG8OzjwT4EM0LIj2+BYVbdqwaJ1vfQbZVEEbig==} @@ -7440,10 +7440,10 @@ packages: '@tiptap/core': 3.27.3 '@tiptap/pm': 3.27.3 - '@tiptap/extension-image@3.27.2': - resolution: {integrity: sha512-xnDCj+9oNwr4Zh9B+HHi+9lXyUVYv2KmCF3QuCdGxhZA4FDNlJT3gISwYnH4LNtKSD1XHh/gSCMTpZF+8oqe3g==} + '@tiptap/extension-image@3.27.3': + resolution: {integrity: sha512-3OVLRH54Xkh9yrI5oGIempuqZkhLG1plyJBRnapx0N8j9IjEImOLF78kPYoAZzaBuU78M3Zk/E7HQZgmnaNRBg==} peerDependencies: - '@tiptap/core': 3.27.2 + '@tiptap/core': 3.27.3 '@tiptap/extension-italic@3.27.3': resolution: {integrity: sha512-7VpVi2vn8kGFAc/HLrt96J/E6+LXOGKn8xLhJS863t150yLIG3EPQLA1h+G5O6eo+/w+9YokvJAAJoeDihj1Jg==} @@ -7466,12 +7466,6 @@ packages: peerDependencies: '@tiptap/extension-list': 3.27.3 - '@tiptap/extension-list@3.27.2': - resolution: {integrity: sha512-SsVnh5ruM9QjgOGbhJjegHAIADuoE+9Sb0UV+lyxAeuEwO98JpVmE926Yur9abzUAA9MWZZ/XBIzoA5q0a4dBQ==} - peerDependencies: - '@tiptap/core': 3.27.2 - '@tiptap/pm': 3.27.2 - '@tiptap/extension-list@3.27.3': resolution: {integrity: sha512-52rcaSzYjtVGUrNkdB8k1Bw/rId2lBirrWMvmnBLsieoeFd8zo8ow5zkwV057BYSgWXfnMAV7GOpDyq2IoSD9A==} peerDependencies: @@ -7498,57 +7492,52 @@ packages: peerDependencies: '@tiptap/core': 3.27.3 - '@tiptap/extension-table-cell@3.27.2': - resolution: {integrity: sha512-OD0Zui+/3pT8XwkHWjOMrcyALrnImcOQ+Khg/4pJb+5KoKCkExBqYeH2Zq8Gz5yAO1r9ICrQvl/Kjil1JnOZuA==} + '@tiptap/extension-table-cell@3.27.3': + resolution: {integrity: sha512-W8onBLgjge3D2PlYetbPC/BvwH5jgYfL9PLqMurfCPD7t8fyU4s+4E3SwbZkuf6eCH0twBjqyapH7+sOW/sPhA==} peerDependencies: - '@tiptap/extension-table': 3.27.2 + '@tiptap/extension-table': 3.27.3 - '@tiptap/extension-table-header@3.27.2': - resolution: {integrity: sha512-HWbSsuYqxGT+trU9GP/EnX4tF4p+pq/YVrQKu4qPK+gFMJUG/FiL2OH/eNRh48r+EXp+VM6znYcIHrfUhNroNw==} + '@tiptap/extension-table-header@3.27.3': + resolution: {integrity: sha512-8iBOxh0S1Wh5VC1rmVTZVJKanGhJpZd5E5O7Pycf+KKUu+BcWQZ17ewj3+VVSdU76sDCUWIjQE0qJ/EyI1vZeg==} peerDependencies: - '@tiptap/extension-table': 3.27.2 + '@tiptap/extension-table': 3.27.3 - '@tiptap/extension-table-row@3.27.2': - resolution: {integrity: sha512-X+HxuA5aTLrGGUHKPVKrVRyZt+zst4fxG5r89ExFH96bWHs3hLQGZnIDEBpOVsMFl65FAUxE0ycw/Vh/xAT5NQ==} + '@tiptap/extension-table-row@3.27.3': + resolution: {integrity: sha512-AFMVQI+1kRh97++g+NA6MjATpwQPvjar4JUyaGlnSBIt+b5sYLTW3qQS8EzpMbKPqUp7uZOX9EkLgpvepWYSow==} peerDependencies: - '@tiptap/extension-table': 3.27.2 + '@tiptap/extension-table': 3.27.3 - '@tiptap/extension-table@3.27.2': - resolution: {integrity: sha512-lOuapZby/RgOmrAg5OlTIh/4li8qYhbbTJLKXGq+vCl2BlWLZBMXrhdzPnIqqCiD7mLhNoOPA4kQOohJUah+ww==} + '@tiptap/extension-table@3.27.3': + resolution: {integrity: sha512-P7iQ0SkkDWZwnXzQDOVFLXktXlX67VQ3s2YxoJYKTHlc8rPZ4UCaSogFq0G2APyBlizlY3LbMNrDeZAVFOD8wQ==} peerDependencies: - '@tiptap/core': 3.27.2 - '@tiptap/pm': 3.27.2 + '@tiptap/core': 3.27.3 + '@tiptap/pm': 3.27.3 - '@tiptap/extension-task-item@3.27.2': - resolution: {integrity: sha512-6JiIlSncQatTCSjy8ECJbmNJbNbbrDkLOI7tzVroo72fuHrjF6W87Fr9EmSOCbYeZF54cr6QksogeZhF9rUmfw==} + '@tiptap/extension-task-item@3.27.3': + resolution: {integrity: sha512-CajIPpHTsuE1l+bM3xKgpZ/3PTvRuFb+ZEyC+yxI/cY2pZd4EJ7iXHT5Fl3ITwdDq8/syVev1BBZ/QDZTOA3rw==} peerDependencies: - '@tiptap/extension-list': 3.27.2 + '@tiptap/extension-list': 3.27.3 - '@tiptap/extension-task-list@3.27.2': - resolution: {integrity: sha512-T3EgjXeiFv4kxZV4TFgAFAbERS2IQ3KjXgWMsMUVM44H1E/VW/2c25Ut2EOZiZ8ZTcBbyJ7aWCx2rbpHa2r/VQ==} + '@tiptap/extension-task-list@3.27.3': + resolution: {integrity: sha512-g1zp2SnR+3DbsXbK1GbAucrj0lh1WnTarp1YPY4beFENzjAPklyVIpF22IjFZD/08QWN1j1fRN3zVzTGHuFXFA==} peerDependencies: - '@tiptap/extension-list': 3.27.2 + '@tiptap/extension-list': 3.27.3 - '@tiptap/extension-text-align@3.27.2': - resolution: {integrity: sha512-38MH2LOYzZPbJMFoL+M+NpIUpI2pMefTVRuqlHSNoAdn6wEyJoByEeceRab2vt7/S4w9lWcWxwzsXEa38G/5WQ==} + '@tiptap/extension-text-align@3.27.3': + resolution: {integrity: sha512-yyhlwRz0Eh6xQCn+GtTDWUGqxGajT02Qf8zBN/3eZQtl3EZ2xrI8lvfLmcV9SAW0srgMd+5hX0CVV+zKeXuxeg==} peerDependencies: - '@tiptap/core': 3.27.2 + '@tiptap/core': 3.27.3 - '@tiptap/extension-text-style@3.27.2': - resolution: {integrity: sha512-kgGg3LXYnEEttqM6CsiSLxRVU5055vON1T5xipskmbdDtV/xXTykzbISiX3ND5zYw+BG7hF8COEKLt1iLo6LbA==} + '@tiptap/extension-text-style@3.27.3': + resolution: {integrity: sha512-oim4RqFtMBCA4N+J8f8dTFFYZrwyfmXLxLL5SvQkn+kkpfGLv8PqJCnJ4uHxsnHXDNsWHBtVVhfTGUABZsTEHg==} peerDependencies: - '@tiptap/core': 3.27.2 + '@tiptap/core': 3.27.3 '@tiptap/extension-text@3.27.3': resolution: {integrity: sha512-wSatZ/bop0pleeC18nF90zvWWEoRe/ewZncvm8LjcMrXwq41kTEs3j7nZflMYAh7X8fZvu00UIMeEyCNJl+5Yg==} peerDependencies: '@tiptap/core': 3.27.3 - '@tiptap/extension-underline@3.27.2': - resolution: {integrity: sha512-5/1XpKxBqAkiR6ND2P/jcPQ06Opcz/Nidd6dasMkjxwuRJPU5OSz3Qy1fsfLlduRU8TzSqjT8vpQJgaqsHqbLA==} - peerDependencies: - '@tiptap/core': 3.27.2 - '@tiptap/extension-underline@3.27.3': resolution: {integrity: sha512-YH7ka/Rp7ZwlzTuQCcs0OZ4CMSx/P/MzbseQKxKebEIZreWaKMZAaED97Fnu0dA66VlreD4/1C7xHJy84ovamg==} peerDependencies: @@ -21059,9 +21048,9 @@ snapshots: dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-color@3.27.2(@tiptap/extension-text-style@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)))': + '@tiptap/extension-color@3.27.3(@tiptap/extension-text-style@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)))': dependencies: - '@tiptap/extension-text-style': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) + '@tiptap/extension-text-style': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3)) '@tiptap/extension-document@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: @@ -21090,7 +21079,7 @@ snapshots: dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-highlight@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': + '@tiptap/extension-highlight@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) @@ -21099,7 +21088,7 @@ snapshots: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) '@tiptap/pm': 3.27.3 - '@tiptap/extension-image@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': + '@tiptap/extension-image@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) @@ -21121,11 +21110,6 @@ snapshots: dependencies: '@tiptap/extension-list': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': - dependencies: - '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/pm': 3.27.3 - '@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) @@ -21147,36 +21131,36 @@ snapshots: dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-table-cell@3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': + '@tiptap/extension-table-cell@3.27.3(@tiptap/extension-table@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-table': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-table': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-table-header@3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': + '@tiptap/extension-table-header@3.27.3(@tiptap/extension-table@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-table': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-table': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-table-row@3.27.2(@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': + '@tiptap/extension-table-row@3.27.3(@tiptap/extension-table@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-table': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-table': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-table@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': + '@tiptap/extension-table@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3)': dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) '@tiptap/pm': 3.27.3 - '@tiptap/extension-task-item@3.27.2(@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': + '@tiptap/extension-task-item@3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-list': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-list': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-task-list@3.27.2(@tiptap/extension-list@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': + '@tiptap/extension-task-list@3.27.3(@tiptap/extension-list@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3))': dependencies: - '@tiptap/extension-list': 3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) + '@tiptap/extension-list': 3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))(@tiptap/pm@3.27.3) - '@tiptap/extension-text-align@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': + '@tiptap/extension-text-align@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-text-style@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': + '@tiptap/extension-text-style@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) @@ -21184,10 +21168,6 @@ snapshots: dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-underline@3.27.2(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': - dependencies: - '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) - '@tiptap/extension-underline@3.27.3(@tiptap/core@3.27.3(@tiptap/pm@3.27.3))': dependencies: '@tiptap/core': 3.27.3(@tiptap/pm@3.27.3) diff --git a/pnpm-workspace.yaml b/pnpm-workspace.yaml index 0a7ba31ce..d01fe67d4 100644 --- a/pnpm-workspace.yaml +++ b/pnpm-workspace.yaml @@ -53,22 +53,22 @@ catalog: "@testcontainers/mysql": ^12.0.4 "@testcontainers/postgresql": ^12.0.4 "@testcontainers/redis": ^12.0.4 - "@tiptap/extension-color": 3.27.2 - "@tiptap/extension-highlight": 3.27.2 - "@tiptap/extension-image": 3.27.2 - "@tiptap/extension-link": ^3.27.2 - "@tiptap/extension-placeholder": ^3.27.2 - "@tiptap/extension-table": 3.27.2 - "@tiptap/extension-table-cell": 3.27.2 - "@tiptap/extension-table-header": 3.27.2 - "@tiptap/extension-table-row": 3.27.2 - "@tiptap/extension-task-item": 3.27.2 - "@tiptap/extension-task-list": 3.27.2 - "@tiptap/extension-text-align": 3.27.2 - "@tiptap/extension-text-style": 3.27.2 - "@tiptap/extension-underline": 3.27.2 - "@tiptap/react": ^3.27.2 - "@tiptap/starter-kit": ^3.27.2 + "@tiptap/extension-color": 3.27.3 + "@tiptap/extension-highlight": 3.27.3 + "@tiptap/extension-image": 3.27.3 + "@tiptap/extension-link": ^3.27.3 + "@tiptap/extension-placeholder": ^3.27.3 + "@tiptap/extension-table": 3.27.3 + "@tiptap/extension-table-cell": 3.27.3 + "@tiptap/extension-table-header": 3.27.3 + "@tiptap/extension-table-row": 3.27.3 + "@tiptap/extension-task-item": 3.27.3 + "@tiptap/extension-task-list": 3.27.3 + "@tiptap/extension-text-align": 3.27.3 + "@tiptap/extension-text-style": 3.27.3 + "@tiptap/extension-underline": 3.27.3 + "@tiptap/react": ^3.27.3 + "@tiptap/starter-kit": ^3.27.3 "@trpc/client": ^11.18.0 "@trpc/next": ^11.18.0 "@trpc/react-query": ^11.18.0 From 7d859c1af03b648a9d409263511045920a872890 Mon Sep 17 00:00:00 2001 From: Manuel <30572287+manuel-rw@users.noreply.github.com> Date: Sun, 12 Jul 2026 14:49:56 +0200 Subject: [PATCH 12/54] docs: update pr template (#6147) --- .github/pull_request_template.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 1ccece4f7..ed325c9a8 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -11,3 +11,11 @@ - [ ] Commits follow the [conventional commits guideline](https://www.conventionalcommits.org/en/v1.0.0/) - [ ] No shorthand variable names are used (eg. `x`, `y`, `i` or any abbrevation) - [ ] Documentation is up to date. Create a pull request [here](https://github.com/homarr-labs/documentation/). +- [ ] When using AI; No temp files are checked in, the code style follows the rest of the project + +**When contributing a new integration or widget:** + +- [ ] I confirmed manually that the integration or widget is working with a real system and performed basic smoke + tests (e.g. what happens if it is offline) +- [ ] Optional but recommended: I confirm that I am willing to be added to the `CODEOWNERS` of this feature and will + provide support and maintain the integration (e.g. in case of breaking or failure) \ No newline at end of file From 8e8c29979f537a975107a9bdd620393ffe54c95c Mon Sep 17 00:00:00 2001 From: Thomas Date: Sun, 12 Jul 2026 14:50:58 +0200 Subject: [PATCH 13/54] fix(system-disks): format absolute storage values with shared byte helper (#6299) --- .../init/_steps/import/file-info-card.tsx | 4 +- .../src/app/[locale]/manage/medias/page.tsx | 4 +- .../manage/tools/docker/docker-table.tsx | 4 +- packages/common/src/number.ts | 109 +++++++++++++++++- packages/common/src/test/number.spec.ts | 90 +++++++++++++++ .../src/dashdot/dashdot-integration.ts | 4 +- .../src/unraid/unraid-integration.ts | 3 +- .../src/archive-team-warrior/component.tsx | 4 +- packages/widgets/src/docker/component.tsx | 6 +- packages/widgets/src/downloads/component.tsx | 35 +++--- .../cluster/resource-popover.tsx | 19 +-- .../src/health-monitoring/system-health.tsx | 4 +- .../src/immich/server-stats/component.tsx | 4 +- .../media-transcoding/panels/queue.panel.tsx | 4 +- .../panels/statistics.panel.tsx | 4 +- .../widgets/src/system-disks/component.tsx | 24 ++-- .../chart/combined-network-traffic.tsx | 4 +- .../system-resources/chart/memory-chart.tsx | 6 +- .../chart/network-traffic.tsx | 6 +- 19 files changed, 259 insertions(+), 79 deletions(-) create mode 100644 packages/common/src/test/number.spec.ts diff --git a/apps/nextjs/src/app/[locale]/init/_steps/import/file-info-card.tsx b/apps/nextjs/src/app/[locale]/init/_steps/import/file-info-card.tsx index 8a3a14fb4..c947719c2 100644 --- a/apps/nextjs/src/app/[locale]/init/_steps/import/file-info-card.tsx +++ b/apps/nextjs/src/app/[locale]/init/_steps/import/file-info-card.tsx @@ -2,7 +2,7 @@ import { ActionIcon, Button, Group, Text } from "@mantine/core"; import type { FileWithPath } from "@mantine/dropzone"; import { IconPencil } from "@tabler/icons-react"; -import { humanFileSize } from "@homarr/common"; +import { formatBytes } from "@homarr/common"; import { useScopedI18n } from "@homarr/translation/client"; import { InitStepCard } from "../../_components/init-step-card"; @@ -22,7 +22,7 @@ export const FileInfoCard = ({ file, onRemove }: FileInfoCardProps) => { {file.name} - {humanFileSize(file.size)} + {formatBytes(file.size)}