Stability fixes: plugin-scoped contexts for owned pages, absent-href guard, checked locale mounts
CI / full-gate (push) Successful in 2m37s

This commit is contained in:
2026-08-03 23:51:40 +02:00
parent 2b20497785
commit be3bc2bdbb
28 changed files with 176 additions and 58 deletions
+1 -1
View File
@@ -17,7 +17,7 @@
const providers = (sso && sso.providers) || [];
const alt = locals.alt;
-%>
<form class="auth-card" method="<%= method %>"<% if (locals.action) { %> action="<%= locals.action %>"<% } %>>
<form class="auth-card" method="<%= method %>"<% if (locals.action) { %> action="<%= localeHref(locals.action) %>"<% } %>>
<div class="auth-head"><% if (back) { %><a class="auth-back" href="<%= localeHref(back.href) %>"><svg class="ico ico-sm" aria-hidden="true"><use href="#i-arrow-left"/></svg><%= back.label %></a><% } %><h1><%= locals.title %></h1><% if (locals.sub) { %><p class="auth-sub"><%= locals.sub %></p><% } %></div>
<% if (providers.length) { -%>
<div class="sso" aria-label="<%= sso.label || t("auth.sso.label") %>">
+3 -3
View File
@@ -11,14 +11,14 @@
Item ∈ { head } · { sep } · { label, icon?, href? ⇒ <a>, hreflang?, current?, danger? } (default: menu-item button)
· { group: { legend?, name, control?(="checkbox"|"radio"), options:{value,label,checked?}[] } }
%><%
const t = locals.trigger || {};
const sumCls = "class" in t ? t.class : "btn";
const trigger = locals.trigger || {}; // not `t` — that name is the translator in every view
const sumCls = "class" in trigger ? trigger.class : "btn";
const items = locals.items || [];
const popCls = "menu-pop" + (locals.align === "left" ? " left" : "") + (locals.up ? " up" : "");
const width = locals.width;
-%>
<details class="menu<%= locals.kebab ? " kebab" : "" %>"<%= locals.open ? " open" : "" %>>
<summary<% if (sumCls) { %> class="<%= sumCls %>"<% } %><% if (t.label) { %> aria-label="<%= t.label %>"<% } %>><% if (t.html != null) { %><%- t.html %><% } else { if (t.icon) { %><svg class="ico ico-sm"><use href="#<%= t.icon %>"/></svg><% } if (t.text) { %><%= t.text %><% } } %></summary>
<summary<% if (sumCls) { %> class="<%= sumCls %>"<% } %><% if (trigger.label) { %> aria-label="<%= trigger.label %>"<% } %>><% if (trigger.html != null) { %><%- trigger.html %><% } else { if (trigger.icon) { %><svg class="ico ico-sm"><use href="#<%= trigger.icon %>"/></svg><% } if (trigger.text) { %><%= trigger.text %><% } } %></summary>
<div class="<%= popCls %>"<% if (width != null) { %> style="min-width:<%= typeof width === "number" ? width + "px" : width %>"<% } %>>
<% items.forEach((it) => { -%>
<% if (it.head != null) { -%>
+1 -1
View File
@@ -70,7 +70,7 @@
<div class="menu-head"><%= t("shell.signedInAs", { name: user.name }) %></div>
<button class="menu-item" type="button"><svg class="ico"><use href="#i-user" /></svg><%= t("shell.profile") %></button>
<%# Sign out is a state change → a POST form (not a GET link), CSRF-guarded by app.ts %>
<form class="menu-item-form" method="post" action="/logout">
<form class="menu-item-form" method="post" action="<%= localeHref("/logout") %>">
<input type="hidden" name="_csrf" value="<%= locals.csrfToken || '' %>" />
<button class="menu-item danger" type="submit"><svg class="ico"><use href="#i-logout" /></svg><%= t("shell.signOut") %></button>
</form>