§10 - one menu everywhere (buildPluginChrome) + shell on every page; instructional starter dashboard; Kratos-native email docs

Collapse the three nav builders into buildPluginChrome: chrome.bestHref does longest-prefix matching so deep admin routes mark their leaf. Delete adminNav; buildConfirmModel and the admin model builders take the resolved nav. The same role-filtered sidebar now renders signed in or out, collapsing to a burger on narrow screens.

shell.ejs gains menu (default true; menu:false -> single-column .app-bare), docTitle (separate <title> from the topbar, so the body keeps the single <h1>), and hideSignIn (suppress the footer Sign-in on auth pages to avoid a login loop). auth/home/landing now render inside the shell.

Dashboard is a replaceable instructional starter (definePlugin snippet, no mock data). Email stays delegated to Kratos: documented its built-in courier.template_override_path instead of adding web-side SMTP.
This commit is contained in:
2026-06-23 21:26:00 +02:00
parent af097a8885
commit e22d24aa8a
24 changed files with 377 additions and 529 deletions
+31 -38
View File
@@ -1,40 +1,33 @@
<%#
Themed Kratos self-service page (todo §4): sign-in / register / reset / verify / settings.
Renders a FlowView (src/flow-view.ts) into the html-css-foundation auth layout, reusing the
auth-card + field partials. The form posts straight to flow.ui.action — Kratos owns its CSRF.
Auto theme follows the OS (styles.css), so no theme switch is shown here.
Themed Kratos self-service page (todo §4) inside the unified app shell (§10): sign-in / register /
reset / verify / settings. Renders a FlowView (src/flow-view.ts) into the shell content, reusing the
auth-card + field partials. The form posts straight to flow.ui.action — Kratos owns its CSRF. The
shell's menu is role-filtered (anonymous ⇒ public items + Sign in); the topbar carries no heading,
so the card's own <h1> is the page's single heading. Data: chrome (PageChrome), flow (FlowView).
%><%
const brand = locals.brand || "Plainpages";
const body = include("partials/flow-body", { flow });
%><!doctype html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title><%= flow.title %></title>
<link rel="stylesheet" href="/public/css/styles.css" />
<link rel="stylesheet" href="/public/css/auth.css" />
<link rel="icon" href="/public/favicon.svg" />
</head>
<body>
<%- include("partials/icons") %>
<main class="auth-stage">
<div class="auth">
<div class="auth-brand">
<span class="brand-mark"><svg class="ico ico-sm"><use href="#i-box" /></svg></span>
<span class="brand-name"><%= brand %></span>
</div>
<%- include("partials/auth-card", {
action: flow.action,
alt: flow.alt,
back: flow.back,
body,
method: flow.method,
sso: { providers: flow.sso },
sub: flow.sub,
title: flow.title,
}) %>
</div>
</main>
</body>
</html>
const nav = include("partials/nav-tree", { nodes: chrome.nav });
const card = include("partials/auth-card", {
action: flow.action,
alt: flow.alt,
back: flow.back,
body: include("partials/flow-body", { flow }),
method: flow.method,
sso: { providers: flow.sso },
sub: flow.sub,
title: flow.title,
});
const body = `<div class="shell-auth"><div class="auth">${card}</div></div>`;
-%>
<%- include("partials/shell", {
body,
brand: chrome.brand,
csrfToken: chrome.csrfToken,
docTitle: flow.title,
hideSignIn: true,
nav,
signInHref: chrome.signInHref,
styles: ["/public/css/auth.css"],
theme: chrome.theme,
title: "",
user: chrome.user,
}) %>