From 9f53b917e468b7d358bb73a7ac78edd3ade1525f Mon Sep 17 00:00:00 2001 From: DavidQ Date: Mon, 22 Jun 2026 18:28:20 -0400 Subject: [PATCH 1/4] PR_26174_ALFA_013-game-hub-game-row-child-rows --- ...-game-row-child-rows-branch-validation.txt | 6 + ...row-child-rows-manual-validation-notes.txt | 9 + ...e-row-child-rows-requirement-checklist.txt | 13 + ...ub-game-row-child-rows-validation-lane.txt | 11 + ...4_ALFA_013-game-hub-game-row-child-rows.md | 18 + .../dev/reports/codex_changed_files.txt | 11 +- docs_build/dev/reports/codex_review.diff | 311 +++++++++++------- .../tools/GameHubMockRepository.spec.mjs | 49 +-- toolbox/game-hub/game-hub.js | 68 ++-- 9 files changed, 299 insertions(+), 197 deletions(-) create mode 100644 docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt create mode 100644 docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt create mode 100644 docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt create mode 100644 docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt create mode 100644 docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt new file mode 100644 index 000000000..c2dc3d048 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt @@ -0,0 +1,6 @@ +Branch Validation: PASS + +PASS - Current branch is pr/26174-ALFA-013-game-hub-game-row-child-rows. +PASS - Expected branch is pr/26174-ALFA-013-game-hub-game-row-child-rows. +PASS - Branch stacks on the current Team Alfa PR_012 branch. +PASS - Work stayed scoped to Game Hub row rendering, targeted Playwright expectations, and required reports. diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt new file mode 100644 index 000000000..4b52998f2 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt @@ -0,0 +1,9 @@ +Manual Validation Notes: PASS + +- Confirmed the Game Hub table uses game rows as the parent rows. +- Confirmed the previous Open Games table identity and caption are absent. +- Confirmed expanding a game creates exactly two child rows. +- Confirmed Source Idea appears before Readiness Output. +- Confirmed the old Game Summary child table is absent. +- Confirmed safe empty and unavailable state checks still pass. +- Confirmed Idea Board-created Source Idea context still appears in Game Hub. diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt new file mode 100644 index 000000000..32ffbbc22 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt @@ -0,0 +1,13 @@ +Requirement Checklist: PASS + +PASS - Open Games is no longer used as the wrapper/accordion table identity. +PASS - Parent rows are games. +PASS - Each game row expands/collapses. +PASS - Expanded game rows show Source Idea child table first. +PASS - Expanded game rows show Readiness Output child table second. +PASS - Source Idea and Readiness Output render as child rows under the game parent row. +PASS - No new readiness math was added. +PASS - Existing API/service contract was preserved. +PASS - Safe empty/unavailable states were preserved by targeted validation. +PASS - No browser-owned project data was introduced. +PASS - No silent fallbacks were introduced. diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt new file mode 100644 index 000000000..306ea16e5 --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt @@ -0,0 +1,11 @@ +Validation Lane: PASS + +Commands: +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" +Result: 4 passed. + +2. npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -g "Idea Board uses accordion table ideas and notes" +Result: 1 passed. + +3. git diff --check +Result: PASS. diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md new file mode 100644 index 000000000..d8919aa9d --- /dev/null +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md @@ -0,0 +1,18 @@ +# PR_26174_ALFA_013-game-hub-game-row-child-rows + +## Purpose + +Correct Game Hub so each game is the parent row and Source Idea plus Readiness Output render as child rows/child tables. + +## Summary + +- Removed the Open Games wrapper/table identity from the Game Hub list table. +- Removed the Game Summary child table from expanded game rows. +- Rendered each expanded game as two child rows in this order: Source Idea, Readiness Output. +- Preserved the existing Game Hub API/service contract and safe empty/unavailable states. + +## Validation + +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"` +PASS - `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -g "Idea Board uses accordion table ideas and notes"` +PASS - `git diff --check` diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index 3c4039b1f..c40eaf03d 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,8 +1,9 @@ +toolbox/game-hub/game-hub.js tests/playwright/tools/GameHubMockRepository.spec.mjs docs_build/dev/reports/codex_review.diff docs_build/dev/reports/codex_changed_files.txt -docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation.md -docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-branch-validation.txt -docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-requirement-checklist.txt -docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-validation-lane.txt -docs_build/dev/reports/PR_26174_ALFA_012-game-hub-parent-child-final-validation-manual-validation-notes.txt +docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md +docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt +docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt +docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt +docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 41e24b645..9b3fba4e2 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,135 +1,192 @@ diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs -index dac65c676..4e617ed35 100644 +index 4e617ed35..b89ff51fe 100644 --- a/tests/playwright/tools/GameHubMockRepository.spec.mjs +++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs -@@ -335,6 +335,131 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { +@@ -264,9 +264,10 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { + await expect(page.locator("[data-game-list]")).toContainText("Gravity Demo"); + await expect(page.locator("[data-game-list]")).toContainText("Collision Demo"); + await expect(page.locator("[data-game-list]")).toContainText("Camera Follow Demo"); +- await expect(page.locator("[data-game-parent-table='open-games']")).toHaveAttribute("aria-label", "Open Games"); +- await expect(page.locator("[data-game-parent-table='open-games'] caption")).toHaveText("Open Games"); +- await expect(page.locator("[data-game-parent-table='open-games'] thead th")).toHaveText([ ++ await expect(page.locator("[data-game-parent-table='open-games']")).toHaveCount(0); ++ await expect(page.locator("[data-game-rows-table='true']")).toHaveAttribute("aria-label", "Games"); ++ await expect(page.locator("[data-game-rows-table='true'] caption")).toHaveCount(0); ++ await expect(page.locator("[data-game-rows-table='true'] thead th")).toHaveText([ + "Game", + "Purpose", + "Status", +@@ -280,17 +281,14 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { + await expect(demoGameRow.getByRole("button", { name: "Open Demo Game (Active)" })).toHaveAttribute("aria-current", "true"); + await demoGameRow.locator("[data-game-toggle='demo-game']").click(); + await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-expanded", "true"); +- await expect(page.locator("[data-game-row='demo-game'] + [data-game-expanded-row='demo-game']")).toHaveCount(1); +- await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='summary']")).toContainText("Game Summary"); +- await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='summary'] tbody tr")).toHaveText([ +- "ProjectDemo Game", +- "PurposeGame", +- "StatusUnder Construction", +- "OwnerUser 1", +- ]); +- await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table]")).toHaveCount(3); +- await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='source-idea'] caption")).toHaveText("Source Idea"); +- const readinessOutputTable = page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='readiness-output']"); ++ const demoChildRows = page.locator("[data-game-expanded-row='demo-game']"); ++ await expect(demoChildRows).toHaveCount(2); ++ await expect(demoChildRows.nth(0)).toHaveAttribute("data-game-child-row", "source-idea"); ++ await expect(demoChildRows.nth(1)).toHaveAttribute("data-game-child-row", "readiness-output"); ++ await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='summary']")).toHaveCount(0); ++ await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table]")).toHaveCount(2); ++ await expect(demoChildRows.nth(0).locator("[data-game-child-table='source-idea'] caption")).toHaveText("Source Idea"); ++ const readinessOutputTable = demoChildRows.nth(1).locator("[data-game-child-table='readiness-output']"); + await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output"); + await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]); + await expect(readinessOutputTable.locator("tbody tr")).toHaveText([ +@@ -335,7 +333,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { } }); -+test("Game Hub validates Open Games parent and child tables", async ({ page }) => { -+ const sourceLinkedGame = { -+ id: "lantern-reef", -+ ownerKey: MOCK_DB_KEYS.users.user1, -+ name: "Lantern Reef", -+ purpose: "Game", -+ status: "Ready for Testing", -+ ownerDisplayName: "User 1", -+ members: [ -+ { -+ displayName: "User 1", -+ gameId: "lantern-reef", -+ permission: "Owner", -+ role: "Owner", -+ userKey: MOCK_DB_KEYS.users.user1, -+ }, -+ ], -+ sourceIdea: { -+ idea: "Lantern Reef", -+ pitch: "Guide reef keepers through dusk storms.", -+ notes: [ -+ "Keep traversal gentle.", -+ "Use warm lantern art.", -+ ], -+ }, -+ }; -+ const journeyBuckets = [ -+ "Idea", -+ "Design", -+ "Graphics", -+ "Audio", -+ "Objects", -+ "Worlds", -+ "Interface", -+ "Controls", -+ "Rules", -+ "Progression", -+ "Play Test", -+ "Publish", -+ "Share", -+ ]; +-test("Game Hub validates Open Games parent and child tables", async ({ page }) => { ++test("Game Hub validates game parent rows and child tables", async ({ page }) => { + const sourceLinkedGame = { + id: "lantern-reef", + ownerKey: MOCK_DB_KEYS.users.user1, +@@ -422,8 +420,10 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = + const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() }); + + try { +- await expect(page.locator("[data-game-parent-table='open-games'] caption")).toHaveText("Open Games"); +- const parentRows = page.locator("[data-game-parent-table='open-games'] tbody > [data-game-row]"); ++ await expect(page.locator("[data-game-parent-table='open-games']")).toHaveCount(0); ++ await expect(page.locator("[data-game-rows-table='true']")).toHaveAttribute("aria-label", "Games"); ++ await expect(page.locator("[data-game-rows-table='true'] caption")).toHaveCount(0); ++ const parentRows = page.locator("[data-game-rows-table='true'] tbody > [data-game-row]"); + await expect(parentRows).toHaveCount(1); + const gameRow = page.locator("[data-game-row='lantern-reef']"); + await expect(gameRow).toContainText("Lantern Reef"); +@@ -431,11 +431,14 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = + + await gameRow.locator("[data-game-toggle='lantern-reef']").click(); + await expect(gameRow.locator("[data-game-toggle='lantern-reef']")).toHaveAttribute("aria-expanded", "true"); +- const expandedRow = page.locator("[data-game-expanded-row='lantern-reef']"); +- await expect(expandedRow).toHaveCount(1); +- await expect(expandedRow.locator("[data-game-child-table]")).toHaveCount(3); +- +- const sourceIdeaTable = expandedRow.locator("[data-game-child-table='source-idea']"); ++ const expandedRows = page.locator("[data-game-expanded-row='lantern-reef']"); ++ await expect(expandedRows).toHaveCount(2); ++ await expect(expandedRows.nth(0)).toHaveAttribute("data-game-child-row", "source-idea"); ++ await expect(expandedRows.nth(1)).toHaveAttribute("data-game-child-row", "readiness-output"); ++ await expect(expandedRows.locator("[data-game-child-table]")).toHaveCount(2); ++ await expect(expandedRows.locator("[data-game-child-table='summary']")).toHaveCount(0); + -+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/getActiveGame", async (route) => { -+ await route.fulfill({ -+ body: JSON.stringify({ -+ data: { result: sourceLinkedGame }, -+ ok: true, -+ rule: "Browser -> Server API -> Data Source", -+ }), -+ contentType: "application/json; charset=utf-8", -+ status: 200, -+ }); -+ }); -+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/getGameProgress", async (route) => { -+ await route.fulfill({ -+ body: JSON.stringify({ -+ data: { -+ result: { -+ currentFocus: "Review source idea context", -+ gameProgress: "Lantern Reef identity ready", -+ gameStatus: "Ready for Testing", -+ publishingProgress: "Launch review pending", -+ recommendedNextTool: "Game Journey", -+ progressChecklist: journeyBuckets.map((label) => ({ label, status: "Planned" })), -+ }, -+ }, -+ ok: true, -+ rule: "Browser -> Server API -> Data Source", -+ }), -+ contentType: "application/json; charset=utf-8", -+ status: 200, -+ }); -+ }); -+ await page.route("**/api/toolbox/game-hub/repositories/*/methods/listGames", async (route) => { -+ await route.fulfill({ -+ body: JSON.stringify({ -+ data: { result: [sourceLinkedGame] }, -+ ok: true, -+ rule: "Browser -> Server API -> Data Source", -+ }), -+ contentType: "application/json; charset=utf-8", -+ status: 200, -+ }); ++ const sourceIdeaTable = expandedRows.nth(0).locator("[data-game-child-table='source-idea']"); + await expect(sourceIdeaTable.locator("caption")).toHaveText("Source Idea"); + await expect(sourceIdeaTable.locator("tbody tr")).toHaveText([ + "IdeaLantern Reef", +@@ -446,7 +449,7 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = + await expect(sourceIdeaTable.locator("button, input, textarea, select, [contenteditable='true'], [role='button']")).toHaveCount(0); + await expect(sourceIdeaTable).not.toContainText(/Edit|Delete|Current Focus|Recommended Next Tool/); + +- const readinessOutputTable = expandedRow.locator("[data-game-child-table='readiness-output']"); ++ const readinessOutputTable = expandedRows.nth(1).locator("[data-game-child-table='readiness-output']"); + await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output"); + await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]); + await expect(readinessOutputTable).not.toContainText(/Guide reef keepers|Keep traversal gentle|Use warm lantern art/); +diff --git a/toolbox/game-hub/game-hub.js b/toolbox/game-hub/game-hub.js +index 4b21814d7..56e5de4aa 100644 +--- a/toolbox/game-hub/game-hub.js ++++ b/toolbox/game-hub/game-hub.js +@@ -241,36 +241,11 @@ function createGameToggleButton(game, expanded) { + button.type = "button"; + button.dataset.gameToggle = game.id; + button.setAttribute("aria-expanded", String(expanded)); +- button.setAttribute("aria-controls", `game-child-${game.id}`); ++ button.setAttribute("aria-controls", `game-child-source-idea-${game.id} game-child-readiness-output-${game.id}`); + button.textContent = game.name; + return button; + } + +-function renderGameSummaryChildTable(parent, game) { +- const wrapper = document.createElement("div"); +- wrapper.className = "table-wrapper"; +- const table = document.createElement("table"); +- table.className = "data-table data-table--fixed"; +- table.dataset.gameChildTable = "summary"; +- table.setAttribute("aria-label", `${game.name} game summary`); +- table.innerHTML = "Game SummaryFieldValue"; +- const body = document.createElement("tbody"); +- [ +- ["Project", game.name], +- ["Purpose", game.purpose], +- ["Status", game.status], +- ["Owner", game.ownerDisplayName], +- ].forEach(([label, value]) => { +- const row = document.createElement("tr"); +- row.append(createCell(label, "th"), createCell(value || "Not set")); +- row.firstElementChild.scope = "row"; +- body.append(row); +- }); +- table.append(body); +- wrapper.append(table); +- parent.append(wrapper); +-} +- + function gameSourceIdeaDetails(game) { + const sourceIdea = isRecord(game?.sourceIdea) ? game.sourceIdea : null; + const name = String(sourceIdea?.idea || "").trim(); +@@ -366,19 +341,28 @@ function renderReadinessOutputChildTable(parent, game, progress, active) { + } + + function renderExpandedGameRow(tbody, game, progress, active) { +- const row = document.createElement("tr"); +- row.dataset.gameExpandedRow = game.id; +- row.id = `game-child-${game.id}`; +- const content = document.createElement("td"); +- content.colSpan = 5; +- const stack = document.createElement("div"); +- stack.className = "content-stack content-stack--compact"; +- renderGameSummaryChildTable(stack, game); +- renderSourceIdeaChildTable(stack, game); +- renderReadinessOutputChildTable(stack, game, progress, active); +- content.append(stack); +- row.append(content); +- tbody.append(row); ++ [ ++ { ++ id: `game-child-source-idea-${game.id}`, ++ render: (parent) => renderSourceIdeaChildTable(parent, game), ++ type: "source-idea", ++ }, ++ { ++ id: `game-child-readiness-output-${game.id}`, ++ render: (parent) => renderReadinessOutputChildTable(parent, game, progress, active), ++ type: "readiness-output", ++ }, ++ ].forEach(({ id, render, type }) => { ++ const row = document.createElement("tr"); ++ row.dataset.gameExpandedRow = game.id; ++ row.dataset.gameChildRow = type; ++ row.id = id; ++ const content = document.createElement("td"); ++ content.colSpan = 5; ++ render(content); ++ row.append(content); ++ tbody.append(row); + }); -+ const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() }); -+ -+ try { -+ await expect(page.locator("[data-game-parent-table='open-games'] caption")).toHaveText("Open Games"); -+ const parentRows = page.locator("[data-game-parent-table='open-games'] tbody > [data-game-row]"); -+ await expect(parentRows).toHaveCount(1); -+ const gameRow = page.locator("[data-game-row='lantern-reef']"); -+ await expect(gameRow).toContainText("Lantern Reef"); -+ await expect(gameRow.locator("[data-game-toggle='lantern-reef']")).toHaveAttribute("aria-expanded", "false"); -+ -+ await gameRow.locator("[data-game-toggle='lantern-reef']").click(); -+ await expect(gameRow.locator("[data-game-toggle='lantern-reef']")).toHaveAttribute("aria-expanded", "true"); -+ const expandedRow = page.locator("[data-game-expanded-row='lantern-reef']"); -+ await expect(expandedRow).toHaveCount(1); -+ await expect(expandedRow.locator("[data-game-child-table]")).toHaveCount(3); -+ -+ const sourceIdeaTable = expandedRow.locator("[data-game-child-table='source-idea']"); -+ await expect(sourceIdeaTable.locator("caption")).toHaveText("Source Idea"); -+ await expect(sourceIdeaTable.locator("tbody tr")).toHaveText([ -+ "IdeaLantern Reef", -+ "PitchGuide reef keepers through dusk storms.", -+ "Note 1Keep traversal gentle.", -+ "Note 2Use warm lantern art.", -+ ]); -+ await expect(sourceIdeaTable.locator("button, input, textarea, select, [contenteditable='true'], [role='button']")).toHaveCount(0); -+ await expect(sourceIdeaTable).not.toContainText(/Edit|Delete|Current Focus|Recommended Next Tool/); -+ -+ const readinessOutputTable = expandedRow.locator("[data-game-child-table='readiness-output']"); -+ await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output"); -+ await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]); -+ await expect(readinessOutputTable).not.toContainText(/Guide reef keepers|Keep traversal gentle|Use warm lantern art/); -+ await expect(readinessOutputTable.locator("[data-readiness-checklist-row] th")).toHaveText(journeyBuckets); -+ -+ await expect(page.locator("[data-game-list] [data-game-list-status='empty']")).toHaveCount(0); -+ await expect(page.locator("[data-game-list] [data-game-list-status='unavailable']")).toHaveCount(0); -+ await expectNoPageFailures(failures); -+ } finally { -+ await failures.server.close(); -+ } -+}); -+ - test("Game Hub preserves guest browsing and blocks guest saves", async ({ page }) => { - const failures = await openRepoPage(page, "/toolbox/game-hub/index.html"); + } + + function renderGameParentRow(tbody, game, activeGame, progress) { +@@ -465,9 +449,9 @@ function renderGameList(progress) { + wrapper.className = "table-wrapper"; + const table = document.createElement("table"); + table.className = "data-table data-table--fixed"; +- table.dataset.gameParentTable = "open-games"; +- table.setAttribute("aria-label", "Open Games"); +- table.innerHTML = "Open GamesGamePurposeStatusOwnerActions"; ++ table.dataset.gameRowsTable = "true"; ++ table.setAttribute("aria-label", "Games"); ++ table.innerHTML = "GamePurposeStatusOwnerActions"; + const body = document.createElement("tbody"); + listResult.forEach((game) => renderGameParentRow(body, game, activeGame, progress)); + table.append(body); diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs index 4e617ed35..b89ff51fe 100644 --- a/tests/playwright/tools/GameHubMockRepository.spec.mjs +++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs @@ -264,9 +264,10 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { await expect(page.locator("[data-game-list]")).toContainText("Gravity Demo"); await expect(page.locator("[data-game-list]")).toContainText("Collision Demo"); await expect(page.locator("[data-game-list]")).toContainText("Camera Follow Demo"); - await expect(page.locator("[data-game-parent-table='open-games']")).toHaveAttribute("aria-label", "Open Games"); - await expect(page.locator("[data-game-parent-table='open-games'] caption")).toHaveText("Open Games"); - await expect(page.locator("[data-game-parent-table='open-games'] thead th")).toHaveText([ + await expect(page.locator("[data-game-parent-table='open-games']")).toHaveCount(0); + await expect(page.locator("[data-game-rows-table='true']")).toHaveAttribute("aria-label", "Games"); + await expect(page.locator("[data-game-rows-table='true'] caption")).toHaveCount(0); + await expect(page.locator("[data-game-rows-table='true'] thead th")).toHaveText([ "Game", "Purpose", "Status", @@ -280,17 +281,14 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { await expect(demoGameRow.getByRole("button", { name: "Open Demo Game (Active)" })).toHaveAttribute("aria-current", "true"); await demoGameRow.locator("[data-game-toggle='demo-game']").click(); await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-expanded", "true"); - await expect(page.locator("[data-game-row='demo-game'] + [data-game-expanded-row='demo-game']")).toHaveCount(1); - await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='summary']")).toContainText("Game Summary"); - await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='summary'] tbody tr")).toHaveText([ - "ProjectDemo Game", - "PurposeGame", - "StatusUnder Construction", - "OwnerUser 1", - ]); - await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table]")).toHaveCount(3); - await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='source-idea'] caption")).toHaveText("Source Idea"); - const readinessOutputTable = page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='readiness-output']"); + const demoChildRows = page.locator("[data-game-expanded-row='demo-game']"); + await expect(demoChildRows).toHaveCount(2); + await expect(demoChildRows.nth(0)).toHaveAttribute("data-game-child-row", "source-idea"); + await expect(demoChildRows.nth(1)).toHaveAttribute("data-game-child-row", "readiness-output"); + await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table='summary']")).toHaveCount(0); + await expect(page.locator("[data-game-expanded-row='demo-game'] [data-game-child-table]")).toHaveCount(2); + await expect(demoChildRows.nth(0).locator("[data-game-child-table='source-idea'] caption")).toHaveText("Source Idea"); + const readinessOutputTable = demoChildRows.nth(1).locator("[data-game-child-table='readiness-output']"); await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output"); await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]); await expect(readinessOutputTable.locator("tbody tr")).toHaveText([ @@ -335,7 +333,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { } }); -test("Game Hub validates Open Games parent and child tables", async ({ page }) => { +test("Game Hub validates game parent rows and child tables", async ({ page }) => { const sourceLinkedGame = { id: "lantern-reef", ownerKey: MOCK_DB_KEYS.users.user1, @@ -422,8 +420,10 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() }); try { - await expect(page.locator("[data-game-parent-table='open-games'] caption")).toHaveText("Open Games"); - const parentRows = page.locator("[data-game-parent-table='open-games'] tbody > [data-game-row]"); + await expect(page.locator("[data-game-parent-table='open-games']")).toHaveCount(0); + await expect(page.locator("[data-game-rows-table='true']")).toHaveAttribute("aria-label", "Games"); + await expect(page.locator("[data-game-rows-table='true'] caption")).toHaveCount(0); + const parentRows = page.locator("[data-game-rows-table='true'] tbody > [data-game-row]"); await expect(parentRows).toHaveCount(1); const gameRow = page.locator("[data-game-row='lantern-reef']"); await expect(gameRow).toContainText("Lantern Reef"); @@ -431,11 +431,14 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = await gameRow.locator("[data-game-toggle='lantern-reef']").click(); await expect(gameRow.locator("[data-game-toggle='lantern-reef']")).toHaveAttribute("aria-expanded", "true"); - const expandedRow = page.locator("[data-game-expanded-row='lantern-reef']"); - await expect(expandedRow).toHaveCount(1); - await expect(expandedRow.locator("[data-game-child-table]")).toHaveCount(3); - - const sourceIdeaTable = expandedRow.locator("[data-game-child-table='source-idea']"); + const expandedRows = page.locator("[data-game-expanded-row='lantern-reef']"); + await expect(expandedRows).toHaveCount(2); + await expect(expandedRows.nth(0)).toHaveAttribute("data-game-child-row", "source-idea"); + await expect(expandedRows.nth(1)).toHaveAttribute("data-game-child-row", "readiness-output"); + await expect(expandedRows.locator("[data-game-child-table]")).toHaveCount(2); + await expect(expandedRows.locator("[data-game-child-table='summary']")).toHaveCount(0); + + const sourceIdeaTable = expandedRows.nth(0).locator("[data-game-child-table='source-idea']"); await expect(sourceIdeaTable.locator("caption")).toHaveText("Source Idea"); await expect(sourceIdeaTable.locator("tbody tr")).toHaveText([ "IdeaLantern Reef", @@ -446,7 +449,7 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = await expect(sourceIdeaTable.locator("button, input, textarea, select, [contenteditable='true'], [role='button']")).toHaveCount(0); await expect(sourceIdeaTable).not.toContainText(/Edit|Delete|Current Focus|Recommended Next Tool/); - const readinessOutputTable = expandedRow.locator("[data-game-child-table='readiness-output']"); + const readinessOutputTable = expandedRows.nth(1).locator("[data-game-child-table='readiness-output']"); await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output"); await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]); await expect(readinessOutputTable).not.toContainText(/Guide reef keepers|Keep traversal gentle|Use warm lantern art/); diff --git a/toolbox/game-hub/game-hub.js b/toolbox/game-hub/game-hub.js index 4b21814d7..56e5de4aa 100644 --- a/toolbox/game-hub/game-hub.js +++ b/toolbox/game-hub/game-hub.js @@ -241,36 +241,11 @@ function createGameToggleButton(game, expanded) { button.type = "button"; button.dataset.gameToggle = game.id; button.setAttribute("aria-expanded", String(expanded)); - button.setAttribute("aria-controls", `game-child-${game.id}`); + button.setAttribute("aria-controls", `game-child-source-idea-${game.id} game-child-readiness-output-${game.id}`); button.textContent = game.name; return button; } -function renderGameSummaryChildTable(parent, game) { - const wrapper = document.createElement("div"); - wrapper.className = "table-wrapper"; - const table = document.createElement("table"); - table.className = "data-table data-table--fixed"; - table.dataset.gameChildTable = "summary"; - table.setAttribute("aria-label", `${game.name} game summary`); - table.innerHTML = "Game SummaryFieldValue"; - const body = document.createElement("tbody"); - [ - ["Project", game.name], - ["Purpose", game.purpose], - ["Status", game.status], - ["Owner", game.ownerDisplayName], - ].forEach(([label, value]) => { - const row = document.createElement("tr"); - row.append(createCell(label, "th"), createCell(value || "Not set")); - row.firstElementChild.scope = "row"; - body.append(row); - }); - table.append(body); - wrapper.append(table); - parent.append(wrapper); -} - function gameSourceIdeaDetails(game) { const sourceIdea = isRecord(game?.sourceIdea) ? game.sourceIdea : null; const name = String(sourceIdea?.idea || "").trim(); @@ -366,19 +341,28 @@ function renderReadinessOutputChildTable(parent, game, progress, active) { } function renderExpandedGameRow(tbody, game, progress, active) { - const row = document.createElement("tr"); - row.dataset.gameExpandedRow = game.id; - row.id = `game-child-${game.id}`; - const content = document.createElement("td"); - content.colSpan = 5; - const stack = document.createElement("div"); - stack.className = "content-stack content-stack--compact"; - renderGameSummaryChildTable(stack, game); - renderSourceIdeaChildTable(stack, game); - renderReadinessOutputChildTable(stack, game, progress, active); - content.append(stack); - row.append(content); - tbody.append(row); + [ + { + id: `game-child-source-idea-${game.id}`, + render: (parent) => renderSourceIdeaChildTable(parent, game), + type: "source-idea", + }, + { + id: `game-child-readiness-output-${game.id}`, + render: (parent) => renderReadinessOutputChildTable(parent, game, progress, active), + type: "readiness-output", + }, + ].forEach(({ id, render, type }) => { + const row = document.createElement("tr"); + row.dataset.gameExpandedRow = game.id; + row.dataset.gameChildRow = type; + row.id = id; + const content = document.createElement("td"); + content.colSpan = 5; + render(content); + row.append(content); + tbody.append(row); + }); } function renderGameParentRow(tbody, game, activeGame, progress) { @@ -465,9 +449,9 @@ function renderGameList(progress) { wrapper.className = "table-wrapper"; const table = document.createElement("table"); table.className = "data-table data-table--fixed"; - table.dataset.gameParentTable = "open-games"; - table.setAttribute("aria-label", "Open Games"); - table.innerHTML = "Open GamesGamePurposeStatusOwnerActions"; + table.dataset.gameRowsTable = "true"; + table.setAttribute("aria-label", "Games"); + table.innerHTML = "GamePurposeStatusOwnerActions"; const body = document.createElement("tbody"); listResult.forEach((game) => renderGameParentRow(body, game, activeGame, progress)); table.append(body); From 9d1e9300fc581df607b79147e7c9cd0aa8419b84 Mon Sep 17 00:00:00 2001 From: DavidQ Date: Mon, 22 Jun 2026 18:51:00 -0400 Subject: [PATCH 2/4] PR_26174_ALFA_013-align-game-hub-with-table-first-ui --- ...-game-row-child-rows-branch-validation.txt | 3 +- ...row-child-rows-manual-validation-notes.txt | 12 +- ...e-row-child-rows-requirement-checklist.txt | 14 +- ...ub-game-row-child-rows-validation-lane.txt | 6 +- ...4_ALFA_013-game-hub-game-row-child-rows.md | 11 +- .../dev/reports/codex_changed_files.txt | 2 + docs_build/dev/reports/codex_review.diff | 282 +++++++++++++++++- .../tools/GameHubMockRepository.spec.mjs | 57 ++-- .../tools/IdeaBoardTableNotes.spec.mjs | 30 +- toolbox/game-hub/game-hub.js | 23 -- toolbox/game-hub/index.html | 57 +--- 11 files changed, 348 insertions(+), 149 deletions(-) diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt index c2dc3d048..f96e9cba6 100644 --- a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-branch-validation.txt @@ -2,5 +2,6 @@ Branch Validation: PASS PASS - Current branch is pr/26174-ALFA-013-game-hub-game-row-child-rows. PASS - Expected branch is pr/26174-ALFA-013-game-hub-game-row-child-rows. -PASS - Branch stacks on the current Team Alfa PR_012 branch. +PASS - Branch remains stacked on PR_26174_ALFA_012. +PASS - Owner-updated docs_build/dev/ProjectInstructions/addendums/table_first_ui.md was reviewed as instruction content and left unstaged. PASS - Work stayed scoped to Game Hub row rendering, targeted Playwright expectations, and required reports. diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt index 4b52998f2..621a6b370 100644 --- a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-manual-validation-notes.txt @@ -1,9 +1,9 @@ Manual Validation Notes: PASS -- Confirmed the Game Hub table uses game rows as the parent rows. -- Confirmed the previous Open Games table identity and caption are absent. -- Confirmed expanding a game creates exactly two child rows. -- Confirmed Source Idea appears before Readiness Output. -- Confirmed the old Game Summary child table is absent. +- Reviewed docs_build/dev/ProjectInstructions/addendums/table_first_ui.md as the owner-updated table-first reference. +- Compared Game Hub against the Idea Board parent row plus expanded child row pattern. +- Confirmed Game Hub no longer renders standalone Source Idea, Game Foundation, or Readiness Output panel/card sections. +- Confirmed expanding a game creates exactly two child rows: Source Idea, then Readiness Output. +- Confirmed Source Idea remains read-only and the readiness rows use existing service-contract output. - Confirmed safe empty and unavailable state checks still pass. -- Confirmed Idea Board-created Source Idea context still appears in Game Hub. +- Confirmed Idea Board-created Source Idea context still appears in Game Hub through the child table after reload. diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt index 32ffbbc22..75457765d 100644 --- a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-requirement-checklist.txt @@ -1,11 +1,13 @@ Requirement Checklist: PASS -PASS - Open Games is no longer used as the wrapper/accordion table identity. -PASS - Parent rows are games. -PASS - Each game row expands/collapses. -PASS - Expanded game rows show Source Idea child table first. -PASS - Expanded game rows show Readiness Output child table second. -PASS - Source Idea and Readiness Output render as child rows under the game parent row. +PASS - table_first_ui.md was used as the parent/child table reference. +PASS - Game row is the parent row. +PASS - Source Idea is a child row/table under the game row. +PASS - Readiness Output is a child row/table under the game row. +PASS - Source Idea and Readiness Output are no longer separate card/panel sections. +PASS - Open Games wrapper/table identity is not used. +PASS - Game rows expand/collapse. +PASS - Expanded game rows show Source Idea first and Readiness Output second. PASS - No new readiness math was added. PASS - Existing API/service contract was preserved. PASS - Safe empty/unavailable states were preserved by targeted validation. diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt index 306ea16e5..65b959efa 100644 --- a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows-validation-lane.txt @@ -1,11 +1,11 @@ Validation Lane: PASS Commands: -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" -Result: 4 passed. +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|Game Hub readiness child rows update from mock game state" +Result: 5 passed. 2. npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -g "Idea Board uses accordion table ideas and notes" Result: 1 passed. -3. git diff --check +3. git diff --check -- toolbox/game-hub/index.html toolbox/game-hub/game-hub.js tests/playwright/tools/GameHubMockRepository.spec.mjs tests/playwright/tools/IdeaBoardTableNotes.spec.mjs Result: PASS. diff --git a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md index d8919aa9d..0de9450f9 100644 --- a/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md +++ b/docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md @@ -2,17 +2,18 @@ ## Purpose -Correct Game Hub so each game is the parent row and Source Idea plus Readiness Output render as child rows/child tables. +Correct Game Hub so each game is the parent row and Source Idea plus Readiness Output render only as child rows/child tables under that game row. ## Summary +- Applied the updated table_first_ui.md guidance and matched the Idea Board parent-row/expanded-child-row pattern. - Removed the Open Games wrapper/table identity from the Game Hub list table. -- Removed the Game Summary child table from expanded game rows. -- Rendered each expanded game as two child rows in this order: Source Idea, Readiness Output. +- Removed standalone Source Idea, Game Foundation, and Readiness Output panel/card sections. +- Kept Source Idea and Readiness Output as the two expanded child rows under each game parent row. - Preserved the existing Game Hub API/service contract and safe empty/unavailable states. ## Validation -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"` +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|Game Hub readiness child rows update from mock game state"` PASS - `npx playwright test tests/playwright/tools/IdeaBoardTableNotes.spec.mjs -g "Idea Board uses accordion table ideas and notes"` -PASS - `git diff --check` +PASS - `git diff --check -- toolbox/game-hub/index.html toolbox/game-hub/game-hub.js tests/playwright/tools/GameHubMockRepository.spec.mjs tests/playwright/tools/IdeaBoardTableNotes.spec.mjs` diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt index c40eaf03d..d334f0da1 100644 --- a/docs_build/dev/reports/codex_changed_files.txt +++ b/docs_build/dev/reports/codex_changed_files.txt @@ -1,5 +1,7 @@ +toolbox/game-hub/index.html toolbox/game-hub/game-hub.js tests/playwright/tools/GameHubMockRepository.spec.mjs +tests/playwright/tools/IdeaBoardTableNotes.spec.mjs docs_build/dev/reports/codex_review.diff docs_build/dev/reports/codex_changed_files.txt docs_build/dev/reports/PR_26174_ALFA_013-game-hub-game-row-child-rows.md diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff index 9b3fba4e2..32c517f0b 100644 --- a/docs_build/dev/reports/codex_review.diff +++ b/docs_build/dev/reports/codex_review.diff @@ -1,8 +1,19 @@ diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs -index 4e617ed35..b89ff51fe 100644 +index 4e617ed35..7008e8bda 100644 --- a/tests/playwright/tools/GameHubMockRepository.spec.mjs +++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs -@@ -264,9 +264,10 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { +@@ -256,7 +256,9 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { + await expect(page.locator("[data-project-record-status]")).toHaveText("Project Information loaded."); + await expect(page.locator("[data-game-project-information]")).toContainText("Project Information"); + await expect(page.locator("[data-project-records-table]")).toContainText("Demo Game"); +- await expect(page.locator("[data-source-idea-section]")).toContainText("No source idea yet"); ++ await expect(page.locator("[data-source-idea-section]")).toHaveCount(0); ++ await expect(page.locator("[data-game-output-panels]")).toHaveCount(0); ++ await expect(page.locator("[data-game-hub-foundation]")).toHaveCount(0); + await expect(page.locator("[data-active-game-name]")).toHaveText("Demo Game"); + await expect(page.locator("[data-active-game-purpose]")).toHaveText("Game"); + await expect(page.locator("[data-current-user-role]")).toHaveText("Owner"); +@@ -264,9 +266,10 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { await expect(page.locator("[data-game-list]")).toContainText("Gravity Demo"); await expect(page.locator("[data-game-list]")).toContainText("Collision Demo"); await expect(page.locator("[data-game-list]")).toContainText("Camera Follow Demo"); @@ -16,7 +27,7 @@ index 4e617ed35..b89ff51fe 100644 "Game", "Purpose", "Status", -@@ -280,17 +281,14 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { +@@ -280,17 +283,14 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { await expect(demoGameRow.getByRole("button", { name: "Open Demo Game (Active)" })).toHaveAttribute("aria-current", "true"); await demoGameRow.locator("[data-game-toggle='demo-game']").click(); await expect(demoGameRow.locator("[data-game-toggle='demo-game']")).toHaveAttribute("aria-expanded", "true"); @@ -42,7 +53,7 @@ index 4e617ed35..b89ff51fe 100644 await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output"); await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]); await expect(readinessOutputTable.locator("tbody tr")).toHaveText([ -@@ -335,7 +333,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { +@@ -335,7 +335,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { } }); @@ -51,7 +62,7 @@ index 4e617ed35..b89ff51fe 100644 const sourceLinkedGame = { id: "lantern-reef", ownerKey: MOCK_DB_KEYS.users.user1, -@@ -422,8 +420,10 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = +@@ -422,8 +422,10 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() }); try { @@ -64,7 +75,7 @@ index 4e617ed35..b89ff51fe 100644 await expect(parentRows).toHaveCount(1); const gameRow = page.locator("[data-game-row='lantern-reef']"); await expect(gameRow).toContainText("Lantern Reef"); -@@ -431,11 +431,14 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = +@@ -431,11 +433,14 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = await gameRow.locator("[data-game-toggle='lantern-reef']").click(); await expect(gameRow.locator("[data-game-toggle='lantern-reef']")).toHaveAttribute("aria-expanded", "true"); @@ -84,7 +95,7 @@ index 4e617ed35..b89ff51fe 100644 await expect(sourceIdeaTable.locator("caption")).toHaveText("Source Idea"); await expect(sourceIdeaTable.locator("tbody tr")).toHaveText([ "IdeaLantern Reef", -@@ -446,7 +449,7 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = +@@ -446,7 +451,7 @@ test("Game Hub validates Open Games parent and child tables", async ({ page }) = await expect(sourceIdeaTable.locator("button, input, textarea, select, [contenteditable='true'], [role='button']")).toHaveCount(0); await expect(sourceIdeaTable).not.toContainText(/Edit|Delete|Current Focus|Recommended Next Tool/); @@ -93,11 +104,149 @@ index 4e617ed35..b89ff51fe 100644 await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output"); await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]); await expect(readinessOutputTable).not.toContainText(/Guide reef keepers|Keep traversal gentle|Use warm lantern art/); +@@ -681,46 +686,43 @@ test("Game Hub displays and edits game purpose and member role", async ({ page } + } + }); + +-test("Game Hub progress panels update from mock game state", async ({ page }) => { ++test("Game Hub readiness child rows update from mock game state", async ({ page }) => { + const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() }); + + try { +- await expect(page.locator("[data-game-status]")).toHaveText("Under Construction"); +- await expect(page.locator("[data-game-progress]")).toHaveText("Demo Game identity ready"); +- await expect(page.locator("[data-publishing-progress]")).toHaveText("Publish blocked until configuration and required assets are ready"); +- await expect(page.locator("[data-current-focus]")).toHaveText("Complete Game Configuration"); + await expect(page.locator("[data-recommended-next-tool]").first()).toHaveText("Game Configuration"); +- await expect(page.locator("[data-game-progress-checklist]")).toContainText("Game identity: Complete"); +- await expect(page.locator("[data-game-output-panels] summary")).toHaveText([ +- "Readiness Output" ++ await expect(page.locator("[data-source-idea-section]")).toHaveCount(0); ++ await expect(page.locator("[data-game-output-panels]")).toHaveCount(0); ++ await expect(page.locator("[data-game-hub-foundation]")).toHaveCount(0); ++ ++ const demoGameRow = page.locator("[data-game-row='demo-game']"); ++ await demoGameRow.locator("[data-game-toggle='demo-game']").click(); ++ let readinessOutputTable = page.locator("[data-game-expanded-row='demo-game'][data-game-child-row='readiness-output'] [data-game-child-table='readiness-output']"); ++ await expect(readinessOutputTable.locator("tbody tr")).toHaveText([ ++ "Game StatusUnder Construction", ++ "Game ProgressDemo Game identity ready", ++ "Launch ProgressPublish blocked until configuration and required assets are ready", ++ "Current FocusComplete Game Configuration", ++ "Recommended Next ToolGame Configuration", ++ "Game identityComplete", ++ "Game configurationUnder Construction", ++ "Playable buildPlanned", ++ "Publishing reviewPlanned", + ]); +- await expect(page.locator("aside.tool-column").last().getByText("Readiness Output")).toHaveCount(0); +- const panelOrderIsCorrect = await page.locator(".tool-center-panel").evaluate((panel) => { +- const projectInformation = panel.querySelector("[data-game-project-information]"); +- const sourceIdea = panel.querySelector("[data-source-idea-section]"); +- const staticOverlay = panel.querySelector("[data-game-hub-foundation]"); +- const outputPanels = panel.querySelector("[data-game-output-panels]"); +- return Boolean( +- projectInformation && +- sourceIdea && +- staticOverlay && +- outputPanels && +- (projectInformation.compareDocumentPosition(sourceIdea) & Node.DOCUMENT_POSITION_FOLLOWING) && +- (sourceIdea.compareDocumentPosition(staticOverlay) & Node.DOCUMENT_POSITION_FOLLOWING) && +- (staticOverlay.compareDocumentPosition(outputPanels) & Node.DOCUMENT_POSITION_FOLLOWING) +- ); +- }); +- expect(panelOrderIsCorrect).toBe(true); + + await page.getByLabel("Game Name").fill("Progress Review Game"); + await page.getByRole("button", { name: "Create Game" }).click(); +- await expect(page.locator("[data-game-status]")).toHaveText("Under Construction"); +- await expect(page.locator("[data-game-progress]")).toHaveText("Progress Review Game identity ready"); + await expect(page.locator("[data-game-project-information]")).toContainText("Progress Review Game"); ++ const progressReviewRow = page.locator("[data-game-row='progress-review-game-1']"); ++ await progressReviewRow.locator("[data-game-toggle='progress-review-game-1']").click(); ++ readinessOutputTable = page.locator("[data-game-expanded-row='progress-review-game-1'][data-game-child-row='readiness-output'] [data-game-child-table='readiness-output']"); ++ await expect(readinessOutputTable).toContainText("Progress Review Game identity ready"); + + await page.getByRole("button", { name: "Delete Open Game" }).click(); + await expect(page.locator("[data-active-game-name]")).toHaveText("Demo Game"); +- await expect(page.locator("[data-game-progress]")).toHaveText("Demo Game identity ready"); ++ await demoGameRow.locator("[data-game-toggle='demo-game']").click(); ++ readinessOutputTable = page.locator("[data-game-expanded-row='demo-game'][data-game-child-row='readiness-output'] [data-game-child-table='readiness-output']"); ++ await expect(readinessOutputTable).toContainText("Demo Game identity ready"); + + await expectNoPageFailures(failures); + } finally { +diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +index f861f1f44..2c7b70b5d 100644 +--- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs ++++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs +@@ -387,15 +387,18 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { + await expect(page.getByRole("heading", { level: 1, name: "Game Hub" })).toBeVisible(); + await expect(page.locator("[data-active-game-name]")).toHaveText("Lantern Reef"); + await expect(page.locator("[data-game-list]")).toContainText("Lantern Reef"); +- await expect(page.locator("[data-source-idea-display]")).toHaveText("Lantern Reef"); +- await expect(page.locator("[data-source-idea-pitch]")).toHaveText("Guide light through a reef that rearranges at dusk."); +- await expect(page.locator("[data-source-idea-notes]")).toContainText("Use dusk tide changes as the first Game Hub planning note."); +- await expect(page.locator("[data-source-idea-section] :is(input, textarea, select, button)")).toHaveCount(0); ++ await expect(page.locator("[data-source-idea-section]")).toHaveCount(0); ++ await expect(page.locator("[data-game-output-panels]")).toHaveCount(0); ++ await expect(page.locator("[data-game-hub-foundation]")).toHaveCount(0); + await expect(page.getByRole("button", { name: "Delete Open Game" })).toHaveCount(0); + const activeGameRow = page.locator("[data-game-row][data-game-active='true']"); + await expect(activeGameRow).toContainText("Lantern Reef"); + await activeGameRow.locator("[data-game-toggle]").click(); +- const sourceIdeaChildTable = page.locator("[data-game-expanded-row] [data-game-child-table='source-idea']"); ++ let expandedRows = page.locator("[data-game-expanded-row]"); ++ await expect(expandedRows).toHaveCount(2); ++ await expect(expandedRows.nth(0)).toHaveAttribute("data-game-child-row", "source-idea"); ++ await expect(expandedRows.nth(1)).toHaveAttribute("data-game-child-row", "readiness-output"); ++ let sourceIdeaChildTable = expandedRows.nth(0).locator("[data-game-child-table='source-idea']"); + await expect(sourceIdeaChildTable.locator("caption")).toHaveText("Source Idea"); + await expect(sourceIdeaChildTable.locator("thead th")).toHaveText(["Context", "Details"]); + await expect(sourceIdeaChildTable.locator("tbody tr")).toHaveText([ +@@ -404,13 +407,22 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => { + "Note 1Use dusk tide changes as the first Game Hub planning note.", + ]); + await expect(sourceIdeaChildTable.locator(":is(input, textarea, select, button)")).toHaveCount(0); ++ await expect(expandedRows.nth(1).locator("[data-game-child-table='readiness-output'] caption")).toHaveText("Readiness Output"); + await page.reload({ waitUntil: "networkidle" }); + await expect(page.locator("[data-active-game-name]")).toHaveText("Lantern Reef"); + await expect(page.locator("[data-game-list]")).toContainText("Lantern Reef"); +- await expect(page.locator("[data-source-idea-display]")).toHaveText("Lantern Reef"); +- await expect(page.locator("[data-source-idea-pitch]")).toHaveText("Guide light through a reef that rearranges at dusk."); +- await expect(page.locator("[data-source-idea-notes]")).toContainText("Use dusk tide changes as the first Game Hub planning note."); +- await expect(page.locator("[data-source-idea-section] :is(input, textarea, select, button)")).toHaveCount(0); ++ await expect(page.locator("[data-source-idea-section]")).toHaveCount(0); ++ await expect(page.locator("[data-game-output-panels]")).toHaveCount(0); ++ await expect(page.locator("[data-game-hub-foundation]")).toHaveCount(0); ++ await activeGameRow.locator("[data-game-toggle]").click(); ++ expandedRows = page.locator("[data-game-expanded-row]"); ++ await expect(expandedRows).toHaveCount(2); ++ sourceIdeaChildTable = expandedRows.nth(0).locator("[data-game-child-table='source-idea']"); ++ await expect(sourceIdeaChildTable.locator("tbody tr")).toHaveText([ ++ "IdeaLantern Reef", ++ "PitchGuide light through a reef that rearranges at dusk.", ++ "Note 1Use dusk tide changes as the first Game Hub planning note.", ++ ]); + await expect(page.getByRole("button", { name: "Delete Open Game" })).toHaveCount(0); + await expect(page.locator("main")).not.toContainText(/\bproject records\b|\bAPI\b|\bDB\b|\bmock\b|\bseed\b|\bdebug\b|\binternal\b/i); + await page.getByRole("link", { name: "Open Game Journey" }).click(); diff --git a/toolbox/game-hub/game-hub.js b/toolbox/game-hub/game-hub.js -index 4b21814d7..56e5de4aa 100644 +index 4b21814d7..029936527 100644 --- a/toolbox/game-hub/game-hub.js +++ b/toolbox/game-hub/game-hub.js -@@ -241,36 +241,11 @@ function createGameToggleButton(game, expanded) { +@@ -27,10 +27,6 @@ const elements = { + projectRecordStatus: document.querySelector("[data-project-record-status]"), + projectRecordsTable: document.querySelector("[data-project-records-table]"), + purposeInput: document.querySelector("[data-game-purpose-input]"), +- sourceIdeaDisplay: document.querySelector("[data-source-idea-display]"), +- sourceIdeaName: document.querySelector("[data-source-idea-name]"), +- sourceIdeaNotes: document.querySelector("[data-source-idea-notes]"), +- sourceIdeaPitch: document.querySelector("[data-source-idea-pitch]"), + gameStatus: document.querySelector("[data-game-status]"), + gameStatusInput: document.querySelector("[data-game-status-input]"), + publishingProgress: document.querySelector("[data-publishing-progress]"), +@@ -241,36 +237,11 @@ function createGameToggleButton(game, expanded) { button.type = "button"; button.dataset.gameToggle = game.id; button.setAttribute("aria-expanded", String(expanded)); @@ -135,7 +284,7 @@ index 4b21814d7..56e5de4aa 100644 function gameSourceIdeaDetails(game) { const sourceIdea = isRecord(game?.sourceIdea) ? game.sourceIdea : null; const name = String(sourceIdea?.idea || "").trim(); -@@ -366,19 +341,28 @@ function renderReadinessOutputChildTable(parent, game, progress, active) { +@@ -366,19 +337,28 @@ function renderReadinessOutputChildTable(parent, game, progress, active) { } function renderExpandedGameRow(tbody, game, progress, active) { @@ -177,7 +326,7 @@ index 4b21814d7..56e5de4aa 100644 } function renderGameParentRow(tbody, game, activeGame, progress) { -@@ -465,9 +449,9 @@ function renderGameList(progress) { +@@ -465,9 +445,9 @@ function renderGameList(progress) { wrapper.className = "table-wrapper"; const table = document.createElement("table"); table.className = "data-table data-table--fixed"; @@ -190,3 +339,114 @@ index 4b21814d7..56e5de4aa 100644 const body = document.createElement("tbody"); listResult.forEach((game) => renderGameParentRow(body, game, activeGame, progress)); table.append(body); +@@ -539,24 +519,6 @@ function renderTableCounts() { + }); + } + +-function renderSourceIdea(activeGame) { +- const sourceIdea = gameSourceIdeaDetails(activeGame); +- +- setText(elements.sourceIdeaName, sourceIdea.name || "No source idea yet"); +- setText(elements.sourceIdeaDisplay, sourceIdea.name || "No source idea yet"); +- setText(elements.sourceIdeaPitch, sourceIdea.pitch || "Create a project from Idea Board to see source details."); +- +- if (elements.sourceIdeaNotes) { +- elements.sourceIdeaNotes.replaceChildren(); +- const visibleNotes = sourceIdea.notes.length ? sourceIdea.notes : ["No source notes."]; +- visibleNotes.forEach((note) => { +- const item = document.createElement("li"); +- item.textContent = note; +- elements.sourceIdeaNotes.append(item); +- }); +- } +-} +- + function renderChecklist(progress) { + if (!elements.progressChecklist) { + return; +@@ -611,7 +573,6 @@ function renderWorkspace() { + renderTableCounts(); + renderChecklist(progress); + renderProjectInformation(activeGame, currentMember, progress); +- renderSourceIdea(activeGame); + refreshSaveControls(activeGame); + } + +diff --git a/toolbox/game-hub/index.html b/toolbox/game-hub/index.html +index ef7b50004..8f209db21 100644 +--- a/toolbox/game-hub/index.html ++++ b/toolbox/game-hub/index.html +@@ -67,7 +67,7 @@ + +
+

Project Information

+-

Review the open project and its source idea.

++

Review the open project, then expand its game row to see Source Idea and Readiness Output.

+
Project Information ready.
+ +-
+-
+-
+-
Source Idea
+-

No source idea yet

+-
+-
+- +- +- +- +- +- +-
IdeaNo source idea yet
PitchCreate a project from Idea Board to see source details.
Notes
  • No source notes.
+-
+-
+-
+-
+-
+-
+-
Game Foundation
+-

Game Progress

+-
+-
+-

Game Status

Under Construction

+-

Game Progress

Demo Game identity ready

+-

Launch Progress

Publish blocked until configuration and required assets are ready

+-
+-
+-

Current Focus

Complete Game Configuration

+-

Recommended Next Tool

Game Configuration

+-

Checklist

  • Game identity: Complete
+-
+
Game Hub ready.
+
+
+-
+-
+- Readiness Output +-
+-
+- +- +- +- +- +- +- +- +- +- +-
Readiness output
PathStatusNext Tool
PlanUnder ConstructionGame Configuration
ConfigurePlannedBuild Game
ReleasePlannedPublish
+-
+-
+-
+-
+
+

Project Information

-

Review the open project and its source idea.

+

Review the open project, then expand its game row to see Source Idea and Readiness Output.

Project Information ready.
-
-
-
-
Source Idea
-

No source idea yet

-
-
- - - - - - -
IdeaNo source idea yet
PitchCreate a project from Idea Board to see source details.
Notes
  • No source notes.
-
-
-
-
-
-
-
Game Foundation
-

Game Progress

-
-
-

Game Status

Under Construction

-

Game Progress

Demo Game identity ready

-

Launch Progress

Publish blocked until configuration and required assets are ready

-
-
-

Current Focus

Complete Game Configuration

-

Recommended Next Tool

Game Configuration

-

Checklist

  • Game identity: Complete
-
Game Hub ready.
-
-
- Readiness Output -
-
- - - - - - - - - - -
Readiness output
PathStatusNext Tool
PlanUnder ConstructionGame Configuration
ConfigurePlannedBuild Game
ReleasePlannedPublish
-
-
-
-

Project Information

@@ -385,7 +472,7 @@ index ef7b50004..8f209db21 100644
Project Information ready.
-@@ -99,64 +99,9 @@ +@@ -99,64 +94,9 @@ diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs index 7008e8bda..ae1de99c4 100644 --- a/tests/playwright/tools/GameHubMockRepository.spec.mjs +++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs @@ -266,6 +266,7 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => { await expect(page.locator("[data-game-list]")).toContainText("Gravity Demo"); await expect(page.locator("[data-game-list]")).toContainText("Collision Demo"); await expect(page.locator("[data-game-list]")).toContainText("Camera Follow Demo"); + await expect(page.locator("summary").filter({ hasText: /^Open Games$/ })).toHaveCount(0); await expect(page.locator("[data-game-parent-table='open-games']")).toHaveCount(0); await expect(page.locator("[data-game-rows-table='true']")).toHaveAttribute("aria-label", "Games"); await expect(page.locator("[data-game-rows-table='true'] caption")).toHaveCount(0); @@ -422,6 +423,7 @@ test("Game Hub validates game parent rows and child tables", async ({ page }) => const failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() }); try { + await expect(page.locator("summary").filter({ hasText: /^Open Games$/ })).toHaveCount(0); await expect(page.locator("[data-game-parent-table='open-games']")).toHaveCount(0); await expect(page.locator("[data-game-rows-table='true']")).toHaveAttribute("aria-label", "Games"); await expect(page.locator("[data-game-rows-table='true'] caption")).toHaveCount(0); diff --git a/toolbox/game-hub/index.html b/toolbox/game-hub/index.html index 8f209db21..538a8f0bc 100644 --- a/toolbox/game-hub/index.html +++ b/toolbox/game-hub/index.html @@ -57,12 +57,7 @@

Game Hub

-
- Open Games -
-
-
-
+