Skip to content

Commit 9f53b91

Browse files
committed
PR_26174_ALFA_013-game-hub-game-row-child-rows
1 parent 0980828 commit 9f53b91

9 files changed

Lines changed: 299 additions & 197 deletions
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
Branch Validation: PASS
2+
3+
PASS - Current branch is pr/26174-ALFA-013-game-hub-game-row-child-rows.
4+
PASS - Expected branch is pr/26174-ALFA-013-game-hub-game-row-child-rows.
5+
PASS - Branch stacks on the current Team Alfa PR_012 branch.
6+
PASS - Work stayed scoped to Game Hub row rendering, targeted Playwright expectations, and required reports.
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+
- Confirmed the Game Hub table uses game rows as the parent rows.
4+
- Confirmed the previous Open Games table identity and caption are absent.
5+
- Confirmed expanding a game creates exactly two child rows.
6+
- Confirmed Source Idea appears before Readiness Output.
7+
- Confirmed the old Game Summary child table is absent.
8+
- Confirmed safe empty and unavailable state checks still pass.
9+
- Confirmed Idea Board-created Source Idea context still appears in Game Hub.
Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
Requirement Checklist: PASS
2+
3+
PASS - Open Games is no longer used as the wrapper/accordion table identity.
4+
PASS - Parent rows are games.
5+
PASS - Each game row expands/collapses.
6+
PASS - Expanded game rows show Source Idea child table first.
7+
PASS - Expanded game rows show Readiness Output child table second.
8+
PASS - Source Idea and Readiness Output render as child rows under the game parent row.
9+
PASS - No new readiness math was added.
10+
PASS - Existing API/service contract was preserved.
11+
PASS - Safe empty/unavailable states were preserved by targeted validation.
12+
PASS - No browser-owned project data was introduced.
13+
PASS - No silent fallbacks were introduced.
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
Validation Lane: PASS
2+
3+
Commands:
4+
1. npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub creates, opens, and deletes mock games|Game Hub validates game parent rows and child tables|Game Hub shows a creator-safe empty state|Game Hub shows a creator-safe unavailable state"
5+
Result: 4 passed.
6+
7+
2. npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -g "Idea Board uses accordion table ideas and notes"
8+
Result: 1 passed.
9+
10+
3. git diff --check
11+
Result: PASS.
Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,18 @@
1+
# PR_26174_ALFA_013-game-hub-game-row-child-rows
2+
3+
## Purpose
4+
5+
Correct Game Hub so each game is the parent row and Source Idea plus Readiness Output render as child rows/child tables.
6+
7+
## Summary
8+
9+
- Removed the Open Games wrapper/table identity from the Game Hub list table.
10+
- Removed the Game Summary child table from expanded game rows.
11+
- Rendered each expanded game as two child rows in this order: Source Idea, Readiness Output.
12+
- Preserved the existing Game Hub API/service contract and safe empty/unavailable states.
13+
14+
## Validation
15+
16+
PASS - `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub creates, opens, and deletes mock games|Game Hub validates game parent rows and child tables|Game Hub shows a creator-safe empty state|Game Hub shows a creator-safe unavailable state"`
17+
PASS - `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -g "Idea Board uses accordion table ideas and notes"`
18+
PASS - `git diff --check`
Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
1+
toolbox/game-hub/game-hub.js
12
tests/playwright/tools/GameHubMockRepository.spec.mjs
23
docs_build/dev/reports/codex_review.diff
34
docs_build/dev/reports/codex_changed_files.txt
4-
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation.md
5-
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-branch-validation.txt
6-
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-requirement-checklist.txt
7-
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-validation-lane.txt
8-
docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-manual-validation-notes.txt
5+
docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md
6+
docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt
7+
docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt
8+
docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt
9+
docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt

docs_build/dev/reports/codex_review.diff

Lines changed: 184 additions & 127 deletions
Large diffs are not rendered by default.

tests/playwright/tools/GameHubMockRepository.spec.mjs

