{# Layout: sidebar + topbar + content slot La host app debe definir/override: - block title - block sidebar_nav → links (usar dg/components/sidebar_link.html) - block breadcrumbs - block topbar_actions → CTA extra (notificaciones, etc.) - block content - block extra_head, block extra_body Variables esperadas (opcionales): - user.email, user.is_admin - tokens_override_href → al CSS de tokens del cliente - logo_html → snippet HTML para sustituir el logotipo por defecto #} {% block title %}Datagrowth{% endblock %} {# FOUC guard: aplica el tema antes del primer paint para evitar el parpadeo light->dark en usuarios con preferencia "dark" guardada. Se ejecuta síncronamente (sin defer) y SOLO toca data-theme; el resto del API (toggle, sync de icons/aria) vive en theme.js. #} {# Fallback a `request.state.dg_tokens_override_href` para handlers que no pasan `tokens_override_href` en el context Jinja (caso: routers del paquete que renderizan con context propio). La host app puebla el state via `populate_template_state` con la URL al CSS del `BrandDesign` que viste al host. Si el handler pasa el href explícito, gana ese (compat con sidebars customizados). Sin el host CSS el shell sale con los neutrales gray del paquete — diseño intencional. #} {% set _tokens_override_href = tokens_override_href or (request.state.dg_tokens_override_href if request.state else None) %} {% if _tokens_override_href %} {% endif %} {# CSRF double-submit wiring (lee cookie `dg_csrf` -> header `X-CSRF-Token` en HTMX, en `window.fetch` patcheado, y campo `csrf_token` en forms HTML). Imprescindible para que el form de logout del topbar (`
`) funcione con `CSRFMiddleware` activo. SIN `defer`: el monkey-patch de `fetch` debe estar activo antes de que cualquier ` {% block extra_head %}{% endblock %} {# Fallback a `request.state.dg_user`/`dg_clients` para handlers que no inyectan `user`/`clients` en el context Jinja (caso: routers del paquete que usan `templates.TemplateResponse` con un context propio). La host app aplica `populate_template_state` como dep global en cada `include_router`, que puebla `request.state.dg_user`. Sin este fallback el topbar pierde email/Admin/Salir en esas páginas (ej. /admin/users). #} {% set user = user or (request.state.dg_user if request.state else None) %}
{% block logo %} datagrowth {% endblock %}
{% block topbar_actions %}{% endblock %} {% from "dg/components/theme_toggle.html" import theme_toggle %} {{ theme_toggle() }} {% if user %} {% if user.is_admin %} Admin {% endif %} {% from "dg/components/csrf.html" import csrf_input %} {{ csrf_input(request) }} {% endif %}
{% block content %}{% endblock %}
{# Región de toasts. `register_error_handlers` (datagrowth_common.errors) puede inyectar un toast inicial server-side vía el bloque `toast_initial` cuando renderiza `dg/pages/error.html`. El mismo contenedor recibe OOB swaps desde HTMX y toasts client-side. #}
{% block toast_initial %}{% endblock %}
{% block extra_body %}{% endblock %}