Skip to content

0.2.1 Release Candidate | Promotion - #572

Merged
mberrys merged 145 commits into
stablefrom
unstable
Sep 14, 2026
Merged

mberrys merged 145 commits into
stablefrom
unstable

Conversation

@mberrys

@mberrys mberrys commented Sep 14, 2026 •

Copy link
Copy Markdown
Contributor

[0.2.1] Operator Completion, Product Surface & Trust Leftovers

Promotes unstable → stable for the 0.2.1 milestone (GitHub milestone 17) — the patch on the 0.2.0 line that closes the operator, product-surface and trust leftovers before governed-correction work starts in 0.3.0.

Scope: 0.2.0.1-alpha (86ee1b5b) → d2e9ce9f — 105 non-merge commits.
Canonical milestone text: docs/github-milestones/0.2.1.md.

Riding this promotion from PR #517, whose fragment is changes/0.2.2.md after that branch name: #144 (async interactive-thread boundary) and #146 (interaction-trace contract evaluator).

Added

Changed

  • One verdict reducer on every surface ([0.2.1] Canonical preflight verdict reducer shared by every surface #234) — pdf::reducePreflightVerdict() is the single Core verdict (Pass / Fail / Incomplete / Error) consumed by Editor copy, PageMaster gate messages, Action List postflight steps and the certificate-issuance gate; budget exhaustion with zero findings is Incomplete (exit 8), never PASS. A whole-tree guard fails the build if any GUI source derives a pass or severity of its own.
  • Standards conversion now flattens live transparency ([Prepress] Add PDF/X and PDF/A conversion, not only validation #167, Standard-conversion PDF/X profile was rejected by PreflightEngine, so no PDF/X rule ever ran (fixed by PR #554) #556, PDFRgbToCmykFixup cannot convert RGB image XObjects, so PDF/X-1a conversion fails whenever content is rasterized #555) — standards-convert flattens before CMYK normalization, honours an explicit flatten opt-out, and skips flattening entirely when the document has no live transparency, so an already-opaque vector document is never replaced by full-page rasters; the flatten is reported verbatim under a new transparency_flatten report field, and the new flatten_transparency operation parameter is available identically from PdfTool repair and PageMaster export. The PDF/X profile now carries the checks PreflightEngine requires, so PDF/X rules actually run.
  • Incremental write contract ([0.2.1] RC | Preflight & GUI Foundation  #554 review) — PDFDocumentWriter keeps its exported four-argument writeIncremental overloads (as forwarding overloads, default argument removed so a four-argument call stays unambiguous) and publishes IncrementalWriteOutcome only after a successful commit.
  • Editor presentation and lifecycle — the findings model carries the verdict's waived findings instead of presenting them as blocking errors; EditorHost clears its interactive-thread registration on teardown; the verdict operator summary is translatable and is assigned before its notify signal fires.
  • OCR sidecar language schema accepts the case the service normalizes; check-change.py resolves clang-tidy by name instead of hardcoding clang-tidy-18.

Fixed

Security

  • PDFLogScrubber redacts all-alphabetic bearer credentials.
  • Bounded allocation and retention on hostile input (see Fixed) closes the memory/DoS class reached by the processing-budget corpus work.
  • Fixed the Semgrep and urllib findings and hardened the CI workflows (Improve CI workflow and address security findings #553).

Internal

  • Architecture truth for the 0.2.1 gate ([0.2.1] Make ADRs and architecture docs a trustworthy source #235) — every ADR carries status and Last-verified metadata, docs/adr/README.md is a new index, generated catalogs are regenerated, the source-of-truth hierarchy is cross-linked in docs/architecture-source-of-truth.md, and docs/REPO_MAP.md and AGENTS.md no longer carry stale repository/default-branch claims. AGENTS.md additionally gains the internal-logic contract and the anti-slop review pass (docs(agent-policy): add the internal-logic contract and an anti-slop review pass #568).
  • 0.2.1 canon — docs/github-milestones/0.2.1.md plus manifest and living-sequence updates across the milestone docs (docs: consolidate 0.2.1 GitHub milestone with leftover-work plan #518).
  • Policy and proof registrations — UnitTestsBlockingThreadGuard and the budget-exhaustion suite mapped into agent-policy.json, the GUI preflight-truth and QML mirror-parity guards added to check-change.py, and check-change.py resolving clang-tidy by name.
  • Docs hygiene — docs/v1-operator-acceptance.md and the readiness evidence repointed at code that exists, and the legacy product-name token removed from the design-system docs.

Closes

Closes #127
Closes #170
Closes #193
Closes #194
Closes #195
Closes #196
Closes #234
Closes #235
Closes #520
Closes #555
Closes #556
Closes #557
Closes #561
Closes #562
Closes #563
Closes #236

#236 (DocumentContext revision authority) closed on this milestone and already shipped in 0.2.0.1-alpha; its keyword here is documentary. Every other reference above is a dev/unstable landing whose issue is still open because closing keywords only take effect on the default branch — merging this PR into stable is what closes them.

Not closed by this promotion (deliberately, work continues):

Refs #241
Refs #125
Refs #163
Refs #559
Refs #560
Refs #569

#241 ships its release gates here, but the institutionalized acceptance criteria — the renderer-differential lane recording measurements rather than screenshots, and documented corpus licensing — are not in this patch. #263 (SBOM and signed provenance) is not planned for 0.2.1 and continues at 0.8.0-S07.

Verification

  • CI, Documentation truth, Supply Chain Policy and CodeQL are green on unstable at d2e9ce9f.
  • The RC into unstable ([0.2.1] RC | Preflight & GUI Foundation  #554) was green on agent-fast / build, policy, source_integrity, architecture-docs, fuzz and semgrep-cloud-platform/scan, plus package_script_tests on both runners.

Breaking changes

None. No public contract, schema version or persistence format changes: the export additions, the interaction-trace contract and the flatten_transparency parameter are additive, and the four-argument writeIncremental overloads are preserved.


View with [code]smith Autofix with [code]smith
Need help on this PR? Tag @codesmith-bot with what you need. Autofix is disabled.

claude and others added 30 commits September 5, 2026 20:41
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.
Defer MSI removal until after LGPL relink evidence is collected and normalize usr\bin install layout in run_qt_relink_test.ps1.

Co-authored-by: Cursor <cursoragent@cursor.com>
Route GitHub Actions context through env vars, add Dependabot cooldown, and validate GitHub API slugs before urllib requests. PDF-standard MD5 usage in pdfsecurityhandler.cpp is intentionally unchanged.

Co-authored-by: Cursor <cursoragent@cursor.com>
…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>
mberrys and others added 19 commits September 13, 2026 13:30
Remove const from compressed QByteArray and qMove it into PDFStream
constructor, matching the pattern used elsewhere in pdfrgbtocmykfixup.cpp.

Co-authored-by: michael berry <mberrys@users.noreply.github.com>
…ation (#566)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

Closes the 0.2.1 release-gate gaps for GitHub #520 and #241.

### #520 — fresh processing budget per search

- `searchDocumentText()` now allocates an operation-scoped
`PDFProcessingBudget` instead of reusing the session budget whose
elapsed timer starts at session construction.
- Budget exhaustion is surfaced as
`PDFDocumentSearchResult::complete=false` / `budgetExceeded=true`;
`QuickDocumentModel::search()` returns `false` without letting
`PDFBudgetExceededException` escape to QML.
- Adds `UnitTestsDocumentSearch` regression coverage and
`scripts/ci/check_search_budget_gate.py`.

### #241 — independent validation release gate

- Adds `scripts/ci/check_independent_validation_gate.py` to keep the
oracle lane reviewable: conversion triad manifests,
`run_independent_validators.py`, `UnitTestsStandardOracle` /
`UnitTestsConversionOracle`, and self-certification source markers.
- Wires both guards into `ci.yml` `source_integrity` and
`scripts/agent/check-change.py`.

## Verification

- `python3 scripts/ci/check_search_budget_gate.py` — pass
- `python3 scripts/ci/check_independent_validation_gate.py` — pass
- `ctest -R '^UnitTestsDocumentSearch$'` — pass
- `python3 scripts/agent/check-change.py --base origin/dev` — focused
tests 34/34 pass; clang-tidy exits non-zero on touched TUs due to
pre-existing `'utility' file not found` diagnostic in this environment
(not introduced by this diff)

## Plan

See coordinator plan:
`/cursor/stores/self/internal/issue-520-241-release-gates-plan.md`

Fixes #520
Refs #241
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-414bc936-d3b7-509a-86c5-5bf256827962?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-414bc936-d3b7-509a-86c5-5bf256827962&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/studio-berry/codesmith/loop/pr/566"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1791864821&installation_model_id=435800&pr_number=566&repository=studio-berry%2Floop&return_to=https%3A%2F%2Fgithub.com%2Fstudio-berry%2Floop%2Fpull%2F566&signature=bec110427aa6391d5e2b976b65459615377aef9982e2758dcf197848e81d2552"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you
need. Autofix is disabled.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->
- Add transparency, ai-art, and thin-parts-fill PDF fixtures
- Unpend malformed-not-pdf and overprint-multiply-on manifest rows
- Generate thin-parts-fill via LoopGenerateFixtures
- Handle PdfTool input-error envelopes in corpus runner
- Add/update golden snapshots for promoted fixtures

Co-authored-by: michael berry <mberrys@users.noreply.github.com>
- Add transparency, ai-art, and thin-parts-fill PDF fixtures
- Unpend malformed-not-pdf and overprint-multiply-on manifest rows
- Generate thin-parts-fill via LoopGenerateFixtures
- Handle PdfTool input-error envelopes in corpus runner
- Add/update golden snapshots for promoted fixtures
Co-authored-by: michael berry <mberrys@users.noreply.github.com>
#196)

- Wire canvas overlay clicks through selectFinding for full navigation
- Add selectNextFinding/selectPreviousFinding with Alt+Up/Down shortcuts
- Invalidate FindingCanvasNavigator on revision sync
- Sync PreflightPane list highlight to selectedFindingId
- Populate evidenceTargets from finding bbox in navigation requests
- Add shell keyboard and inspector dispatch tests for finding navigation

Co-authored-by: michael berry <mberrys@users.noreply.github.com>
Move adjacent-finding selection into PreflightFindingsModel so EditorHost
does not aggregate finding counts. Enable Alt+Up/Down shortcuts from Core
preflightStateName and mirror Main.qml in the accessibility smoke target.

Co-authored-by: michael berry <mberrys@users.noreply.github.com>
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
Closes the documentation scope of GitHub #235 / Linear MB-463 for the
**0.2.1** architecture-truth gate.

PR #255 (2026-08-10) introduced ADR verification headers, generated
architecture catalogs, and the `Documentation truth` CI workflow. This
follow-up re-verifies that narrative against **current `dev`** at
`e65cdd19` after DocumentContext revision authority (#236) and
subsequent preflight/session work.

## Changes

- **ADR-001** — align with revision-keyed caches, optional
`PDFDocumentContext`, admission modes, byte-bounded eviction, and
factory API
- **ADR-002** — add `thin-parts`, graph-backed checks, and pointers to
generated check catalogs
- **All ADRs** — refresh `Last-verified` to `2026-09-13 @ e65cdd1…`
- **`docs/adr/README.md`** — new ADR index with status table and catalog
regeneration instructions
- **`docs/architecture-source-of-truth.md`** — cross-link revision
authority and `preflight-check-catalog.json`
- **`docs/REPO_MAP.md`** — link ADR index in layout table
- **`loop-preflight/README.md`** — clarify v2 examples vs v3 engine
output

## Verification

- `python3 scripts/generate-architecture-catalogs.py --check` — pass
- `python3 scripts/agent/check-change.py --base origin/dev` — pass
(includes mapped preflight focused tests)

No protected contract or schema changes.

Refs #235
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-f35ce71d-cccd-5600-a2ca-25dac77ee16d?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-f35ce71d-cccd-5600-a2ca-25dac77ee16d&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/studio-berry/codesmith/loop/pr/565"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1791864553&installation_model_id=435800&pr_number=565&repository=studio-berry%2Floop&return_to=https%3A%2F%2Fgithub.com%2Fstudio-berry%2Floop%2Fpull%2F565&signature=a50e96230032f93f6325b2a26bec45140ef068785ea2a39a5697946431046be1"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you
need. Autofix is disabled.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->
## Summary

Implements the Qt Quick operator loop across shell information
architecture, first-class revision-bound preflight, report-backed
Prepress Inspector, finding-to-canvas navigation, and bounded GUI/E2E
verification.

## Issue linkage

- Related to #193
- Related to #195
- Related to #127
- Related to #196
- Related to #170

## Verification

- Focused CTest: 9/9 passed.
- Product operator-loop: 8/8 passed.
- Quick accessibility smoke: 3/3 passed on native and software paths.
- Repository gate: source, architecture, policy, preflight-truth,
mirror-parity, formatting, builds, clang-tidy, and 29/29 policy-selected
tests passed with the configured environment.

No issues are auto-closed by this PR.

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/studio-berry/codesmith/loop/pr/564"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1791862718&installation_model_id=435800&pr_number=564&repository=studio-berry%2Floop&return_to=https%3A%2F%2Fgithub.com%2Fstudio-berry%2Floop%2Fpull%2F564&signature=07ad839c084425c0ba71236010ce4f16fdbad9e5842e8c5e44fe3acc388b79b9"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you
need. Autofix is disabled.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->
Fix whitespace alignment in the nested ternary that maps preflight
verdict states to exit codes. Unblocks agent-fast on dev and RC PR #554.

Co-authored-by: michael berry <mberrys@users.noreply.github.com>
<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## What changed

Agent-fast on `dev` failed with `-Wclang-format-violations` on lines
298–299 of `UnitTests/tst_preflightcorpus.cpp` (nested ternary
whitespace). This PR applies clang-format to that file only, unblocking
RC PR #554.

## Proof

- [x] `clang-format --dry-run --Werror
UnitTests/tst_preflightcorpus.cpp` passes locally
- [x] One `changes/cursor-fix-preflightcorpus-format-6547.md` fragment
added
- [ ] Changed behaviour has a test that fails without the change — N/A
(format-only)
- [ ] Protected-path or contract change — none

## Internal logic (touched behavior-bearing code)

Format-only change; no logic touched.

Anti-slop summary: Whitespace alignment only; no comments or logic
changed.

## Security and rollback

- [x] No security impact
- Rollback: revert this commit

## Docs

- [x] none needed — format-only fix

## Self-review (BSP-002 §4.3)

- [x] Reviewed diff; format-only whitespace in ternary chain
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-ecb73197-e4af-511c-8e56-122ec4c8b371?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-ecb73197-e4af-511c-8e56-122ec4c8b371&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/studio-berry/codesmith/loop/pr/571"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1791933972&installation_model_id=435800&pr_number=571&repository=studio-berry%2Floop&return_to=https%3A%2F%2Fgithub.com%2Fstudio-berry%2Floop%2Fpull%2F571&signature=0fa64747af7a597566091c8ef76c5067c90f5578b7788d889b0fa4e6b5d1c284"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you
need. Autofix is disabled.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->
@mberrys mberrys added this to the 0.2.1 milestone Sep 14, 2026
mberrys and others added 2 commits September 13, 2026 18:12
Extract preflight profile catalog, run submitter, and inspector dispatch
into LoopLibInteraction. Add manifest-driven menu_group to the command
catalog. Split RGB image XObject conversion out of pdfrgbtocmykfixup.cpp.

Co-authored-by: michael berry <mberrys@users.noreply.github.com>
mberrys and others added 4 commits September 13, 2026 18:54
)

<!-- CURSOR_AGENT_PR_BODY_BEGIN -->
## Summary

Implements **Option A** from the thermo-nuclear review of PR #572:
decompose on `unstable` before merging #572 (`unstable`→`stable`).

### EditorHost decomposition
- **`PreflightProfileCatalog`** (LoopLibInteraction): bundled/local
profile scan, digest/check-set staleness, QFileSystemWatcher
- **`PreflightRunSubmitter`** (LoopLibInteraction): scheduler worker
extracted from `EditorHost::runPreflight()`
- **`ShellInspectorDispatch`** (LoopLibInteraction): inspector selection
builders; removed `"pending"` placeholder properties
- **Manifest `menu_group`**: added to `docs/loop-shell-actions.json` +
`CommandDescriptor`; deleted `shellMenuGroupForAction()` from EditorHost

### Core split
- **`pdfrgbtocmykimagefixup.cpp`**: RGB image XObject conversion path
extracted from `pdfrgbtocmykfixup.cpp`
- **`pdfrgbtocmykhelpers`**: shared `isRgbColorSpaceName` /
`targetProfileId`

### Small cleanups
- Removed duplicate `#include "interactiontarget.h"`
- Shared `resolvedPreflightVisual()` helper for preflight visual/color
resolution

## Line counts

| File | Before | After |
|------|--------|-------|
| `LoopEditor/editorhost.cpp` | 1,792 | **1,436** |
| `LoopLibCore/sources/pdfrgbtocmykfixup.cpp` | 1,158 | **860** |
| `LoopLibCore/sources/pdfrgbtocmykimagefixup.cpp` | — | **309** |

## Verification

- `UnitTestsShellWorkspace` — pass (menu policy / manifest groups)
- `UnitTestsEditorHost` — pass (preflight run, visual mapping, export
parity)
- `scripts/verify-command-catalog.py` — pass
- `python3 scripts/agent/check-change.py --base origin/unstable` —
focused_tests pass; changelog pass

## Follow-up (not in scope)

- `editorhost.cpp` still above ~1,200-line target —
`EditorHostShellBindings` ctor wiring extract
- `pdfdocumentreader.cpp` 1,061-line cross (thermo §1.3, lower priority)
- `findingnavigation` hard-coded check IDs; `looptokens` codegen; QML
mirror duplication tax

Does **not** merge #572 to `stable`.
<!-- CURSOR_AGENT_PR_BODY_END -->

<div><a
href="https://cursor.com/agents/bc-53742eb4-1b17-5dca-8a18-ed5c27ea95c8?cursor_ref=pr_footer&cursor_cta=open_in_web"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-web-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-web-light.png"><img
alt="Open in Web" width="114" height="28"
src="https://cursor.com/assets/images/open-in-web-dark.png"></picture></a>&nbsp;<a
href="https://cursor.com/background-agent?bcId=bc-53742eb4-1b17-5dca-8a18-ed5c27ea95c8&cursor_ref=pr_footer&cursor_cta=open_in_cursor"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://cursor.com/assets/images/open-in-cursor-dark.png"><source
media="(prefers-color-scheme: light)"
srcset="https://cursor.com/assets/images/open-in-cursor-light.png"><img
alt="Open in Cursor" width="131" height="28"
src="https://cursor.com/assets/images/open-in-cursor-dark.png"></picture></a>&nbsp;</div>

<!-- codesmith:footer -->
---
<a
href="https://app.blacksmith.sh/studio-berry/codesmith/loop/pr/573"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-light-v2.svg"><img
alt="View with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/view-with-codesmith-dark-v2.svg"></picture></a>
<a
href="https://backend.blacksmith.sh/track/enable-autofix?expires=1791941795&installation_model_id=435800&pr_number=573&repository=studio-berry%2Floop&return_to=https%3A%2F%2Fgithub.com%2Fstudio-berry%2Floop%2Fpull%2F573&signature=f5d03e081da0364fa129e4e8dfcb55e9e2e64faff067775eebdafd826ae18ecf"><picture><source
media="(prefers-color-scheme: dark)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"><source
media="(prefers-color-scheme: light)"
srcset="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-light.svg"><img
alt="Autofix with [code]smith"
src="https://pr-comments-assets.blacksmith.sh/codesmith/autofix-with-codesmith-dark.svg"></picture></a>
<sup>Need help on this PR? Tag <code>@codesmith-bot</code> with what you
need. Autofix is disabled.</sup>

<!-- codesmith:autofix:disabled -->
<!-- /codesmith:footer -->
The widgets-free release profile build failed because
tst_quickdocumentmodeltest.cpp passed a const QByteArray lvalue to
PDFStream(PDFDictionary&&, QByteArray&&). Use std::move for both
arguments in the two affected test helpers.

Co-authored-by: michael berry <mberrys@users.noreply.github.com>
- Pass rvalue refs to PDFStream in tst_rgbtocmykfixup.cpp (GCC 13)
- Load font-embedded.pdf fixture in DocumentSearchTest instead of QPainter::drawText

Co-authored-by: michael berry <mberrys@users.noreply.github.com>
- Unwrap PdfTool preflight JSON envelope in bleed stress tests
- Use empty-page fixture for independent search-budget test
- Surface budget error text via PDFException::getMessage() in search

Co-authored-by: michael berry <mberrys@users.noreply.github.com>
@mberrys
mberrys merged commit 7ab79f9 into stable Sep 14, 2026
29 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment