Pre-release polish: /new ~800px overflow, page titles, multipart burn validation #308

Open
fen wants to merge 4 commits from fix-prerelease-polish-r2 into dev
pull from: fix-prerelease-polish-r2
Collaborator

Three pre-release fixes from the QA/security sweep, one commit each:

  1. /new horizontal overflow at ~800px (Medium): .deck grid used 1fr 300px; the 1fr editor column could not shrink below min-content, expanding the grid to ~1000px at 800px viewports (document.scrollWidth 980 vs innerWidth 800; culprit div.pane-r with .float.side-section right=970). Fix: minmax(0, 1fr). CDP probe now reports scrollWidth == innerWidth at 800x600, 375x812, 1400x900 (and 700x600).

  2. Missing <title> on every page (Low): layout.html now emits <title>{{ pageTitle . }} · Palette</title> via a pageTitle template func mapping the Page field. Verified via curl grep on /, /public, /new, /saved, /settings, paste view, and a CDP document.title check per page. (/raw serves plain text with no HTML document, so no title applies there.)

  3. Multipart create silently ignores malformed burn_after_reads (Low): handleCreatePasteMultipart dropped non-numeric values (err == nil guard), yielding 201 with no burn budget. Now 400 with {"error": ..., "code": "burn_invalid"} — same error+code shape as the JSON path (where an invalid value fails unmarshal into the int field). Empty value stays unset like the JSON omitempty. Verified live: burn_after_reads=abc -> 400 burn_invalid; =2 -> 201.

go test ./... green; go vet clean.

Three pre-release fixes from the QA/security sweep, one commit each: 1. **/new horizontal overflow at ~800px** (Medium): `.deck` grid used `1fr 300px`; the `1fr` editor column could not shrink below min-content, expanding the grid to ~1000px at 800px viewports (document.scrollWidth 980 vs innerWidth 800; culprit `div.pane-r` with `.float.side-section` right=970). Fix: `minmax(0, 1fr)`. CDP probe now reports scrollWidth == innerWidth at 800x600, 375x812, 1400x900 (and 700x600). 2. **Missing `<title>` on every page** (Low): layout.html now emits `<title>{{ pageTitle . }} · Palette</title>` via a `pageTitle` template func mapping the `Page` field. Verified via curl grep on /, /public, /new, /saved, /settings, paste view, and a CDP `document.title` check per page. (/raw serves plain text with no HTML document, so no title applies there.) 3. **Multipart create silently ignores malformed burn_after_reads** (Low): `handleCreatePasteMultipart` dropped non-numeric values (err == nil guard), yielding 201 with no burn budget. Now 400 with `{"error": ..., "code": "burn_invalid"}` — same error+code shape as the JSON path (where an invalid value fails unmarshal into the int field). Empty value stays unset like the JSON `omitempty`. Verified live: `burn_after_reads=abc` -> 400 burn_invalid; `=2` -> 201. `go test ./...` green; go vet clean.
fen added 3 commits 2026-10-16 21:53:02 +00:00
The .deck grid used '1fr 300px'; the 1fr editor column could not shrink
below its min-content, expanding the grid to ~1000px at 800px viewports
and pushing .pane-r to right=980 (document.scrollWidth 980 vs
innerWidth 800). Use minmax(0, 1fr) so the editor column can shrink;
the editor itself already clips/scrolls its own content.

Verified with a CDP overflow probe at 800x600, 375x812, 1400x900 and
700x600: scrollWidth == innerWidth at all four.
layout.html now emits <title>{{ pageTitle . }} · Palette</title>; the
pageTitle template func maps the Page field to a label (New paste,
Public pastes, Saved, Settings, paste title or 'Paste <id>', Unlock,
Can, Not found).
Reject malformed multipart burn_after_reads with 400
CI / docker (pull_request) Skipped
CI / test (pull_request) Successful in 21s
135e0d2dc7
handleCreatePasteMultipart silently dropped non-numeric
burn_after_reads values (err == nil guard), so burn_after_reads=abc
created a 201 paste with NO burn budget. Now returns 400 with the
error+code shape ({'code':'burn_invalid'}) matching the JSON path,
where an invalid value fails unmarshal into the int field. An empty
value stays 'unset' like the JSON path's omitempty. Verified live:
burn_after_reads=abc -> 400 burn_invalid; =2 -> 201.
Author
Collaborator

Evidence (probes run against a local build of this branch)

Fix 1 — /new overflow (CDP probe, Emulation.setDeviceMetricsOverride + document.scrollWidth vs innerWidth):

Before (pre-fix build):

800x600  {"iw":800,"culprits":["DIV.pane-r right=980 left=680","DIV.float side-section right=970 left=680", ...]}
375x812  {"iw":375,"culprits":[]}
1400x900 {"iw":1400,"culprits":[]}

