Skip to content

Commit be22d2b

Browse files
committed
Add environment banner coverage across public Account Admin and Owner pages - PR_26169_024-environment-banner-all-pages
1 parent d421644 commit be22d2b

13 files changed

Lines changed: 1764 additions & 3806 deletions

.env.example

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,17 @@
44
# Configure the runtime auth and database connections here.
55
# Missing connection configuration reports diagnostics instead of falling back.
66

7+
# Browser-safe public site configuration exposed through the server API.
8+
# These values may be sent to browser pages. Do not place secrets here.
9+
GAMEFOUNDRY_SITE_URL=
10+
GAMEFOUNDRY_API_URL=
11+
# Valid environment banner labels:
12+
# - Development Environment
13+
# - Integration Testing Environment
14+
# - User Acceptance Testing Environment
15+
# - Production
16+
GAMEFOUNDRY_ENVIRONMENT_LABEL="Development Environment"
17+
718
# Database SSL mode is required and authoritative.
819
# Supported values:
920
# - disable: plain TCP Postgres

assets/theme-v2/js/gamefoundry-partials.js

Lines changed: 90 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -405,13 +405,14 @@
405405
}
406406
}
407407

408-
function normalizedPlatformBanner(data) {
409-
const banner = data?.banner && typeof data.banner === "object" ? data.banner : {};
408+
function normalizedPlatformBanner(data, fallbackSource) {
409+
const banner = data && typeof data === "object" ? data : {};
410410
const tone = ["info", "warning", "danger"].includes(banner.tone) ? banner.tone : "info";
411411
return {
412412
active: banner.active === true,
413413
message: typeof banner.message === "string" ? banner.message.trim() : "",
414-
sourceTable: typeof banner.sourceTable === "string" ? banner.sourceTable : data?.sourceTable || "",
414+
source: typeof banner.source === "string" ? banner.source : fallbackSource || "platform-settings",
415+
sourceTable: typeof banner.sourceTable === "string" ? banner.sourceTable : "",
415416
sourceTableRowKey: typeof banner.sourceTableRowKey === "string" ? banner.sourceTableRowKey : "",
416417
tone
417418
};
@@ -428,7 +429,32 @@
428429
if (!response.ok || payload?.ok === false) {
429430
throw new Error(payload?.error || "Platform banner settings are unavailable.");
430431
}
431-
return normalizedPlatformBanner(payload?.data);
432+
const data = payload?.data || {};
433+
return {
434+
banner: normalizedPlatformBanner({
435+
...(data.banner || {}),
436+
sourceTable: data.banner?.sourceTable || data.sourceTable || "",
437+
}, "platform-settings"),
438+
diagnostics: data.diagnostics || data.banner || {}
439+
};
440+
}
441+
442+
async function requestEnvironmentBanner() {
443+
const response = await fetch("/api/public/config", {
444+
headers: { "Accept": "application/json" },
445+
method: "GET"
446+
});
447+
const payload = await response.json().catch(function () {
448+
return null;
449+
});
450+
if (!response.ok || payload?.ok === false) {
451+
throw new Error(payload?.error || "Public configuration is unavailable.");
452+
}
453+
const data = payload?.data || {};
454+
return {
455+
banner: normalizedPlatformBanner(data.environmentBanner || {}, "environment-config"),
456+
diagnostics: data.diagnostics || {}
457+
};
432458
}
433459

434460
function removePlatformBanner() {
@@ -442,6 +468,7 @@
442468
section.className = "platform-banner platform-banner--" + banner.tone;
443469
section.dataset.platformBanner = "";
444470
section.dataset.platformBannerPlacement = placement;
471+
section.dataset.platformBannerSource = banner.source || "platform-settings";
445472
section.setAttribute("aria-label", "Platform notice");
446473
const inner = document.createElement("div");
447474
inner.className = "platform-banner__inner";
@@ -453,42 +480,74 @@
453480
return section;
454481
}
455482

456-
async function renderPlatformBanner() {
457-
try {
458-
const banner = await requestPlatformBanner();
459-
window.GameFoundryPlatformBannerDiagnostics = {
460-
active: banner.active,
461-
message: banner.message,
462-
sourceTable: banner.sourceTable,
463-
sourceTableRowKey: banner.sourceTableRowKey
464-
};
465-
removePlatformBanner();
466-
if (!banner.active || !banner.message) {
467-
return;
468-
}
469-
const header = document.querySelector("header.site-header");
470-
if (header) {
471-
header.after(createPlatformBanner(banner, "header"));
483+
function platformBannerDiagnostics(banner) {
484+
return {
485+
active: banner.active,
486+
message: banner.message,
487+
sourceTable: banner.sourceTable,
488+
sourceTableRowKey: banner.sourceTableRowKey
489+
};
490+
}
491+
492+
function renderBannerPlacement(banners, placement) {
493+
const nodes = banners.map((banner) => createPlatformBanner(banner, placement));
494+
const header = document.querySelector("header.site-header");
495+
if (placement === "header" && header) {
496+
header.after(...nodes);
497+
return;
498+
}
499+
const footer = document.querySelector("footer.footer");
500+
if (placement === "footer" && footer?.parentNode) {
501+
footer.before(...nodes);
502+
return;
503+
}
504+
if (placement === "header") {
505+
const main = document.querySelector("main");
506+
if (main) {
507+
main.before(...nodes);
472508
}
473-
const footer = document.querySelector("footer.footer");
474-
if (footer?.parentNode) {
475-
footer.parentNode.insertBefore(createPlatformBanner(banner, "footer"), footer);
509+
}
510+
}
511+
512+
async function renderPlatformBanner() {
513+
const [environmentResponse, platformResponse] = await Promise.allSettled([
514+
requestEnvironmentBanner(),
515+
requestPlatformBanner()
516+
]);
517+
const banners = [];
518+
removePlatformBanner();
519+
if (environmentResponse.status === "fulfilled") {
520+
const banner = environmentResponse.value.banner;
521+
window.GameFoundryEnvironmentBannerDiagnostics = environmentResponse.value.diagnostics || {};
522+
if (banner.active && banner.message) {
523+
banners.push(banner);
476524
}
477-
if (!header) {
478-
const main = document.querySelector("main");
479-
if (main?.parentNode) {
480-
main.parentNode.insertBefore(createPlatformBanner(banner, "header"), main);
481-
}
525+
} else {
526+
window.GameFoundryEnvironmentBannerDiagnostics = {
527+
environmentBannerActive: false,
528+
environmentLabelConfigured: false,
529+
secretsExposed: false
530+
};
531+
console.warn("[platform-settings/operator] Public configuration unavailable:", environmentResponse.reason instanceof Error ? environmentResponse.reason.message : String(environmentResponse.reason || ""));
532+
}
533+
if (platformResponse.status === "fulfilled") {
534+
const banner = platformResponse.value.banner;
535+
window.GameFoundryPlatformBannerDiagnostics = platformResponse.value.diagnostics || platformBannerDiagnostics(banner);
536+
if (banner.active && banner.message) {
537+
banners.push(banner);
482538
}
483-
} catch (error) {
484-
removePlatformBanner();
539+
} else {
485540
window.GameFoundryPlatformBannerDiagnostics = {
486541
active: false,
487542
message: "",
488543
sourceTable: "",
489544
sourceTableRowKey: ""
490545
};
491-
console.warn("[platform-settings/operator] Platform banner unavailable:", error instanceof Error ? error.message : String(error || ""));
546+
console.warn("[platform-settings/operator] Platform banner unavailable:", platformResponse.reason instanceof Error ? platformResponse.reason.message : String(platformResponse.reason || ""));
547+
}
548+
if (banners.length) {
549+
renderBannerPlacement(banners, "header");
550+
renderBannerPlacement(banners, "footer");
492551
}
493552
}
494553

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# PR_26169_024-environment-banner-all-pages
2+
3+
## Summary
4+
Added a server-safe public config route for environment banner data and rendered it through the existing shared Theme V2 `.platform-banner` pattern on shared-layout pages. The existing DB-backed platform settings banner remains intact and can coexist with the environment banner.
5+
6+
## Branch Guard
7+
- Current branch: `main`
8+
- Expected branch: `main`
9+
- Local branches found: `main`
10+
- Branch validation: PASS
11+
12+
## Requirement Checklist
13+
| Requirement | Status | Evidence |
14+
| --- | --- | --- |
15+
| Environment banner appears consistently on shared public/root pages | PASS | `EnvironmentBannerCoverage.spec.mjs` checks `/index.html`. |
16+
| Environment banner appears on Marketplace pages | PASS | `EnvironmentBannerCoverage.spec.mjs` checks `/marketplace/index.html`. |
17+
| Environment banner appears on Learn pages | PASS | `EnvironmentBannerCoverage.spec.mjs` checks `/learn/index.html`. |
18+
| Environment banner appears on Account pages | PASS | `EnvironmentBannerCoverage.spec.mjs` checks `/account/sign-in.html`. |
19+
| Environment banner appears on Admin pages | PASS | `EnvironmentBannerCoverage.spec.mjs` checks `/admin/system-health.html`. |
20+
| Environment banner appears on Owner pages | PASS | `EnvironmentBannerCoverage.spec.mjs` checks `/owner/memberships.html`. |
21+
| Environment banner appears on Legal pages if present | PASS | `EnvironmentBannerCoverage.spec.mjs` checks `/legal/disclaimer.html`. |
22+
| Environment banner appears on Memberships page if present | PASS | `EnvironmentBannerCoverage.spec.mjs` checks `/memberships/index.html`. |
23+
| No second competing banner system | PASS | Reused `.platform-banner` markup/classes and existing shared-layout renderer in `gamefoundry-partials.js`. |
24+
| Banner data comes from server-safe `.env` config | PASS | `/api/public/config` returns only `publicConfig`, `environmentBanner`, and diagnostics from `process.env`. |
25+
| Browser does not read `.env` directly | PASS | Browser fetches `/api/public/config`; no `.env` browser access added. |
26+
| Browser does not receive secrets | PASS | `PublicEnvironmentConfig.test.mjs` verifies database URL, service role, and storage credentials names/values are absent. |
27+
| PRD/Production shows no banner by default | PASS | Node and Playwright tests verify `Production` hides environment banner. |
28+
| DEV/IST/UAT-style labels show visible banner | PASS | Tests use `Development Environment`; route displays configured non-production labels. |
29+
| Missing local API-backed label shows actionable diagnostic | PASS | Node and Playwright tests verify visible `GAMEFOUNDRY_ENVIRONMENT_LABEL`/`.env` diagnostic. |
30+
| Do not infer environment from hostname when config exists | PASS | Hostname is used only for missing-label local diagnostic; configured label controls display. |
31+
| Do not touch archive or samples | PASS | No `archive/v1-v2` or sample files changed. |
32+
33+
## Changed Files
34+
- `.env.example`
35+
- `assets/theme-v2/js/gamefoundry-partials.js`
36+
- `src/dev-runtime/server/local-api-router.mjs`
37+
- `tests/dev-runtime/PublicEnvironmentConfig.test.mjs`
38+
- `tests/playwright/tools/EnvironmentBannerCoverage.spec.mjs`
39+
- `tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs`
40+
- `docs_build/pr/BUILD_PR_26169_024-environment-banner-all-pages.md`
41+
- `docs_build/dev/reports/environment_agnostic_browser_gate_report.md`
42+
- `docs_build/dev/reports/playwright_v8_coverage_report.txt`
43+
- `docs_build/dev/reports/coverage_changed_js_guardrail.txt`
44+
45+
## Validation
46+
| Command | Result |
47+
| --- | --- |
48+
| `node --check src/dev-runtime/server/local-api-router.mjs` | PASS |
49+
| `node --check assets/theme-v2/js/gamefoundry-partials.js` | PASS |
50+
| `node --check tests/dev-runtime/PublicEnvironmentConfig.test.mjs` | PASS |
51+
| `node --check tests/playwright/tools/EnvironmentBannerCoverage.spec.mjs` | PASS |
52+
| `node --check tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs` | PASS |
53+
| `node --test tests/dev-runtime/PublicEnvironmentConfig.test.mjs` | PASS, 3/3 |
54+
| `npm run validate:browser-env-agnostic` | PASS |
55+
| `npx playwright test tests/playwright/tools/EnvironmentBannerCoverage.spec.mjs` | PASS, 3/3 |
56+
| `npx playwright test tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs -g "Platform banner"` | PASS, 2/2 |
57+
| `npx playwright test tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs -g "Platform Settings Admin controls"` | PASS, 1/1 |
58+
| `npx playwright test tests/playwright/tools/AdminOwnerNavigationBoundary.spec.mjs` | PASS, 3/3 |
59+
| `git diff --check` | PASS with line-ending warnings only |
60+
61+
## Playwright
62+
- Playwright impacted: Yes.
63+
- Behavior validated: shared-layout environment banner rendering, missing-label diagnostic, Production hiding, platform banner coexistence, and Admin/Owner navigation after shared layout changes.
64+
- Expected pass behavior: configured non-production label renders header/footer environment banners; Production renders none; missing local label renders danger diagnostic; platform settings banner keeps its own source-specific rendering.
65+
- Expected fail behavior: missing safe public config route, secret exposure, absent banner on covered page families, or platform banner count collision would fail targeted tests.
66+
- V8 coverage: `docs_build/dev/reports/playwright_v8_coverage_report.txt` includes `assets/theme-v2/js/gamefoundry-partials.js` at 71%. Guardrail warnings are advisory and include unrelated HEAD-changed JS files from the coverage helper.
67+
68+
## Lanes
69+
- Executed:
70+
- runtime/config: public config API and safe env exposure.
71+
- shared Theme V2 page/runtime: shared banner renderer.
72+
- targeted admin/owner navigation: shared layout dependency check.
73+
- Skipped:
74+
- engine: no engine/runtime surface changed.
75+
- integration/workspace: no workspace, manifest, palette, or toolState handoff changed.
76+
- samples: SKIP, no samples touched and no broad sample runtime impact.
77+
- full suite: SKIP, targeted coverage proves the changed shared page/layout behavior.
78+
79+
## Manual Test
80+
1. Start `npm run dev:local-api` with `.env` containing `GAMEFOUNDRY_ENVIRONMENT_LABEL=Development Environment`.
81+
2. Open `http://127.0.0.1:5501/index.html`, `/account/sign-in.html`, `/admin/system-health.html`, and `/owner/memberships.html`.
82+
3. Expected: the environment banner appears under the header and above the footer with the configured label.
83+
4. Remove `GAMEFOUNDRY_ENVIRONMENT_LABEL`, restart the local API, and open `/index.html`.
84+
5. Expected: a visible diagnostic banner says to set `GAMEFOUNDRY_ENVIRONMENT_LABEL` in `.env`.
85+
6. Set `GAMEFOUNDRY_ENVIRONMENT_LABEL=Production`, restart the local API, and open `/index.html`.
86+
7. Expected: no environment banner is shown by default.
Lines changed: 24 additions & 57 deletions
Original file line numberDiff line numberDiff line change
@@ -1,57 +1,24 @@
1-
admin/analytics.html
2-
admin/branding.html
3-
admin/controls.html
4-
admin/design-system.html
5-
admin/grouping-colors.html
6-
admin/moderation.html
7-
admin/ratings.html
8-
admin/roles.html
9-
admin/site-settings.html
10-
admin/themes.html
11-
assets/theme-v2/js/account-achievements.js
12-
assets/theme-v2/js/admin-db-status-panel.js
13-
assets/theme-v2/js/admin-owner-navigation.js
14-
assets/theme-v2/js/admin-service-page-data.js
15-
assets/theme-v2/js/gamefoundry-partials.js
16-
docs_build/dev/reports/codex_changed_files.txt
17-
docs_build/dev/reports/codex_review.diff
18-
docs_build/dev/reports/coverage_changed_js_guardrail.txt
19-
docs_build/dev/reports/playwright_v8_coverage_report.txt
20-
docs_build/dev/reports/PR_26169_023-api-menu-path-cleanup.md
21-
docs_build/pr/BUILD_PR_26169_023-api-menu-path-cleanup.md
22-
owner/branding.html
23-
owner/design-system.html
24-
owner/grouping-colors.html
25-
owner/site-settings.html
26-
owner/themes.html
27-
src/api/admin-infrastructure-api-client.js
28-
src/api/admin-invitations-api-client.js
29-
src/api/admin-operations-api-client.js
30-
src/api/admin-owner-navigation.js
31-
src/api/admin-setup-api-client.js
32-
src/api/admin-system-health-api-client.js
33-
src/api/ai-credits-api-client.js
34-
src/api/db-viewer-api-client.js
35-
src/api/legal-api-client.js
36-
src/api/marketplace-api-client.js
37-
src/api/memberships-api-client.js
38-
src/api/owner-ai-credits-api-client.js
39-
src/api/owner-memberships-api-client.js
40-
src/api/platform-settings-api-client.js
41-
src/api/server-api-client.js
42-
src/api/session-api-client.js
43-
src/api/toolbox-votes-api-client.js
44-
src/engine/api/server-api-client.js
45-
tests/dev-runtime/ApiMenuPathCleanup.test.mjs
46-
tests/dev-runtime/ArchitectureCleanupApiNavInvitations.test.mjs
47-
tests/playwright/tools/AdminOwnerNavigationBoundary.spec.mjs
48-
toolbox/assets/assets-api-client.js
49-
toolbox/colors/palette-api-client.js
50-
toolbox/controls/controls-api-client.js
51-
toolbox/game-configuration/game-configuration-api-client.js
52-
toolbox/game-design/game-design-api-client.js
53-
toolbox/game-journey/game-journey-api-client.js
54-
toolbox/game-workspace/game-workspace-api-client.js
55-
toolbox/objects/objects-api-client.js
56-
toolbox/tags/tags-api-client.js
57-
toolbox/tool-registry-api-client.js
1+
git status --short
2+
M .env.example
3+
M assets/theme-v2/js/gamefoundry-partials.js
4+
M docs_build/dev/reports/codex_changed_files.txt
5+
M docs_build/dev/reports/codex_review.diff
6+
M docs_build/dev/reports/coverage_changed_js_guardrail.txt
7+
M docs_build/dev/reports/environment_agnostic_browser_gate_report.md
8+
M docs_build/dev/reports/playwright_v8_coverage_report.txt
9+
M src/dev-runtime/server/local-api-router.mjs
10+
M tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs
11+
?? docs_build/dev/reports/PR_26169_024-environment-banner-all-pages.md
12+
?? docs_build/pr/BUILD_PR_26169_024-environment-banner-all-pages.md
13+
?? tests/dev-runtime/PublicEnvironmentConfig.test.mjs
14+
?? tests/playwright/tools/EnvironmentBannerCoverage.spec.mjs
15+
16+
git diff --stat -- . :!docs_build/dev/reports/codex_review.diff :!docs_build/dev/reports/codex_changed_files.txt
17+
.env.example | 6 +
18+
assets/theme-v2/js/gamefoundry-partials.js | 121 +++++++++++++++------
19+
.../dev/reports/coverage_changed_js_guardrail.txt | 44 +-------
20+
.../environment_agnostic_browser_gate_report.md | 20 ++--
21+
.../dev/reports/playwright_v8_coverage_report.txt | 80 ++------------
22+
src/dev-runtime/server/local-api-router.mjs | 92 ++++++++++++++++
23+
.../tools/AdminPlatformToolsWireframes.spec.mjs | 28 ++---
24+
7 files changed, 220 insertions(+), 171 deletions(-)

0 commit comments

Comments
 (0)