Skip to content

Commit d8c3bc6

Browse files
committed
Persist Toolbox vote visibility and correct tool state display - PR_26160_056-toolbox-votes-and-state-cleanup
1 parent 88602fa commit d8c3bc6

11 files changed

Lines changed: 417 additions & 59 deletions

File tree

admin/tool-votes.html

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
<!doctype html>
2+
<html lang="en">
3+
4+
<head>
5+
<meta charset="utf-8">
6+
<base href="/">
7+
<meta name="viewport" content="width=device-width, initial-scale=1">
8+
<title>Tool Votes - GameFoundryStudio</title>
9+
<meta name="description" content="Admin review page for Toolbox vote totals and current-user vote state.">
10+
<link rel="icon" href="/favicon.svg">
11+
<link rel="stylesheet" href="assets/theme-v2/css/theme.css">
12+
</head>
13+
14+
<body>
15+
<div data-partial="header-nav"></div>
16+
<main data-admin-only="true">
17+
<section class="page-title">
18+
<div class="container">
19+
<div class="kicker">Admin</div>
20+
<h1>Tool Votes</h1>
21+
<p class="lede">Review Toolbox vote totals by tool, vote direction, and current user vote state.</p>
22+
</div>
23+
</section>
24+
<section class="section">
25+
<div class="container account-panel">
26+
<aside class="side-menu" aria-label="Admin pages">
27+
<a href="admin/analytics.html">Analytics</a>
28+
<a href="admin/branding.html">Branding</a>
29+
<a href="admin/controls.html">Controls</a>
30+
<a href="admin/moderation.html">Moderation</a>
31+
<a href="toolbox/platform-settings/index.html">Platform Settings</a>
32+
<a href="admin/ratings.html">Ratings</a>
33+
<a href="admin/roles.html">Roles</a>
34+
<a href="admin/site-settings.html">Site Settings</a>
35+
<a href="admin/themes.html">Themes</a>
36+
<a class="active" aria-current="page" href="admin/tool-votes.html">Tool Votes</a>
37+
<a href="admin/users.html">Users</a>
38+
</aside>
39+
<div class="card">
40+
<div class="card-body content-stack">
41+
<div>
42+
<div class="kicker">Shared Toolbox Data</div>
43+
<h2>Vote Review</h2>
44+
</div>
45+
<p>Vote totals are read from the server API data source used by Toolbox tiles.</p>
46+
<p class="status" role="status" data-toolbox-votes-status>Loading Toolbox vote totals.</p>
47+
<div class="table-wrapper">
48+
<table class="data-table" aria-label="Toolbox vote totals">
49+
<caption>Toolbox Vote Totals</caption>
50+
<thead>
51+
<tr>
52+
<th scope="col">Tool</th>
53+
<th scope="col">Group</th>
54+
<th scope="col">State</th>
55+
<th scope="col">Up</th>
56+
<th scope="col">Down</th>
57+
<th scope="col">Current User Vote</th>
58+
</tr>
59+
</thead>
60+
<tbody data-toolbox-votes-body></tbody>
61+
</table>
62+
</div>
63+
</div>
64+
</div>
65+
</div>
66+
</section>
67+
</main>
68+
<div data-partial="footer"></div>
69+
<script src="assets/theme-v2/js/gamefoundry-partials.js" defer></script>
70+
<script type="module" src="admin/tool-votes.js"></script>
71+
</body>
72+
73+
</html>

