Architecture

Architecture

This document provides a technical overview of mbr’s architecture, design decisions, and implementation details.

High-Level Overview

flowchart TD
    subgraph Input
        FILES[Markdown Files]
        CONFIG[.mbr/ Config]
    end

    subgraph Core
        SCANNER[Repository Scanner]
        PARSER[Markdown Parser]
        RESOLVER[Path Resolver]
        TMPL[Template Engine]
    end

    subgraph Output
        SERVER[Axum Server]
        BUILD[Static Builder]
        GUI[Native Window]
    end

    FILES --> SCANNER
    CONFIG --> TMPL
    SCANNER --> RESOLVER
    RESOLVER --> PARSER
    PARSER --> TMPL
    TMPL --> SERVER
    TMPL --> BUILD
    SERVER --> GUI

Rust Modules

ModulePurpose
main.rsEntry point, CLI mode selection
cli.rsCommand-line argument parsing (clap)
config.rsConfiguration loading (figment)
server.rsHTTP server (axum)
build.rsStatic site generator
browser.rsNative GUI window (wry/tao)
path_resolver.rsURL to file path resolution
markdown.rsMarkdown parsing (pulldown-cmark)
templates.rsTemplate rendering (tera)
repo.rsRepository scanning
vid.rsVideo shortcode handling
oembed.rsURL metadata extraction
quicklook.rsmacOS QuickLook extension - markdown rendering plus verbatim plain-text/source previews
errors.rsError type definitions

Request Flow

flowchart TD
    REQ["HTTP Request<br/>/docs/guide/"] --> HANDLER[Request Handler]
    HANDLER --> RESOLVER["Path Resolver<br/>resolve_request_path()"]

    RESOLVER --> |MarkdownFile| PARSE["Parse Markdown<br/>+ Extract Frontmatter"]
    RESOLVER --> |StaticFile| SERVE[Serve File]
    RESOLVER --> |DirectoryListing| LIST[Generate Listing]
    RESOLVER --> |NotFound| E404[404 Response]

    PARSE --> RENDER[Render Template]
    LIST --> RENDER
    RENDER --> CACHE["Add Cache Headers<br/>ETag, Last-Modified"]
    CACHE --> RESP[HTTP Response]
    SERVE --> RESP

Path Resolution

The ResolvedPath enum represents resolution outcomes:

enum ResolvedPath {
    MarkdownFile(PathBuf),
    StaticFile(PathBuf),
    DirectoryListing(PathBuf),
    NotFound,
}

Resolution order:

  1. Direct file match → StaticFile
  2. Directory + index file → MarkdownFile
  3. Path with / suffix matching .md file → MarkdownFile
  4. File in static folder → StaticFile
  5. Directory without index → DirectoryListing
  6. Nothing matches → NotFound

One canonical URL per page

A markdown page is served at exactly one URL — the directory-style one:

FileCanonical URL
docs/guide.md/docs/guide/
docs/index.md/docs/
README.md/README/

The trailing slash is load-bearing, not cosmetic. It decides the base a browser uses for the page’s own relative links: from /docs/guide/ a ../other/ href resolves to /docs/other/, but from /docs/guide it resolves to /other/. Serving a page at the slashless URL therefore breaks every relative link on that page — one click after the wrong URL, which is what makes the symptom so hard to trace.

Server and GUI mode answer any non-canonical spelling — /docs/guide, /docs/guide.md, /docs, /docs/index/ — with a 301 to the canonical URL, preserving the query string. Fragments are not echoed in Location, because per RFC 9110 §10.2.2 the client reapplies the original one. Static files and directory listings are never redirected.

Static builds have no server, so the redirect cannot save them: there the correct href has to be emitted at render time, and the build’s link checker reports any that are not (see Build Mode).

link_transform::transform_link rewrites each authored href for the trailing-slash convention:

