Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
Branch Validation: PASS

PASS - Current branch is pr/26174-ALFA-012-game-hub-parent-child-final-validation.
PASS - Expected branch is pr/26174-ALFA-012-game-hub-parent-child-final-validation.
PASS - Continued stack from PR_26174_ALFA_011 branch lineage.
PASS - Work stayed scoped to targeted Game Hub Playwright validation and required reports.
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
Manual Validation Notes: PASS

- Reviewed the expanded Open Games row behavior through targeted Playwright assertions.
- Confirmed Source Idea and Readiness Output use distinct child-table selectors and captions.
- Confirmed Source Idea child context exposes no form controls, buttons, or editable regions.
- Confirmed readiness checklist rows preserve the incoming service-contract order.
- Confirmed creator-safe empty and unavailable states still pass targeted validation.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Requirement Checklist: PASS

PASS - Open Games parent rows are validated.
PASS - Expanded game shows Source Idea child table.
PASS - Expanded game shows Readiness Output child table.
PASS - Source Idea is read-only with no edit/delete controls.
PASS - Readiness Output is separate from Source Idea.
PASS - Empty and unavailable states still pass targeted validation.
PASS - Added targeted Playwright coverage only.
PASS - No unrelated cleanup was introduced.
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
Validation Lane: PASS

Command:
npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub validates Open Games parent and child tables|Game Hub shows a creator-safe empty state|Game Hub shows a creator-safe unavailable state"

Result:
3 passed.

Additional check:
git diff --check
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# PR_26174_ALFA_012-game-hub-parent-child-final-validation

## Purpose

Final validation and report pass for the Game Hub parent/child table stack.

## Summary

- Added targeted Playwright coverage for Open Games parent rows and expanded child tables.
- Validated Source Idea and Readiness Output render as separate child tables.
- Validated Source Idea context is read-only and readiness output preserves incoming checklist order.
- Re-ran targeted empty and unavailable state checks.

## Validation

PASS - `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub validates Open Games parent and child tables|Game Hub shows a creator-safe empty state|Game Hub shows a creator-safe unavailable state"`
11 changes: 5 additions & 6 deletions docs_build/dev/reports/codex_changed_files.txt
Original file line number Diff line number Diff line change
@@ -1,9 +1,8 @@
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_011-game-hub-readiness-output-child-table.md
docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-branch-validation.txt
docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-requirement-checklist.txt
docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-validation-lane.txt
docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-manual-validation-notes.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
268 changes: 127 additions & 141 deletions docs_build/dev/reports/codex_review.diff
Original file line number Diff line number Diff line change
@@ -1,149 +1,135 @@
diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs
index 695b0a609..dac65c676 100644
index dac65c676..4e617ed35 100644
--- a/tests/playwright/tools/GameHubMockRepository.spec.mjs
+++ b/tests/playwright/tools/GameHubMockRepository.spec.mjs
@@ -288,6 +288,22 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
"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']");
+ await expect(readinessOutputTable.locator("caption")).toHaveText("Readiness Output");
+ await expect(readinessOutputTable.locator("thead th")).toHaveText(["Output", "Status"]);
+ 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 demoGameRow.locator("[data-game-toggle='demo-game']").click();
await expect(page.locator("[data-game-expanded-row='demo-game']")).toHaveCount(0);

diff --git a/toolbox/game-hub/game-hub.js b/toolbox/game-hub/game-hub.js
index 67f56ca41..4b21814d7 100644
--- a/toolbox/game-hub/game-hub.js
+++ b/toolbox/game-hub/game-hub.js
@@ -319,7 +319,53 @@ function renderSourceIdeaChildTable(parent, game) {
parent.append(wrapper);
}
@@ -335,6 +335,131 @@ test("Game Hub creates, opens, and deletes mock games", async ({ page }) => {
}
});

