---
phase: 01-foundation
plan: 01
subsystem: infra
tags: [docker, fastapi, jinja2, htmx, pico-css, alpine-js, pytest, uvicorn]

# Dependency graph
requires: []
provides:
  - Running FastAPI app with GET /health and dashboard page
  - Docker scaffold with offline static asset baking (Pico CSS, HTMX, Alpine.js)
  - Sidebar navigation shell with 5 sections (Dashboard, Drivers, Printers, Clients, Packages)
  - Test scaffold with health and no-CDN-URL tests passing
affects: [01-02, 01-03, 02-drivers, 03-printers, 04-clients, 05-packages]

# Tech tracking
tech-stack:
  added: [fastapi==0.115.x, uvicorn[standard]==0.30.x, jinja2==3.1.x, python-multipart==0.0.9, pycryptodome==3.20.x, python-dotenv==1.0.x, peewee==3.17.x, pytest, httpx]
  patterns:
    - Sync def route handlers (FastAPI runs in thread pool — Peewee-compatible)
    - StaticFiles mount from pathlib.Path(__file__).parent / "static"
    - asynccontextmanager lifespan for startup hooks (not deprecated on_event)
    - TemplateResponse with request= kwarg for Starlette 0.40+ compatibility
    - Docker offline asset baking — curl in RUN layer, assets in /app/imptune/static/

key-files:
  created:
    - Dockerfile
    - docker-compose.yml
    - requirements.txt
    - requirements-dev.txt
    - imptune/__init__.py
    - imptune/main.py
    - imptune/config.py
    - imptune/api/__init__.py
    - imptune/api/health.py
    - imptune/api/pages.py
    - imptune/templates/base.html
    - imptune/templates/dashboard.html
    - imptune/static/app.css
    - tests/__init__.py
    - tests/conftest.py
    - tests/test_health.py
    - tests/test_static.py
  modified: []

key-decisions:
  - "Use asynccontextmanager lifespan instead of deprecated @app.on_event (FastAPI/Starlette best practice)"
  - "TemplateResponse uses request= keyword arg (not positional context dict) for Starlette 0.40+ compatibility"
  - "Static dir resolved via pathlib.Path(__file__).parent / static — works inside Docker and local dev"
  - "Sync def route handlers throughout — FastAPI auto-threads, compatible with Peewee ORM"

patterns-established:
  - "Pattern 1: All static asset references use /static/ paths — no CDN URLs anywhere in templates"
  - "Pattern 2: TemplateResponse(request=request, name=..., context={...}) — Starlette 0.40+ signature"
  - "Pattern 3: config.py loads from env with sensible defaults; all paths derived from DATA_DIR"
  - "Pattern 4: TestClient fixture in conftest.py with monkeypatched tmp_data_dir for isolation"

requirements-completed: [INFRA-01, INFRA-02]

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

# Phase 1, Plan 01: Docker Scaffold and App Shell Summary

**FastAPI app with Pico CSS sidebar shell, offline-baked static assets (HTMX, Alpine.js), GET /health, and 3-test green suite — all in a single python:3.12-slim-bookworm container**

## Performance

- **Duration:** 3 min
- **Started:** 2026-04-10T09:23:15Z
- **Completed:** 2026-04-10T09:26:30Z
- **Tasks:** 2
- **Files modified:** 17

## Accomplishments

- Docker scaffold with python:3.12-slim-bookworm base; curl downloads Pico CSS v2, HTMX 2.x, Alpine.js 3.x at build time and purges curl — zero CDN at runtime
- FastAPI app with asynccontextmanager lifespan, StaticFiles mount, health router, and dashboard page router
- Sidebar layout template (`base.html`) with `data-theme="auto"` for OS dark/light preference and 5 flat equal-weight nav sections
- Test suite: 3 passing tests covering health endpoint, no-CDN-URLs scan, and dashboard 200 response

## Task Commits

