Skip to content

Let editors pick the story that leads the homepage - #206

Merged
ssavutu merged 1 commit into
mainfrom
feat/featured-article
Aug 7, 2026
Merged

Let editors pick the story that leads the homepage#206
ssavutu merged 1 commit into
mainfrom
feat/featured-article

Conversation

@ssavutu

@ssavutu ssavutu commented Aug 7, 2026

Copy link
Copy Markdown
Member

Why

is_featured (the legacy priority column) has been in the article schema since the WordPress import, but it has never done anything. The ETL hardcodes it false on every row, no query orders by it, and Scalene has never referenced it on any branch. Editors asking how to set a featured article were right that there was no way to — the flag was inert end to end. All 43 articles on the live homepage currently carry is_featured: false.

Per the newsroom, featured means: the big centre card at the top of the homepage, which Scalene renders from news[0] in its 3-6-3 layout.

What it does

  • Editor toggle — "Featured article" checkbox in the article sidebar, below Breaking news, wired through load / create / save / dirty-tracking. Help text says it replaces the current pick, since that's a side effect on somebody else's article.
  • Cross-section — the article is spliced into the news block rather than sorted into the news query, because the featured story may be filed anywhere. A featured sports story still takes the lead card, which a news-scoped ORDER BY could never do. The block is re-trimmed to 13 so it doesn't outgrow the layout.
  • Keeps its own slot — a featured sports story leads the homepage and stays in the sports rundown, the way a lead story does in print. Only the news block dedupes, so featuring a news story promotes it rather than printing it twice.
  • Exclusive — featuring one article clears the flag on every other, on all three write paths (PATCH / PUT / POST). The homepage has one lead card; leaving the previous pick flagged would let the tiebreak, not the editor, decide which story runs. The clear runs after the target write succeeds, so a failed save leaves the old lead standing rather than leaving the homepage with none.
  • Published only — drafts, scheduled and archived rows are skipped when resolving the lead. The flag alone must not put an unpublished headline on the front page.

No Scalene changes are required for this to work — sorting happens server-side and news[0] is already the lead card. The companion Scalene PR is only about cache freshness.

Cache freshness

/v1/homepage now states public, max-age=60, stale-while-revalidate=300. It previously sent no Cache-Control at all, so every intermediary applied its own heuristic and an editor swapping the lead had no bound on when readers would see it. 60s is short enough that a change shows on the next reload; stale-while-revalidate keeps that cheap under load.

Drive-by

TestTaxonomyAliasSeedAndCacheRoundTrip was failing on main. It pinned entertainment to exactly ["Arts & Entertainment"] and expected news to have no aliases — both invalidated by c0f2ae9 filing the remaining orphaned categories. It now derives expectations from defaultCategoryAliases (so filing more categories can't fail it), moves the no-defaults case off news to a guarded slug, and checks the HTML-escaping regression explicitly instead of via a comparison that shares a marshaller with the code under test.

Testing

  • 5 unit cases for the splice, 4 integration tests against MariaDB — exclusivity, clearing, unpublished rows never leading, and an end-to-end one that features an older sports story and asserts the homepage leads with it.
  • Verified by hand against the seeded dev corpus: featured a 2010 crew story, confirmed news[0] became it (is_featured: true, category Sports), block still 13, previous lead demoted to news[1], no duplication — then reverted and confirmed the original lead returned.
  • Full server suite green, verified in a clean worktree of this commit. Frontend 12 tests + tsc clean.

🤖 Generated with Claude Code

The article schema has carried is_featured (the legacy `priority` column)
since the WordPress import, but nothing ever set it and nothing ever read
it: the ETL hardcodes it false on every row, no query orders by it, and
Scalene has never referenced it on any branch. Editors asking how to set
a featured article were right that there was no way to.

Featured now means what the newsroom means by it: the big centre card at
the top of the homepage, which Scalene renders from news[0].

The article is spliced into the news block rather than sorted into the
news query, because the featured story may be filed under any section --
a featured sports story still takes the lead card, which a news-scoped
ORDER BY could never do. It keeps its place in its own section block, the
way a lead story does in print; only the news block dedupes, so featuring
a news story promotes it instead of printing it twice.

Featuring is exclusive on all three write paths. The homepage has one
lead card, and leaving the previous pick flagged would let the tiebreak,
rather than the editor, decide which story runs. The clear happens after
the target write succeeds, so a failed save leaves the old lead standing
instead of leaving the homepage with none.

Drafts, scheduled and archived rows are skipped when resolving the lead:
the flag alone must not be able to put an unpublished headline on the
front page.

The homepage response also states its freshness for the first time
(60s, plus stale-while-revalidate). Without a Cache-Control header every
intermediary applies its own heuristic, so an editor swapping the lead
had no bound at all on when readers would see it.

Also unpins TestTaxonomyAliasSeedAndCacheRoundTrip from a literal alias
list. It asserted entertainment held exactly ["Arts & Entertainment"] and
that news had no aliases, both of which c0f2ae9 invalidated when it filed
the remaining orphaned categories. It now derives expectations from
defaultCategoryAliases, so filing more categories cannot fail it, and
checks the HTML-escaping regression explicitly rather than through a
comparison that shares a marshaller with the code under test.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ssavutu
ssavutu merged commit 54c41f3 into main Aug 7, 2026
6 checks passed
@ssavutu
ssavutu deleted the feat/featured-article branch August 7, 2026 05:16
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant