← Back

Bug-fix report — 2026-06-15

Defects found and fixed on Ethica.no during the newsletter follow-up and the CMS (Hugo → live) Phase 1 work. Patch-note summary: Patch/patch-notes-2026-06-15.md.


Newsletter

Symptom: a campaign using a branded footer showed two unsubscribe links — the styled one inside the footer box, plus a stray plain one appended at the very bottom. Cause: the layout used a {{unsubscribe_url}} slot, but the engine only recognised {{unsubscribe}}. Not finding it, render_campaign appended its default footer link — and left href="{{unsubscribe_url}}" literally unfilled (a broken link). Fix: support {{unsubscribe_url}} (raw href) alongside {{unsubscribe}}; a layout that provides either slot no longer gets the fallback appended. The fallback still applies when a layout uses neither, so an email can never go out without a way to unsubscribe. Covered by a unit test (render_unsubscribe_url_slot).

BUG-2 · Unsubscribed subscribers showed as “pending”

Symptom: after clicking unsubscribe, the subscriber went back to a “pending” state in the admin list rather than reading as unsubscribed. Cause: unsubscribe set confirmed=0, and the list renders any confirmed=0 row as “pending” — one flag overloaded for three states (pending / confirmed / unsubscribed). Fix: added an unsubscribed_at column (idempotent migration); unsubscribe stamps it (and clears confirmed so it stays out of sends). The list now shows a distinct “unsubscribed” badge + stat, and re-confirming clears the flag. Smoke asserts the stamp + badge.

BUG-3 · Opt-in confirmation sent from the wrong mailer

Symptom: the homepage signup confirmation used a different sender identity than the newsletter, and could fail silently if only the newsletter SMTP was configured. Cause: handle_subscribe sent via send_mail (the contact-form mailer on the env SMTP_* config), not the newsletter SMTP the campaigns use. Fix: the confirmation now sends over the newsletter SMTP (nl_send_text, with Date/Message-ID), falling back to the general mailer only when the newsletter SMTP isn’t configured. Shared _base_headers/_deliver helpers extracted so both paths get the same headers.

CMS (Hugo → live, Phase 1)

BUG-4 · init_db crashed at container start — semicolons in SQL comments

Symptom: with the new content table added to the schema, the web container failed to start: init_db raised psycopg.errors.SyntaxError, so the whole app was down (every request returned nothing). Caught in the Docker smoke during development — never reached production. Cause: init_db.main() applies the schema by naively splitting the SQL on ;. Two of the new table’s comments contained a semicolon — an inline column comment (-- unix; gates …) and, ironically, a meta note that read “no semicolons in these comments — splits on ‘;’.” Each ; shattered the CREATE TABLE mid-statement. Fix: removed every semicolon from the table’s comments. Added a stronger local check (the parsed content statement must contain its last column and close with )), replacing the earlier check that only looked at columns before the break. This is the same class of bug that previously bit the newsletter schema — now guarded explicitly in the comment and the validation.

BUG-5 · Image-upload smoke assertion too strict (test-only)

Symptom: the new image-upload smoke check failed against a correct response. Cause: the assertion matched "url":"/uploads/, but json.dumps emits "url": "/uploads/…" (a space after the colon). The endpoint worked — the GET of the returned URL passed; only the substring match was wrong. Fix: corrected the assertion to include the space. No app change.


Files changed

  • newsletter.py{{unsubscribe_url}} slot, nl_send_text, _base_headers/_deliver.
  • server.py — opt-in over newsletter SMTP; unsubscribe unsubscribed_at; the live CMS render + image upload/serve.
  • scripts/init_db.pycontent + content_images tables (comments de-semicoloned), subscribers.unsubscribed_at.
  • newsletter_tests.py, .claude/skills/run-ethica-no/smoke.sh — coverage for the above.

Notes

All fixes verified by py_compile, the newsletter unit tests (7/7), and the Docker smoke (91/0). BUG-4 was found and fixed in development before any deploy; the others were user-reported on the live newsletter and are now on main.