-function renderExpandedGameRow(tbody, game) {
+function renderReadinessOutputChildTable(parent, game, progress, active) {
+ const readiness = active && isRecord(progress)
+ ? progress
+ : {
+ currentFocus: "Open this game to review readiness",
+ gameProgress: `${game.name} identity ready`,
+ gameStatus: game.status || "No status",
+ publishingProgress: "Open this game to review launch progress",
+ recommendedNextTool: "Game Hub",
+ progressChecklist: [],
+ };
+ const wrapper = document.createElement("div");
+ wrapper.className = "table-wrapper";
+ const table = document.createElement("table");
+ table.className = "data-table data-table--fixed";
+ table.dataset.gameChildTable = "readiness-output";
+ table.setAttribute("aria-label", `${game.name} readiness output`);
+ table.innerHTML = "<caption>Readiness Output</caption><thead><tr><th scope=\"col\">Output</th><th scope=\"col\">Status</th></tr></thead>";
+ const body = document.createElement("tbody");
+ [
+ ["Game Status", readiness.gameStatus],
+ ["Game Progress", readiness.gameProgress],
+ ["Launch Progress", readiness.publishingProgress],
+ ["Current Focus", readiness.currentFocus],
+ ["Recommended Next Tool", readiness.recommendedNextTool],
+ ].forEach(([label, value]) => {
+ const row = document.createElement("tr");
+ row.append(createCell(label, "th"), createCell(value || "Not available"));
+ row.firstElementChild.scope = "row";
+ body.append(row);
+ });
+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",
+ ];
+
+ const checklist = Array.isArray(readiness.progressChecklist) ? readiness.progressChecklist : [];
+ checklist.forEach((item) => {
+ const row = document.createElement("tr");
+ row.dataset.readinessChecklistRow = item.label || "Checklist";
+ row.append(createCell(item.label || "Checklist", "th"), createCell(item.status || "Not available"));
+ row.firstElementChild.scope = "row";
+ body.append(row);
+ 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 failures = await openRepoPage(page, "/toolbox/game-hub/index.html", { session: creatorSession() });
+
+ table.append(body);
+ wrapper.append(table);
+ parent.append(wrapper);
+}
+ 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");
+
+function renderExpandedGameRow(tbody, game, progress, active) {
const row = document.createElement("tr");
row.dataset.gameExpandedRow = game.id;
row.id = `game-child-${game.id}`;
@@ -329,16 +375,18 @@ function renderExpandedGameRow(tbody, game) {
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);
}

-function renderGameParentRow(tbody, game, activeGame) {
+function renderGameParentRow(tbody, game, activeGame, progress) {
const expanded = state.expandedGameId === game.id;
+ const active = activeGame?.id === game.id;
const row = document.createElement("tr");
row.dataset.gameRow = game.id;
- if (activeGame?.id === game.id) {
+ if (active) {
row.dataset.gameActive = "true";
}

@@ -353,12 +401,12 @@ function renderGameParentRow(tbody, game, activeGame) {
);

const actions = document.createElement("td");
- actions.append(createGameButton(game, activeGame?.id === game.id));
+ actions.append(createGameButton(game, active));
row.append(actions);
tbody.append(row);

if (expanded) {
- renderExpandedGameRow(tbody, game);
+ renderExpandedGameRow(tbody, game, progress, active);
}
}

@@ -389,7 +437,7 @@ function renderProjectInformation(activeGame, currentMember, progress) {
: "Project Information loaded. Sign in to save changes.");
}

-function renderGameList() {
+function renderGameList(progress) {
if (!elements.gameList) {
return;
}
@@ -421,7 +469,7 @@ function renderGameList() {
table.setAttribute("aria-label", "Open Games");
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>";
const body = document.createElement("tbody");
- listResult.forEach((game) => renderGameParentRow(body, game, activeGame));
+ listResult.forEach((game) => renderGameParentRow(body, game, activeGame, progress));
table.append(body);
wrapper.append(table);
elements.gameList.append(wrapper);
@@ -558,7 +606,7 @@ function renderWorkspace() {
}
}

- renderGameList();
+ renderGameList(progress);
renderMembersTable(activeGame);
renderTableCounts();
renderChecklist(progress);
+ 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");
Loading
Loading