Icon uploaded successfully
")` (HTMX-friendly) + + 5. Register router in `imptune/main.py`: + - Add `from imptune.api import icons` to imports + - Add `app.include_router(icons.router)` after packages router + + 6. Also create `ICONS_DIR` in lifespan startup (same as DRIVERS_DIR pattern): + - Add `os.makedirs(cfg.ICONS_DIR, exist_ok=True)` in lifespan -- but use dynamic `cfg.ICONS_DIR` to avoid import-time evaluation. Actually, follow the existing pattern: import ICONS_DIR from config at top of main.py and makedirs in lifespan. But note: the test monkepatches cfg module, so use `import imptune.config as cfg` in lifespan OR just use the string directly. The simplest correct pattern: add `from imptune.config import ICONS_DIR` alongside the existing imports and `os.makedirs(ICONS_DIR, exist_ok=True)` in lifespan. This works because the lifespan runs AFTER monkeypatch has been applied in tests (TestClient context manager triggers lifespan). + + Wait -- looking at the existing code more carefully: main.py imports `DATA_DIR, DRIVERS_DIR` at top level and uses them directly in lifespan. This works for tests because conftest patches `cfg.DRIVERS_DIR` before TestClient enters context. But the top-level import captures the original value. Let me check... Actually the conftest patches the cfg module attributes, but main.py imported the values at module load time. The lifespan still uses the stale import-time values. This is fine because the tests use `tmp_data_dir` which patches cfg, and the test client triggers lifespan which uses the already-imported constants -- wait, this is a potential issue. + + Actually: looking at the conftest, it patches `cfg.DATA_DIR`, `cfg.DB_PATH`, `cfg.DRIVERS_DIR` on the module. The main.py does `from imptune.config import DATA_DIR, DRIVERS_DIR` which binds to the original values. BUT init_db() calls `db.init(cfg.DB_PATH)` dynamically, and the lifespan makedirs uses the imported constant. Since tests have their own tmp_data_dir and the test client is created AFTER monkeypatch, the lifespan runs with stale DATA_DIR/DRIVERS_DIR values. But this seems to work because the test fixtures create those dirs themselves via `data_dir.mkdir()`. + + Simplest approach: Add ICONS_DIR to the import in main.py alongside the others. The conftest already creates `data_dir` and the tests will create `data_dir / "icons"` as needed. In the icons.py endpoint, use `import imptune.config as cfg` and read `cfg.DATA_DIR` at call time (consistent with RESEARCH anti-pattern guidance). + + 7. Run tests GREEN. +` blocks. Add Alpine.js copy button for each:
+ ```html
+
+
+ {{ install_cmd }}
+
+
+ ```
+ Repeat for uninstall command with id="uninstall-cmd". Only show this section if `has_driver` is true.
+
+ **Export Downloads section**:
+ Show download buttons only when `has_driver` is true:
+ ```html
+ Export
+ Download NinjaRMM ZIP
+ Download .intunewin
+ ```
+
+ **Icon Upload section** (PKG-04):
+ ```html
+ Icon
+ {% if has_icon %}
+ Icon uploaded
+ {% endif %}
+
+
+ ```
+
+ Replace the old disabled "Regenerate Package" button with the real export buttons.
+
+ 3. Add a test in `tests/test_packages.py` class `TestCommandPreview`:
+ - test_detail_page_shows_commands: GET /printers/{id} returns HTML containing "install-cmd" and "uninstall-cmd" ids and the command strings
+ - test_detail_page_shows_export_links: GET /printers/{id} returns HTML containing "/packages/ninja" and "/packages/intunewin" hrefs
+
+ These are simple integration tests using the `client` fixture -- create a Printer+Driver, GET the detail page, assert the command text and download links appear in the response HTML.
+
+ 4. Run all tests green.
+