# PNGer Development & Technical Instructions ## Project Overview PNGer is a single-container web application for PNG editing and resizing, designed for deployment on Unraid with Gitea version control. It features a modern Svelte frontend and a high-performance Node.js/Sharp backend. ## Technical Architecture ### Architecture Overview PNGer uses a multi-layered architecture designed for responsiveness and efficiency: ```mermaid graph TD A[User Browser] --> B[Svelte Frontend] B --> C["Canvas API (live preview, client-side)"] B --> D["Express API (POST /api/transform)"] D --> E[Sharp Image Library] E --> F[Response buffer streamed back as a download] ``` Nothing is persisted at any point: the upload lives in memory for the duration of the request and the result is streamed straight back. ### Backend (Express + Sharp) The backend is built with Node.js and TypeScript, using Express for the API and Sharp for high-performance image processing. **Endpoints** — there is exactly one: - `POST /api/transform` — `multipart/form-data`, field `file`, plus optional `width`, `height`, `quality` (default 80), `format` (`png` | `webp` | `jpeg`, default `png`), `fit` (default `inside`) and `position` (default `center`). Returns the image bytes with `Content-Disposition: attachment`; `400` if no file, `500` if Sharp throws. > **There is no `GET /api/health`.** Earlier revisions of this document claimed one. The router defines only `POST /transform`, so a GET to `/api/health` falls through to the SPA catch-all and returns `index.html` with a 200 — which means a monitor pointed at it will always report healthy. The container's own `HEALTHCHECK` correctly probes `/` instead. **Key Dependencies:** - `sharp`: High-performance image processing (handles resizing, cropping, and format conversion). Resizes use `withoutEnlargement: true`, so output never exceeds the source dimensions. - `multer`: Middleware for handling `multipart/form-data`, used for file uploads. Configured with `memoryStorage()` and **no `limits`** — see the dead-code note below. - `express`: Web framework for the API; also serves the built frontend from `dist/public` with an `app.get("*")` SPA fallback. - `cors`: Applied wide open (`app.use(cors())`) so the Vite dev server on `:5173` can reach the API on `:3000`. In production both are the same origin. ### Dead code you will trip over - **`backend/src/server.js`** is not the server. The entrypoint is `src/index.ts` → `dist/index.js`. `server.js` is an older standalone CommonJS implementation with `/api/health`, `/api/process` and `/api/metadata`, `helmet`, a PNG-only file filter and a real `MAX_FILE_SIZE` limit. `tsconfig.json` has no `allowJs`, so `tsc` never compiles it and it never reaches `dist/`; it also `require`s `helmet` and `dotenv`, which are not in `package.json`. It is the source of most of the features these docs used to describe. Either port the good parts (size limit, MIME filter, real health endpoint) into `routes/image.ts` or delete it. - **`frontend/src/main.js` and `frontend/src/main.ts`** both exist. `index.html` loads `main.js`, and only `main.js` imports `./app.css` — "fixing" the script tag to point at `main.ts` would silently drop every style in the app. ### Frontend (Svelte + Vite) The frontend is a reactive Svelte application that prioritizes real-time feedback and UX. **Core Components & Modules:** - `App.svelte`: Main application container managing state and UI. - `lib/api.ts`: API client for backend communication. - `lib/preview.ts`: Client-side preview logic using the Canvas API for instant feedback. - `lib/theme.ts`: Dark/light theme management with persistent storage. - `lib/presets.ts`: Configuration for smart image presets. ### Design System (Dark/Light Modes) The UI uses a modern design system with CSS custom properties for theming: - **Light Mode**: Clean white background with dark gold (`#b8860b`) accents. - **Dark Mode**: Deep black (`#0a0a0a`) background with light gold (`#daa520`) accents. - **Transparencies**: Dark/Light toggling allows users to inspect PNG transparency against different backgrounds. ## Implementation Details ### Live Preview System Live preview is implemented using a client-side Canvas-based approach to provide instant feedback (< 100ms) without server round-trips. **How it works:** 1. User uploads an image. 2. The browser loads the image into an HTML5 Canvas. 3. On any parameter change (width, quality, etc.), a debounced (300ms) update applies transformations to the canvas. 4. The canvas content is displayed side-by-side with the original for comparison. 5. File sizes are estimated from the Canvas data URL to provide immediate feedback on optimization savings. **Fidelity caveats — the preview is an approximation, not the output:** - The preview is encoded by the browser's Canvas encoder; the download is encoded by Sharp. Byte counts will differ. - Canvas ignores the quality argument for PNG (it is always lossless), so dragging quality changes nothing in a PNG preview while it does change the real output. - The preview's `calculateDimensions()` will happily scale an image **up**; the server sets `withoutEnlargement: true` and will not. The "Retina @2x" preset is exactly this case — the preview grows, the downloaded file does not. ### Docker Strategy & Fixes PNGer uses a multi-stage Docker build to minimize image size and maximize security. **Optimization Fixes applied:** - **Dependency Installation**: Uses `npm install` instead of `npm ci` to handle missing lockfiles gracefully while maintaining stability via caret ranges in `package.json`. - **Security**: Runs as a non-root `node` user in the final Alpine-based image. - **Multer Upgrade**: Upgraded to `v2.1.0` for improved security and performance. ## Local Development Setup ### Prerequisites - Node.js 20+ - npm or pnpm - Docker (optional) ### Setup Steps ```bash # Clone repository git clone https://git.alwisp.com/jason/pnger.git cd pnger # Setup backend cd backend npm install npm run dev # Setup frontend (separate terminal) cd frontend npm install npm run dev ``` ### Environment Variables Only `PORT` is actually read by the application. The full table, including which variables are declared-but-unused, is in [INSTALL.md](INSTALL.md#environment-variables). **Backend:** - `PORT`: 3000 — read by `src/index.ts`. - `MAX_FILE_SIZE`: declared in the `Dockerfile` and compose file, **not enforced** — the live `multer` config sets no `limits`. - `CORS_ORIGIN`: in `backend/.env.example`, **not read** — `app.use(cors())` takes no options. - `TEMP_DIR` / `/app/temp`: created by the `Dockerfile`, **never written to** — processing is entirely in memory. **Frontend:** - `VITE_API_URL`: **not read.** `lib/api.ts` picks its base URL from `import.meta.env.DEV` (`http://localhost:3000/api` in dev, `/api` in a build). ## Development Workflow & Standards ### Workflow 1. **Feature Branch**: Create from `main`. 2. **Develop**: Use hot-reload (`npm run dev`). 3. **Test**: Perform manual testing of image operations and presets. 4. **Commit**: Use `type: description` format (e.g., `feat: add rotation`). 5. **Merge**: Merge into `main` after review. ### Code Standards - **TypeScript**: Use strict types where possible. - **Svelte**: Keep components modular and under 300 lines. - **Async/Await**: Use for all asynchronous operations. - **Semantic HTML**: Ensure accessibility and proper structure. ## Troubleshooting More symptoms, including container-level ones, in [INSTALL.md](INSTALL.md#troubleshooting). - **Port in use**: `lsof -ti:3000 | xargs kill -9` - **Sharp issues**: `npm rebuild sharp` - **Docker Cache**: `docker builder prune` if builds fail unexpectedly. - **Preview Glitches**: Check browser console for Canvas API errors. --- **Last Updated**: July 2026 — reconciled against the code during the docs standardization pass. Deployment and configuration detail now lives in [INSTALL.md](INSTALL.md) and [UNRAID.md](UNRAID.md).