Remove completed todo.md + html-css-foundation mockups; strip dead §N phase refs from comments/docs (simplify visual E2E to drop the mockup-comparison oracle)

This commit is contained in:
2026-06-23 22:49:28 +02:00
parent e22d24aa8a
commit a9f25a7692
143 changed files with 394 additions and 1538 deletions
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
OAuth2 client detail page (todo §6): the client-detail body (info · one-time secret · delete) in the
OAuth2 client detail page: the client-detail body (info · one-time secret · delete) in the
shell. Doubles as the post-register page when `created`/`secret` are set.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
OAuth2 client register page (todo §6): the client-form body captured into the app shell.
OAuth2 client register page: the client-form body captured into the app shell.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
const body = include("partials/client-form-body", { error: model.error, form: model.form });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
OAuth2 clients admin list (todo §6): apps that log in *through* us (Hydra). Same building blocks as
OAuth2 clients admin list: apps that log in *through* us (Hydra). Same building blocks as
the Roles screen, around the shell, backed by live Hydra OAuth2 clients (src/admin-clients.ts).
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Admin destructive-action confirmation page (todo §5): the confirm body in the app shell. Model
Admin destructive-action confirmation page: the confirm body in the app shell. Model
from buildConfirmModel: { message, confirm:{action,label}, cancelHref, nav, shell }.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Group admin detail / membership page (todo §5): the group-detail body in the app shell.
Group admin detail / membership page: the group-detail body in the app shell.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
const body = include("partials/group-detail-body", { add: model.add, csrfToken: model.csrfToken, del: model.delete, error: model.error, group: model.group, members: model.members });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Group admin create page (todo §5): the group-form body captured into the app shell.
Group admin create page: the group-form body captured into the app shell.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
const body = include("partials/group-form-body", { error: model.error, form: model.form });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Groups admin list (todo §5): the same building blocks as the Users screen, around the shell, but
Groups admin list: the same building blocks as the Users screen, around the shell, but
backed by live Keto subject sets (src/admin-groups.ts). Filter/sort/page round-trip the URL.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Role admin detail page (todo §5): the role-detail body (members · effective access) in the shell.
Role admin detail page: the role-detail body (members · effective access) in the shell.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
const body = include("partials/role-detail-body", { add: model.add, csrfToken: model.csrfToken, del: model.delete, effective: model.effective, error: model.error, members: model.members, role: model.role });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Role admin create page (todo §5): the role-form body captured into the app shell.
Role admin create page: the role-form body captured into the app shell.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
const body = include("partials/role-form-body", { error: model.error, form: model.form });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Roles admin list (todo §5): the same building blocks as the Groups screen, around the shell, backed
Roles admin list: the same building blocks as the Groups screen, around the shell, backed
by live Keto Role subject sets (src/admin-roles.ts). Filter/sort/page round-trip the URL.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Users admin create/edit page (todo §5): the user-form body captured into the app shell.
Users admin create/edit page: the user-form body captured into the app shell.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
const body = include("partials/user-form-body", { edit: model.edit, error: model.error, form: model.form, recovery: model.recovery });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Users admin list (todo §5): the same building blocks as the dashboard, around the shell, but
Users admin list: the same building blocks as the dashboard, around the shell, but
backed by live Kratos identities (src/admin-users.ts). Filter/sort/page all round-trip the URL.
%><%
const nav = include("partials/nav-tree", { nodes: model.nav });
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Themed Kratos self-service page (todo §4) inside the unified app shell (§10): sign-in / register /
Themed Kratos self-service page inside the unified app shell: 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,
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Public landing page (todo §10): the ungated "/", rendered inside the unified app shell so the menu
Public landing page: the ungated "/", rendered inside the unified app shell so the menu
shows (role-filtered — anonymous ⇒ public items + Sign in). A brief intro + a prominent way in, or a
dashboard link when already signed in. A plugin may replace this via its `home` handler.
Data: chrome (PageChrome), user (or null).
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Dashboard (todo §10): the gated post-login app home. The built-in default is a short instructional
Dashboard: the gated post-login app home. The built-in default is a short instructional
starter — what /dashboard is and how to replace it from a plugin. Composes the app shell (the one
global menu via model.nav) around the prose. A plugin owns the real dashboard via a `dashboard`
handler; this renders until then. Data: model { nav, shell }.
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Themed OAuth2 consent page (todo §6): shown when a third-party client wants access and the
Themed OAuth2 consent page: shown when a third-party client wants access and the
user must approve. Reuses the auth layout + auth-card; the form posts (Allow/Deny) to our own
/oauth2/consent route, CSRF-guarded (consent-body carries the token). Auto theme (styles.css).
%><%
+1 -1
View File
@@ -1,7 +1,7 @@
<%#
Auth card: the <form class="auth-card"> shell — head (back · title · sub),
optional SSO providers + divider, a body slot (field partials + submit), alt footer.
Mirrors html-css-foundation auth markup. Config:
Config:
title, sub?
method? (default "post"), action?
back? { href, label } back link above the title
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Admin OAuth2 client detail body (todo §6), captured into the shell content slot. Config:
Admin OAuth2 client detail body, captured into the shell content slot. Config:
client { firstParty, id, name, public, redirectUris[], scopes[] }
created bool just registered → success banner
secret? string one-time client secret (confidential clients), shown once right after create
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Admin OAuth2 client register form body (todo §6), captured into the shell content slot. Config:
Admin OAuth2 client register form body, captured into the shell content slot. Config:
form { action, csrfToken, submitLabel, cancelHref, nameField, scopeField (field.ejs configs),
redirectUris: string (newline-separated), public: bool, firstParty: bool }
error? string shown when a write was rejected
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Destructive-action confirm body (todo §5), captured into the shell content slot. Zero-JS: the
Destructive-action confirm body, captured into the shell content slot. Zero-JS: the
delete is a deliberate second step (a POST form), with a cancel link back. Config:
message string
confirm { action, label } the danger POST endpoint + button label
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
OAuth2 consent form body (todo §6): the inner of the auth-card form — the signed-in account, the
OAuth2 consent form body: the inner of the auth-card form — the signed-in account, the
CSRF + challenge hidden inputs, the requested scopes, then Allow / Deny submit buttons (one
`decision` field). Locals: account?, challenge, csrfField, csrfToken, scopes (string[]). Captured
by views/oauth-consent.ejs.
+1 -1
View File
@@ -1,6 +1,6 @@
<%#
Data table: sortable headers, row-select, typed cells, badges, kebab row actions.
Mirrors the html-css-foundation markup; zero-JS (sort = links, select highlight = CSS).
Zero-JS (sort = links, select highlight = CSS).
Config:
caption?, selectable?, actions? sr-only caption; toggle the check / kebab columns
columns: { label, sortable?, sort?: "asc"|"desc", href?, className? }[]
+1 -1
View File
@@ -1,6 +1,6 @@
<%#
Form field: label (+ inline link / "Optional") · icon input · hint · error.
Mirrors html-css-foundation auth markup. Config:
Config:
id, name, label
type? (default "text"), value?, placeholder?, autocomplete?, required?, readonly?, minlength?
inputmode?, pattern? virtual-keyboard hint + client-side validity regex (e.g. the OTP code)
+1 -2
View File
@@ -1,6 +1,5 @@
<%#
Filter bar: a real GET form so filtering is server-side and zero-JS. Mirrors the
html-css-foundation markup. Config:
Filter bar: a real GET form so filtering is server-side and zero-JS. Config:
rows: Control[][] rows of controls, laid out left→right
pills, clearHref, label, action, applyLabel
Control.type ∈ search | segmented | select | chips | daterange | spacer.
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Kratos flow form body (todo §4): the inner of the auth-card form — flow messages,
Kratos flow form body: the inner of the auth-card form — flow messages,
hidden inputs (incl. csrf_token), themed fields, then submit button(s). `flow` is a
FlowView (src/flow-view.ts). Captured by views/auth.ejs and handed to auth-card's body.
-%>
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Admin group membership body (todo §5), captured into the shell content slot. Config:
Admin group membership body, captured into the shell content slot. Config:
group { name }
members { action, rows: { kind:"group"|"user", label, subject }[] } action = remove-member endpoint
add { action, options: {label,value}[] } action = add-member endpoint
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Admin group create form body (todo §5), captured into the shell content slot. Config:
Admin group create form body, captured into the shell content slot. Config:
form { action, csrfToken, submitLabel, cancelHref, nameField: field.ejs config,
memberOptions: {label,value}[], selectedMember }
error? string shown when a write was rejected
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Public landing body (§10): hero + intro + a prominent way in (or a dashboard link when signed in).
Public landing body: hero + intro + a prominent way in (or a dashboard link when signed in).
Rendered into the app-shell content. Locals: brand (name), signedIn (bool).
%><div class="shell-auth">
<div class="landing">
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Popover menu: pure <details>/<summary>, zero-JS. Mirrors html-css-foundation .menu.
Popover menu: pure <details>/<summary>, zero-JS.
Config:
trigger { class?(="btn", "" ⇒ none) · label?(aria-label) · icon? · text? · html?(raw inner, wins) }
align? "left" left-align the popover (default right)
+2 -2
View File
@@ -1,8 +1,8 @@
<%#
Recursive nav tree. Each node: { label, href?, icon?, count?, current?, open?, children? }.
Shape is orthogonal: header (children → disclosure toggle + nested <ul>) vs leaf (spacer),
and clickable (href → <a>) vs static (<span>). Mirrors the html-css-foundation markup; the
CSS reveals children only when the sibling .nav-disc is [open]. `root` picks the outer class.
and clickable (href → <a>) vs static (<span>). The CSS reveals children only when the
sibling .nav-disc is [open]. `root` picks the outer class.
%><%
const nodes = locals.nodes || [];
const root = locals.root !== false;
+1 -1
View File
@@ -1,6 +1,6 @@
<%#
Pagination footer: rows-per-page (GET form) + page numbers. Query-param driven, zero-JS.
Mirrors html-css-foundation markup; page items are <a>, inert ones (current/ellipsis/disabled) aren't.
Page items are <a>, inert ones (current/ellipsis/disabled) aren't.
Config (all optional; never throws):
label? nav aria-label (default "Pagination")
summary? { from, to, total } → "fromto of <b>total</b>"
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Admin role detail body (todo §5), captured into the shell content slot. Config:
Admin role detail body, captured into the shell content slot. Config:
role { name }
members { action, rows: { kind:"group"|"user", label, subject }[] } action = revoke endpoint
effective { label }[] users who hold the role (expand)
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Admin role create form body (todo §5), captured into the shell content slot. Config:
Admin role create form body, captured into the shell content slot. Config:
form { action, csrfToken, submitLabel, cancelHref, nameField: field.ejs config,
memberOptions: {label,value}[], selectedMember }
error? string shown when a write was rejected
+3 -3
View File
@@ -1,6 +1,6 @@
<%#
App shell: sidebar (brand + nav slot + footer) · topbar · content slot. The one chrome every page
renders (§10) — dashboard, admin, plugin, login/registration/front — so the menu is identical
renders — dashboard, admin, plugin, login/registration/front — so the menu is identical
everywhere, role-filtered to the visitor (anonymous ⇒ public items + Sign in).
Slots are pre-rendered HTML locals — `nav` (sidebar tree, see nav-tree partial),
`actions` (topbar buttons), `body` (page content); `styles` is an optional array of
@@ -9,7 +9,7 @@
(the <title> tag; defaults to title or the brand), `brand` ({ name, logo?, sub? }), `theme`
(theme-switch default), `user`, `breadcrumbs`, `csrfToken` (the Sign-out form's hidden field),
`signInHref` (anonymous "Sign in" target; default /login). `menu` (default true) — set false to
drop the sidebar and render a focused single-column page (§10).
drop the sidebar and render a focused single-column page.
%><%
const brand = locals.brand || { name: "Plainpages" };
const title = locals.title || ""; // topbar heading; empty ⇒ no topbar <h1> (the body owns it)
@@ -76,7 +76,7 @@
</div>
</details>
<% } else if (!hideSignIn) { %>
<%# anonymous (a public page in the shell, §10): no session to end — offer a way in instead.
<%# anonymous (a public page in the shell): no session to end — offer a way in instead.
signInHref carries this page as return_to (chrome.signInHref); falls back to bare /login.
Suppressed on the auth pages themselves (hideSignIn) — a Sign-in there only loops back. %>
<a class="btn btn-primary" href="<%= locals.signInHref || '/login' %>" style="flex:1 1 auto"><svg class="ico ico-sm" aria-hidden="true"><use href="#i-user" /></svg>Sign in</a>
+1 -1
View File
@@ -1,5 +1,5 @@
<%#
Admin user create/edit form body (todo §5), captured into the shell content slot. Config:
Admin user create/edit form body, captured into the shell content slot. Config:
form { action, csrfToken, submitLabel, cancelHref, fields: field.ejs config[] }
edit? { nextLabel, stateAction, recoveryAction, deleteAction } (edit mode only)
recovery? { code? } shown after a recovery code is generated (recovery is code-based)