Conversation
Adds pdfquick::tokens (LoopLibQuick/sources/looptokens.h/.cpp): semantic spacing and colour-role tokens with dark/light/high-contrast values, each foreground/background pair WCAG contrast-checked. Adds resolveStateVisual() (loopstatevisual.h/.cpp), the canonical finding/check presentation mapping issue #194 asks for, with a table-driven test (UnitTestsLoopStateVisual) asserting an incomplete check and an actively-waived finding never resolve to the passed treatment. Documents both in docs/LOOP_DESIGN_SYSTEM.md, including why this uses the repo's current Loop/Quick naming rather than the issue's stale pre-rebrand Pdf4QtLibGui/Loupe paths, and what remains open (component implementations land with their consuming surfaces #193, Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxoABBuEB3mYqr8QyA6KuB
scripts/ci/check_loop_identity.py fails CI on any tracked text file containing the pre-rebrand product name outside its historical-evidence allowlist. The "Naming note" section quoted that name three times while explaining why this doc uses current naming instead of issue #194's stale paths; reword it to make the same point without the literal token. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxoABBuEB3mYqr8QyA6KuB
Adding UnitTestsLoopStateVisual to UnitTests/CMakeLists.txt bumped the project-wide CMake target count the Phase 5 Widgets inventory tracks (its generator enumerates every target to prove widgets-linkage status, not just widgets-related ones). Regenerate docs/generated/phase5-widgets-inventory.json via scripts/generate_phase5_widgets_evidence.py --write (new target's widgets_linkage is "none", as expected) and update the test's hardcoded target count (70 -> 71) to match. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxoABBuEB3mYqr8QyA6KuB
agent-fast's clang-tidy step runs on every changed C++ file regardless of module classification, but the cmake --build step that generates each test's AUTOMOC .moc file only runs for targets selected by classify()/selected_values() against agent-policy.json's module_boundaries paths. UnitTestsBudgetExhaustion was already listed in core's `tests`, but its source file, UnitTests/tst_budgetexhaustiontest.cpp, was missing from core's `paths` glob list, so a change touching only that file never classified as "core" and the target was never built before clang-tidy ran on it standalone -- producing "tst_budgetexhaustiontest.moc file not found". Surfaced by 0aa0d4c's one-line fix to that file. Add the missing path entry so the target builds first, as it does for every other core test file already listed there. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01NxoABBuEB3mYqr8QyA6KuB
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 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JgoNmy614kqRiTBjiSjRJU
Works the net-new findings from the September 2026 read-only review that were reproducible against the current tree, plus three adjacent defects the review did not name. Security / privacy - PDFLogScrubber scrubs credential material (URL userinfo such as a Sentry DSN, HTTP authorization values, secret-named key/value pairs) ahead of the existing path and email passes. The bare auth-scheme pass deliberately excludes "Token" so parser diagnostics are not redacted as secrets. - loop-ocr reads a staged raster once by descriptor instead of re-resolving the path for isfile(), PIL, and easyocr in turn, closing the TOCTOU window; language codes are shape-validated before they reach easyocr's model file names; PdfTool stages the raster 0600. - Diagnostics bundles truncate plugin display fields. Fail-closed behaviour - PdfTool extraction commands record output.empty-result and accept a shared --fail-if-empty (exit 1, findings) so an empty output directory cannot pass a pipeline that gates on produced files. - writeIncremental reports whether it appended or only byte-copied; damaged documents now carry a source digest, so its "file changed underneath us" guard is no longer silently disabled for permissively recovered documents. Bounds on attacker-controlled shapes - Damaged-document recovery bounds its dense object table by objects recovered, not by the highest declared object number. - PDFNameTreeLoader terminates cyclic Kids chains and caps depth, entry count, and key length (net-new: the cycle was unbounded recursion). - Structure-tree parsing bounds recursion depth on long acyclic chains. - PDFJBIG2Bitmap::paint validates grown dimensions on its expandY path, the one path that escaped the constructor's dimension check (net-new). Ergonomics - isPathContained no longer rejects a planned output whose target directory does not exist yet, keeping the stricter symlinked-parent rule for the file side. - makeUniqueFileName probes 128 sequential names, then random ones. - OCR option defaults are defined once and shared by capability discovery and the command-line parser. Not built or run here: this environment has no Qt, so the C++ changes are unverified by compilation. The Python sidecar tests pass and the new scrubber patterns were validated against a reference implementation of the same passes. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01FDJQDFR5ctoKDJbp7LG4GS
Whole-file clang-format across LoopLibCore/PdfTool/UnitTests sources to clear format drift flagged by agent-fast. Add moc-generated autogen include dirs to UnitTests target so Qt meta-object headers resolve. Map agent-policy tidy gates to the three active test suites.
Add canonical milestone text for 0.2.1 (patch on the 0.2.0 line), register GitHub milestone 17 in the sync manifest, and update living-sequence references across milestone docs and the 0.5.0–0.10.0 roadmap extension. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Replace legacy product tokens in milestone issue summaries, rename the budget-exhaustion corpus schema kind to loop, and fix workflow working-directory typos left from the Loupe-to-Loop rebrand. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Manual .moc includes are generated at build time, so clang-tidy cannot analyze those sources during agent-fast. Exclude them from tidy while keeping format checks. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
…#144) Add pdf::PDFBlockingThreadGuard, a runtime thread-affinity check a blocking service adapter opens with so it refuses to run on the registered interactive (canvas) thread instead of silently stalling pointer and frame handling. Wire it into PreflightEngine::run(), the one blocking implementation already reached through PDFJobScheduler, and register EditorHost's owning thread as the interactive thread at construction. Add PDFJobKind to PDFJobTraceEvent so job traces identify async work by type, and document the interactive-thread boundary (what pointer/frame callbacks may do directly vs. what must go through a submitted job) in docs/JOB_SCHEDULER.md. Wiring an actual UI trigger for interactive preflight runs, and correlating PDFJobTraceEvent timing against InteractionTraceRecorder's slow-frame attribution, remain open follow-up work for #144. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xnr9TZYfGr1nMgBeh16CSb
Add pdfinteraction::evaluateTraceContracts() and its TraceContract/TracePhase enums (LoopLibInteraction/sources/interactiontracecontract.h/.cpp): the fixed-order pass/fail evaluator issue #146 AC7 asks for. Given an ordered QList<TraceContractCheck>, it returns the first unsatisfied contract and the phase responsible, matching scripts/ci/check_interaction_traces.py's CONTRACTS/PHASES tuples and docs/schemas/interaction-trace-report.schema.json exactly, so a future harness that supplies the nine checks in order gets AC7 for free. phaseForStage() is docs/INTERACTION_CONTRACT.md's TraceStage-to-phase attribution table given a type. The evaluator takes an already-assembled checklist rather than an InteractionTraceRecorder or a live replay, so it is fully testable with synthetic checks -- no InteractionController, no scheduler, no hit-test dispatch -- and is covered by the new UnitTestsInteractionTraceContract target. The prior gh-146 work (scenario/report schemas, the nine-scenario corpus, and check_interaction_traces.py) validates the corpus as data but has no test binary to produce a run; this evaluator is the piece that decides a run's verdict once one exists. Replaying a scenario through InteractionController, applying its cost model, and assembling a full report run remain open, as UnitTestsInteractionTraces/UnitTestsInteractionTracesPresent in docs/INTERACTION_CONTRACT.md's two-lanes table. Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xnr9TZYfGr1nMgBeh16CSb
…readGuard docs/generated/phase5-widgets-inventory.json went stale when UnitTestsBlockingThreadGuard landed (issue #144 commit): it's a target directly in UnitTests/CMakeLists.txt, which scripts/generate_phase5_widgets_evidence.py scans, but the catalog was never regenerated for it. CI's policy job caught the drift -- test_verify_phase5_widgets_contract.py's crlf-currency check and its hardcoded target count (70) both went red against the actual count (71). Regenerate via scripts/generate_phase5_widgets_evidence.py --write and bump the test's hardcoded count to match. (scripts/generate-architecture-catalogs.py's separate architecture-catalog.json was already regenerated for this target in the #144 commit; this is the sibling widgets-surface catalog that step didn't cover.) Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Xnr9TZYfGr1nMgBeh16CSb
Rename leftover working-directory: loupe to loop in reusable Linux and Windows workflows so agent-fast/build can run generate_corpus.py --check. Rename schema_kind from loupe-processing-budget-exhaustion-corpus to loop-processing-budget-exhaustion-corpus in the corpus generator, test, and manifest so check_loop_identity passes. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Co-authored-by: michael berry <mberrys@users.noreply.github.com>
The agent-fast build proof never built the blocking-thread-guard test target because it was absent from the core module's test list, so its AUTOMOC .moc file was never generated and clang-tidy failed on tst_blockingthreadguardtest.cpp. Registering the target lets check-change.py build it (and generate the moc) before static analysis.
…ues (#194) Continue PR #525 on current origin/dev. Shared pdfquick::tokens cover spacing, typography, focus geometry, and colour roles; resolveStateVisual() is the canonical finding/check mapping. Incomplete and waived states never resolve as passed, and each state carries a unique icon plus accessible name. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Qt::red against white is only ~4.0:1. Use the light-theme fill (#B91C1C) so a destructive button label still meets WCAG text contrast in high contrast. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Drop restated enum notes, unused includes, and CMake/doc padding. Mapping invariants stay in tests and LOOP_DESIGN_SYSTEM.md. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Keep both UnitTests/tst_jbig2decodertest.cpp (PR 519) and UnitTests/tst_lifecycletest.cpp (dev) in the core module boundary paths. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Keep 0.2.1 in the living release train while adopting dev's consolidated 0.5.0-0.8.0 planned-train wording and retired-title note. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Resolve agent-policy.json conflict by keeping both tst_budgetexhaustiontest.cpp and tst_diagnosticstest.cpp in the core module paths. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Editor, PdfTool, PageMaster, Action List postflight, and the certificate gate now consume reducePreflightVerdict so budget exhaustion with zero findings cannot report PASS. Closes the remaining #234 surface gaps. Co-authored-by: michael berry <mberrys@users.noreply.github.com>
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: a89ed56646
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| const bool flattenTransparency = settings.flattenTransparency || flattensTransparencyByDefault(settings.target); | ||
| if (flattenTransparency) |
There was a problem hiding this comment.
Honor the explicit transparency opt-out
For PDF/X-1a and PDF/X-3 conversions, an explicit flatten_transparency: false is stored as settings.flattenTransparency == false, but this || flattensTransparencyByDefault(...) immediately turns it back on. Consequently the preview claims transparency will be flattened and apply() rasterizes page content despite the documented opt-out; the setting needs an explicit auto/true/false distinction or the parsed value must be used directly.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 2eb00de. The parsed flatten_transparency value is now a three-state policy (Automatic / Always / Never) resolved once by flattensTransparency(), so an explicit false is no longer re-enabled by the target default: preview() stops advertising a transparency.flatten change and apply() does not run the flattener. An explicit opt-out on a document with live transparency is fail-closed — pdfx.transparency.allowed stays a blocker. PDFStandardConversionSettings::flattenTransparency was added by this same PR and has never shipped in unstable/stable, so replacing it is not a break for a released consumer.
There was a problem hiding this comment.
Worth recording alongside this fix: while verifying it I found that the PDF/X rules were not running at all. pdfxProfile() returned a profile with no checks array, and PreflightEngine::parseProfile() (preflightengine.cpp:6110-6115) rejects an empty checks array before it reads pdfx. So preview() reported zero blockers for every PDF/X target — making collectPreflightBlockers() dead code and the pdfx.transparency.allowed fixability clause unreachable — and every PDF/X apply() failed at postflight. Fixed here in 08d42b9 by giving the profile the shared checks of loop-preflight/examples/profile-pdfx-x1a2001.json; this is what makes the transparency opt-out actually fail closed rather than silently succeeding. Recorded as #556.
| @@ -0,0 +1,6 @@ | |||
| # Async interactive-thread boundary (issue #144) and interaction-trace contract evaluator (issue #146) | |||
There was a problem hiding this comment.
Consolidate the change fragments
This commit adds eight separate changes/*.md fragments, whereas repository policy requires every PR to add exactly one. Consolidate these entries into the single fragment representing this change so changelog attribution and change validation remain deterministic.
AGENTS.md reference: AGENTS.md:L35-L35
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Not applicable to this PR, and no fragment is removed.
The eight fragments this PR carries are the per-topic-PR fragments of the PRs already merged into dev; each of those PRs added exactly one fragment named after its own head branch. For this promotion PR the changelog check is explicitly not-applicable, because the head branch (dev) is an integration branch:
$ python scripts/agent/check-change.py --base origin/unstable --head origin/dev --head-branch dev --dry-run
→ changelog: {"result": "not-applicable", "reason": "integration branch"}
scripts/agent/test_check_change.py::test_check_changelog_allows_stacked_topic_fragments already asserts that stacked topic fragments are legal, and deleting them here would falsify the changelog attribution of those merged PRs. changes/dev.md (this PR's own fragment) was updated in 7bd2363 to cover the review fixes.
| setState(State::Stale); | ||
| m_operatorSummary = QStringLiteral("Preflight is stale for the current revision."); |
There was a problem hiding this comment.
Assign the summary before emitting its notify signal
When a completed preflight becomes stale, setState() emits stateChanged before m_operatorSummary is updated, even though that signal is the operatorSummary property's notifier and is also what triggers EditorHost::presentationChanged. Observers handling the signal can therefore read and retain the previous run's summary; cancelRun() has the same ordering. Set the summary first or emit a dedicated notification after changing it.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 22225d1. setCurrentRevision() and cancelRun() now assign m_operatorSummary before calling setState(), so an observer reading the summary from the stateChanged handler never sees the previous run's copy. The new slot operatorSummaryIsCurrentWhenTheStateSignalFires reads the summary from inside the signal handler and fails on the unmodified code ("Preflight is running." vs the expected stale/cancelled text).
| const PDFOperationResult result = writeIncremental(&targetFile, originalData, originalDocument, document, outcome); | ||
| if (result && !targetFile.commit()) | ||
| { | ||
| return tr("File '%1' can't be committed after incremental save. %2").arg(fileName, targetFile.errorString()); |
There was a problem hiding this comment.
Publish the outcome only after commit succeeds
With safeWrite == true, the nested device overload writes Appended or CopiedUnchanged directly into the caller's outcome before QSaveFile::commit() runs. If the final rename or flush fails, this overload returns an error while leaving an outcome that claims the save completed, contradicting the API's “set on success” contract. Hold the nested outcome locally and copy it to the caller only after a successful commit.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in c2c7373. The fileName overload now holds the nested IncrementalWriteOutcome locally and copies it to the caller only after QSaveFile::commit() succeeds (and, in the direct-write branch, only after close()).
One caveat, stated plainly: on this platform the failure path cannot be covered by a test. QSaveFile::open() refuses a read-only target ("Existing file ... is not writable") before the nested write runs, so no test can reach commit() and be made to fail for the intended reason. That is recorded in the commit message rather than papered over with a test that cannot fail. The new slot fileOverloadReportsWhatItDid covers the file overload's success reporting (append vs verbatim copy), which was previously untested; it passes identically before and after this change.
| // and frame callbacks on -- as the thread blocking service adapters | ||
| // (PreflightEngine::run, and future OCR/AI/file-I/O adapters) must | ||
| // refuse to run on (issue #144). | ||
| pdf::PDFBlockingThreadGuard::registerInteractiveThread(); |
There was a problem hiding this comment.
Clear the interactive-thread registration on host teardown
EditorHost registers its construction thread globally, but its destructor never calls clearInteractiveThread(). When a host is destroyed while the process continues—such as an embedded or recreated editor—later synchronous preflight work on that thread is still rejected as an interactive-thread violation, and a host created on a temporary thread leaves a stale QThread* registration. Pair this registration with lifetime-aware cleanup.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 057cba7. EditorHost::~EditorHost() now clears the registration when the destroying thread is the one that registered it, so a recreated or embedded host no longer leaves a stale registration that keeps refusing synchronous blocking work on that thread.
| PDFTransparencyFlattenSettings transparencySettings = settings.transparencyFlattenSettings; | ||
| transparencySettings.analyzeOnly = false; | ||
| PDFTransparencyFlattenReport transparencyReport; | ||
| const PDFOperationResult transparencyResult = PDFTransparencyFlattener::apply(&candidate, transparencySettings, &transparencyReport); |
There was a problem hiding this comment.
Flatten before the CMYK normalization pass
For a typical non-spot PDF/X-1a conversion, both color normalization and transparency flattening are enabled, but the new flattening call runs after PDFRgbToCmykFixup. The flattener's non-spot path embeds its QImage page raster as DeviceRGB, reintroducing exactly the color space the earlier normalization removed; the independent validator or internal pdfx.color.device-rgb postflight then rejects the candidate. Run flattening before RGB-to-CMYK or emit a CMYK raster.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Deferred, not dismissed. Reordering alone cannot fix this.
PDFRgbToCmykFixup refuses to convert RGB image XObjects — LoopLibCore/sources/pdfrgbtocmykfixup.cpp:462-479 appends every image whose /ColorSpace resolves to an RGB name to report->unsupported with the reason "RGB image XObjects require image-sample conversion", and both preview() (blockers) and apply() (early return) treat that as a hard failure. The flattener's non-spot path embeds exactly such a DeviceRGB page raster (pdftransparencyflattener.cpp:384-416).
So with today's order (CMYK, then flatten) a working postflight rejects the DeviceRGB candidate; with the order you propose (flatten, then CMYK) the CMYK pass fails first with "RGB image XObjects require image-sample conversion". Neither order lets a PDF/X-1a conversion of a document with live transparency succeed.
Resolving it needs either image-sample conversion in the fixup (no API change, but it changes behaviour for every consumer) or a CMYK raster from the flattener (which changes the exported PDFTransparencyFlattenSettings, i.e. a protected interface). Filed as #555 and deliberately left out of this review-fix PR.
| case PreflightVerdictState::Pass: | ||
| return verdict.waivedFindingIds.isEmpty() | ||
| ? QStringLiteral("No problems found.") | ||
| : QStringLiteral("No problems found. Active dispositions cover previously blocking findings."); |
There was a problem hiding this comment.
Translate the operator-facing verdict summaries
preflightVerdictOperatorSummary() now supplies the text displayed directly by PreflightPane.qml, but its pass and fallback messages use QStringLiteral rather than a translation context. In every non-English build, a clean, incomplete, or otherwise reason-less run therefore replaces the previously translatable QML status with English-only copy. Route these operator-facing strings through QCoreApplication::translate or the Core translation context.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in ed4314f. preflightVerdictOperatorSummary() now routes every literal through QCoreApplication::translate("pdf::PreflightVerdict", ...) — the same context preflightGateFailureMessage() already used — so PreflightPane.qml is no longer English-only. The literals are byte-identical, which is why the pre-existing untranslated-path assertions still pass. The test binary needed QTEST_GUILESS_MAIN instead of QTEST_APPLESS_MAIN for a QTranslator to be installable at all; that macro change is in the same commit.
| const pdf::PreflightVerdict verdict = pdf::reducePreflightVerdict(result); | ||
| m_operatorSummary = pdf::preflightVerdictOperatorSummary(verdict); | ||
| switch (verdict.state) | ||
| { | ||
| setState(State::Pass); | ||
| case pdf::PreflightVerdictState::Pass: | ||
| setState(State::Pass); |
There was a problem hiding this comment.
Carry waiver state into the findings model
When all error findings have active dispositions, the reducer returns Pass here, but the controller has already copied the original error findings into PreflightFindingsModel without any decision or waived-state information. The Editor consequently announces a passing verdict while its findings list and canvas overlays continue to expose those entries as ordinary errors. Pass the active dispositions into the model so waived findings receive their distinct presentation instead of contradicting the overall state.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 82e4f43. acceptResult() now passes verdict.waivedFindingIds into PreflightFindingsModel::replace(), which marks the affected views (waived, exposed as WaivedRole) and maps them to a non-blocking overlay severity, so the canvas stops painting a PASS run's findings as blocking errors. The dispositions now reach the same model the verdict was reduced from, so the list and the headline can no longer contradict each other.
| PDFOperationResult writeIncremental(const QString& fileName, | ||
| const PDFDocument* originalDocument, | ||
| const PDFDocument* document, | ||
| bool safeWrite); | ||
| const PDFDocument* originalDocument, | ||
| const PDFDocument* document, | ||
| bool safeWrite, | ||
| IncrementalWriteOutcome* outcome = nullptr); |
There was a problem hiding this comment.
Preserve the exported incremental-writer overloads
Adding a defaulted parameter changes the mangled C++ symbol; it does not preserve the previous four-argument exported overload. Existing binaries built against LoopLibCore will therefore fail to resolve writeIncremental(...) after updating to this library even though the change is marked non-breaking. Keep the old signature as a forwarding overload and add the outcome-reporting overload separately.
AGENTS.md reference: AGENTS.md:L37-L37
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in 8ddc6e0. Both four-argument overloads are back as real forwarding functions and both default arguments are gone, so the original mangled symbols are preserved and a four-argument call stays unambiguous (a default argument plus an overload would have made every such call ambiguous).
Symbol check (dumpbin is not installed here; this is the DLL's PE export table): LoopLibCore.dll exports exactly four writeIncremental symbols — the two four-argument signatures plus the two five-argument ones. UnitTestsIncrementalSave now static_casts both signatures, so a future defaulted parameter cannot silently remove them again.
| "languages": { | ||
| "type": "array", | ||
| "items": { "type": "string", "minLength": 1 } | ||
| "items": { "type": "string", "minLength": 1, "pattern": "^[a-z]{2,3}(_[a-z]{2,4})?$" } |
There was a problem hiding this comment.
Align the OCR language schema with runtime normalization
The service deliberately trims and lowercases language values before validating them, so a request containing "EN" is accepted and normalized to "en" (the added test explicitly expects this). The tightened protocol schema, however, rejects the same request because its pattern permits only lowercase input. Clients validating requests against the published schema will reject inputs the runtime contract accepts; either make the schema case-insensitive in shape or stop accepting uppercase at runtime.
Useful? React with 👍 / 👎.
There was a problem hiding this comment.
Fixed in b6832f3. The schema pattern is now case-tolerant in shape (^[A-Za-z]{2,3}(_[A-Za-z]{2,4})?$, plus a description saying the service normalizes case) instead of lower-case only, so a client validating the published contract no longer rejects a request the runtime accepts and normalizes. The new test checks the schema and the runtime agree in both directions: the codes the runtime normalizes must match the pattern, and the traversal-shaped junk it refuses must not.
…ds-convert (#554) The target default was re-applied with '||' on top of the parsed value, so 'flatten_transparency: false' on PDF/X-1a/X-3 still flattened. The parsed setting becomes a three-state policy (Automatic/Always/Never) resolved once by flattensTransparency(), so an explicit opt-out is no longer overridden: preview() stops advertising a transparency.flatten change and apply() does not run the flattener. PDFStandardConversionSettings::flattenTransparency was added by this same PR and has never shipped in unstable or stable, so replacing it is not a break for a released consumer.
PDFTransparencyFlattener::apply() rasterizes every selected page, while preview() only advertises a transparency change when hasLiveTransparency() is true. Guarding the apply path with the same condition stops opaque vector/text documents from being silently replaced by full-page rasters.
…ecks (#554) preview() and apply() pass pdfxProfile() to PreflightEngine, but parseProfile() rejects a profile with an empty 'checks' array before it reads 'pdfx', so result.pdfx was never populated: preview() reported no blockers for any PDF/X target and every PDF/X apply() failed at postflight. The profile now carries the shared checks of loop-preflight/examples/profile-pdfx-x1a2001.json, which is what makes an explicit flatten opt-out fail closed.
The file overload handed the caller's outcome pointer to the device overload,
which filled it in before QSaveFile::commit(). A failed rename returned an
error while the outcome claimed the save had completed.
The commit-failure path is verified by construction rather than by a unit test:
QSaveFile::open() refuses a read-only target ('Existing file ... is not
writable') before the nested write runs, so a test cannot reach commit() on
this platform and none can be made to fail for the intended reason. The new
slot covers the file overload's success reporting (append vs verbatim copy).
…ds (#554) A defaulted parameter changes the mangled symbol, so the previous overloads would have disappeared from LoopLibCore. They return as forwarding overloads and the default argument is gone, which also keeps a four-argument call unambiguous. UnitTestsIncrementalSave now static_casts both signatures so a future defaulted parameter cannot silently remove them again. Symbol check (dumpbin is not installed here; this is the DLL's PE export table): LoopLibCore.dll exports exactly four writeIncremental symbols - the two four-argument signatures plus the two five-argument ones.
The standalone authorization pass required a digit or punctuation character in the credential, so an opaque value such as 'Bearer abcdefghijklmnop' reached support bundles verbatim. A second alternative now matches scheme-delimited all-letter tokens of 16+ characters; prose after a scheme name still survives.
PreflightPane.qml renders preflightVerdictOperatorSummary() directly, so its pass and fallback copy must go through a translation context instead of QStringLiteral - otherwise every non-English build shows English-only status.
…#554) stateChanged is operatorSummary's notifier, but setCurrentRevision() and cancelRun() emitted it before assigning the new text, so observers retained the previous run's summary.
…554) acceptResult() copied the raw error findings into the model, so a run whose blocking findings were all covered by an active disposition announced PASS while the findings list and canvas overlays still showed them as errors. The model now takes the verdict's waived IDs, exposes them through WaivedRole, and maps them to a non-blocking overlay severity.
…wn (#554) EditorHost::registerInteractiveThread() had no matching cleanup, so a host recreated in the same process left a stale registration that kept refusing synchronous blocking work on that thread.
…554) The schema pattern was lower-case only, so a client validating the published contract rejected 'EN' - a request the service accepts and normalizes to 'en'. The pattern is now case-tolerant in shape and says so in a description, and a test keeps the schema and the runtime normalization in step in both directions.
No description provided.