Skip to content

Commit 50fe81c

Browse files
committed
PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup
1 parent b42248d commit 50fe81c

10 files changed

Lines changed: 141 additions & 36 deletions
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
# PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup
2+
3+
## Summary
4+
5+
Updated Game Hub parent table and save behavior for the ALFA_017 stack item.
6+
7+
## Implementation
8+
9+
- Guest Add/Edit rows remain reachable for browsing, but Add and Edit Save buttons redirect to `account/sign-in.html`.
10+
- Renamed the current role side control area to a `Game Crew` accordion.
11+
- Removed Owner from displayed parent table headers, parent rows, add rows, edit rows, and expanded row colspan.
12+
- Kept parent game rows with Source Idea and Readiness Output child rows/tables.
13+
- Removed the instructional copy from the center panel.
14+
- Matched parent table action buttons to compact game button sizing.
15+
16+
## Scope Control
17+
18+
- Preserved existing API/service contract.
19+
- Did not add browser-owned product data.
20+
- Did not add readiness math.
21+
- Did not modify table-first governance content.
22+
23+
## ZIP
24+
25+
- `tmp/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_delta.zip`
Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,14 @@
1+
Branch validation: PASS
2+
3+
Branch:
4+
pr/26174-ALFA-017-game-hub-guest-save-and-crew-cleanup
5+
6+
Base stack branch:
7+
pr/26174-ALFA-016-game-hub-row-edit-add-selected-state
8+
9+
Checks:
10+
- Current branch is the ALFA_017 branch: PASS
11+
- Worktree was clean before ALFA_017 edits: PASS
12+
- Scope limited to Game Hub page/script, targeted Playwright coverage, and required reports: PASS
13+
- No protected Project Instructions changes: PASS
14+
- No merge to main performed: PASS
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
Manual validation notes: PASS
2+
3+
- Reviewed `docs_build/dev/ProjectInstructions/addendums/table_first_ui.md` and applied the game-row parent table pattern.
4+
- Confirmed the Game Hub center panel no longer includes the removed instructional copy.
5+
- Confirmed parent table headers display only Game, Purpose, Status, and Actions.
6+
- Confirmed parent rows no longer display Owner while keeping owner fields available to existing repository data.
7+
- Confirmed Source Idea and Readiness Output remain expanded child rows/tables under each game parent row.
8+
- Confirmed guest Add/Edit Save controls redirect to `account/sign-in.html`.
9+
- Confirmed Add, Edit, Save, and Cancel actions use compact button sizing consistent with game buttons.
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
Requirement checklist: PASS
2+
3+
- As a guest, clicking any Save button redirects to account/sign-in.html: PASS
4+
- Move/rename current user role accordion to Game Crew: PASS
5+
- Remove Owner from the displayed parent table fields: PASS
6+
- Parent table columns are Game, Purpose, Status, Actions: PASS
7+
- Owner remains implicit and is not displayed in the parent table: PASS
8+
- Action buttons match the same scale/height as the game buttons: PASS
9+
- Removed instructional copy: PASS
10+
- Preserve Game row parent structure: PASS
11+
- Preserve Source Idea child row/table: PASS
12+
- Preserve Readiness Output child row/table: PASS
13+
- Preserve API/service contract: PASS
14+
- No browser-owned product data: PASS
15+
- No silent fallbacks: PASS
16+
- Follow table_first_ui.md: PASS
Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
Validation lane: PASS
2+
3+
Commands:
4+
- `git diff --check -- toolbox/game-hub/index.html toolbox/game-hub/game-hub.js tests/playwright/tools/GameHubMockRepository.spec.mjs`
5+
- PASS
6+
- `node --check toolbox/game-hub/game-hub.js`
7+
- PASS
8+
- `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub"`
9+
- PASS, 11 passed
10+
11+
Notes:
12+
- A broader unscoped run of `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs` was attempted before the final targeted lane. It reported 12 passed and 2 failed. One failure was the ALFA_017 guest redirect assertion and was fixed. The remaining failure was outside this PR's surface: `Toolbox member-role filters focus tools without exposing admin-only controls` received existing `500 /api/game-journey/completion-metrics` responses.
Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,10 @@
1-
assets/theme-v2/css/tables.css
1+
docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup.md
2+
docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_branch-validation.txt
3+
docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_manual-validation-notes.txt
4+
docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_requirement-checklist.txt
5+
docs_build/dev/reports/PR_26174_ALFA_017-game-hub-guest-save-and-crew-cleanup_validation-lane.txt
6+
docs_build/dev/reports/codex_changed_files.txt
7+
docs_build/dev/reports/codex_review.diff
28
tests/playwright/tools/GameHubMockRepository.spec.mjs
39
toolbox/game-hub/game-hub.js
10+
toolbox/game-hub/index.html
24.7 KB
Binary file not shown.