Lines changed: 26 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -264,9 +264,10 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
264264
await expect(page.locator("[data-game-list]")).toContainText("Gravity Demo");
265265
await expect(page.locator("[data-game-list]")).toContainText("Collision Demo");
266266
await expect(page.locator("[data-game-list]")).toContainText("Camera Follow Demo");
267-
await expect(page.locator("[data-game-parent-table='open-games']")).toHaveAttribute("aria-label", "Open Games");
268-
await expect(page.locator("[data-game-parent-table='open-games'] caption")).toHaveText("Open Games");
269-
await expect(page.locator("[data-game-parent-table='open-games'] thead th")).toHaveText([
267+
await expect(page.locator("[data-game-parent-table='open-games']")).toHaveCount(0);
268+
await expect(page.locator("[data-game-rows-table='true']")).toHaveAttribute("aria-label", "Games");
269+
await expect(page.locator("[data-game-rows-table='true'] caption")).toHaveCount(0);
270+
await expect(page.locator("[data-game-rows-table='true'] thead th")).toHaveText([
270271
"Game",
271272
"Purpose",
272273
"Status",
@@ -280,17 +281,14 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
280281
await expect(demoGameRow.getByRole("button", { name: "Open Demo Game (Active)" })).toHaveAttribute("aria-current", "true");
281282
await demoGameRow.locator("[data-game-toggle='demo-game']").click();
282283
await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-expanded", "true");
283-
await expect(page.locator("[data-game-row='demo-game'] + [data-game-expanded-row='demo-game']")).toHaveCount(1);
284-
await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='summary']")).toContainText("Game Summary");
285-
await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='summary'] tbody tr")).toHaveText([
286-
"ProjectDemo Game",
287-
"PurposeGame",
288-
"StatusUnder Construction",
289-
"OwnerUser 1",
290-
]);
291-
await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table]")).toHaveCount(3);
292-
await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='source-idea'] caption")).toHaveText("Source Idea");
293-
const readinessOutputTable = page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='readiness-output']");
284+
const demoChildRows = page.locator("[data-game-expanded-row='demo-game']");
285+
await expect(demoChildRows).toHaveCount(2);
286+
await expect(demoChildRows.nth(0)).toHaveAttribute("data-game-child-row", "source-idea");
287+
await expect(demoChildRows.nth(1)).toHaveAttribute("data-game-child-row", "readiness-output");
288+
await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='summary']")).toHaveCount(0);
289+
await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table]")).toHaveCount(2);
290+
await expect(demoChildRows.nth(0).locator("[data-game-child-table='source-idea'] caption")).toHaveText("Source Idea");
291+
const readinessOutputTable = demoChildRows.nth(1).locator("[data-game-child-table='readiness-output']");
294292
await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output");
295293
await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]);
296294
await expect(readinessOutputTable.locator("tbody tr")).toHaveText([
@@ -335,7 +333,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
335333
}
336334
});
337335

