[Feature/Medium] Stage 2: paper source selection — enumerate vendor tray keys and wire through ticket, panel, and spool #218

Closed
opened 2026-09-22 16:35:20 +01:00 by gronod · 0 comments
Owner

Summary

Stage 2 has a "Tray" picker (printerTraySelect, Stage2View.swift:299-307) populated from printerCaps.trays, but it never appears on real Epson/Canon queues and is never applied anywhere:

  • CupsService.capabilities(from:ppd:) builds trays only from InputSlot/MediaSource lpoptions -l listings (CupsService.swift:139-144). Epson InkjetPrinter2 exposes the feed source as EPIJ_PSrc/EPIJ_FdSo; Canon BJPrinter as CNIJMediaSupply (docs/24:133). Neither is enumerated → trays is empty → the picker is hidden. (Confirmed on XP-55 and Pro9500-class queues.)
  • selectedTray never reaches PrintPanelInitialSelections, TargetPrintOverrides/TicketWriteResolver, or makeRequest (PrintSessionViewModel.swift:416-433). Captured InputSlot= in the panel result is never mapped back to the picker, and PrintOptions.paperSource (PrinterModels.swift:171) is never populated.
  • The picker has been inert since the M3 live-print-panel commit (issue 17); v1 documented unix paper_source as ignored (docs/10 §paper_source).
  • The Portrait/Landscape button pair is also inconsistent with the other dropdown-style printer controls and should become an Orientation picker as part of this UI pass.

Users must open the driver dialog to choose tray/cassette/manual feed — currently the only place it can be set at all.

Implementation plan

