fix: keep dashboard shell dimensions stable #41

Merged
Zayin_Admin merged 2 commits from fix/signage-dashboard-layout into main 2026-09-29 23:55:56 +00:00
4 changed files with 37 additions and 7 deletions
+2 -2
View File
@@ -1,11 +1,11 @@
<!doctype html> <!doctype html>
<html lang="en"> <html lang="en" style="min-width: 0; scrollbar-gutter: stable">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<meta content="width=device-width, initial-scale=1" name="viewport" /> <meta content="width=device-width, initial-scale=1" name="viewport" />
<title>Ekklesia</title> <title>Ekklesia</title>
</head> </head>
<body> <body style="margin: 0; min-width: 0">
<!-- The shell's root. A module UI never receives this element: it is given a container the <!-- The shell's root. A module UI never receives this element: it is given a container the
shell creates inside its own layout. --> shell creates inside its own layout. -->
<div id="ekklesia-host"></div> <div id="ekklesia-host"></div>
+12 -3
View File
@@ -115,8 +115,10 @@ const shellStyles = {
actions: { display: "flex", gap: designToken("spacing.sm"), justifyContent: "center" }, actions: { display: "flex", gap: designToken("spacing.sm"), justifyContent: "center" },
aside: { aside: {
borderRight: `${designToken("border.width")} solid ${designToken("border.divider")}`, borderRight: `${designToken("border.width")} solid ${designToken("border.divider")}`,
boxSizing: "border-box",
minWidth: "13rem", minWidth: "13rem",
padding: designToken("spacing.lg"), padding: designToken("spacing.lg"),
width: "13rem",
}, },
groupLabel: { groupLabel: {
color: designToken("text.muted"), color: designToken("text.muted"),
@@ -140,10 +142,17 @@ const shellStyles = {
color: designToken("text.primary"), color: designToken("text.primary"),
display: "grid", display: "grid",
fontFamily: designToken("typography.family-sans"), fontFamily: designToken("typography.family-sans"),
gridTemplateColumns: "auto 1fr", gridTemplateColumns: "13rem minmax(0, 1fr)",
minHeight: "100vh", minHeight: "100vh",
minWidth: 0,
width: "100%",
},
main: {
boxSizing: "border-box",
minWidth: 0,
overflowX: "auto",
padding: designToken("spacing.lg"),
}, },
main: { padding: designToken("spacing.lg") },
message: { message: {
color: designToken("text.secondary"), color: designToken("text.secondary"),
display: "grid", display: "grid",
@@ -531,7 +540,7 @@ export function HostApp(props: HostAppProps): ReactElement {
const { groups, ungrouped } = bootstrap.navigation; const { groups, ungrouped } = bootstrap.navigation;
return ( return (
<div style={shellStyles.layout}> <div data-testid="host-shell" style={shellStyles.layout}>
<header style={shellStyles.header}> <header style={shellStyles.header}>
<h1 style={shellStyles.heading}>Ekklesia</h1> <h1 style={shellStyles.heading}>Ekklesia</h1>
<Button data-testid="host-logout" onClick={() => void signOut()}> <Button data-testid="host-logout" onClick={() => void signOut()}>
+9 -2
View File
@@ -116,7 +116,10 @@ export function ModuleUiFrame(props: ModuleUiFrameProps): ReactElement {
}, [config, descriptor, importModule, onAuthorizationResponse, principal]); }, [config, descriptor, importModule, onAuthorizationResponse, principal]);
return ( return (
<section aria-live="polite" style={{ display: "grid", gap: "0.75rem" }}> <section
aria-live="polite"
style={{ display: "grid", gap: "0.75rem", minWidth: 0, width: "100%" }}
>
{status === "loading" ? ( {status === "loading" ? (
<p data-testid="module-loading" style={{ color: "#57534e", margin: 0 }}> <p data-testid="module-loading" style={{ color: "#57534e", margin: 0 }}>
Loading module… Loading module…
@@ -128,7 +131,11 @@ export function ModuleUiFrame(props: ModuleUiFrameProps): ReactElement {
to check the installation. to check the installation.
</p> </p>
) : undefined} ) : undefined}
<div data-testid="module-container" ref={containerRef} /> <div
data-testid="module-container"
ref={containerRef}
style={{ minWidth: 0, width: "100%" }}
/>
</section> </section>
); );
} }
+14
View File
@@ -971,6 +971,20 @@ describe("Ekklesia shell — Dashboard navigation", () => {
}); });
describe("Ekklesia shell — mount lifecycle", () => { describe("Ekklesia shell — mount lifecycle", () => {
it("keeps fixed shell columns while module content changes width", async () => {
await render({ apiBaseUrl, fetch: platformFetch() }, async () => artifact("example", "0.1.0"));
const shell = query("host-shell") as HTMLElement;
const aside = shell.querySelector("aside") as HTMLElement;
const main = shell.querySelector("main") as HTMLElement;
const frame = query("module-container") as HTMLElement;
expect(shell.style.gridTemplateColumns).toBe("13rem minmax(0, 1fr)");
expect(aside.style.width).toBe("13rem");
expect(main.style.minWidth).toBe("0");
expect(main.style.overflowX).toBe("auto");
expect(frame.style.minWidth).toBe("0");
});
it("mounts the first module into the host's own container", async () => { it("mounts the first module into the host's own container", async () => {
const mountedInto: Element[] = []; const mountedInto: Element[] = [];
await render({ apiBaseUrl, fetch: platformFetch() }, async () => await render({ apiBaseUrl, fetch: platformFetch() }, async () =>