Newsletter audit: unique auto-slug so recurring titles can't clobber prior issues (1.2.41)

The composer derived the kind 30023 addressable `d` identifier straight from
slugify(title). Because 30023 is addressable, two issues sharing a slug —
a recurring "Weekly Roundup", or "Update!" vs "Update?" — would share a
coordinate and the newer publish would silently overwrite the older issue.

Blank slug now auto-generates a unique coordinate (title-slug + UTC date +
short base36 time suffix). An explicit manual slug is still honored verbatim,
so intentionally editing/replacing a specific issue keeps working.

Verified clean (no changes): backfill panel is idempotent (skips slugs already
on-relay, deterministic issue-{n} identifiers) and the subscriber-count hook
uses the standard newest-marker-per-author tally with no double-counting.

Co-authored-by: shakespeare.diy <assistant@shakespeare.diy>
This commit is contained in:
shakespeare.diy 2026-09-08 18:34:54 +07:00
parent 6ad112dea3
commit c376f37164
2 changed files with 15 additions and 3 deletions

View file

@ -1,7 +1,7 @@
{
"name": "skate-spots",
"private": true,
"version": "1.2.40",
"version": "1.2.41",
"type": "module",
"homepage": "https://skate-spots.com",
"scripts": {

View file

@ -48,12 +48,24 @@ export function NewsletterComposer() {
if (!isAdminPubkey(user?.pubkey)) return null;
const effectiveSlug = slug.trim() || slugify(title);
const canPublish = title.trim().length >= 2 && body.trim().length >= 10 && !!effectiveSlug;
// A manual slug is used verbatim (lets an admin intentionally EDIT/replace a
// specific existing issue). When left blank we must NOT use the bare
// title-slug: kind 30023 is addressable, so two issues that slugify the same
// (e.g. a recurring "Weekly Roundup", or "Update!" vs "Update?") would share a
// `d` and the newer one would silently OVERWRITE the older. So an auto-slug
// gets a short date suffix to make each published issue its own coordinate.
const manualSlug = slug.trim();
const canPublish = title.trim().length >= 2 && body.trim().length >= 10 && (!!manualSlug || title.trim().length >= 2);
const handlePublish = async () => {
if (!canPublish) return;
const now = Math.floor(Date.now() / 1000);
// YYYYMMDD suffix (UTC) keeps auto-slugs readable and unique per day; a
// second same-title issue on the same day also gets a time component.
const d = new Date(now * 1000);
const dateSuffix = `${d.getUTCFullYear()}${String(d.getUTCMonth() + 1).padStart(2, '0')}${String(d.getUTCDate()).padStart(2, '0')}`;
const autoSlug = `${slugify(title) || 'issue'}-${dateSuffix}-${now.toString(36).slice(-4)}`;
const effectiveSlug = manualSlug || autoSlug;
try {
await publish({
kind: NEWSLETTER_KIND,