338-
test("Game Hub validates Open Games parent and child tables", async ({ page }) => {
336+
test("Game Hub validates game parent rows and child tables", async ({ page }) => {
339337
const sourceLinkedGame = {
340338
id: "lantern-reef",
341339
ownerKey: MOCK_DB_KEYS.users.user1,
@@ -422,20 +420,25 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) =
422420
const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() });
423421

424422
try {
425-
await expect(page.locator("[data-game-parent-table='open-games'] caption")).toHaveText("Open Games");
426-
const parentRows = page.locator("[data-game-parent-table='open-games'] tbody > [data-game-row]");
423+
await expect(page.locator("[data-game-parent-table='open-games']")).toHaveCount(0);
424+
await expect(page.locator("[data-game-rows-table='true']")).toHaveAttribute("aria-label", "Games");
425+
await expect(page.locator("[data-game-rows-table='true'] caption")).toHaveCount(0);
426+
const parentRows = page.locator("[data-game-rows-table='true'] tbody > [data-game-row]");
427427
await expect(parentRows).toHaveCount(1);
428428
const gameRow = page.locator("[data-game-row='lantern-reef']");
429429
await expect(gameRow).toContainText("Lantern Reef");
430430
await expect(gameRow.locator("[data-game-toggle='lantern-reef']")).toHaveAttribute("aria-expanded", "false");
431431

432432
await gameRow.locator("[data-game-toggle='lantern-reef']").click();
433433
await expect(gameRow.locator("[data-game-toggle='lantern-reef']")).toHaveAttribute("aria-expanded", "true");
434-
const expandedRow = page.locator("[data-game-expanded-row='lantern-reef']");
435-
await expect(expandedRow).toHaveCount(1);
436-
await expect(expandedRow.locator("[data-game-child-table]")).toHaveCount(3);
437-
438-
const sourceIdeaTable = expandedRow.locator("[data-game-child-table='source-idea']");
434+
const expandedRows = page.locator("[data-game-expanded-row='lantern-reef']");
435+
await expect(expandedRows).toHaveCount(2);
436+
await expect(expandedRows.nth(0)).toHaveAttribute("data-game-child-row", "source-idea");
437+
await expect(expandedRows.nth(1)).toHaveAttribute("data-game-child-row", "readiness-output");
438+
await expect(expandedRows.locator("[data-game-child-table]")).toHaveCount(2);
439+
await expect(expandedRows.locator("[data-game-child-table='summary']")).toHaveCount(0);
440+
441+
const sourceIdeaTable = expandedRows.nth(0).locator("[data-game-child-table='source-idea']");
439442
await expect(sourceIdeaTable.locator("caption")).toHaveText("Source Idea");
440443
await expect(sourceIdeaTable.locator("tbody tr")).toHaveText([
441444
"IdeaLantern Reef",
@@ -446,7 +449,7 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) =
446449
await expect(sourceIdeaTable.locator("button, input, textarea, select, [contenteditable='true'], [role='button']")).toHaveCount(0);
447450
await expect(sourceIdeaTable).not.toContainText(/Edit|Delete|Current Focus|Recommended Next Tool/);
448451

449-
const readinessOutputTable = expandedRow.locator("[data-game-child-table='readiness-output']");
452+
const readinessOutputTable = expandedRows.nth(1).locator("[data-game-child-table='readiness-output']");
450453
await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output");
451454
await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]);
452455
await expect(readinessOutputTable).not.toContainText(/Guide reef keepers|Keep traversal gentle|Use warm lantern art/);

toolbox/game-hub/game-hub.js

Lines changed: 26 additions & 42 deletions
Original file line numberDiff line numberDiff line change
@@ -241,36 +241,11 @@ function createGameToggleButton(game, expanded) {
241241
button.type = "button";
242242
button.dataset.gameToggle = game.id;
243243
button.setAttribute("aria-expanded", String(expanded));
244-
button.setAttribute("aria-controls", `game-child-${game.id}`);
244+
button.setAttribute("aria-controls", `game-child-source-idea-${game.id} game-child-readiness-output-${game.id}`);
245245
button.textContent = game.name;
246246
return button;
247247
}
248248

249-
function renderGameSummaryChildTable(parent, game) {
250-
const wrapper = document.createElement("div");
251-
wrapper.className = "table-wrapper";
252-
const table = document.createElement("table");
253-
table.className = "data-table data-table--fixed";
254-
table.dataset.gameChildTable = "summary";
255-
table.setAttribute("aria-label", `${game.name} game summary`);
256-
table.innerHTML = "<caption>Game Summary</caption><thead><tr><th scope=\"col\">Field</th><th scope=\"col\">Value</th></tr></thead>";
257-
const body = document.createElement("tbody");
258-
[
259-
["Project", game.name],
260-
["Purpose", game.purpose],
261-
["Status", game.status],
262-
["Owner", game.ownerDisplayName],
263-
].forEach(([label, value]) => {
264-
const row = document.createElement("tr");
265-
row.append(createCell(label, "th"), createCell(value || "Not set"));
266-
row.firstElementChild.scope = "row";
267-
body.append(row);
268-
});
269-
table.append(body);
270-
wrapper.append(table);
271-
parent.append(wrapper);
272-
}
273-
274249
function gameSourceIdeaDetails(game) {
275250
const sourceIdea = isRecord(game?.sourceIdea) ? game.sourceIdea : null;
276251
const name = String(sourceIdea?.idea || "").trim();
@@ -366,19 +341,28 @@ function renderReadinessOutputChildTable(parent, game, progress, active) {
366341
}
367342

368343
function renderExpandedGameRow(tbody, game, progress, active) {
369-
const row = document.createElement("tr");
370-
row.dataset.gameExpandedRow = game.id;
371-
row.id = `game-child-${game.id}`;
372-
const content = document.createElement("td");
373-
content.colSpan = 5;
374-
const stack = document.createElement("div");
375-
stack.className = "content-stack content-stack--compact";
376-
renderGameSummaryChildTable(stack, game);
377-
renderSourceIdeaChildTable(stack, game);
378-
renderReadinessOutputChildTable(stack, game, progress, active);
379-
content.append(stack);
380-
row.append(content);
381-
tbody.append(row);
344+
[
345+
{
346+
id: `game-child-source-idea-${game.id}`,
347+
render: (parent) => renderSourceIdeaChildTable(parent, game),
348+
type: "source-idea",
349+
},
350+
{
351+
id: `game-child-readiness-output-${game.id}`,
352+
render: (parent) => renderReadinessOutputChildTable(parent, game, progress, active),
353+
type: "readiness-output",
354+
},
355+
].forEach(({ id, render, type }) => {
356+
const row = document.createElement("tr");
357+
row.dataset.gameExpandedRow = game.id;
358+
row.dataset.gameChildRow = type;
359+
row.id = id;
360+
const content = document.createElement("td");
361+
content.colSpan = 5;
362+
render(content);
363+
row.append(content);
364+
tbody.append(row);
365+
});
382366
}
383367

384368
function renderGameParentRow(tbody, game, activeGame, progress) {
@@ -465,9 +449,9 @@ function renderGameList(progress) {
465449
wrapper.className = "table-wrapper";
466450
const table = document.createElement("table");
467451
table.className = "data-table data-table--fixed";
468-
table.dataset.gameParentTable = "open-games";
469-
table.setAttribute("aria-label", "Open Games");
470-
table.innerHTML = "<caption>Open Games</caption><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>";
452+
table.dataset.gameRowsTable = "true";
453+
table.setAttribute("aria-label", "Games");
454+
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>";
471455
const body = document.createElement("tbody");
472456
listResult.forEach((game) => renderGameParentRow(body, game, activeGame, progress));
473457
table.append(body);

0 commit comments

Comments
 (0)