Thread tag names from collect_tags() through
generate_sitemap_file to generate_sitemap, appending
/tags/<tag>.html entries to sitemap.xml output.
Refactor: hoist collect_tags into run(), rename
generate_tag_pages to write_tag_pages.
Test suite: 83 → 84, phase 3 complete.
Detect content/_404.md as special page (underscore
prefix convention), store in SiteManifest.page_404,
render to 404.html in output root.
Add collect_tags() grouping all tagged content into
BTreeMap for deterministic output. render_tag_page()
renders through tags/default.html template.
Add 5 tests. Test suite: 78 → 83, all passing.
Add generate_aliases(), write_aliases(), and
redirect_html() to main.rs. For each content item
with aliases in frontmatter, writes a minimal HTML
redirect stub with meta refresh and canonical link.
Alias paths are normalized: bare paths get
/index.html appended, .html extensions preserved.
Add 2 tests. Test suite: 76 → 78, all passing.
Phase 2 complete — all plan items checked off.
Add draft filtering at discovery source functions:
collect_items(), discover_nav(), discover_pages().
Posts, feed, and sitemap inherit filtering
automatically via collect_items().
Items with frontmatter draft = true are excluded
from section listings, navigation, feed, and
sitemap. Default is false — no behavior change
for existing content.
Add 3 tests. Test suite: 73 → 76, all passing.
Wire config.feed.enabled and config.sitemap.enabled
into main.rs run() function. Both default to true,
so existing behavior is preserved.
Add 5 new tests: feed/sitemap config defaults,
independent disable, and ConfigContext nav structure
with base_url trailing-slash normalization.
Test suite: 69 → 73 tests, all passing.
Phase 1 complete — all plan items checked off.
Add FeedConfig and SitemapConfig structs to config.rs
with enabled: bool (default true) for opt-out control.
Refactor ConfigContext: flat nested_nav bool replaced
with nested nav: NavContext { nested, toc } to mirror
site.toml [nav] table structure.
Remove standalone base_url template variable; use
config.base_url as single source of truth with
trailing-slash trimming in ConfigContext::from().
Add section template fallback: try section/<type>.html,
fall back to section/default.html for unknown types.
Delete section/features.html (duplicate of default.html)
and homepage.html (dead code, never referenced).
Update base.html for new variable names.
Replace hand-rolled YAML parser (70 lines) with serde-backed
toml::from_str<Frontmatter> (6 lines). Frontmatter delimiter
changes from --- to +++ (Hugo TOML convention).
New Frontmatter fields:
- draft: bool (#[serde(default)]) for draft filtering
- aliases: Vec<String> (#[serde(default)]) for URL redirects
Date field upgraded from Option<String> to Option<NaiveDate>
with custom deserializer for TOML native dates. Parse, don't
validate: invalid dates now fail at deserialization time.
Add chrono dependency with serde feature. Update cascade in
sitemap.rs (NaiveDate→String at boundary), template_engine.rs
(FrontmatterContext gains draft/aliases), and all 14 tests.
BREAKING CHANGE: Content files must use +++ TOML frontmatter
instead of --- YAML frontmatter.
Manual Display and Error trait implementations replace derive macro.
Preserves source() chaining for all io::Error and tera::Error variants.
One less dependency, same behavior.
Two issues fixed:
1. Language-less code blocks (``` without lang) were not accumulating
text content. The guard `code_block_lang.is_some()` was false for
them, so content fell through to regular text rendering.
Fix: Add `in_code_block` flag to track code block state separately
from language presence.
2. Single quotes in code blocks were being HTML-escaped as ',
breaking CSP headers like 'self' in documentation.
Fix: Create code_escape/code_escape_into in escape.rs that only
escapes <, >, & (required to prevent HTML tag injection) but
preserves quotes (safe inside <pre><code> content).
Rationale for code_escape:
- < and > MUST be escaped to prevent browser interpreting code as HTML
- & MUST be escaped to prevent HTML entity interpretation
- Quotes are safe inside element content (no attribute context)
Also:
- Add test for unlabeled code block quote preservation
All 71 tests pass.
Apply html_escape to:
- Link href and title attributes (start_tag_to_html)
- Image src attribute (Event::End TagEnd::Image handler)
Add test cases for:
- Quote-breaking URL attacks
- Link title escaping
- Image src escaping
Addresses HIGH severity finding from security audit.
All 70 tests pass.
Add '\'' → ''' case to html_escape_into for complete XSS
protection in HTML attribute contexts. Update documentation
and add test case.
Addresses LOW severity finding from security audit.
- content: DEFAULT_WEIGHT, DEFAULT_WEIGHT_HIGH → pub(crate)
- sitemap: SitemapEntry → pub(crate)
These items are implementation details not exposed to consumers.
- Create escape.rs with shared html_escape, html_escape_into, xml_escape
- Remove duplicate implementations from render.rs, highlight.rs, feed.rs, sitemap.rs
- Add DEFAULT_WEIGHT (50) and DEFAULT_WEIGHT_HIGH (99) constants to content.rs
- Replace all magic number weight defaults with named constants
No functional changes; all 67 tests pass.
- Add 6 syntax highlighting themes (dracula, gruvbox, nord, github)
- Rewrite css.rs to use lightningcss bundler for @import resolution
- Theme CSS is inlined at build time, producing single bundled output
- Add pilcrow (¶) anchor link after heading text for deep-linking
- CSS: hidden by default, visible on heading hover, user-overridable
- Add strip_parens Tera filter for cleaner nav anchor labels
- Update test expectations for new heading format
- Add toc: bool to [nav] config section (default: false)
- Frontmatter toc: true/false overrides global config
- If frontmatter toc is not specified, falls back to config.nav.toc
- Enable nav.toc = true in docs/site.toml for global TOC
- Pass extracted anchors through template context
- Render anchor links under active page when page.toc is true
- Hierarchical indentation by heading level (h2-h6)
- CSS styling: smaller font, muted color, border-left indicator
- Add toc: true to templates.md as example
- Add Anchor struct with id, label, level fields
- markdown_to_html() now returns (String, Vec<Anchor>) tuple
- Headings h2-h6 are extracted with slugified IDs
- Add toc: bool frontmatter field for per-page TOC opt-in
- All heading tags now include id attributes for anchor links
Extend NavItem struct with children field to support nested navigation.
discover_nav() now populates children for section items by collecting
section pages via Section::collect_items() and mapping them to child
NavItem entries. Children are sorted by weight then alphabetically.
pulldown-cmark emits alt text as Text events between Image start/end
tags. Previously, images were rendered immediately on the Start event
with empty alt text, losing the user-provided description.
Refactored to accumulate alt text using the same pattern as code block
handling: state variables track image attributes and alt content, then
the full <img> tag is rendered on the End event.
Also omits the title attribute entirely when no title is provided,
producing cleaner HTML output.
Fixes Lighthouse "Image elements have [alt] attributes" audit issue.
Eliminate redundant Content::from_path call by using the pre-discovered
manifest.homepage content. Also uses manifest.nav directly.
- Removes dead_code warning on SiteManifest.homepage field
- Reduces I/O by reusing discovered content
- Simplifies function signature (4 args vs 5)
Replace separate discover_nav + discover_sections calls with single
SiteManifest::discover() in run() pipeline:
- Update generate_feed() to accept &SiteManifest
- Update generate_sitemap_file() to accept &SiteManifest
- Simplify process_pages() to return Result<()>
- Remove redundant all_posts collection (now in manifest.posts)
- Remove unused discover_nav and discover_sections imports
All 49 tests pass, site builds identically.
Introduce SiteManifest struct that aggregates all site content from a
single discovery pass:
- homepage: content/_index.md
- sections: directories with _index.md
- pages: top-level standalone .md files
- posts: blog section items (sorted by date for feed)
- nav: navigation menu items
Add discover_pages() helper and 5 unit tests covering homepage,
sections, pages, posts, and nav discovery.
Not yet integrated into main.rs pipeline.
Wire generate_sitemap() into main.rs pipeline:
- Refactor process_pages() to return discovered pages
- Add generate_sitemap_file() helper function
- Generate sitemap.xml after homepage (Step 5 in pipeline)
- All 44 tests pass, sitemap contains 12 URLs
The sitemap includes homepage, section indices, section items,
and standalone pages following XML Sitemap 1.0 protocol.
Implement src/sitemap.rs with generate_sitemap() for SEO-compliant
XML sitemap generation. Follows the feed.rs pattern:
- SitemapEntry struct for URL metadata
- build_sitemap_xml() for XML construction
- xml_escape() for special character handling
- 5 unit tests covering single/multiple entries, lastmod, escaping
Module declared in main.rs but not yet integrated into pipeline.
Use tree-sitter-toml-ng v0.7.0 from tree-sitter-grammars, which is
compatible with tree-sitter 0.26 (unlike the older tree-sitter-toml).
- Add tree-sitter-toml-ng dependency
- Add Toml variant to Language enum
- Add TOML_CONFIG with crate's HIGHLIGHTS_QUERY
Fix markdown code block highlighting to properly support both markdown
structure (headings, frontmatter) and language injection (rust, bash).
The key fix uses `#set! injection.include-children` in the injection
query to override tree-sitter-md's internal tokenization, enabling
proper highlighting of embedded languages within code fences.
Changes:
- Use crate's HIGHLIGHT_QUERY_BLOCK for base markdown highlighting
- Add custom injection query with include-children directive
- Add YAML/TOML frontmatter and HTML block injection rules
- Add text.* highlight names (title, literal, uri, reference)
- Add string.escape highlight name
- Add CSS styles for new highlight classes
- Remove unused custom md-highlights.scm
Self-documenting docs site built with sukr itself (dogfooding):
Core changes:
- Rename package from nrd-sh to sukr
- Move personal site to sites/nrd.sh/
- Update AGENTS.md and README.md
Documentation site (docs/):
- Add site.toml with sukr.io base URL
- Create docs-specific templates with sidebar navigation
- Add dark theme CSS with syntax highlighting colors
- Document all features: templates, sections, syntax highlighting,
mermaid diagrams, and LaTeX math rendering
Bug fixes:
- Render individual pages for all sections (not just blog type)
- Add #[source] error chaining for Tera template errors
- Print full error chain in main() for better debugging
Self-documenting docs site built with sukr itself (dogfooding):
- docs/site.toml with sukr.io base URL
- docs-specific templates with sidebar navigation
- Dark theme CSS, responsive layout
- Documentation: getting-started, configuration, features
Also: improved error chaining for better template debugging
Enable monorepo support with CLI configuration:
- Add PathsConfig struct with serde defaults for content, output,
static, and templates directories
- Add optional [paths] table to site.toml (backward compatible)
- Add -c/--config <FILE> flag to specify config file path
- Add -h/--help flag with usage information
- Resolve all paths relative to config file location
Users can now run multiple sites from a single repo:
nrd-sh # uses ./site.toml
nrd-sh -c sites/blog/site.toml # looks in sites/blog/
Includes 2 new unit tests for path configuration parsing.
Fully migrate from compile-time maud templates to runtime Tera:
- Rewrote main.rs to use TemplateEngine and discover_sections()
- Replaced hardcoded blog/projects with generic section loop
- Added Clone derive to Frontmatter and Content
- Fixed section_type dispatch via Section struct
- Deleted src/templates.rs, removed maud dependency
Users can now add sections without code changes.
Enable generic section processing by adding:
- Section struct with index, name, section_type, path
- Section::collect_items() to gather content in a section
- discover_sections() to find all directories with _index.md
Section type is determined from frontmatter `section_type` field,
falling back to directory name. Includes 5 unit tests.
Lay groundwork for user-editable templates by adding Tera as a
runtime template engine alongside the existing maud templates.
Changes:
- Add tera dependency
- Create TemplateEngine struct with render methods
- Add TemplateLoad/TemplateRender error variants
- Add section_type/template fields to Frontmatter
- Create templates/ directory with base, page, section, and content templates
Dead code warnings are expected; TemplateEngine will be wired
in to replace maud in subsequent commits.
Replace hardcoded "/blog/{slug}.html" URL pattern with dynamic
path derivation using Content.output_path(). This ensures feed
URLs work correctly for any content location, not just blog posts.
Update all template functions to accept nav parameter and iterate
over discovered NavItem slice instead of hardcoded links.
Refactor main.rs:
- Call discover_nav() early in run()
- Thread nav to all template renders
- Replace hardcoded page list with dynamic discovery
Navigation is now fully driven by filesystem structure.
Add NavItem struct and discover_nav() function to scan content
directory and automatically build navigation from:
- Top-level .md files (pages)
- Directories with _index.md (sections)
Navigation is sorted by frontmatter weight, then alphabetically.
Custom nav_label field allows overriding title in nav menu.
Includes 5 unit tests covering page/section discovery, weight
ordering, and nav_label support.
Intercept 'mermaid' code blocks in render.rs and call
mermaid::render_diagram() to convert to inline SVG.
- Use catch_unwind to handle upstream dagre_rust panics gracefully
- Graceful fallback: show raw code with mermaid-error class on failure
- Flowcharts render correctly; some diagram types hit upstream bugs
Add src/mermaid.rs with render_diagram() wrapper around
mermaid-rs-renderer for build-time Mermaid-to-SVG conversion.
- Use mermaid-rs-renderer git dependency (SVG-only, no PNG)
- Configure with RenderOptions::modern() theme
- Include unit tests for flowchart and sequence diagrams