tests/playwright/tools/GameHubMockRepository.spec.mjs

Lines changed: 28 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -249,15 +249,18 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
249249
try {
250250
await expect(page.locator(".tool-workspace")).toBeVisible();
251251
await expect(page.locator("style, [style], script:not([src])")).toHaveCount(0);
252-
await expect(page.getByRole("button", { name: "Add Game" })).toHaveClass("btn");
252+
await expect(page.getByRole("button", { name: "Add Game" })).toHaveClass(/\bbtn\b/);
253+
await expect(page.getByRole("button", { name: "Add Game" })).toHaveClass(/\bbtn--compact\b/);
253254
await expect(page.getByRole("button", { name: "Add Game" })).toBeEnabled();
254255
await expect(page.getByLabel("Game Name")).toHaveCount(0);
255256
await expect(page.getByLabel("Game Purpose")).toHaveCount(0);
256257
await expect(page.getByLabel("Game Status")).toHaveCount(0);
257258
await expect(page.getByRole("button", { name: "Delete Open Game" })).toHaveClass("btn");
258259
await expect(page.getByRole("button", { name: "Delete Open Game" })).toBeEnabled();
259260
await expect(page.locator("summary").filter({ hasText: /^Game Setup$/ })).toHaveCount(0);
261+
await expect(page.locator("summary").filter({ hasText: /^Game Crew$/ })).toHaveCount(1);
260262
await expect(page.getByRole("link", { name: "Open Game Journey" })).toHaveCount(0);
263+
await expect(page.locator(".tool-center-panel")).not.toContainText("Review games in the parent table");
261264
await expect(page.locator("[data-project-record-status]")).toHaveText("Game table loaded.");
262265
await expect(page.locator("[data-game-project-information]")).toHaveCount(0);
263266
await expect(page.locator("[data-project-records-table]")).toHaveCount(0);
@@ -281,12 +284,12 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
281284
"Game",
282285
"Purpose",
283286
"Status",
284-
"Owner",
285287
"Actions",
286288
]);
287-
await expect(page.locator("[data-game-rows-table='true'] thead")).not.toContainText(/Role|Next Tool/);
289+
await expect(page.locator("[data-game-rows-table='true'] thead")).not.toContainText(/Owner|Role|Next Tool/);
288290
const demoGameRow = page.locator("[data-game-row='demo-game']");
289-
await expect(demoGameRow.locator("td")).toHaveText(["Game", "Under Construction", "User 1", "Edit"]);
291+
await expect(demoGameRow.locator("td")).toHaveText(["Game", "Under Construction", "Edit"]);
292+
await expect(demoGameRow).not.toContainText("User 1");
290293
await expect(demoGameRow).toHaveAttribute("data-game-active", "true");
291294
await expect(demoGameRow).toHaveAttribute("aria-current", "true");
292295
await expect(demoGameRow.locator("th[data-game-active-cell='true']")).toContainText("Demo Game");
@@ -303,9 +306,11 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
303306
await expect(demoGameRow.locator("> .status")).toHaveCount(0);
304307
await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-expanded", "false");
305308
await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).not.toHaveClass(/primary/);
309+
await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveClass(/\bbtn--compact\b/);
306310
await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-current", "true");
307311
await expect(demoGameRow.getByRole("button", { name: "Edit Demo Game" })).toHaveText("Edit");
308312
await expect(demoGameRow.getByRole("button", { name: "Edit Demo Game" })).not.toHaveClass(/primary/);
313+
await expect(demoGameRow.getByRole("button", { name: "Edit Demo Game" })).toHaveClass(/\bbtn--compact\b/);
309314
await expect(demoGameRow.getByRole("button", { name: "Edit Demo Game" })).not.toHaveAttribute("aria-current", "true");
310315
await demoGameRow.locator("[data-game-toggle='demo-game']").click();
311316
await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-expanded", "true");
@@ -336,6 +341,8 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
336341
await page.getByRole("button", { name: "Add Game" }).click();
337342
const addGameRow = page.locator("[data-game-add-row='input']");
338343
await expect(addGameRow.locator("[data-game-action]")).toHaveText(["Save", "Cancel"]);
344+
await expect(addGameRow.getByRole("button", { name: "Save" })).toHaveClass(/\bbtn--compact\b/);
345+
await expect(addGameRow.locator("td")).toHaveCount(3);
339346
await addGameRow.getByLabel("Game").fill("Launch Test Game");
340347
await addGameRow.getByLabel("Purpose").selectOption("Learning Game");
341348
await addGameRow.getByLabel("Status").selectOption("Ready for Testing");
@@ -351,6 +358,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
351358
await page.getByRole("button", { name: "Edit Launch Test Game" }).click();
352359
const editGameRow = page.locator("[data-game-edit-row='launch-test-game-1']");
353360
await expect(editGameRow.locator("[data-game-action]")).toHaveText(["Save", "Cancel"]);
361+
await expect(editGameRow.getByRole("button", { name: "Save" })).toHaveClass(/\bbtn--compact\b/);
354362
await expect(editGameRow.getByLabel("Game")).toHaveValue("Launch Test Game");
355363
await expect(editGameRow.getByLabel("Game")).toHaveAttribute("readonly", "");
356364
await editGameRow.getByLabel("Purpose").selectOption("Capability Demo");
@@ -525,11 +533,11 @@ test("Game Hub preserves guest browsing and blocks guest saves", async ({ page }
525533
try {
526534
await expect(page.locator("[data-game-row='demo-game'] [data-game-toggle='demo-game']")).not.toHaveClass(/primary/);
527535
await expect(page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Edit Demo Game" })).not.toHaveClass(/primary/);
528-
await expect(page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Edit Demo Game" })).toBeDisabled();
536+
await expect(page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Edit Demo Game" })).toBeEnabled();
529537
await expect(page.locator("[data-game-list]")).toContainText("Gravity Demo");
530538
await expect(page.locator("[data-project-record-status]")).toHaveText("Game table loaded. Sign in to save changes.");
531539
await expect(page.locator("[data-project-records-table]")).toHaveCount(0);
532-
await expect(page.getByRole("button", { name: "Add Game" })).toBeDisabled();
540+
await expect(page.getByRole("button", { name: "Add Game" })).toBeEnabled();
533541
await expect(page.getByRole("button", { name: "Delete Open Game" })).toBeDisabled();
534542
await expect(page.getByLabel("Game Name")).toHaveCount(0);
535543
await expect(page.getByLabel("Game Purpose")).toHaveCount(0);
@@ -538,10 +546,21 @@ test("Game Hub preserves guest browsing and blocks guest saves", async ({ page }
538546

539547
await page.locator("[data-game-row='gravity-demo'] [data-game-toggle='gravity-demo']").click();
540548
await expect(page.locator("[data-game-row='gravity-demo'] [data-game-toggle='gravity-demo']")).not.toHaveClass(/primary/);
541-
await expect(page.locator("[data-game-row='gravity-demo']").getByRole("button", { name: "Edit Gravity Demo" })).toBeDisabled();
549+
await expect(page.locator("[data-game-row='gravity-demo']").getByRole("button", { name: "Edit Gravity Demo" })).toBeEnabled();
542550
await expect(page.locator("[data-game-hub-log]")).toHaveText("Sign in to create or update Game Hub projects.");
543551

544-
await expectNoPageFailures(failures);
552+
await page.locator("[data-game-row='demo-game']").getByRole("button", { name: "Edit Demo Game" }).click();
553+
await page.locator("[data-game-edit-row='demo-game']").getByRole("button", { name: "Save" }).click();
554+
await page.waitForURL(/\/account\/sign-in\.html$/);
555+
556+
await page.goto(`${failures.server.baseUrl}/toolbox/game-hub/index.html`, { waitUntil: "networkidle" });
557+
await page.getByRole("button", { name: "Add Game" }).click();
558+
await page.locator("[data-game-add-row='input']").getByRole("button", { name: "Save" }).click();
559+
await page.waitForURL(/\/account\/sign-in\.html$/);
560+
561+
expect(failures.pageErrors).toEqual([]);
562+
expect(failures.consoleErrors).toEqual([]);
563+
expect(failures.failedRequests.filter((request) => /^\d/.test(request) && !request.includes("/account/sign-in.html"))).toEqual([]);
545564
} finally {
546565
await failures.server.close();
547566
}
@@ -599,7 +618,6 @@ test("Game Hub shows a creator-safe empty state when no projects exist", async (
599618
"Game",
600619
"Purpose",
601620
"Status",
602-
"Owner",
603621
"Actions",
604622
]);
605623
await expect(page.locator("[data-game-list] [data-game-row]")).toHaveCount(0);
@@ -687,7 +705,7 @@ test("Game Hub reports malformed active-game payloads without throwing", async (
687705
await expect(page.locator("[data-active-game-name]")).toHaveCount(0);
688706
await expect(page.locator("[data-current-user-role]")).toHaveCount(0);
689707
await expect(page.locator("[data-game-hub-log]")).toContainText("Active game is temporarily unavailable.");
690-
await expect(page.getByRole("button", { name: "Add Game" })).toBeDisabled();
708+
await expect(page.getByRole("button", { name: "Add Game" })).toBeEnabled();
691709

692710
await expectNoPageFailures(failures);
693711
} finally {

toolbox/game-hub/game-hub.js

Lines changed: 20 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -158,6 +158,18 @@ function ensureProjectRecordsSaveAllowed(action) {
158158
return false;
159159
}
160160

161+
function redirectGuestToSignIn() {
162+
window.location.href = "account/sign-in.html";
163+
}
164+
165+
function ensureProjectRecordsSaveAllowedForSave() {
166+
if (projectRecordsSaveAllowed()) {
167+
return true;
168+
}
169+
redirectGuestToSignIn();
170+
return false;
171+
}
172+
161173
function populateSelect(select, options) {
162174
if (!select) {
163175
return;
@@ -188,7 +200,7 @@ function currentGameMember(activeGame) {
188200

189201
function createActionButton(label, action, options = {}) {
190202
const button = document.createElement("button");
191-
button.className = options.primary ? "btn primary" : "btn";
203+
button.className = options.primary ? "btn btn--compact primary" : "btn btn--compact";
192204
button.type = "button";
193205
button.dataset.gameAction = action;
194206
if (options.gameId) {
@@ -207,7 +219,6 @@ function createActionButton(label, action, options = {}) {
207219
function createGameButton(game) {
208220
const button = createActionButton("Edit", "edit-game", {
209221
ariaLabel: `Edit ${game.name}`,
210-
disabled: !projectRecordsSaveAllowed(),
211222
gameId: game.id,
212223
});
213224
return button;
@@ -381,7 +392,7 @@ function renderExpandedGameRow(tbody, game, progress, active) {
381392
row.dataset.gameChildRow = type;
382393
row.id = id;
383394
const content = document.createElement("td");
384-
content.colSpan = 5;
395+
content.colSpan = 4;
385396
render(content);
386397
row.append(content);
387398
tbody.append(row);
@@ -394,10 +405,8 @@ function renderAddGameRow(tbody) {
394405

395406
if (!state.addingGame) {
396407
const cell = document.createElement("td");
397-
cell.colSpan = 5;
398-
cell.append(createActionButton("Add Game", "start-add-game", {
399-
disabled: !projectRecordsSaveAllowed(),
400-
}));
408+
cell.colSpan = 4;
409+
cell.append(createActionButton("Add Game", "start-add-game"));
401410
row.append(cell);
402411
tbody.append(row);
403412
return;
@@ -415,14 +424,13 @@ function renderAddGameRow(tbody) {
415424
const statusCell = document.createElement("td");
416425
statusCell.append(createSelect(GAME_HUB_GAME_STATUSES, "Planning", "gameStatusInput", "Status"));
417426

418-
const ownerCell = createCell("Current user");
419427
const actions = document.createElement("td");
420428
actions.append(
421429
createActionButton("Save", "save-add-game", { primary: true }),
422430
createActionButton("Cancel", "cancel-add-game"),
423431
);
424432

425-
row.append(nameCell, purposeCell, statusCell, ownerCell, actions);
433+
row.append(nameCell, purposeCell, statusCell, actions);
426434
tbody.append(row);
427435
}
428436

@@ -457,7 +465,6 @@ function renderEditGameRow(tbody, game) {
457465
nameCell,
458466
purposeCell,
459467
statusCell,
460-
createCell(game.ownerDisplayName || "No owner"),
461468
actions,
462469
);
463470
tbody.append(row);
@@ -490,7 +497,6 @@ function renderGameParentRow(tbody, game, activeGame, progress) {
490497
nameCell,
491498
createCell(game.purpose || "Game"),
492499
createCell(game.status || "No status"),
493-
createCell(game.ownerDisplayName || "No owner"),
494500
);
495501

496502
const actions = document.createElement("td");
@@ -538,7 +544,7 @@ function renderGameList(progress) {
538544
table.className = "data-table data-table--fixed";
539545
table.dataset.gameRowsTable = "true";
540546
table.setAttribute("aria-label", "Games");
541-
table.innerHTML = "<thead><tr><th scope=\"col\">Game</th><th scope=\"col\">Purpose</th><th scope=\"col\">Status</th><th scope=\"col\">Owner</th><th scope=\"col\">Actions</th></tr></thead>";
547+
table.innerHTML = "<thead><tr><th scope=\"col\">Game</th><th scope=\"col\">Purpose</th><th scope=\"col\">Status</th><th scope=\"col\">Actions</th></tr></thead>";
542548
const body = document.createElement("tbody");
543549
listResult.forEach((game) => renderGameParentRow(body, game, activeGame, progress));
544550
renderAddGameRow(body);
@@ -652,7 +658,7 @@ function readGameRowFields(row) {
652658
}
653659

654660
function saveAddedGame(row) {
655-
if (!ensureProjectRecordsSaveAllowed("create")) {
661+
if (!ensureProjectRecordsSaveAllowedForSave()) {
656662
return;
657663
}
658664
const input = readGameRowFields(row);
@@ -677,7 +683,7 @@ function saveAddedGame(row) {
677683
}
678684

679685
function saveEditedGame(row, gameId) {
680-
if (!ensureProjectRecordsSaveAllowed("update")) {
686+
if (!ensureProjectRecordsSaveAllowedForSave()) {
681687
return;
682688
}
683689
const input = readGameRowFields(row);
@@ -740,9 +746,6 @@ elements.gameList?.addEventListener("click", (event) => {
740746
}
741747

742748
if (action.dataset.gameAction === "start-add-game") {
743-
if (!ensureProjectRecordsSaveAllowed("create")) {
744-
return;
745-
}
746749
state.addingGame = true;
747750
state.editingGameId = "";
748751
renderWorkspace();
@@ -762,9 +765,6 @@ elements.gameList?.addEventListener("click", (event) => {
762765
}
763766

764767
if (action.dataset.gameAction === "edit-game") {
765-
if (!ensureProjectRecordsSaveAllowed("update")) {
766-
return;
767-
}
768768
const game = repository.openGame(action.dataset.gameId);
769769
if (reportRepositoryError(game, "Edit game") || !isRecord(game)) {
770770
if (!isRepositoryErrorResult(game)) {

0 commit comments

Comments
 (0)