admin/tool-votes.js

Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
import { readToolboxVoteSnapshot } from "../src/engine/api/toolbox-votes-api-client.js";
2+
3+
const status = document.querySelector("[data-toolbox-votes-status]");
4+
const body = document.querySelector("[data-toolbox-votes-body]");
5+
6+
function setStatus(message) {
7+
if (status) {
8+
status.textContent = message;
9+
}
10+
}
11+
12+
function tableCell(value) {
13+
const cell = document.createElement("td");
14+
cell.textContent = String(value || "");
15+
return cell;
16+
}
17+
18+
function renderRows(rows) {
19+
if (!body) {
20+
return;
21+
}
22+
if (!rows.length) {
23+
const row = document.createElement("tr");
24+
const cell = document.createElement("td");
25+
cell.colSpan = 6;
26+
cell.textContent = "No Toolbox vote rows are available.";
27+
row.append(cell);
28+
body.replaceChildren(row);
29+
return;
30+
}
31+
32+
body.replaceChildren(...rows.map((voteRow) => {
33+
const row = document.createElement("tr");
34+
row.dataset.toolboxVotesToolId = voteRow.toolId;
35+
row.append(
36+
tableCell(voteRow.toolName),
37+
tableCell(voteRow.group),
38+
tableCell(voteRow.releaseChannelLabel),
39+
tableCell(voteRow.up),
40+
tableCell(voteRow.down),
41+
tableCell(voteRow.currentUserVote || "None"),
42+
);
43+
return row;
44+
}));
45+
}
46+
47+
function renderToolboxVotes() {
48+
if (window.GameFoundrySessionGuard?.blocked) {
49+
return;
50+
}
51+
try {
52+
const snapshot = readToolboxVoteSnapshot();
53+
renderRows(snapshot.rows || []);
54+
setStatus(`Showing Toolbox vote totals for ${snapshot.currentUserName || "current session"}.`);
55+
} catch (error) {
56+
const message = error instanceof Error ? error.message : String(error || "Toolbox votes unavailable.");
57+
renderRows([]);
58+
setStatus(`Toolbox votes unavailable. ${message}`);
59+
}
60+
}
61+
62+
renderToolboxVotes();
63+

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

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -102,6 +102,7 @@
102102
"admin-ratings": "admin/ratings.html",
103103
"admin-users": "admin/users.html",
104104
"admin-roles": "admin/roles.html",
105+
"admin-tool-votes": "admin/tool-votes.html",
105106
"admin-moderation": "admin/moderation.html",
106107
"admin-analytics": "admin/analytics.html",
107108
"admin-tools-progress": "admin/tools-progress.html",
@@ -144,6 +145,7 @@
144145
Object.freeze({ label: "Roles", route: "admin-roles" }),
145146
Object.freeze({ label: "Site Settings", route: "admin-site-settings" }),
146147
Object.freeze({ label: "Themes", route: "admin-themes" }),
148+
Object.freeze({ label: "Tool Votes", route: "admin-tool-votes" }),
147149
Object.freeze({ label: "Users", route: "admin-users" })
148150
]);
149151
const localAdminMyStuffItems = Object.freeze([

docs_build/dev/reports/coverage_changed_js_guardrail.txt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -6,10 +6,13 @@ 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/dev-runtime/server/mock-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
910
(0%) toolbox/toolRegistry.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
10-
(65%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 545/545; executed functions 30/46
11-
(75%) toolbox/tools-page-accordions.js - executed lines 1054/1054; executed functions 77/103
11+
(65%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 547/547; executed functions 30/46
12+
(67%) src/engine/api/toolbox-votes-api-client.js - executed lines 19/19; executed functions 2/3
13+
(75%) toolbox/tools-page-accordions.js - executed lines 1067/1067; executed functions 78/104
1214
(93%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 13/14
1315

1416
Guardrail warnings:
17+
(0%) src/dev-runtime/server/mock-api-router.mjs - WARNING: changed runtime JS file missing from coverage; advisory only
1518
(0%) toolbox/toolRegistry.js - WARNING: changed runtime JS file missing from coverage; advisory only

docs_build/dev/reports/playwright_v8_coverage_report.txt

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -17,17 +17,20 @@ Exercised tool entry points detected:
1717
(72%) Theme V2 Shared JS - exercised 2 runtime JS files
1818

1919
Changed runtime JS files covered:
20+
(0%) src/dev-runtime/server/mock-api-router.mjs - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
2021
(0%) toolbox/toolRegistry.js - WARNING: changed runtime JS file was not collected by Playwright V8 coverage; advisory only
21-
(65%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 545/545; executed functions 30/46
22-
(75%) toolbox/tools-page-accordions.js - executed lines 1054/1054; executed functions 77/103
22+
(65%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 547/547; executed functions 30/46
23+
(67%) src/engine/api/toolbox-votes-api-client.js - executed lines 19/19; executed functions 2/3
24+
(75%) toolbox/tools-page-accordions.js - executed lines 1067/1067; executed functions 78/104
2325
(93%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 13/14
2426

2527
Files with executed line/function counts where available:
2628
(38%) src/engine/api/session-api-client.js - executed lines 34/34; executed functions 3/8
2729
(53%) src/engine/api/server-api-client.js - executed lines 159/159; executed functions 10/19
28-
(65%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 545/545; executed functions 30/46
30+
(65%) assets/theme-v2/js/gamefoundry-partials.js - executed lines 547/547; executed functions 30/46
31+
(67%) src/engine/api/toolbox-votes-api-client.js - executed lines 19/19; executed functions 2/3
2932
(71%) toolbox/project-workspace/project-workspace.js - executed lines 264/264; executed functions 17/24
30-
(75%) toolbox/tools-page-accordions.js - executed lines 1054/1054; executed functions 77/103
33+
(75%) toolbox/tools-page-accordions.js - executed lines 1067/1067; executed functions 78/104
3134
(78%) toolbox/game-configuration/game-configuration.js - executed lines 169/169; executed functions 14/18
3235
(91%) toolbox/game-design/game-design.js - executed lines 242/242; executed functions 20/22
3336
(93%) assets/theme-v2/js/tool-display-mode.js - executed lines 209/209; executed functions 13/14
@@ -37,11 +40,15 @@ Files with executed line/function counts where available:
3740
(100%) toolbox/project-workspace/project-workspace-api-client.js - executed lines 12/12; executed functions 2/2
3841

3942
Uncovered or low-coverage changed JS files:
43+
(0%) src/dev-runtime/server/mock-api-router.mjs - WARNING: uncovered changed runtime JS file; advisory only
4044
(0%) toolbox/toolRegistry.js - WARNING: uncovered changed runtime JS file; advisory only
4145

4246
Changed JS files considered:
47+
(0%) admin/tool-votes.js - changed JS file not collected as browser runtime coverage
48+
(0%) src/dev-runtime/server/mock-api-router.mjs - changed JS file not collected as browser runtime coverage
4349
(0%) tests/playwright/tools/ToolboxRoutePages.spec.mjs - changed JS file not collected as browser runtime coverage
4450
(0%) toolbox/toolRegistry.js - changed JS file not collected as browser runtime coverage
4551
(65%) assets/theme-v2/js/gamefoundry-partials.js - changed JS file with browser V8 coverage
52+
(67%) src/engine/api/toolbox-votes-api-client.js - changed JS file with browser V8 coverage
4653
(75%) toolbox/tools-page-accordions.js - changed JS file with browser V8 coverage
4754
(93%) assets/theme-v2/js/tool-display-mode.js - changed JS file with browser V8 coverage
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
# PR_26160_056 Toolbox Votes And State Cleanup Report
2+
3+
## Branch Validation
4+
5+
| Check | Expected | Actual | Status |
6+
| --- | --- | --- | --- |
7+
| Current git branch before changes | `main` | `main` | PASS |
8+
9+
## Requirement Checklist
10+
11+
| Requirement | Status | Evidence |
12+
| --- | --- | --- |
13+
| Treat Toolbox vote controls as monitorable shared data, not non-persistent wireframe-only controls | PASS | `src/dev-runtime/server/mock-api-router.mjs` owns shared server-side Toolbox vote state; `src/engine/api/toolbox-votes-api-client.js` exposes snapshot/cast API calls; `toolbox/tools-page-accordions.js` reads/casts through the API client. |
14+
| Add an Admin votes review wireframe/page | PASS | Added `admin/tool-votes.html` and `admin/tool-votes.js`; Playwright verifies the page renders and reads vote totals/current-user state. |
15+
| Review totals by tool, vote direction, and current user vote state | PASS | Admin table columns: Tool, Group, State, Up, Down, Current User Vote. |
16+
| Preserve one-vote-per-user per tool | PASS | `castToolboxVote` changes a user from Up to Down by decrementing the previous direction and incrementing the new direction; Playwright verifies Up -> Down for the same user. |
17+
| Colors is the only complete tool | PASS | Registry audit: `complete` count is 1 and the only complete tool is `colors`. |
18+
| Achievements, Build Game, Saved Data, and Languages are wireframe | PASS | Registry audit: `wireframe` tools are exactly `achievements`, `build-game`, `saved-data`, and `languages`. |
19+
| Previously Complete tools other than Colors become beta | PASS | Registry audit: `project-workspace`, `project-journey`, `game-design`, `game-configuration`, and `assets` are `beta`. |
20+
| All remaining tools become planned | PASS | Registry audit: remaining visible tools resolve to `planned`; no group labels are treated as states. |
21+
| Move state badge/value to the bottom of each Toolbox tile | PASS | `toolbox/tools-page-accordions.js` appends `createStateLabel(tool)` after the other tile body parts; Playwright verifies the Colors state badge is the final card-body child. |
22+
| Preserve group labels such as AI, Design, Audio, Assets, Build, System, Community, and Admin as group metadata, not states | PASS | `createGroupLabel` remains separate from `createStateLabel`; Playwright verifies group badge and state badge are separate DOM nodes. |
23+
| Keep Toolbox card order otherwise aligned | PASS | Playwright verifies Build Game order: action row, feedback controls, plan details, bottom state badge. |
24+
| Do not wire new tool wireframes to runtime code | PASS | The four tool wireframe pages remain static controls with only shared Theme V2 scripts. |
25+
| Do not use inline script, inline style, or inline event handlers | PASS | Inline scan returned no matches for the touched HTML pages. |
26+
27+
## State Audit
28+
29+
```text
30+
counts: planned 28, beta 5, complete 1, wireframe 4
31+
wireframe: build-game, saved-data, languages, achievements
32+
complete: colors
33+
beta: project-workspace, project-journey, game-design, game-configuration, assets
34+
```
35+
36+
## Impacted Lane
37+
38+
- Toolbox/page validation.
39+
- Toolbox card display and state filters.
40+
- Toolbox vote behavior through server API.
41+
- Admin Tool Votes review visibility.
42+
43+
## Validation Evidence
44+
45+
| Command | Result |
46+
| --- | --- |
47+
| `node --check assets/theme-v2/js/gamefoundry-partials.js; node --check admin/tool-votes.js; node --check src/engine/api/toolbox-votes-api-client.js; node --check src/dev-runtime/server/mock-api-router.mjs; node --check toolbox/toolRegistry.js; node --check toolbox/tools-page-accordions.js; node --check tests/playwright/tools/ToolboxRoutePages.spec.mjs` | PASS |
48+
| `node scripts/validate-tool-registry.mjs` | PASS |
49+
| Inline script/style/event-handler `rg` scan over touched HTML | PASS, no matches |
50+
| Registry state audit for complete/wireframe/beta/planned | PASS |
51+
| `npx playwright test tests/playwright/tools/ToolboxRoutePages.spec.mjs --reporter=line` | PASS, 5/5 |
52+
| `npx playwright test tests/playwright/tools/ToolDisplayModeNavigation.spec.mjs --reporter=line` | PASS, 5/5 |
53+
| `git diff --check -- ...` | PASS, line-ending warnings only |
54+
55+
## Coverage Notes
56+
57+
- `docs_build/dev/reports/playwright_v8_coverage_report.txt` was refreshed.
58+
- Browser V8 coverage covered `assets/theme-v2/js/gamefoundry-partials.js`, `src/engine/api/toolbox-votes-api-client.js`, `toolbox/tools-page-accordions.js`, and `assets/theme-v2/js/tool-display-mode.js`.
59+
- Advisory WARN entries remain for server-side `src/dev-runtime/server/mock-api-router.mjs` and registry `toolbox/toolRegistry.js`, which are not collected directly by browser V8 coverage.
60+
61+
## Skipped Lanes
62+
63+
| Lane | Reason |
64+
| --- | --- |
65+
| Full samples validation | Explicitly skipped per request; no sample loader or sample manifest behavior changed. |
66+
| Full test suite | Not required for this targeted Toolbox/Admin vote review PR. |
67+
| Non-Toolbox runtime lanes | Not affected by the Toolbox state/vote/Admin review changes. |
68+
69+
## Manual Test Notes
70+
71+
- Branch guard passed before changes: current branch `main`, expected `main`.
72+
- Toolbox vote controls now say votes are recorded for Admin review instead of non-persistent wireframe controls.
73+
- Admin `Tool Votes` route is added to the admin menu through `gamefoundry-partials.js`; it is still protected by the existing admin page guard.
74+
- The Admin review page is read-only and does not add edit/write controls.
75+

src/dev-runtime/server/mock-api-router.mjs

Lines changed: 82 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@ import {
1313
getToolImageDiagnostics,
1414
getToolImageSource,
1515
getToolProgressReadiness,
16+
getToolReleaseChannel,
17+
getToolReleaseChannelLabel,
1618
getToolRegistry,
1719
getToolRoute,
1820
toolRegistryMetadataDiagnostic,
@@ -596,6 +598,7 @@ class LocalDevMockDataSource {
596598
this.repositoryById = new Map();
597599
this.sessionModeId = LOCAL_MEM_MODE_ID;
598600
this.sessionUserKey = "";
601+
this.toolboxVoteState = new Map();
599602
this.adapterByModeId = new Map(
600603
MOCK_DB_SESSION_MODES.map((mode) => [
601604
mode.id,
@@ -764,6 +767,76 @@ class LocalDevMockDataSource {
764767
];
765768
}
766769

770+
toolboxVoteVoterKey() {
771+
const session = this.currentSession();
772+
return session.userKey || "guest";
773+
}
774+
775+
toolboxVoteRecord(toolId) {
776+
const key = String(toolId || "");
777+
if (!this.toolboxVoteState.has(key)) {
778+
this.toolboxVoteState.set(key, {
779+
down: 0,
780+
up: 0,
781+
voters: new Map(),
782+
});
783+
}
784+
return this.toolboxVoteState.get(key);
785+
}
786+
787+
toolboxVoteSnapshot() {
788+
const session = this.currentSession();
789+
const voterKey = this.toolboxVoteVoterKey();
790+
return {
791+
currentUserKey: session.userKey || "",
792+
currentUserName: session.displayName || "Guest",
793+
rows: getActiveToolRegistry()
794+
.filter((tool) => tool.visibleInToolsList === true)
795+
.map((tool) => {
796+
const record = this.toolboxVoteRecord(tool.id);
797+
const releaseChannel = getToolReleaseChannel(tool);
798+
return {
799+
currentUserVote: record.voters.get(voterKey) || "",
800+
down: record.down,
801+
group: tool.category || "",
802+
releaseChannel,
803+
releaseChannelLabel: getToolReleaseChannelLabel(releaseChannel),
804+
toolId: tool.id,
805+
toolName: tool.displayName || tool.name || tool.id,
806+
up: record.up,
807+
};
808+
}),
809+
};
810+
}
811+
812+
castToolboxVote(toolId, direction) {
813+
const normalizedToolId = String(toolId || "");
814+
const normalizedDirection = String(direction || "").toLowerCase();
815+
if (!["up", "down"].includes(normalizedDirection)) {
816+
throw new Error("Toolbox vote direction must be up or down.");
817+
}
818+
const tool = getActiveToolRegistry().find((candidate) => candidate.id === normalizedToolId);
819+
if (!tool || tool.visibleInToolsList !== true) {
820+
throw new Error(`Unknown Toolbox vote tool: ${normalizedToolId || "missing"}.`);
821+
}
822+
823+
const record = this.toolboxVoteRecord(normalizedToolId);
824+
const voterKey = this.toolboxVoteVoterKey();
825+
const previousDirection = record.voters.get(voterKey);
826+
if (previousDirection !== normalizedDirection) {
827+
if (previousDirection === "up") {
828+
record.up = Math.max(0, record.up - 1);
829+
}
830+
if (previousDirection === "down") {
831+
record.down = Math.max(0, record.down - 1);
832+
}
833+
record[normalizedDirection] += 1;
834+
record.voters.set(voterKey, normalizedDirection);
835+
}
836+
837+
return this.toolboxVoteSnapshot();
838+
}
839+
767840
repositoryForTool(toolId) {
768841
this.assertConfiguredAdapter(`Opening ${toolId} repository`);
769842
if (toolId === "workspace") return this.workspaceRepository;
@@ -1044,6 +1117,15 @@ export function createMockApiRouter() {
10441117
}
10451118

10461119
if (parts[1] === "toolbox") {
1120+
if (request.method === "GET" && parts[2] === "votes" && parts[3] === "snapshot") {
1121+
ok(response, dataSource.toolboxVoteSnapshot());
1122+
return true;
1123+
}
1124+
if (request.method === "POST" && parts[2] === "votes" && parts[3] === "cast") {
1125+
const body = await readRequestJson(request);
1126+
ok(response, dataSource.castToolboxVote(body.toolId, body.direction));
1127+
return true;
1128+
}
10471129
if (request.method === "GET" && parts[2] === "registry" && parts[3] === "snapshot") {
10481130
ok(response, toolRegistrySnapshot());
10491131
return true;

0 commit comments

Comments
 (0)