Files
sukr/docs/plans/api-stabilization.md
Timothy DeHerrera 5c3299b774 docs: close API stabilization plan review and fix stale refs
Close plan review: fix Decisions table 404 naming (content/404.md
→ content/_404.md), check off Verification items, log
collect_items() quadruple-call as technical debt, fill in
Retrospective.

Fix post-changeset staleness: AGENTS.md YAML→TOML reference,
add [nav]/[feed]/[sitemap] config tables to AGENTS.md example,
mark charter workstream 1 complete.
2026-02-15 09:52:39 -07:00

16 KiB
Raw Blame History

PLAN: sukr 1.0 API Stabilization

Goal

Implement the pre-1.0 API changes required to stabilize sukr's public contract: switch frontmatter from hand-rolled YAML to serde-backed TOML, normalize template variable naming, add missing features (draft mode, 404 page, tag listing pages, aliases, date validation, feed/sitemap config), remove dead code, and add template fallback behavior. After this work, the five public surfaces (site.toml schema, frontmatter fields, template variables, CLI, content directory conventions) are locked — post-1.0 breaking changes require explicit user approval.

Constraints

  • Pre-1.0: breaking changes are acceptable now but the goal is to make them unnecessary after this work
  • Suckless philosophy: no speculative features, no new dependencies unless already transitively present
  • tree-house (git dep) and lightningcss (alpha) are accepted risks — do not attempt to resolve
  • chrono is acceptable for date validation — it's already a transitive dependency via tera
  • Every surface stabilized is a surface committed to maintaining

Decisions

