Fix push-digest sentinel filter, watermark, and cleanups from review

- push digest queries now exclude _duplicate channel like every other
  visibility query (bookmarks list/count and NewClassifiedSince)
- advance push watermark to newest scanned story, not pass-start now, so
  stories arriving during a long send pass aren't re-counted next pass
- replace hand-rolled escapeHTMLText with stdlib html.EscapeString
- drop em-dashes from user-facing copy; bump PWA CACHE_VERSION so clients
  pick up the changed shell assets
This commit is contained in:
prosolis
2026-07-07 01:25:23 -07:00
parent 8863b75916
commit 35850eaf73
7 changed files with 16 additions and 22 deletions

View File

@@ -3,6 +3,7 @@ package web
import (
"encoding/json"
"encoding/xml"
"html"
"log/slog"
"net/http"
"strings"
@@ -284,20 +285,8 @@ func contentToHTML(text string) string {
continue
}
b.WriteString("<p>")
b.WriteString(strings.ReplaceAll(escapeHTMLText(para), "\n", "<br>"))
b.WriteString(strings.ReplaceAll(html.EscapeString(para), "\n", "<br>"))
b.WriteString("</p>")
}
return b.String()
}
// escapeHTMLText escapes the five characters that matter inside HTML text so the
// article body can't inject markup into content:encoded.
func escapeHTMLText(s string) string {
return strings.NewReplacer(
"&", "&amp;",
"<", "&lt;",
">", "&gt;",
`"`, "&#34;",
"'", "&#39;",
).Replace(s)
}

View File

@@ -56,7 +56,6 @@ func (s *Server) sendDigests() {
if len(subs) == 0 {
return
}
now := time.Now().Unix()
// Disabled-source sets are per user; cache within a pass so a user with
// several devices only parses prefs once.
disabledByUser := make(map[string]map[string]bool)
@@ -116,7 +115,11 @@ func (s *Server) sendDigests() {
slog.Warn("push: send failed", "sub", sub.UserSub, "err", err)
continue
}
if derr := storage.TouchPushSubscription(sub.Endpoint, now); derr != nil {
// Advance to the newest story this digest actually accounted for, not a
// pass-start "now": the loop can run for minutes (one slow endpoint per
// send), so stories arriving mid-pass would otherwise be re-counted next
// pass. stories[0] is the newest in the scanned window (seen_at DESC).
if derr := storage.TouchPushSubscription(sub.Endpoint, stories[0].SeenAt); derr != nil {
slog.Error("push: advance watermark failed", "err", derr)
}
sent++
@@ -133,7 +136,7 @@ func buildDigestPayload(count int, top string) []byte {
body = "1 new story"
}
if top != "" {
body += " " + top
body += ": " + top
}
b, _ := json.Marshal(map[string]string{
"title": "Pete has fresh news",

View File

@@ -101,7 +101,7 @@
if (sub) return unsubscribe().then(function () { paint(false, "Notifications off."); });
return Notification.requestPermission().then(function (perm) {
if (perm !== "granted") { paint(false, "Permission denied."); return; }
return subscribe().then(function () { paint(true, "You're all set — new stories will nudge you."); });
return subscribe().then(function () { paint(true, "You're all set. New stories will nudge you."); });
});
}).catch(function () {
paint(false, "Something went wrong. Try again.");

View File

@@ -242,7 +242,7 @@
: "";
var href = safeURL(it.url);
var note = '<p class="pete-reader-note">' +
(it.paywalled ? "This source is paywalled the text above may be partial. " : "") +
(it.paywalled ? "This source is paywalled, so the text above may be partial. " : "") +
(href ? 'Read it at the source: <a href="' + escapeHTML(href) + '" target="_blank" rel="noopener noreferrer">' +
escapeHTML(it.source || "original article") + " ↗</a>." : "") +
"</p>";

View File

@@ -4,7 +4,7 @@
//
// Bump CACHE_VERSION whenever the precached shell assets change; activate()
// drops every cache that doesn't match the current version.
var CACHE_VERSION = "v2";
var CACHE_VERSION = "v3";
var SHELL_CACHE = "pete-shell-" + CACHE_VERSION;
var RUNTIME_CACHE = "pete-runtime-" + CACHE_VERSION;
@@ -77,7 +77,7 @@ function offlineFallback() {
"<div style=\"font-family:system-ui,sans-serif;max-width:32rem;margin:20vh auto;padding:0 1.5rem;text-align:center;color:#3a2f1a\">" +
"<div style=font-size:3rem>🦆</div>" +
"<h1 style=font-size:1.4rem>You're offline</h1>" +
"<p style=opacity:.7>Pete can't reach the news right now. Articles you've already opened are still readable head back and try one of those.</p>" +
"<p style=opacity:.7>Pete can't reach the news right now. Articles you've already opened are still readable, so head back and try one of those.</p>" +
"</div>",
{ headers: { "Content-Type": "text/html; charset=utf-8" }, status: 503 }
);