Capabilities / parsing (ICCeryCore)

  1. CupsParsers: add trayKeys roster + detectTrayKey(optionKeys:). Candidates in preference order: InputSlot, MediaSource, EPIJ_FdSo, EPIJ_PSrc, CNIJMediaSupply. EPIJ_FdSo is preferred over EPIJ_PSrc because recorded XP-55 listings label EPIJ_FdSo as Paper Source and EPIJ_PSrc as Page Setup; EPIJ_FdSo is the actual feed-source control. AP_D_InputSlot is a capture-side alias, not an enumeration key.
  2. CupsService.capabilities(from:ppd:): enumerate trays from the detected roster key (replace the hard-coded InputSlot/MediaSource last-wins match); record the producing key on a new PrinterCapabilities.trayKey (mirrors qualityKey, #183). Where a queue exposes more than one roster key, enumerate the preferred one and record it.
  3. PrinterTray: add a token field for the machine value written to the ticket; keep the 1-based index id; name becomes the PPD-enriched label (like media/quality).
  4. Seed selectedTray from the listing's * defaultChoice instead of trays.first (reloadSelectedCapabilities, PrintSessionViewModel.swift:131-133).
  5. MediaQualityConstraints.resolve: accept an optional tray override so Epson *EPIJUIConstraint LHS terms (EPIJ_PSrc, EPIJ_FdSo) are evaluated against the selected feed source rather than the queue's lpoptions default. Precompute one media→quality map per enumerated tray token during capability loading; no async reload on tray switch.

Write path (Stage 2 → ticket → driver)

  1. PrintSessionViewModel.selectedTrayToken: String? — id→token lookup (mirrors selectedPaperSizeToken, :217-221).
  2. PrintPanelInitialSelections gains trayKey/tray; applyInitialSelections writes PMPrintSettingsSetValue(trayKey, token) immediately after PageSize.
  3. TargetPrintOverrides gains trayKey/tray; TicketWriteResolver adds a numbered write <trayKey>=<token> placed after PageSize and before media; update the locked-order contract comment and testLockedWriteOrder.
  4. makeRequest passes the tray override. NativeTargetSpooler.mirror needs no change — it replays every resolved write into NSPrintInfo.printSettings.

Capture-back (driver → Stage 2)

  1. Extract the tray token from the captured options string using the detected tray key, the full tray roster, and AP_D_InputSlot. Map token → PrinterTray.id; set selectedTray and populate PrintOptions.paperSource (Int, 1-based — docs/10 semantics). If the token is non-empty but unknown, append a synthetic PrinterTray entry (id = current max + 1, token/name = raw token) and select it.
  2. UITestHooks.printPanelResult mirrors the new field so UI tests can drive it (AppEnvironment.swift:136-165).

UI

  1. Picker shows "Paper source" as its label when populated; keep the printerTraySelect a11y id so existing UI tests don't churn.
  2. Replace the Portrait/Landscape button pair with a Picker("Orientation", selection: $workflow.print.printOrientation) dropdown using printerOrientationSelect; keep portrait/landscape values and downstream orientation-requested=3|4 behavior unchanged.

Considerations / non-goals

  • Vendor companion keys: if a single trayKey write doesn't move the driver PDE on hardware, note it — do not guess key pairs.
  • MediaSource is also in some media rosters (docs/11:410); tray detection must not regress media-key detection — assert precedence in tests.
  • Cross-launch persistence of driver state is out of scope; tickets and captured options remain session-only.

Acceptance criteria

  • Tray/paper-source picker appears on Epson and Canon queues (vendor keys enumerated)
  • EPIJ_FdSo is preferred over EPIJ_PSrc on Epson queues
  • Selection is written to the print ticket — ICCERY_TEST_SPOOL_LOG records <trayKey>=<token> — and honoured by the driver
  • PPD-enriched labels display while raw tokens reach panel/spool
  • Default seeds from the queue's * default choice
  • Selected source pre-selects in the properties dialog and wins over a restored ticket
  • A source chosen inside the driver dialog applies back to the Stage 2 picker; PrintOptions.paperSource populated
  • Unknown non-empty captured source is preserved as a synthetic entry and re-emitted canonically
  • Tray changes select the correct precomputed Epson media→quality map and clamp stale quality picks
  • Orientation is shown as a dropdown (printerOrientationSelect); old button IDs removed; portrait/landscape string state and orientation-requested=3|4 unchanged
  • Unit tests: CupsParserTests, TicketWriteResolverTests, PrintSessionViewModelTests, PrintPanelTicketTests, PrintPanelStubTests, MediaQualityConstraintsTests, CupsOptionsFilterTests
  • UI tests: fixture exercises vendor tray key, spool log contains paper-source write, orientation dropdown selection

Dependencies

  • Blocked by: #217 — it restructures runNativePanel (ticket restore + validate) and both touch PrintPanelInitialSelections/TicketWriteResolver.
  • Related: #183 (Stage 2 pickers), #186 (dialog apply-back), #201 (native spool ticket), #214 (media→quality constraints — tray-LHS interaction now in scope).

References

  • Sources/ICCery/Stage2View.swift:299-307 — hidden printerTraySelect picker
  • Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsService.swift:139-144 — InputSlot/MediaSource → trays
  • Packages/ICCeryCore/Sources/ICCeryCore/Print/PrinterModels.swift:50-60,171 — PrinterTray, PrintOptions.paperSource
  • Sources/ICCery/Print/TicketWriteResolver.swift:37-54 — locked write order
  • Sources/ICCery/Print/PrintPanelService.swift:214-244 — initial-selection writes
  • docs/24-issues-invariants.md:133 — vendor tray-key roster
  • docs/10-print-system.md — paper_source semantics
  • docs/11-print-macos.md — native panel/spool path and resolver order
## Summary Stage 2 has a "Tray" picker (`printerTraySelect`, `Stage2View.swift:299-307`) populated from `printerCaps.trays`, but it never appears on real Epson/Canon queues and is never applied anywhere: - `CupsService.capabilities(from:ppd:)` builds `trays` only from `InputSlot`/`MediaSource` `lpoptions -l` listings (`CupsService.swift:139-144`). Epson InkjetPrinter2 exposes the feed source as `EPIJ_PSrc`/`EPIJ_FdSo`; Canon BJPrinter as `CNIJMediaSupply` (docs/24:133). Neither is enumerated → `trays` is empty → the picker is hidden. (Confirmed on XP-55 and Pro9500-class queues.) - `selectedTray` never reaches `PrintPanelInitialSelections`, `TargetPrintOverrides`/`TicketWriteResolver`, or `makeRequest` (PrintSessionViewModel.swift:416-433). Captured `InputSlot=` in the panel result is never mapped back to the picker, and `PrintOptions.paperSource` (`PrinterModels.swift:171`) is never populated. - The picker has been inert since the M3 live-print-panel commit (issue 17); v1 documented unix `paper_source` as ignored (docs/10 §paper_source). - The Portrait/Landscape button pair is also inconsistent with the other dropdown-style printer controls and should become an Orientation picker as part of this UI pass. Users must open the driver dialog to choose tray/cassette/manual feed — currently the only place it can be set at all. ## Implementation plan **Capabilities / parsing (ICCeryCore)** 1. `CupsParsers`: add `trayKeys` roster + `detectTrayKey(optionKeys:)`. Candidates in preference order: `InputSlot`, `MediaSource`, `EPIJ_FdSo`, `EPIJ_PSrc`, `CNIJMediaSupply`. `EPIJ_FdSo` is preferred over `EPIJ_PSrc` because recorded XP-55 listings label `EPIJ_FdSo` as `Paper Source` and `EPIJ_PSrc` as `Page Setup`; `EPIJ_FdSo` is the actual feed-source control. `AP_D_InputSlot` is a capture-side alias, not an enumeration key. 2. `CupsService.capabilities(from:ppd:)`: enumerate trays from the detected roster key (replace the hard-coded `InputSlot`/`MediaSource` last-wins match); record the producing key on a new `PrinterCapabilities.trayKey` (mirrors `qualityKey`, #183). Where a queue exposes more than one roster key, enumerate the preferred one and record it. 3. `PrinterTray`: add a `token` field for the machine value written to the ticket; keep the 1-based index `id`; `name` becomes the PPD-enriched label (like media/quality). 4. Seed `selectedTray` from the listing's `*` `defaultChoice` instead of `trays.first` (`reloadSelectedCapabilities`, PrintSessionViewModel.swift:131-133). 5. `MediaQualityConstraints.resolve`: accept an optional tray override so Epson `*EPIJUIConstraint` LHS terms (`EPIJ_PSrc`, `EPIJ_FdSo`) are evaluated against the selected feed source rather than the queue's lpoptions default. Precompute one media→quality map per enumerated tray token during capability loading; no async reload on tray switch. **Write path (Stage 2 → ticket → driver)** 6. `PrintSessionViewModel.selectedTrayToken: String?` — id→token lookup (mirrors `selectedPaperSizeToken`, :217-221). 7. `PrintPanelInitialSelections` gains `trayKey`/`tray`; `applyInitialSelections` writes `PMPrintSettingsSetValue(trayKey, token)` immediately after `PageSize`. 8. `TargetPrintOverrides` gains `trayKey`/`tray`; `TicketWriteResolver` adds a numbered write `<trayKey>=<token>` placed after `PageSize` and before media; update the locked-order contract comment and `testLockedWriteOrder`. 9. `makeRequest` passes the tray override. `NativeTargetSpooler.mirror` needs no change — it replays every resolved write into `NSPrintInfo.printSettings`. **Capture-back (driver → Stage 2)** 10. Extract the tray token from the captured options string using the detected tray key, the full tray roster, and `AP_D_InputSlot`. Map token → `PrinterTray.id`; set `selectedTray` and populate `PrintOptions.paperSource` (Int, 1-based — docs/10 semantics). If the token is non-empty but unknown, append a synthetic `PrinterTray` entry (id = current max + 1, token/name = raw token) and select it. 11. `UITestHooks.printPanelResult` mirrors the new field so UI tests can drive it (`AppEnvironment.swift:136-165`). **UI** 12. Picker shows "Paper source" as its label when populated; keep the `printerTraySelect` a11y id so existing UI tests don't churn. 13. Replace the Portrait/Landscape button pair with a `Picker("Orientation", selection: $workflow.print.printOrientation)` dropdown using `printerOrientationSelect`; keep `portrait`/`landscape` values and downstream `orientation-requested=3|4` behavior unchanged. **Considerations / non-goals** - Vendor companion keys: if a single `trayKey` write doesn't move the driver PDE on hardware, note it — do not guess key pairs. - `MediaSource` is also in some media rosters (docs/11:410); tray detection must not regress media-key detection — assert precedence in tests. - Cross-launch persistence of driver state is out of scope; tickets and captured options remain session-only. ## Acceptance criteria - [ ] Tray/paper-source picker appears on Epson and Canon queues (vendor keys enumerated) - [ ] `EPIJ_FdSo` is preferred over `EPIJ_PSrc` on Epson queues - [ ] Selection is written to the print ticket — `ICCERY_TEST_SPOOL_LOG` records `<trayKey>=<token>` — and honoured by the driver - [ ] PPD-enriched labels display while raw tokens reach panel/spool - [ ] Default seeds from the queue's `*` default choice - [ ] Selected source pre-selects in the properties dialog and wins over a restored ticket - [ ] A source chosen inside the driver dialog applies back to the Stage 2 picker; `PrintOptions.paperSource` populated - [ ] Unknown non-empty captured source is preserved as a synthetic entry and re-emitted canonically - [ ] Tray changes select the correct precomputed Epson media→quality map and clamp stale quality picks - [ ] Orientation is shown as a dropdown (`printerOrientationSelect`); old button IDs removed; `portrait`/`landscape` string state and `orientation-requested=3|4` unchanged - [ ] Unit tests: `CupsParserTests`, `TicketWriteResolverTests`, `PrintSessionViewModelTests`, `PrintPanelTicketTests`, `PrintPanelStubTests`, `MediaQualityConstraintsTests`, `CupsOptionsFilterTests` - [ ] UI tests: fixture exercises vendor tray key, spool log contains paper-source write, orientation dropdown selection ## Dependencies - **Blocked by:** #217 — it restructures `runNativePanel` (ticket restore + validate) and both touch `PrintPanelInitialSelections`/`TicketWriteResolver`. - Related: #183 (Stage 2 pickers), #186 (dialog apply-back), #201 (native spool ticket), #214 (media→quality constraints — tray-LHS interaction now in scope). ## References - `Sources/ICCery/Stage2View.swift:299-307` — hidden `printerTraySelect` picker - `Packages/ICCeryCore/Sources/ICCeryCore/Print/CupsService.swift:139-144` — `InputSlot`/`MediaSource` → trays - `Packages/ICCeryCore/Sources/ICCeryCore/Print/PrinterModels.swift:50-60,171` — `PrinterTray`, `PrintOptions.paperSource` - `Sources/ICCery/Print/TicketWriteResolver.swift:37-54` — locked write order - `Sources/ICCery/Print/PrintPanelService.swift:214-244` — initial-selection writes - `docs/24-issues-invariants.md:133` — vendor tray-key roster - `docs/10-print-system.md` — `paper_source` semantics - `docs/11-print-macos.md` — native panel/spool path and resolver order
gronod added this to the M13 — Print ticket persistence & paper source milestone 2026-09-22 18:49:32 +01:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Reference: gronod/iccery-v2-mac#218