# Presto App Guidance

This directory contains MicroPython apps for the Pimoroni Presto. The main app is
`main.py`, deployed to the device as `main.py`.

## Context To Read First

- Read `README.md` before changing setup, deployment, user-facing behavior, or API response assumptions.
- Read `main.py` before changing layout, touch handling, networking, display sleep, or performance-sensitive code.
- The root project conventions still apply, but most Phoenix/Elixir conventions are not relevant inside `presto/`.

## Deployment And Verification

```bash
mise run presto                     # deploy and reset
```

Manual deployment:

```bash
mpremote fs cp main.py :main.py
mpremote fs cp secrets.py :secrets.py
mpremote reset
```

Syntax check (no `__pycache__`):

```bash
python3 -c "import py_compile; py_compile.compile('main.py', cfile='/tmp/main.pyc', doraise=True)"
```

Do not claim device behavior is verified unless it was tested on the physical Presto.

## Testing

The test harness lives in `presto/tests/` and uses the `pimoroni-emulator`'s
mock MicroPython modules for headless smoke testing.

- Run tests: `mise run test`
- Tests import `main.py` without starting the event loop (guarded by
  `PRESTO_TEST_MODE=1`).
- Smoke tests cover every screen state.  When adding a new screen, add a
  corresponding test in `presto/tests/test_screens.py`.
- All tests use mock data from `make_mock_record()`.  No network calls are
  made during test execution — `fetch_thumbnail` is monkey-patched to raise
  on any accidental network access.
- Screenshots are saved to `presto/tests/fixtures/` for manual visual
  inspection.

## Hardware And Runtime Constraints

- Stock Presto firmware: MicroPython, PicoGraphics, touch driver, `urequests`, `ntptime`, usually `jpegdec`.
- Treat as single-threaded. Do not assume `_thread` or user-accessible multicore.
- Keep memory pressure low: no large in-memory buffers, no aggressive response caching, no `gc.collect()` in hot render paths.
- Network calls (`urequests`) are blocking. Never make HTTP requests during drag scrolling or inside repeated redraw loops.

## State Management

- Every view state (`STATE_STARTUP`, `STATE_MONTH`, `STATE_DAY`, `STATE_RECORD`) must be handled in `redraw_current_view()` for correct display sleep/wake.
- When transitioning into a view, reset its scroll offset to 0 so it always starts at the top.
- When adding a new state, update `main()` globals, `redraw_current_view()`, the main loop dispatch, and any related state cleanup on navigation.

## Scroll Performance

_These rules apply to any scrollable view (day list, detail page, or future additions)._

- The scroll hot path must not: measure text, join strings, sanitize text, compute layout heights, or fetch images over the network.
- Pre-compute and cache all display strings and dimensions after data arrives and before the first draw.
- Throttle drag redraws by both time (`DRAG_REDRAW_MS`) and pixel delta (`DRAG_REDRAW_PX`). Preserve pending delta on touch release and redraw once.
- Each scrollable view gets its own offset variable and its own content-height cache. Never reuse one view's scroll state for another.
- If the view has a fixed header, use `display.set_clip()` / `display.remove_clip()` to prevent scrollable content from drawing over it.

## Partial Display Updates

- Use full display updates for full-screen transitions, state changes, and any redraw that changes the header, background, or multiple unrelated regions.
- Use the app's partial-update helper for bounded redraws; do not call `presto.partial_update()` directly. The helper must fall back to a full update when firmware or emulator support is missing.
- Before a partial update, redraw the complete changed rectangle in the framebuffer, including background clearing, borders, labels, and any pixels that may have been covered by the previous state.
- Partial update rectangles must fully contain the changed pixels and be expressed with named geometry constants, not scattered literals.
- For fixed-header scroll views, partial redraws should clear and redraw only the scroll viewport below the header, then update that viewport rectangle. Keep clipping active so scrolled content cannot overwrite the header.
- Do not make network requests, measure text, or rebuild layout inside partial redraw hot paths; prepare and cache that work before the first draw.
- Emulator tests may assert which partial-update rectangle is requested. Do not claim physical display behavior is verified unless tested on the physical Presto.

## Image Handling

- Row thumbnails use `covers.small` from the API. Detail covers use `covers.medium`.
- The API provides the row thumbnail size directly: `covers.small` is 80px. Detail covers use `covers.medium` in a `DETAIL_COVER_SIZE = px(200)` 400px layout area; do not expand it to fill the 480px display unless explicitly requested.
- Cache downloaded image bytes on the record dict. Use separate cache keys for different sizes (e.g., `_thumb_data` for rows, `_detail_thumb_data` for detail view). Never fetch images during drag — show placeholders instead and repaint real covers on release.
- JPEG is the practical default. PNG has worse memory characteristics; raw/RGB565 needs a confirmed stable blit API on the target firmware.

## Text Rendering

- All user-visible text shown with bitmap fonts must pass through `display_text()` to replace unsupported punctuation (smart quotes, dashes, ellipsis). Do not strip diacritics — the font can render them.
- `release_date` is displayed as year only (first 4 characters).

## Layout

- Use named constants for all pixel geometry. No scattered literals.
- Keep header geometry consistent across views: same height, button dimensions, and side margins.

## Display Sleep

- Sleep by setting backlight to `0.0`. WiFi stays enabled.
- The first touch after sleep must wake the backlight and be consumed — it must not also activate a button or trigger a scroll.
- On wake, `set_today()` refreshes the global date from the system clock, WiFi is reconnected if it dropped, and the current view is always redrawn so the today-highlight stays current across midnight.

## API Contract

```
GET https://music-library.claudio-ortolina.org/api/v1/collection/on_this_day?date=YYYY-MM-DD
Authorization: Bearer <API_TOKEN>
```

Fields used by the app: `title`, `artists`, `format`, `release_date`, `genres`, `record_type`, `purchased_at`, `covers.small`, `covers.medium`, `selected_release_id`.

POST /api/v1/collection/:record_id/scrobble
Authorization: Bearer <API_TOKEN>

When adding or changing API assumptions, update `README.md`.
