Skip to content

feat: v3.15.0 — page authoring + reach copy (closes #78, #79)#81

Merged
vnykmshr merged 5 commits into
mainfrom
feat/v3.15.0
May 18, 2026
Merged

feat: v3.15.0 — page authoring + reach copy (closes #78, #79)#81
vnykmshr merged 5 commits into
mainfrom
feat/v3.15.0

Conversation

@vnykmshr
Copy link
Copy Markdown
Collaborator

Wave-4 of log.1mb.dev operator feedback closes the v3.13.0/v3.14.0 deferred threads for type:page first-class authoring (#79) and operator-voiced /about reach copy (#78). The four commits split as: new exported article.ValidateSlug contract for operator-supplied slugs (strict charset + length + reserved set {index,feed,rss,atom}); /compose/new-page route rendering compose.html in page mode via data-mode (slug field required, banner/tags/categories/link_url hidden, hidden type=page); three new ABOUT_REACH_HEADING / ABOUT_EMAIL_HEADING / ABOUT_EMAIL_INTRO env vars wired through the existing about_handler pattern with byte-exact defaults; and a regression test locking the type:page edit-roundtrip contract. Plan deviated at execution: the original Phase 1 (edit-loses-type fix) was dropped after TestUpdateArticle_PreservesAMAFields confirmed UpdateArticle already preserves unknown frontmatter via generic-map round-trip — plumbing absorbed into the page-affordance commit instead. Verified via make lint (cache-cleaned, 0 issues), make test green, an 11-scenario manual smoke matrix on the dev server, and byte-exact /about default rendering against the v3.14.0 baseline. Follow-up #80 filed for the adjacent BLOG_AUTHOR_EMAIL placeholder-default surface — deferred because it's a config-default change with its own scope.

Closes #78, #79.

vnykmshr added 4 commits May 18, 2026 11:55
Exports a slug contract for compose/new-page writes (Phase 2 prereq).
Stricter than FileSystemRepository.validateSlug, which stays permissive
for already-stored slugs at admin surfaces.
Closes #79. Separate route renders the form in page mode: required slug
input, type=page hidden, banner/tags/categories/link_url hidden. Edit
of an existing page surfaces the same mode (header + hidden fields)
while keeping the slug immutable. CreatePost branches on TypePage —
explicit slug, no date frontmatter. Handler validates the operator's
slug via article.ValidateSlug plus a uniqueness check against the
in-memory store. FAB stays thought-scoped.
Closes #78. Three new ABOUT_REACH_HEADING / ABOUT_EMAIL_HEADING /
ABOUT_EMAIL_INTRO env vars; pattern parity with the v3.14.0 AMA copy
wiring. Defaults match the current template literals byte-for-byte
so /about renders identically without operator override.
End-to-end regression guard for /compose/edit/<page-slug> POST.
UpdateArticle's generic-map preservation is locked by sibling tests;
this asserts the handler+form plumbing carries type=page round-trip
so a future refactor can't silently drop it.
@vnykmshr
Copy link
Copy Markdown
Collaborator Author

Code review

Found 1 issue:

  1. ValidateSlug regex ^[a-z0-9-]+$ accepts slugs with leading or trailing hyphens (e.g. -mypage, mypage-) — but the pre-existing codebase-wide validSlug gate at internal/handlers/compose.go:22 (^[a-z0-9]([a-z0-9-]{0,198}[a-z0-9])?$) rejects them. A page submitted via /compose/new-page with slug -mypage passes validatePageSlug, the file is written to disk, then the operator's /compose/edit/-mypage request fails the validSlug.MatchString(slug) gate and returns 404. The page becomes uneditable via the compose UI (and unpublishable via PublishDraft, which uses the same gate). TestValidateSlug doesn't cover leading or trailing hyphens.

// slugCharClass enforces lowercase ASCII letters, digits, and hyphens.
// generateSlug() in the compose service already produces this shape, so
// auto-generated slugs are regex-compatible by construction.
var slugCharClass = regexp.MustCompile(`^[a-z0-9-]+$`)
// reservedSlugs blocks slugs that would confuse readers if served from

Suggested fix: change slugCharClass to mirror validSlug's anchored form (^[a-z0-9]([a-z0-9-]*[a-z0-9])?$) and add leading/trailing-hyphen rejection cases to TestValidateSlug.

// validSlug matches URL-safe slugs: lowercase alphanumeric with hyphens, no leading/trailing hyphens, max 200 chars.
var validSlug = regexp.MustCompile(`^[a-z0-9]([a-z0-9-]{0,198}[a-z0-9])?$`)
const (

ValidateSlug regex tightened to require alphanumeric at both ends so
operator-supplied slugs match the codebase-wide validSlug gate; a page
with `-foo` would otherwise be created but immediately uneditable.
Page submission now rejects empty title server-side (template gains a
required attribute too). Edit-form Cancel link routes through a new
canonicalPath data field so type:page edits return to /p/<slug>
instead of triggering the v3.13.0 301 via /writing/<slug>.
@vnykmshr vnykmshr merged commit fe001a5 into main May 18, 2026
6 checks passed
@vnykmshr vnykmshr deleted the feat/v3.15.0 branch May 18, 2026 08:11
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.

feat(about): env-driven copy for /about reach section heading + email card

1 participant