Skip to content

Commit 2485cf1

Browse files
committed
Move Admin navigation metadata behind API - PR_26160_081-navigation-db-contract-audit
1 parent aaff483 commit 2485cf1

6 files changed

Lines changed: 239 additions & 55 deletions

File tree

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

Lines changed: 91 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -137,27 +137,7 @@
137137
const currentScript = document.currentScript || document.querySelector("script[src*='gamefoundry-partials.js']");
138138
const assetRoot = currentScript ? new URL("../", currentScript.src) : null;
139139
const apiBackedLoginDiagnostic = "Use the API-backed local server for login. Run npm run dev:local-api and open http://127.0.0.1:5501/login.html.";
140-
const adminMainItems = Object.freeze([
141-
Object.freeze({ label: "Analytics", route: "admin-analytics" }),
142-
Object.freeze({ label: "Branding", route: "admin-branding" }),
143-
Object.freeze({ label: "Controls", route: "admin-controls" }),
144-
Object.freeze({ label: "Environments", route: "admin-environments" }),
145-
Object.freeze({ label: "Game Migration", route: "admin-game-migration" }),
146-
Object.freeze({ label: "Moderation", route: "admin-moderation" }),
147-
Object.freeze({ label: "Platform Settings", route: "admin-platform-settings" }),
148-
Object.freeze({ label: "Ratings", route: "admin-ratings" }),
149-
Object.freeze({ label: "Roles", route: "admin-roles" }),
150-
Object.freeze({ label: "Site Settings", route: "admin-site-settings" }),
151-
Object.freeze({ label: "Themes", route: "admin-themes" }),
152-
Object.freeze({ label: "Tool Votes", route: "admin-tool-votes" }),
153-
Object.freeze({ label: "Users", route: "admin-users" })
154-
]);
155-
const localAdminMyStuffItems = Object.freeze([
156-
Object.freeze({ label: "DB Viewer", route: "admin-db-viewer" }),
157-
Object.freeze({ label: "Design System", route: "admin-design-system" }),
158-
Object.freeze({ label: "Grouping Colors", route: "admin-grouping-colors" }),
159-
Object.freeze({ label: "Notes", href: "/admin/admin-notes.html", localNotes: true })
160-
]);
140+
let navigationAdminMenuCache = null;
161141

162142
function assetUrl(path) {
163143
if (!assetRoot) return rootPrefix() + path;
@@ -186,50 +166,128 @@
186166
return rootPrefix() + (routeMap[routeName] || routeName || "index.html");
187167
}
188168

169+
function pathHref(path) {
170+
const normalizedPath = String(path || "").replace(/^\/+/, "");
171+
return normalizedPath ? rootPrefix() + normalizedPath : "#";
172+
}
173+
189174
function isLocalDevMode(loginState) {
190175
return String(loginState?.mode || "").indexOf("local-") === 0;
191176
}
192177

178+
function missingNavigationMenu(diagnostic) {
179+
return {
180+
adminMainItems: [],
181+
diagnostic: diagnostic || "Admin navigation API did not return menu data.",
182+
localAdminMyStuffItems: [],
183+
source: "missing-api"
184+
};
185+
}
186+
187+
function normalizeNavigationItems(items) {
188+
return Array.isArray(items)
189+
? items.map(function (item) {
190+
return {
191+
href: typeof item.href === "string" ? item.href : "",
192+
label: typeof item.label === "string" ? item.label : "",
193+
localNotes: item.localNotes === true,
194+
path: typeof item.path === "string" ? item.path : "",
195+
route: typeof item.route === "string" ? item.route : ""
196+
};
197+
}).filter(function (item) {
198+
return item.label && (item.route || item.path || item.href);
199+
})
200+
: [];
201+
}
202+
203+
function readNavigationAdminMenu() {
204+
if (navigationAdminMenuCache) {
205+
return navigationAdminMenuCache;
206+
}
207+
try {
208+
const request = new XMLHttpRequest();
209+
request.open("GET", "/api/navigation/admin-menu", false);
210+
request.setRequestHeader("Accept", "application/json");
211+
request.send(null);
212+
const payload = request.responseText ? JSON.parse(request.responseText) : null;
213+
if (request.status < 200 || request.status >= 300 || payload?.ok === false) {
214+
if (request.status === 404 || request.status === 405) {
215+
throw new Error(localRouteUnavailableDiagnostic("GET", "/api/navigation/admin-menu", request.status));
216+
}
217+
throw new Error(payload?.error || "Navigation API did not return Admin menu data.");
218+
}
219+
const data = payload?.data || {};
220+
navigationAdminMenuCache = {
221+
adminMainItems: normalizeNavigationItems(data.adminMainItems),
222+
diagnostic: "",
223+
localAdminMyStuffItems: normalizeNavigationItems(data.localAdminMyStuffItems),
224+
source: data.source || "server-api"
225+
};
226+
return navigationAdminMenuCache;
227+
} catch (error) {
228+
navigationAdminMenuCache = missingNavigationMenu(error instanceof Error ? error.message : "");
229+
return navigationAdminMenuCache;
230+
}
231+
}
232+
233+
function menuItemHref(item) {
234+
if (item.path) {
235+
return pathHref(item.path);
236+
}
237+
if (item.route) {
238+
return routeHref(item.route);
239+
}
240+
return item.href || "#";
241+
}
242+
193243
function createMenuLink(item) {
194244
const link = document.createElement("a");
195245
link.dataset.navLink = "";
196246
if (item.route) {
197247
link.dataset.route = item.route;
198-
link.href = routeHref(item.route);
199-
} else {
200-
link.href = item.href || "#";
201248
}
249+
link.href = menuItemHref(item);
202250
if (item.localNotes) {
203251
link.dataset.adminNotesLocalMenu = "";
204252
}
205253
link.textContent = item.label;
206254
return link;
207255
}
208256

