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: 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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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.
Original file line number Diff line number Diff line change
@@ -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"
Original file line number Diff line number Diff line change
@@ -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"`
12 changes: 6 additions & 6 deletions docs_build/dev/reports/codex_changed_files.txt
Original file line number Diff line number Diff line change
@@ -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
185 changes: 109 additions & 76 deletions docs_build/dev/reports/codex_review.diff
Original file line number Diff line number Diff line change
@@ -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 = "<caption>Source Idea</caption><thead><tr><th scope=\"col\">Context</th><th scope=\"col\">Details</th></tr></thead>";
+ 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");
+ [
+ ["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);
+ });
Expand All @@ -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 = "<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() {
}
}

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);
16 changes: 16 additions & 0 deletions tests/playwright/tools/GameHubMockRepository.spec.mjs
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand Down
Loading
Loading