Retire shell-context and render the dashboard from ctx.chrome #188

Merged
lilleman merged 5 commits from item-24 into main 2026-10-31 08:45:45 +01:00
5 changed files with 15 additions and 10 deletions
Showing only changes of commit 437a2e8580 - Show all commits
+2 -2
View File
@@ -1825,8 +1825,8 @@ src/ The app — strict tsc, no build step. *.test.ts sit beside
plugin-host/ plugin.ts (the contract) · plugin-api.ts (the `@plainpages/plugin-api` barrel) · system.ts plugin-host/ plugin.ts (the contract) · plugin-api.ts (the `@plainpages/plugin-api` barrel) · system.ts
(ctx.system) · discovery · router · hooks · view-resolver · storage (the rules) · (ctx.system) · discovery · router · hooks · view-resolver · storage (the rules) ·
storage-provisioning (the DDL; bootstrap-only, holds the driver) storage-provisioning (the DDL; bootstrap-only, holds the driver)
ui/ chrome (the one global menu) · nav (composeNav) · ui/ chrome (`ctx.chrome`: menu, brand, user) · nav (composeNav) · menu-config
menu-config (`#menu-config`) · icons (lucide sprite builder) · list-query · paginate (`#menu-config`) · icons (lucide sprite builder) · list-query · paginate
plugin-api/ The `@plainpages/plugin-api` package — the author barrel, linked into /node_modules plugin-api/ The `@plainpages/plugin-api` package — the author barrel, linked into /node_modules
views/ Core EJS in the one app shell: home, index, auth, oauth-consent, error, 403/404/500/503, views/ Core EJS in the one app shell: home, index, auth, oauth-consent, error, 403/404/500/503,
+2
View File
@@ -65,6 +65,8 @@ test("the dashboard at /dashboard: the instructional starter in the unified shel
assert.match(html, /<aside class="sidebar"/); assert.match(html, /<aside class="sidebar"/);
// The default is a short instructional starter, not a mock-data list. // The default is a short instructional starter, not a mock-data list.
assert.match(html, /Starter dashboard/); assert.match(html, /Starter dashboard/);
assert.match(html, /<h1 class="page-title">Dashboard<\/h1>/); // the topbar heading …
assert.match(html, /<nav class="crumbs"[^>]*>\s*<span>Dashboard<\/span>/); // … and its breadcrumb
assert.match(html, /export default definePlugin/); // shows how to replace it from a plugin assert.match(html, /export default definePlugin/); // shows how to replace it from a plugin
assert.doesNotMatch(html, /<form class="filters"/); // the old mock People list is gone assert.doesNotMatch(html, /<form class="filters"/); // the old mock People list is gone
assert.doesNotMatch(html, /Avery Kline/); assert.doesNotMatch(html, /Avery Kline/);
+8 -1
View File
@@ -75,8 +75,15 @@ test("branding logo + default theme flow through when set", () => {
assert.equal(chrome.theme, "dark"); assert.equal(chrome.theme, "dark");
}); });
test("branding name and sub are translated; an unknown key renders as itself", () => {
const t = (key: string): string => (key === "brand.sub" ? "Undertitel" : key);
const chrome = buildPluginChrome({ menu: { branding: { name: "Acme", sub: "brand.sub" }, override: {} }, t });
assert.equal(chrome.brand.name, "Acme");
assert.equal(chrome.brand.sub, "Undertitel");
assert.equal(buildPluginChrome({ menu: { branding: { name: "Acme" }, override: {} } }).brand.sub, undefined);
});
test("shellUser derives the profile from the real user; anonymous → Guest", () => { test("shellUser derives the profile from the real user; anonymous → Guest", () => {
assert.deepEqual(shellUser(null), { email: "", initials: "G", name: "Guest" }); assert.deepEqual(shellUser(null), { email: "", initials: "G", name: "Guest" });
// Real user: name = email local part, email kept, initials = first two letters of the local part.
assert.deepEqual(shellUser({ email: "ada@example.com", id: "u1", permissions: [] }), { email: "ada@example.com", initials: "AD", name: "ada" }); assert.deepEqual(shellUser({ email: "ada@example.com", id: "u1", permissions: [] }), { email: "ada@example.com", initials: "AD", name: "ada" });
}); });
+1 -3
View File
@@ -70,8 +70,7 @@ export function buildPluginChrome(opts: ChromeOptions): PageChrome {
}; };
} }
// The User carries no display name (the JWT holds only id/email/permissions), so the profile shows // The JWT holds only id/email/permissions, so the profile name is the email's local part.
// the email's local part as the name; anonymous ⇒ "Guest".
export function shellUser(user: User | null | undefined, t: Translate = ENGLISH): ShellUser { export function shellUser(user: User | null | undefined, t: Translate = ENGLISH): ShellUser {
if (!user) { if (!user) {
const guest = t("shell.guest"); const guest = t("shell.guest");
@@ -81,7 +80,6 @@ export function shellUser(user: User | null | undefined, t: Translate = ENGLISH)
return { email: user.email, initials: (local.slice(0, 2) || "U").toUpperCase(), name: local }; return { email: user.email, initials: (local.slice(0, 2) || "U").toUpperCase(), name: local };
} }
// `name`/`sub` pass through `t`, so a catalog key is translated and an operator's own wording renders as written.
function branding(menu: MenuConfig, t: Translate): { logo?: string; name: string; sub?: string; theme?: string } { function branding(menu: MenuConfig, t: Translate): { logo?: string; name: string; sub?: string; theme?: string } {
const b = menu.branding; const b = menu.branding;
return { return {
+2 -4
View File
@@ -1,8 +1,6 @@
<%# <%#
Dashboard: the gated post-login app home. The built-in default is a short instructional The built-in /dashboard starter, shown until a plugin's `dashboard` handler replaces it.
starter — what /dashboard is and how to replace it from a plugin. Composes the app shell (the one Data: chrome (PageChrome).
global menu via model.nav) around the prose. A plugin owns the real dashboard via a `dashboard`
handler; this renders until then. Data: chrome (PageChrome).
%><% %><%
const nav = include("partials/nav-tree", { nodes: chrome.nav }); const nav = include("partials/nav-tree", { nodes: chrome.nav });
// These four messages carry markup (<code> spans), so they render raw — the documented // These four messages carry markup (<code> spans), so they render raw — the documented