Decision Choice Rationale
Frontmatter format TOML (replacing hand-rolled YAML) toml crate + serde already in deps. Eliminates the fragile hand-rolled parser. Every future field is just a struct field with #[derive(Deserialize)].
Frontmatter delimiter +++ Hugo convention for TOML frontmatter. Unambiguous — no risk of confusion with YAML --- or Markdown horizontal rules.
Template naming Mirror site.toml structure (config.nav.nested not config.nested_nav) Consistency between config and templates; pre-1.0 is only window for this break
Date type Option<chrono::NaiveDate> with custom deserialize_date fn for TOML native dates Parse, don't validate. Custom serde deserializer accepts toml::Datetime, extracts date, constructs NaiveDate. Invalid dates fail at deser.
Draft filtering draft: bool (#[serde(default)]) in Frontmatter, filter in collect_items() and discover() Filter early so drafts don't appear in nav, listings, sitemap, or feed.
Feed/sitemap config [feed] and [sitemap] tables with enabled boolean in SiteConfig Users need opt-out. Default true preserves backward compat.
Tag listing pages Generate /tags/<tag>.html using a new tags/default.html template Minimal approach — one template, one generation loop. No pagination.
Aliases aliases = ["/old/path"] in frontmatter, generate HTML redirect stubs Standard pattern (Hugo). <meta http-equiv="refresh"> redirect.
404 page content/_404.md404.html at output root Underscore prefix convention (matches _index.md). Most static hosts auto-serve /404.html.
Template fallback Try section/<type>.html, fall back to section/default.html Removes the requirement to create a template for every section_type.
Dead template cleanup Delete section/features.html and homepage.html Byte-for-byte duplicate and dead code respectively.
base_url duplication Remove top-level base_url template variable Single source of truth via config.base_url.
Tags syntax tags = ["foo", "bar"] (flat TOML array) Replaces nested taxonomies.tags YAML. Simpler, no indirection.

Risks & Assumptions

Risk / Assumption Severity Status Mitigation / Evidence
TOML frontmatter breaks all 17 existing content files HIGH Validated One-time mechanical migration. All files are simple key-value; no complex structures. Migration is part of Phase 1.
Documentation pages embed YAML frontmatter examples MEDIUM Validated configuration.md, content-organization.md, getting-started.md, feeds.md, sections.md, sitemap.md, templates.md all contain example frontmatter in their body text. Must update these doc examples too.
Template naming break affects existing user templates MEDIUM Validated Only docs/templates/base.html references config.nested_nav and base_url. The docs site is the only known deployment.
Tag listing pages need a template but no default ships MEDIUM Unvalidated Must design and ship tags/default.html in docs/templates/.
collect_items() triple-call not fixed by this plan LOW Accepted Performance issue, not API concern. Deferred.
Removing base_url top-level variable breaks templates MEDIUM Validated Only docs/templates/base.html uses it.

Open Questions

All resolved during CHALLENGE. See CHALLENGE Notes below.

CHALLENGE Notes

Items presented to nrd for decision:

  1. YAML → TOML frontmatter switch. The hand-rolled YAML parser can't handle aliases lists. Rather than extending a fragile parser, nrd decided to switch frontmatter to TOML — the toml crate and serde are already dependencies. This eliminates the parser entirely and replaces 70 lines of hand-rolled code with #[derive(Deserialize)]. Decision: switch to TOML.

Items validated by codebase investigation:

  1. ConfigContext normalization is clean. Flat struct at template_engine.rs:139-155. Refactoring to nested nav: NavContext { nested, toc } is straightforward.
  2. base_url duplication confirmed. template_engine.rs:118 injects standalone base_url. ConfigContext.base_url at line 142 is canonical. Removal safe.
  3. Template variable content is correct. Lines 57 and 83 inject rendered HTML as content, matching the sketch contract.
  4. Phase ordering is sound. No circular dependencies between phases.
  5. 17 content files need migration. All simple frontmatter — no nested structures beyond taxonomies.tags (which becomes flat tags = [...]). 7 files also contain embedded YAML examples in body text that need updating.

Scope

In Scope

  1. TOML frontmatter switch — replace hand-rolled YAML parser with #[derive(Deserialize)] + toml::from_str, migrate 17 content files, change delimiter from --- to +++
  2. Template naming normalization (config.nested_navconfig.nav.nested, add config.nav.toc, remove duplicate base_url)
  3. draft frontmatter field + filtering
  4. aliases frontmatter field + redirect stub generation
  5. Date validation (YYYY-MM-DD) at parse time
  6. [feed].enabled and [sitemap].enabled config
  7. content/_404.md404.html support
  8. Tag listing page generation (/tags/<tag>.html)
  9. Template section fallback (section/<type>.htmlsection/default.html)
  10. Dead template removal (section/features.html, homepage.html)
  11. Tests for all new and changed behavior

Out of Scope

  • Pagination
  • Asset co-location
  • i18n
  • Verbose/quiet CLI flags
  • collect_items() caching
  • ContentKind refactoring
  • Magic string enum extraction

Phases

  1. Phase 1: TOML Frontmatter & Config Normalization — replace the parser, migrate content, fix naming

    • Replace Frontmatter struct with #[derive(Deserialize)]
    • Add new fields: draft: bool (#[serde(default)]), aliases: Vec<String> (#[serde(default)]), keep all existing fields
    • Replace parse_frontmatter() with toml::from_str::<Frontmatter>()
    • Update extract_frontmatter() to detect +++ delimiters instead of ---
    • Add date validation: custom deserialize_date fn for TOML native dates → chrono::NaiveDate
    • Change tags from taxonomies.tags nesting to flat tags = ["..."] (direct TOML array)
    • Migrate all 17 content files from YAML (---) to TOML (+++) frontmatter
    • Update embedded frontmatter examples in documentation pages (7 files)
    • Add FeedConfig and SitemapConfig structs to config.rs with enabled: bool (default true)
    • Wire feed/sitemap config into SiteConfig deserialization
    • Gate feed generation in main.rs on config.feed.enabled
    • Gate sitemap generation in main.rs on config.sitemap.enabled
    • Refactor ConfigContext: flat nested_nav: bool → nested nav: NavContext { nested, toc }
    • Remove duplicate base_url top-level template variable injection
    • Update docs/templates/base.html: config.nested_navconfig.nav.nested, base_urlconfig.base_url
    • Delete docs/templates/section/features.html and docs/templates/homepage.html
    • Add template section fallback in render_section: try section/<type>.html, fall back to section/default.html
    • Update/fix all existing tests to use TOML frontmatter
    • Add new tests: TOML parsing, date validation (valid + invalid), feed/sitemap config gating
    • Verify all 69 existing tests pass (updated for TOML)
  2. Phase 2: Draft & Alias Features — implement filtering and redirect generation

    • Filter items where draft == true from collect_items() results
    • Filter drafts from SiteManifest.posts during discovery
    • Filter drafts from nav discovery (discover_nav())
    • Filter drafts from sitemap entries
    • Filter drafts from feed entries
    • Generate HTML redirect stubs for each alias path (<meta http-equiv="refresh">)
    • Add tests: draft filtering (excluded from listing, nav, feed, sitemap)
    • Add tests: alias redirect generation (valid HTML, correct target URL)
  3. Phase 3: 404 & Tag Pages — new content generation features

    • Detect content/_404.md in content discovery, treat as special page
    • Render _404.md to 404.html in output root
    • Collect all unique tags across content items during build
    • Create tags/default.html template in docs/templates/
    • Generate /tags/<tag>.html for each unique tag with list of tagged items
    • Add tag listing page entries to sitemap (if enabled)
    • Add tests: 404 page generation
    • Add tests: tag listing page generation (correct paths, correct items per tag)
    • End-to-end: build docs/ site and verify all outputs

Verification

  • cargo test — all existing tests pass (updated for TOML frontmatter)
  • cargo test — all new tests pass (16 new tests across 3 phases: 69 → 84)
  • cargo clippy -- -D warnings — no warnings
  • cargo build — clean compilation
  • End-to-end: build docs/ site with cargo run, verify:
    • public/sitemap.xml exists (default enabled)
    • public/atom.xml — N/A (no blog sections in docs site; feed gating verified by unit tests)
    • public/404.html exists (with _404.md in docs/content)
    • Templates use config.nav.nested (not config.nested_nav)
    • Templates use config.base_url (not bare base_url)
    • No section/features.html or homepage.html templates remain

Technical Debt

Item Severity Why Introduced Follow-Up Resolved
collect_items() called 4× per build LOW Tag collection (commit 7) and alias generation (commit 6) each added a new call site, growing the original 2-call pattern to 4 Cache section items in SiteManifest — deferred to Error Hardening or Performance pass

Retrospective

Process

The plan held up without requiring replanning during execution. The YAML → TOML pivot — the single largest decision — was identified during CHALLENGE (in /plan), not mid-execution. Once baked into the plan, execution was linear across all 3 phases.

Phase sequencing was sound: Phase 1 (parser + config) → Phase 2 (features depending on new fields) → Phase 3 (independent new features). No circular dependencies materialized.

Estimates were accurate: 3 phases, 8 commits predicted and delivered. CHALLENGE caught the risks that materialized — the 17-file content migration (flagged HIGH) was the most labor-intensive step but proceeded mechanically.

Outcomes

Unexpected debt: collect_items() grew from 2 to 4 call sites. The original plan flagged the triple-call as "Accepted — LOW" but execution added a fourth. Logged in Technical Debt above.

What we'd do differently: Commit 3 (45448cc) bundled 4 concerns (config refactor, base_url dedup, dead template deletion, section fallback). Tests for that commit's new behavior landed in commit 4. A 2-commit split would have maintained stricter atomicity without being excessive.

Pipeline Improvements

  • AGENTS.md has stale fragment paths (.agent/predicates/fragments/.agent/personas/). Discovered during predicate refresh — should be fixed independently.
  • /plan-review should remind agents to mark Verification checkboxes during execution, not defer to review time.

References

  • Charter: docs/charters/sukr-v1.md
  • Sketch: .sketches/2026-02-13-api-stabilization.md