Feature #120: image paste/drop attachments #129

Merged
crueber merged 2 commits from feat/issue-120 into main 2026-09-05 07:37:24 +00:00
Owner

Implements Forgejo issue #120 per the plan + R1 + review (R1 normative on conflict).

What: paste (Ctrl/Cmd+V) or drop an image onto the issue-create textarea or the thread comment composer uploads it and inserts ![name](url) at the cursor.

Backend (internal/issues, §12): POST /{o}/{r}/api/attachments (authenticated+read, LimitReader 8 MiB cap to 413 with no Content-Length requirement per B1, spool+hash, client sha optional-when-present per S4, magic-byte allowlist PNG/JPEG/GIF/WebP with SVG to 415, Create-only content-addressed, 201 record) + GET|HEAD /{o}/{r}/attachments/<sha>/<name> via ChainRepo/HandleRepo (in-package static contract per B3: ETag/304/206/416/HEAD, re-sniffed Content-Type, private, immutable, nosniff) + [attachments] max_image_bytes (default 8MiB, setup-schema-pending per S3). One seam correction from the review: the byte route rides ChainRepo/HandleRepo (repo_extra.go), not the Handle ExtraRoutes chain — the core router only sends lane paths to the api seam (verified against the live server).
Frontend: SDK attachments.js + shared attachUpload.js (alt escaping S1, placeholder replace, 64 KiB pre-check) + onPaste/onDrop glue in IssueNew.jsx and CommentComposer (uploader prop, wired by Issue.jsx only — PR adoption needs the Pull.jsx markdown switch per S6) + ui.css img rule. Sanitizer untouched, no new deps.
Docs: 02 §12 + Decisions (incl. B2 discovery-exempt, S7 ceiling/sweep, dedup oracle), 06 private-cache-class note, 11 config row.

Tests: table-driven httptest for every handler/rejection (96.1% package cover, -race clean), headless node suites (288/288), real-Chromium paste drive dark+light ending with rendered images and zero console errors (screenshots in /tmp/opencode/drive120-*.png on the dev host).

Implements Forgejo issue #120 per the plan + R1 + review (R1 normative on conflict). What: paste (Ctrl/Cmd+V) or drop an image onto the issue-create textarea or the thread comment composer uploads it and inserts `![name](url)` at the cursor. Backend (`internal/issues`, §12): `POST /{o}/{r}/api/attachments` (authenticated+read, LimitReader 8 MiB cap to 413 with no Content-Length requirement per B1, spool+hash, client sha optional-when-present per S4, magic-byte allowlist PNG/JPEG/GIF/WebP with SVG to 415, Create-only content-addressed, 201 record) + `GET|HEAD /{o}/{r}/attachments/<sha>/<name>` via ChainRepo/HandleRepo (in-package static contract per B3: ETag/304/206/416/HEAD, re-sniffed Content-Type, `private, immutable`, nosniff) + `[attachments] max_image_bytes` (default 8MiB, setup-schema-pending per S3). One seam correction from the review: the byte route rides ChainRepo/HandleRepo (repo_extra.go), not the Handle ExtraRoutes chain — the core router only sends lane paths to the api seam (verified against the live server). Frontend: SDK `attachments.js` + shared `attachUpload.js` (alt escaping S1, placeholder replace, 64 KiB pre-check) + onPaste/onDrop glue in IssueNew.jsx and CommentComposer (uploader prop, wired by Issue.jsx only — PR adoption needs the Pull.jsx markdown switch per S6) + ui.css img rule. Sanitizer untouched, no new deps. Docs: 02 §12 + Decisions (incl. B2 discovery-exempt, S7 ceiling/sweep, dedup oracle), 06 private-cache-class note, 11 config row. Tests: table-driven httptest for every handler/rejection (96.1% package cover, -race clean), headless node suites (288/288), real-Chromium paste drive dark+light ending with rendered images and zero console errors (screenshots in /tmp/opencode/drive120-*.png on the dev host).
Backend (internal/issues): POST /{o}/{r}/api/attachments (auth+read,
8 MiB LimitReader cap to 413, spool+hash, optional client sha, magic
allowlist PNG/JPEG/GIF/WebP, SVG to 415, Create-only content-addressed,
201 record) + GET|HEAD /{o}/{r}/attachments/<sha>/<name> via ChainRepo
HandleRepo (in-package static contract: ETag/304/206/416/HEAD, sniffed
Content-Type, private immutable, nosniff). [attachments] max_image_bytes
struct+default+validation+docs. Frontend: SDK attachments.js, shared
attachUpload.js, onPaste/onDrop glue in IssueNew + CommentComposer
(wired by Issue.jsx), ui.css img rule. Docs: 02 §12 + Decisions, 06
private-cache-class note, 11 config row.
Sign in to join this conversation.
No description provided.