Target: https://henrymakhermes.zeabur.app/qr/ (proxy) + http://127.0.0.1:5052 (direct) Date: 2026-07-12 Scope: Full service review, focus on upload → store failure Tester: Ming / Hermes (code + API repro; browser tool unavailable on host)
Overall Assessment: Direct port 5052 can save images; public path via Ming's Note /qr/ is effectively read-only/broken for upload-store because frontend hits wrong API base path and proxy only allows GET.
When using the normal entry https://henrymakhermes.zeabur.app/qr/:
public/app.js hardcodes const API = '/api'https://henrymakhermes.zeabur.app/api/... → 404 (not under /qr/)/qr/api/..., md-reader.py proxy only implements GET → POST/PUT/DELETE return 405/uploads/... also resolve outside /qr/ → thumbs 404 on public entryVerified:
| Request | Result |
|---|---|
| GET /api/qr | 404 |
| GET /qr/api/qr | 200 |
| POST /api/qr | 404 |
| POST /qr/api/qr | 405 |
| POST /qr/api/analyze | 405 |
| GET /uploads/ |
404 |
| GET /qr/uploads/ |
200 |
| POST http://127.0.0.1:5052/api/qr (direct) | 200 + DB row + file on disk |
Fact: direct Express save works. Public proxy path is the store blocker.
Description: Frontend always calls absolute /api/*. Under Ming's Note, that is not the QR app.
Steps:
1. Open /qr/
2. Upload image / click save
3. Observe network: requests go to /api/analyze and /api/qr
Expected: API under /qr/api/...
Actual: /api/... → 404; nothing stored
Evidence: const API = '/api' in public/app.js; curl GET/POST /api/qr on 9119 = 404
/qr/<path> proxy is GET-onlyDescription: md-reader.py qr_proxy always req.get(...). No methods list on route → Flask default GET. Upload/analyze/update/delete impossible through proxy.
Expected: Proxy forwards POST/PUT/DELETE + multipart body Actual: 405 Method Not Allowed
Evidence:
POST /qr/api/qr → 405
POST /qr/api/analyze → 405
PUT /qr/api/qr/1 → 405
DEL /qr/api/qr/1 → 405
Code: md-reader.py ~967-983
Description: DB stores image_path=/uploads/.... Under /qr/, browser requests /uploads/... (404). Correct path would be /qr/uploads/....
Evidence: list items use /uploads/...; curl /uploads/x=404, /qr/uploads/x=200
Description: Analyze UI shows vision_desc, but saveQr() never appends vision_desc to FormData. Unless user later hits detail Analyze, AI text is lost.
Code: public/app.js saveQr() only sends image/url/category/label/tags/notes
Description: Multer fileFilter only allows png/jpg/gif/webp/bmp/svg by extension. iPhone photos often .heic or extension-less. Error not caught by Express JSON error handler → HTML stack trace 500.
Evidence:
- filename=photo.heic → 500 HTML "Only image files are allowed"
- filename=photo → same
Description: renderHome() does new URL(item.decoded_url) without try/catch. WiFi/text/vCard QR content throws and blank-screens home.
/api/analyze deletes file then returns dead preview URLDescription: Server unlinks temp upload, still returns image_preview: /uploads/<name>. Preview file is already gone. Frontend currently uses FileReader for preview, so UI mostly ok; API contract is still wrong.
Description: If POST returns non-JSON/error without id, button stays disabled and no alert (only catch path alerts).
Description: Running as ad-hoc bash -lic ... node server.js. No package.json start script, no restart policy. Restart of container can lose the service while /qr/ proxy still exists.
Description: CATEGORIES hard-coded in both db.js and app.js. Drift risk.
Description: Flow uploads once for analyze (then deletes), uploads again for save. Works when direct, wasteful; under proxy both fail differently.
uploads//qr/<path> accept GET/POST/PUT/DELETE; forward method, headers, query, raw body/files; longer timeout for vision/qr prefix (e.g. const BASE = location.pathname.startsWith('/qr') ? '/qr' : '')vision_desc/qragent-browser Chrome missing)