From 3c934570215c6718b043b35fdb821f94118e433f Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 1 Jul 2026 13:40:27 +0100 Subject: [PATCH 01/11] Fix rearrange-pages DUPLICATE producing shared page nodes (pypdf cyclic-references CI break) (#6851) --- .../api/RearrangePagesPDFController.java | 16 ++++++++-- .../api/RearrangePagesPDFControllerTest.java | 31 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java b/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java index 6dd7aacd79..12e3a15b89 100644 --- a/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java +++ b/app/core/src/main/java/stirling/software/SPDF/controller/api/RearrangePagesPDFController.java @@ -3,9 +3,12 @@ package stirling.software.SPDF.controller.api; import java.io.IOException; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.List; import java.util.Locale; +import java.util.Set; +import org.apache.pdfbox.cos.COSDictionary; import org.apache.pdfbox.pdmodel.PDDocument; import org.apache.pdfbox.pdmodel.PDPage; import org.apache.pdfbox.pdmodel.PDPageTree; @@ -261,10 +264,19 @@ public class RearrangePagesPDFController { log.info("newPageOrder = {}", newPageOrder); log.info("totalPages = {}", totalPages); - // Snapshot the desired pages before mutating the source document's page tree. + // Snapshot desired pages before mutating the tree; clone repeats (e.g. DUPLICATE) + // so each slot is a distinct node, not one PDPage under multiple /Kids. List newPages = new ArrayList<>(newPageOrder.size()); + Set seenIndices = new HashSet<>(); for (Integer idx : newPageOrder) { - newPages.add(document.getPage(idx)); + PDPage page = document.getPage(idx); + if (!seenIndices.add(idx)) { + // Duplicate index: distinct page node sharing content/resources. + COSDictionary clonedDict = new COSDictionary(); + clonedDict.addAll(page.getCOSObject()); + page = new PDPage(clonedDict); + } + newPages.add(page); } // Rearrange in-place on the source document rather than copying pages into a diff --git a/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java b/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java index a225c3fc52..14b1d9892a 100644 --- a/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java +++ b/app/core/src/test/java/stirling/software/SPDF/controller/api/RearrangePagesPDFControllerTest.java @@ -9,6 +9,7 @@ import java.io.File; import java.io.IOException; import java.nio.file.Files; import java.util.ArrayList; +import java.util.HashSet; import java.util.List; import org.apache.pdfbox.Loader; @@ -302,6 +303,11 @@ class RearrangePagesPDFControllerTest { assertNotNull(response); // 2 pages * 3 duplicates = 6 final pages assertEquals(6, realDoc.getNumberOfPages()); + // Each duplicate must be a distinct page node in the saved output; a shared + // node under multiple /Kids is an invalid tree readers reject as cyclic. + List savedPages = reloadAndSnapshot(response); + assertEquals(6, savedPages.size()); + assertEquals(6, new HashSet<>(savedPages).size()); } } @@ -323,4 +329,29 @@ class RearrangePagesPDFControllerTest { assertEquals(4, realDoc.getNumberOfPages()); } } + + @Test + void testRearrangePages_SideStitchBooklet_RepeatedPaddingPagesAreDistinctNodes() + throws IOException { + MockMultipartFile file = createMockPdf(); + RearrangePagesRequest request = new RearrangePagesRequest(); + request.setFileInput(file); + request.setPageNumbers(""); + request.setCustomMode("SIDE_STITCH_BOOKLET_SORT"); + + // 6 pages is not a multiple of 4, so booklet padding repeats the last page index + // several times; each repeat must be a distinct page node, not one shared node. + try (PDDocument realDoc = buildRealPdf(6)) { + when(pdfDocumentFactory.load(file)).thenReturn(realDoc); + + ResponseEntity response = controller.rearrangePages(request); + + assertNotNull(response); + assertEquals(200, response.getStatusCode().value()); + assertEquals(8, realDoc.getNumberOfPages()); + List savedPages = reloadAndSnapshot(response); + assertEquals(8, savedPages.size()); + assertEquals(8, new HashSet<>(savedPages).size()); + } + } } From 8e4b2e2fc685380c2366308295a5820439e5f2a9 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Fri, 3 Jul 2026 09:55:07 +0100 Subject: [PATCH 02/11] Disable update check and notification in SaaS mode (#6863) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit # Description of Changes In SaaS mode the self-hosted "Update Available" notification could still appear and the update-check code (external call to `supabase.stirling.com/functions/v1/updates`) still ran, even though the cloud owns app versioning. The web `UpdateStartupPopup` was already SaaS-gated via a null override, but two other paths were not: - **Desktop app in SaaS connection mode** - `useDesktopUpdatePopup()` ran its startup check and rendered the `UpdateModal` regardless of connection mode, so a self-hosted update popup appeared while connected to SaaS. - **Settings → General** - the core `GeneralSection` fired `checkForUpdate()` on mount unconditionally, even when the update section was hidden (as SaaS does), so the external call still ran. **What changed** - `useDesktopUpdatePopup.ts` - the startup timer now bails out immediately when `connectionModeService.getCurrentMode() === "saas"`. No mode lookup, no external fetch, no modal. - `core/GeneralSection.tsx` - the mount `checkForUpdate()` now returns early when `hideUpdateSection` is set, so hiding the section (web SaaS, managed-disabled desktop) also stops the external call. - `desktop/GeneralSection.tsx` - passes `hideUpdateSection` when `useSaaSMode()` is true, which (via the above) suppresses the settings check in desktop-SaaS too. **Why** - in SaaS the update check should never be called and no update notification should be shown; the cloud handles versioning. --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [ ] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have run `task check` to verify linters, typechecks, and tests pass - [ ] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --- .../config/configSections/GeneralSection.tsx | 6 +++-- .../config/configSections/GeneralSection.tsx | 7 ++++- .../hooks/useDesktopUpdatePopup.test.ts | 26 ++++++++++++++++++- .../desktop/hooks/useDesktopUpdatePopup.ts | 5 ++++ 4 files changed, 40 insertions(+), 4 deletions(-) diff --git a/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx b/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx index e5fdacea79..598b705347 100644 --- a/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx +++ b/frontend/editor/src/core/components/shared/config/configSections/GeneralSection.tsx @@ -112,12 +112,14 @@ const GeneralSection: React.FC = ({ // falling back to the backend version const currentVersion = appVersion ?? config?.appVersion ?? null; - // Check for updates on mount + // Check for updates on mount — skipped when the update UI is hidden (SaaS + // build, managed-disabled desktop) so no external update call ever fires. useEffect(() => { + if (hideUpdateSection) return; if (currentVersion) { checkForUpdate(); } - }, [currentVersion, config?.machineType]); + }, [currentVersion, config?.machineType, hideUpdateSection]); const checkForUpdate = async () => { if (!currentVersion) return; diff --git a/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx b/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx index abe37d10b8..98c4455ce0 100644 --- a/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx +++ b/frontend/editor/src/desktop/components/shared/config/configSections/GeneralSection.tsx @@ -4,6 +4,7 @@ import { useTranslation } from "react-i18next"; import CoreGeneralSection from "@core/components/shared/config/configSections/GeneralSection"; import { DefaultAppSettings } from "@app/components/shared/config/configSections/DefaultAppSettings"; import { useDesktopInstall } from "@app/hooks/useDesktopInstall"; +import { useSaaSMode } from "@app/hooks/useSaaSMode"; import { desktopUpdateService, type UpdateMode, @@ -22,6 +23,9 @@ import { const GeneralSection: React.FC = () => { const { t } = useTranslation(); const install = useDesktopInstall(); + // In SaaS connection mode the cloud owns app versioning — hide the update + // section (which also stops the core auto-check from firing). + const isSaaSMode = useSaaSMode(); const [updateModeInfo, setUpdateModeInfo] = useState({ mode: "prompt", locked: false, @@ -93,7 +97,8 @@ const GeneralSection: React.FC = () => { )} ({ invoke: (cmd: string, args?: unknown) => invokeMock(cmd, args), @@ -36,6 +37,12 @@ vi.mock("@app/services/desktopUpdateService", () => ({ }, })); +vi.mock("@app/services/connectionModeService", () => ({ + connectionModeService: { + getCurrentMode: () => getCurrentModeMock(), + }, +})); + import { useDesktopUpdatePopup } from "@app/hooks/useDesktopUpdatePopup"; /** Flush pending microtasks so awaited promises settle. */ @@ -74,8 +81,11 @@ describe("useDesktopUpdatePopup — auto mode", () => { getUpdateModeMock.mockReset(); canInstallUpdatesMock.mockReset(); getUpdateSummaryMock.mockReset(); + getCurrentModeMock.mockReset(); - // Defaults: auto mode, update available, install permitted. + // Defaults: local (non-SaaS) connection, auto mode, update available, + // install permitted. + getCurrentModeMock.mockResolvedValue("local"); getUpdateModeMock.mockResolvedValue("auto"); getVersionMock.mockResolvedValue("1.0.0"); getUpdateSummaryMock.mockResolvedValue({ latest_version: "2.0.0" }); @@ -189,4 +199,18 @@ describe("useDesktopUpdatePopup — auto mode", () => { expect(invocations).toContain("download_and_install_update"); expect(invocations).toContain("restart_app"); }); + + it("skips the update check entirely in SaaS connection mode", async () => { + // In SaaS mode the cloud owns versioning — the self-hosted update check + // must never run: no mode lookup, no external summary fetch, no install. + getCurrentModeMock.mockResolvedValue("saas"); + + await runStartup(); + + expect(getUpdateModeMock).not.toHaveBeenCalled(); + expect(getUpdateSummaryMock).not.toHaveBeenCalled(); + const invocations = invokeMock.mock.calls.map((c) => c[0]); + expect(invocations).not.toContain("download_and_install_update"); + expect(invocations).not.toContain("restart_app"); + }); }); diff --git a/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts b/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts index 6a2df77914..0cd0fcd003 100644 --- a/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts +++ b/frontend/editor/src/desktop/hooks/useDesktopUpdatePopup.ts @@ -6,6 +6,7 @@ import { desktopUpdateService, type CanInstallResult, } from "@app/services/desktopUpdateService"; +import { connectionModeService } from "@app/services/connectionModeService"; const SNOOZE_KEY = "stirling-pdf-updater:snoozedUntil"; const STARTUP_DELAY_MS = 15_000; @@ -72,6 +73,10 @@ export function useDesktopUpdatePopup() { hasChecked.current = true; const timer = setTimeout(async () => { + // In SaaS connection mode the cloud owns app versioning — the self-hosted + // update check + popup must never run (no external call, no modal). + if ((await connectionModeService.getCurrentMode()) === "saas") return; + let mode: Awaited> = "prompt"; try { From 67a0ca6110c11ed58d5912a6d7b8068761cf57e3 Mon Sep 17 00:00:00 2001 From: Ludy Date: Mon, 6 Jul 2026 11:21:09 +0200 Subject: [PATCH 03/11] fix(frontend): respect analytics config before initializing PostHog (#6812) # Description of Changes Please provide a summary of the changes, including: - What was changed - Moved PostHog startup out of `index.tsx` and into a config-aware initializer inside `AppProviders`. - Added a dedicated `usePosthogTracking` hook that only initializes PostHog when `enableAnalytics` is explicitly `true` and `enablePosthog` is not disabled. - Kept cookie-consent handling in the same flow so consent is applied only after PostHog is actually initialized. - Removed the unconditional `PostHogProvider` and `posthog.init(...)` bootstrap from the app entrypoint. - Added targeted frontend tests covering analytics-disabled and analytics-enabled startup behavior. - Why the change was made - The previous frontend bootstrap initialized PostHog before app config was loaded, so disabling analytics in the UI or via environment settings did not prevent PostHog network activity. - This change makes analytics behavior follow the server-provided config instead of always connecting on page load. Closes #6358 --- ## Checklist ### General - [x] I have read the [Contribution Guidelines](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/CONTRIBUTING.md) - [x] I have read the [Stirling-PDF Developer Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md) (if applicable) - [ ] I have read the [How to add new languages to Stirling-PDF](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md) (if applicable) - [x] I have performed a self-review of my own code - [x] My changes generate no new warnings ### Documentation - [ ] I have updated relevant docs on [Stirling-PDF's doc repo](https://github.com/Stirling-Tools/Stirling-Tools.github.io/blob/main/docs/) (if functionality has heavily changed) - [ ] I have read the section [Add New Translation Tags](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/devGuide/HowToAddNewLanguage.md#add-new-translation-tags) (for new translation tags only) ### Translations (if applicable) - [ ] I ran [`scripts/counter_translation.py`](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/docs/counter_translation.md) ### UI Changes (if applicable) - [ ] Screenshots or videos demonstrating the UI changes are attached (e.g., as comments or direct attachments in the PR) ### Testing (if applicable) - [x] I have run `task check` to verify linters, typechecks, and tests pass - [x] I have tested my changes locally. Refer to the [Testing Guide](https://github.com/Stirling-Tools/Stirling-PDF/blob/main/DeveloperGuide.md#7-testing) for more details. --- .../src/core/components/AppProviders.tsx | 7 ++ .../core/hooks/usePosthogTracking.test.tsx | 76 +++++++++++++++++ .../src/core/hooks/usePosthogTracking.ts | 83 +++++++++++++++++++ frontend/editor/src/index.tsx | 37 +-------- 4 files changed, 169 insertions(+), 34 deletions(-) create mode 100644 frontend/editor/src/core/hooks/usePosthogTracking.test.tsx create mode 100644 frontend/editor/src/core/hooks/usePosthogTracking.ts diff --git a/frontend/editor/src/core/components/AppProviders.tsx b/frontend/editor/src/core/components/AppProviders.tsx index ede9ed266a..f050ae7f9b 100644 --- a/frontend/editor/src/core/components/AppProviders.tsx +++ b/frontend/editor/src/core/components/AppProviders.tsx @@ -27,6 +27,7 @@ import { AdminTourOrchestrationProvider } from "@app/contexts/AdminTourOrchestra import { PageEditorProvider } from "@app/contexts/PageEditorContext"; import { BannerProvider } from "@app/contexts/BannerContext"; import ErrorBoundary from "@app/components/shared/ErrorBoundary"; +import { usePosthogTracking } from "@app/hooks/usePosthogTracking"; import { useScarfTracking } from "@app/hooks/useScarfTracking"; import { useAppInitialization } from "@app/hooks/useAppInitialization"; import { useLogoAssets } from "@app/hooks/useLogoAssets"; @@ -43,6 +44,11 @@ function ScarfTrackingInitializer() { return null; } +function PosthogTrackingInitializer() { + usePosthogTracking(); + return null; +} + // Component to run app-level initialization (must be inside AppProviders for context access) function AppInitializer() { useAppInitialization(); @@ -122,6 +128,7 @@ export function AppProviders({ retryOptions={appConfigRetryOptions} {...appConfigProviderProps} > + diff --git a/frontend/editor/src/core/hooks/usePosthogTracking.test.tsx b/frontend/editor/src/core/hooks/usePosthogTracking.test.tsx new file mode 100644 index 0000000000..41f5fad7dc --- /dev/null +++ b/frontend/editor/src/core/hooks/usePosthogTracking.test.tsx @@ -0,0 +1,76 @@ +import { ReactNode } from "react"; +import { renderHook, waitFor } from "@testing-library/react"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { AppConfigProvider } from "@app/contexts/AppConfigContext"; + +const posthogState = vi.hoisted(() => ({ loaded: false })); +const posthogMock = vi.hoisted(() => ({ + get __loaded() { + return posthogState.loaded; + }, + init: vi.fn(() => { + posthogState.loaded = true; + }), + opt_out_capturing: vi.fn(), + opt_in_capturing: vi.fn(), + set_config: vi.fn(), + has_opted_in_capturing: vi.fn(() => false), +})); + +vi.mock("posthog-js", () => ({ + default: posthogMock, +})); + +import { usePosthogTracking } from "@app/hooks/usePosthogTracking"; + +describe("usePosthogTracking", () => { + beforeEach(() => { + posthogState.loaded = false; + posthogMock.init.mockClear(); + posthogMock.opt_out_capturing.mockClear(); + posthogMock.opt_in_capturing.mockClear(); + posthogMock.set_config.mockClear(); + vi.stubEnv("VITE_PUBLIC_POSTHOG_KEY", "test-key"); + vi.stubEnv("VITE_PUBLIC_POSTHOG_HOST", "https://eu.i.posthog.com"); + }); + + afterEach(() => { + vi.unstubAllEnvs(); + }); + + it("does not initialize PostHog when analytics is disabled", async () => { + const wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + + ); + + renderHook(() => usePosthogTracking(), { wrapper }); + + await waitFor(() => { + expect(posthogMock.init).not.toHaveBeenCalled(); + }); + }); + + it("initializes PostHog when analytics is enabled", async () => { + const wrapper = ({ children }: { children: ReactNode }) => ( + + {children} + + ); + + renderHook(() => usePosthogTracking(), { wrapper }); + + await waitFor(() => { + expect(posthogMock.init).toHaveBeenCalledTimes(1); + }); + }); +}); diff --git a/frontend/editor/src/core/hooks/usePosthogTracking.ts b/frontend/editor/src/core/hooks/usePosthogTracking.ts new file mode 100644 index 0000000000..a8037ec4a7 --- /dev/null +++ b/frontend/editor/src/core/hooks/usePosthogTracking.ts @@ -0,0 +1,83 @@ +import { useEffect } from "react"; +import posthog from "posthog-js"; +import { useAppConfig } from "@app/contexts/AppConfigContext"; + +function applyPosthogConsent(): void { + if (typeof window === "undefined" || !posthog.__loaded) { + return; + } + + const optedIn = + window.CookieConsent?.acceptedService?.("posthog", "analytics") || false; + + if (optedIn) { + posthog.set_config({ persistence: "localStorage+cookie" }); + posthog.opt_in_capturing(); + return; + } + + posthog.opt_out_capturing(); + posthog.set_config({ persistence: "memory" }); +} + +function ensurePosthogInitialized(): boolean { + if (typeof window === "undefined") { + return false; + } + + const posthogKey = import.meta.env.VITE_PUBLIC_POSTHOG_KEY; + const posthogHost = import.meta.env.VITE_PUBLIC_POSTHOG_HOST; + + if (!posthogKey || !posthogHost) { + return false; + } + + if (!posthog.__loaded) { + posthog.init(posthogKey, { + api_host: posthogHost, + defaults: "2025-05-24", + capture_exceptions: true, + debug: false, + opt_out_capturing_by_default: true, + persistence: "memory", + cross_subdomain_cookie: false, + }); + } + + return true; +} + +export function usePosthogTracking(): void { + const { config } = useAppConfig(); + + useEffect(() => { + const analyticsEnabled = config?.enableAnalytics === true; + const posthogEnabled = analyticsEnabled && config?.enablePosthog !== false; + + if (!posthogEnabled) { + if (posthog.__loaded) { + posthog.opt_out_capturing(); + posthog.set_config({ persistence: "memory" }); + } + return; + } + + if (!ensurePosthogInitialized()) { + return; + } + + applyPosthogConsent(); + + const handleConsentChange = () => { + applyPosthogConsent(); + }; + + window.addEventListener("cc:onConsent", handleConsentChange); + window.addEventListener("cc:onChange", handleConsentChange); + + return () => { + window.removeEventListener("cc:onConsent", handleConsentChange); + window.removeEventListener("cc:onChange", handleConsentChange); + }; + }, [config?.enableAnalytics, config?.enablePosthog]); +} diff --git a/frontend/editor/src/index.tsx b/frontend/editor/src/index.tsx index 017138cebc..d776918bd1 100644 --- a/frontend/editor/src/index.tsx +++ b/frontend/editor/src/index.tsx @@ -13,8 +13,6 @@ import { ColorSchemeScript } from "@mantine/core"; import { BrowserRouter } from "react-router-dom"; import App from "@app/App"; import "@app/i18n"; // Initialize i18next -import posthog from "posthog-js"; -import { PostHogProvider } from "@posthog/react"; import { BASE_PATH } from "@app/constants/app"; import { startEagerWasmCompilation } from "@app/services/wasmPrecompiler"; @@ -35,33 +33,6 @@ if (typeof window !== "undefined") { } } -posthog.init(import.meta.env.VITE_PUBLIC_POSTHOG_KEY, { - api_host: import.meta.env.VITE_PUBLIC_POSTHOG_HOST, - defaults: "2025-05-24", - capture_exceptions: true, // This enables capturing exceptions using Error Tracking, set to false if you don't want this - debug: false, - opt_out_capturing_by_default: true, // Opt-out by default, controlled by cookie consent - persistence: "memory", // No cookies/localStorage written until user opts in - cross_subdomain_cookie: false, -}); - -function updatePosthogConsent() { - if (!posthog.__loaded) return; - const optIn = - window.CookieConsent?.acceptedService?.("posthog", "analytics") || false; - if (optIn) { - posthog.set_config({ persistence: "localStorage+cookie" }); - posthog.opt_in_capturing(); - } else { - posthog.opt_out_capturing(); - posthog.set_config({ persistence: "memory" }); - } - console.log("Updated PostHog consent: ", optIn ? "opted in" : "opted out"); -} - -window.addEventListener("cc:onConsent", updatePosthogConsent); -window.addEventListener("cc:onChange", updatePosthogConsent); - const container = document.getElementById("root"); if (!container) { throw new Error("Root container missing in index.html"); @@ -71,10 +42,8 @@ const root = ReactDOM.createRoot(container); // Finds the root DOM element root.render( - - - - - + + + , ); From 1b7ffcdbac721b754414d8a7ee390327d2058a9e Mon Sep 17 00:00:00 2001 From: James Brunton Date: Mon, 6 Jul 2026 10:22:41 +0100 Subject: [PATCH 04/11] Fix tooltip positioning on Add Page Numbers (#6885) # Description of Changes ## Before image ## After image --- .../addPageNumbers/AddPageNumbersAppearanceSettings.tsx | 5 +++++ .../tools/addPageNumbers/AddPageNumbersPositionSettings.tsx | 2 ++ 2 files changed, 7 insertions(+) diff --git a/frontend/editor/src/core/components/tools/addPageNumbers/AddPageNumbersAppearanceSettings.tsx b/frontend/editor/src/core/components/tools/addPageNumbers/AddPageNumbersAppearanceSettings.tsx index 627f7c1b9e..11ca767bca 100644 --- a/frontend/editor/src/core/components/tools/addPageNumbers/AddPageNumbersAppearanceSettings.tsx +++ b/frontend/editor/src/core/components/tools/addPageNumbers/AddPageNumbersAppearanceSettings.tsx @@ -27,6 +27,7 @@ const AddPageNumbersAppearanceSettings = ({ return ( 001). Set 0 to disable.", @@ -90,6 +93,7 @@ const AddPageNumbersAppearanceSettings = ({ Date: Tue, 7 Jul 2026 10:37:35 +0100 Subject: [PATCH 05/11] Set App version to v2.14.1 (#6891) Upped version in build.gradle then ran build so version falls through --- .github/aur/stirling-pdf-desktop/PKGBUILD | 2 +- .github/aur/stirling-pdf-server-bin/PKGBUILD | 2 +- build.gradle | 2 +- frontend/editor/src-tauri/tauri.conf.json | 2 +- frontend/editor/src/core/testing/serverExperienceSimulations.ts | 2 +- .../src/proprietary/testing/serverExperienceSimulations.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/aur/stirling-pdf-desktop/PKGBUILD b/.github/aur/stirling-pdf-desktop/PKGBUILD index c02bde345e..5d92425b19 100644 --- a/.github/aur/stirling-pdf-desktop/PKGBUILD +++ b/.github/aur/stirling-pdf-desktop/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Stirling PDF Inc pkgname=stirling-pdf-desktop -pkgver=2.14.0 +pkgver=2.14.1 pkgrel=1 pkgdesc="Locally hosted, web-based PDF manipulation tool (Tauri desktop app, official Stirling PDF Inc build)" arch=('x86_64') diff --git a/.github/aur/stirling-pdf-server-bin/PKGBUILD b/.github/aur/stirling-pdf-server-bin/PKGBUILD index c73b1c5087..f5a2bf3c6c 100644 --- a/.github/aur/stirling-pdf-server-bin/PKGBUILD +++ b/.github/aur/stirling-pdf-server-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Stirling PDF Inc pkgname=stirling-pdf-server-bin -pkgver=2.14.0 +pkgver=2.14.1 pkgrel=1 pkgdesc="Locally hosted, web-based PDF manipulation tool (server JAR, prebuilt)" arch=('any') diff --git a/build.gradle b/build.gradle index 3946b32449..5c89fd7af2 100644 --- a/build.gradle +++ b/build.gradle @@ -91,7 +91,7 @@ springBoot { allprojects { group = 'stirling.software' - version = '2.14.0' + version = '2.14.1' configurations.configureEach { exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" diff --git a/frontend/editor/src-tauri/tauri.conf.json b/frontend/editor/src-tauri/tauri.conf.json index 87f3a48a1d..6cfe33cbe6 100644 --- a/frontend/editor/src-tauri/tauri.conf.json +++ b/frontend/editor/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "Stirling PDF", "mainBinaryName": "Stirling-PDF", - "version": "2.14.0", + "version": "2.14.1", "identifier": "stirling.pdf.dev", "build": { "frontendDist": "../dist", diff --git a/frontend/editor/src/core/testing/serverExperienceSimulations.ts b/frontend/editor/src/core/testing/serverExperienceSimulations.ts index 09344f9234..cd7ed8f2ba 100644 --- a/frontend/editor/src/core/testing/serverExperienceSimulations.ts +++ b/frontend/editor/src/core/testing/serverExperienceSimulations.ts @@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: "2.14.0", + appVersion: "2.14.1", serverCertificateEnabled: false, enableAlphaFunctionality: false, serverPort: 8080, diff --git a/frontend/editor/src/proprietary/testing/serverExperienceSimulations.ts b/frontend/editor/src/proprietary/testing/serverExperienceSimulations.ts index 50bf5eb230..1aaabfeb33 100644 --- a/frontend/editor/src/proprietary/testing/serverExperienceSimulations.ts +++ b/frontend/editor/src/proprietary/testing/serverExperienceSimulations.ts @@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: "2.14.0", + appVersion: "2.14.1", serverCertificateEnabled: false, enableAlphaFunctionality: false, enableDesktopInstallSlide: true, From 8535c7e9aced4e050d3f660edd13319eba1a06a4 Mon Sep 17 00:00:00 2001 From: Ludy Date: Tue, 7 Jul 2026 22:57:34 +0200 Subject: [PATCH 06/11] feat(ui): add dedicated third-party license sections to settings (#6820) --- .../public/locales/en-US/translation.toml | 15 + frontend/editor/public/og-metadata.json | 12 + .../editor/src/assets/3rdPartyLicenses.json | 347 ++++++++++++++---- .../shared/config/configNavSections.tsx | 16 + .../ThirdPartyLicensesSection.tsx | 235 ++++++++++++ .../core/components/shared/config/types.ts | 2 + 6 files changed, 555 insertions(+), 72 deletions(-) create mode 100644 frontend/editor/src/core/components/shared/config/configSections/ThirdPartyLicensesSection.tsx diff --git a/frontend/editor/public/locales/en-US/translation.toml b/frontend/editor/public/locales/en-US/translation.toml index 7f81e56f3b..bd5b20bcf6 100644 --- a/frontend/editor/public/locales/en-US/translation.toml +++ b/frontend/editor/public/locales/en-US/translation.toml @@ -6934,6 +6934,21 @@ manage = "Manage" description = "Policies and legal information for this service." title = "Legal Documents" +[settings.licenses] +backendDescription = "Licenses for backend dependencies bundled with this server." +backendLabel = "Backend Licenses" +backendTitle = "Backend 3rd Party Licenses" +empty = "No dependencies found." +frontendDescription = "Licenses for frontend dependencies bundled into the release build." +frontendLabel = "Frontend Licenses" +frontendTitle = "Frontend 3rd Party Licenses" +license = "License" +listDescription = "The list is shown directly in the UI from the release bundle or backend endpoint." +listTitle = "Bundled dependencies" +loadError = "Failed to load third-party licenses" +module = "Module" +version = "Version" + [settings.licensingAnalytics] audit = "Audit" plan = "Plan" diff --git a/frontend/editor/public/og-metadata.json b/frontend/editor/public/og-metadata.json index e8aded59d2..65f1da789c 100644 --- a/frontend/editor/public/og-metadata.json +++ b/frontend/editor/public/og-metadata.json @@ -485,6 +485,16 @@ "title": "Legal Settings - Stirling PDF", "description": "The Free Adobe Acrobat alternative (10M+ Downloads)" }, + "/settings/backendThirdPartyLicenses": { + "image": "/og_images/home.png", + "title": "Backend Third Party Licenses Settings - Stirling PDF", + "description": "The Free Adobe Acrobat alternative (10M+ Downloads)" + }, + "/settings/frontendThirdPartyLicenses": { + "image": "/og_images/home.png", + "title": "Frontend Third Party Licenses Settings - Stirling PDF", + "description": "The Free Adobe Acrobat alternative (10M+ Downloads)" + }, "/settings/payg": { "image": "/og_images/home.png", "title": "Payg Settings - Stirling PDF", @@ -641,6 +651,8 @@ "/settings/adminMcp": "/settings/adminMcp", "/settings/help": "/settings/help", "/settings/legal": "/settings/legal", + "/settings/backendThirdPartyLicenses": "/settings/backendThirdPartyLicenses", + "/settings/frontendThirdPartyLicenses": "/settings/frontendThirdPartyLicenses", "/settings/payg": "/settings/payg" } } diff --git a/frontend/editor/src/assets/3rdPartyLicenses.json b/frontend/editor/src/assets/3rdPartyLicenses.json index baf60879b6..3b606376de 100644 --- a/frontend/editor/src/assets/3rdPartyLicenses.json +++ b/frontend/editor/src/assets/3rdPartyLicenses.json @@ -3,133 +3,182 @@ { "moduleName": "@atlaskit/pragmatic-drag-and-drop", "moduleUrl": "git+https://github.com/atlassian/pragmatic-drag-and-drop.git", - "moduleVersion": "1.7.7", + "moduleVersion": "1.7.9", "moduleLicense": "Apache-2.0", "moduleLicenseUrl": "git+https://github.com/atlassian/pragmatic-drag-and-drop.git" }, { - "moduleName": "@embedpdf/core", - "moduleUrl": "https://registry.npmjs.org/@embedpdf/core/-/core-1.3.1.tgz", - "moduleVersion": "1.3.0", + "moduleName": "@cantoo/pdf-lib", + "moduleUrl": "git+https://github.com/cantoo-scribe/pdf-lib.git", + "moduleVersion": "2.6.5", "moduleLicense": "MIT", - "moduleLicenseUrl": "https://registry.npmjs.org/@embedpdf/core/-/core-1.3.1.tgz" + "moduleLicenseUrl": "git+https://github.com/cantoo-scribe/pdf-lib.git" + }, + { + "moduleName": "@dnd-kit/core", + "moduleUrl": "git+https://github.com/clauderic/dnd-kit.git", + "moduleVersion": "6.3.1", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/clauderic/dnd-kit.git" + }, + { + "moduleName": "@embedpdf/core", + "moduleUrl": "https://registry.npmjs.org/@embedpdf/core/-/core-2.14.4.tgz", + "moduleVersion": "2.14.1", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://registry.npmjs.org/@embedpdf/core/-/core-2.14.4.tgz" }, { "moduleName": "@embedpdf/engines", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" + }, + { + "moduleName": "@embedpdf/models", + "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-annotation", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" + }, + { + "moduleName": "@embedpdf/plugin-attachment", + "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", + "moduleVersion": "2.14.1", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" + }, + { + "moduleName": "@embedpdf/plugin-bookmark", + "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", + "moduleVersion": "2.14.1", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" + }, + { + "moduleName": "@embedpdf/plugin-document-manager", + "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-export", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-history", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-interaction-manager", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", - "moduleLicense": "MIT", - "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" - }, - { - "moduleName": "@embedpdf/plugin-loader", - "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-pan", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" + }, + { + "moduleName": "@embedpdf/plugin-print", + "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", + "moduleVersion": "2.14.1", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" + }, + { + "moduleName": "@embedpdf/plugin-redaction", + "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-render", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-rotate", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-scroll", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-search", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-selection", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-spread", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-thumbnail", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-tiling", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-viewport", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, { "moduleName": "@embedpdf/plugin-zoom", "moduleUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git", - "moduleVersion": "1.3.0", + "moduleVersion": "2.14.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/embedpdf/embed-pdf-viewer.git" }, @@ -157,94 +206,185 @@ { "moduleName": "@mantine/core", "moduleUrl": "git+https://github.com/mantinedev/mantine.git", - "moduleVersion": "8.3.1", + "moduleVersion": "8.3.18", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/mantinedev/mantine.git" }, { "moduleName": "@mantine/dates", "moduleUrl": "git+https://github.com/mantinedev/mantine.git", - "moduleVersion": "8.3.1", + "moduleVersion": "8.3.18", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/mantinedev/mantine.git" }, { "moduleName": "@mantine/dropzone", "moduleUrl": "git+https://github.com/mantinedev/mantine.git", - "moduleVersion": "8.3.1", + "moduleVersion": "8.3.18", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/mantinedev/mantine.git" }, { "moduleName": "@mantine/hooks", "moduleUrl": "git+https://github.com/mantinedev/mantine.git", - "moduleVersion": "8.3.1", + "moduleVersion": "8.3.18", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/mantinedev/mantine.git" }, { "moduleName": "@mui/icons-material", "moduleUrl": "git+https://github.com/mui/material-ui.git", - "moduleVersion": "7.3.2", + "moduleVersion": "9.0.0", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/mui/material-ui.git" }, { "moduleName": "@mui/material", "moduleUrl": "git+https://github.com/mui/material-ui.git", - "moduleVersion": "7.3.2", + "moduleVersion": "9.0.0", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/mui/material-ui.git" }, { - "moduleName": "@tailwindcss/postcss", - "moduleUrl": "git+https://github.com/tailwindlabs/tailwindcss.git", - "moduleVersion": "4.1.13", + "moduleName": "@posthog/react", + "moduleUrl": "git+https://github.com/PostHog/posthog-js.git", + "moduleVersion": "1.8.2", "moduleLicense": "MIT", - "moduleLicenseUrl": "git+https://github.com/tailwindlabs/tailwindcss.git" + "moduleLicenseUrl": "git+https://github.com/PostHog/posthog-js.git" + }, + { + "moduleName": "@reactour/tour", + "moduleUrl": "git+https://github.com/elrumordelaluz/reactour.git", + "moduleVersion": "3.8.0", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/elrumordelaluz/reactour.git" + }, + { + "moduleName": "@stripe/react-stripe-js", + "moduleUrl": "https://github.com/stripe/react-stripe-js.git", + "moduleVersion": "4.0.2", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://github.com/stripe/react-stripe-js.git" + }, + { + "moduleName": "@stripe/stripe-js", + "moduleUrl": "https://github.com/stripe/stripe-js.git", + "moduleVersion": "7.9.0", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://github.com/stripe/stripe-js.git" + }, + { + "moduleName": "@supabase/supabase-js", + "moduleUrl": "https://github.com/supabase/supabase-js.git", + "moduleVersion": "2.100.0", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://github.com/supabase/supabase-js.git" + }, + { + "moduleName": "@tailwindcss/postcss", + "moduleUrl": "https://github.com/tailwindlabs/tailwindcss.git", + "moduleVersion": "4.2.2", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://github.com/tailwindlabs/tailwindcss.git" }, { "moduleName": "@tanstack/react-virtual", "moduleUrl": "git+https://github.com/TanStack/virtual.git", - "moduleVersion": "3.13.12", + "moduleVersion": "3.13.23", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/TanStack/virtual.git" }, + { + "moduleName": "@tauri-apps/api", + "moduleUrl": "git+https://github.com/tauri-apps/tauri.git", + "moduleVersion": "2.10.1", + "moduleLicense": "Apache-2.0 OR MIT", + "moduleLicenseUrl": "git+https://github.com/tauri-apps/tauri.git" + }, + { + "moduleName": "@tauri-apps/plugin-dialog", + "moduleUrl": "git+https://github.com/tauri-apps/plugins-workspace.git", + "moduleVersion": "2.7.0", + "moduleLicense": "MIT OR Apache-2.0", + "moduleLicenseUrl": "git+https://github.com/tauri-apps/plugins-workspace.git" + }, + { + "moduleName": "@tauri-apps/plugin-fs", + "moduleUrl": "git+https://github.com/tauri-apps/plugins-workspace.git", + "moduleVersion": "2.5.0", + "moduleLicense": "MIT OR Apache-2.0", + "moduleLicenseUrl": "git+https://github.com/tauri-apps/plugins-workspace.git" + }, + { + "moduleName": "@tauri-apps/plugin-http", + "moduleUrl": "git+https://github.com/tauri-apps/plugins-workspace.git", + "moduleVersion": "2.5.7", + "moduleLicense": "MIT OR Apache-2.0", + "moduleLicenseUrl": "git+https://github.com/tauri-apps/plugins-workspace.git" + }, + { + "moduleName": "@tauri-apps/plugin-notification", + "moduleUrl": "git+https://github.com/tauri-apps/plugins-workspace.git", + "moduleVersion": "2.3.3", + "moduleLicense": "MIT OR Apache-2.0", + "moduleLicenseUrl": "git+https://github.com/tauri-apps/plugins-workspace.git" + }, + { + "moduleName": "@tauri-apps/plugin-shell", + "moduleUrl": "git+https://github.com/tauri-apps/plugins-workspace.git", + "moduleVersion": "2.3.5", + "moduleLicense": "MIT OR Apache-2.0", + "moduleLicenseUrl": "git+https://github.com/tauri-apps/plugins-workspace.git" + }, + { + "moduleName": "@userback/widget", + "moduleUrl": "git+https://github.com/userback/widget-js.git", + "moduleVersion": "0.3.12", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/userback/widget-js.git" + }, { "moduleName": "autoprefixer", "moduleUrl": "git+https://github.com/postcss/autoprefixer.git", - "moduleVersion": "10.4.21", + "moduleVersion": "10.4.27", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/postcss/autoprefixer.git" }, { "moduleName": "axios", "moduleUrl": "git+https://github.com/axios/axios.git", - "moduleVersion": "1.12.2", + "moduleVersion": "1.15.0", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/axios/axios.git" }, + { + "moduleName": "d3", + "moduleUrl": "git+https://github.com/d3/d3.git", + "moduleVersion": "7.9.0", + "moduleLicense": "ISC", + "moduleLicenseUrl": "git+https://github.com/d3/d3.git" + }, + { + "moduleName": "globals", + "moduleUrl": "git+https://github.com/sindresorhus/globals.git", + "moduleVersion": "17.5.0", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/sindresorhus/globals.git" + }, { "moduleName": "i18next", "moduleUrl": "git+https://github.com/i18next/i18next.git", - "moduleVersion": "25.5.2", + "moduleVersion": "25.10.10", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/i18next/i18next.git" }, { "moduleName": "i18next-browser-languagedetector", "moduleUrl": "git+https://github.com/i18next/i18next-browser-languageDetector.git", - "moduleVersion": "8.2.0", + "moduleVersion": "8.2.1", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/i18next/i18next-browser-languageDetector.git" }, - { - "moduleName": "i18next-http-backend", - "moduleUrl": "git+ssh://git@github.com/i18next/i18next-http-backend.git", - "moduleVersion": "3.0.2", - "moduleLicense": "MIT", - "moduleLicenseUrl": "git+ssh://git@github.com/i18next/i18next-http-backend.git" - }, { "moduleName": "jszip", "moduleUrl": "git+https://github.com/Stuk/jszip.git", @@ -254,66 +394,129 @@ }, { "moduleName": "license-report", - "moduleUrl": "git+https://github.com/kessler/license-report.git", - "moduleVersion": "6.8.0", + "moduleUrl": "git+https://github.com/bepo65/license-report.git", + "moduleVersion": "6.8.2", "moduleLicense": "MIT", - "moduleLicenseUrl": "git+https://github.com/kessler/license-report.git" - }, - { - "moduleName": "pdf-lib", - "moduleUrl": "git+https://github.com/Hopding/pdf-lib.git", - "moduleVersion": "1.17.1", - "moduleLicense": "MIT", - "moduleLicenseUrl": "git+https://github.com/Hopding/pdf-lib.git" + "moduleLicenseUrl": "git+https://github.com/bepo65/license-report.git" }, { "moduleName": "pdfjs-dist", "moduleUrl": "git+https://github.com/mozilla/pdf.js.git", - "moduleVersion": "5.4.149", + "moduleVersion": "5.5.207", "moduleLicense": "Apache-2.0", "moduleLicenseUrl": "git+https://github.com/mozilla/pdf.js.git" }, + { + "moduleName": "peerjs", + "moduleUrl": "git+https://github.com/peers/peerjs.git", + "moduleVersion": "1.5.5", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/peers/peerjs.git" + }, + { + "moduleName": "pixelmatch", + "moduleUrl": "git+https://github.com/mapbox/pixelmatch.git", + "moduleVersion": "7.1.0", + "moduleLicense": "ISC", + "moduleLicenseUrl": "git+https://github.com/mapbox/pixelmatch.git" + }, { "moduleName": "posthog-js", - "moduleUrl": "git+https://github.com/PostHog/posthog-js.git", - "moduleVersion": "1.268.0", + "moduleUrl": "https://github.com/PostHog/posthog-js", + "moduleVersion": "1.363.3", "moduleLicense": "SEE LICENSE IN LICENSE https://github.com/PostHog/posthog-js/blob/main/LICENSE", - "moduleLicenseUrl": "git+https://github.com/PostHog/posthog-js.git" + "moduleLicenseUrl": "https://github.com/PostHog/posthog-js" + }, + { + "moduleName": "qrcode.react", + "moduleUrl": "git+https://github.com/zpao/qrcode.react.git", + "moduleVersion": "4.2.0", + "moduleLicense": "ISC", + "moduleLicenseUrl": "git+https://github.com/zpao/qrcode.react.git" }, { "moduleName": "react", "moduleUrl": "git+https://github.com/facebook/react.git", - "moduleVersion": "19.1.1", + "moduleVersion": "19.2.4", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/facebook/react.git" }, { "moduleName": "react-dom", "moduleUrl": "git+https://github.com/facebook/react.git", - "moduleVersion": "19.1.1", + "moduleVersion": "19.2.4", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/facebook/react.git" }, + { + "moduleName": "react-easy-crop", + "moduleUrl": "git+https://github.com/ValentinH/react-easy-crop.git", + "moduleVersion": "5.5.6", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/ValentinH/react-easy-crop.git" + }, { "moduleName": "react-i18next", "moduleUrl": "git+https://github.com/i18next/react-i18next.git", - "moduleVersion": "15.7.3", + "moduleVersion": "16.6.6", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/i18next/react-i18next.git" }, + { + "moduleName": "react-markdown", + "moduleUrl": "git+https://github.com/remarkjs/react-markdown.git", + "moduleVersion": "9.1.0", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/remarkjs/react-markdown.git" + }, + { + "moduleName": "react-rnd", + "moduleUrl": "git+https://github.com/bokuweb/react-rnd.git", + "moduleVersion": "10.5.3", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/bokuweb/react-rnd.git" + }, { "moduleName": "react-router-dom", "moduleUrl": "git+https://github.com/remix-run/react-router.git", - "moduleVersion": "7.9.1", + "moduleVersion": "7.13.2", "moduleLicense": "MIT", "moduleLicenseUrl": "git+https://github.com/remix-run/react-router.git" }, { - "moduleName": "tailwindcss", - "moduleUrl": "git+https://github.com/tailwindlabs/tailwindcss.git", - "moduleVersion": "4.1.13", + "moduleName": "recharts", + "moduleUrl": "git+https://github.com/recharts/recharts.git", + "moduleVersion": "3.8.0", "moduleLicense": "MIT", - "moduleLicenseUrl": "git+https://github.com/tailwindlabs/tailwindcss.git" + "moduleLicenseUrl": "git+https://github.com/recharts/recharts.git" + }, + { + "moduleName": "remark-gfm", + "moduleUrl": "git+https://github.com/remarkjs/remark-gfm.git", + "moduleVersion": "4.0.1", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/remarkjs/remark-gfm.git" + }, + { + "moduleName": "signature_pad", + "moduleUrl": "git+https://github.com/szimek/signature_pad.git", + "moduleVersion": "5.1.3", + "moduleLicense": "MIT", + "moduleLicenseUrl": "git+https://github.com/szimek/signature_pad.git" + }, + { + "moduleName": "smol-toml", + "moduleUrl": "github:squirrelchat/smol-toml", + "moduleVersion": "1.6.1", + "moduleLicense": "BSD-3-Clause", + "moduleLicenseUrl": "github:squirrelchat/smol-toml" + }, + { + "moduleName": "tailwindcss", + "moduleUrl": "https://github.com/tailwindlabs/tailwindcss.git", + "moduleVersion": "4.2.2", + "moduleLicense": "MIT", + "moduleLicenseUrl": "https://github.com/tailwindlabs/tailwindcss.git" }, { "moduleName": "web-vitals", diff --git a/frontend/editor/src/core/components/shared/config/configNavSections.tsx b/frontend/editor/src/core/components/shared/config/configNavSections.tsx index 260cbe970a..db02ad8458 100644 --- a/frontend/editor/src/core/components/shared/config/configNavSections.tsx +++ b/frontend/editor/src/core/components/shared/config/configNavSections.tsx @@ -5,6 +5,10 @@ import HotkeysSection from "@app/components/shared/config/configSections/Hotkeys import GeneralSection from "@app/components/shared/config/configSections/GeneralSection"; import HelpSection from "@app/components/shared/config/configSections/HelpSection"; import LegalSection from "@app/components/shared/config/configSections/LegalSection"; +import { + BackendThirdPartyLicensesSection, + FrontendThirdPartyLicensesSection, +} from "@app/components/shared/config/configSections/ThirdPartyLicensesSection"; export interface ConfigNavItem { key: NavKey; @@ -80,6 +84,18 @@ export const useConfigNavSections = ( icon: "gavel-rounded", component: , }, + { + key: "backendThirdPartyLicenses", + label: t("settings.licenses.backendLabel", "Backend Licenses"), + icon: "article-rounded", + component: , + }, + { + key: "frontendThirdPartyLicenses", + label: t("settings.licenses.frontendLabel", "Frontend Licenses"), + icon: "code-rounded", + component: , + }, ], }, ]; diff --git a/frontend/editor/src/core/components/shared/config/configSections/ThirdPartyLicensesSection.tsx b/frontend/editor/src/core/components/shared/config/configSections/ThirdPartyLicensesSection.tsx new file mode 100644 index 0000000000..5d0de982fc --- /dev/null +++ b/frontend/editor/src/core/components/shared/config/configSections/ThirdPartyLicensesSection.tsx @@ -0,0 +1,235 @@ +import { useEffect, useMemo, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { + Alert, + Anchor, + Group, + Loader, + Paper, + Stack, + Table, + Text, +} from "@mantine/core"; +import { isAxiosError } from "axios"; +import apiClient from "@app/services/apiClient"; +import frontendLicenses from "../../../../../assets/3rdPartyLicenses.json"; // eslint-disable-line no-restricted-imports -- asset lives outside @app alias root + +interface Dependency { + moduleName?: string; + moduleUrl?: string; + moduleVersion?: string; + moduleLicense?: string; + moduleLicenseUrl?: string; +} + +interface LicensesResponse { + dependencies?: Dependency[]; +} + +interface LicensesSectionBodyProps { + title: string; + description: string; + dependencies: Dependency[]; +} + +function LicensesSectionBody({ + title, + description, + dependencies, +}: LicensesSectionBodyProps) { + const { t } = useTranslation(); + const sortedDependencies = useMemo( + () => + [...dependencies].sort((a, b) => + (a.moduleName || "").localeCompare(b.moduleName || ""), + ), + [dependencies], + ); + + const getDependencyKey = (dependency: Dependency) => + [ + dependency.moduleName ?? "module", + dependency.moduleVersion ?? "version", + dependency.moduleUrl ?? "url", + ].join(":"); + + return ( + + + +
+ + {title} + + + {description} + +
+ + +
+ + {t("settings.licenses.listTitle", "Bundled dependencies")} + + + {t( + "settings.licenses.listDescription", + "The list is shown directly in the UI from the release bundle or backend endpoint.", + )} + +
+
+ + + + + {t("settings.licenses.module", "Module")} + {t("settings.licenses.version", "Version")} + {t("settings.licenses.license", "License")} + + + + {sortedDependencies.length === 0 ? ( + + + + {t("settings.licenses.empty", "No dependencies found.")} + + + + ) : ( + sortedDependencies.map((dependency) => ( + + + {dependency.moduleUrl ? ( + + {dependency.moduleName || "-"} + + ) : ( + {dependency.moduleName || "-"} + )} + + + + {dependency.moduleVersion || "-"} + + + + {dependency.moduleLicenseUrl ? ( + + {dependency.moduleLicense || "-"} + + ) : ( + {dependency.moduleLicense || "-"} + )} + + + )) + )} + +
+
+
+
+ ); +} + +export function BackendThirdPartyLicensesSection() { + const { t } = useTranslation(); + const [dependencies, setDependencies] = useState([]); + const [loading, setLoading] = useState(true); + const [error, setError] = useState(null); + + useEffect(() => { + const loadLicenses = async () => { + try { + setLoading(true); + setError(null); + const response = await apiClient.get( + "/api/v1/ui-data/licenses", + { suppressErrorToast: true }, + ); + setDependencies(response.data?.dependencies ?? []); + } catch (err: unknown) { + setError( + isAxiosError(err) + ? err.response?.data?.message || err.message + : t( + "settings.licenses.loadError", + "Failed to load third-party licenses", + ), + ); + } finally { + setLoading(false); + } + }; + + void loadLicenses(); + }, [t]); + + if (loading) { + return ( + + + + ); + } + + if (error) { + return ( + + + {error} + + + ); + } + + return ( + + ); +} + +export function FrontendThirdPartyLicensesSection() { + const { t } = useTranslation(); + const dependencies = + (frontendLicenses as LicensesResponse).dependencies ?? []; + + return ( + + ); +} + +export default function ThirdPartyLicensesSection() { + return ; +} diff --git a/frontend/editor/src/core/components/shared/config/types.ts b/frontend/editor/src/core/components/shared/config/types.ts index a71866d0c1..9fa87fe915 100644 --- a/frontend/editor/src/core/components/shared/config/types.ts +++ b/frontend/editor/src/core/components/shared/config/types.ts @@ -32,6 +32,8 @@ export const VALID_NAV_KEYS = [ "adminMcp", "help", "legal", + "backendThirdPartyLicenses", + "frontendThirdPartyLicenses", "payg", ] as const; From 01a1ef8c44448a08f67fa31231175af0df4194ee Mon Sep 17 00:00:00 2001 From: LFdev <146497073+LFd3v@users.noreply.github.com> Date: Tue, 7 Jul 2026 17:58:21 -0300 Subject: [PATCH 07/11] Fix missing app icon on Linux/Wayland (#6875) Co-authored-by: EthanHealy01 <80844253+EthanHealy01@users.noreply.github.com> Co-authored-by: Ludy --- frontend/editor/src-tauri/stirling-pdf.desktop | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/frontend/editor/src-tauri/stirling-pdf.desktop b/frontend/editor/src-tauri/stirling-pdf.desktop index e9e26a39c1..13da1b88c6 100644 --- a/frontend/editor/src-tauri/stirling-pdf.desktop +++ b/frontend/editor/src-tauri/stirling-pdf.desktop @@ -10,7 +10,8 @@ Terminal=false MimeType=application/pdf; Categories=Office;Graphics;Utility; Actions=open-file; +StartupWMClass=Stirling-PDF [Desktop Action open-file] Name=Open PDF File -Exec={{exec}} %F \ No newline at end of file +Exec={{exec}} %F From 5fba2720f0a350fb395f3aa9741c828562aee2b8 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Tue, 7 Jul 2026 22:39:57 +0100 Subject: [PATCH 08/11] Fix cert sign not showing under certain instances (#6908) --- .../tests/stubbed/cert-sign-wizard.spec.ts | 24 ++++++++++++++----- frontend/editor/src/core/tools/CertSign.tsx | 22 +++++++++++++++++ 2 files changed, 40 insertions(+), 6 deletions(-) diff --git a/frontend/editor/src/core/tests/stubbed/cert-sign-wizard.spec.ts b/frontend/editor/src/core/tests/stubbed/cert-sign-wizard.spec.ts index 807e4231d4..17f9070a78 100644 --- a/frontend/editor/src/core/tests/stubbed/cert-sign-wizard.spec.ts +++ b/frontend/editor/src/core/tests/stubbed/cert-sign-wizard.spec.ts @@ -59,7 +59,7 @@ async function mockHardwareEndpoints(page: Page) { } test.describe("CertSign tool - certificate source model", () => { - test("renders, accepts a PDF, and exposes the Upload source", async ({ + test("skips the redundant source step and goes straight to certificate format when Upload is the only source", async ({ page, }) => { await page.route("**/api/v1/security/cert-sign", (route) => @@ -76,10 +76,21 @@ test.describe("CertSign tool - certificate source model", () => { await uploadFiles(page, SAMPLE_PDF); await expect(page).toHaveURL(/\/cert-sign/); - // Source step always offers "Upload" (the former "Manual" mode). + // With no server cert or hardware token there is nothing to choose, so the + // whole "Certificate source" step is hidden and the format picker shows directly. await expect( - page.getByRole("button", { name: /^upload$/i }).first(), - ).toBeAttached({ timeout: 10_000 }); + page.getByRole("button", { name: /pkcs12/i }).first(), + ).toBeVisible({ timeout: 10_000 }); + await expect(page.getByText(/certificate source/i)).toHaveCount(0); + await expect( + page.getByText(/no other certificate sources are available/i), + ).toHaveCount(0); + await expect( + page.getByRole("button", { name: /this device/i }), + ).toHaveCount(0); + await expect(page.getByRole("button", { name: /^server$/i })).toHaveCount( + 0, + ); }); test("does NOT offer 'This device' when not running as desktop", async ({ @@ -89,9 +100,10 @@ test.describe("CertSign tool - certificate source model", () => { await page.waitForLoadState("domcontentloaded"); await uploadFiles(page, SAMPLE_PDF); + // No alternative sources: the source step is hidden, and hardware is never offered. await expect( - page.getByRole("button", { name: /^upload$/i }).first(), - ).toBeAttached({ timeout: 10_000 }); + page.getByRole("button", { name: /pkcs12/i }).first(), + ).toBeVisible({ timeout: 10_000 }); await expect( page.getByRole("button", { name: /this device/i }), ).toHaveCount(0); diff --git a/frontend/editor/src/core/tools/CertSign.tsx b/frontend/editor/src/core/tools/CertSign.tsx index dd0668a491..4f5173b174 100644 --- a/frontend/editor/src/core/tools/CertSign.tsx +++ b/frontend/editor/src/core/tools/CertSign.tsx @@ -1,5 +1,7 @@ +import { useEffect } from "react"; import { useTranslation } from "react-i18next"; import { createToolFlow } from "@app/components/tools/shared/createToolFlow"; +import { useAppConfig } from "@app/contexts/AppConfigContext"; import CertificateTypeSettings from "@app/components/tools/certSign/CertificateTypeSettings"; import CertificateFormatSettings from "@app/components/tools/certSign/CertificateFormatSettings"; import CertificateFilesSettings from "@app/components/tools/certSign/CertificateFilesSettings"; @@ -23,6 +25,25 @@ const CertSign = (props: BaseToolProps) => { props, ); + const { config } = useAppConfig(); + // "Upload" is always available; the source chooser is only meaningful when a + // server certificate or a hardware token gives the user an actual alternative. + const hasCertSourceChoice = + (config?.serverCertificateEnabled ?? false) || + (config?.hardwareSigningAvailable ?? false); + + // With Upload as the only source, keep signMode on MANUAL even if a saved + // automation set AUTO/DEVICE, so the hidden source step can't strand the flow. + useEffect(() => { + if (!hasCertSourceChoice && base.params.parameters.signMode !== "MANUAL") { + base.params.updateParameter("signMode", "MANUAL"); + } + }, [ + hasCertSourceChoice, + base.params.parameters.signMode, + base.params.updateParameter, + ]); + const certTypeTips = useCertificateTypeTips(); const appearanceTips = useSignatureAppearanceTips(); const signModeTips = useSignModeTips(); @@ -63,6 +84,7 @@ const CertSign = (props: BaseToolProps) => { steps: [ { title: t("certSign.source.stepTitle", "Certificate source"), + isVisible: hasCertSourceChoice, isCollapsed: base.settingsCollapsed, onCollapsedClick: base.settingsCollapsed ? base.handleSettingsReset From 72729e99c1294e41dda5ddaf9cf72bd975337d89 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 1 Jul 2026 01:02:38 +0100 Subject: [PATCH 09/11] fix(release): stop msiexec hang in Windows signature verify; don't force latest or regen release notes --- .github/workflows/multiOSReleases.yml | 29 +++++++++++++++++++++------ 1 file changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/multiOSReleases.yml b/.github/workflows/multiOSReleases.yml index f5fb743a3f..3a76708460 100644 --- a/.github/workflows/multiOSReleases.yml +++ b/.github/workflows/multiOSReleases.yml @@ -510,6 +510,7 @@ jobs: # cargo output unsigned, so checking it produces false negatives. - name: Verify Windows Code Signature if: ${{ matrix.platform == 'windows-latest' && env.SM_API_KEY != '' && (github.event_name == 'release' || (github.event_name == 'workflow_dispatch' && github.event.inputs.sign != 'false') || github.ref == 'refs/heads/V2-master') }} + timeout-minutes: 15 shell: pwsh run: | $allSigned = $true @@ -531,11 +532,26 @@ jobs: # Extract MSI and verify the inner exe (the file that actually gets installed). # This is the critical check - AV flags the installed exe at runtime. + # Use lessmsi, not `msiexec /a`: msiexec serializes on the global + # _MSIExecute mutex and hangs forever on hosted runners when another + # installer is busy. lessmsi reads MSI tables directly - no mutex, no service. $msi = $msiFiles[0].FullName $extractDir = Join-Path $env:RUNNER_TEMP "msi-verify" if (Test-Path $extractDir) { Remove-Item $extractDir -Recurse -Force } - $proc = Start-Process msiexec.exe -ArgumentList '/a', $msi, '/qn', "TARGETDIR=$extractDir" -Wait -PassThru -NoNewWindow - if ($proc.ExitCode -eq 0) { + New-Item -ItemType Directory -Force -Path $extractDir | Out-Null + + choco install lessmsi -y --no-progress --limit-output | Out-Null + + # Bound the extraction and kill on hang (defence in depth over timeout-minutes). + $proc = Start-Process lessmsi -ArgumentList 'x', "`"$msi`"", "`"$extractDir\`"" -PassThru -NoNewWindow + if (-not $proc.WaitForExit(120000)) { + try { $proc.Kill() } catch {} + Write-Host "[ERROR] MSI extraction timed out after 120s" + $allSigned = $false + } elseif ($proc.ExitCode -ne 0) { + Write-Host "[ERROR] Failed to extract MSI for verification (exit code: $($proc.ExitCode))" + $allSigned = $false + } else { $innerExe = Get-ChildItem -Path $extractDir -Filter "stirling-pdf.exe" -Recurse -File | Select-Object -First 1 if ($innerExe) { $sig = Get-AuthenticodeSignature -FilePath $innerExe.FullName @@ -548,9 +564,6 @@ jobs: Write-Host "[ERROR] Could not find stirling-pdf.exe inside MSI" $allSigned = $false } - } else { - Write-Host "[ERROR] Failed to extract MSI for verification (exit code: $($proc.ExitCode))" - $allSigned = $false } if (-not $allSigned) { @@ -800,7 +813,11 @@ jobs: uses: softprops/action-gh-release@b4309332981a82ec1c5618f44dd2e27cc8bfbfda # v3.0.0 with: tag_name: v${{ needs.determine-matrix.outputs.version }} - generate_release_notes: true + # Don't regenerate/append notes on re-runs, and don't force this into the + # "Latest" slot - leave the release body and latest marker as they are. + generate_release_notes: false + append_body: false + make_latest: false fail_on_unmatched_files: true # Installers + updater payloads + manifest. .sig contents are embedded # in latest.json so the .sig files themselves are not uploaded. From a7307ff393f05733281f9521db12af2fd073dc05 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:50:36 +0100 Subject: [PATCH 10/11] Fix Postgres user settings for some users --- .../java/stirling/software/proprietary/security/model/User.java | 1 - 1 file changed, 1 deletion(-) diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java index 2b2d22cfc7..eeeda1823b 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/model/User.java @@ -103,7 +103,6 @@ public class User implements UserDetails, Serializable { @ElementCollection @MapKeyColumn(name = "setting_key") - @Lob @Column(name = "setting_value", columnDefinition = "text") @CollectionTable(name = "user_settings", joinColumns = @JoinColumn(name = "user_id")) @JsonIgnore From 38ccea074cefe98235b1f67a5c4d0eabcc55c523 Mon Sep 17 00:00:00 2001 From: Anthony Stirling <77850077+Frooodle@users.noreply.github.com> Date: Wed, 8 Jul 2026 10:50:37 +0100 Subject: [PATCH 11/11] Version bump --- .github/aur/stirling-pdf-desktop/PKGBUILD | 2 +- .github/aur/stirling-pdf-server-bin/PKGBUILD | 2 +- build.gradle | 2 +- frontend/editor/src-tauri/tauri.conf.json | 2 +- frontend/editor/src/core/testing/serverExperienceSimulations.ts | 2 +- .../src/proprietary/testing/serverExperienceSimulations.ts | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/aur/stirling-pdf-desktop/PKGBUILD b/.github/aur/stirling-pdf-desktop/PKGBUILD index 5d92425b19..fb6a99cfca 100644 --- a/.github/aur/stirling-pdf-desktop/PKGBUILD +++ b/.github/aur/stirling-pdf-desktop/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Stirling PDF Inc pkgname=stirling-pdf-desktop -pkgver=2.14.1 +pkgver=2.14.2 pkgrel=1 pkgdesc="Locally hosted, web-based PDF manipulation tool (Tauri desktop app, official Stirling PDF Inc build)" arch=('x86_64') diff --git a/.github/aur/stirling-pdf-server-bin/PKGBUILD b/.github/aur/stirling-pdf-server-bin/PKGBUILD index f5a2bf3c6c..70bcee0423 100644 --- a/.github/aur/stirling-pdf-server-bin/PKGBUILD +++ b/.github/aur/stirling-pdf-server-bin/PKGBUILD @@ -1,6 +1,6 @@ # Maintainer: Stirling PDF Inc pkgname=stirling-pdf-server-bin -pkgver=2.14.1 +pkgver=2.14.2 pkgrel=1 pkgdesc="Locally hosted, web-based PDF manipulation tool (server JAR, prebuilt)" arch=('any') diff --git a/build.gradle b/build.gradle index 5c89fd7af2..073bccc654 100644 --- a/build.gradle +++ b/build.gradle @@ -91,7 +91,7 @@ springBoot { allprojects { group = 'stirling.software' - version = '2.14.1' + version = '2.14.2' configurations.configureEach { exclude group: "org.springframework.boot", module: "spring-boot-starter-tomcat" diff --git a/frontend/editor/src-tauri/tauri.conf.json b/frontend/editor/src-tauri/tauri.conf.json index 6cfe33cbe6..dee2cc7023 100644 --- a/frontend/editor/src-tauri/tauri.conf.json +++ b/frontend/editor/src-tauri/tauri.conf.json @@ -2,7 +2,7 @@ "$schema": "../node_modules/@tauri-apps/cli/config.schema.json", "productName": "Stirling PDF", "mainBinaryName": "Stirling-PDF", - "version": "2.14.1", + "version": "2.14.2", "identifier": "stirling.pdf.dev", "build": { "frontendDist": "../dist", diff --git a/frontend/editor/src/core/testing/serverExperienceSimulations.ts b/frontend/editor/src/core/testing/serverExperienceSimulations.ts index cd7ed8f2ba..97da95c730 100644 --- a/frontend/editor/src/core/testing/serverExperienceSimulations.ts +++ b/frontend/editor/src/core/testing/serverExperienceSimulations.ts @@ -38,7 +38,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: "2.14.1", + appVersion: "2.14.2", serverCertificateEnabled: false, enableAlphaFunctionality: false, serverPort: 8080, diff --git a/frontend/editor/src/proprietary/testing/serverExperienceSimulations.ts b/frontend/editor/src/proprietary/testing/serverExperienceSimulations.ts index 1aaabfeb33..d92cf8fdf9 100644 --- a/frontend/editor/src/proprietary/testing/serverExperienceSimulations.ts +++ b/frontend/editor/src/proprietary/testing/serverExperienceSimulations.ts @@ -48,7 +48,7 @@ const FREE_LICENSE_INFO: LicenseInfo = { const BASE_NO_LOGIN_CONFIG: AppConfig = { enableAnalytics: true, - appVersion: "2.14.1", + appVersion: "2.14.2", serverCertificateEnabled: false, enableAlphaFunctionality: false, enableDesktopInstallSlide: true,