209-
function createLocalAdminMyStuffMenu() {
257+
function createLocalAdminMyStuffMenu(items) {
210258
const item = document.createElement("div");
211259
item.className = "nav-item nav-popout-item";
212260
item.dataset.adminMyStuffMenu = "";
213261

214262
const label = document.createElement("a");
215263
label.dataset.adminMyStuffLabel = "";
216-
label.href = "/admin/admin-notes.html";
264+
label.href = items[0] ? menuItemHref(items[0]) : "#";
217265
label.setAttribute("aria-haspopup", "true");
218266
label.textContent = "My Stuff \u25B8";
219267

220268
const submenu = document.createElement("div");
221269
submenu.className = "sub-menu sub-menu--nested";
222270
submenu.dataset.adminMyStuffSubmenu = "";
223271
submenu.setAttribute("aria-label", "My Stuff");
224-
localAdminMyStuffItems.forEach(function (menuItem) {
272+
items.forEach(function (menuItem) {
225273
submenu.append(createMenuLink(menuItem));
226274
});
227275

228276
item.append(label, submenu);
229277
return item;
230278
}
231279

280+
function createAdminNavigationDiagnostic(message) {
281+
const diagnostic = document.createElement("p");
282+
diagnostic.className = "status";
283+
diagnostic.dataset.adminNavigationDiagnostic = "";
284+
diagnostic.setAttribute("role", "status");
285+
diagnostic.textContent = "Admin navigation unavailable: " + (message || "Start the local server API and refresh.");
286+
return diagnostic;
287+
}
288+
232289
function createAdminMenu(loginState) {
290+
const navigationMenu = readNavigationAdminMenu();
233291
const item = document.createElement("div");
234292
item.className = "nav-item";
235293
item.dataset.adminMenu = "";
@@ -242,15 +300,18 @@
242300

243301
const submenu = document.createElement("div");
244302
submenu.className = "sub-menu";
245-
if (isLocalDevMode(loginState)) {
303+
if (navigationMenu.diagnostic) {
304+
submenu.append(createAdminNavigationDiagnostic(navigationMenu.diagnostic));
305+
}
306+
if (isLocalDevMode(loginState) && navigationMenu.localAdminMyStuffItems.length) {
246307
const separator = document.createElement("hr");
247308
separator.dataset.adminMyStuffSeparator = "";
248309
separator.setAttribute("role", "separator");
249310
separator.setAttribute("aria-disabled", "true");
250311
separator.tabIndex = -1;
251-
submenu.append(createLocalAdminMyStuffMenu(), separator);
312+
submenu.append(createLocalAdminMyStuffMenu(navigationMenu.localAdminMyStuffItems), separator);
252313
}
253-
adminMainItems.forEach(function (menuItem) {
314+
navigationMenu.adminMainItems.forEach(function (menuItem) {
254315
submenu.append(createMenuLink(menuItem));
255316
});
256317

docs_build/dev/reports/coverage_changed_js_guardrail.txt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,8 @@ Missing changed runtime JS files are WARN, not FAIL.
66
Source: Playwright/Chromium built-in V8 coverage from the active Playwright run.
77

88
Changed runtime JS files considered:
9-
(0%) src/engine/api/mock-db-viewer-ui.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
10-
(89%) toolbox/colors/colors.js - executed lines 2161/2161; executed functions 190/213
9+
(0%) src/dev-runtime/server/mock-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
10+
(77%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 603/603; executed functions 43/56
1111

1212
Guardrail warnings:
13-
(0%) src/engine/api/mock-db-viewer-ui.js - WARNING: changed runtime JS file missing from coverage; advisory only
13+
(0%) src/dev-runtime/server/mock-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# PR_26160_081 Navigation DB Contract Audit
2+
3+
Generated: 2026-06-09
4+
5+
## Branch Validation
6+
7+
| Check | Expected | Actual | Status |
8+
| --- | --- | --- | --- |
9+
| Current branch | `main` | `main` | PASS |
10+
11+
## Requirement Checklist
12+
13+
| Requirement | Status | Evidence |
14+
| --- | --- | --- |
15+
| Audit navigation/header route ownership in `assets/theme-v2/js/gamefoundry-partials.js` | PASS | `routeMap`, `adminMainItems`, and `localAdminMyStuffItems` classified below. |
16+
| Focus on `routeMap`, `adminMainItems`, `localAdminMyStuffItems` | PASS | Findings table covers all three. |
17+
| Report entries as product data, navigation config, or static shell behavior | PASS | Ownership table below. |
18+
| Move only safe Admin/Toolbox navigation metadata behind API/service contract | PASS | Admin menu item lists moved to `/api/navigation/admin-menu`; broad `routeMap` left as static shell behavior. |
19+
| Do not migrate Colors catalog, Project Journey suggestions, or creator-user | PASS | Those files were not modified. |
20+
| No inline script/style/event handlers | PASS | Changes are external JS only. |
21+
22+
## Navigation Ownership Audit
23+
24+
| Item | Classification | Decision | Reason |
25+
| --- | --- | --- | --- |
26+
| `routeMap` | Static shell behavior with mixed route aliases | KEPT in browser shell | It drives partial link rewriting, active-link detection, login redirect links, and root-relative path resolution across the full site. Moving the whole map would affect public navigation, account/admin protection, and every `data-route` partial link. |
27+
| Admin/Toolbox entries inside `routeMap` | Static shell route resolution, not order/status/group product metadata | KEPT in browser shell | The entries are route aliases used by header/path helpers. Toolbox/Admin product metadata remains DB/API-backed elsewhere. |
28+
| `adminMainItems` | Admin navigation config | MOVED behind API | Low-risk scoped migration. It only controls generated Admin submenu links for authenticated admins. |
29+
| `localAdminMyStuffItems` | Local/dev Admin navigation config | MOVED behind API | Low-risk scoped migration. It only controls local Admin My Stuff links for authenticated admins in local modes. |
30+
31+
## Implementation
32+
33+
| File | Change |
34+
| --- | --- |
35+
| `src/dev-runtime/server/mock-api-router.mjs` | Added DB/API-shaped Admin navigation contract data and served it from `GET /api/navigation/admin-menu`. The payload includes `adminMainItems`, `localAdminMyStuffItems`, `source`, and ownership classifications. |
36+
| `assets/theme-v2/js/gamefoundry-partials.js` | Removed browser-owned Admin menu item arrays. Admin menu rendering now reads `/api/navigation/admin-menu`; if the API is missing, the Admin submenu shows a visible diagnostic instead of falling back to hardcoded Admin item lists. |
37+
38+
## Deferred Items
39+
40+
| Item | Reason |
41+
| --- | --- |
42+
| Full `routeMap` migration | Too broad for PR_081. Needs a dedicated site navigation contract because it affects all site partial links and active-link behavior. |
43+
| Colors catalog | Explicitly out of scope for this PR. |
44+
| Project Journey suggestions | Explicitly out of scope for this PR. |
45+
| `creator-user` Workspace/Achievements scoping | Explicitly out of scope for this PR. |
46+
47+
## Validation
48+
49+
| Lane | Status | Command | Evidence |
50+
| --- | --- | --- | --- |
51+
| Branch guard | PASS | `git branch --show-current` | Returned `main`. |
52+
| Syntax | PASS | `node --check assets/theme-v2/js/gamefoundry-partials.js` | Exited 0. |
53+
| Syntax | PASS | `node --check src/dev-runtime/server/mock-api-router.mjs` | Exited 0. |
54+
| Navigation ownership static check | PASS | `rg -n "adminMainItems\|localAdminMyStuffItems\|routeMap" assets/theme-v2/js/gamefoundry-partials.js src/dev-runtime/server/mock-api-router.mjs` | Browser file no longer owns Admin menu item arrays; server route owns menu lists. |
55+
| Targeted Playwright | PASS | `npx playwright test tests/playwright/tools/LoginSessionMode.spec.mjs tests/playwright/tools/AdminPlatformToolsWireframes.spec.mjs tests/playwright/tools/ToolboxRoutePages.spec.mjs --reporter=line` | 20 passed. Covers header navigation, Admin menu rendering, My Stuff placement, Admin page navigation, and Toolbox routes. |
56+
| Static diff validation | PASS | `git diff --check` | No whitespace errors; Git reported expected LF/CRLF warning for `mock-api-router.mjs`. |
57+
58+
## Impacted Lanes
59+
60+
- Navigation/header runtime
61+
- Admin menu runtime
62+
- Local dev server API contract
63+
64+
## Skipped Lanes
65+
66+
| Lane | Reason |
67+
| --- | --- |
68+
| Full samples validation | Samples and sample loaders were not changed. |
69+
| Colors runtime | Explicitly out of scope. |
70+
| Project Journey runtime | Explicitly out of scope. |
71+
| Workspace/Achievements user scoping | Explicitly out of scope. |
72+
73+
## Manual Test Notes
74+
75+
No separate manual browser walkthrough was needed after targeted Playwright. The tests opened API-backed local pages and verified Admin/My Stuff menu placement and route visibility through the rendered header.

docs_build/dev/reports/playwright_v8_coverage_report.txt

Lines changed: 19 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -12,27 +12,34 @@ Note: entry percentages use function coverage when available, otherwise line cov
1212
Note: coverage entries are aggregated across every page/tool where coverageReporter.start(page) and coverageReporter.stop(page) ran.
1313

1414
Exercised tool entry points detected:
15-
(88%) Toolbox Index - exercised 3 runtime JS files
15+
(70%) Toolbox Index - exercised 9 runtime JS files
1616
(0%) Tool Template V2 - not exercised by this Playwright run
17-
(77%) Theme V2 Shared JS - exercised 3 runtime JS files
17+
(74%) Theme V2 Shared JS - exercised 2 runtime JS files
1818

1919
Changed runtime JS files covered:
20-
(0%) src/engine/api/mock-db-viewer-ui.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
21-
(89%) toolbox/colors/colors.js - executed lines 2161/2161; executed functions 190/213
20+
(0%) src/dev-runtime/server/mock-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
21+
(77%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 603/603; executed functions 43/56
2222

2323
Files with executed line/function counts where available:
24+
(38%) src/engine/api/session-api-client.js - executed lines 34/34; executed functions 3/8
25+
(55%) toolbox/project-journey/project-journey.js - executed lines 1003/1003; executed functions 54/99
2426
(58%) src/engine/api/server-api-client.js - executed lines 159/159; executed functions 11/19
27+
(59%) toolbox/colors/colors.js - executed lines 2161/2161; executed functions 120/205
2528
(64%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 9/14
26-
(75%) toolbox/tool-registry-api-client.js - executed lines 152/152; executed functions 21/28
27-
(77%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 548/548; executed functions 37/48
28-
(89%) toolbox/colors/colors.js - executed lines 2161/2161; executed functions 190/213
29-
(92%) assets/theme-v2/js/account-controls.js - executed lines 47/47; executed functions 12/13
29+
(71%) toolbox/assets/assets.js - executed lines 519/519; executed functions 42/59
30+
(77%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 603/603; executed functions 43/56
31+
(83%) src/engine/api/toolbox-votes-api-client.js - executed lines 46/46; executed functions 5/6
32+
(89%) toolbox/tool-registry-api-client.js - executed lines 152/152; executed functions 25/28
33+
(92%) admin/tool-votes.js - executed lines 378/378; executed functions 47/51
34+
(96%) toolbox/tools-page-accordions.js - executed lines 954/954; executed functions 109/113
35+
(100%) toolbox/assets/assets-api-client.js - executed lines 17/17; executed functions 3/3
3036
(100%) toolbox/colors/palette-api-client.js - executed lines 19/19; executed functions 4/4
37+
(100%) toolbox/project-journey/project-journey-api-client.js - executed lines 12/12; executed functions 2/2
38+
(100%) toolbox/project-workspace/project-workspace-api-client.js - executed lines 12/12; executed functions 2/2
3139

3240
Uncovered or low-coverage changed JS files:
33-
(0%) src/engine/api/mock-db-viewer-ui.js - WARNING: uncovered changed runtime JS file; advisory only
41+
(0%) src/dev-runtime/server/mock-api-router.mjs - WARNING: uncovered changed runtime JS file; advisory only
3442

3543
Changed JS files considered:
36-
(0%) src/engine/api/mock-db-viewer-ui.js - changed JS file not collected as browser runtime coverage
37-
(0%) tests/playwright/tools/AdminDbViewer.spec.mjs - changed JS file not collected as browser runtime coverage
38-
(89%) toolbox/colors/colors.js - changed JS file with browser V8 coverage
44+
(0%) src/dev-runtime/server/mock-api-router.mjs - changed JS file not collected as browser runtime coverage
45+
(77%) assets/theme-v2/js/gamefoundry-partials.js - changed JS file with browser V8 coverage

0 commit comments

Comments
 (0)