diff --git a/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-branch-validation.txt b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-branch-validation.txt
new file mode 100644
index 000000000..bfd6b0fd6
--- /dev/null
+++ b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-branch-validation.txt
@@ -0,0 +1,6 @@
+Branch Validation: PASS
+
+PASS - Current branch: pr/26174-ALFA-011-game-hub-readiness-output-child-table.
+PASS - Stack base: pr/26174-ALFA-010-game-hub-source-idea-child-table-polish.
+PASS - Changes are scoped to Readiness Output child table rendering, targeted tests, and required reports.
+PASS - No merge to main performed.
diff --git a/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-manual-validation-notes.txt b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-manual-validation-notes.txt
new file mode 100644
index 000000000..0cd0ad7dc
--- /dev/null
+++ b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-manual-validation-notes.txt
@@ -0,0 +1,6 @@
+Manual Validation Notes: PASS
+
+PASS - Confirmed expanded Demo Game row renders three separate child tables.
+PASS - Confirmed Readiness Output caption and columns are separate from Source Idea.
+PASS - Confirmed readiness rows use existing Game Status, Game Progress, Launch Progress, Current Focus, Recommended Next Tool, and checklist data.
+PASS - Confirmed no Game Journey bucket order files/constants were modified.
diff --git a/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-requirement-checklist.txt b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-requirement-checklist.txt
new file mode 100644
index 000000000..867afa6d0
--- /dev/null
+++ b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-requirement-checklist.txt
@@ -0,0 +1,8 @@
+Requirement Checklist: PASS
+
+PASS - Readiness Output appears as its own child table under the same game parent row.
+PASS - Readiness Output stays separate from Source Idea.
+PASS - Displays existing readiness/progress output only.
+PASS - No new readiness scoring/math was added.
+PASS - Existing Game Journey bucket order was preserved; no Game Journey bucket constants were changed.
+PASS - Existing API/service contract is preserved.
diff --git a/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-validation-lane.txt b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-validation-lane.txt
new file mode 100644
index 000000000..0cfcf92be
--- /dev/null
+++ b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table-validation-lane.txt
@@ -0,0 +1,3 @@
+Validation Lane: PASS
+
+PASS - npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub creates, opens, and deletes mock games"
diff --git a/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table.md b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table.md
new file mode 100644
index 000000000..230c62d2b
--- /dev/null
+++ b/docs_build/dev/reports/PR_26174_ALFA_011-game-hub-readiness-output-child-table.md
@@ -0,0 +1,16 @@
+# PR_26174_ALFA_011-game-hub-readiness-output-child-table
+
+## Purpose
+
+Make Readiness Output a separate child table under the expanded game row.
+
+## Summary
+
+- Added a separate Readiness Output child table to expanded Game Hub parent rows.
+- Kept Readiness Output separate from Source Idea and Game Summary child tables.
+- Displayed existing progress/readiness output fields and checklist rows only.
+- Preserved the existing Game Hub API/service contract and did not change Game Journey bucket ordering.
+
+## Validation
+
+PASS - `npx playwright test tests/playwright/tools/GameHubMockRepository.spec.mjs -g "Game Hub creates, opens, and deletes mock games"`
diff --git a/docs_build/dev/reports/codex_changed_files.txt b/docs_build/dev/reports/codex_changed_files.txt
index 6353fc8be..137f2a6f7 100644
--- a/docs_build/dev/reports/codex_changed_files.txt
+++ b/docs_build/dev/reports/codex_changed_files.txt
@@ -1,9 +1,9 @@
toolbox/game-hub/game-hub.js
-tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
+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_010-game-hub-source-idea-child-table-polish.md
-docs_build/dev/reports/PR_26174_ALFA_010-game-hub-source-idea-child-table-polish-branch-validation.txt
-docs_build/dev/reports/PR_26174_ALFA_010-game-hub-source-idea-child-table-polish-requirement-checklist.txt
-docs_build/dev/reports/PR_26174_ALFA_010-game-hub-source-idea-child-table-polish-validation-lane.txt
-docs_build/dev/reports/PR_26174_ALFA_010-game-hub-source-idea-child-table-polish-manual-validation-notes.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
diff --git a/docs_build/dev/reports/codex_review.diff b/docs_build/dev/reports/codex_review.diff
index d241bee0b..04ad8ebfe 100644
--- a/docs_build/dev/reports/codex_review.diff
+++ b/docs_build/dev/reports/codex_review.diff
@@ -1,73 +1,76 @@
-diff --git a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-index 722595ae9..f861f1f44 100644
---- a/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-+++ b/tests/playwright/tools/IdeaBoardTableNotes.spec.mjs
-@@ -392,6 +392,18 @@ test("Idea Board uses accordion table ideas and notes", async ({ page }) => {
- 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.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']");
-+ await expect(sourceIdeaChildTable.locator("caption")).toHaveText("Source Idea");
-+ await expect(sourceIdeaChildTable.locator("thead th")).toHaveText(["Context", "Details"]);
-+ 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.",
+diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs
+index 695b0a609..dac65c676 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 expect(sourceIdeaChildTable.locator(":is(input, textarea, select, button)")).toHaveCount(0);
- 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 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 121e82562..67f56ca41 100644
+index 67f56ca41..4b21814d7 100644
--- a/toolbox/game-hub/game-hub.js
+++ b/toolbox/game-hub/game-hub.js
-@@ -271,6 +271,54 @@ function renderGameSummaryChildTable(parent, game) {
+@@ -319,7 +319,53 @@ function renderSourceIdeaChildTable(parent, game) {
parent.append(wrapper);
}
-+function gameSourceIdeaDetails(game) {
-+ const sourceIdea = isRecord(game?.sourceIdea) ? game.sourceIdea : null;
-+ const name = String(sourceIdea?.idea || "").trim();
-+ const pitch = String(sourceIdea?.pitch || "").trim();
-+ const notes = Array.isArray(sourceIdea?.notes)
-+ ? sourceIdea.notes.map((note) => String(note || "").trim()).filter(Boolean)
-+ : [];
-+ return {
-+ name,
-+ notes,
-+ pitch,
-+ };
-+}
-+
-+function renderSourceIdeaChildTable(parent, game) {
-+ const sourceIdea = gameSourceIdeaDetails(game);
+-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 = "source-idea";
-+ table.setAttribute("aria-label", `${game.name} source idea`);
-+ table.innerHTML = "
Source Idea| Context | Details |
";
++ table.dataset.gameChildTable = "readiness-output";
++ table.setAttribute("aria-label", `${game.name} readiness output`);
++ table.innerHTML = "Readiness Output| Output | Status |
";
+ const body = document.createElement("tbody");
+ [
-+ ["Idea", sourceIdea.name || "No source idea yet"],
-+ ["Pitch", sourceIdea.pitch || "Create a project from Idea Board to see source details."],
++ ["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));
++ row.append(createCell(label, "th"), createCell(value || "Not available"));
+ row.firstElementChild.scope = "row";
+ body.append(row);
+ });
+
-+ const notes = sourceIdea.notes.length ? sourceIdea.notes : ["No source notes."];
-+ notes.forEach((note, index) => {
++ const checklist = Array.isArray(readiness.progressChecklist) ? readiness.progressChecklist : [];
++ checklist.forEach((item) => {
+ const row = document.createElement("tr");
-+ row.dataset.sourceIdeaNoteRow = String(index + 1);
-+ row.append(createCell(`Note ${index + 1}`, "th"), createCell(note));
++ 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);
+ });
@@ -77,40 +80,70 @@ index 121e82562..67f56ca41 100644
+ parent.append(wrapper);
+}
+
- function renderExpandedGameRow(tbody, game) {
++function renderExpandedGameRow(tbody, game, progress, active) {
const row = document.createElement("tr");
row.dataset.gameExpandedRow = game.id;
-@@ -280,6 +328,7 @@ function renderExpandedGameRow(tbody, game) {
- const stack = document.createElement("div");
+ 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);
+ renderSourceIdeaChildTable(stack, game);
++ renderReadinessOutputChildTable(stack, game, progress, active);
content.append(stack);
row.append(content);
tbody.append(row);
-@@ -443,20 +492,15 @@ function renderTableCounts() {
}
- function renderSourceIdea(activeGame) {
-- const sourceIdea = isRecord(activeGame?.sourceIdea) ? activeGame.sourceIdea : null;
-- const name = String(sourceIdea?.idea || "").trim();
-- const pitch = String(sourceIdea?.pitch || "").trim();
-- const notes = Array.isArray(sourceIdea?.notes)
-- ? sourceIdea.notes.map((note) => String(note || "").trim()).filter(Boolean)
-- : [];
-+ const sourceIdea = gameSourceIdeaDetails(activeGame);
+-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.");
+ }
-- setText(elements.sourceIdeaName, name || "No source idea yet");
-- setText(elements.sourceIdeaDisplay, name || "No source idea yet");
-- setText(elements.sourceIdeaPitch, pitch || "Create a project from Idea Board to see source details.");
-+ 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.");
+-function renderGameList() {
++function renderGameList(progress) {
+ if (!elements.gameList) {
+ return;
+ }
+@@ -421,7 +469,7 @@ function renderGameList() {
+ table.setAttribute("aria-label", "Open Games");
+ table.innerHTML = "Open Games| Game | Purpose | Status | Owner | Actions |
";
+ 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() {
+ }
+ }
- if (elements.sourceIdeaNotes) {
- elements.sourceIdeaNotes.replaceChildren();
-- const visibleNotes = notes.length ? notes : ["No source notes."];
-+ const visibleNotes = sourceIdea.notes.length ? sourceIdea.notes : ["No source notes."];
- visibleNotes.forEach((note) => {
- const item = document.createElement("li");
- item.textContent = note;
+- renderGameList();
++ renderGameList(progress);
+ renderMembersTable(activeGame);
+ renderTableCounts();
+ renderChecklist(progress);
diff --git a/tests/playwright/tools/GameHubMockRepository.spec.mjs b/tests/playwright/tools/GameHubMockRepository.spec.mjs
index 695b0a609..dac65c676 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);
}
-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 = "Readiness Output| Output | Status |
";
+ 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);
+ });
+
+ 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);
+ });
+
+ table.append(body);
+ wrapper.append(table);
+ parent.append(wrapper);
+}
+
+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 = "Open Games| Game | Purpose | Status | Owner | Actions |
";
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);