diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md index 1f54424..53a2a0f 100644 --- a/.github/copilot-instructions.md +++ b/.github/copilot-instructions.md @@ -2,50 +2,163 @@ # Copilot Instructions for AI Coding Agents ## Project Overview -AutoKanban is a FastAPI-based web application for managing a physical kanban workflow, integrating with a 58mm Arduino thermal printer. The system allows users to submit tasks, admins to approve and print them, and cards to be pinned to a physical board. +AutoKanban is a FastAPI web app for managing a physical kanban workflow with a 58mm Arduino thermal printer. Users submit tasks via web UI, admins approve and print them to physical cards. Designed for Raspberry Pi deployment but tested on macOS. -## Major Components -- `app/main.py`: Main FastAPI app, task model, admin session, printer/image generation, static mounts. -- `app/models.py`: Pydantic Task model. -- `app/printer.py`: (Optional) Printer abstraction for cross-platform serial printing. -- `app/templates/`: Jinja2 HTML templates for UI. -- `app/static/`: Static files (fonts, CSS, images, Font Awesome OTFs). -- `data/tasks.json`: Persistent task storage (JSON). -- `.env`: Secrets (admin password, etc). Not committed. -- `.gitignore`: Ignores `.env`, `out/`, and other sensitive/generated files. -- `out/`: Stores generated card preview images (debug mode). +## Architecture & Data Flow +``` +User submits task → Task (pending) → Admin approves → Task (approved) → Admin prints → Task (printed) → Physical kanban card +``` -## Key Features & Workflows -- Users submit tasks via web UI. -- Admins approve tasks and print them to a 58mm ESC/POS thermal printer. -- Card preview images are generated using Pillow (debug mode), with custom and Font Awesome fonts. -- Semantic icon rendering: Task keywords map to Font Awesome icons. -- Admin authentication via password (from `.env`). -- Persistent storage in `data/tasks.json`. -- German localization in UI and card output. +### Core Components +- **`app/main.py`** (270 lines): Monolithic FastAPI app containing all routes, business logic, session management, and card rendering. All state managed in-memory with JSON persistence. +- **`app/models.py`**: Single Pydantic model `Task` with factory method `Task.create()` that generates UUID. +- **`app/printer.py`**: Minimal printer wrapper (currently unused in main.py - printer logic is duplicated in `/print` route). +- **`app/templates/`**: Jinja2 HTML templates with German localization and inline CSS/JS. +- **`app/static/fonts/`**: Custom fonts (BauPro, HealTheWeb) + Font Awesome 7.1.0 OTFs for semantic icons. -## Build, Test, and Debug -- Install dependencies: `pip install -r requirements.txt` -- Run app: `uvicorn app.main:app --reload` -- For card preview (debug): Set `DEBUG_PRINT_TO_IMAGE = True` in `app/main.py`. -- Fonts: Place custom and Font Awesome OTFs in `app/static/fonts/`. -- Set admin password in `.env` as `ADMIN_PASSWORD=yourpassword`. +### State Management Pattern +- **Persistence**: Simple JSON file (`data/tasks.json`) loaded into in-memory list on startup. Call `save_tasks()` after every mutation. +- **Sessions**: Starlette `SessionMiddleware` stores admin auth state and flash messages (`login_result`, `print_result`, `preview_image`) in cookies. +- **Admin auth**: Password-only (no username), checked against `.env` variable. Session flag `admin: True/False`. + +### Dual-Mode Printing Architecture +**Key insight**: App has two print modes controlled by `DEBUG_PRINT_TO_IMAGE` flag in `main.py`: +- `True` (default for testing): Generates PNG previews using Pillow with custom fonts, semantic icons, and card layout to `out/` directory. +- `False` (production): Prints to ESC/POS thermal printer via serial (`/dev/ttyUSB0`, 19200 baud). + +**Why this matters**: When modifying print logic, update BOTH branches in the `/print/{task_id}` route (lines 142-244). + +### Semantic Icon System +`KEYWORD_ICONS` list in `main.py` maps German/English keywords to Font Awesome unicode codepoints: +- Pattern: `(['keyword1', 'keyword2'], '\uf0f4')` +- Matching: Case-insensitive substring search in task content +- Default icon: `\uf328` (fa-sticky-note) +- Add new mappings by extending this list - requires knowing FA unicode values. + +## Developer Workflows + +### Running the App +**Preferred method** (handles venv automatically): +```bash +python start_app.py # Launches uvicorn on 0.0.0.0:8000 with --reload +``` + +**Manual method**: +```bash +source .venv/bin/activate +uvicorn app.main:app --reload +``` + +### First-Time Setup +1. Create `.env` file with `ADMIN_PASSWORD=yourpassword` +2. Ensure `data/` directory exists (create if missing - .gitignored) +3. Install system fonts or update font paths in `main.py` (lines 29-31) +4. For real printing: Set `DEBUG_PRINT_TO_IMAGE = False` and configure serial device + +### Testing Print Without Hardware +1. Keep `DEBUG_PRINT_TO_IMAGE = True` +2. Submit task → approve → print +3. Check `out/task_{uuid}.png` for rendered card +4. Preview images shown in web UI after printing + +### Debugging Font Issues +Font loading uses fallback chain (lines 129-135): +```python +load_font(path, size, fallback=None, font_label="descriptive_name") +``` +- Logs errors but gracefully falls back to default font +- Font errors appended to `font_error_msgs[]` and rendered on card preview ## Project Conventions -- Use environment variables for secrets (never hardcode passwords). -- Use Pillow's robust font loading with error handling for card previews. -- All static assets (fonts, images) go in `app/static/`. -- All persistent data in `data/`. -- All generated output in `out/` (gitignored). + +### Code Organization +- **Monolithic by design**: All logic in `main.py` for simplicity. Don't split unless file exceeds 500 lines. +- **No database**: JSON file persistence is intentional. Don't add SQLite/Postgres without discussion. +- **German-first**: UI text, card labels, and comments use German. Keep this convention. + +### Security Patterns +- **Never commit**: `.env`, `data/tasks.json`, `out/*.png` (all in `.gitignore`) +- **Session secret**: Hardcoded as `"CHANGE_THIS_SECRET"` (line 18) - should be env var in production +- **Admin password**: Must be set in `.env` or app will crash on login attempt + +### Font Asset Management +- Custom fonts in `app/static/fonts/` (committed to repo) +- Font Awesome 7.1.0 desktop OTFs (committed) - use Solid 900 weight for card icons +- Paths defined as constants at top of `main.py` - update these if fonts move +- Required fonts: `FONT_BOLD`, `FONT_REGULAR`, `FA_FONT` + +### Task Status Lifecycle +Tasks have exactly 3 states: `pending` → `approved` → `printed` +- Users can only submit (creates `pending`) +- Admins can approve (`pending` → `approved`) +- Anyone can print `approved` tasks +- Admins can re-print `printed` tasks ## Integration Points -- Thermal printer: ESC/POS via `python-escpos` (serial connection, `/dev/ttyUSB0` by default). -- Font Awesome: Use OTF from `app/static/fonts/fontawesome-free-7.1.0-desktop/otfs/`. -## How to Contribute -- Update this file as new features, files, or conventions are added. -- Reference key files and directories. -- Keep instructions concise and actionable for future AI agents and developers. +### Thermal Printer (ESC/POS) +- Library: `python-escpos` v3.1 +- Connection: Serial over USB (`/dev/ttyUSB0` on Linux, `/dev/tty.usbserial-*` on macOS) +- Baudrate: 19200 (standard for Arduino thermal printers) +- See `WIRING.md` for Raspberry Pi GPIO wiring diagram +- Permissions: User must be in `dialout` group on Linux + +### Font Awesome Integration +- Version: 7.1.0 Free (Solid weight) +- Format: OTF desktop fonts (not web fonts) +- Icon mapping: Manual unicode lookup required for new icons +- Path: `app/static/fonts/fontawesome-free-7.1.0-desktop/otfs/Font Awesome 7 Free-Solid-900.otf` + +## Critical Code Sections + +### Card Rendering Logic (lines 142-239) +Complex Pillow-based layout with: +- Dynamic text wrapping based on pixel width (not character count) +- Vertical centering calculation using `getbbox()` for text height +- Icon positioning in lower-right corner +- Border and padding calculations for 354×236px card + +**Common pitfalls**: +- Font methods (`getlength()`, `getbbox()`) may fail if font is None - wrap in try/except +- Text wrapping happens twice: word-based via `textwrap`, then pixel-based +- Changing card dimensions requires recalculating all spacing + +### Task Persistence (lines 65-70) +Simple load/save pattern - no locking or concurrency control: +```python +tasks: List[Task] = load_tasks() # On startup +save_tasks() # After every mutation +``` +**Risk**: Race conditions if multiple requests mutate simultaneously. Not handled - single-user/low-traffic assumption. + +## Platform-Specific Notes + +### macOS Development +- Printer device likely `/dev/tty.usbserial-*` (check `ls /dev/tty.*`) +- Use `DEBUG_PRINT_TO_IMAGE = True` for testing without hardware +- Font paths may need adjustment (DejaVu fonts not standard on macOS) + +### Raspberry Pi Deployment +- Enable UART/serial interface via `raspi-config` +- Add user to `dialout` group: `sudo usermod -a -G dialout $USER` +- Default device `/dev/ttyUSB0` should work for USB-serial printers +- See `WIRING.md` for GPIO hardware serial wiring (pins 8/10) + +## When Making Changes + +### Adding New Routes +- Follow existing pattern: FastAPI route → mutate `tasks` list → call `save_tasks()` → redirect with session flash message +- Admin-only routes: Check `request.session.get("admin")` first + +### Modifying Card Layout +- Update constants: `CARD_WIDTH_PX`, `CARD_HEIGHT_PX` (lines 25-26) +- Test with `DEBUG_PRINT_TO_IMAGE = True` before printing to hardware +- Remember to update BOTH debug and production print branches + +### Adding Icon Keywords +- Extend `KEYWORD_ICONS` list (lines 33-58) +- Use Font Awesome unicode lookup: https://fontawesome.com/v7/icons +- Format: `(['keyword'], '\ufXXX')` where XXX is FA codepoint --- -_Last updated: 2025-10-20_ +_Last updated: 2025-11-20_ diff --git a/app/main.py b/app/main.py index f58948a..c2f416d 100644 --- a/app/main.py +++ b/app/main.py @@ -66,6 +66,7 @@ def load_tasks(): return [] def save_tasks(): + TASKS_FILE.parent.mkdir(parents=True, exist_ok=True) # Ensure data/ directory exists with open(TASKS_FILE, "w", encoding="utf-8") as f: json.dump([t.dict() for t in tasks], f, ensure_ascii=False, indent=2) diff --git a/requirements.txt b/requirements.txt index d5fa905..6def8da 100644 --- a/requirements.txt +++ b/requirements.txt @@ -11,3 +11,4 @@ pyserial==3.5 python-barcode==0.16.1 pyyaml==6.0.3 qrcode==8.2 +itsdangerous==2.2.0