Three pre-release fixes from the QA/security sweep, one commit each:
/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).
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.)
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.
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).
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.
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.
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).
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Three pre-release fixes from the QA/security sweep, one commit each:
/new horizontal overflow at ~800px (Medium):
.deckgrid used1fr 300px; the1freditor column could not shrink below min-content, expanding the grid to ~1000px at 800px viewports (document.scrollWidth 980 vs innerWidth 800; culpritdiv.pane-rwith.float.side-sectionright=970). Fix:minmax(0, 1fr). CDP probe now reports scrollWidth == innerWidth at 800x600, 375x812, 1400x900 (and 700x600).Missing
<title>on every page (Low): layout.html now emits<title>{{ pageTitle . }} · Palette</title>via apageTitletemplate func mapping thePagefield. Verified via curl grep on /, /public, /new, /saved, /settings, paste view, and a CDPdocument.titlecheck per page. (/raw serves plain text with no HTML document, so no title applies there.)Multipart create silently ignores malformed burn_after_reads (Low):
handleCreatePasteMultipartdropped 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 JSONomitempty. Verified live:burn_after_reads=abc-> 400 burn_invalid;=2-> 201.go test ./...green; go vet clean.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).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.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):
After (this branch):
scrollWidth == innerWidth at all four viewports.
Fix 2 — page titles:
curl grep:
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:
Tests:
go test ./...all packages ok;go vet ./internal/api ./internal/webclean.Fix round 1 (multipart burn_after_reads negatives):
6e68889now callsparseBurnAfterReads(n)after the successful Atoi inhandleCreatePasteMultipart, soburn_after_reads=-5returns 400burn_invalidwith the same message text as the JSON path ("burn_after_reads must be a positive number"), matching validate.go:92.go test ./...green,go vetclean, CI / test (pull_request) success on6e68889(run 442).View command line instructions
Checkout
From your project repository, check out a new branch and test the changes.