Skip to content

Give every WordPress sub-category a home without a nav link - #215

Merged
ssavutu merged 1 commit into
mainfrom
feat/subsection-visibility
Aug 13, 2026
Merged

Give every WordPress sub-category a home without a nav link#215
ssavutu merged 1 commit into
mainfrom
feat/subsection-visibility

Conversation

@ssavutu

@ssavutu ssavutu commented Aug 13, 2026

Copy link
Copy Markdown
Member

What

Freezes the subsection strip as it looks today, then files the WordPress sub-categories the import left behind as hidden subsections: a slug, a page and a place in the tree, but no link in the strip. Editors get full control of the row, including visibility and section ↔ subsection conversion.

Why

The taxonomy had two ways to hold a category and neither fit these. A subsection row gave a category a page and a nav link; an alias gave it neither, only a way to reach its section's page. So a category either joined the navigation or stayed anonymous — and 48 stayed anonymous, including 13 that matched nothing at all:

category published articles appeared on
wrestling 103 nothing
triangle talks 73 nothing
theater 28 nothing
sadie says, tech tuesday, wine reviews, administration, sjn grant, … ~90 combined nothing

The other ~35 (tennis, crew, men's lacrosse, style, tv, beer reviews, editorial, letters to the editor, podcasts, word search, …) reached a section only through a category_aliases entry, so the category itself had no identity or page.

How

  • site_taxonomy.is_visible, added by a runtime ALTER, defaulting to 1. That's the freeze: all 44 existing subsections keep their links unchanged, and everything seeded arrives hidden.
  • Only subsectionsForSection filters on it. sectionMatchSlugs, taxonomyMatchSlugs and parentSectionForSubsection are untouched, so a hidden subsection's articles still roll up to its section, its own /v1/subsections/<slug>/articles still answers, and its counts still work.
  • The seed runs once, flagged in cms_settings, because these rows are editable and a deploy must not undo an editor's deletion. It skips a slug already taken, and defers entirely against an empty taxonomy rather than burning its one run before the sections are imported. Each row carries its exact category title as an alias rather than trusting the slug to derive it.
  • PUT /v1/taxonomy/{type}/{slug} now accepts type and is_visible, both omit-safe so a client that doesn't send them changes nothing. Conversion refuses to demote a section that still has children, and answers a (kind, slug) collision with a 400 instead of a driver-level 500. The slug guard is unchanged — renaming a slug that articles use still breaks live URLs.
  • Sections screen: a Linked/Hidden column with a one-click toggle (which omits category_aliases, so it can't wipe matching rules), a show/hide filter, a Type select in edit mode with a URL-breakage warning, and a visibility checkbox in the editor.

Testing

go build; full go test including the DB-backed integration suites against a real MariaDB 11.8, with new tests for the seed's run-once / hidden / skip-taken properties, the visibility round-trip, and conversion in both directions; tsc; eslint; npm run build; swagger regenerated; Playwright screenshots of the sections screen and its editor.

Note for whoever deploys this

On the first boot the seed runs and recounts the rows it created, so the counts on the sections screen jump for Sports, Entertainment, Opinion, Columns and News. That's the previously stranded articles arriving, not a bug.

🤖 Generated with Claude Code

The taxonomy had two ways to hold a category and neither fit the ones the
import left behind. A subsection row gave a category a page and a link in
its section's strip; an alias gave it neither, only a way to reach the
section's own page. So a category either joined the navigation or stayed
anonymous, and 48 of them stayed anonymous -- including 13 that matched
nothing at all, leaving wrestling (103 published articles), Triangle Talks
(73) and Theater (28) on no section page anywhere.

Splitting visibility off from existence covers the case that was missing.
site_taxonomy gains is_visible, defaulting to 1 so today's 44 subsections
keep their links exactly as they are, and the 48 seeded rows arrive hidden:
a slug, a page and a place in the tree, but no entry in the strip. Only
subsectionsForSection filters on it -- matching, counting and the item's own
listing are untouched, so a hidden subsection still feeds its section.

The seed runs once, flagged in cms_settings, because these rows are
editable and a deploy must not undo an editor's deletion. It skips a slug
already taken and defers entirely against an empty taxonomy, where the
sections have not been imported yet rather than there being nothing to do.

Editors get the rest of the row: visibility from a toggle in the table or
the editor, and section <-> subsection conversion, guarded against demoting
a section that still has children and against a (kind, slug) collision. The
slug guard stays as it was, since renaming a slug that articles use breaks
live URLs.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@ssavutu
ssavutu merged commit ef996e9 into main Aug 13, 2026
6 checks passed
@ssavutu
ssavutu deleted the feat/subsection-visibility branch August 13, 2026 04:18
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