v2.0.0.2 - M12 Native Print Spool #210

Merged
gronod merged 21 commits from develop into main 2026-09-18 15:14:49 +01:00
21 Commits
Author SHA1 Message Date
gronod f7f6a8bd87 chore: bump MARKETING_VERSION to 2.0.3
macOS CI / package (push) Successful in 3m37s
macOS CI / build-and-test (push) Skipped
2026-09-18 11:06:42 +01:00
gronod ed91ebf807 test(print): scroll stage-2 via synthesized scroll wheel (#215)
macOS CI / package (pull_request) Canceled after 0s
macOS CI / build-and-test (push) Canceled after 0s
macOS CI / package (push) Canceled after 0s
macOS CI / build-and-test (pull_request) Canceled after 24m1s
press(forDuration:thenDragTo:) is inert on macOS — content-drag
scrolling is iOS-only, so the helper burned its 20s budget with the
gallery frame never moving. The scroll wheel is the mechanism the
platform supports: one scroll(byDeltaX:deltaY:) call on a coordinate
inside app.scrollViews["stage-2"] per poll iteration, negative deltaY
to move content up off the Dock zone.
2026-09-18 10:38:57 +01:00
gronod 77c065f433 docs(agents): add remote test build procedure for CI runner host
macOS CI / package (push) Canceled after 0s
macOS CI / build-and-test (push) Canceled after 36m50s
- SSH access: localadmin@192.168.0.172, credentials in ~/Projects/build-host.env
- sshpass usage via SSHPASS env var (never command-line)
- Remote checkout sync: git checkout + scp uncommitted changes
- xcodegen PATH workaround: prepend ~/.local/xcodegen/2.38.0/xcodegen/bin (#109)
- Full xcodebuild build-for-testing + codesign + test-without-building sequence mirroring .gitea/workflows/macos.yml
- Unit tests only (ICCeryCoreTests) —
2026-09-18 09:55:22 +01:00
gronod f7099a9808 fix(print): filter Stage 2 quality picker by selected media type (#214)
macOS CI / build-and-test (push) Successful in 44m15s
macOS CI / package (push) Successful in 3m27s
lpoptions -l only exposes the global quality list, so the picker offered every token for every media — the driver then rejected invalid pairs (e.g. Fast Economy on Premium Semigloss) at print time. Neither the Epson nor the Canon PPD carries standard *UIConstraints, so resolve the media-to-quality map from each driver's own data: Epson InkjetPrinter2 *EPIJUIConstraint forbid-rules in the machine bundle's PDEData.dat (non-media LHS terms evaluated against lpoptions defaults), Canon BJPrinter's binary cnb_<TableID>0.tbl record table via *CNIJNameTblPath/*CNIJTableID, and generic PPD *UIConstraints pairs.

MediaQualityConstraints picks the first non-empty source and fails open — queues without constraint data keep the full list. PrinterCapabilities gains qualityIDsByMediaType plus filtered qualities(forMediaType:)/allowsQuality accessors; the view model exposes availableQualities, clamps the pick on media change / seeding / panel apply-back, and substitutes a stale quality defensively in makeRequest so an invalid pair can never reach the ticket.
2026-09-17 23:08:51 +01:00
gronod ddb9245f44 fix(print): anchor raster at page top in bottom-up canvas space (#211)
macOS CI / package (pull_request) Canceled after 0s
macOS CI / build-and-test (pull_request) Canceled after 11m49s
macOS CI / build-and-test (push) Failing after 44m23s
macOS CI / package (push) Skipped
226e7b2 un-flipped the canvas but left destinationRect anchored at
pageRect.minY — the page's BOTTOM edge in bottom-up space — so
sub-page targets printed at the foot of the sheet and oversize
targets clipped their top rows. Anchor at pageRect.maxY - height,
restoring the spec'd top-left anchor (docs/14 §6, D9).

Adds the missing regression coverage: the geometry test now asserts
the top anchor with a sub-page raster, and the PDF harness gains a
banded (red-over-blue) fixture that fails on either a mirrored draw
or a bottom anchor — solid fills could catch neither.
2026-09-17 21:53:17 +01:00
gronod 226e7b2a78 test(print): flip canvas coordinate space to bottom-up for NSPrintOperation compatibility
macOS CI / build-and-test (push) Skipped
macOS CI / package (pull_request) Canceled after 0s
macOS CI / build-and-test (pull_request) Canceled after 34s
- TargetPageCanvasView.isFlipped: true → false (bottom-up CoreGraphics origin)
- TargetCanvasGeometryTests: assertion updated to XCTAssertFalse(view.isFlipped)
2026-09-17 20:49:52 +01:00
gronod 2407abfd9e chore: bump MARKETING_VERSION for M12 native spool release
macOS CI / build-and-test (push) Failing after 4m51s
macOS CI / package (push) Skipped
2026-09-17 18:03:00 +01:00
gronod 356ae0bfa9 Merge pull request 'M12: native NSPrintOperation target spool (#201, #202)' (#209) from milestone/m12-native-spool into develop
macOS CI / build-and-test (push) Canceled after 0s
macOS CI / package (push) Canceled after 0s
2026-09-17 18:02:33 +01:00
gronod 60f3aa3991 Merge pull request 'docs(print): native spool documentation update + issue #201 analysis (#201 Phase 6)' (#208) from feat/201-docs into milestone/m12-native-spool
macOS CI / build-and-test (push) Skipped
macOS CI / package (pull_request) Canceled after 0s
macOS CI / build-and-test (pull_request) Canceled after 32m12s
2026-09-17 18:02:12 +01:00
gronod b89d9777ad docs(print): native spool documentation update + issue #201 analysis (#201 Phase 6)
macOS CI / build-and-test (push) Skipped
- Create docs/issue-printer-quality-ignored.md: lifecycle trace, ticket-loss and override-inversion root causes with file:line evidence, dismissed argv tokenisation hypothesis, locked D1-D13 table.
- docs/11: retitle walkthrough to UI click to NSPrintOperation; replace build_lp_args/print_target with the S1-S14 native-spool trace; add layers 5' (Quartz vocabulary) and 7 (ticket serialise/restore); mark lp flag table and macOS test list historical v1.
- docs/14: decision table rows (Spool, ColorSync ticket, Geometry, Interpolation, AirPrint) marked adopted in v2.0 via #201; note section 7 Quartz vocabulary now live in ICCery proper.
- AGENTS.md: spooler documented in app target (D1); new Print spool section (lp eradicated, parsers retained per D4, ICCERY_TEST_SPOOL_LOG seam); ColorSync SPI rule replaced by the D2 single-path dual-vocabulary rule.
- Eradication sweep: label remaining lp/LpArgs references historical v1 in docs/10, docs/13, docs/25, docs/26; drop deleted LpArgs from README.
2026-09-17 17:40:25 +01:00
gronod 7a3a0886be Merge pull request 'feat(print): AirPrint queue detection + Stage 2 warning badge (#202 Phase 5)' (#207) from feat/202-airprint-detection into milestone/m12-native-spool
macOS CI / build-and-test (push) Skipped
2026-09-17 17:31:09 +01:00
gronod 5389e4c2bd feat(print): AirPrint queue detection + Stage 2 warning badge (#202 Phase 5)
macOS CI / build-and-test (push) Skipped
- CupsParsers: lpstatDeviceURIs / lpoptionsMakeAndModel /
  detectAirPrint — the six docs/14 §10.2 rules (apple-airprint:// URI,
  *APAirPrint: True, Apple+AirPrint make-and-model, ipps://+PPD
  mention, ipp:// + _universal mDNS subtype, cupsFilter2→image/urf).
- CupsService.listPrinters: one tolerated lpstat -v spawn feeds
  detection; lpoptions -p yields displayName + makeAndModel in a
  single spawn; Printer.isAirPrint populated per queue.
- Printer.isAirPrint defaults false with a tolerant decoder.
- Stage2View: live airPrintWarningBadge — "AirPrint queue —
  unmanaged colour cannot be guaranteed."
- Fixtures: lpstat -v marks Mock_Canon_Pro via _universal subtype;
  lpoptions emits printer-make-and-model.
- Tests: six rule fixtures + USB negative + a no-fallback-respawn
  listPrinters case; Milestone12AirPrintUITests tracks the badge
  across queue selection.

Gate: full xcodebuild test green (515 unit + 70 UI).
2026-09-17 17:20:25 +01:00
gronod 972368aa85 Merge pull request 'feat(print): native NSPrintOperation spooler, eradicate lp (#201 Phase 4)' (#206) from feat/201-native-print-operation into milestone/m12-native-spool
macOS CI / build-and-test (push) Skipped
2026-09-17 16:35:44 +01:00
gronod 4f3caf335d feat(print): native NSPrintOperation spooler, eradicate lp (#201 Phase 4)
macOS CI / build-and-test (push) Skipped
Replace the LpArgs argv builder with the deterministic
TicketWriteResolver (locked 1-10 write order, Stage 2 overrides win
unconditionally, no raw, nil overrides emit no write) and spool via a
headless NSPrintOperation over TargetPageCanvasView instead of the
CUPS lp binary. RecordingTargetSpooler (DEBUG) logs deterministic
request lines to ICCERY_TEST_SPOOL_LOG for tests. Single spool job
toggle switches printAllPages between per-page requests and one
multi-page job. CupsService keeps lpstat/lpoptions enumeration but
loses printTarget; ProcessID.lp* renamed cupsLp*; LpArgs, its tests
and the lp fixture deleted.
2026-09-17 16:30:27 +01:00
gronod de142a9690 Merge pull request 'feat(print): 1:1 TIFF raster loader and headless page canvas (#201 Phase 3)' (#205) from feat/201-raster-canvas into milestone/m12-native-spool
macOS CI / build-and-test (push) Skipped
2026-09-17 15:08:06 +01:00
gronod a2f994821f feat(print): 1:1 TIFF raster loader and headless page canvas (#201 Phase 3)
macOS CI / build-and-test (push) Skipped
TargetRasterLoader decodes printtarg TIFFs via ImageIO and re-tags
into DeviceGray/DeviceRGB/DeviceCMYK around the decoder's own
dataProvider — no resample, no bit-depth change (D10). Skip-only
alpha padding (noneSkipLast/First, which ImageIO emits for opaque
RGB TIFFs) is accepted; real coverage alpha, indexed, float and
non-1/3/4-component layouts are rejected as unsupportedLayout.
DPI metadata drives pointSize (px/dpi*72) with a 72 dpi fallback +
warn; manifestDrift reports >0.5 mm disagreement with the printtarg
manifest (D9).

TargetPageCanvasView is the headless flipped canvas NSPrintOperation
paginates: rectForPage stacks pages top-down, destinationRect anchors
1:1 at the page's top-left snapped to 0.001 pt, and draw() forces
interpolation/antialias/font-smoothing off per page with a drawProbe
seam for tests.

PDF harness (NativeSpoolPDFTests, skippable via ICCERY_SKIP_PRINT_PDF=1)
ran on this host: run() -> true, 2 pages at A4 media box, and the
fixture's 72 pt patch verified 1:1 at the painted top-left (printer
unprintable-margin offset tolerated per R10).
2026-09-17 14:57:24 +01:00
gronod 1d6032456d Merge pull request 'feat(print): capture native print ticket on panel OK (#201 Phase 2)' (#204) from feat/201-ticket-capture into milestone/m12-native-spool
macOS CI / build-and-test (push) Skipped
2026-09-17 13:50:45 +01:00
gronod fc1530ab2e feat(print): capture native print ticket on panel OK (#201 Phase 2)
macOS CI / build-and-test (push) Skipped
Serialise PMPrintSettings/PMPageFormat to XML data plus a plist-safe
NSPrintInfo dictionary snapshot (PrintTicket), restore via
PM*CreateWithDataRepresentation -> PMCopy* -> PMSessionValidate* ->
updateFromPM*, and refuse cross-queue replay (R3).

PrintPanelService.showProperties now returns PanelCaptureResult; the
view model stores tickets in capturedTickets keyed by queue.

Also fixes a latent over-release: PMSessionGetCurrentPrinter hands back
the session's own printer (borrowed) — the extracted currentPrinterID
was PMRelease-ing it, which dangled the session and crashed AppKit's
_printerInPrintSession path and session teardown.
2026-09-17 13:43:43 +01:00
gronod ffbe56f43a Merge pull request 'refactor(print): extract PMTicketBridge from PrintPanelService (#201 Phase 1)' (#203) from feat/201-pm-ticket-bridge into milestone/m12-native-spool
macOS CI / build-and-test (push) Skipped
2026-09-17 12:16:26 +01:00
gronod ee5da4a637 refactor(print): extract PMTicketBridge from PrintPanelService
macOS CI / build-and-test (push) Skipped
Centralise every Core Printing (PM*) call in one @MainActor bridge so
the upcoming ticket serialise/restore path and native spooler share a
single documented ownership policy. Pure move — no behaviour change.
2026-09-17 12:15:27 +01:00
gronod 0513b272cb docs(agents): add issue ticket style and dependency tracking guidelines
macOS CI / package (push) Skipped
macOS CI / build-and-test (push) Skipped
2026-09-17 10:34:50 +01:00