# Templates (askama 0.16) Templates are compiled into the binary at build time, so `cargo build` reports template errors as Rust type errors pointing at the `#[derive(Template)]` struct. Each page's struct sits next to its handler in `src/web/*.rs`. ## Layouts ``` base.html blocks: title, page_class, subheader, content ├── layout_repo.html repository header and tabs; blocks: page_title, content └── layout_settings.html account settings side nav; block: settings ``` - Every page struct needs `ctx: Ctx`, because `base.html` reads `ctx.user`, `ctx.flash` and `ctx.path`. - Repo pages also need `r: RepoCtx` and `tab: &'static str`. `tab` is one of `code`, `commits`, `branches`, `pulls` or `settings` and selects the active tab. - Settings pages need `tab` (`profile` or `keys`). - In repo pages, `page_title` is a prefix ending in ` · `, e.g. `{% block page_title %}Branches · {% endblock %}`. - **Never create a file named `base.html` in a subdirectory.** askama resolves `{% extends %}` relative to the current template's directory first, so `repo/*.html` extending `"base.html"` would pick up `repo/base.html` and fail with a cyclic dependency. That's why section layouts are named `layout_*.html` and kept at the root. ## Partials (`{% include %}`, which share the including template's scope) | Partial | Variables it expects in scope | | --- | --- | | `partials/diff.html` | `diff: DiffSummary`, `file_ref` (commit SHA or URL-encoded ref to link "View file" at), `base` (repo URL) | | `partials/commit_list.html` | `groups: Vec`, `base` | | `partials/refsel.html` | `refsel: RefSelector` | | `partials/clone.html` | `r: RepoCtx`, `view` with a `refname` field (used for archive links) | | `partials/repo_list.html` | `repos: Vec` | Bind them with `{% let %}` before including, e.g. `{% let file_ref = commit.id.clone() %}`, or `{% let repos = recent %}` to reuse the repo list. ## Calling Rust from templates Helpers live in `src/web/fmt.rs` and are called by full path: ```jinja {{ crate::web::fmt::icon("git-branch")|safe }} {# names: match arms in src/icons.rs #} {{ crate::web::fmt::avatar(user.username, 20)|safe }} {{ crate::web::fmt::plural(view.commit_count, "commit") }} {# "1 commit", "3 commits", "2 branches" #} {{ base }}/tree/{{ crate::web::fmt::urlpath(view.refname) }}/{{ crate::web::fmt::urlpath(entry.path) }} /login?next={{ crate::web::fmt::urlencode(ctx.path) }} ``` - askama passes struct fields by reference (`&c.committer_time`) but method-call results and literals by value. Numeric helpers therefore take `impl fmt::Num` / `impl fmt::Num`, which accepts both. Follow that pattern for new numeric helpers. String helpers take `&str`. - Methods on view structs are fine too (`r.base()`, `r.pull_url(pr.number)`, `f.display_path()`, `l.class()`). - Use `urlpath` for ref names and file paths in URL paths (keeps `/`, encodes spaces, `#` and so on). Use `urlencode` for query-string values. - Only mark output `|safe` when it's HTML we generate: `icon`, `avatar`, highlighted lines, and `markdown::render` output. Never put `|safe` on user-provided strings. ## Syntax that works here - `{% if let Some(x) = opt %}…{% endif %}`, `{% else if %}`, `{% for (a, b) in pairs %}`, `loop.index`, `loop.last`. - `{% match value %}{% when crate::web::code::BlobContent::Code with { lines } %}…{% endmatch %}`. Name enum variants by full path. - Comparing an `Option`: `data.mergeability == Some(crate::git::Mergeability::Clean)`. - Comparing a loop item against a field: `{% if *b == view.base %}`. ## Markup conventions - There is no inline JavaScript (the CSP forbids it). Use the `data-*` hooks from `static/app.js`, which are listed in the root `CLAUDE.md`. Destructive forms get `data-confirm="…"`. - Dropdowns are `