feat: v3.14.0 — dedicated-route polish (closes #75)#76
Merged
Conversation
GetPages is the only list-shaped accessor that returns dedicated-route content. Filters !Draft && Type == TypePage; preserves natural insertion order (date-desc, matching siblings) — alphabetical sort belongs in the /p index handler. Powers /p index (Phase 3) and sitemap pages section (Phase 2). Interface ripple touches 11 files: Repository + 4 Service shapes (internal Service interface + CompositeService impl + ServiceAdapter delegation + ArticleServiceInterface) + 5 downstream mocks (FileSystemRepository / CachedRepository real impls + mockRepository in service_test + MockArticleService in handlers_test + TestArticleService in article_test + mockArticleService in feed_test + MockArticleService in seo/service_test). Test mocks return canned data per CLAUDE.md — only the repository-layer mock filters by type (since the unit-under-test IS the filter); all handler-layer mocks are passthrough.
…r; predicate-correct sitemap 15 hardcoded /writing/<slug> literals replaced across 5 files: feed.go (5 sites in RSS Link/GUID, JSON Feed id/url, sitemap loc), compose.go (5 redirect+JSON-response sites via new canonicalSlugPath helper), taxonomy_handler.go (2 collectionSchema URLs), post_handler.go (2 sites — canonicalPath + BlogPosting URL), seo_helper.go (1 breadcrumb in the non-dedicated-route branch). Verified: grep '/writing/' across non-test internal/ returns only 2 legitimate sites — predicate.go (inside CanonicalURLFor itself) + command.go (route registration). Sitemap gains explicit static entries for /about (0.5, yearly) and /p (0.5, monthly), plus a pages section iterating GetPages() with each page at /p/<slug> (0.5, yearly). /about was silently absent before because GetAllArticles→GetPublished excludes dedicated-route content via the predicate; pages were absent for the same reason — both now surface explicitly. Sitemap URL count grows from 4-static+articles to 6-static+articles+pages. Removes dead seo.Helper.GenerateSitemap (and orphan URLSet/URL types, unused xml+sort imports) — no production call sites, only test code. feed.Service.GenerateSitemap (served at /sitemap.xml) is the live implementation. SEOServiceInterface narrowed accordingly. The GenerateSitemap-error path test on disabled helper now exercises GenerateRobotsTxt for the same disabled-fail assertion. Extracts ogTypeWebsite constant to close the goconst regression exposed by the file shrinking (3 "website" occurrences within seo package: 2 in seo.go, 1 in service_test.go).
Adds the /p reader-facing index that lists published type:page articles sorted alphabetically by title (case-insensitive). Sort is a presentation concern in the handler; the repository returns natural insertion order (date-desc) for symmetry with sibling list methods. New pages.html template uses only binary or/not/eq per the markgo strict-typed template gotcha (CLAUDE.md). Empty state copy when no pages exist. Footer link slots between /categories and /about. Template integration test in services package renders pages-content against the real embedded web.Assets template with empty/single/many inputs — guards against silent mid-render truncation that handler status==200 checks would miss. Handler tests verify alphabetical sort (zoo/alpha/Beta → alpha/Beta/Zoo), canonicalPath = /p, pageCount, and HEAD support. README content-type count corrected to five (the v3.13.0 page type was never reflected). design.md "Three Streams" heading dropped in favor of "Content Types"; pages discoverability sentence now mentions /p index, footer link, sitemap, search. Project CLAUDE.md route count refreshed ~41 → ~65 (counting HEAD pairs from registerGET), handler count 11→13 (AMA+Upload), CSS 20→23, JS modules 10→19, templates 17→19. Smoke-tested end-to-end: GET /p with 2 type:page articles renders sorted, HEAD /p returns 200, /p/<slug> still renders, sitemap.xml has /about + /p static entries + /p/<slug> page entries, footer link visible on home.
Closes #75. The /about page's adjacent AMA promo + mailto-only contact sections collapse into a single .about-reach card with two affordance columns. AMA copy now flows from the existing v3.11.0 AMA_PAGE_HEADING / AMA_PAGE_INTRO / AMA_SUBMIT_LABEL env vars so operators voice /ama and /about consistently. SMTP-backed contact form (when has_contact_form) stays untouched as a separate section — out of scope here. ShowAbout gains a doc-comment enumeration of the full template-data contract (identity / bio / social / contact / reach groups) — pays the debt of the implicit data["..."] convention that previously had no single source of truth. AMA half always renders on /about, matching pre-v3.14.0 behavior: getEnv treats empty as unset and falls back to the non-empty default, so clearing AMA_PAGE_HEADING in .env can't hide it. Hiding the AMA half requires overriding about.html via TEMPLATES_PATH; documented in configuration.md alongside the doubled-context note on AMA_PAGE_* vars. Tests restructured per the LastData inspection pattern (handlers_test.go MockTemplateService) — previous status==200 assertions couldn't verify new env-driven keys reached the template. Three rows: defaults, no email, custom AMA copy. docs/configuration.md: 3 stale claims corrected — Pages › Sitemap ("pages are not currently emitted" → "pages are emitted with canonical URLs"), Pages › Future enhancements (drop /p index + auto-nav bullets since v3.14.0 ships /p with footer link), AMA Copy table (extend descriptions to note doubled context).
Collaborator
Author
Code reviewFound 1 issue:
markgo/web/static/css/pages.css Lines 1 to 47 in c8d7461 |
Three issues from the automated review pass: 1. pages.css used undefined --spacing-lg token (collapsing .pages-index padding to zero) and hardcoded rem/timing values where design tokens exist. Replaced with --spacing-5, --transition-fast, --font-size-lg, --font-size-base, --spacing-1. Closes the CLAUDE.md "Component CSS: tokens only" violation. 2. compose.go HandleEdit redirected via canonicalSlugPath(slug) which passed a synthetic empty-Type article to CanonicalURLFor — wrong for existing type:page articles edited via /compose/edit/:slug. Lookup now hits articleService.GetArticleBySlug first; falls back to the empty-Type synthetic only if reload failed. Removes the needless 301 round-trip for page authors. canonicalSlugPath becomes the method canonicalPathForSlug on ComposeHandler. 3. about.html previously gated the entire about-reach section on `not .has_contact_form`, hiding the AMA card from operators with full SMTP configured. Doc comment claimed "AMA always renders" but template contradicted it. Restructured: AMA card now renders unconditionally inside about-reach; only the mailto card stays gated on `has_contact && not has_contact_form`. id="contact" stays on about-reach (the primary /contact landing target); the SMTP form section gets id="contact-form" to deduplicate. Test gains a regression-guard row for the SMTP-configured case.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
v3.13.0 made dedicated-route content (
/about,/p/:slug) structurally separate via the predicate. v3.14.0 makes it operationally first-class — pages become discoverable (sitemap +/pindex + footer link),/aboutjoins the sitemap, and the/aboutreach section consolidates AMA + mailto into one section voiced by the existing v3.11.0AMA_PAGE_*env vars (closes #75).Summary
81ba14f):Repository.GetPages()+ArticleServiceInterface.GetPages()— publishedtype:pagearticles, natural insertion order. 8-file interface ripple + 3 downstream test mocks.21662af): All 15 hardcoded/writing/<slug>literals across 5 files now route througharticlepkg.CanonicalURLFor. RSS + JSON Feed + Sitemap all uniformly honor the dedicated-route predicate. Sitemap gains static entries for/aboutand/pplus a pages section. Deadseo.Helper.GenerateSitemapremoved (no production call sites;feed.Service.GenerateSitemapis the live impl).4fbe152):/phandler +pages.html(compact list, case-insensitive alphabetical sort in the handler) + footer link + template integration test guarding against the strict-typed-template silent-truncation trap. README content-type count corrected to five;docs/design.md+ projectCLAUDE.mdcounts refreshed.c8d7461):/aboutconsolidates the AMA promo + mailto-only contact into one.about-reachcard. Operator voice unified across/amaoverlay and/aboutreach viaAMA_PAGE_HEADING/INTRO/SUBMIT_LABEL.ShowAboutgains a doc-comment enumeration of the full template-data contract.Test plan
golangci-lint cache clean && make lintmake test-racegreencurl /sitemap.xml—/about+/p+ each/p/<slug>visible, no/writing/<page-slug>leakcurl /p— alphabetical list;HEAD /p200curl /about— reach section renders both AMA + mailto cards by default/p, click into a page, back-navCloses #75.