Authored in docs/guide.mdEmitted hrefLands on
other.md../other//docs/other/
other../other//docs/other/
other/../other//docs/other/
subfolder/index.md../subfolder//docs/subfolder/
../folder/file.md../../folder/file//folder/file/
photo.png../photo.png/docs/photo.png
Makefile../Makefile/docs/Makefile
/docs/other/unchanged (server)/docs/other/
https://…, mailto:…unchangedoff-site

The extra ../ on non-index pages compensates for the trailing slash; index pages already sit at a directory URL and get none.

An extension-less target is ambiguous — ../folder/file could be a markdown page or a file literally named file. Guessing either way corrupts the other (LICENSE, Makefile, Dockerfile are real, common link targets), so mbr asks the repository through the same path resolver a live request uses. Contexts with no repository — CLI and QuickLook rendering — treat an extension-less target as a static file.

ModeWhereWhat it reads
Server / GUIGET /{page}/errors.json (page_errors.rs)Every <a href> in the rendered HTML
Buildafter rendering (build.rs::validate_links)Every <a href> in the generated HTML

Both read the href that was actually emitted rather than re-deriving one from the markdown source: a checker that re-derives re-applies the same rules the transform used, so a transform defect is invisible to it by construction. Both report three kinds of problem — a target that does not exist, a page link missing its trailing slash, and a ../ chain that escapes the repository root (which browsers silently clamp).

Links into mbr’s own /.mbr/ namespace are skipped in server/GUI mode: the media viewers (/.mbr/videos/?path=… and friends) and the JSON endpoints are axum routes with no file behind them, and /.mbr/theme.css-style assets fall back to the compiled-in defaults when the repository has no .mbr/ folder. The path resolver never sees any of it, so its verdict would be a 404 claim about a URL that serves 200. A static build has no such gap — it writes that whole tree itself, so those files are checked like any other.

Design Decisions

On-the-Fly Rendering

mbr renders markdown on every request rather than using caches:

Rationale:

Performance:

No Temp Files

mbr never writes to the filesystem during normal operation:

Rationale:

Exception: Static build mode writes to output directory.

Static builds use symlinks instead of copying assets:

Rationale:

Limitation: Requires Unix-like OS (macOS, Linux).

Parallel Scanning

Repository scanning uses rayon for parallelism:

// Parallel directory traversal
files.par_iter().for_each(|file| {
    // Process each file concurrently
});

Benefits:

Template Fallback Chain

Templates resolve through a layered system:

1. --template-folder flag
2. .mbr/ folder in repo
3. Compiled-in defaults

Each layer can override specific files while inheriting others.

Performance Goals

mbr prioritizes speed in these areas:

AreaGoalApproach
Server startup< 1 secondLazy initialization
Page render< 50msSIMD markdown, in-memory template caching
Site build< 1 file/msParallel rendering
Static page load< 100msMinimal JS, client caching

Optimization Techniques

Lazy Loading:

Concurrent Processing:

Efficient Data Structures:

Key Dependencies

CratePurpose
axumHTTP server framework
tokioAsync runtime
pulldown-cmarkMarkdown parsing
teraTemplate engine
figmentConfiguration management
wryWebView wrapper
taoWindow management
mudaNative menu bar
rayonParallel iteration
papayaConcurrent hash maps
proptestProperty-based testing

Error Handling

mbr uses custom error types with thiserror:

#[derive(thiserror::Error, Debug)]
pub enum MbrError {
    #[error("Configuration error: {0}")]
    Config(#[from] ConfigError),

    #[error("Build error: {0}")]
    Build(#[from] BuildError),

    #[error("IO error: {0}")]
    Io(#[from] std::io::Error),
}

Errors propagate with context.

Testing Strategy

TypeLocationPurpose
Unit testssrc/*/testsModule behavior
Property testssrc/*/proptestsInvariant verification
Integration teststests/HTTP endpoint testing
Doc testsInlineExample correctness

Property-Based Testing

Key invariants verified with proptest:

Future Considerations

Potential Optimizations

Extensibility Points