{# The document shell. Every page extends this, directly or through the app's own base.html. What it owns: the
, the theme flash-guard, the asset links, and the header/main/footer skeleton. What it does not own: your brand, your nav, your footer text — those are blocks. Typical app base.html: {% extends "ui/shell.html" %} {% from "ui/nav.html" import brand, nav_links %} {% block site_title %}Acme{% endblock %} {% block brand %}{{ brand("Acme", url_for(request, "home"), "gauge") }}{% endblock %} {% block nav %}{{ nav_links(request, [("home", "Overview")]) }}{% endblock %} Fill the `sidebar` block instead of `nav` and the skeleton becomes a side column plus a thin top bar; see the note above . #} {% from "ui/feedback.html" import toast, toaster %} {% from "ui/nav.html" import theme_toggle %} {% from "ui/sidebar.html" import sidebar_trigger %} {# Rendered into a variable rather than straight into the page, because three things downstream depend on whether the app filled it in: the aside has to come first in the body, the content wrapper beside it changes shape, and the header grows a toggle. Jinja cannot ask whether a block is empty, so the block is captured once here and the emptiness test runs on the string. The cost is buffering the sidebar's markup — a few hundred bytes — before the response starts. The alternative is a second flag the app has to remember to set, and that flag gets forgotten. #} {% set sidebar_slot %}{% block sidebar %}{% endblock %}{% endset %} {% set has_sidebar = sidebar_slot.strip() | length > 0 -%}