1. **Task 1: Docker scaffold, FastAPI app shell, templates** - `bd4e132` (feat)
2. **Task 2: Test scaffold, health and static tests** - `34c7cb3` (feat)

## Files Created/Modified

- `Dockerfile` — python:3.12-slim-bookworm, curl-baked static assets, stdlib healthcheck, uvicorn CMD
- `docker-compose.yml` — imptune_data:/data volume, DATA_DIR env, restart unless-stopped
- `requirements.txt` — all phase 1-5 deps (fastapi, uvicorn, jinja2, peewee, pycryptodome, etc.)
- `requirements-dev.txt` — pytest, httpx
- `imptune/main.py` — FastAPI app with lifespan, StaticFiles, router registration
- `imptune/config.py` — DATA_DIR/PORT env loading, DB_PATH/DRIVERS_DIR derivation
- `imptune/api/health.py` — GET /health → {"status": "ok"}
- `imptune/api/pages.py` — GET / → dashboard.html (sync def, new TemplateResponse signature)
- `imptune/templates/base.html` — data-theme="auto", /static/ assets only, sidebar nav
- `imptune/templates/dashboard.html` — quick actions + empty state recent activity
- `imptune/static/app.css` — sidebar flex layout, active link highlight, quick action styling
- `tests/conftest.py` — client and tmp_data_dir fixtures
- `tests/test_health.py` — health endpoint 200 test
- `tests/test_static.py` — no-CDN-URL scan + dashboard 200 test

## Decisions Made

- Used `asynccontextmanager lifespan` instead of deprecated `@app.on_event("startup")` — avoids DeprecationWarning on FastAPI 0.115+ / Python 3.13
- Used `TemplateResponse(request=request, name=..., context={...})` signature — the old positional dict form triggers a `TypeError: unhashable type: 'dict'` on Starlette 0.40+ due to LRUCache key behavior
- Static directory resolved from `pathlib.Path(__file__).parent / "static"` — works in Docker and local dev without hardcoded paths

## Deviations from Plan

### Auto-fixed Issues

**1. [Rule 1 - Bug] Fixed Starlette TemplateResponse signature incompatibility**
- **Found during:** Task 2 (test_dashboard_returns_200 failed)
- **Issue:** `templates.TemplateResponse("dashboard.html", {"request": request, ...})` raises `TypeError: unhashable type: 'dict'` on Starlette 0.40+ — context dict used as LRUCache key
- **Fix:** Changed to `templates.TemplateResponse(request=request, name="dashboard.html", context={...})`
- **Files modified:** `imptune/api/pages.py`
- **Verification:** test_dashboard_returns_200 passes
- **Committed in:** `34c7cb3` (Task 2 commit)

**2. [Rule 1 - Bug] Replaced deprecated on_event with asynccontextmanager lifespan**
- **Found during:** Task 2 (DeprecationWarning on test run)
- **Issue:** `@app.on_event("startup")` is deprecated in FastAPI 0.95+ / Starlette 0.37+; triggers warning on every test run
- **Fix:** Replaced with `@asynccontextmanager async def lifespan(app)` passed to `FastAPI(lifespan=lifespan)`
- **Files modified:** `imptune/main.py`
- **Verification:** Tests pass with zero warnings
- **Committed in:** `34c7cb3` (Task 2 commit)

---

**Total deviations:** 2 auto-fixed (both Rule 1 - Bug)
**Impact on plan:** Both fixes required for compatibility with installed library versions. No scope creep.

## Issues Encountered

- Starlette's `TemplateResponse` API changed in 0.40.0 — old positional-dict form breaks silently until test run. Fixed inline.

## Next Phase Readiness

- App shell and health endpoint ready — next plan (01-02) can build the SQLite schema and Peewee models on this foundation
- Docker image can be built once assets are downloaded; local dev works without Docker via `python3 -m pytest` and direct uvicorn run
- No blockers for 01-02

---
*Phase: 01-foundation*
*Completed: 2026-04-10*