After (this branch):

800x600  {"iw":800,"culprits":[]}
375x812  {"iw":375,"culprits":[]}
1400x900 {"iw":1400,"culprits":[]}
700x600  {"iw":700,"culprits":[]}

scrollWidth == innerWidth at all four viewports.

Fix 2 — page titles:

curl grep:

/: <title>Public pastes · Palette</title>
/public: <title>Public pastes · Palette</title>
/new: <title>New paste · Palette</title>
/saved: <title>Saved · Palette</title>
/settings: <title>Settings · Palette</title>
paste view (titled paste): <title>My Paste · Palette</title>

CDP document.title check: / -> "Public pastes · Palette", /public -> "Public pastes · Palette", /new -> "New paste · Palette", /saved -> "Saved · Palette", /settings -> "Settings · Palette".
(/raw serves plain text with no HTML document, so no title applies there.)

Fix 3 — malformed multipart burn_after_reads:

$ POST /api/pastes (multipart) content=x, burn_after_reads=abc
HTTP/1.1 400 Bad Request
{"code":"burn_invalid","error":"burn_after_reads must be a positive number"}

$ POST /api/pastes (multipart) content=y, burn_after_reads=2
HTTP/1.1 201 Created  (paste created with burn budget intact)

Tests: go test ./... all packages ok; go vet ./internal/api ./internal/web clean.

## Evidence (probes run against a local build of this branch) **Fix 1 — /new overflow (CDP probe, Emulation.setDeviceMetricsOverride + document.scrollWidth vs innerWidth):** Before (pre-fix build): ``` 800x600 {"iw":800,"culprits":["DIV.pane-r right=980 left=680","DIV.float side-section right=970 left=680", ...]} 375x812 {"iw":375,"culprits":[]} 1400x900 {"iw":1400,"culprits":[]} ``` After (this branch): ``` 800x600 {"iw":800,"culprits":[]} 375x812 {"iw":375,"culprits":[]} 1400x900 {"iw":1400,"culprits":[]} 700x600 {"iw":700,"culprits":[]} ``` scrollWidth == innerWidth at all four viewports. **Fix 2 — page titles:** curl grep: ``` /: <title>Public pastes · Palette</title> /public: <title>Public pastes · Palette</title> /new: <title>New paste · Palette</title> /saved: <title>Saved · Palette</title> /settings: <title>Settings · Palette</title> paste view (titled paste): <title>My Paste · Palette</title> ``` CDP document.title check: `/` -> "Public pastes · Palette", `/public` -> "Public pastes · Palette", `/new` -> "New paste · Palette", `/saved` -> "Saved · Palette", `/settings` -> "Settings · Palette". (/raw serves plain text with no HTML document, so no title applies there.) **Fix 3 — malformed multipart burn_after_reads:** ``` $ POST /api/pastes (multipart) content=x, burn_after_reads=abc HTTP/1.1 400 Bad Request {"code":"burn_invalid","error":"burn_after_reads must be a positive number"} $ POST /api/pastes (multipart) content=y, burn_after_reads=2 HTTP/1.1 201 Created (paste created with burn budget intact) ``` **Tests:** `go test ./...` all packages ok; `go vet ./internal/api ./internal/web` clean.
fen added 1 commit 2026-10-16 21:59:41 +00:00
Reject negative multipart burn_after_reads via parseBurnAfterReads
CI / docker (pull_request) Skipped
CI / test (pull_request) Successful in 22s
6e68889b35
Author
Collaborator

Fix round 1 (multipart burn_after_reads negatives): 6e68889 now calls parseBurnAfterReads(n) after the successful Atoi in handleCreatePasteMultipart, so burn_after_reads=-5 returns 400 burn_invalid with the same message text as the JSON path ("burn_after_reads must be a positive number"), matching validate.go:92. go test ./... green, go vet clean, CI / test (pull_request) success on 6e68889 (run 442).

Fix round 1 (multipart burn_after_reads negatives): 6e68889 now calls `parseBurnAfterReads(n)` after the successful Atoi in `handleCreatePasteMultipart`, so `burn_after_reads=-5` returns 400 `burn_invalid` with the same message text as the JSON path ("burn_after_reads must be a positive number"), matching validate.go:92. `go test ./...` green, `go vet` clean, CI / test (pull_request) success on 6e68889 (run 442).
All checks were successful
CI / test (pull_request) Successful in 22s
CI / docker (pull_request) Skipped
You are not authorized to merge this pull request.
This pull request can be merged automatically.
This branch is out-of-date with the base branch
View command line instructions

Checkout

From your project repository, check out a new branch and test the changes.
git fetch -u origin fix-prerelease-polish-r2:fix-prerelease-polish-r2
git checkout fix-prerelease-polish-r2
Sign in to join this conversation.