From 1c08647742fa1087f7658b15aa3f0fe6dad818d4 Mon Sep 17 00:00:00 2001 From: fen Date: Fri, 16 Oct 2026 16:52:29 -0500 Subject: [PATCH 1/4] Fix /new horizontal overflow at ~800px viewports 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. --- internal/web/static/app.css | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/internal/web/static/app.css b/internal/web/static/app.css index b6f9634..bcbf27d 100644 --- a/internal/web/static/app.css +++ b/internal/web/static/app.css @@ -129,7 +129,7 @@ body { /* new paste page */ .deck { - display: grid; grid-template-columns: 1fr 300px; gap: 16px; + display: grid; grid-template-columns: minmax(0, 1fr) 300px; gap: 16px; padding: 16px 20px 20px; height: calc(100vh - 76px); max-width: 1400px; margin: 0 auto; } -- 2.54.0 From b4c23a09bf871e929da3fe1c7b706f0fd92cdfef Mon Sep 17 00:00:00 2001 From: fen Date: Fri, 16 Oct 2026 16:52:29 -0500 Subject: [PATCH 2/4] Add page to every rendered page MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit layout.html now emits <title>{{ pageTitle . }} · Palette; the pageTitle template func maps the Page field to a label (New paste, Public pastes, Saved, Settings, paste title or 'Paste ', Unlock, Can, Not found). --- internal/web/templates/layout.html | 1 + internal/web/web.go | 33 ++++++++++++++++++++++++++++++ 2 files changed, 34 insertions(+) diff --git a/internal/web/templates/layout.html b/internal/web/templates/layout.html index 61eeca0..80cf025 100644 --- a/internal/web/templates/layout.html +++ b/internal/web/templates/layout.html @@ -1,5 +1,6 @@ {{define "head"}} +{{ pageTitle . }} · Palette diff --git a/internal/web/web.go b/internal/web/web.go index 049817d..36f7692 100644 --- a/internal/web/web.go +++ b/internal/web/web.go @@ -74,6 +74,7 @@ func New() (*UI, error) { "humanSize": humanSize, "version": func() string { return Version }, // #93: topbar version label "defaultDark": func() bool { return DefaultDark }, // #127: server-configured default + "pageTitle": pageTitle, // page labels } t, err := template.New("").Funcs(funcs).ParseFS(tmplFS, "templates/*.html") if err != nil { @@ -82,6 +83,38 @@ func New() (*UI, error) { return &UI{tmpl: t}, nil } +// pageTitle returns the <title> label for the rendered page. Page data is +// always a map with a "Page" key set by the handlers. +func pageTitle(data any) string { + m, _ := data.(map[string]any) + page, _ := m["Page"].(string) + id, _ := m["ID"].(string) + title, _ := m["Title"].(string) + switch page { + case "new": + return "New paste" + case "public": + return "Public pastes" + case "saved": + return "Saved" + case "settings": + return "Settings" + case "paste": + if title != "" { + return title + } + return "Paste " + id + case "unlock": + return "Unlock " + id + case "can": + return "Can " + id + case "notfound": + return "Not found" + default: + return "Palette" + } +} + func humanSize(n int) string { if n < 1024 { return fmt.Sprintf("%d B", n) -- 2.54.0 From 135e0d2dc729a136b5ae1b3efdb089ad5b5c070c Mon Sep 17 00:00:00 2001 From: fen <fen@archfox.org> Date: Fri, 16 Oct 2026 16:52:29 -0500 Subject: [PATCH 3/4] Reject malformed multipart burn_after_reads with 400 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. --- internal/api/attachments.go | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/internal/api/attachments.go b/internal/api/attachments.go index 862f544..6bdb31b 100644 --- a/internal/api/attachments.go +++ b/internal/api/attachments.go @@ -186,9 +186,18 @@ func (a *apiServer) handleCreatePasteMultipart(w http.ResponseWriter, r *http.Re case "burn_after_read": p.BurnAfterRead = v == "true" || v == "1" || v == "on" case "burn_after_reads": - if n, err := strconv.Atoi(v); err == nil { - p.BurnAfterReads = &n + if v == "" { // absent value: unset, like the JSON path's omitempty + continue } + n, err := strconv.Atoi(v) + if err != nil { + // malformed burn_after_reads is a 400, not a silently-dropped + // budget: same 400-with-code shape the JSON path uses for + // invalid values (which fail unmarshal into the int field). + writeErrCode(w, 400, "burn_invalid", "burn_after_reads must be a positive number") + return + } + p.BurnAfterReads = &n } } -- 2.54.0 From 6e68889b35302408218b3d60a99abde5ffcfaa87 Mon Sep 17 00:00:00 2001 From: fen <fen@archfox.org> Date: Fri, 16 Oct 2026 16:59:41 -0500 Subject: [PATCH 4/4] Reject negative multipart burn_after_reads via parseBurnAfterReads --- internal/api/attachments.go | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/internal/api/attachments.go b/internal/api/attachments.go index 6bdb31b..5a2cfff 100644 --- a/internal/api/attachments.go +++ b/internal/api/attachments.go @@ -197,6 +197,12 @@ func (a *apiServer) handleCreatePasteMultipart(w http.ResponseWriter, r *http.Re writeErrCode(w, 400, "burn_invalid", "burn_after_reads must be a positive number") return } + // negative values are invalid too: same 400 burn_invalid shape + // as the JSON path (validate.go:92), identical message text. + if err := parseBurnAfterReads(n); err != nil { + writeErrCode(w, 400, "burn_invalid", err.Error()) + return + } p.BurnAfterReads = &n } } -- 2.54.0