Add /poetry/ and /fiction/ indexes; widen tag-collision guard

Nav, the home portal grid, and the library have linked both URLs since
the portals were added, but no rule generated either index — confirmed
404s in production (AUDIT §2.1). Both rules mirror the essays index;
fiction renders an empty list until content exists.

sectionOwnedTopLevelTags now lists every namespace owning a
<name>/index.html route, not just photography — Hakyll silently
overwrites on duplicate routes, so an essay tagged e.g. "music" would
have clobbered a real section landing (AUDIT §2.2).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
This commit is contained in:
Levi Neuwirth 2026-06-10 09:25:50 -04:00
parent f11495ff9a
commit 902e43ea19
2 changed files with 52 additions and 12 deletions

View File

@ -566,6 +566,46 @@ rules = do
>>= loadAndApplyTemplate "templates/default.html" ctx >>= loadAndApplyTemplate "templates/default.html" ctx
>>= relativizeUrls >>= relativizeUrls
-- ---------------------------------------------------------------------------
-- Poetry index
-- ---------------------------------------------------------------------------
-- Nav, the home portal grid, and the library all link /poetry/; this
-- rule is what keeps those links from 404ing. Lists flat poems and
-- collection poems alike; collection index pages are excluded by
-- 'P.poetryPattern' itself.
create ["poetry/index.html"] $ do
route idRoute
compile $ do
poems <- recentFirst =<< loadAll (P.poetryPattern .&&. hasNoVersion)
let ctx =
listField "essays" poetryCtx (return poems)
<> constField "title" "Poetry"
<> constField "portal" "true"
<> siteCtx
makeItem ""
>>= loadAndApplyTemplate "templates/essay-index.html" ctx
>>= loadAndApplyTemplate "templates/default.html" ctx
>>= relativizeUrls
-- ---------------------------------------------------------------------------
-- Fiction index
-- ---------------------------------------------------------------------------
-- Same rationale as the poetry index. content/fiction/ has no entries
-- yet; an empty match list renders an empty index rather than a 404.
create ["fiction/index.html"] $ do
route idRoute
compile $ do
stories <- recentFirst =<< loadAll (P.fictionPattern .&&. hasNoVersion)
let ctx =
listField "essays" fictionCtx (return stories)
<> constField "title" "Fiction"
<> constField "portal" "true"
<> siteCtx
makeItem ""
>>= loadAndApplyTemplate "templates/essay-index.html" ctx
>>= loadAndApplyTemplate "templates/default.html" ctx
>>= relativizeUrls
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------
-- New page — all content sorted by creation date, newest first -- New page — all content sorted by creation date, newest first
-- --------------------------------------------------------------------------- -- ---------------------------------------------------------------------------

View File

@ -80,23 +80,23 @@ expandTag t =
-- | Top-level tags that own a section URL outside the tag system, and -- | Top-level tags that own a section URL outside the tag system, and
-- therefore must NOT be created as tag pages — doing so would -- therefore must NOT be created as tag pages — doing so would
-- collide with a section landing route. The literal @"photography"@ -- collide with a section landing route. Hakyll does not error on
-- is the only one currently affected: every photo's @tags:@ list -- duplicate routes (one item silently overwrites the other), so an
-- begins with the bare @"photography"@ portal tag (per the section's -- essay tagged e.g. @music@ would otherwise clobber
-- convention), and 'tagIdentifier' would route that to -- @music/index.html@. The set therefore lists every namespace that
-- @"photography/index.html"@ — already owned by -- owns a @<name>/index.html@ route, not just the tags currently in
-- @photographyLandingRules@. -- use: @photography@ (every photo's @tags:@ list begins with it, per
-- the section convention) plus the other section landings and
-- generated index namespaces.
-- --
-- Sub-tags (@photography/landscape@, @photography/film@, …) are -- Sub-tags (@photography/landscape@, @photography/film@, …) are
-- unaffected; they keep their tag pages because no section landing -- unaffected; they keep their tag pages because no section landing
-- claims those URLs. -- claims those URLs.
--
-- Other portal tags (@music@, @poetry@, @fiction@, …) don't appear
-- here because their content types don't currently feed
-- 'tagIndexable', so the top-level tag never enters the tag system.
-- Add to this set if that ever changes.
sectionOwnedTopLevelTags :: [String] sectionOwnedTopLevelTags :: [String]
sectionOwnedTopLevelTags = ["photography"] sectionOwnedTopLevelTags =
[ "photography", "poetry", "fiction", "music", "essays", "blog"
, "cv", "archive", "authors", "bibliography"
]
-- | All expanded tags for an item (reads the "tags" metadata field). -- | All expanded tags for an item (reads the "tags" metadata field).
-- Filters out any 'sectionOwnedTopLevelTags' to prevent route -- Filters out any 'sectionOwnedTopLevelTags' to prevent route