~ / blog / 0x09-mea-culpa-draft-book-with-no-published-articles

Mea culpa, I shipped a draft-book feature that hid every chapter inside it

M12 said draft books are public works-in-progress. I updated the book-level visibility filter and called it done. The article-level filter, which lived in seven other queries, was a separate code path. Anonymous visitors saw the book, saw the chapters, and every chapter rendered 'no published articles'. Here's what I missed and the fix.

Mea culpa, I shipped a draft-book feature that hid every chapter inside it

The M12 design said this: draft books are public works-in-progress. Anonymous visitors should be able to reach /books/{slug} for a book in Draft state, with a pink banner so they know what they're looking at. The point was to invite readers in early. Corrections, first-hand accounts, the lot.

I shipped it. The book page rendered. The chapter list rendered. The banner rendered. I marked the ticket done.

Then I clicked into a chapter as an anonymous visitor and got:

no published articles

Three days, every chapter, every draft book, by my hand.

What I changed, and what I didn't

I updated the book-level visibility filter, the query that decides whether /books/{slug} returns a 200 to an anonymous visitor. That part worked.

What I missed is that the article-level filter is a different query. The book page renders the chapter list, but each chapter then queries its own articles, and that query was untouched:

.Where(a => a.IsPublished && a.PublishedAt <= now)

Articles in a brand-new draft book default to unpublished. The book is the unit of drafting, not each article. So the book-level query said yes, you may see this book; the article-level query said there are no articles here you are allowed to see; and the chapter rendered empty.

Both queries were correct in isolation. They disagreed about what "public" means.

The blast radius

The same blind spot was in six more places: the single-article page, the book history page, the author profile feed, the sitemap, the homepage activity feed, and the Books index card counts. Seven queries, one mental model, one updated, six not.

The fix

Each query gets the same bypass:

.Where(a =>
    (a.IsPublished && a.PublishedAt <= now)
    || a.Chapter.Book.Status == BookStatus.Draft)

If the parent book is in draft, the article rides along. The article's own IsPublished flag is irrelevant while its book is a draft, because the book is the unit of drafting. That is what M12 meant. I just didn't say it in code seven times.

The follow-up commit pulls the predicate into a single ArticleVisibility.IsPubliclyVisible(article, now) so the next rule change is one place, not seven.

To the early readers who tried to read the VB book and saw blanks

If you hit A history of Visual Basic earlier this week and the chapters were empty, you weren't crazy and the link wasn't broken. The articles really were being filtered out. Please go back and try again. I would still very much like the corrections.

The book is in draft state on purpose. That part was working as designed. The part where you couldn't actually read it was, regrettably, also by my hand. Apologies. Fixed.

// comments

0 ENTRIES
// sign in to comment