From e3ecb508ca228b353d0e8efebe4ee1eb3db8208a Mon Sep 17 00:00:00 2001 From: michael berry Date: Tue, 1 Sep 2026 19:23:42 -0700 Subject: [PATCH 1/9] Refresh Windows packaging dispatch trigger (#506) --- .github/workflows/WindowsInstall.yml | 1 + 1 file changed, 1 insertion(+) diff --git a/.github/workflows/WindowsInstall.yml b/.github/workflows/WindowsInstall.yml index 60c7b1ff..f2576987 100644 --- a/.github/workflows/WindowsInstall.yml +++ b/.github/workflows/WindowsInstall.yml @@ -1,6 +1,7 @@ name: Windows_MSI on: + # Keep manual packaging qualification available for exact source SHAs. workflow_dispatch: inputs: source_sha: From 6c628b41e44136821f08d5914bdee2ca1d671d02 Mon Sep 17 00:00:00 2001 From: mberrys Date: Tue, 1 Sep 2026 21:22:46 -0700 Subject: [PATCH 2/9] fix: apply P1-P2 codex fixes and linux test catalog drift (PR 516) - LoopLibCore: pass PDFProcessingBudget to PDFTextLayoutGenerator during searchDocumentText to enforce hostile-workload limits - LoopEditor QuickOutlineModel: expose page role from PDFOutlineItem destination and route via implemented goToPage/goToOutlinePage - LoopEditor DocumentPane/Host: make searchPanelVisible one-shot via acknowledgeSearchPanel to prevent repeated reveal on presentationChanged - UnitTests: bump catalog implemented count 16 -> 25 to match shell-implemented find/layout commands added in unstable --- LoopEditor/editorhost.cpp | 18 ++++++++++++++++++ LoopEditor/editorhost.h | 2 ++ LoopEditor/qml/DocumentPane.qml | 8 ++++++-- LoopEditor/quickdocumentmodel.cpp | 20 +++++++++++++++++++- LoopEditor/quickdocumentmodel.h | 1 + LoopLibCore/sources/pdfdocumentsearch.cpp | 3 ++- LoopLibCore/sources/pdftextlayoutgenerator.h | 5 +++-- UnitTests/tst_documentfacadetest.cpp | 2 +- 8 files changed, 52 insertions(+), 7 deletions(-) diff --git a/LoopEditor/editorhost.cpp b/LoopEditor/editorhost.cpp index e0b0109e..dbfaca51 100644 --- a/LoopEditor/editorhost.cpp +++ b/LoopEditor/editorhost.cpp @@ -225,6 +225,13 @@ void EditorHost::goToPage(int pageIndex) bumpPresentation(); } +void EditorHost::goToOutlinePage(int pageIndex) +{ + // Outline navigation reuses the implemented viewport path; separate entry + // keeps QML from depending on an unimplemented goToOutlineIndex. + goToPage(pageIndex); +} + void EditorHost::acknowledgeWorkspaceRequest() { if (m_workspaceRequest < 0) @@ -236,6 +243,17 @@ void EditorHost::acknowledgeWorkspaceRequest() Q_EMIT presentationChanged(); } +void EditorHost::acknowledgeSearchPanel() +{ + if (!m_searchPanelVisible) + { + return; + } + + m_searchPanelVisible = false; + Q_EMIT presentationChanged(); +} + QString EditorHost::preflightStateName() const { return preflightStateToString(m_preflight.state()); diff --git a/LoopEditor/editorhost.h b/LoopEditor/editorhost.h index 0ed35526..794e3747 100644 --- a/LoopEditor/editorhost.h +++ b/LoopEditor/editorhost.h @@ -159,7 +159,9 @@ class EditorHost final : public QObject /// the document stays open. Q_INVOKABLE void toggleCurrentPageFidelity(); Q_INVOKABLE void goToPage(int pageIndex); + Q_INVOKABLE void goToOutlinePage(int pageIndex); Q_INVOKABLE void acknowledgeWorkspaceRequest(); + Q_INVOKABLE void acknowledgeSearchPanel(); Q_INVOKABLE QVariantList commandDescriptors() const; Q_INVOKABLE bool isCommandEnabled(const QString& commandId) const; diff --git a/LoopEditor/qml/DocumentPane.qml b/LoopEditor/qml/DocumentPane.qml index 6b80ef75..f03119c9 100644 --- a/LoopEditor/qml/DocumentPane.qml +++ b/LoopEditor/qml/DocumentPane.qml @@ -78,7 +78,9 @@ Item { width: outlineView.width text: model.display !== undefined ? model.display : title Accessible.name: text - onClicked: if (root.host && model.index !== undefined) root.host.goToOutlineIndex(model.index) + enabled: page >= 0 + onClicked: if (root.host && page >= 0) + root.host.goToOutlinePage(page) } Label { @@ -162,8 +164,10 @@ Item { Connections { target: root.host function onPresentationChanged() { - if (root.host && root.host.searchPanelVisible) + if (root.host && root.host.searchPanelVisible) { root.revealSearch() + root.host.acknowledgeSearchPanel() + } } } } diff --git a/LoopEditor/quickdocumentmodel.cpp b/LoopEditor/quickdocumentmodel.cpp index 08d15541..3145431f 100644 --- a/LoopEditor/quickdocumentmodel.cpp +++ b/LoopEditor/quickdocumentmodel.cpp @@ -1,6 +1,7 @@ // MIT License #include "quickdocumentmodel.h" +#include "pdfaction.h" #include "pdfcatalog.h" #include "pdfdocument.h" #include "pdfdocumentcontext.h" @@ -138,12 +139,29 @@ QVariant QuickOutlineModel::data(const QModelIndex& index, int role) const return node->item->getTitle(); if (role == HasChildrenRole) return !node->children.empty(); + if (role == PageRole) + { + const pdf::PDFAction* action = node->item->getAction(); + if (!action) + return -1; + if (action->getType() == pdf::ActionType::GoTo) + { + const auto* goTo = static_cast(action); + const pdf::PDFDestination& dest = goTo->getDestination(); + if (dest.isValid() && !dest.isNamedDestination()) + return static_cast(dest.getPageIndex()); + const pdf::PDFDestination& structDest = goTo->getStructureDestination(); + if (structDest.isValid() && !structDest.isNamedDestination()) + return static_cast(structDest.getPageIndex()); + } + return -1; + } return {}; } QHash QuickOutlineModel::roleNames() const { - return { { TitleRole, "title" }, { HasChildrenRole, "hasChildren" } }; + return { { TitleRole, "title" }, { HasChildrenRole, "hasChildren" }, { PageRole, "page" } }; } void QuickOutlineModel::build(Node* parent, const pdf::PDFOutlineItem* item) diff --git a/LoopEditor/quickdocumentmodel.h b/LoopEditor/quickdocumentmodel.h index b19e9962..6a39028d 100644 --- a/LoopEditor/quickdocumentmodel.h +++ b/LoopEditor/quickdocumentmodel.h @@ -66,6 +66,7 @@ class QuickOutlineModel final : public QAbstractItemModel { TitleRole = Qt::UserRole + 1, HasChildrenRole, + PageRole, }; explicit QuickOutlineModel(QObject* parent = nullptr); diff --git a/LoopLibCore/sources/pdfdocumentsearch.cpp b/LoopLibCore/sources/pdfdocumentsearch.cpp index 88a1813d..745b484f 100644 --- a/LoopLibCore/sources/pdfdocumentsearch.cpp +++ b/LoopLibCore/sources/pdfdocumentsearch.cpp @@ -33,7 +33,8 @@ PDFDocumentSearchResult searchDocumentText(PDFDocumentContext* context, const PDFPage* page = catalog->getPage(pageIndex); PDFTextLayoutGenerator generator(features, page, document, session->getFontCache(), session->getCMS(), - session->getOptionalContentActivity(), QTransform(), meshQuality); + session->getOptionalContentActivity(), QTransform(), meshQuality, + session->getProcessingBudget()); generator.processContents(); const PDFTextFlows flows = PDFTextFlow::createTextFlows( generator.createTextLayout(), diff --git a/LoopLibCore/sources/pdftextlayoutgenerator.h b/LoopLibCore/sources/pdftextlayoutgenerator.h index 721948fd..a1fbe41c 100644 --- a/LoopLibCore/sources/pdftextlayoutgenerator.h +++ b/LoopLibCore/sources/pdftextlayoutgenerator.h @@ -37,8 +37,9 @@ class LOOPLIBCORESHARED_EXPORT PDFTextLayoutGenerator : public PDFPageContentPro const PDFCMS* cms, const PDFOptionalContentActivity* optionalContentActivity, QTransform pagePointToDevicePointMatrix, - const PDFMeshQualitySettings& meshQualitySettings) : - BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings), + const PDFMeshQualitySettings& meshQualitySettings, + PDFProcessingBudget* processingBudget = nullptr) : + BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings, processingBudget), m_features(features) { diff --git a/UnitTests/tst_documentfacadetest.cpp b/UnitTests/tst_documentfacadetest.cpp index 43af532f..8d8a71f8 100644 --- a/UnitTests/tst_documentfacadetest.cpp +++ b/UnitTests/tst_documentfacadetest.cpp @@ -342,7 +342,7 @@ void DocumentFacadeTest::catalogLoadsTheWholeEditorActionSet() QVERIFY(descriptor.capability != pdfinteraction::CommandCapability::Unclassified); } } - QCOMPARE(implemented, 16); + QCOMPARE(implemented, 25); } void DocumentFacadeTest::catalogPublishesAvailabilityAtomically() From 4e4aed1271f0c997eb470b88f144563fe41ac9cb Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 07:10:03 +0000 Subject: [PATCH 3/9] fix(packaging): point AppStream URLs at studio-berry/loop AppImage packaging failed because appstreamcli could not reach the legacy mberrys/Loop-pdf homepage, bugtracker, and help URLs (404). Update metainfo to the canonical studio-berry/loop repository. Co-authored-by: michael berry --- Desktop/io.github.mberrys.Loop-pdf.appdata.xml | 6 +++--- changes/cursor-fix-appstream-urls-8f37.md | 4 ++++ 2 files changed, 7 insertions(+), 3 deletions(-) create mode 100644 changes/cursor-fix-appstream-urls-8f37.md diff --git a/Desktop/io.github.mberrys.Loop-pdf.appdata.xml b/Desktop/io.github.mberrys.Loop-pdf.appdata.xml index f753da24..e8746b31 100644 --- a/Desktop/io.github.mberrys.Loop-pdf.appdata.xml +++ b/Desktop/io.github.mberrys.Loop-pdf.appdata.xml @@ -16,9 +16,9 @@ Utility Michael Berry - https://github.com/mberrys/Loop-pdf - https://github.com/mberrys/Loop-pdf/issues - https://github.com/mberrys/Loop-pdf + https://github.com/studio-berry/loop + https://github.com/studio-berry/loop/issues + https://github.com/studio-berry/loop io.github.mberrys.Loop-pdf.desktop diff --git a/changes/cursor-fix-appstream-urls-8f37.md b/changes/cursor-fix-appstream-urls-8f37.md new file mode 100644 index 00000000..be8a74b3 --- /dev/null +++ b/changes/cursor-fix-appstream-urls-8f37.md @@ -0,0 +1,4 @@ +Category: fixed +Audience: developers +Breaking-Change: no +Summary: Point AppStream metainfo URLs at the canonical studio-berry/loop repository so appimagetool AppStream validation passes during Linux packaging. From fbe7fbfd73fe5a473e9c79523d20c5c0d0774c45 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 07:30:33 +0000 Subject: [PATCH 4/9] fix(packaging): target Sentry debug upload at loupe-pdf project Windows MSI packaging failed because sentry-cli uploaded PDBs to berry-studios/loop-pdf, which does not exist on de.sentry.io. The org project slug is loupe-pdf (project id 4511866328449104, matching the compile-time DSN). Co-authored-by: michael berry --- .github/workflows/WindowsInstall.yml | 2 +- .github/workflows/reusable-windows.yml | 2 +- changes/cursor-fix-appstream-urls-8f37.md | 2 +- docs/CI.md | 2 +- docs/PRODUCTION_RUNBOOK.md | 2 +- scripts/ci/upload_sentry_debug_files.ps1 | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/WindowsInstall.yml b/.github/workflows/WindowsInstall.yml index 8e589fee..69a92e96 100644 --- a/.github/workflows/WindowsInstall.yml +++ b/.github/workflows/WindowsInstall.yml @@ -317,7 +317,7 @@ jobs: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SENTRY_ORG: berry-studios - SENTRY_PROJECT: loop-pdf + SENTRY_PROJECT: loupe-pdf SENTRY_URL: https://de.sentry.io run: | .\scripts\ci\upload_sentry_debug_files.ps1 -BuildDir "${env:GITHUB_WORKSPACE}\loop\build" diff --git a/.github/workflows/reusable-windows.yml b/.github/workflows/reusable-windows.yml index a15c3ac6..79aca4ee 100644 --- a/.github/workflows/reusable-windows.yml +++ b/.github/workflows/reusable-windows.yml @@ -406,7 +406,7 @@ jobs: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SENTRY_ORG: berry-studios - SENTRY_PROJECT: loop-pdf + SENTRY_PROJECT: loupe-pdf SENTRY_URL: https://de.sentry.io run: | .\scripts\ci\upload_sentry_debug_files.ps1 -BuildDir "${env:GITHUB_WORKSPACE}\loop\build" diff --git a/changes/cursor-fix-appstream-urls-8f37.md b/changes/cursor-fix-appstream-urls-8f37.md index be8a74b3..3996f58c 100644 --- a/changes/cursor-fix-appstream-urls-8f37.md +++ b/changes/cursor-fix-appstream-urls-8f37.md @@ -1,4 +1,4 @@ Category: fixed Audience: developers Breaking-Change: no -Summary: Point AppStream metainfo URLs at the canonical studio-berry/loop repository so appimagetool AppStream validation passes during Linux packaging. +Summary: Point AppStream metainfo URLs at studio-berry/loop for Linux AppImage packaging, and pin Windows Sentry debug-file upload to the existing berry-studios/loupe-pdf project so MSI packaging is not blocked by a missing project slug. diff --git a/docs/CI.md b/docs/CI.md index c4972b24..fde4ef17 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -111,7 +111,7 @@ the signing step refuses to run against an unpinned toolchain. Windows Release builds with `LOOP_ENABLE_SENTRY` emit PDBs (`/Zi` + `/DEBUG:FULL`) so crashpad minidumps can be symbolicated. After the Windows CI and MSI packaging jobs, `scripts/ci/upload_sentry_debug_files.ps1` -uploads Loop PDBs to `berry-studios/loop-pdf` on the EU region +uploads Loop PDBs to `berry-studios/loupe-pdf` on the EU region (`https://de.sentry.io`) using the pinned `sentryCli` binary. GitHub Actions cannot reference `secrets` in `if:` conditionals, so the workflow always runs the step; `upload_sentry_debug_files.ps1` no-ops when diff --git a/docs/PRODUCTION_RUNBOOK.md b/docs/PRODUCTION_RUNBOOK.md index 35b7c8b2..4ae4ecf8 100644 --- a/docs/PRODUCTION_RUNBOOK.md +++ b/docs/PRODUCTION_RUNBOOK.md @@ -111,7 +111,7 @@ Debug Files — they are not Issues or traces. **Privacy:** Desktop sentry-native 0.15.x does not send default PII (`send_default_pii` is NX-only in that pin). Crashes may still include OS-level paths and PDF bytes in minidumps — set `SENTRY_DSN=off` in high-classification environments. CI sets `SENTRY_DSN=off` so test runs do not flood the project. -**Debug files:** Windows CI uploads Loop PDBs to `berry-studios/loop-pdf` (EU) when `SENTRY_AUTH_TOKEN` is set. Without those files, crash stacks stay unsymbolicated. Store the token as a GitHub Actions secret with `project:releases` (or broader) scope; do not commit it. +**Debug files:** Windows CI uploads Loop PDBs to `berry-studios/loupe-pdf` (EU) when `SENTRY_AUTH_TOKEN` is set. Without those files, crash stacks stay unsymbolicated. Store the token as a GitHub Actions secret with `project:releases` (or broader) scope; do not commit it. **Verify (Windows, Sentry-enabled build):** diff --git a/scripts/ci/upload_sentry_debug_files.ps1 b/scripts/ci/upload_sentry_debug_files.ps1 index 6f4ae1c2..0bb36a24 100644 --- a/scripts/ci/upload_sentry_debug_files.ps1 +++ b/scripts/ci/upload_sentry_debug_files.ps1 @@ -34,7 +34,7 @@ if (-not $cli -or -not $cli.assetId -or -not $cli.sha256 -or -not $cli.upstream) } $org = if ($env:SENTRY_ORG) { $env:SENTRY_ORG } else { "berry-studios" } -$project = if ($env:SENTRY_PROJECT) { $env:SENTRY_PROJECT } else { "loop-pdf" } +$project = if ($env:SENTRY_PROJECT) { $env:SENTRY_PROJECT } else { "loupe-pdf" } $url = if ($env:SENTRY_URL) { $env:SENTRY_URL } else { "https://de.sentry.io" } $cliPath = Join-Path $env:RUNNER_TEMP "sentry-cli-Windows-x86_64.exe" From a78f7eccf88eb99acc57a309e4776ef6e1fac3d4 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 07:40:07 +0000 Subject: [PATCH 5/9] fix(packaging): use Sentry numeric project id to satisfy identity contract sentry-cli accepts project id 4511866328449104 (same as compile-time DSN) instead of the legacy slug, avoiding the banned product token in tracked source while still uploading to the correct berry-studios EU project. Co-authored-by: michael berry --- .github/workflows/WindowsInstall.yml | 2 +- .github/workflows/reusable-windows.yml | 2 +- changes/cursor-fix-appstream-urls-8f37.md | 2 +- docs/CI.md | 2 +- docs/PRODUCTION_RUNBOOK.md | 2 +- scripts/ci/upload_sentry_debug_files.ps1 | 2 +- 6 files changed, 6 insertions(+), 6 deletions(-) diff --git a/.github/workflows/WindowsInstall.yml b/.github/workflows/WindowsInstall.yml index 69a92e96..f435785a 100644 --- a/.github/workflows/WindowsInstall.yml +++ b/.github/workflows/WindowsInstall.yml @@ -317,7 +317,7 @@ jobs: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SENTRY_ORG: berry-studios - SENTRY_PROJECT: loupe-pdf + SENTRY_PROJECT: "4511866328449104" SENTRY_URL: https://de.sentry.io run: | .\scripts\ci\upload_sentry_debug_files.ps1 -BuildDir "${env:GITHUB_WORKSPACE}\loop\build" diff --git a/.github/workflows/reusable-windows.yml b/.github/workflows/reusable-windows.yml index 79aca4ee..f73d03ad 100644 --- a/.github/workflows/reusable-windows.yml +++ b/.github/workflows/reusable-windows.yml @@ -406,7 +406,7 @@ jobs: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SENTRY_ORG: berry-studios - SENTRY_PROJECT: loupe-pdf + SENTRY_PROJECT: "4511866328449104" SENTRY_URL: https://de.sentry.io run: | .\scripts\ci\upload_sentry_debug_files.ps1 -BuildDir "${env:GITHUB_WORKSPACE}\loop\build" diff --git a/changes/cursor-fix-appstream-urls-8f37.md b/changes/cursor-fix-appstream-urls-8f37.md index 3996f58c..cf87d4f7 100644 --- a/changes/cursor-fix-appstream-urls-8f37.md +++ b/changes/cursor-fix-appstream-urls-8f37.md @@ -1,4 +1,4 @@ Category: fixed Audience: developers Breaking-Change: no -Summary: Point AppStream metainfo URLs at studio-berry/loop for Linux AppImage packaging, and pin Windows Sentry debug-file upload to the existing berry-studios/loupe-pdf project so MSI packaging is not blocked by a missing project slug. +Summary: Point AppStream metainfo URLs at studio-berry/loop for Linux AppImage packaging, and pin Windows Sentry debug-file upload to berry-studios project 4511866328449104 (EU) so MSI packaging is not blocked by a missing project slug. diff --git a/docs/CI.md b/docs/CI.md index fde4ef17..b890cb2a 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -111,7 +111,7 @@ the signing step refuses to run against an unpinned toolchain. Windows Release builds with `LOOP_ENABLE_SENTRY` emit PDBs (`/Zi` + `/DEBUG:FULL`) so crashpad minidumps can be symbolicated. After the Windows CI and MSI packaging jobs, `scripts/ci/upload_sentry_debug_files.ps1` -uploads Loop PDBs to `berry-studios/loupe-pdf` on the EU region +uploads Loop PDBs to `berry-studios` project `4511866328449104` on the EU region (`https://de.sentry.io`) using the pinned `sentryCli` binary. GitHub Actions cannot reference `secrets` in `if:` conditionals, so the workflow always runs the step; `upload_sentry_debug_files.ps1` no-ops when diff --git a/docs/PRODUCTION_RUNBOOK.md b/docs/PRODUCTION_RUNBOOK.md index 4ae4ecf8..79c7b998 100644 --- a/docs/PRODUCTION_RUNBOOK.md +++ b/docs/PRODUCTION_RUNBOOK.md @@ -111,7 +111,7 @@ Debug Files — they are not Issues or traces. **Privacy:** Desktop sentry-native 0.15.x does not send default PII (`send_default_pii` is NX-only in that pin). Crashes may still include OS-level paths and PDF bytes in minidumps — set `SENTRY_DSN=off` in high-classification environments. CI sets `SENTRY_DSN=off` so test runs do not flood the project. -**Debug files:** Windows CI uploads Loop PDBs to `berry-studios/loupe-pdf` (EU) when `SENTRY_AUTH_TOKEN` is set. Without those files, crash stacks stay unsymbolicated. Store the token as a GitHub Actions secret with `project:releases` (or broader) scope; do not commit it. +**Debug files:** Windows CI uploads Loop PDBs to `berry-studios` project `4511866328449104` (EU) when `SENTRY_AUTH_TOKEN` is set. Without those files, crash stacks stay unsymbolicated. Store the token as a GitHub Actions secret with `project:releases` (or broader) scope; do not commit it. **Verify (Windows, Sentry-enabled build):** diff --git a/scripts/ci/upload_sentry_debug_files.ps1 b/scripts/ci/upload_sentry_debug_files.ps1 index 0bb36a24..9f311c9b 100644 --- a/scripts/ci/upload_sentry_debug_files.ps1 +++ b/scripts/ci/upload_sentry_debug_files.ps1 @@ -34,7 +34,7 @@ if (-not $cli -or -not $cli.assetId -or -not $cli.sha256 -or -not $cli.upstream) } $org = if ($env:SENTRY_ORG) { $env:SENTRY_ORG } else { "berry-studios" } -$project = if ($env:SENTRY_PROJECT) { $env:SENTRY_PROJECT } else { "loupe-pdf" } +$project = if ($env:SENTRY_PROJECT) { $env:SENTRY_PROJECT } else { "4511866328449104" } $url = if ($env:SENTRY_URL) { $env:SENTRY_URL } else { "https://de.sentry.io" } $cliPath = Join-Path $env:RUNNER_TEMP "sentry-cli-Windows-x86_64.exe" From 1a1f2f89e5173d5fc7654b618468d8a0e7a40468 Mon Sep 17 00:00:00 2001 From: Cursor Agent Date: Wed, 2 Sep 2026 07:42:41 +0000 Subject: [PATCH 6/9] fix(packaging): resolve Sentry project id from LOOP_SENTRY_DSN No Loop-named project slug exists on de.sentry.io; loop-pdf is absent and the legacy slug is policy-banned. Derive the numeric project id from the canonical LOOP_SENTRY_DSN in CMakeLists.txt instead of hardcoding slugs in workflows or tracked defaults. Co-authored-by: michael berry --- .github/workflows/WindowsInstall.yml | 1 - .github/workflows/reusable-windows.yml | 1 - changes/cursor-fix-appstream-urls-8f37.md | 2 +- docs/CI.md | 2 +- docs/PRODUCTION_RUNBOOK.md | 2 +- scripts/ci/upload_sentry_debug_files.ps1 | 21 ++++++++++++++++++++- 6 files changed, 23 insertions(+), 6 deletions(-) diff --git a/.github/workflows/WindowsInstall.yml b/.github/workflows/WindowsInstall.yml index f435785a..d4dfa288 100644 --- a/.github/workflows/WindowsInstall.yml +++ b/.github/workflows/WindowsInstall.yml @@ -317,7 +317,6 @@ jobs: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SENTRY_ORG: berry-studios - SENTRY_PROJECT: "4511866328449104" SENTRY_URL: https://de.sentry.io run: | .\scripts\ci\upload_sentry_debug_files.ps1 -BuildDir "${env:GITHUB_WORKSPACE}\loop\build" diff --git a/.github/workflows/reusable-windows.yml b/.github/workflows/reusable-windows.yml index f73d03ad..ed28a289 100644 --- a/.github/workflows/reusable-windows.yml +++ b/.github/workflows/reusable-windows.yml @@ -406,7 +406,6 @@ jobs: SENTRY_AUTH_TOKEN: ${{ secrets.SENTRY_AUTH_TOKEN }} GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} SENTRY_ORG: berry-studios - SENTRY_PROJECT: "4511866328449104" SENTRY_URL: https://de.sentry.io run: | .\scripts\ci\upload_sentry_debug_files.ps1 -BuildDir "${env:GITHUB_WORKSPACE}\loop\build" diff --git a/changes/cursor-fix-appstream-urls-8f37.md b/changes/cursor-fix-appstream-urls-8f37.md index cf87d4f7..6580e274 100644 --- a/changes/cursor-fix-appstream-urls-8f37.md +++ b/changes/cursor-fix-appstream-urls-8f37.md @@ -1,4 +1,4 @@ Category: fixed Audience: developers Breaking-Change: no -Summary: Point AppStream metainfo URLs at studio-berry/loop for Linux AppImage packaging, and pin Windows Sentry debug-file upload to berry-studios project 4511866328449104 (EU) so MSI packaging is not blocked by a missing project slug. +Summary: Point AppStream metainfo URLs at studio-berry/loop for Linux AppImage packaging, and resolve Windows Sentry debug-file upload from LOOP_SENTRY_DSN in CMakeLists.txt so MSI packaging is not blocked by a stale project slug. diff --git a/docs/CI.md b/docs/CI.md index b890cb2a..8da773f9 100644 --- a/docs/CI.md +++ b/docs/CI.md @@ -111,7 +111,7 @@ the signing step refuses to run against an unpinned toolchain. Windows Release builds with `LOOP_ENABLE_SENTRY` emit PDBs (`/Zi` + `/DEBUG:FULL`) so crashpad minidumps can be symbolicated. After the Windows CI and MSI packaging jobs, `scripts/ci/upload_sentry_debug_files.ps1` -uploads Loop PDBs to `berry-studios` project `4511866328449104` on the EU region +uploads Loop PDBs to the `berry-studios` EU project id encoded in `LOOP_SENTRY_DSN` (`https://de.sentry.io`) using the pinned `sentryCli` binary. GitHub Actions cannot reference `secrets` in `if:` conditionals, so the workflow always runs the step; `upload_sentry_debug_files.ps1` no-ops when diff --git a/docs/PRODUCTION_RUNBOOK.md b/docs/PRODUCTION_RUNBOOK.md index 79c7b998..7cc4d52a 100644 --- a/docs/PRODUCTION_RUNBOOK.md +++ b/docs/PRODUCTION_RUNBOOK.md @@ -111,7 +111,7 @@ Debug Files — they are not Issues or traces. **Privacy:** Desktop sentry-native 0.15.x does not send default PII (`send_default_pii` is NX-only in that pin). Crashes may still include OS-level paths and PDF bytes in minidumps — set `SENTRY_DSN=off` in high-classification environments. CI sets `SENTRY_DSN=off` so test runs do not flood the project. -**Debug files:** Windows CI uploads Loop PDBs to `berry-studios` project `4511866328449104` (EU) when `SENTRY_AUTH_TOKEN` is set. Without those files, crash stacks stay unsymbolicated. Store the token as a GitHub Actions secret with `project:releases` (or broader) scope; do not commit it. +**Debug files:** Windows CI uploads Loop PDBs to the `berry-studios` EU project id encoded in `LOOP_SENTRY_DSN` when `SENTRY_AUTH_TOKEN` is set. Without those files, crash stacks stay unsymbolicated. Store the token as a GitHub Actions secret with `project:releases` (or broader) scope; do not commit it. **Verify (Windows, Sentry-enabled build):** diff --git a/scripts/ci/upload_sentry_debug_files.ps1 b/scripts/ci/upload_sentry_debug_files.ps1 index 9f311c9b..bff0e017 100644 --- a/scripts/ci/upload_sentry_debug_files.ps1 +++ b/scripts/ci/upload_sentry_debug_files.ps1 @@ -34,9 +34,28 @@ if (-not $cli -or -not $cli.assetId -or -not $cli.sha256 -or -not $cli.upstream) } $org = if ($env:SENTRY_ORG) { $env:SENTRY_ORG } else { "berry-studios" } -$project = if ($env:SENTRY_PROJECT) { $env:SENTRY_PROJECT } else { "4511866328449104" } $url = if ($env:SENTRY_URL) { $env:SENTRY_URL } else { "https://de.sentry.io" } +function Resolve-SentryProject([string]$Root) { + if ($env:SENTRY_PROJECT) { + return [string]$env:SENTRY_PROJECT + } + $cmakePath = Join-Path $Root "CMakeLists.txt" + if (-not (Test-Path -LiteralPath $cmakePath)) { + throw "upload_sentry_debug_files.ps1: CMakeLists.txt not found; cannot resolve Sentry project id." + } + $dsnMatch = [regex]::Match( + (Get-Content -LiteralPath $cmakePath -Raw), + 'ingest\.de\.sentry\.io/(\d+)' + ) + if (-not $dsnMatch.Success) { + throw "upload_sentry_debug_files.ps1: LOOP_SENTRY_DSN in CMakeLists.txt has no ingest project id." + } + return $dsnMatch.Groups[1].Value +} + +$project = Resolve-SentryProject $repoRoot + $cliPath = Join-Path $env:RUNNER_TEMP "sentry-cli-Windows-x86_64.exe" if ([string]::IsNullOrWhiteSpace($env:RUNNER_TEMP)) { $cliPath = Join-Path ([System.IO.Path]::GetTempPath()) "sentry-cli-Windows-x86_64.exe" From 542b3c08385b4c6c5eca10241c55deceb8a4c8c7 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 09:28:36 +0000 Subject: [PATCH 7/9] Wire transparency flattening into standards-convert (#167) PDF/X-1a:2001 and PDF/X-3:2002 forbid live transparency, but standards-convert never called the existing PDFTransparencyFlattener operation (#164) -- live transparency was an unconditional, unfixable blocker even though Core already has a working flatten path that PageMaster's export pipeline uses as a separate stage. Wire PDFTransparencyFlattener::apply()/hasLiveTransparency() into PDFStandardConversion::preview()/apply(), mirroring the existing RGB-to-CMYK integration exactly: - New PDFStandardConversionSettings::flattenTransparency (default-on for X-1a/X-3, matching normalizeColor's existing default pattern; opt-in for X-4/PDF-A, which permit live transparency). - pdfx.transparency.allowed becomes a fixable preflight blocker only when flattening is requested, so unrelated fixtures without live transparency are unaffected (transparencyObjects stays 0, the rule already reports Passed). - The flatten runs before the output-intent/page-box rewrite and its report is surfaced verbatim under a new transparency_flatten report field -- a real, reported content change, never a silent approximation. - New flatten_transparency parameter on the standards-convert operation, available identically from PdfTool's repair command and PageMaster's export job (the one shared Core implementation). Also correct docs/STANDARD_CONVERSION.md's stale claim that an Editor adapter can land "after the 0.1.1 GUI gate": that gate is already complete per docs/LOOP_SHELL_CONTRACT.md, which gates product GUI work behind the still-closed S21/S22 admission contracts instead. Note that docs/REPO_MAP.md's LoopEditorPlugins/ module does not exist in the current Qt-Quick-based tree, so a future Editor adapter belongs under LoopLibInteraction/ + LoopEditor/qml/. No Qt/CMake toolchain is available in this environment, so the build and UnitTestsStandardOracle/UnitTestsConversionOracle/UnitTestsRepairOperation targets could not be run locally; clang-format, source-integrity, and architecture-catalog checks all pass. CI will provide the first real build/test signal for this change. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01JgoNmy614kqRiTBjiSjRJU --- LoopLibCore/sources/pdfrepairprimitives.cpp | 7 +++- LoopLibCore/sources/pdfstandardconversion.cpp | 35 +++++++++++++++++-- LoopLibCore/sources/pdfstandardconversion.h | 6 +++- changes/cc-pensive-cerf-kkvr3f.md | 20 +++++++++++ docs/REPAIR_OPERATIONS.md | 7 ++-- docs/STANDARD_CONVERSION.md | 27 ++++++++++---- 6 files changed, 89 insertions(+), 13 deletions(-) create mode 100644 changes/cc-pensive-cerf-kkvr3f.md diff --git a/LoopLibCore/sources/pdfrepairprimitives.cpp b/LoopLibCore/sources/pdfrepairprimitives.cpp index a6e0bea3..6e2a6810 100644 --- a/LoopLibCore/sources/pdfrepairprimitives.cpp +++ b/LoopLibCore/sources/pdfrepairprimitives.cpp @@ -465,6 +465,9 @@ PDFStandardConversionSettings standardConversionSettings(const QJsonObject& para ? parameters.value(QStringLiteral("normalize_color")).toBool() : (settings.target == PDFStandardTarget::PDFX1a2001 || settings.target == PDFStandardTarget::PDFX3_2002); settings.blackPointCompensation = parameters.value(QStringLiteral("black_point_compensation")).toBool(true); + settings.flattenTransparency = parameters.contains(QStringLiteral("flatten_transparency")) + ? parameters.value(QStringLiteral("flatten_transparency")).toBool() + : (settings.target == PDFStandardTarget::PDFX1a2001 || settings.target == PDFStandardTarget::PDFX3_2002); settings.independentValidatorProgram = parameters.value(QStringLiteral("validator_program")).toString(); const QJsonValue validatorArguments = parameters.value(QStringLiteral("validator_arguments")); if (validatorArguments.isArray()) @@ -496,6 +499,7 @@ QJsonObject standardConversionParameterSchema() { QStringLiteral("target_profile_name"), QJsonObject{ { QStringLiteral("type"), QStringLiteral("string") } } }, { QStringLiteral("normalize_color"), QJsonObject{ { QStringLiteral("type"), QStringLiteral("boolean") } } }, { QStringLiteral("black_point_compensation"), QJsonObject{ { QStringLiteral("type"), QStringLiteral("boolean") } } }, + { QStringLiteral("flatten_transparency"), QJsonObject{ { QStringLiteral("type"), QStringLiteral("boolean") } } }, { QStringLiteral("validator_program"), QJsonObject{ { QStringLiteral("type"), QStringLiteral("string") } } }, { QStringLiteral("validator_arguments"), QJsonObject{ { QStringLiteral("oneOf"), QJsonArray{ QJsonObject{ { QStringLiteral("type"), QStringLiteral("string") } }, QJsonObject{ { QStringLiteral("type"), QStringLiteral("array") }, { QStringLiteral("items"), QJsonObject{ { QStringLiteral("type"), QStringLiteral("string") } } } } } } } }, { QStringLiteral("validator_timeout_ms"), QJsonObject{ { QStringLiteral("type"), QStringLiteral("integer") }, { QStringLiteral("minimum"), 1000 }, { QStringLiteral("maximum"), 3600000 } } }, @@ -547,7 +551,8 @@ class PDFStandardConversionRepair final : public PDFRepairOperation plan->expectedChanges.outputIntent = true; plan->expectedChanges.pageBoxes = true; plan->expectedChanges.colorSpaces = settings.normalizeColor; - plan->expectedChanges.pageContent = settings.normalizeColor; + plan->expectedChanges.pageContent = settings.normalizeColor || settings.flattenTransparency; + plan->expectedChanges.images = settings.flattenTransparency; plan->validators = { PDFRepairValidatorKind::StructuralIntegrity, PDFRepairValidatorKind::OutputIntent, PDFRepairValidatorKind::NormalPreflight, diff --git a/LoopLibCore/sources/pdfstandardconversion.cpp b/LoopLibCore/sources/pdfstandardconversion.cpp index 72fff448..c7911f86 100644 --- a/LoopLibCore/sources/pdfstandardconversion.cpp +++ b/LoopLibCore/sources/pdfstandardconversion.cpp @@ -26,6 +26,7 @@ #include "pdfdocumentwriter.h" #include "pdfstreamfilters.h" #include "pdfrgbtocmykfixup.h" +#include "pdftransparencyflattener.h" #include "preflightengine.h" #include "pdfutils.h" #include "pdfworkloadenvelope.h" @@ -59,6 +60,13 @@ bool normalizesColorByDefault(PDFStandardTarget target) return target == PDFStandardTarget::PDFX1a2001 || target == PDFStandardTarget::PDFX3_2002; } +// PDF/X-1a and PDF/X-3 prohibit live transparency (see docs/PDFX_POLICY_MATRIX.md); +// PDF/X-4 and PDF/A-2b permit it, so flattening is opt-in there. +bool flattensTransparencyByDefault(PDFStandardTarget target) +{ + return target == PDFStandardTarget::PDFX1a2001 || target == PDFStandardTarget::PDFX3_2002; +} + QByteArray targetMarker(PDFStandardTarget target) { return pdfStandardTargetToString(target).toUtf8(); @@ -217,13 +225,14 @@ void collectPreflightBlockers(const PDFStandardConversionSettings& settings, } const bool normalizeColor = settings.normalizeColor || normalizesColorByDefault(settings.target); + const bool flattenTransparency = settings.flattenTransparency || flattensTransparencyByDefault(settings.target); for (const PDFXRuleResult& rule : result.pdfx->rules) { if (rule.state != PDFXRuleState::Failed && rule.state != PDFXRuleState::NotInspected) { continue; } - const bool fixable = rule.ruleId == QStringLiteral("pdfx.metadata.identification") || rule.ruleId == QStringLiteral("pdfx.output-intent.present") || rule.ruleId == QStringLiteral("pdfx.output-intent.identity") || rule.ruleId == QStringLiteral("pdfx.output-intent.subtype") || rule.ruleId == QStringLiteral("pdfx.output-intent.profile") || rule.ruleId == QStringLiteral("pdfx.output-intent.profile-space") || rule.ruleId == QStringLiteral("pdfx.page.trim-box") || rule.ruleId == QStringLiteral("pdfx.page.bleed-box") || rule.ruleId == QStringLiteral("pdfx.document.version") || (rule.ruleId == QStringLiteral("pdfx.color.device-rgb") && normalizeColor); + const bool fixable = rule.ruleId == QStringLiteral("pdfx.metadata.identification") || rule.ruleId == QStringLiteral("pdfx.output-intent.present") || rule.ruleId == QStringLiteral("pdfx.output-intent.identity") || rule.ruleId == QStringLiteral("pdfx.output-intent.subtype") || rule.ruleId == QStringLiteral("pdfx.output-intent.profile") || rule.ruleId == QStringLiteral("pdfx.output-intent.profile-space") || rule.ruleId == QStringLiteral("pdfx.page.trim-box") || rule.ruleId == QStringLiteral("pdfx.page.bleed-box") || rule.ruleId == QStringLiteral("pdfx.document.version") || (rule.ruleId == QStringLiteral("pdfx.color.device-rgb") && normalizeColor) || (rule.ruleId == QStringLiteral("pdfx.transparency.allowed") && flattenTransparency); if (!fixable) { report->blockers.append(rule.ruleId + QStringLiteral(": ") + rule.diagnostic); @@ -402,7 +411,8 @@ QJsonObject PDFStandardConversionReport::toJson() const { QStringLiteral("changes"), changesArray }, { QStringLiteral("blockers"), QJsonArray::fromStringList(blockers) }, { QStringLiteral("warnings"), QJsonArray::fromStringList(warnings) }, - { QStringLiteral("validator"), validator } + { QStringLiteral("validator"), validator }, + { QStringLiteral("transparency_flatten"), transparencyFlatten } }; } @@ -419,6 +429,7 @@ PDFOperationResult PDFStandardConversion::preview(const PDFDocument* document, report->blockers.clear(); report->warnings.clear(); report->preflightBefore = QJsonObject(); + report->transparencyFlatten = QJsonObject(); const PDFOperationResult profileResult = validateIcc(settings); if (!profileResult) @@ -453,6 +464,12 @@ PDFOperationResult PDFStandardConversion::preview(const PDFDocument* document, } } + const bool flattenTransparency = settings.flattenTransparency || flattensTransparencyByDefault(settings.target); + if (flattenTransparency && PDFTransparencyFlattener::hasLiveTransparency(document)) + { + report->changes.append({ QStringLiteral("transparency.flatten"), QStringLiteral("live transparency"), QStringLiteral("flattened to opaque raster content") }); + } + if (isPDFX(settings.target)) { PDFDocument copy = *document; @@ -501,6 +518,20 @@ PDFOperationResult PDFStandardConversion::apply(PDFDocument* document, } } + const bool flattenTransparency = settings.flattenTransparency || flattensTransparencyByDefault(settings.target); + if (flattenTransparency) + { + PDFTransparencyFlattenSettings transparencySettings = settings.transparencyFlattenSettings; + transparencySettings.analyzeOnly = false; + PDFTransparencyFlattenReport transparencyReport; + const PDFOperationResult transparencyResult = PDFTransparencyFlattener::apply(&candidate, transparencySettings, &transparencyReport); + report->transparencyFlatten = transparencyReport.toJson(); + if (!transparencyResult) + { + return transparencyResult; + } + } + PDFDocumentBuilder builder(&candidate); const PDFVersion version = minimumVersion(settings.target); addVersion(&builder, version); diff --git a/LoopLibCore/sources/pdfstandardconversion.h b/LoopLibCore/sources/pdfstandardconversion.h index 2946007d..38cfb58a 100644 --- a/LoopLibCore/sources/pdfstandardconversion.h +++ b/LoopLibCore/sources/pdfstandardconversion.h @@ -25,6 +25,7 @@ #include "pdfdocument.h" #include "pdfglobal.h" +#include "pdftransparencyflattener.h" #include "pdfutils.h" // PDFOperationResult, returned by preview()/apply() below #include @@ -45,7 +46,7 @@ enum class PDFStandardTarget LOOPLIBCORESHARED_EXPORT QString pdfStandardTargetToString(PDFStandardTarget target); LOOPLIBCORESHARED_EXPORT bool pdfStandardTargetFromString(const QString& value, - PDFStandardTarget* target); + PDFStandardTarget* target); LOOPLIBCORESHARED_EXPORT QStringList supportedPDFStandardTargets(); struct LOOPLIBCORESHARED_EXPORT PDFStandardConversionSettings @@ -56,6 +57,8 @@ struct LOOPLIBCORESHARED_EXPORT PDFStandardConversionSettings QString outputIntentName; bool normalizeColor = false; bool blackPointCompensation = true; + bool flattenTransparency = false; + PDFTransparencyFlattenSettings transparencyFlattenSettings; QString independentValidatorProgram; QStringList independentValidatorArguments; int independentValidatorTimeoutMs = 120000; @@ -83,6 +86,7 @@ struct LOOPLIBCORESHARED_EXPORT PDFStandardConversionReport QStringList blockers; QStringList warnings; QJsonObject validator; + QJsonObject transparencyFlatten; QJsonObject toJson() const; }; diff --git a/changes/cc-pensive-cerf-kkvr3f.md b/changes/cc-pensive-cerf-kkvr3f.md new file mode 100644 index 00000000..6b30150d --- /dev/null +++ b/changes/cc-pensive-cerf-kkvr3f.md @@ -0,0 +1,20 @@ +Category: added +Audience: developers, print-production operators +Breaking-Change: no +Summary: Wire the existing PDFTransparencyFlattener operation (issue #164) into +the standards-convert Core operation (issue #167) so PDF/X-1a:2001 and +PDF/X-3:2002 conversion no longer treats live transparency as an unconditional, +unfixable blocker. Flattening runs by default for those two targets (which +prohibit live transparency) before the output-intent and page-box rewrite, is +reported as a real content change under a new transparency_flatten report +field (never a silent approximation), and remains skippable/enable-able via a +new flatten_transparency parameter surfaced identically through PdfTool's +repair command and PageMaster's export job (the one shared implementation). +PDF/X-4 and PDF/A-2b, which permit live transparency, do not flatten by +default. Also correct docs/STANDARD_CONVERSION.md's stale claim that an Editor +adapter can land "after the 0.1.1 GUI gate" — that gate is already complete +per docs/LOOP_SHELL_CONTRACT.md; Editor integration actually remains deferred +behind the still-closed S21/S22 product-GUI admission contracts, and +docs/REPO_MAP.md's LoopEditorPlugins/ module does not exist in the current +Qt-Quick-based tree, so a future Editor adapter belongs under +LoopLibInteraction/ + LoopEditor/qml/ instead. diff --git a/docs/REPAIR_OPERATIONS.md b/docs/REPAIR_OPERATIONS.md index 71ee95fa..389cad35 100644 --- a/docs/REPAIR_OPERATIONS.md +++ b/docs/REPAIR_OPERATIONS.md @@ -39,9 +39,10 @@ The first adapters use the existing bounded Core fixups: `PDF/X-1a:2001`, `PDF/X-3:2002`, `PDF/X-4`, and `PDF/A-2b`. It produces a pre-conversion change report and refuses to commit unless an explicitly configured independent validator accepts the candidate. Validator arguments - must include `{input}`. Transparency flattening, font embedding, and - forbidden-action removal are not silently approximated; unsupported cases - fail closed. + must include `{input}`. Transparency flattening (via the shared + `PDFTransparencyFlattener` operation, default-on for X-1a/X-3) is a reported + content change, not a silent approximation; font embedding and + forbidden-action removal remain unsupported and fail closed. The preflight capability list is derived from the same registry: an operation is advertised only when its descriptor marks it as a preflight fixup. Profile diff --git a/docs/STANDARD_CONVERSION.md b/docs/STANDARD_CONVERSION.md index 23f1bbed..4d3c965d 100644 --- a/docs/STANDARD_CONVERSION.md +++ b/docs/STANDARD_CONVERSION.md @@ -2,18 +2,33 @@ Loop exposes standard conversion as the Core operation `standards-convert`. PdfTool's `repair` command and PageMaster's headless export job call this same -operation; an Editor adapter can be added after the 0.1.1 GUI gate without -creating a second conversion implementation. +operation. An Editor adapter is deferred until Loop's product GUI work clears +the S21 canvas / S22 Quick admission contracts described in +[`LOOP_SHELL_CONTRACT.md`](LOOP_SHELL_CONTRACT.md) — the 0.1.1 release gate +itself is already complete, so that document, not this one, is authoritative +on timing. No second conversion implementation is planned; PdfTool and +PageMaster already share the one Core implementation. Supported targets are explicit: `PDF/X-1a:2001`, `PDF/X-3:2002`, `PDF/X-4`, and `PDF/A-2b`. The selected target is recorded in the operation plan and report. The report lists metadata, PDF version, output-intent, page-box, and optional -color-normalization changes before mutation. +color-normalization and transparency-flattening changes before mutation. Conversion is fail-closed. A CMYK ICC profile is required for PDF/X-1a and -PDF/X-3 normalization. Loop does not claim that transparency was flattened, -fonts embedded, actions removed, or other unsupported constructs repaired when -the Core implementation cannot do so. Those findings remain blockers. +PDF/X-3 normalization. Loop does not claim that fonts were embedded, actions +removed, or other unsupported constructs repaired when the Core implementation +cannot do so. Those findings remain blockers. + +PDF/X-1a:2001 and PDF/X-3:2002 forbid live transparency. `standards-convert` +runs the shared `PDFTransparencyFlattener` operation (issue #164) against +those two targets by default before the output-intent and page-box rewrite, +so `pdfx.transparency.allowed` stops being an unconditional blocker; set the +`flatten_transparency` parameter explicitly to override the default (`false` +opts out for X-1a/X-3, `true` opts in for X-4, which otherwise permits live +transparency). Flattening rasterizes affected page content — it is a real +content change, reported under `transparency_flatten` in the conversion +report, not a silent approximation. PDF/X-4 and PDF/A-2b do not flatten by +default. Every non-dry-run conversion requires an independent validator command. The validator receives a temporary candidate through the `{input}` argument From c3d610d20a88f2458ff576ef083aa166e47dbd18 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 09:32:54 +0000 Subject: [PATCH 8/9] ci: fix base-branch legacy-token and working-directory regressions CI on #524 failed with three unrelated pre-existing dev-branch bugs (reproduced identically on origin/dev's own tip, confirmed by diffing these exact files against origin/dev before this commit): - .github/workflows/reusable-linux.yml and reusable-windows.yml's "Verify processing-budget exhaustion corpus" step used working-directory: loupe, a pre-rebrand path that doesn't exist (the checkout path is "loop") -- this is what broke agent-fast/build. - UnitTests/testdata/budget-exhaustion/manifest.json, UnitTests/tst_budgetexhaustiontest.cpp, and scripts/budget_exhaustion/generate_corpus.py still used the legacy "loupe-processing-budget-exhaustion-corpus" schema_kind token, which scripts/ci/check_loop_identity.py's fail-closed legacy-token scan correctly flags -- this is what broke source_integrity and policy. Ported the same fix already sitting in open PR #518 (which bundles it with unrelated 0.2.1 milestone doc changes not relevant here) rather than duplicating the diagnosis. Merged origin/dev first since this topic branch was 14 commits behind it and didn't have these files locally yet. Verified locally (all pass): scripts/budget_exhaustion/generate_corpus.py --check, scripts/ci/test_check_loop_identity.py, the full scripts/ci/test_*.py suite (223 tests), check_source_integrity.py, check_supply_chain_pins.py, verify-loop-shell-contract.py, generate-architecture-catalogs.py --check, clang-format. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01JgoNmy614kqRiTBjiSjRJU --- .github/workflows/reusable-linux.yml | 2 +- .github/workflows/reusable-windows.yml | 2 +- UnitTests/testdata/budget-exhaustion/manifest.json | 2 +- UnitTests/tst_budgetexhaustiontest.cpp | 2 +- scripts/budget_exhaustion/generate_corpus.py | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/.github/workflows/reusable-linux.yml b/.github/workflows/reusable-linux.yml index 9fc119e7..caedfb3a 100644 --- a/.github/workflows/reusable-linux.yml +++ b/.github/workflows/reusable-linux.yml @@ -76,7 +76,7 @@ jobs: working-directory: loop run: python3 scripts/ci/check_version_policy.py - name: Verify processing-budget exhaustion corpus - working-directory: loupe + working-directory: loop run: python3 scripts/budget_exhaustion/generate_corpus.py --check - name: Prepare vcpkg directories diff --git a/.github/workflows/reusable-windows.yml b/.github/workflows/reusable-windows.yml index 43f174de..d45552f4 100644 --- a/.github/workflows/reusable-windows.yml +++ b/.github/workflows/reusable-windows.yml @@ -89,7 +89,7 @@ jobs: --page-count 256 --operations 256 --family pathological-vector - name: Verify processing-budget exhaustion corpus - working-directory: loupe + working-directory: loop shell: pwsh run: python scripts\budget_exhaustion\generate_corpus.py --check diff --git a/UnitTests/testdata/budget-exhaustion/manifest.json b/UnitTests/testdata/budget-exhaustion/manifest.json index 12642000..80f90486 100644 --- a/UnitTests/testdata/budget-exhaustion/manifest.json +++ b/UnitTests/testdata/budget-exhaustion/manifest.json @@ -187,6 +187,6 @@ } ], "generated_by": "scripts/budget_exhaustion/generate_corpus.py", - "schema_kind": "loupe-processing-budget-exhaustion-corpus", + "schema_kind": "loop-processing-budget-exhaustion-corpus", "schema_version": 2 } diff --git a/UnitTests/tst_budgetexhaustiontest.cpp b/UnitTests/tst_budgetexhaustiontest.cpp index b7334d86..04ea24f1 100644 --- a/UnitTests/tst_budgetexhaustiontest.cpp +++ b/UnitTests/tst_budgetexhaustiontest.cpp @@ -130,7 +130,7 @@ QList loadCorpus() } const QJsonObject root = document.object(); - if (root.value(QStringLiteral("schema_kind")).toString() != QLatin1String("loupe-processing-budget-exhaustion-corpus") || root.value(QStringLiteral("schema_version")).toInt() != 2) + if (root.value(QStringLiteral("schema_kind")).toString() != QLatin1String("loop-processing-budget-exhaustion-corpus") || root.value(QStringLiteral("schema_version")).toInt() != 2) { qFatal("Unexpected generated budget exhaustion corpus schema"); } diff --git a/scripts/budget_exhaustion/generate_corpus.py b/scripts/budget_exhaustion/generate_corpus.py index 3ad577a6..03cfc314 100644 --- a/scripts/budget_exhaustion/generate_corpus.py +++ b/scripts/budget_exhaustion/generate_corpus.py @@ -11,7 +11,7 @@ DEFAULT_OUTPUT = Path(__file__).resolve().parents[2] / "UnitTests" / "testdata" / "budget-exhaustion" -SCHEMA_KIND = "loupe-processing-budget-exhaustion-corpus" +SCHEMA_KIND = "loop-processing-budget-exhaustion-corpus" SCHEMA_VERSION = 2 From dcebaed5cc835fe96fad066d4258a1cba38c8122 Mon Sep 17 00:00:00 2001 From: Claude Date: Wed, 2 Sep 2026 10:05:22 +0000 Subject: [PATCH 9/9] style: fix pre-existing clang-format violation in pdftextlayoutgenerator.h Unrelated to this PR's diff (introduced by commit 6c628b4, already on this topic branch before this PR's work began) but blocking agent-fast/ build's format:LoopLibCore/sources/pdftextlayoutgenerator.h check. Single blank line removed inside the constructor body; no behavior change. Confirmed via the same CI run that build:LoopLibCore, build:UnitTestsStandardOracle, build:UnitTestsConversionOracle, and focused_tests all pass with this PR's actual code changes. Co-Authored-By: Claude Sonnet 5 Claude-Session: https://claude.ai/code/session_01JgoNmy614kqRiTBjiSjRJU --- LoopLibCore/sources/pdftextlayoutgenerator.h | 1 - 1 file changed, 1 deletion(-) diff --git a/LoopLibCore/sources/pdftextlayoutgenerator.h b/LoopLibCore/sources/pdftextlayoutgenerator.h index a1fbe41c..3bdda43e 100644 --- a/LoopLibCore/sources/pdftextlayoutgenerator.h +++ b/LoopLibCore/sources/pdftextlayoutgenerator.h @@ -42,7 +42,6 @@ class LOOPLIBCORESHARED_EXPORT PDFTextLayoutGenerator : public PDFPageContentPro BaseClass(page, document, fontCache, cms, optionalContentActivity, pagePointToDevicePointMatrix, meshQualitySettings, processingBudget), m_features(features) { - } /// Creates text layout from the text