---
phase: 05-package-export
plan: 02
subsystem: ui
tags: [fastapi, pillow, htmx, alpine.js, icon-upload, png-validation, printer-detail]

# Dependency graph
requires:
  - phase: 05-01
    provides: NinjaRMM ZIP and .intunewin package export endpoints
  - phase: 04-02
    provides: script generation endpoints (install/uninstall/detect)
  - phase: 03-02
    provides: printer detail page foundation in pages.py
provides:
  - Icon upload endpoint with PNG format/dimension/size validation
  - SHA256-addressed icon storage under DATA_DIR/icons/
  - Icon ORM record tracking (one per printer, replace-on-upload)
  - Printer detail page with Intune Commands section (install/uninstall command strings with copy buttons)
  - Printer detail page with Export section (NinjaRMM ZIP and .intunewin download links)
  - Printer detail page with Icon Upload form (HTMX submission)
affects: [deployment, ui, export]

# Tech tracking
tech-stack:
  added: [Pillow>=10.0 (PNG dimension/format validation)]
  patterns:
    - "Read cfg.DATA_DIR at call time (not import time) for monkeypatch compatibility"
    - "SHA256-addressed icon storage — dedup automatic, filename = sha256 hash"
    - "Icon replace pattern: delete existing record then create new (unique FK)"
    - "Alpine.js copy-to-clipboard with copied state and 2-second timeout"
    - "HTMX icon upload form with #icon-status swap target"

key-files:
  created:
    - imptune/api/icons.py
    - tests/test_icon_upload.py
  modified:
    - imptune/config.py
    - imptune/main.py
    - imptune/api/pages.py
    - imptune/templates/printer_detail.html
    - tests/conftest.py
    - tests/test_packages.py
    - requirements.txt

key-decisions:
  - "Pillow used for PNG validation — provides format, dimension, and byte-read in one library"
  - "Icons stored SHA256-addressed (not by printer ID) — enables dedup if same PNG used for multiple printers"
  - "Read cfg.DATA_DIR dynamically in icons.py endpoint, not at import time — consistent with monkeypatch pattern established in Phase 02"
  - "Icon replace via delete-then-create rather than get_or_create — unique FK makes upsert awkward, simpler to delete first"
  - "Export and command sections conditionally shown only when has_driver is true — avoids confusing 422 before driver is assigned"

patterns-established:
  - "Icon upload: read MAX+1 bytes, check len > MAX for oversized detection"
  - "Printer detail page sections gated on has_driver boolean from view context"

requirements-completed: [PKG-04, PKG-05]

# Metrics
duration: 15min
completed: 2026-04-10
---

# Phase 05 Plan 02: Icon Upload and Printer Detail Export UI Summary

**PNG icon upload endpoint with 750KB/256x256/format validation, SHA256 storage, and printer detail page with Intune command preview and download links**

## Performance

- **Duration:** ~15 min
- **Started:** 2026-04-10T12:00:00Z
- **Completed:** 2026-04-10T12:15:00Z
- **Tasks:** 2
- **Files modified:** 9

## Accomplishments
- Icon upload endpoint (POST /printers/{id}/icon) with full validation: PNG format, 256x256 dimensions, 750KB max, 404 on missing printer
- Re-upload replaces previous Icon ORM record (unique FK constraint handled via delete-then-create)
- Printer detail page rewritten with three new sections: Intune Commands (install/uninstall with Alpine.js copy-to-clipboard), Export (NinjaRMM ZIP and .intunewin download links), Icon (HTMX upload form)
- Full test suite green: 94 tests pass

## Task Commits

Each task was committed atomically:

1. **Task 1 RED: Icon upload tests** - `d8ce223` (test)
2. **Task 1 GREEN: Icon upload implementation** - `f9e13ba` (feat)
3. **Task 2: Printer detail page with export UI** - `f96ea6f` (feat)

## Files Created/Modified
- `imptune/api/icons.py` - Icon upload endpoint with PNG format/dimension/size validation
- `imptune/config.py` - Added ICONS_DIR constant
- `imptune/main.py` - Registered icons.router, added ICONS_DIR makedirs in lifespan
- `imptune/api/pages.py` - Updated printer_detail() with install_cmd, uninstall_cmd, has_driver, has_icon context
- `imptune/templates/printer_detail.html` - Added Intune Commands, Export, Icon Upload sections; removed placeholder button
- `tests/test_icon_upload.py` - 6 TDD integration tests for icon upload validation
- `tests/test_packages.py` - Added TestCommandPreview class (4 tests)
- `tests/conftest.py` - Patched cfg.ICONS_DIR in tmp_data_dir fixture
- `requirements.txt` - Added Pillow>=10.0

## Decisions Made
- Used Pillow for PNG validation — single library handles format detection, dimension check, and byte reading in one pass
- Icons stored SHA256-addressed under DATA_DIR/icons/ — consistent with DRIVERS_DIR content-addressing pattern from Phase 02
- cfg.DATA_DIR read at call time in icons.py endpoint — consistent with monkeypatch pattern established in Phase 02 for DRIVERS_DIR
- Export and command sections conditionally shown only when has_driver is true — prevents confusing broken download links before driver is assigned

## Deviations from Plan

None - plan executed exactly as written.

## Issues Encountered
- Pillow was not yet installed in the environment (requirements.txt addition needed `python -m pip install` before tests could run). Resolved automatically.

## User Setup Required
None - no external service configuration required.

## Next Phase Readiness
- Icon upload and command preview complete — export UI is fully functional
- Phase 05 is the final phase; all requirements PKG-01 through PKG-05 are now implemented
- Remaining validation: byte-level .intunewin format compliance against real Intune tenant (noted as MEDIUM confidence concern)

---
*Phase: 05-package-export*
*Completed: 2026-04-10*
