Add app label to root header and auto-reload sexp templates in dev
Show current subdomain name (blog, cart, events, etc.) next to the site title in the root header row. Remove the redundant second "cart" menu row from cart overview and checkout error pages. Add dev-mode hot-reload for sexp templates: track file mtimes and re-read changed files per-request when RELOAD=true, so .sexp edits are picked up without restarting services. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -98,6 +98,7 @@ async def base_context() -> dict:
|
||||
"qs_filter": _qs_filter_fn(),
|
||||
"print": print,
|
||||
"base_url": base_url,
|
||||
"app_label": current_app.name,
|
||||
"base_title": config()["title"],
|
||||
"hx_select": hx_select,
|
||||
"hx_select_search": hx_select_search,
|
||||
|
||||
@@ -114,6 +114,14 @@ def create_base_app(
|
||||
setup_sexp_bridge(app)
|
||||
load_shared_components()
|
||||
load_relation_registry()
|
||||
|
||||
# Dev-mode: auto-reload sexp templates when files change on disk
|
||||
if os.getenv("RELOAD") == "true":
|
||||
from shared.sexp.jinja_bridge import reload_if_changed
|
||||
|
||||
@app.before_request
|
||||
async def _sexp_hot_reload():
|
||||
reload_if_changed()
|
||||
errors(app)
|
||||
|
||||
# Auto-register OAuth client blueprint for non-account apps
|
||||
|
||||
@@ -9,10 +9,11 @@ from __future__ import annotations
|
||||
|
||||
import os
|
||||
|
||||
from .jinja_bridge import load_sexp_dir
|
||||
from .jinja_bridge import load_sexp_dir, watch_sexp_dir
|
||||
|
||||
|
||||
def load_shared_components() -> None:
|
||||
"""Register all shared s-expression components."""
|
||||
templates_dir = os.path.join(os.path.dirname(__file__), "templates")
|
||||
load_sexp_dir(templates_dir)
|
||||
watch_sexp_dir(templates_dir)
|
||||
|
||||
@@ -36,6 +36,7 @@ def root_header_html(ctx: dict, *, oob: bool = False) -> str:
|
||||
cart_mini_html=ctx.get("cart_mini_html", ""),
|
||||
blog_url=call_url(ctx, "blog_url", ""),
|
||||
site_title=ctx.get("base_title", ""),
|
||||
app_label=ctx.get("app_label", ""),
|
||||
nav_tree_html=ctx.get("nav_tree_html", ""),
|
||||
auth_menu_html=ctx.get("auth_menu_html", ""),
|
||||
nav_panel_html=ctx.get("nav_panel_html", ""),
|
||||
|
||||
@@ -53,11 +53,49 @@ def load_sexp_dir(directory: str) -> None:
|
||||
register_components(f.read())
|
||||
|
||||
|
||||
# ---------------------------------------------------------------------------
|
||||
# Dev-mode auto-reload of sexp templates
|
||||
# ---------------------------------------------------------------------------
|
||||
|
||||
_watched_dirs: list[str] = []
|
||||
_file_mtimes: dict[str, float] = {}
|
||||
|
||||
|
||||
def watch_sexp_dir(directory: str) -> None:
|
||||
"""Register a directory for dev-mode file watching."""
|
||||
_watched_dirs.append(directory)
|
||||
# Seed mtimes
|
||||
for fp in sorted(
|
||||
glob.glob(os.path.join(directory, "*.sexp"))
|
||||
+ glob.glob(os.path.join(directory, "*.sexpr"))
|
||||
):
|
||||
_file_mtimes[fp] = os.path.getmtime(fp)
|
||||
|
||||
|
||||
def reload_if_changed() -> None:
|
||||
"""Re-read sexp files if any have changed on disk. Called per-request in dev."""
|
||||
changed = False
|
||||
for directory in _watched_dirs:
|
||||
for fp in sorted(
|
||||
glob.glob(os.path.join(directory, "*.sexp"))
|
||||
+ glob.glob(os.path.join(directory, "*.sexpr"))
|
||||
):
|
||||
mtime = os.path.getmtime(fp)
|
||||
if fp not in _file_mtimes or _file_mtimes[fp] != mtime:
|
||||
_file_mtimes[fp] = mtime
|
||||
changed = True
|
||||
if changed:
|
||||
_COMPONENT_ENV.clear()
|
||||
for directory in _watched_dirs:
|
||||
load_sexp_dir(directory)
|
||||
|
||||
|
||||
def load_service_components(service_dir: str) -> None:
|
||||
"""Load service-specific s-expression components from {service_dir}/sexp/."""
|
||||
sexp_dir = os.path.join(service_dir, "sexp")
|
||||
if os.path.isdir(sexp_dir):
|
||||
load_sexp_dir(sexp_dir)
|
||||
watch_sexp_dir(sexp_dir)
|
||||
|
||||
|
||||
def register_components(sexp_source: str) -> None:
|
||||
|
||||
@@ -96,7 +96,7 @@
|
||||
:class "w-12 h-12 rotate-180 transition-transform group-open/root:block hidden self-start"
|
||||
(path :d "M6 9l6 6 6-6" :fill "currentColor"))))
|
||||
|
||||
(defcomp ~header-row (&key cart-mini-html blog-url site-title
|
||||
(defcomp ~header-row (&key cart-mini-html blog-url site-title app-label
|
||||
nav-tree-html auth-menu-html nav-panel-html
|
||||
settings-url is-admin oob)
|
||||
(<>
|
||||
@@ -106,8 +106,10 @@
|
||||
(div :class "w-full flex flex-row items-top"
|
||||
(when cart-mini-html (raw! cart-mini-html))
|
||||
(div :class "font-bold text-5xl flex-1"
|
||||
(a :href (or blog-url "/") :class "flex justify-center md:justify-start"
|
||||
(h1 (or site-title ""))))
|
||||
(a :href (or blog-url "/") :class "flex justify-center md:justify-start items-baseline gap-2"
|
||||
(h1 (or site-title ""))
|
||||
(when app-label
|
||||
(span :class "!text-2xl font-normal text-white" app-label))))
|
||||
(nav :class "hidden md:flex gap-4 text-sm ml-2 justify-end items-center flex-0"
|
||||
(when nav-tree-html (raw! nav-tree-html))
|
||||
(when auth-menu-html (raw! auth-menu-html))
|
||||
|
||||
Reference in New Issue
Block a user